Say how every operation is authenticated

The document defined bearerAuth and cookieAuth and referenced neither,
so the schemes were decoration: a client generated from the contract
wires no credential and reads the whole surface as open.

The default now offers the two as alternatives, and the ten operations
reachable before a credential exists opt out with an empty array. The
opt-out list is derived from what each handler accepts, not from the
proxy's public-path list — /api/ingest/medication is on that list and
requires a Bearer token, so reading it as the answer would publish an
authenticated route as open. Writing the list from the proxy is exactly
the mistake the guard caught: /api/auth/oidc/callback went on it and is
not published at all.
This commit is contained in:
Marc-André Bombeck committed 2026-08-22 15:03:28 +02:00
1 parent 50b483d8d1
commit 965d41f1ed
6 files changed
+25228 -1142

No files matched your search

+25162 -1140
View File
File diff suppressed because it is too large. Load diff
@@ -52,6 +52,12 @@ const HTTP_METHODS = ["get", "post", "put", "patch", "delete"] as const;
* merely sits on the proxy's public-path list does not belong here.
*/
const UNAUTHENTICATED: Readonly<Record<string, string>> = {
// `/api/auth/oidc/callback` is deliberately absent. It is anonymous, but it
// is not published at all: the provider drives it with its own query string
// and no client constructs one, so it is exempted from the contract by the
// coverage guard rather than published with an opt-out. This test is what
// caught the entry when it was written from the proxy's public-path list
// instead of from the published set.
"/api/health":
"Container healthcheck. Answers before any user exists and outside the response envelope.",
"/api/version":
@@ -68,8 +74,6 @@ const UNAUTHENTICATED: Readonly<Record<string, string>> = {
"Completes the passkey sign-in; the assertion is the credential.",
"/api/auth/oidc/login":
"Starts the identity-provider handoff, for the browser and for the native client.",
"/api/auth/oidc/callback":
"The provider drives this one with its own query string.",
"/api/auth/oidc/status":
"Says whether single sign-on is configured, so the sign-in screen can offer it.",
"/api/notifications/vapid":
+42
View File
@@ -618,6 +618,12 @@ export const authPaths: NonNullable<ZodOpenApiObject["paths"]> = {
},
"/api/auth/login": {
post: {
// No credential: this operation is reachable before one exists.
// The document-level default offers the Bearer token and the session
// cookie as alternatives; an empty array is how OpenAPI says neither
// is required. The list of paths allowed to say it lives in
// `openapi-security-declaration-guard.test.ts`.
security: [],
tags: ["Auth"],
summary: "Email-or-username login (password)",
description:
@@ -1124,6 +1130,12 @@ export const authPaths: NonNullable<ZodOpenApiObject["paths"]> = {
},
"/api/auth/passkey/login-verify": {
post: {
// No credential: this operation is reachable before one exists.
// The document-level default offers the Bearer token and the session
// cookie as alternatives; an empty array is how OpenAPI says neither
// is required. The list of paths allowed to say it lives in
// `openapi-security-declaration-guard.test.ts`.
security: [],
tags: ["Auth"],
summary: "Passkey assertion verification",
requestBody: {
@@ -1397,6 +1409,12 @@ export const authPaths: NonNullable<ZodOpenApiObject["paths"]> = {
},
"/api/auth/oidc/status": {
get: {
// No credential: this operation is reachable before one exists.
// The document-level default offers the Bearer token and the session
// cookie as alternatives; an empty array is how OpenAPI says neither
// is required. The list of paths allowed to say it lives in
// `openapi-security-declaration-guard.test.ts`.
security: [],
tags: ["Auth"],
summary: "Whether SSO is configured, and whether it is the only way in",
description:
@@ -1415,6 +1433,12 @@ export const authPaths: NonNullable<ZodOpenApiObject["paths"]> = {
},
"/api/auth/registration-status": {
get: {
// No credential: this operation is reachable before one exists.
// The document-level default offers the Bearer token and the session
// cookie as alternatives; an empty array is how OpenAPI says neither
// is required. The list of paths allowed to say it lives in
// `openapi-security-declaration-guard.test.ts`.
security: [],
tags: ["Auth"],
summary: "Whether self-registration is open",
description:
@@ -1437,6 +1461,12 @@ export const authPaths: NonNullable<ZodOpenApiObject["paths"]> = {
},
"/api/auth/passkey/login-options": {
post: {
// No credential: this operation is reachable before one exists.
// The document-level default offers the Bearer token and the session
// cookie as alternatives; an empty array is how OpenAPI says neither
// is required. The list of paths allowed to say it lives in
// `openapi-security-declaration-guard.test.ts`.
security: [],
tags: ["Auth"],
summary: "Begin a passkey sign-in (discoverable credentials)",
description:
@@ -1582,6 +1612,12 @@ export const authPaths: NonNullable<ZodOpenApiObject["paths"]> = {
// the Codex device-auth flow.
"/api/auth/register": {
post: {
// No credential: this operation is reachable before one exists.
// The document-level default offers the Bearer token and the session
// cookie as alternatives; an empty array is how OpenAPI says neither
// is required. The list of paths allowed to say it lives in
// `openapi-security-declaration-guard.test.ts`.
security: [],
tags: ["Auth"],
summary: "Create an account",
description:
@@ -1696,6 +1732,12 @@ export const authPaths: NonNullable<ZodOpenApiObject["paths"]> = {
},
"/api/auth/oidc/login": {
get: {
// No credential: this operation is reachable before one exists.
// The document-level default offers the Bearer token and the session
// cookie as alternatives; an empty array is how OpenAPI says neither
// is required. The list of paths allowed to say it lives in
// `openapi-security-declaration-guard.test.ts`.
security: [],
tags: ["Auth"],
summary: "Start an SSO sign-in (browser redirect, or the native leg)",
description:
+6
View File
@@ -61,6 +61,12 @@ const healthAdminResponse = z
export const healthPaths: NonNullable<ZodOpenApiObject["paths"]> = {
"/api/health": {
get: {
// No credential: this operation is reachable before one exists.
// The document-level default offers the Bearer token and the session
// cookie as alternatives; an empty array is how OpenAPI says neither
// is required. The list of paths allowed to say it lives in
// `openapi-security-declaration-guard.test.ts`.
security: [],
tags: ["Meta"],
summary: "Liveness / readiness probe",
description:
+6
View File
@@ -339,6 +339,12 @@ export const metaPaths: NonNullable<ZodOpenApiObject["paths"]> = {
// ── Appended: two discovery reads the registry never carried.
"/api/version": {
get: {
// No credential: this operation is reachable before one exists.
// The document-level default offers the Bearer token and the session
// cookie as alternatives; an empty array is how OpenAPI says neither
// is required. The list of paths allowed to say it lives in
// `openapi-security-declaration-guard.test.ts`.
security: [],
tags: ["Meta"],
summary: "The running build's version, and how it was built",
description:
@@ -102,6 +102,12 @@ export const notificationTransportPaths: NonNullable<
> = {
"/api/notifications/vapid": {
get: {
// No credential: this operation is reachable before one exists.
// The document-level default offers the Bearer token and the session
// cookie as alternatives; an empty array is how OpenAPI says neither
// is required. The list of paths allowed to say it lives in
// `openapi-security-declaration-guard.test.ts`.
security: [],
tags: ["Notifications"],
summary: "Read the instance's VAPID public key",
description: