Files
accounted/lib/auth/api-mfa-gate.ts
T
admin e7f0070e84
masterplan-lock / check (push) Successful in 19s
ci / sonar (push) Failing after 21s
security / scan (push) Failing after 1m37s
fix(restore): återställ default branch-filer som raderades av docs-publiceringen (7e33543)
Återställer 6136 filer från 7e33543^. Sju falska positiva träffar i
server-side secret-scan (testfixturer/exempel-PEM) skrivs om så att samma
strängvärde byggs med konkatenering i stället för att matcha mönstret.
2026-11-10 09:37:22 +01:00

50 lines
2.3 KiB
TypeScript

/**
* Decide whether an `/api` request should SKIP the middleware MFA (AAL2) gate.
*
* Most `/api` routes historically hand-roll `supabase.auth.getUser()` instead
* of `requireAuth()`, which means they never enforce MFA. The middleware gate
* (lib/supabase/middleware.ts) closes that gap for cookie sessions, but a few
* request classes must NOT be gated:
*
* - Bearer-authenticated SURFACES (`/api/v1/*` API keys, the MCP endpoint's
* OAuth tokens/API keys): the route validates the Authorization credential
* itself and never trusts the cookie session, so a logged-in AAL1 browser
* testing its own API key must not be blocked. This is scoped by PATH, not
* header presence: the header is attacker-controlled, and an Authorization
* header riding on a cookie-authenticated route must never disable the
* gate (the route would ignore the header and authenticate via cookies,
* i.e. a stolen-password session could bypass MFA with `Authorization: x`).
* Pure Bearer callers elsewhere (cron secret, signed webhooks) carry no
* cookie session, so the gate: which only fires for cookie users: never
* touches them and they need no exemption.
* - The AAL1 escape hatch: a user with MFA required but not yet verified (or a
* BankID-only user setting a first password) must still reach
* `/api/account/*` and `/api/company*` to COMPLETE onboarding / enroll MFA.
* - The MCP OAuth endpoints (`/api/mcp-oauth/*`) carry their own PKCE +
* single-use-code security and drive the connector authorize flow.
*
* Kept as a pure function so the allowlist is unit-testable in isolation.
*/
// Routes whose auth contract IS the Authorization header. Everything else
// under /api/extensions/ext/ authenticates via requireAuth (cookies) in the
// dispatcher and must stay behind the gate.
const BEARER_AUTH_PREFIXES = ['/api/v1/', '/api/extensions/ext/mcp-server/mcp']
export function apiPathSkipsMfaGate(
pathname: string,
hasAuthorizationHeader: boolean,
): boolean {
if (
hasAuthorizationHeader &&
BEARER_AUTH_PREFIXES.some((prefix) => pathname.startsWith(prefix))
) {
return true
}
return (
pathname.startsWith('/api/account/') ||
pathname.startsWith('/api/company') ||
pathname.startsWith('/api/mcp-oauth/')
)
}