Files
suaveplan/openspec/archive/2026-05-25-api-auth-provider/proposal.md
2026-05-27 23:29:53 +01:00

4.8 KiB

@suaveplan/api-auth-provider — abstract account-lifecycle contract

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

Why

Account lifecycle (register, login, logout, refresh, oauth/*, verify-email, ...) is the second half of the auth split (design §2.3). It is OPTIONAL — sites using hosted IdPs (Clerk, Auth0, Cognito) don't need a provider; they only need a session resolver. Sites running their own auth pick one provider: Better-Auth (api-auth-better-auth, Stage 6) or the rolled-our-own reference (api-auth-rolled, Stage 8). This package owns the abstract contract: the route surface a provider owns, the path-ownership declaration (default /auth/*, configurable), and the collision detection that runs at install time so plugin routes can't accidentally squat on auth paths.

Per design §2.3: "Each provider adapter declares which paths it owns (default /auth/*, configurable). Host reserves the prefix and refuses plugin routes that collide."

What changes

Net-new package. Contract types + the route-collision detector + the conformance harness.

  • ADDED: ./provider — abstract AuthProviderAdapter: pathPrefix: string (default /auth), routes(router, ctx) callback registering provider routes, lifecycle: { install, start, stop, uninstall } hooks.
  • ADDED: ./route-surface — declarative ProviderRouteSurface: the canonical set of paths a provider OWNS (/register, /login, /logout, /refresh, /oauth/:provider/start, /oauth/:provider/callback, /verify-email/:token, /forgot-password, /reset-password). Concrete providers MAY implement a subset; manifest declares which.
  • ADDED: ./path-ownership — PathOwnership shape: { prefix: string, owned: readonly string[] }; runtime registry tracking which paths are reserved by an installed provider.
  • ADDED: ./collision-detector — detectAuthPathCollisions({ provider, plugins }): CollisionReport; runs at install time. Plugin routes whose prefix overlaps with provider.pathPrefix AND aren't publicRoutes MUST be reported as AUTH_PROVIDER.PATH_COLLISION.
  • ADDED: ./capability-id — adds "auth.provider" to the CapabilityId union.
  • ADDED: ./error-codes — frozen AUTH_PROVIDER_CODES: PATH_COLLISION, MULTIPLE_PROVIDERS_REGISTERED, PROVIDER_LIFECYCLE_FAILED, OAUTH_CALLBACK_INVALID_STATE.
  • ADDED: ./testing — runAuthProviderConformance(factory) reusable harness.

CapabilityId additions

This package contributes "auth.provider" to the CapabilityId union — separate from "auth.session" so a plugin can require session resolution without forcing the host to ship account-lifecycle routes.

Out of scope

  • Concrete providers — api-auth-better-auth (Stage 6), api-auth-rolled (Stage 8).
  • The Principal shape or session resolution — that's api-auth + api-types.
  • OAuth state-store implementation — provider-specific.

Source mapping

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

Requirement Design doc section
Path ownership (/auth/*, configurable) §2.3 (lines 192-198)
Collision detection at install §2.3 ("Host reserves the prefix and refuses plugin routes that collide")
Provider lifecycle hooks §4 (FSM inherited from generic plugin lifecycle)
Provider route surface (register/login/oauth/verify-email) §2.3 (lines 193-194)

Requirements with no source line

Requirement Justification
MULTIPLE_PROVIDERS_REGISTERED enforcement Implicit in §2.3 ("Mix-and-match" describes a single provider + session resolvers chain). Pinned here so the host's behavior is explicit.
runAuthProviderConformance harness Stage-5 deliverable (§13.4).

Verification approach

  • src/provider/provider.spec-conformance.test.ts — interface shape.
  • src/route-surface/route-surface.test.ts — canonical path set documented; providers can declare a subset.
  • src/collision-detector/collision-detector.test.ts — overlap rules: /auth/register collides with plugin route /auth/x; /auth/register does NOT collide with plugin route /billing/x; configurable prefix /myauth shifts the collision domain.
  • src/path-ownership/path-ownership.test.ts — registry rejects double-registration (MULTIPLE_PROVIDERS_REGISTERED).
  • src/capability-id/capability-id.spec-conformance.test.ts — "auth.provider" in CapabilityId.
  • tests/dep-graph.test.ts — deps: @suaveplan/api-types, @suaveplan/api-router (peer, for Router type), @suaveplan/error, @suaveplan/types, zod.

Migration impact

None — additive only.

Impact

Affected dependents

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