first commit
This commit is contained in:
195
docs/api-stack/api/api-plugin-auth/README.md
Normal file
195
docs/api-stack/api/api-plugin-auth/README.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# @suaveplan/api-plugin-auth - Better-Auth-first authentication plugin for @suaveplan/api. Wraps Better-Auth as a @suaveplan/api-plugin definePlugin descriptor, applies the canonical 17-table SurrealDB schema at boot via @suaveplan/api-migrations, mounts 9 OpenAPI routes through @suaveplan/api-openapi, registers a typed authService in the host ServiceRegistry, installs a global session-resolver middleware that puts a typed Principal on every Hono context, and exports requireAuth / requireRole / requireEmailVerified middleware factories. Server-tier — runs in Bun and Node. Reference implementation of the 3-way plug-and-play contract (.surql DDL + Zod schemas at boundary + OpenAPI route registration) every @suaveplan/api-plugin-* follows.
|
||||
|
||||
Better-Auth-first authentication plugin for `@suaveplan/api`. The
|
||||
canonical exemplar of the 3-way plug-and-play contract every
|
||||
`@suaveplan/api-plugin-*` package follows: a plugin owns its `.surql`
|
||||
DDL, its Zod boundary schemas, and its OpenAPI route definitions —
|
||||
all wired into the host's `PluginRegistry`, `MigrationRunner`, and
|
||||
`OpenAPIRegistry` from a single `definePlugin` descriptor.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [error-codes](./src/error-codes/error-codes.md) — The error-codes module owns the stable `AUTH.*` code registry and the `AuthError` class used by every production failur…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — The error-codes module owns the stable `AUTH.*` code registry and the `AuthError` class used by every production failur…
|
||||
- [context-augmentation](./src/middleware/context-augmentation.md) — This module installs the canonical Hono `ContextVariableMap` augmentation that lets every consumer call `c.get("user")`…
|
||||
- [require-auth](./src/middleware/require-auth.md) — `requireAuth()` is the canonical Hono middleware factory for gating a route or sub-route on an authenticated principal.…
|
||||
- [require-email-verified](./src/middleware/require-email-verified.md) — `requireEmailVerified()` is the Hono middleware factory that gates a route on the principal's email having been verifie…
|
||||
- [require-role](./src/middleware/require-role.md) — `requireRole(...roles)` builds a Hono middleware that gates a route on the principal carrying at least one of the suppl…
|
||||
- [session](./src/middleware/session.md) — `sessionMiddleware` is the global Hono middleware that resolves every incoming request's Better-Auth session and expose…
|
||||
- [otel](./src/otel/instruments.md) — The instruments module owns the OpenTelemetry counters and histogram emitted by every code path inside `@suaveplan/api-…
|
||||
- [instruments](./src/otel/instruments.md) — The instruments module owns the OpenTelemetry counters and histogram emitted by every code path inside `@suaveplan/api-…
|
||||
- [plugin](./src/plugin/plugin.md) — `createAuthPlugin(options)` is the factory that returns a typed `PluginDescriptor<AuthPluginConfig>` ready to register…
|
||||
- [plugin](./src/plugin/plugin.md) — `createAuthPlugin(options)` is the factory that returns a typed `PluginDescriptor<AuthPluginConfig>` ready to register…
|
||||
- [principal](./src/principal/principal.md) — The principal module defines the canonical authenticated-user shape used across the entire `@suaveplan/api-plugin-*` fa…
|
||||
- [principal](./src/principal/principal.md) — The principal module defines the canonical authenticated-user shape used across the entire `@suaveplan/api-plugin-*` fa…
|
||||
- [routes](./src/routes/build-routes.md) — `buildAuthRoutes(auth)` returns the 9 `PluginRouteEntry` values that `createAuthPlugin` mounts on the host's Hono app v…
|
||||
- [build-routes](./src/routes/build-routes.md) — `buildAuthRoutes(auth)` returns the 9 `PluginRouteEntry` values that `createAuthPlugin` mounts on the host's Hono app v…
|
||||
- [schema](./src/schema/schema.md) — This module re-exports the canonical Better-Auth SurrealQL schema shipped by `@suaveplan/better-auth-surrealdb` so cons…
|
||||
- [schema](./src/schema/schema.md) — This module re-exports the canonical Better-Auth SurrealQL schema shipped by `@suaveplan/better-auth-surrealdb` so cons…
|
||||
- [forgot-password](./src/schemas/routes/forgot-password.md) — Schemas for `POST /auth/forgot-password`. The body schema accepts a single email and the response is `{ sent: true }`.…
|
||||
- [oauth](./src/schemas/routes/oauth.md) — Schemas for the OAuth pair `GET /auth/oauth/:provider/start` and `GET /auth/oauth/:provider/callback`. The start endpoi…
|
||||
- [reset-password](./src/schemas/routes/reset-password.md) — Schemas for `POST /auth/reset-password`. The body accepts the single-use token previously emailed to the user plus the…
|
||||
- [session](./src/schemas/routes/session.md) — Response schema for `GET /auth/session`. Returns the current authenticated `Principal` and the matching Better-Auth ses…
|
||||
- [sign-in](./src/schemas/routes/sign-in.md) — Request/response Zod schemas for `POST /auth/sign-in`. The body schema validates email + password credential input; the…
|
||||
- [sign-out](./src/schemas/routes/sign-out.md) — Schemas for `POST /auth/sign-out`. The body is optional (Better-Auth identifies the session from the cookie or `Authori…
|
||||
- [sign-up](./src/schemas/routes/sign-up.md) — Request/response Zod schemas for `POST /auth/sign-up`. The body schema constrains the email + password + optional name…
|
||||
- [verify-email](./src/schemas/routes/verify-email.md) — Schemas for `POST /auth/verify-email`. The body carries the single-use verification token (typically emailed from a sig…
|
||||
- [options](./src/schemas/options.md) — The options module exports `AuthPluginOptionsSchema`, the Zod validator that `createAuthPlugin` runs at the factory bou…
|
||||
- [principal](./src/schemas/principal.md) — This module exports the canonical Zod schemas for the auth boundary — `PrincipalSchema`, `SessionSchema`, `AccountSchem…
|
||||
- [auth-service](./src/services/auth-service.md) — The `auth-service` module owns the `AuthService` interface, its constructor (`createAuthService`), and the side-effect…
|
||||
- [registry-augmentation](./src/services/registry-augmentation.md) — Module augmentation that wires the `authService` slot into `@suaveplan/api-plugin/service-registry`'s `ServiceRegistry`…
|
||||
- [types](./src/types/types.md) — Top-level type definitions for `@suaveplan/api-plugin-auth`. This module exports the `AuthPluginOptions` shape (what ca…
|
||||
- [types](./src/types/types.md) — Top-level type definitions for `@suaveplan/api-plugin-auth`. This module exports the `AuthPluginOptions` shape (what ca…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Quick start
|
||||
|
||||
```ts
|
||||
import { defineApp } from "@suaveplan/api";
|
||||
import { betterAuthSurrealAdapter } from "@suaveplan/better-auth-surrealdb";
|
||||
import { createAuthPlugin } from "@suaveplan/api-plugin-auth";
|
||||
|
||||
const auth = await defineApp()
|
||||
.name("my-api")
|
||||
.use(
|
||||
createAuthPlugin({
|
||||
betterAuth: {
|
||||
database: betterAuthSurrealAdapter({ client: surreal }),
|
||||
secret: process.env.BETTER_AUTH_SECRET!,
|
||||
baseURL: "http://localhost:3000",
|
||||
emailAndPassword: { enabled: true },
|
||||
},
|
||||
})
|
||||
)
|
||||
.start();
|
||||
```
|
||||
|
||||
`createAuthPlugin` returns a typed `PluginDescriptor`. The host's
|
||||
`PluginRegistry` calls `onInit(ctx)` which:
|
||||
|
||||
1. Resolves `app`, `openAPIRegistry`, `migrationRunner`, and `db` from
|
||||
the host `ServiceRegistry`.
|
||||
2. Applies `BETTER_AUTH_SURREAL_SCHEMA` (the canonical 17-table DDL
|
||||
shipped by `@suaveplan/better-auth-surrealdb`) — uses the
|
||||
`migrationRunner` when registered, otherwise calls
|
||||
`applyBetterAuthSchema(db)`.
|
||||
3. Constructs the Better-Auth instance.
|
||||
4. Registers `authService` in the typed `ServiceRegistry`.
|
||||
5. Mounts 9 OpenAPI routes via `definePluginRoutes` so they appear in
|
||||
the unified `/openapi.json` doc with full Zod schemas.
|
||||
6. Installs a global session-resolver middleware so every request's
|
||||
`c.get("user")` is a typed `Principal | null`.
|
||||
|
||||
## The 3-way plug-and-play contract
|
||||
|
||||
The auth plugin is the canonical example of the plugin
|
||||
contract. Three artefacts move together; the plugin owns all three.
|
||||
|
||||
| Surface | Implementation |
|
||||
|---|---|
|
||||
| `.surql` DDL | Re-exports `BETTER_AUTH_SURREAL_SCHEMA` (17 tables) and applies it via the host's `MigrationRunner` |
|
||||
| Zod schemas | Per-route bodies + responses + the canonical `PrincipalSchema` / `SessionSchema` / `AccountSchema` at `./schemas/principal` |
|
||||
| OpenAPI routes | `buildAuthRoutes(auth)` returns 9 `RouteBuilder` entries; `definePluginRoutes` writes both the Hono app AND the `OpenAPIRegistry` |
|
||||
|
||||
User and vault plugins follow the same shape — read this README to
|
||||
understand the contract, then look at `api-plugin-user` / `api-plugin-vault`
|
||||
for second and third examples.
|
||||
|
||||
## Middleware recipes
|
||||
|
||||
```ts
|
||||
import {
|
||||
requireAuth,
|
||||
requireRole,
|
||||
requireEmailVerified,
|
||||
} from "@suaveplan/api-plugin-auth";
|
||||
|
||||
app.use("/me", requireAuth());
|
||||
app.use("/admin/*", requireAuth(), requireRole("admin"));
|
||||
app.use("/billing/*", requireAuth(), requireEmailVerified());
|
||||
```
|
||||
|
||||
Each middleware throws a typed `AuthError(AUTH_*)` carrying
|
||||
`context.statusCode` so the host's error-handler returns the right
|
||||
HTTP status (401 / 403). The error-code registry has 10 entries —
|
||||
see `./src/error-codes/error-codes.md` for the full list.
|
||||
|
||||
## OAuth + organisations + 2FA
|
||||
|
||||
Better-Auth handles every plugin / provider option. Pass them through
|
||||
`options.betterAuth` verbatim:
|
||||
|
||||
```ts
|
||||
createAuthPlugin({
|
||||
betterAuth: {
|
||||
database: betterAuthSurrealAdapter({ client: surreal }),
|
||||
secret: process.env.BETTER_AUTH_SECRET!,
|
||||
baseURL: "https://app.example.test",
|
||||
emailAndPassword: { enabled: true },
|
||||
socialProviders: {
|
||||
github: {
|
||||
clientId: process.env.GITHUB_CLIENT_ID!,
|
||||
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The plugin re-emits Better-Auth's full router under `routePrefix` —
|
||||
9 OpenAPI-typed routes are advertised; the remaining surface (e.g.
|
||||
the OAuth provider list) is still reachable but not documented in the
|
||||
served OpenAPI doc.
|
||||
|
||||
## Schema apply + migrations integration
|
||||
|
||||
The plugin applies the 17-table DDL during `onInit` by default. Two
|
||||
paths are supported:
|
||||
|
||||
- **`migrationRunner` (preferred)** — the runner registers the schema
|
||||
as a tracked migration named `0000-better-auth-schema` so subsequent
|
||||
custom migrations can build on it.
|
||||
- **`db` fallback** — when no runner is registered, the plugin calls
|
||||
`applyBetterAuthSchema(db)` directly. The DDL is idempotent
|
||||
(`DEFINE TABLE OVERWRITE`), so re-running is safe.
|
||||
|
||||
Disable the apply step entirely with `applySchemaOnInit: false` when
|
||||
operators manage migrations externally.
|
||||
|
||||
## Customising the principal
|
||||
|
||||
Override the Better-Auth → `Principal` mapping with
|
||||
`options.principalMapping` to project plugin-extended user fields
|
||||
(organisations, 2FA flags) onto the resolved principal. The default
|
||||
mapping (`mapBetterAuthUserToPrincipal`) covers email, verification
|
||||
state, name/image metadata, and `activeOrganizationId` → `tenantId`.
|
||||
|
||||
```ts
|
||||
import { mapBetterAuthUserToPrincipal } from "@suaveplan/api-plugin-auth";
|
||||
|
||||
createAuthPlugin({
|
||||
betterAuth,
|
||||
principalMapping: (view) => {
|
||||
const base = mapBetterAuthUserToPrincipal(view);
|
||||
const session = view.session as { roles?: string[] };
|
||||
return { ...base, roles: [...base.roles, ...(session.roles ?? [])] };
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Subpath exports
|
||||
|
||||
`.`, `./plugin/plugin`, `./middleware/require-auth`,
|
||||
`./middleware/require-role`, `./middleware/require-email-verified`,
|
||||
`./middleware/session`, `./principal/principal`,
|
||||
`./services/auth-service`, `./schemas/principal`, `./schemas/options`,
|
||||
`./schemas/routes`, `./routes/routes`, `./schema/schema`,
|
||||
`./error-codes/error-codes`, `./otel/instruments`, `./types/types`.
|
||||
|
||||
## Peer deps
|
||||
|
||||
- `better-auth ^1.4` (resolves to 1.6.x via the catalog).
|
||||
- `hono`, `@hono/zod-openapi`, `zod` — supplied by the host
|
||||
application; the plugin imports them directly.
|
||||
|
||||
Reference in New Issue
Block a user