Skip to content

Commit 434abab

Browse files
committed
refactor(oauth): simplify state management and enhance state generation for OAuth
1 parent 6a51313 commit 434abab

2 files changed

Lines changed: 20 additions & 44 deletions

File tree

server/src/services/user.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,32 @@ import base from "../base";
66
export const UserService = base()
77
.group('/user', (group) =>
88
group
9-
.get("/github", ({ oauth2, set, headers: { referer }, cookie: { redirect_to } }) => {
9+
.get("/github", ({ oauth2, set, headers: { referer }, cookie: { redirect_to, state } }) => {
1010
if (!referer) {
1111
return 'Referer not found'
1212
}
1313
redirect_to.value = `${referer}`
14-
set.headers['Location'] = oauth2.createRedirectUrl("GitHub")
14+
const genState = oauth2.generateState();
15+
// Store state in cookie to verify later
16+
state.value = genState;
17+
set.headers['Location'] = oauth2.createRedirectUrl(genState, "GitHub")
1518
set.status = 302
1619
})
17-
.get("/github/callback", async ({ jwt, oauth2, set, store, query, cookie: { token, redirect_to } }) => {
20+
.get("/github/callback", async ({ jwt, oauth2, set, store, query, cookie: { token, redirect_to, state } }) => {
1821
const { db } = store;
1922

20-
console.log('p_state', query.state)
23+
console.log('param_state', query.state)
24+
console.log('cookie_state', state.value)
2125

2226
// Verify state to prevent CSRF attacks
23-
if (!oauth2.verifyState(query.state)) {
27+
if (query.state != state.value) {
2428
set.status = 400;
2529
return 'Invalid state parameter';
2630
}
31+
// Clear state cookie
32+
state.value = '';
2733

28-
oauth2.removeState(query.state);
34+
// Exchange code for access token
2935
const gh_token = await oauth2.authorize("GitHub", query.code);
3036
if (!gh_token) {
3137
set.status = 400;

server/src/utils/oauth.ts

Lines changed: 8 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -49,49 +49,32 @@ export interface OAuth2Methods {
4949
}
5050

5151
export function createOAuthPlugin(providers: Record<string, OAuthProvider>) {
52-
const stateStore = new Map<string, { provider: string; timestamp: number }>();
53-
54-
// Clean up expired states (older than 10 minutes)
55-
const cleanupExpiredStates = () => {
56-
const now = Date.now();
57-
const expiryTime = 10 * 60 * 1000; // 10 minutes
58-
for (const [state, data] of stateStore.entries()) {
59-
if (now - data.timestamp > expiryTime) {
60-
stateStore.delete(state);
61-
}
62-
}
63-
};
6452

6553
// Generate random state string
66-
const generateState = (): string => {
67-
const array = new Uint8Array(32);
68-
crypto.getRandomValues(array);
69-
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
70-
};
54+
7155

7256
return new Elysia({ name: "oauth2" })
7357
.decorate("oauth2", {
74-
createRedirectUrl: (providerName: string): string => {
58+
generateState: () => {
59+
const array = new Uint8Array(32);
60+
crypto.getRandomValues(array);
61+
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
62+
},
63+
createRedirectUrl: (state: string, providerName: string): string => {
7564
const provider = providers[providerName];
7665
if (!provider) {
7766
throw new Error(`OAuth provider "${providerName}" not found`);
7867
}
7968

80-
cleanupExpiredStates();
8169

82-
const state = generateState();
83-
stateStore.set(state, {
84-
provider: providerName,
85-
timestamp: Date.now(),
86-
});
8770

8871
const params = new URLSearchParams({
8972
client_id: provider.clientId,
9073
state: state,
9174
});
9275

9376
// if (provider.redirectUri) {
94-
// params.set("redirect_uri", provider.redirectUri);
77+
// params.set("redirect_uri", provider.redirectUri);
9578
// }
9679

9780
return `${provider.authorizeUrl}?${params.toString()}`;
@@ -146,18 +129,5 @@ export function createOAuthPlugin(providers: Record<string, OAuthProvider>) {
146129
refreshToken: data.refresh_token,
147130
};
148131
},
149-
150-
verifyState: (state: string): boolean => {
151-
cleanupExpiredStates();
152-
return stateStore.has(state);
153-
},
154-
155-
getStateData: (state: string) => {
156-
return stateStore.get(state);
157-
},
158-
159-
removeState: (state: string) => {
160-
stateStore.delete(state);
161-
},
162132
});
163133
}

0 commit comments

Comments
 (0)