5.3 KiB
5.3 KiB
name, description
| name | description |
|---|---|
| sp-api-auth | Authentication-strategy meta-package for the Fetch-native API platform. Re-exports anonymous, static, bearer, and JWT strategies from standalone sibling packages, and owns the chain combinator (first-non-null-wins composition, fail-closed on throw). |
@suaveplan/api-auth
Purpose
Authentication-strategy meta-package for the Suaveplan API platform. Each strategy is a factory returning a frozen SessionResolver (Request -> Principal | null) that plugs into createApi({ sessionResolver }) to populate ctx.principal before any route handler executes.
Following the api-<seam>-<plugin> extension convention, the concrete strategies live in standalone sibling packages. @suaveplan/api-auth owns the chain combinator and re-exports every strategy on identical subpaths for back-compat.
Architecture
| Component | Source | Role |
|---|---|---|
createResolverChain |
src/chain/chain.ts |
First-non-null-wins composition of SessionResolvers; fail-closed on throw |
createAnonymousResolver |
src/anonymous/index.ts |
Re-export from @suaveplan/api-auth-anonymous |
createStaticResolver |
src/static/index.ts |
Re-export from @suaveplan/api-auth-static |
createBearerResolver |
src/bearer/index.ts |
Re-export from @suaveplan/api-auth-bearer |
createJwtResolver |
src/jwt/index.ts |
Re-export from @suaveplan/api-auth-jwt |
AUTH_CODES / AuthError |
src/error-codes/index.ts |
Re-export from @suaveplan/api-auth-codes |
File Structure
src/
index.ts # Re-exports all strategies + chain + error codes
chain/
chain.ts # createResolverChain implementation
chain.test.ts # Unit tests for chain composition
index.ts # Subpath barrel
anonymous/index.ts # Re-export barrel → @suaveplan/api-auth-anonymous
bearer/index.ts # Re-export barrel → @suaveplan/api-auth-bearer
jwt/index.ts # Re-export barrel → @suaveplan/api-auth-jwt
static/index.ts # Re-export barrel → @suaveplan/api-auth-static
error-codes/index.ts # Re-export barrel → @suaveplan/api-auth-codes
Entry Points
| Import | Exports |
|---|---|
@suaveplan/api-auth |
All strategies + chain + AUTH_CODES + AuthError |
@suaveplan/api-auth/chain |
createResolverChain |
@suaveplan/api-auth/anonymous |
createAnonymousResolver |
@suaveplan/api-auth/static |
createStaticResolver |
@suaveplan/api-auth/bearer |
createBearerResolver |
@suaveplan/api-auth/jwt |
createJwtResolver, defaultClaimsToPrincipal |
@suaveplan/api-auth/error-codes |
AUTH_CODES, AuthError |
Patterns
Chain composition (JWT → Bearer → Anonymous)
import { createApi } from "@suaveplan/api";
import { createJwtResolver } from "@suaveplan/api-auth/jwt";
import { createBearerResolver } from "@suaveplan/api-auth/bearer";
import { createAnonymousResolver } from "@suaveplan/api-auth/anonymous";
import { createResolverChain } from "@suaveplan/api-auth/chain";
const runtime = createApi({
plugins: [/* ... */],
sessionResolver: createResolverChain(
createJwtResolver({ hmacSecret: process.env.JWT_SECRET! }),
createBearerResolver({
tokens: [{ token: "sk_live_abc123", principal: { id: "u_alice", scopes: ["billing:read"], claims: {} } }],
}),
createAnonymousResolver(),
),
});
Fail-closed on resolver throw
If any resolver in the chain throws, the chain re-raises AUTH.VERIFIER_FAILED wrapping the cause:
import { AUTH_CODES, AuthError } from "@suaveplan/api-auth/error-codes";
// This will throw AuthError with code AUTH_CODES.VERIFIER_FAILED
try {
await chain.resolve(request);
} catch (err) {
if (err instanceof AuthError && err.code === AUTH_CODES.VERIFIER_FAILED) {
// Infrastructure failure — not an unauthenticated request
}
}
Empty chain guard
import { createResolverChain } from "@suaveplan/api-auth/chain";
// Throws AuthError AUTH_CODES.CHAIN_EMPTY at construction
createResolverChain(); // ❌ at least one resolver required
Constraints
- Zero runtime strategy code — all strategies are re-exports from standalone sibling packages; this package only owns
createResolverChain. - Fail-closed — resolver throws are never swallowed; they surface as
AUTH.VERIFIER_FAILED. - Short-circuit — the first non-null principal wins; later resolvers are not invoked.
- Empty chain — construction with zero resolvers raises
AUTH_CODES.CHAIN_EMPTY. - Dependencies:
@suaveplan/api-auth-{anonymous,bearer,codes,jwt,static},@suaveplan/api-types,tslib.
Error Codes
| Code | Raised When |
|---|---|
AUTH.CHAIN_EMPTY |
createResolverChain() called with zero resolvers |
AUTH.VERIFIER_FAILED |
A resolver in the chain threw (fail-closed) |
AUTH.OPTIONS_INVALID |
Raised by sibling strategy packages (e.g. empty bearer table) |
Checklist
- Read the chain implementation in
src/chain/chain.tsbefore extending composition behaviour - Read
src/chain/chain.test.tsbefore modifying chain semantics - If adding a new strategy subpath, create the standalone
api-auth-<strategy>package first, then add a re-export barrel here - Keep the
api-<seam>-<plugin>convention — strategies are plugins for the auth seam