Skip to content

Commit 5640103

Browse files
committed
feat: add refresh token handling and update session management
1 parent 269b417 commit 5640103

6 files changed

Lines changed: 66 additions & 2 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"dist"
99
],
1010
"license": "WTFPL",
11-
"version": "1.0.3",
11+
"version": "1.0.4",
1212
"type": "module",
1313
"keywords": [
1414
"auth",

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export interface Session {
77
};
88
expires: string;
99
accessToken?: string;
10+
refreshToken?: string;
1011
}
1112

1213
export interface Config {

src/lib/oauth.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,41 @@ export const ExchangeCodeForTokens = async (config: Config, code: string) => {
3838
expiresIn: data.expires_in,
3939
};
4040
};
41+
42+
export const RefreshAccessToken = async (config: Config, refreshToken: string) => {
43+
const response = await fetch("https://discord.com/api/oauth2/token", {
44+
method: "POST",
45+
headers: {
46+
"Content-Type": "application/x-www-form-urlencoded",
47+
},
48+
body: new URLSearchParams({
49+
client_id: config.clientId,
50+
client_secret: config.clientSecret,
51+
grant_type: "refresh_token",
52+
refresh_token: refreshToken,
53+
scope: config.scopes.join(" "),
54+
}),
55+
});
56+
57+
if (!response.ok) {
58+
const error = (await response.json()) as {
59+
error_description?: string;
60+
message?: string;
61+
};
62+
throw new Error(
63+
`Failed to refresh access token: ${error.error_description || error.message}`,
64+
);
65+
}
66+
67+
const data = (await response.json()) as {
68+
refresh_token: string;
69+
access_token: string;
70+
expires_in: number;
71+
};
72+
73+
return {
74+
accessToken: data.access_token,
75+
refreshToken: data.refresh_token || refreshToken,
76+
expiresIn: data.expires_in,
77+
};
78+
}

src/redirect.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ export const handleRedirect = async (req: NextRequest) => {
5353
avatar: `https://cdn.discordapp.com/avatars/${userData.id}/${userData.avatar}.png`
5454
},
5555
expires: new Date(Date.now() + response.expiresIn * 1000).toISOString(),
56+
accessToken: response.accessToken,
57+
refreshToken: response.refreshToken,
5658
};
5759

5860
const token = jwt.sign(session, config.jwtSecret, {

src/server-actions.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { headers } from "next/headers";
33
import { cookies } from "next/headers";
44
import { redirect } from "next/navigation";
55
import { type Session, getGlobalConfig } from "./index";
6+
import { RefreshAccessToken } from "./lib/oauth";
67
import jwt from "jsonwebtoken";
78

89
export const getSession = async (): Promise<Session | null> => {
@@ -12,7 +13,28 @@ export const getSession = async (): Promise<Session | null> => {
1213
if (!token) return null;
1314

1415
try {
15-
return jwt.verify(token, config.jwtSecret) as Session;
16+
const session = jwt.verify(token, config.jwtSecret) as Session;
17+
if (session.expires) {
18+
const expiresAt = new Date(session.expires);
19+
if (expiresAt < new Date()) {
20+
cookieStore.delete("AUTH_SESSION");
21+
return null;
22+
} else {
23+
const timeUntilExpiration = expiresAt.getTime() - Date.now();
24+
if (timeUntilExpiration < 5 * 60 * 1000) { // less than 5 minutes
25+
const refreshedSession = await RefreshAccessToken(config, session.refreshToken || "");
26+
if (refreshedSession) {
27+
session.accessToken = refreshedSession.accessToken;
28+
session.refreshToken = refreshedSession.refreshToken;
29+
session.expires = new Date(Date.now() + refreshedSession.expiresIn * 1000).toISOString();
30+
const newToken = jwt.sign(session, config.jwtSecret);
31+
cookieStore.set("AUTH_SESSION", newToken, { sameSite: "lax", httpOnly: true, secure: true });
32+
}
33+
}
34+
return session;
35+
}
36+
}
37+
return null;
1638
} catch (error) {
1739
console.error("Invalid token:", error);
1840
return null;

types/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export interface Session {
77
};
88
expires: string;
99
accessToken?: string;
10+
refreshToken?: string;
1011
}
1112

1213
export interface Config {

0 commit comments

Comments
 (0)