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

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();