import { randomBytes, scryptSync, timingSafeEqual } from 'node:crypto';
import { get, run } from './db.js';
const SCRYPT = { N: 16384, r: 8, p: 1, keylen: 64 };
const SESSION_DAYS = 30;
export const COOKIE = 'metube_session';
export function hashPassword(password) {
const salt = randomBytes(16).toString('hex');
const key = scryptSync(password, salt, SCRYPT.keylen, SCRYPT).toString('hex');
return `scrypt$${salt}$${key}`;
}
export function verifyPassword(password, stored) {
const [scheme, salt, key] = String(stored).split('$');
if (scheme !== 'scrypt' || !salt || !key) return false;
const expected = Buffer.from(key, 'hex');
const actual = scryptSync(password, salt, expected.length, SCRYPT);
return expected.length === actual.length && timingSafeEqual(expected, actual);
}
export function createSession(userId) {
const token = randomBytes(32).toString('base64url');
run(
`INSERT INTO sessions (token, user_id, expires_at)
VALUES (?, ?, datetime('now', '+${SESSION_DAYS} days'))`,
token, userId
);
return token;
}
export function destroySession(token) {
if (token) run('DELETE FROM sessions WHERE token = ?', token);
}
export function userForToken(token) {
if (!token) return null;
return get(
`SELECT u.* FROM sessions s
JOIN users u ON u.id = s.user_id
WHERE s.token = ? AND s.expires_at > datetime('now')`,
token
) ?? null;
}
export function parseCookies(header = '') {
const out = {};
for (const part of header.split(';')) {
const i = part.indexOf('=');
if (i < 0) continue;
out[part.slice(0, i).trim()] = decodeURIComponent(part.slice(i + 1).trim());
}
return out;
}
export function sessionCookie(token) {
const maxAge = SESSION_DAYS * 24 * 60 * 60;
return `${COOKIE}=${token}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${maxAge}`;
}
export const clearCookie = () => `${COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`;