'use strict'; // One-time, short-lived tickets for WebSocket connects. // // A browser cannot set an Authorization header on a WebSocket, and putting a // 1-hour session JWT in a query string leaks it into proxy and access logs. So // the REST layer mints a ticket that is single-use and expires in seconds, and // the WebSocket carries only that. const { randomToken } = require('./crypto'); const config = require('./config'); const tickets = new Map(); // token -> { payload, expiresAt } function issue(payload, ttlMs = config.ticketTtlMs) { const token = randomToken(24); tickets.set(token, { payload, expiresAt: Date.now() + ttlMs }); return { token, expiresIn: Math.floor(ttlMs / 1000) }; } function redeem(token) { if (!token) return null; const entry = tickets.get(token); if (!entry) return null; tickets.delete(token); // single use, redeemed or not if (entry.expiresAt < Date.now()) return null; return entry.payload; } const sweep = setInterval(() => { const now = Date.now(); for (const [token, entry] of tickets) if (entry.expiresAt < now) tickets.delete(token); }, 60_000); sweep.unref?.(); module.exports = { issue, redeem };