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

4.4 KiB

@suaveplan/api-auth — abstract session-resolver contract

Package: @suaveplan/api-auth Status: PROPOSED Type: new-package Stage: 1 (Contracts) — see umbrella openspec/changes/api-plugin-foundation/

Why

Conflating session resolution and account lifecycle is what makes most plugin systems painful to integrate with third-party auth (design §2.3). api-auth is the session-resolver half: Request → Principal | null, used on EVERY request, called at step 3 of the per-request pipeline (§6). Concrete adapters (api-auth-jwt, api-auth-anonymous, api-auth-bearer-static, api-auth-better-auth, api-auth-rolled) plug behind this contract. Multiple resolvers can be chained — host config takes an ordered chain, first match wins (e.g. JWT for users + bearer-static for service callers).

The contract also covers SecuritySchemeContribution — every resolver declares what OpenAPI securitySchemes entry it contributes (api-openapi reads these — design §7 step 3).

What changes

Net-new package. Contract types + the chain runner + the conformance harness.

  • ADDED: ./resolver — abstract SessionResolver: resolve(req: Request): Promise<Principal | null>; dispose(): Promise<void>.
  • ADDED: ./chain — SessionResolverChain: ordered list of resolvers; runs each in order, first non-null wins; returns null if all return null.
  • ADDED: ./security-scheme — SecuritySchemeContribution: { name: string, scheme: OpenApiSecurityScheme }; every resolver MUST contribute one so api-openapi can emit the right securitySchemes block.
  • ADDED: ./csrf-policy — CsrfPolicy: requiresCsrf(scheme): boolean; cookie-based schemes default ON (per §6), JWT/Bearer default OFF.
  • ADDED: ./session-adapter — composite AuthAdapter interface: { resolver, securityScheme, csrf }; what concrete adapters export.
  • ADDED: ./error-codes — frozen AUTH_CODES: RESOLVER_THREW, CHAIN_EMPTY, PRINCIPAL_INVALID_SHAPE, SECURITY_SCHEME_MISSING.
  • ADDED: ./capability-id — adds "auth.session" to the CapabilityId union via type-level augmentation.
  • ADDED: ./testing — runAuthSessionConformance(factory) reusable harness.

CapabilityId additions

This package contributes "auth.session" to the CapabilityId union. (The provider-side "auth.provider" is contributed by api-auth-provider — they're intentionally separate capabilities so a plugin can require session resolution without forcing the host to ship account-lifecycle routes.)

Out of scope

  • The Principal type itself — that's in @suaveplan/api-types.
  • Account lifecycle (login/register/oauth) — that's api-auth-provider.
  • Concrete adapters (api-auth-jwt, api-auth-anonymous, api-auth-bearer-static, api-auth-better-auth, api-auth-rolled) — Stages 4/6/8.

Source mapping

Genesis-original. Source of design intent: docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md.

Requirement Design doc section
Session-resolver shape §2.3 (top half)
Chained resolvers (first match wins) §2.3 ("ordered chain, first match wins")
SecuritySchemeContribution for OpenAPI §7 step 3
CSRF default ON for cookie auth §6 ("Cookie-based auth adapters trigger CSRF verification")
401 only if non-public AND null §6 step 3

Requirements with no source line

Requirement Justification
runAuthSessionConformance harness Stage-5 deliverable (§13.4).

Verification approach

  • src/resolver/resolver.spec-conformance.test.ts — SessionResolver shape; dispose lifecycle.
  • src/chain/chain.test.ts — ordering: A returns null → B runs; A returns Principal → B skipped. Empty chain → AUTH.CHAIN_EMPTY.
  • src/chain/chain.test.ts — A throws → AUTH.RESOLVER_THREW surfaces; chain does NOT continue to B (fail closed).
  • src/security-scheme/security-scheme.test.ts — resolver MUST contribute a scheme; missing → AUTH.SECURITY_SCHEME_MISSING.
  • src/csrf-policy/csrf-policy.test.ts — cookie scheme default ON; JWT/Bearer default OFF.
  • src/capability-id/capability-id.spec-conformance.test.ts — "auth.session" is in CapabilityId.
  • tests/dep-graph.test.ts — deps: @suaveplan/api-types, @suaveplan/error, @suaveplan/types, zod.

Migration impact

None — additive only.

Impact

Affected dependents

  • (none) — [x] no action needed (reason: net-new; concrete adapters land Stages 4/6/8)