Files
2026-05-27 23:29:53 +01:00

7.2 KiB

Tasks — Auth requireApiPermission middleware

Every checkbox MUST reflect work actually done in this change. Do not retroactively tick.

0. Pre-flight (non-negotiable)

  • 0.1 Confirm clean working tree (git status reports nothing) and no in-flight worktrees with uncommitted work (git worktree list then git status in each)
  • 0.2 If any worktrees exist under .claude/worktrees/, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (git worktree remove) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
  • 0.3 Confirm the package set in MANIFEST.md matches find packages -maxdepth 3 -name package.json count. Drift here means a prior change archived without regenerating the manifest.

Audit Note (2026-05-02)

Minimal progress — DEFERRED, do not archive.

  • No packages/auth/auth/src/api-keys/require-api-permission/ directory exists.
  • grep -rn "requireApiPermission\|requireAnyApiPermission\|requireAllApiPermissions" packages/auth/auth/src/ returns zero hits.
  • bunx turbo lint typecheck test --filter=@suaveplan/auth is GREEN (161 tests) — but the green is on the existing surface; the new exports promised by this proposal are not implemented.
  • All 43 tasks remain to be done as written (spec authoring, three-middleware implementation, OTel counter, Zod boundary validation, sibling .md, README, subpath export, conformance test).

1. Spec authoring

  • 1.1 Read ../suaveplan/enterprise/auth.md end-to-end (confirm requireApiPermission is documented at L1-L100 baseline)
  • 1.2 Read openspec/specs/auth/spec.md (canonical capability spec; this change introduces it if absent)
  • 1.3 Read openspec/AGENTS.md Sections 13 and 14
  • 1.4 Read existing packages/auth/auth/src/api-keys/api-keys.ts and auth-middleware/auth-middleware.ts to confirm the MiddlewareFn and next(error) patterns
  • 1.5 Draft proposal.md from openspec/templates/proposal.md
  • 1.6 Fill ## Source mapping table with upstream api-keys.ts L487-L506 reference
  • 1.7 Draft specs/auth/spec.md from openspec/templates/spec-delta.md
  • 1.8 Every requirement carries a **Verified by:** line
  • 1.9 openspec change validate auth-require-api-permission exits with no errors

2. Implementation

  • 2.1 Create new feature directory packages/auth/auth/src/api-keys/require-api-permission/
  • 2.2 Implement requireApiPermission(permission, options?): MiddlewareFn in require-api-permission.ts:
    1. Read req.apiKey (cast through the same r as Record<string, unknown> pattern used in validateApiKeyMiddleware).
    2. If absent, call next(new ApiKeyInvalidError({ message: "API key required for permission check" })) and return.
    3. If present, check hasPermission(apiKey, permission). On miss, call next(new PermissionDeniedError({ permission, userId: apiKey.userId })).
    4. On match, call next().
  • 2.3 Implement requireAnyApiPermission(permissions, options?): MiddlewareFn — passes if permissions.some(p => hasPermission(apiKey, p))
  • 2.4 Implement requireAllApiPermissions(permissions, options?): MiddlewareFn — passes if permissions.every(p => hasPermission(apiKey, p))
  • 2.5 Emit OTel counter auth.apikey.permission_check_total tagged { result: "granted" | "denied" | "missing-key" }
  • 2.6 Reject empty permissions array in requireAnyApiPermission / requireAllApiPermissions at construction time (throw ApiKeyInvalidError with message: "permissions array must be non-empty")
  • 2.7 Write barrel index.ts re-exporting the three middleware factories
  • 2.8 Sibling .md (≥200 words, basic + advanced examples)

3. Wiring

  • 3.1 Update packages/auth/auth/src/index.ts to re-export requireApiPermission, requireAnyApiPermission, requireAllApiPermissions from the new feature directory
  • 3.2 Add subpath export ./api-keys/require-api-permission/require-api-permission to packages/auth/auth/package.json exports
  • 3.3 Update root README.md ## Modules section to link the new co-located .md

4. Tests

  • 4.1 packages/auth/auth/src/api-keys/require-api-permission/require-api-permission.test.ts — happy path (apiKey has permission, next() called with no args); missing req.apiKey (next(ApiKeyInvalidError)); missing permission (next(PermissionDeniedError)); wildcard * permission grants any (next() called with no args)
  • 4.2 requireAnyApiPermission tests — passes with subset match; fails when none match; throws on empty array
  • 4.3 requireAllApiPermissions tests — passes when all match (including via wildcard); fails on partial match; throws on empty array
  • 4.4 packages/auth/auth/src/api-keys/require-api-permission/require-api-permission.spec-conformance.test.ts — expectTypeOf<ReturnType<typeof requireApiPermission>>().toMatchTypeOf<MiddlewareFn>(); OTel counter auth.apikey.permission_check_total increments with the right result tag for each branch; works under Express-style (req, res, next) AND Hono-style (c, next) adapter shapes
  • 4.5 Every it(...) carries { timeout: 5000 }

5. Documentation

  • 5.1 Sibling .md for require-api-permission.ts (≥200 words, basic + advanced examples, full API reference covering all three factories)
  • 5.2 README links the new .md under ## Modules
  • 5.3 Update api-keys.md ## Features bullet list to mention the new permission-check middleware (cross-link, do not duplicate the API reference)

6. Pre-archive verification (the 5-gate triple)

  • 6.1 bun run check — Biome 0/0/0 across the monorepo
  • 6.2 bunx turbo lint typecheck test --filter=@suaveplan/auth — green
  • 6.3 bunx turbo test:coverage --filter=@suaveplan/auth — 100/100/100/100
  • 6.4 bun scripts/openspec-conformance.ts @suaveplan/auth — exit 0, no error findings
  • 6.5 bunx turbo build --filter=@suaveplan/auth — green

7. Pre-archive 7-row checklist (AGENTS.md Section 13)

  • 7.1 Generics threaded; expectTypeOf assertion that the return type matches MiddlewareFn
  • 7.2 Zod schemas wired at the public boundary — accept permission: string validated as non-empty
  • 7.3 Existing error codes (AUTH.API_KEY_INVALID, AUTH.PERMISSION_DENIED) thrown from the new production paths; no new codes registered (none needed)
  • 7.4 Strict-mode tested: explicit undefined apiKey vs missing apiKey property both route to ApiKeyInvalidError
  • 7.5 OTel counter auth.apikey.permission_check_total emitted from production paths and asserted with a real in-memory MeterProvider
  • 7.6 Lifecycle dedup: middleware is a fresh closure per call to the factory; verified that two factory calls produce independent MiddlewareFn instances
  • 7.7 No retroactive tick — every box reflects work done in this change

8. Archive

  • 8.1 Fold spec delta into openspec/specs/auth/spec.md (creating it if absent)
  • 8.2 Move openspec/changes/auth-require-api-permission/ → openspec/archive/<yyyy-mm-dd>-auth-require-api-permission/
  • 8.3 Refresh MANIFEST.md (no package set or dep changes expected, but rerun for safety)