first commit
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
# `provider` — `AuthProviderAdapter` contract + path-prefix validation
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The `provider` module is the type-level + thin-runtime heart of
|
||||
`@suaveplan/api-auth-provider`. It declares the abstract `AuthProviderAdapter`
|
||||
interface every concrete account-lifecycle provider (Better-Auth, the rolled
|
||||
reference, future Cognito/Auth0 bridges) structurally implements, and ships
|
||||
the Zod-validated `authPathPrefix` constructor that asserts a deployment's
|
||||
configured prefix is a legal segment-aligned mount point. By splitting the
|
||||
contract from any concrete provider we keep this package optional: a
|
||||
hosted-IdP deployment installs the session resolver (`api-auth`) only and
|
||||
never pulls in a provider implementation.
|
||||
|
||||
## Features
|
||||
|
||||
- `AuthProviderAdapter<Caps>` interface with `id`, configurable `pathPrefix`,
|
||||
`routes(router, ctx)` registration callback, and the four-hook
|
||||
`AuthProviderLifecycle` quartet (`install`/`start`/`stop`/`uninstall`).
|
||||
- `AuthPathPrefix` opaque brand — protects against accidental string
|
||||
substitution at the install boundary.
|
||||
- `AuthPathPrefixSchema` — a Zod schema enforcing leading slash, no trailing
|
||||
slash (except `/`), no path parameters, no whitespace.
|
||||
- `authPathPrefix(value)` constructor — returns the branded value or throws
|
||||
`AUTH_PROVIDER.PROVIDER_LIFECYCLE_FAILED` with a structured `reason`.
|
||||
- `isAuthPathPrefix(value)` predicate — non-throwing variant for tests.
|
||||
- `DEFAULT_AUTH_PATH_PREFIX` constant (`"/auth"`).
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import {
|
||||
authPathPrefix,
|
||||
DEFAULT_AUTH_PATH_PREFIX,
|
||||
type AuthProviderAdapter,
|
||||
} from "@suaveplan/api-auth-provider/provider";
|
||||
|
||||
const myProvider: AuthProviderAdapter = {
|
||||
id: "rolled",
|
||||
pathPrefix: authPathPrefix(DEFAULT_AUTH_PATH_PREFIX),
|
||||
routes(router) {
|
||||
router.post("/login", { auth: null }, async () => new Response());
|
||||
},
|
||||
lifecycle: {
|
||||
install: async () => {},
|
||||
start: async () => {},
|
||||
stop: async () => {},
|
||||
uninstall: async () => {},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
A deployment that mounts its provider under a non-standard prefix MUST run
|
||||
the input through `authPathPrefix` so the value is validated before the host
|
||||
records the claim in the path-ownership registry.
|
||||
|
||||
```ts
|
||||
import { authPathPrefix } from "@suaveplan/api-auth-provider/provider";
|
||||
|
||||
const prefix = authPathPrefix(process.env.AUTH_PREFIX ?? "/auth/v2");
|
||||
// Throws AUTH_PROVIDER.PROVIDER_LIFECYCLE_FAILED on input like
|
||||
// "/auth/v2/" or "/auth/:tenant".
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `interface AuthProviderAdapter<Caps>` — provider contract.
|
||||
- `interface AuthProviderLifecycle<Caps>` — install/start/stop/uninstall.
|
||||
- `type AuthPathPrefix` — branded prefix string.
|
||||
- `const AuthPathPrefixSchema: ZodType` — validation schema.
|
||||
- `function authPathPrefix(value: string): AuthPathPrefix` — throwing
|
||||
constructor.
|
||||
- `function isAuthPathPrefix(value: string): boolean` — predicate.
|
||||
- `const DEFAULT_AUTH_PATH_PREFIX = "/auth"` — host default.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The brand is purely a TypeScript construct (`unique symbol`) — at runtime
|
||||
`authPathPrefix("/auth")` returns the string unchanged. The schema's order of
|
||||
refinements is significant: the leading-slash check fires first so callers
|
||||
see the most actionable error message. Whitespace is rejected before the
|
||||
parameter check because shell-supplied configuration is the typical source of
|
||||
both classes of error. The constructor surfaces failures as
|
||||
`PROVIDER_LIFECYCLE_FAILED` (not a hypothetical `INVALID_PREFIX`) because the
|
||||
install lifecycle is the only legitimate call site — keeping the code small
|
||||
makes the error registry tighter and the metadata table denser.
|
||||
Reference in New Issue
Block a user