diff --git a/src/servers/_middlewares/origin-validation.ts b/src/servers/_middlewares/origin-validation.ts index 83a60166..898df015 100644 --- a/src/servers/_middlewares/origin-validation.ts +++ b/src/servers/_middlewares/origin-validation.ts @@ -85,18 +85,29 @@ for (const { slug, origin } of APP_ORIGIN_LIST) { // Last, so the web origin wins if an app ever declares the same one. if (PUBLIC_ORIGIN) ORIGIN_RULES[PUBLIC_ORIGIN] = { superAdminOnly: true }; -// ── TEMPORARY: origin exemption for /api/music ── -// Off unless ALLOW_ANY_ORIGIN_MUSIC=true. While the platform is still internet-exposed, this lets a music -// client that cannot present the app's custom-scheme origin reach /api/music anyway. It drops ONE layer: -// a valid token is still required (userMiddleware) and the non-owner account backstop still confines -// music accounts. Origin was never authentication — it is client-chosen and trivially forged outside a -// browser — so this removes defence in depth, not the lock. Delete once the tailnet is the perimeter. +// ── TEMPORARY: origin checking switched off ── +// ALLOW_ANY_ORIGIN=true accepts every Origin, everywhere, and skips the per-origin path scoping. +// ALLOW_ANY_ORIGIN_MUSIC=true is the narrower version, /api/music only — which is not enough on its own, +// because an app has to reach /api/auth to sign in before it ever calls its own feature. +// +// What still holds with these on: every protected route requires a valid token (userMiddleware), and the +// account backstop below still confines a non-owner account to /api/auth + /api/music whatever Origin it +// claims — that one is deliberately NOT disabled, since it is account-based, not origin-based. +// +// What is lost: defence in depth, not the lock. Origin was never authentication here — `officer://` +// is chosen by the client, forgeable outside a browser, and extractable from a shipped app binary. +// Both flags and their call sites come out once the tailnet is the perimeter. +const ALLOW_ANY_ORIGIN = process.env.ALLOW_ANY_ORIGIN === 'true'; const ALLOW_ANY_ORIGIN_MUSIC = process.env.ALLOW_ANY_ORIGIN_MUSIC === 'true'; -export function isMusicOriginExempt(path: string): boolean { - return ALLOW_ANY_ORIGIN_MUSIC && pathAllowed(path, ['/api/music']); +export function isOriginCheckDisabled(path?: string): boolean { + if (ALLOW_ANY_ORIGIN) return true; + return ALLOW_ANY_ORIGIN_MUSIC && !!path && pathAllowed(path, ['/api/music']); } +/** @deprecated use isOriginCheckDisabled — kept so existing call sites read the same. */ +export const isMusicOriginExempt = isOriginCheckDisabled; + // True when an Origin is reserved for the platform owner (used at signin to reject a non-owner login). export function isSuperAdminOnlyOrigin(origin: string | undefined): boolean { return !!origin && ORIGIN_RULES[origin]?.superAdminOnly === true; @@ -104,6 +115,7 @@ export function isSuperAdminOnlyOrigin(origin: string | undefined): boolean { export function isOriginAllowed(origin: string | undefined, host?: string): boolean { if (IS_DEV_BUILD) return true; + if (ALLOW_ANY_ORIGIN) return true; if (origin) { if (origin.startsWith('chrome-extension://')) { @@ -178,7 +190,10 @@ export const originScopeMiddleware: MiddlewareHandler = async function (ctx, nex throw errors.FORBIDDEN('This account is limited to the music app'); } - // 2. Per-origin rules. + // 2. Per-origin rules. Skipped entirely while origin checking is off (the account backstop above is + // account-based, not origin-based, so it deliberately still applies). + if (isOriginCheckDisabled(path)) return next(); + const rule = origin ? ORIGIN_RULES[origin] : undefined; if (rule) { if (rule.paths && !pathAllowed(path, rule.paths)) {