Spaces:
Running
Running
assafvayner HF Staff
fix(auth): bound scrypt params, cap concurrency, evict rate-limit keys, re-bootstrap admin
1c7b998 | import { createHmac, timingSafeEqual } from 'node:crypto'; | |
| export interface SessionPayload { | |
| uid: string; | |
| sv: number; | |
| /** Expiry, ms since epoch. */ | |
| exp: number; | |
| } | |
| export const SESSION_COOKIE = 'session'; | |
| export const SESSION_TTL_SECONDS = 30 * 24 * 60 * 60; | |
| function sign(data: string, secret: string): string { | |
| return createHmac('sha256', secret).update(data).digest('base64url'); | |
| } | |
| export function createSessionToken(payload: Omit<SessionPayload, 'exp'>, secret: string, now = new Date(), ttlSeconds = SESSION_TTL_SECONDS): string { | |
| const full: SessionPayload = { ...payload, exp: now.getTime() + ttlSeconds * 1000 }; | |
| const data = Buffer.from(JSON.stringify(full)).toString('base64url'); | |
| return `${data}.${sign(data, secret)}`; | |
| } | |
| /** | |
| * Verifies only the token's signature and expiry. This does NOT prove the session is still | |
| * valid: callers MUST also check that `payload.uid` refers to a user that still exists, is | |
| * active, and that `user.sessionVersion === payload.sv` (the hooks do this). | |
| */ | |
| export function verifySessionToken(token: string, secret: string, now = new Date()): SessionPayload | null { | |
| const dot = token.lastIndexOf('.'); | |
| if (dot <= 0) return null; | |
| const data = token.slice(0, dot); | |
| const sig = token.slice(dot + 1); | |
| const expected = sign(data, secret); | |
| const a = Buffer.from(sig); | |
| const b = Buffer.from(expected); | |
| if (a.length !== b.length || !timingSafeEqual(a, b)) return null; | |
| try { | |
| const parsed = JSON.parse(Buffer.from(data, 'base64url').toString()) as Partial<SessionPayload>; | |
| if (typeof parsed.uid !== 'string' || typeof parsed.sv !== 'number' || typeof parsed.exp !== 'number') return null; | |
| if (parsed.exp <= now.getTime()) return null; | |
| return { uid: parsed.uid, sv: parsed.sv, exp: parsed.exp }; | |
| } catch { | |
| return null; | |
| } | |
| } | |