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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
import { createHash, createHmac, timingSafeEqual } from 'node:crypto';
import type { Cookies } from '@sveltejs/kit';
import { env } from '$env/dynamic/private';
export const AUTH_COOKIE_NAME = 'home_auth';
export const SESSION_MAX_AGE_SECONDS = 60 * 60 * 24 * 7;
const SESSION_MAX_AGE_MS = SESSION_MAX_AGE_SECONDS * 1000;
function configuredPassword(): string {
if (!env.AUTH_PASSWORD) {
throw new Error('AUTH_PASSWORD is not configured');
}
return env.AUTH_PASSWORD;
}
function digest(value: string): Buffer {
return createHash('sha256').update(value).digest();
}
function sign(value: string): string {
return createHmac('sha256', configuredPassword())
.update(value)
.digest('base64url');
}
export function verifyPassword(candidate: string): boolean {
const expected = digest(configuredPassword());
const actual = digest(candidate);
return timingSafeEqual(actual, expected);
}
export function createSession(now = Date.now()): string {
const expiresAt = now + SESSION_MAX_AGE_MS;
const payload = String(expiresAt);
return `${payload}.${sign(payload)}`;
}
export function isValidSession(
value: string | undefined,
now = Date.now(),
): boolean {
if (!value) return false;
const [expiresAtText, signature, extra] = value.split('.');
if (!expiresAtText || !signature || extra) return false;
const expiresAt = Number(expiresAtText);
if (!Number.isSafeInteger(expiresAt) || expiresAt <= now) return false;
const expected = Buffer.from(sign(expiresAtText), 'base64url');
const actual = Buffer.from(signature, 'base64url');
if (actual.length !== expected.length) return false;
return timingSafeEqual(actual, expected);
}
export function renewSession(
cookies: Cookies,
secure: boolean,
now = Date.now(),
): boolean {
const current = cookies.get(AUTH_COOKIE_NAME);
if (!isValidSession(current, now)) return false;
cookies.set(AUTH_COOKIE_NAME, createSession(now), {
path: '/',
httpOnly: true,
secure,
sameSite: 'lax',
maxAge: SESSION_MAX_AGE_SECONDS,
});
return true;
}
export function setSession(
cookies: Cookies,
secure: boolean,
now = Date.now(),
): void {
cookies.set(AUTH_COOKIE_NAME, createSession(now), {
path: '/',
httpOnly: true,
secure,
sameSite: 'lax',
maxAge: SESSION_MAX_AGE_SECONDS,
});
}
|