import { createHmac, timingSafeEqual } from 'node:crypto'; import { terms } from '../shared/terms.mjs'; export const TERMS_COOKIE = 'juegoland_terms'; export const TERMS_MAX_AGE = 365 * 24 * 60 * 60; const signature = (payload, secret) => createHmac('sha256', secret) .update(`juegoland-terms:${payload}`) .digest('hex'); export function termsCookie(secret, production, now = Date.now()) { const payload = `${terms.version}.${Math.floor(now / 1000)}`; return `${TERMS_COOKIE}=${payload}.${signature(payload, secret)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${TERMS_MAX_AGE}${production ? '; Secure' : ''}`; } export function clearTermsCookie(production) { return `${TERMS_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0${production ? '; Secure' : ''}`; } export function termsAcceptance(header = '', secret, now = Date.now()) { const rejected = { accepted: false, version: terms.version, acceptedAt: null, expiresAt: null, }; const value = header .split(';') .map((part) => part.trim()) .find((part) => part.startsWith(`${TERMS_COOKIE}=`)) ?.slice(TERMS_COOKIE.length + 1); const match = value?.match( /^(\d{4}-\d{2}-\d{2}\.\d+)\.(\d{10})\.([a-f0-9]{64})$/, ); if (!match || match[1] !== terms.version) return rejected; const issuedAt = Number(match[2]) * 1000; if (issuedAt > now || now >= issuedAt + TERMS_MAX_AGE * 1000) return rejected; const expected = Buffer.from( signature(`${match[1]}.${match[2]}`, secret), 'hex', ); if (!timingSafeEqual(expected, Buffer.from(match[3], 'hex'))) return rejected; return { accepted: true, version: terms.version, acceptedAt: new Date(issuedAt).toISOString(), expiresAt: new Date(issuedAt + TERMS_MAX_AGE * 1000).toISOString(), }; }