import { createHash, timingSafeEqual } from 'node:crypto'; export type TelemetryAuthentication = | { ok: true } | { ok: false; status: 401 | 503; error: string }; const tokenPattern = /^[A-Za-z0-9._~+\/-]+=*$/; const validToken = (value: string): boolean => value.length >= 32 && value.length <= 512 && tokenPattern.test(value); const hash = (value: string): Buffer => createHash('sha256').update(value, 'utf8').digest(); /** Machine telemetry has its own credential; browser sessions never authorize it. */ export function authenticateTelemetry(request: Request): TelemetryAuthentication { const expected = process.env.EC_TELEMETRY_TOKEN ?? ''; if (!validToken(expected)) { return { ok: false, status: 503, error: 'Game telemetry authentication is not configured. Set EC_TELEMETRY_TOKEN to a secret of at least 32 characters.' }; } const authorization = request.headers.get('authorization'); const custom = request.headers.get('x-ec-telemetry-token'); const bearer = authorization?.match(/^Bearer ([A-Za-z0-9._~+\/-]+=*)$/i)?.[1]; const denied = (): TelemetryAuthentication => ({ ok: false, status: 401, error: 'A valid game telemetry token is required.' }); // Reject malformed/ambiguous credentials rather than falling back to a // second header. A shared secret must never come from a URL, cookie or body. if (authorization !== null && !bearer) return denied(); if (custom !== null && !validToken(custom)) return denied(); if (bearer !== undefined && !validToken(bearer)) return denied(); const supplied = bearer ?? custom; if (!supplied) return denied(); const suppliedHash = hash(supplied); if (bearer !== undefined && custom !== null && !timingSafeEqual(suppliedHash, hash(custom))) return denied(); if (!timingSafeEqual(suppliedHash, hash(expected))) return denied(); return { ok: true }; }