fix(auth): the refusal code joins the published catalogue

Two structural guards caught the new gate. The error-code catalogue is derived
from the route tree on every run and fails in both directions, so a code the
server emits and does not publish is a client that cannot branch on it; the
session-surface guard freezes the short list of files under `src/app` that
resolve a caller without `requireAuth`, and the register route now belongs on
it — `requireAuth` is the wrong gate there twice over, since it would throw at
the anonymous caller the route exists for and this route wants the ABSENCE of a
session.

The code is `auth.already_authenticated` rather than the bare
`already_authenticated`: the catalogue's own header rules that new codes are
dot.case, and it sits beside `auth.mfa.code_invalid` in the same family. The
OpenAPI document renders the catalogue into the ErrorEnvelope description, so
it is regenerated.
This commit is contained in:
Marc-André Bombeck committed 2026-09-11 09:51:32 +02:00
1 parent 0290554d36
commit d2f47e3bb5
6 files changed
+23 -16

No files matched your search

@@ -79,6 +79,12 @@ describe("S1 — the direct session-resolution set is frozen", () => {
// the freshness comparison, which `requireAuth` does not project.
"app/api/auth/native/complete/route.ts":
"reads createdAt for the handoff freshness check",
// Anonymous self-registration. Probes for an already-valid session so an
// invitation opened from inside one is refused instead of minting a second
// account over the caller's own cookie. `requireAuth` is the wrong gate
// here twice over: it would throw 401 at the anonymous caller the route
// exists for, and this route wants the ABSENCE of a session.
"app/api/auth/register/route.ts": "refuses to register over a live session",
// Anonymous IdP callback. Probes for an already-valid session so a
// replayed callback redirects instead of minting a second identity.
"app/api/auth/oidc/callback/route.ts":
@@ -340,7 +340,7 @@ describe("POST /api/auth/register — refuses a live session (v1.38.19)", () =>
meta?: { errorCode?: string };
};
expect(body.data).toBeNull();
expect(body.meta?.errorCode).toBe("already_authenticated");
expect(body.meta?.errorCode).toBe("auth.already_authenticated");
expect(body.error).toMatch(/sign/i);
});
+1 -1
View File
@@ -60,7 +60,7 @@ export const POST = apiHandler(async (request: NextRequest) => {
return apiError(
"Already signed in — sign out first to use an invitation",
409,
{ errorCode: "already_authenticated" },
{ errorCode: "auth.already_authenticated" },
);
}
+1
View File
@@ -46,6 +46,7 @@ export const ERROR_CODE_CATALOGUE: Readonly<Record<string, readonly string[]>> =
],
auth: [
"auth.admin.required",
"auth.already_authenticated",
"auth.mfa.code_invalid",
"auth.missing",
"auth.refresh.invalid",