mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 22:50:51 +00:00
Publish the passkey list and the base-token 422
GET /api/auth/passkeys was absent from the contract entirely — no path entry, no schema — while the client teams had been pointed at a published schema for it since v1.32.8. It is now registered with the shape the route actually serves: id, name, credentialDeviceType, credentialBackedUp, createdAt, and the nullable lastUsedAt that started this. The route itself is unchanged; only the record of it was missing. PasskeyInfo is deliberately separate from WebauthnMfaCredentialInfo — one lists sign-in credentials, the other second-factor security keys, and merging them would blur two surfaces that behave differently. The route test now compares the projection the route asks Prisma for against the published property set, so neither end can move without the other. `invalid_base_updated_at` existed only in route tests. It is now on all eight published endpoints that accept a `baseUpdatedAt` body token, and the field description says the thing implementers keep getting wrong: an unparseable token, `null` included, is rejected with 422 rather than falling back to the unconditional write. Omitting the key is the only way to get that write. /api/auth/me/health-score-config also returns this 422 but is not in the spec at all; publishing that route is a separate piece of work.
This commit is contained in:
1 parent
43c591fe19
commit
6a69bad05e
9 files changed
+317
-83
No files matched your search
@@ -10,6 +10,14 @@
|
||||
*
|
||||
* The write half (the stamp on a verified assertion) is pinned in
|
||||
* `src/lib/auth/__tests__/passkey.test.ts`.
|
||||
*
|
||||
* v1.35.1 adds the published half. The route was absent from
|
||||
* `docs/api/openapi.yaml` entirely — no path entry, no schema — while iOS
|
||||
* had been told to generate against one (iOS #60). The last case below
|
||||
* compares the projection the route actually asks Prisma for against the
|
||||
* property set the contract publishes, so the two cannot drift: a column
|
||||
* added to the select without a schema entry fails, and so does a schema
|
||||
* property the route never serves.
|
||||
*/
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
@@ -29,6 +37,7 @@ vi.mock("@/lib/logging/context", () => ({ annotate: vi.fn() }));
|
||||
import { GET } from "../route";
|
||||
import { requireAuth } from "@/lib/api-handler";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { buildOpenApiDocument } from "@/lib/openapi/registry";
|
||||
|
||||
const USER = { id: "user-1", username: "u", email: "u@example.com" };
|
||||
|
||||
@@ -81,6 +90,29 @@ describe("GET /api/auth/passkeys", () => {
|
||||
expect(Object.keys(body.data[0])).toContain("lastUsedAt");
|
||||
expect(body.data[0].lastUsedAt).toBeNull();
|
||||
});
|
||||
|
||||
it("publishes exactly the columns it selects", async () => {
|
||||
await (GET as unknown as () => Promise<Response>)();
|
||||
const args = vi.mocked(prisma.passkey.findMany).mock.calls[0][0];
|
||||
const selected = Object.keys(args?.select ?? {}).sort();
|
||||
|
||||
const doc = buildOpenApiDocument() as {
|
||||
paths?: Record<string, { get?: { responses?: Record<string, unknown> } }>;
|
||||
components?: {
|
||||
schemas?: Record<string, { properties?: Record<string, unknown> }>;
|
||||
};
|
||||
};
|
||||
|
||||
expect(
|
||||
doc.paths?.["/api/auth/passkeys"]?.get,
|
||||
"GET /api/auth/passkeys is not registered in the OpenAPI document",
|
||||
).toBeDefined();
|
||||
|
||||
const published = doc.components?.schemas?.["PasskeyInfo"]?.properties;
|
||||
expect(published, "PasskeyInfo schema is not emitted").toBeDefined();
|
||||
|
||||
expect(Object.keys(published ?? {}).sort()).toEqual(selected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("primary-passkey enrollment route guards", () => {
|
||||
|
||||
Reference in new issue
Block a user