first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View 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.