import crypto from 'crypto'; import { Response } from 'express'; import { pool } from './db'; /** * Establishing a signed-in session, for every way of signing in. * * Lifted out of routes/customers.ts when passkey authentication arrived (#39), * which requires that a passkey sign-in "go through the same session creation as * password login, so cookie flags, expiry, and logout behave identically. A * second, subtly different session path is how auth bugs get in." * * Shared rather than copied is what makes that true rather than merely intended. * Two implementations that agree today are two implementations that can be * changed one at a time — and the one that would be forgotten is whichever is * not the password path, because that is the one every manual test exercises. * * Anything that establishes a session belongs here: password login, * registration, password reset, passkeys, and social sign-in when #332 lands. */ export const SESSION_DAYS = 30; const SESSION_MS = SESSION_DAYS * 24 * 60 * 60 * 1000; export function setSessionCookie(res: Response, token: string): void { res.cookie('rd_session', token, { httpOnly: true, // Gated on NODE_ENV rather than hardcoded true, or the integration tests — // plain HTTP, no TLS — would silently fail to persist a session and every // signed-in assertion would fail for a reason that looks unrelated. secure: process.env.NODE_ENV === 'production', sameSite: 'lax', maxAge: SESSION_MS }); } export async function createSession(customerId: number): Promise { const token = crypto.randomBytes(32).toString('hex'); const expiresAt = new Date(Date.now() + SESSION_MS); await pool.query( `INSERT INTO customer_sessions (token, customer_id, expires_at) VALUES ($1, $2, $3)`, [token, customerId, expiresAt] ); return token; } /** Mints a session and sets its cookie — the whole of "sign this customer in". */ export async function signIn(res: Response, customerId: number): Promise { setSessionCookie(res, await createSession(customerId)); }