Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
12 KiB
@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.
Documentation tree
- error-codes — The error-codes module owns the stable
AUTH.*code registry and theAuthErrorclass used by every production failur…- error-codes — The error-codes module owns the stable
AUTH.*code registry and theAuthErrorclass used by every production failur…
- error-codes — The error-codes module owns the stable
- context-augmentation — This module installs the canonical Hono
ContextVariableMapaugmentation that lets every consumer callc.get("user")… - require-auth —
requireAuth()is the canonical Hono middleware factory for gating a route or sub-route on an authenticated principal.… - require-email-verified —
requireEmailVerified()is the Hono middleware factory that gates a route on the principal's email having been verifie… - require-role —
requireRole(...roles)builds a Hono middleware that gates a route on the principal carrying at least one of the suppl… - session —
sessionMiddlewareis the global Hono middleware that resolves every incoming request's Better-Auth session and expose… - otel — The instruments module owns the OpenTelemetry counters and histogram emitted by every code path inside `@suaveplan/api-…
- instruments — The instruments module owns the OpenTelemetry counters and histogram emitted by every code path inside `@suaveplan/api-…
- plugin —
createAuthPlugin(options)is the factory that returns a typedPluginDescriptor<AuthPluginConfig>ready to register…- plugin —
createAuthPlugin(options)is the factory that returns a typedPluginDescriptor<AuthPluginConfig>ready to register…
- plugin —
- principal — The principal module defines the canonical authenticated-user shape used across the entire
@suaveplan/api-plugin-*fa…- principal — The principal module defines the canonical authenticated-user shape used across the entire
@suaveplan/api-plugin-*fa…
- principal — The principal module defines the canonical authenticated-user shape used across the entire
- routes —
buildAuthRoutes(auth)returns the 9PluginRouteEntryvalues thatcreateAuthPluginmounts on the host's Hono app v…- build-routes —
buildAuthRoutes(auth)returns the 9PluginRouteEntryvalues thatcreateAuthPluginmounts on the host's Hono app v…
- build-routes —
- schema — This module re-exports the canonical Better-Auth SurrealQL schema shipped by
@suaveplan/better-auth-surrealdbso cons…- schema — This module re-exports the canonical Better-Auth SurrealQL schema shipped by
@suaveplan/better-auth-surrealdbso cons…
- schema — This module re-exports the canonical Better-Auth SurrealQL schema shipped by
- forgot-password — Schemas for
POST /auth/forgot-password. The body schema accepts a single email and the response is{ sent: true }.… - oauth — Schemas for the OAuth pair
GET /auth/oauth/:provider/startandGET /auth/oauth/:provider/callback. The start endpoi… - reset-password — Schemas for
POST /auth/reset-password. The body accepts the single-use token previously emailed to the user plus the… - session — Response schema for
GET /auth/session. Returns the current authenticatedPrincipaland the matching Better-Auth ses… - sign-in — Request/response Zod schemas for
POST /auth/sign-in. The body schema validates email + password credential input; the… - sign-out — Schemas for
POST /auth/sign-out. The body is optional (Better-Auth identifies the session from the cookie or `Authori… - sign-up — Request/response Zod schemas for
POST /auth/sign-up. The body schema constrains the email + password + optional name… - verify-email — Schemas for
POST /auth/verify-email. The body carries the single-use verification token (typically emailed from a sig… - options — The options module exports
AuthPluginOptionsSchema, the Zod validator thatcreateAuthPluginruns at the factory bou… - principal — This module exports the canonical Zod schemas for the auth boundary —
PrincipalSchema,SessionSchema, `AccountSchem… - auth-service — The
auth-servicemodule owns theAuthServiceinterface, its constructor (createAuthService), and the side-effect… - registry-augmentation — Module augmentation that wires the
authServiceslot into@suaveplan/api-plugin/service-registry'sServiceRegistry… - types — Top-level type definitions for
@suaveplan/api-plugin-auth. This module exports theAuthPluginOptionsshape (what ca…- types — Top-level type definitions for
@suaveplan/api-plugin-auth. This module exports theAuthPluginOptionsshape (what ca…
- types — Top-level type definitions for
Quick start
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:
- Resolves
app,openAPIRegistry,migrationRunner, anddbfrom the hostServiceRegistry. - Applies
BETTER_AUTH_SURREAL_SCHEMA(the canonical 17-table DDL shipped by@suaveplan/better-auth-surrealdb) — uses themigrationRunnerwhen registered, otherwise callsapplyBetterAuthSchema(db). - Constructs the Better-Auth instance.
- Registers
authServicein the typedServiceRegistry. - Mounts 9 OpenAPI routes via
definePluginRoutesso they appear in the unified/openapi.jsondoc with full Zod schemas. - Installs a global session-resolver middleware so every request's
c.get("user")is a typedPrincipal | 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
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:
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 named0000-better-auth-schemaso subsequent custom migrations can build on it.dbfallback — when no runner is registered, the plugin callsapplyBetterAuthSchema(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.
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.