refactor(auth): move the request guards out of api-handler into lib/auth/guards

api-handler.ts held two modules: the route wrapper and thirteen guards. The
guards now live in lib/auth/guards/, one module per family (the caller,
the acting-account carrier, the record modes, the cookie-only helpers, the
fresh-factor and recent-proof gates, the Bearer elevation, MFA management).
api-handler.ts keeps the wrapper and re-exports every guard, so it stays the
one public entry and no route changes its import.

The bodies move verbatim; only the version prefixes of moved comments are
dropped. The structural guards that read the auth layer now find the module
that declares a guard by name (helpers/auth-guards.ts) instead of a fixed
path, and a new guard holds the entry: nothing outside the folder but
api-handler.ts imports a guard module.
This commit is contained in:
Marc-André Bombeck committed 2026-10-23 10:54:23 +02:00
1 parent 9de64cae3e
commit ee1561496b
40 files changed
+2153 -1668

No files matched your search

@@ -11,6 +11,8 @@ import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { guardSourceOf } from "@/__tests__/helpers/auth-guards";
const SRC = join(process.cwd(), "src");
const read = (rel: string) => readFileSync(join(SRC, rel), "utf8");
@@ -94,18 +96,15 @@ describe("T11/T13 — cookie-only boundaries and no bundle-in-browser", () => {
});
it("requireAdmin and requireFreshMfa remain cookie-only (getSession)", () => {
const handler = read("lib/api-handler.ts");
// Both resolve via getSession() (cookie-only) — restated as a tripwire so a
// future edit that threads a Bearer into either trips this test.
const requireAdmin = handler.slice(
handler.indexOf("export async function requireAdmin"),
handler.indexOf("export async function requireAdmin") + 400,
);
expect(requireAdmin).toMatch(/getSession\(\)/);
const requireFresh = handler.slice(
handler.indexOf("export async function requireFreshMfa"),
handler.indexOf("export async function requireFreshMfa") + 400,
);
expect(requireFresh).toMatch(/getSession\(\)/);
// future edit that threads a Bearer into either trips this test. Read from
// the guard module that declares each, which throws if it is gone.
const opening = (name: string) => {
const src = guardSourceOf(name, [`export async function ${name}(`]);
const start = src.indexOf(`export async function ${name}(`);
return src.slice(start, start + 600);
};
expect(opening("requireAdmin")).toMatch(/getSession\(\)/);
expect(opening("requireFreshMfa")).toMatch(/getSession\(\)/);
});
});