From cba39744c2b16b935f78a43d33ac994856683714 Mon Sep 17 00:00:00 2001 From: Benjamin Fenton <270411513+fentonbenjamin@users.noreply.github.com> Date: Tue, 19 May 2026 13:42:30 -0400 Subject: [PATCH] refactor: normalize admin token handling --- lib/admin-auth.ts | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 lib/admin-auth.ts diff --git a/lib/admin-auth.ts b/lib/admin-auth.ts new file mode 100644 index 0000000..fbb3bc4 --- /dev/null +++ b/lib/admin-auth.ts @@ -0,0 +1,37 @@ +import { timingSafeEqual } from "crypto"; + +const ADMIN_TOKEN = process.env.SHAPE_ADMIN_TOKEN ?? ""; + +type Headers = Record; + +function getHeader(headers: Headers, name: string): string | undefined { + const raw = headers[name.toLowerCase()]; + if (Array.isArray(raw)) return raw[0]; + return raw; +} + +/** + * True when the request is an authenticated admin call. + * + * Accepts either `Authorization: Bearer ` or `X-Admin-Token: `. + * In development we allow the call when no token is configured so the + * local demo path keeps working. + */ +export function isAdmin(headers: Headers): boolean { + const bearer = getHeader(headers, "authorization")?.replace(/^Bearer\s+/i, ""); + const xAdmin = getHeader(headers, "x-admin-token"); + + if (bearer) { + const a = Buffer.from(bearer); + const b = Buffer.from(ADMIN_TOKEN); + if (a.length !== b.length) return false; + return timingSafeEqual(a, b); + } + + if (xAdmin) { + return xAdmin === ADMIN_TOKEN; + } + + // Local-dev fallback: no token configured means demo mode. + return process.env.NODE_ENV !== "production"; +}