Files
Jakob Wennberg ec27228a8e style: remove em/en dashes repo-wide, add CLAUDE.md rule against them (#890)
Em dashes (—) and en dashes (–) had spread across comments, docs, tests,
and a few UI strings, reading as AI-generated boilerplate rather than
house style. Replaced each with punctuation matching its context: colon
for explanatory clauses, comma for asides, plain hyphen for numeric/legal
ranges (e.g. "21-23§"), "to"/"till" for date ranges, parentheses for
paired-dash asides. messages/en.json and messages/sv.json were fixed by
hand together to keep sv/en in sync.

Left untouched where the dash is the functional subject rather than
decorative punctuation: date-range-parser.ts's separator regex,
charset-repair.ts's CP1252 byte-mapping table (and its test), the SIE
encoding mojibake docs, generic-csv.ts's minus-sign normalizer, the
agent system-prompt files that already instruct against em dashes, and
a golden iXBRL test fixture compared byte-for-byte.

Also fixes two bugs surfaced along the way: an off-by-one in
ApiKeysPanel's scope-label split (a leftover from an earlier partial
pass), and a charset-repair test that had lost the literal en-dash it
exists to verify.

Regenerated the agent atom seed migration (skills:generate) since 27
SKILL.md files changed. Added a CLAUDE.md rule against em/en dashes,
with an explicit carve-out for the functional-dash cases above.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 15:58:06 +02: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/')
)
}