import { createAuthenticatedPrincipal, parseEkklesiaUserId, parseGrantId, parsePermissionId, } from "@ekklesia/contracts"; import { type AuthorizationEvaluator, type AuthorizationStore, AuthorizationStoreError, CORE_AUTHORIZATION_GRANTS_MANAGE, CORE_PERMISSION_REGISTRY, } from "@ekklesia/core/authorization"; import type { EkklesiaIdentityRuntime } from "@ekklesia/core/identity"; import { Hono } from "hono"; import { describe, expect, it, vi } from "vitest"; import { CORE_MUTATION_MAX_BODY_BYTES } from "../src/body-limits.js"; import { createGrantsRoutes } from "../src/grants-routes.js"; const trustedOrigin = "https://dashboard.example.test"; const untrustedOrigin = "https://evil.example.test"; const manager = createAuthenticatedPrincipal("manager_1"); const subjectUserId = parseEkklesiaUserId("subject_1"); // Well-formed but never declared in `CORE_PERMISSION_REGISTRY` — used to prove the routes reject // (or, for listing, still return) an unregistered permission id rather than assuming registration. const permission = parsePermissionId("calendar.events.read"); // A permission `CORE_PERMISSION_REGISTRY` actually declares, so tests that must reach // `store.createGrant`/`store.revokeGrant` can do so. This suite exercises `createGrantsRoutes` in // isolation and intentionally injects `CORE_PERMISSION_REGISTRY` directly rather than a composed // platform registry — the routes have no notion of "Core" versus "module" permissions, they only // ever consult whichever registry they are given. Coverage proving a module-declared permission // works through the exact same route code lives in // `apps/api/tests/authorization-middleware.test.ts` (unit) and the "installed module permission // end-to-end" scenario in `apps/api/tests/api-http.integration.ts` (live). const registeredPermission = CORE_AUTHORIZATION_GRANTS_MANAGE; const grantId = parseGrantId("11111111-1111-1111-1111-111111111111"); function fakeRuntime(overrides: Partial = {}): EkklesiaIdentityRuntime { return { handleAuthRequest: vi.fn(), resolvePrincipal: vi.fn().mockResolvedValue(manager), ...overrides, }; } function fakeStore(overrides: Partial = {}): AuthorizationStore { return { createGrant: vi.fn(), grantPlatformOwner: vi.fn(), hasActiveGrant: vi.fn().mockResolvedValue(false), isPlatformOwner: vi.fn().mockResolvedValue(false), listGrantsForUser: vi.fn().mockResolvedValue({ grants: [] }), revokeGrant: vi.fn(), revokePlatformOwner: vi.fn(), ...overrides, }; } function buildApp(options: { readonly allowed?: boolean; readonly now?: () => Date; readonly runtime?: EkklesiaIdentityRuntime; readonly store?: AuthorizationStore; }) { const evaluator: AuthorizationEvaluator = { authorize: vi.fn().mockResolvedValue({ allowed: options.allowed ?? true }), }; const runtime = options.runtime ?? fakeRuntime(); const store = options.store ?? fakeStore(); const app = new Hono(); app.route( "/", createGrantsRoutes({ evaluator, ...(options.now === undefined ? {} : { now: options.now }), registry: CORE_PERMISSION_REGISTRY, runtime, store, trustedOrigins: new Set([trustedOrigin]), }), ); return { app, evaluator, runtime, store }; } describe("GET /api/v1/grants", () => { it("denies an anonymous or non-manager authenticated caller (evaluator says no)", async () => { const { app, evaluator } = buildApp({ allowed: false }); const response = await app.request(`/api/v1/grants?userId=${subjectUserId}`); expect(response.status).toBe(403); expect(evaluator.authorize).toHaveBeenCalledWith( expect.objectContaining({ baseline: "restricted" }), ); }); it("allows a caller the evaluator authorizes — whether by platform ownership or a delegated grants.manage grant, the route cannot tell and does not need to (see core/tests/authorization-evaluator.test.ts for that distinction)", async () => { const grants = [ { createdAt: new Date("2026-01-01T00:00:00.000Z"), id: grantId, permission, sourceKind: "explicit" as const, userId: subjectUserId, }, ]; const store = fakeStore({ listGrantsForUser: vi.fn().mockResolvedValue({ grants }) }); const { app } = buildApp({ store }); const response = await app.request(`/api/v1/grants?userId=${subjectUserId}`); const body = (await response.json()) as { grants: unknown[]; nextCursor: string | null }; expect(response.status).toBe(200); expect(body.grants).toHaveLength(1); expect(body.nextCursor).toBeNull(); // The default page size is applied server-side; `sourceKind` filtering stays server-side too. expect(store.listGrantsForUser).toHaveBeenCalledWith(subjectUserId, { limit: 50, sourceKind: "explicit", }); }); it("rejects a missing or malformed userId query parameter", async () => { const { app, store } = buildApp({}); const response = await app.request("/api/v1/grants?userId="); expect(response.status).toBe(400); expect(store.listGrantsForUser).not.toHaveBeenCalled(); }); it("maps a store infrastructure failure to a sanitized 503", async () => { const store = fakeStore({ listGrantsForUser: vi .fn() .mockRejectedValue(new AuthorizationStoreError("AUTHORIZATION_GRANT_LIST_FAILED")), }); const { app } = buildApp({ store }); const response = await app.request(`/api/v1/grants?userId=${subjectUserId}`); expect(response.status).toBe(503); expect(await response.json()).toEqual({ code: "AUTHORIZATION_GRANT_LIST_FAILED" }); }); }); describe("POST /api/v1/grants", () => { it("rejects a request with no trusted Origin header before ever checking permission", async () => { const { app, evaluator } = buildApp({}); const response = await app.request("/api/v1/grants", { body: JSON.stringify({ permission, subjectUserId }), headers: { "content-type": "application/json" }, method: "POST", }); expect(response.status).toBe(403); expect(await response.json()).toEqual({ code: "UNTRUSTED_ORIGIN" }); expect(evaluator.authorize).not.toHaveBeenCalled(); }); it("rejects an untrusted Origin header the same way", async () => { const { app } = buildApp({}); const response = await app.request("/api/v1/grants", { body: JSON.stringify({ permission, subjectUserId }), headers: { "content-type": "application/json", origin: untrustedOrigin }, method: "POST", }); expect(response.status).toBe(403); expect(await response.json()).toEqual({ code: "UNTRUSTED_ORIGIN" }); }); it("denies a caller the evaluator does not authorize", async () => { const { app, store } = buildApp({ allowed: false }); const response = await app.request("/api/v1/grants", { body: JSON.stringify({ permission, subjectUserId }), headers: { "content-type": "application/json", origin: trustedOrigin }, method: "POST", }); expect(response.status).toBe(403); expect(store.createGrant).not.toHaveBeenCalled(); }); it("creates an explicit grant, deriving sourceKind and actor server-side from the authenticated principal, never from the request body", async () => { const createdGrant = { createdAt: new Date("2026-01-01T00:00:00.000Z"), id: grantId, permission: registeredPermission, sourceKind: "explicit" as const, userId: subjectUserId, }; const store = fakeStore({ createGrant: vi.fn().mockResolvedValue(createdGrant) }); const { app } = buildApp({ store }); const response = await app.request("/api/v1/grants", { body: JSON.stringify({ permission: registeredPermission, subjectUserId }), headers: { "content-type": "application/json", origin: trustedOrigin }, method: "POST", }); const body = (await response.json()) as { grant: { id: string } }; expect(response.status).toBe(201); expect(body.grant.id).toBe(grantId); expect(store.createGrant).toHaveBeenCalledWith( expect.objectContaining({ actor: { type: "user", userId: manager.userId }, permission: registeredPermission, sourceKind: "explicit", userId: subjectUserId, }), ); // The generic HTTP surface creates unscoped explicit grants only. expect(store.createGrant).toHaveBeenCalledWith( expect.not.objectContaining({ scope: expect.anything() }), ); }); it("rejects a well-formed but unregistered permission with the same sanitized denial as an unauthorized caller, and never calls the store", async () => { const { app, store } = buildApp({}); const response = await app.request("/api/v1/grants", { body: JSON.stringify({ permission, subjectUserId }), headers: { "content-type": "application/json", origin: trustedOrigin }, method: "POST", }); expect(response.status).toBe(403); expect(await response.json()).toEqual({ code: "AUTHORIZATION_DENIED" }); expect(store.createGrant).not.toHaveBeenCalled(); }); it.each([ "actor", "owner", "isOwner", "sourceKind", "sourceRef", "id", "grantId", "createdAt", "revokedAt", "createdByActorId", "effectivePermissions", ])("rejects a request body carrying a disallowed field (%s), even alongside otherwise-valid fields", async (field) => { const { app, store } = buildApp({}); const response = await app.request("/api/v1/grants", { body: JSON.stringify({ [field]: "hostile-value", permission, subjectUserId }), headers: { "content-type": "application/json", origin: trustedOrigin }, method: "POST", }); expect(response.status).toBe(400); expect(store.createGrant).not.toHaveBeenCalled(); }); it("rejects a malformed permission, subject id, scope, or expiresAt", async () => { const { app, store } = buildApp({}); const responses = await Promise.all( [ { permission: "not-namespaced", subjectUserId }, { permission, subjectUserId: "" }, { permission, scope: "", subjectUserId }, { expiresAt: "not-a-date", permission, subjectUserId }, ].map((body) => app.request("/api/v1/grants", { body: JSON.stringify(body), headers: { "content-type": "application/json", origin: trustedOrigin }, method: "POST", }), ), ); for (const response of responses) { expect(response.status).toBe(400); } expect(store.createGrant).not.toHaveBeenCalled(); }); it("maps a store infrastructure failure to a sanitized 503", async () => { const store = fakeStore({ createGrant: vi .fn() .mockRejectedValue(new AuthorizationStoreError("AUTHORIZATION_GRANT_CREATE_FAILED")), }); const { app } = buildApp({ store }); const response = await app.request("/api/v1/grants", { body: JSON.stringify({ permission: registeredPermission, subjectUserId }), headers: { "content-type": "application/json", origin: trustedOrigin }, method: "POST", }); expect(response.status).toBe(503); expect(await response.json()).toEqual({ code: "AUTHORIZATION_GRANT_CREATE_FAILED" }); }); }); describe("POST /api/v1/grants/:grantId/revoke", () => { it("rejects a request with no trusted Origin header before ever checking permission", async () => { const { app, evaluator } = buildApp({}); const response = await app.request(`/api/v1/grants/${grantId}/revoke`, { method: "POST" }); expect(response.status).toBe(403); expect(await response.json()).toEqual({ code: "UNTRUSTED_ORIGIN" }); expect(evaluator.authorize).not.toHaveBeenCalled(); }); it("revokes an explicit grant, deriving the actor server-side from the authenticated principal", async () => { const store = fakeStore({ revokeGrant: vi.fn().mockResolvedValue(undefined) }); const { app } = buildApp({ store }); const response = await app.request(`/api/v1/grants/${grantId}/revoke`, { headers: { origin: trustedOrigin }, method: "POST", }); expect(response.status).toBe(200); expect(await response.json()).toEqual({ success: true }); expect(store.revokeGrant).toHaveBeenCalledWith({ actor: { type: "user", userId: manager.userId }, grantId, sourceKind: "explicit", }); }); it("always constrains revocation to sourceKind explicit, so a relationship-derived grant can never be targeted by id", async () => { // The store itself enforces this by treating a sourceKind mismatch exactly like a missing // grant; this test proves the route always asks for that constraint, and correctly surfaces // the resulting "not found" as 404 rather than leaking that the grant exists but is the wrong // kind. const store = fakeStore({ revokeGrant: vi .fn() .mockRejectedValue(new AuthorizationStoreError("AUTHORIZATION_GRANT_NOT_FOUND")), }); const { app } = buildApp({ store }); const response = await app.request(`/api/v1/grants/${grantId}/revoke`, { headers: { origin: trustedOrigin }, method: "POST", }); expect(response.status).toBe(404); expect(await response.json()).toEqual({ code: "AUTHORIZATION_GRANT_NOT_FOUND" }); expect(store.revokeGrant).toHaveBeenCalledWith( expect.objectContaining({ sourceKind: "explicit" }), ); }); it("rejects a malformed grant id", async () => { const { app, store } = buildApp({}); const response = await app.request("/api/v1/grants/%20/revoke", { headers: { origin: trustedOrigin }, method: "POST", }); expect(response.status).toBe(400); expect(store.revokeGrant).not.toHaveBeenCalled(); }); it("maps a store infrastructure failure to a sanitized 503", async () => { const store = fakeStore({ revokeGrant: vi .fn() .mockRejectedValue(new AuthorizationStoreError("AUTHORIZATION_GRANT_REVOKE_FAILED")), }); const { app } = buildApp({ store }); const response = await app.request(`/api/v1/grants/${grantId}/revoke`, { headers: { origin: trustedOrigin }, method: "POST", }); expect(response.status).toBe(503); expect(await response.json()).toEqual({ code: "AUTHORIZATION_GRANT_REVOKE_FAILED" }); }); }); describe("Core grant mutation request body limit", () => { it("accepts a comfortably-under-limit grant-creation body and rejects one byte over the exact limit", async () => { const store = fakeStore(); const { app } = buildApp({ store }); const overSizedBody = "a".repeat(CORE_MUTATION_MAX_BODY_BYTES + 1); const overResponse = await app.request("/api/v1/grants", { body: overSizedBody, headers: { "content-length": String(overSizedBody.length), "content-type": "application/json", origin: trustedOrigin, }, method: "POST", }); expect(overResponse.status).toBe(413); expect(await overResponse.json()).toEqual({ code: "REQUEST_BODY_TOO_LARGE" }); expect(store.createGrant).not.toHaveBeenCalled(); // A well-formed, comfortably-under-limit payload still succeeds through the same route. const underResponse = await app.request("/api/v1/grants", { body: JSON.stringify({ permission: registeredPermission, subjectUserId }), headers: { "content-type": "application/json", origin: trustedOrigin }, method: "POST", }); expect(underResponse.status).not.toBe(413); }); it("rejects an oversized declared Content-Length on grant creation without invoking permission evaluation", async () => { const evaluatorAuthorize = vi.fn().mockResolvedValue({ allowed: true }); const evaluator: AuthorizationEvaluator = { authorize: evaluatorAuthorize }; const store = fakeStore(); const app = new Hono(); app.route( "/", createGrantsRoutes({ evaluator, registry: CORE_PERMISSION_REGISTRY, runtime: fakeRuntime(), store, trustedOrigins: new Set([trustedOrigin]), }), ); const response = await app.request("/api/v1/grants", { body: "a", headers: { "content-length": String(CORE_MUTATION_MAX_BODY_BYTES * 10), "content-type": "application/json", origin: trustedOrigin, }, method: "POST", }); expect(response.status).toBe(413); expect(evaluatorAuthorize).not.toHaveBeenCalled(); expect(store.createGrant).not.toHaveBeenCalled(); }); it("rejects an oversized streamed grant-creation body with no declared Content-Length", async () => { const store = fakeStore(); const { app } = buildApp({ store }); const chunk = new Uint8Array(1024).fill(97); let enqueued = 0; const stream = new ReadableStream({ pull(controller) { if (enqueued > CORE_MUTATION_MAX_BODY_BYTES) { controller.close(); return; } enqueued += chunk.length; controller.enqueue(chunk); }, }); const response = await app.request("/api/v1/grants", { body: stream, duplex: "half", headers: { "content-type": "application/json", origin: trustedOrigin }, method: "POST", } as RequestInit); expect(response.status).toBe(413); expect(await response.json()).toEqual({ code: "REQUEST_BODY_TOO_LARGE" }); expect(store.createGrant).not.toHaveBeenCalled(); }); it("does not read an oversized body from an untrusted Origin: the trusted-origin rejection wins first", async () => { const store = fakeStore(); const { app } = buildApp({ store }); const chunk = new Uint8Array(1024).fill(97); let pullCount = 0; const stream = new ReadableStream({ pull(controller) { pullCount += 1; if (pullCount > 3) { controller.close(); return; } controller.enqueue(chunk); }, }); const response = await app.request("/api/v1/grants", { body: stream, duplex: "half", headers: { "content-type": "application/json", origin: untrustedOrigin }, method: "POST", } as RequestInit); expect(response.status).toBe(403); expect(await response.json()).toEqual({ code: "UNTRUSTED_ORIGIN" }); expect(store.createGrant).not.toHaveBeenCalled(); }); it("also bounds the revoke mutation body", async () => { const store = fakeStore(); const { app } = buildApp({ store }); const response = await app.request(`/api/v1/grants/${grantId}/revoke`, { body: "a".repeat(CORE_MUTATION_MAX_BODY_BYTES + 1), headers: { "content-length": String(CORE_MUTATION_MAX_BODY_BYTES + 1), origin: trustedOrigin, }, method: "POST", }); expect(response.status).toBe(413); expect(store.revokeGrant).not.toHaveBeenCalled(); }); }); /** * Pass 3 request-contract regressions. Every case here asserts both the sanitized public response * *and* that the store was never reached, because the point of each fix is that a malformed or * unsupported request is decided at the request boundary rather than turning into database work or * an indistinguishable infrastructure failure. */ describe("explicit-grant request invariants", () => { const validExpiry = "2099-01-01T00:00:00.000Z"; async function post(app: Hono, body: unknown) { return await app.request("/api/v1/grants", { body: JSON.stringify(body), headers: { "content-type": "application/json", origin: trustedOrigin }, method: "POST", }); } it("rejects a scope field in any form, before any store mutation", async () => { // Core keeps scoped-grant semantics for trusted internal callers, but no HTTP middleware // supplies a scope to the evaluator, so a scope accepted here could never authorize anything. for (const scope of ["calendar-event:123", "", " ", null, 42, {}, []]) { const { app, store } = buildApp({}); const response = await post(app, { permission: registeredPermission, scope, subjectUserId }); expect(response.status, `scope ${JSON.stringify(scope)}`).toBe(400); expect(await response.json()).toEqual({ code: "INVALID_REQUEST" }); expect(response.headers.get("cache-control")).toBe("no-store"); expect(store.createGrant).not.toHaveBeenCalled(); } }); it("accepts exactly one canonical expiration representation", async () => { const createdGrant = { createdAt: new Date("2026-01-01T00:00:00.000Z"), expiresAt: new Date(validExpiry), id: grantId, permission: registeredPermission, sourceKind: "explicit" as const, userId: subjectUserId, }; const store = fakeStore({ createGrant: vi.fn().mockResolvedValue(createdGrant) }); const { app } = buildApp({ store }); const response = await post(app, { expiresAt: validExpiry, permission: registeredPermission, subjectUserId, }); expect(response.status).toBe(201); expect(store.createGrant).toHaveBeenCalledWith( expect.objectContaining({ expiresAt: new Date(validExpiry) }), ); }); it("rejects normalized-impossible, locale, offset, imprecise, padded, and past expirations", async () => { const now = new Date("2026-07-26T12:00:00.000Z"); for (const expiresAt of [ // Normalized impossible date: `new Date` silently rewrote this to March 3rd. "2026-02-31T00:00:00.000Z", "2026-13-01T00:00:00.000Z", // Locale-shaped and date-only forms. "March 5, 2027", "2027-01-01", // Missing milliseconds, excess fractional precision, offsets, lowercase separators. "2027-01-01T00:00:00Z", "2027-01-01T00:00:00.123456Z", "2027-01-01T00:00:00.000+02:00", "2027-01-01t00:00:00.000z", "2027-01-01T00:00:00.000+00:00", // Whitespace padding and trailing text. " 2027-01-01T00:00:00.000Z", "2027-01-01T00:00:00.000Z ", "2027-01-01T00:00:00.000Z extra", // Past and exactly-now are both dead on arrival. "2020-01-01T00:00:00.000Z", "2026-07-26T12:00:00.000Z", // Non-strings. 42, null, true, ]) { const { app, store } = buildApp({ now: () => now }); const response = await post(app, { expiresAt, permission: registeredPermission, subjectUserId, }); expect(response.status, `expiresAt ${JSON.stringify(expiresAt)}`).toBe(400); expect(await response.json()).toEqual({ code: "INVALID_REQUEST" }); expect(store.createGrant).not.toHaveBeenCalled(); } }); it("returns a sanitized 409 for an equivalent grant that is already effective", async () => { const store = fakeStore({ createGrant: vi .fn() .mockRejectedValue(new AuthorizationStoreError("AUTHORIZATION_GRANT_ALREADY_EXISTS")), }); const { app } = buildApp({ store }); const response = await post(app, { permission: registeredPermission, subjectUserId }); const raw = await response.text(); expect(response.status).toBe(409); expect(JSON.parse(raw)).toEqual({ code: "AUTHORIZATION_GRANT_ALREADY_EXISTS" }); expect(response.headers.get("cache-control")).toBe("no-store"); // The body is the stable code and nothing else — no existing grant id, actor, scope, source // reference, expiry, SQL, constraint name, or driver text. expect(Object.keys(JSON.parse(raw))).toEqual(["code"]); for (const leaked of [ grantId, manager.userId, subjectUserId, "calendar-event", "constraint", "select ", "insert ", "ekklesia_core_grant", "Error", "at ", ]) { expect(raw, `must not leak ${leaked}`).not.toContain(leaked); } }); it("rejects a non-canonical grant id on revoke without ever calling the store", async () => { for (const raw of [ "not-a-uuid", "0B36B1B6-9A29-4C4E-9E2D-4C0E4C5E6F70", "%200b36b1b6-9a29-4c4e-9e2d-4c0e4c5e6f70", "0b36b1b6-9a29-4c4e-9e2d-4c0e4c5e6f7", "0b36b1b6-9a29-4c4e-9e2d-4c0e4c5e6f700", "0b36b1b69a294c4e9e2d4c0e4c5e6f70", ]) { const { app, store } = buildApp({}); const response = await app.request(`/api/v1/grants/${raw}/revoke`, { headers: { origin: trustedOrigin }, method: "POST", }); expect(response.status, `grantId ${raw}`).toBe(400); expect(await response.json()).toEqual({ code: "INVALID_REQUEST" }); expect(response.headers.get("cache-control")).toBe("no-store"); expect(store.revokeGrant).not.toHaveBeenCalled(); } }); it("still returns the sanitized 404 for a canonical but unknown grant id", async () => { const store = fakeStore({ revokeGrant: vi .fn() .mockRejectedValue(new AuthorizationStoreError("AUTHORIZATION_GRANT_NOT_FOUND")), }); const { app } = buildApp({ store }); const response = await app.request(`/api/v1/grants/${grantId}/revoke`, { headers: { origin: trustedOrigin }, method: "POST", }); expect(response.status).toBe(404); expect(await response.json()).toEqual({ code: "AUTHORIZATION_GRANT_NOT_FOUND" }); expect(store.revokeGrant).toHaveBeenCalled(); }); }); describe("explicit-grant listing pagination", () => { function grantAt(index: number, iso: string) { return { createdAt: new Date(iso), id: parseGrantId(`00000000-0000-4000-8000-${String(index).padStart(12, "0")}`), permission: registeredPermission, sourceKind: "explicit" as const, userId: subjectUserId, }; } it("applies the default limit of 50 and accepts the 1 and 100 boundaries", async () => { for (const [query, expected] of [ ["", 50], ["&limit=1", 1], ["&limit=50", 50], ["&limit=100", 100], ] as const) { const { app, store } = buildApp({}); const response = await app.request(`/api/v1/grants?userId=${subjectUserId}${query}`); expect(response.status, query).toBe(200); expect(store.listGrantsForUser).toHaveBeenCalledWith(subjectUserId, { limit: expected, sourceKind: "explicit", }); } }); it("rejects every non-canonical or out-of-range limit before any store work", async () => { for (const limit of [ "0", "101", "-1", "+5", "1.0", "1.5", "01", "005", " 5", "5 ", "1e2", "", "abc", "0x10", "Infinity", "NaN", ]) { const { app, store } = buildApp({}); const response = await app.request( `/api/v1/grants?userId=${subjectUserId}&limit=${encodeURIComponent(limit)}`, ); expect(response.status, `limit ${JSON.stringify(limit)}`).toBe(400); expect(await response.json()).toEqual({ code: "INVALID_REQUEST" }); expect(response.headers.get("cache-control")).toBe("no-store"); expect(store.listGrantsForUser).not.toHaveBeenCalled(); } }); it("rejects unknown and duplicated query keys", async () => { for (const query of [ `userId=${subjectUserId}&sort=asc`, `userId=${subjectUserId}&offset=10`, `userId=${subjectUserId}&sourceKind=relationship`, `userId=${subjectUserId}&userId=${subjectUserId}`, `userId=${subjectUserId}&limit=5&limit=6`, `userId=${subjectUserId}&cursor=a&cursor=b`, `userId=${subjectUserId}&cursor=`, ]) { const { app, store } = buildApp({}); const response = await app.request(`/api/v1/grants?${query}`); expect(response.status, query).toBe(400); expect(store.listGrantsForUser).not.toHaveBeenCalled(); } }); it("emits a cursor only when another page exists, and traverses without duplicates or omissions", async () => { // Three rows sharing one `created_at`, which is exactly the case a timestamp-only keyset // boundary would skip or repeat. const sameInstant = "2026-01-01T00:00:00.000Z"; const all = [grantAt(3, sameInstant), grantAt(2, sameInstant), grantAt(1, sameInstant)]; const seen: string[] = []; let cursor: string | null = null; let pages = 0; do { const store = fakeStore({ listGrantsForUser: vi.fn().mockImplementation((_userId, listOptions) => { const startIndex = listOptions.after === undefined ? 0 : all.findIndex((grant) => grant.id === listOptions.after.id) + 1; const slice = all.slice(startIndex, startIndex + listOptions.limit); const more = startIndex + listOptions.limit < all.length; const last = slice.at(-1); return Promise.resolve({ grants: slice, ...(more && last ? { nextBoundary: { createdAt: last.createdAt, id: last.id } } : {}), }); }), }); const { app } = buildApp({ store }); const url: string = `/api/v1/grants?userId=${subjectUserId}&limit=1${ cursor === null ? "" : `&cursor=${encodeURIComponent(cursor)}` }`; const response = await app.request(url); const body = (await response.json()) as { grants: { id: string }[]; nextCursor: string | null; }; expect(response.status).toBe(200); expect(response.headers.get("cache-control")).toBe("no-store"); expect(body.grants).toHaveLength(1); seen.push(body.grants[0]?.id as string); cursor = body.nextCursor; pages += 1; } while (cursor !== null && pages < 10); expect(pages).toBe(3); expect(seen).toEqual(all.map((grant) => grant.id)); expect(new Set(seen).size).toBe(3); // The final page ends the traversal explicitly. expect(cursor).toBeNull(); }); it("refuses a cursor issued for a different subject, and every malformed encoding", async () => { const boundary = { createdAt: new Date("2026-01-01T00:00:00.000Z"), id: grantId }; const store = fakeStore({ listGrantsForUser: vi.fn().mockResolvedValue({ grants: [grantAt(1, boundary.createdAt.toISOString())], nextBoundary: boundary, }), }); const { app: issuingApp } = buildApp({ store }); const issued = (await ( await issuingApp.request(`/api/v1/grants?userId=${subjectUserId}&limit=1`) ).json()) as { nextCursor: string }; expect(issued.nextCursor).toMatch(/^[A-Za-z0-9_-]+$/u); const encode = (payload: unknown) => Buffer.from(JSON.stringify(payload), "utf8").toString("base64url"); const canonical = { g: grantId, t: "2026-01-01T00:00:00.000Z", u: subjectUserId, v: 1, }; for (const [label, cursor] of [ // A cursor minted for a different subject must not be replayable against this one. ["other subject", encode({ ...canonical, u: "someone_else_1" })], ["wrong version", encode({ ...canonical, v: 2 })], ["extra field", encode({ ...canonical, extra: "x" })], ["missing field", encode({ g: canonical.g, t: canonical.t, u: canonical.u })], ["invalid timestamp", encode({ ...canonical, t: "2026-01-01" })], ["non-canonical timestamp", encode({ ...canonical, t: "2026-01-01T00:00:00Z" })], ["invalid grant id", encode({ ...canonical, g: "not-a-uuid" })], ["array payload", encode([canonical])], ["not json", Buffer.from("nonsense", "utf8").toString("base64url")], ["padded base64", `${Buffer.from(JSON.stringify(canonical), "utf8").toString("base64")}`], ["malformed base64url", "!!!not-base64!!!"], ["trailing data", `${encode(canonical)}AA`], ] as const) { const { app, store: pagedStore } = buildApp({}); const response = await app.request( `/api/v1/grants?userId=${subjectUserId}&cursor=${encodeURIComponent(cursor)}`, ); const raw = await response.text(); expect(response.status, label).toBe(400); expect(JSON.parse(raw)).toEqual({ code: "INVALID_REQUEST" }); // A rejected cursor reveals nothing it decoded, and does zero store/database work. expect(raw).not.toContain(grantId); expect(raw).not.toContain("someone_else_1"); expect(pagedStore.listGrantsForUser).not.toHaveBeenCalled(); } // The cursor the API itself issued is accepted. const { app: acceptingApp, store: acceptingStore } = buildApp({}); const accepted = await acceptingApp.request( `/api/v1/grants?userId=${subjectUserId}&cursor=${encodeURIComponent(issued.nextCursor)}`, ); expect(accepted.status).toBe(200); expect(acceptingStore.listGrantsForUser).toHaveBeenCalledWith(subjectUserId, { after: boundary, limit: 50, sourceKind: "explicit", }); }); });