You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
79 lines
1.7 KiB
79 lines
1.7 KiB
import { randomUUID } from 'crypto';
|
|
|
|
export type OAuthState = {
|
|
providerName: string;
|
|
redirectUri: string;
|
|
createdAt: number;
|
|
userId?: number;
|
|
isBinding: boolean;
|
|
};
|
|
|
|
const STATE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
|
|
export class OAuthStateStore {
|
|
private store = new Map<string, OAuthState>();
|
|
|
|
private cleanupInterval: NodeJS.Timeout | null = null;
|
|
|
|
constructor() {
|
|
this.startCleanup();
|
|
}
|
|
|
|
generate(
|
|
providerName: string,
|
|
redirectUri: string,
|
|
userId?: number
|
|
): { state: string; oauthState: OAuthState } {
|
|
const state = randomUUID();
|
|
const oauthState: OAuthState = {
|
|
providerName,
|
|
redirectUri,
|
|
createdAt: Date.now(),
|
|
userId,
|
|
isBinding: userId !== undefined,
|
|
};
|
|
this.store.set(state, oauthState);
|
|
return { state, oauthState };
|
|
}
|
|
|
|
validate(state: string): OAuthState | null {
|
|
const oauthState = this.store.get(state);
|
|
if (!oauthState) {
|
|
return null;
|
|
}
|
|
|
|
if (Date.now() - oauthState.createdAt > STATE_TTL_MS) {
|
|
this.store.delete(state);
|
|
return null;
|
|
}
|
|
|
|
return oauthState;
|
|
}
|
|
|
|
consume(state: string): OAuthState | null {
|
|
const oauthState = this.validate(state);
|
|
if (oauthState) {
|
|
this.store.delete(state);
|
|
}
|
|
return oauthState;
|
|
}
|
|
|
|
private startCleanup() {
|
|
this.cleanupInterval = setInterval(() => {
|
|
const now = Date.now();
|
|
for (const [state, data] of this.store.entries()) {
|
|
if (now - data.createdAt > STATE_TTL_MS) {
|
|
this.store.delete(state);
|
|
}
|
|
}
|
|
}, 60_000);
|
|
}
|
|
|
|
destroy() {
|
|
if (this.cleanupInterval) {
|
|
clearInterval(this.cleanupInterval);
|
|
}
|
|
}
|
|
}
|
|
|
|
export const oauthStateStore = new OAuthStateStore();
|