UNPKG

ethercalc

Version:

Multi-User Spreadsheet Server — TypeScript rewrite (Cloudflare fullstack)

61 lines (56 loc) 2.25 kB
/** * Deployment-operator bearer authorization. * * Migration writes and hosted PITR restore are gated by the token sourced * from `env.ETHERCALC_MIGRATE_TOKEN`. Rules: * * - Token unset (or empty) → endpoint is DISABLED: the route returns * `404 Not Found` exactly like an unknown path. * - Token set, no `Authorization` header → `401 Unauthorized`. * - Token set, wrong value → `401 Unauthorized`. * - Token set, matching `Bearer <token>` → OK. * * Constant-time comparison via `constantTimeEqual` avoids propagating * direct string equality into security-adjacent code. */ /** Verdict returned by {@link verifyMigrateToken}. */ export type MigrateAuthResult = /** Endpoint disabled: the caller should respond `404`. */ | { readonly kind: 'disabled' } /** Caller didn't supply `Authorization` or supplied a malformed value. */ | { readonly kind: 'missing' } /** Token supplied but didn't match; respond `401`. */ | { readonly kind: 'bad' } /** Token verified; caller may proceed. */ | { readonly kind: 'ok' }; const BEARER_PREFIX = 'Bearer '; /** * Match the incoming `Authorization` header against the expected token. * `expected` is typically `env.ETHERCALC_MIGRATE_TOKEN` — treat empty or * whitespace-only as unset. */ export function verifyMigrateToken( expected: string | undefined, authHeader: string | null, ): MigrateAuthResult { const token = (expected ?? '').trim(); if (token.length === 0) return { kind: 'disabled' }; if (authHeader === null) return { kind: 'missing' }; if (!authHeader.startsWith(BEARER_PREFIX)) return { kind: 'missing' }; const presented = authHeader.slice(BEARER_PREFIX.length); if (!constantTimeEqual(presented, token)) return { kind: 'bad' }; return { kind: 'ok' }; } /** * Length-constant comparison. Returns `false` when lengths differ without * looking at the contents; same-length inputs are compared byte-by-byte * with an XOR accumulator so runtime doesn't leak a match prefix length. */ function constantTimeEqual(a: string, b: string): boolean { if (a.length !== b.length) return false; let diff = 0; for (let i = 0; i < a.length; i++) { diff |= a.charCodeAt(i) ^ b.charCodeAt(i); } return diff === 0; }