const crypto = require('crypto'); const COOKIE_NAME = 'session'; const MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days function getSecret() { if (process.env.APP_SECRET) return process.env.APP_SECRET; if (process.env.APP_PASSWORD) { return crypto.createHmac('sha256', 'cookie-tracker-session') .update(process.env.APP_PASSWORD) .digest('hex'); } return null; } function sign(payload) { const secret = getSecret(); if (!secret) return null; const data = JSON.stringify(payload); const hmac = crypto.createHmac('sha256', secret).update(data).digest('hex'); const encoded = Buffer.from(data, 'utf8').toString('base64url'); return `${encoded}.${hmac}`; } function verify(token) { const secret = getSecret(); if (!secret || !token) return false; const parts = token.split('.'); if (parts.length !== 2) return false; const [encoded, hmac] = parts; let data; try { data = Buffer.from(encoded, 'base64url').toString('utf8'); } catch { return false; } const expected = crypto.createHmac('sha256', secret).update(data).digest('hex'); const expectedBuf = Buffer.from(expected); const hmacBuf = Buffer.from(hmac); if (expectedBuf.length !== hmacBuf.length || !crypto.timingSafeEqual(expectedBuf, hmacBuf)) { return false; } let payload; try { payload = JSON.parse(data); } catch { return false; } if (payload.t && Date.now() - payload.t > MAX_AGE_MS) return false; return true; } function createSessionCookie() { const token = sign({ t: Date.now() }); if (!token) return null; const opts = { httpOnly: true, sameSite: 'lax', path: '/', maxAge: Math.floor(MAX_AGE_MS / 1000), }; if (process.env.NODE_ENV === 'production') opts.secure = true; return { token, opts }; } function clearSessionCookie() { return { httpOnly: true, sameSite: 'lax', path: '/', maxAge: 0, }; } function authMiddleware(req, res, next) { if (!process.env.APP_PASSWORD) { return next(); } const p = req.path || ''; if (p.startsWith('/auth')) return next(); const token = req.cookies?.[COOKIE_NAME] || null; if (!token || !verify(token)) { return res.status(401).json({ error: 'Unauthorized' }); } next(); } module.exports = { COOKIE_NAME, getSecret, sign, verify, createSessionCookie, clearSessionCookie, authMiddleware, };