const crypto = require('crypto'); const SESSION_COOKIE_NAME = 'portal_admin_session'; const SESSION_TTL_MS = 24 * 60 * 60 * 1000; function timingSafeEqualStrings(a, b) { const bufA = Buffer.from(String(a)); const bufB = Buffer.from(String(b)); if (bufA.length !== bufB.length) { crypto.timingSafeEqual(bufA, bufA); return false; } return crypto.timingSafeEqual(bufA, bufB); } function verifyMasterKey(input, masterKey) { if (!masterKey || !input) return false; return timingSafeEqualStrings(input, masterKey); } function sign(payload, secret) { return crypto.createHmac('sha256', secret).update(payload).digest('hex'); } function createSessionToken(secret, now = Date.now()) { const payload = String(now); return `${payload}.${sign(payload, secret)}`; } function verifySessionToken(token, secret, now = Date.now()) { if (!token || typeof token !== 'string') return false; const parts = token.split('.'); if (parts.length !== 2) return false; const [payload, signature] = parts; if (!timingSafeEqualStrings(signature, sign(payload, secret))) return false; const issuedAt = Number(payload); if (!Number.isFinite(issuedAt)) return false; return now - issuedAt < SESSION_TTL_MS; } function parseCookies(cookieHeader) { const result = {}; if (!cookieHeader) return result; cookieHeader.split(';').forEach((pair) => { const idx = pair.indexOf('='); if (idx === -1) return; const key = pair.slice(0, idx).trim(); const value = pair.slice(idx + 1).trim(); if (key) result[key] = decodeURIComponent(value); }); return result; } function buildSessionCookieHeader(token) { return [ `${SESSION_COOKIE_NAME}=${encodeURIComponent(token)}`, 'HttpOnly', 'Secure', 'Path=/admin', 'SameSite=Strict', `Max-Age=${Math.floor(SESSION_TTL_MS / 1000)}`, ].join('; '); } function buildLogoutCookieHeader() { return [`${SESSION_COOKIE_NAME}=`, 'HttpOnly', 'Secure', 'Path=/admin', 'SameSite=Strict', 'Max-Age=0'].join('; '); } module.exports = { SESSION_COOKIE_NAME, SESSION_TTL_MS, verifyMasterKey, createSessionToken, verifySessionToken, parseCookies, buildSessionCookieHeader, buildLogoutCookieHeader, };