File size: 1,793 Bytes
ef84ace
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1c7b998
 
 
 
 
ef84ace
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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;
	}
}