ethercalc
Version:
Multi-User Spreadsheet Server — TypeScript rewrite (Cloudflare fullstack)
61 lines (56 loc) • 2.25 kB
text/typescript
/**
* 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;
}