Move docs/ package mirror into document-navigator plugin
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>
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
# schemas/options
|
||||
|
||||
## Purpose
|
||||
|
||||
The options module exports `AuthPluginOptionsSchema`, the Zod validator
|
||||
that `createAuthPlugin` runs at the factory boundary. The schema both
|
||||
validates user input and applies defaults so the descriptor's lifecycle
|
||||
hooks read a fully-resolved options object — never a partial input.
|
||||
|
||||
## Features
|
||||
|
||||
- Required `betterAuth` field (forwarded verbatim to Better-Auth's own
|
||||
`betterAuth(options)` factory) typed as `Record<string, unknown>`.
|
||||
- Default `routePrefix: "/auth"` with a regex that rejects missing
|
||||
leading slash and trailing slashes (which produce broken Hono routes).
|
||||
- Default `applySchemaOnInit: true` — set `false` to disable the
|
||||
built-in SurrealQL schema apply step when operators manage migrations
|
||||
externally.
|
||||
- Optional `principalMapping` callback for projecting Better-Auth output
|
||||
onto a custom `Principal` extension. Validated as a function.
|
||||
- Optional `LoggerLike`-compatible `logger`. Defaults to the
|
||||
`PluginContext` logger when omitted.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { AuthPluginOptionsSchema } from "@suaveplan/api-plugin-auth/schemas/options";
|
||||
|
||||
const opts = AuthPluginOptionsSchema.parse({ betterAuth: {} });
|
||||
// opts.routePrefix === "/auth"; opts.applySchemaOnInit === true
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Use the schema in a custom CLI that asks the operator for a non-default
|
||||
prefix and a custom mapping function. `safeParse` lets you surface the
|
||||
ZodError directly to the user instead of letting `createAuthPlugin`
|
||||
throw:
|
||||
|
||||
```ts
|
||||
import { AuthPluginOptionsSchema } from "@suaveplan/api-plugin-auth/schemas/options";
|
||||
import { mapBetterAuthUserToPrincipal } from "@suaveplan/api-plugin-auth/principal/principal";
|
||||
|
||||
const result = AuthPluginOptionsSchema.safeParse({
|
||||
betterAuth: { secret: process.env["BETTER_AUTH_SECRET"] },
|
||||
routePrefix: "/api/v1/auth",
|
||||
applySchemaOnInit: false,
|
||||
principalMapping: (view) => ({
|
||||
...mapBetterAuthUserToPrincipal(view as never),
|
||||
roles: ["service"],
|
||||
}),
|
||||
});
|
||||
if (!result.success) {
|
||||
console.error(result.error.format());
|
||||
process.exit(1);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `AuthPluginOptionsSchema: ZodObject`
|
||||
|
||||
Required:
|
||||
|
||||
- `betterAuth: Record<string, unknown>` — forwarded to Better-Auth's
|
||||
factory.
|
||||
|
||||
With defaults:
|
||||
|
||||
- `routePrefix: string = "/auth"` — must match
|
||||
`^/[A-Za-z0-9\-_/]*[A-Za-z0-9\-_]$` or be exactly `/`.
|
||||
- `applySchemaOnInit: boolean = true`.
|
||||
|
||||
Optional:
|
||||
|
||||
- `principalMapping?: (view: unknown) => unknown`.
|
||||
- `logger?: LoggerLike` with all 4 methods (`debug`, `info`, `warn`,
|
||||
`error`).
|
||||
|
||||
### `AuthPluginOptionsInput` / `AuthPluginOptionsResolved`
|
||||
|
||||
`z.input` and `z.output` aliases so consumers can reference both the
|
||||
caller surface (defaults optional) and the post-parse surface (defaults
|
||||
present) without re-deriving the types.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- `betterAuth` is typed as `Record<string, unknown>` rather than the
|
||||
upstream `BetterAuthOptions` because pinning to a specific Better-Auth
|
||||
type version would couple the plugin to a single minor. Runtime
|
||||
validation is delegated to `betterAuth(...)` itself.
|
||||
- The route-prefix regex accepts nested paths (`/api/v1/auth`) and
|
||||
hyphenated segments while rejecting `auth` (no leading slash) and
|
||||
`/auth/` (trailing slash). The exact-root `/` form is allowed because
|
||||
some hosts mount auth at the root path.
|
||||
- `logger` is validated structurally rather than nominally (via a
|
||||
branded type) so any LoggerLike-shaped object from
|
||||
`@suaveplan/types` or a third-party adapter is accepted without
|
||||
cross-package coupling.
|
||||
- `principalMapping` is typed loosely on input (`(view: unknown) =>
|
||||
unknown`); the plugin re-applies the strict `BetterAuthSessionView`
|
||||
type at the call site so authors get a typed `view` argument inside
|
||||
their mapping function.
|
||||
@@ -0,0 +1,90 @@
|
||||
# schemas/principal
|
||||
|
||||
## Purpose
|
||||
|
||||
This module exports the canonical Zod schemas for the auth boundary —
|
||||
`PrincipalSchema`, `SessionSchema`, `AccountSchema` — and their inferred
|
||||
TypeScript types. Sibling plugins (`@suaveplan/api-plugin-user`,
|
||||
`@suaveplan/api-plugin-vault`, …) consume these schemas directly via
|
||||
the `./schemas/principal` subpath so the wire shape of an authenticated
|
||||
user is one type, defined once.
|
||||
|
||||
## Features
|
||||
|
||||
- `PrincipalSchema` matches the runtime `Principal` interface from
|
||||
`../principal/principal.ts` field-for-field.
|
||||
- `SessionSchema` matches the Better-Auth `session` row 1:1 (including
|
||||
optional plugin-extended fields used by the organisations plugin).
|
||||
- `AccountSchema` matches the Better-Auth `account` row — exposed so
|
||||
downstream test fixtures can validate persisted rows without
|
||||
duplicating the schema.
|
||||
- Every schema exports its `z.infer` companion type so consumers can
|
||||
reference `PrincipalSchemaType` without re-deriving it.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { PrincipalSchema } from "@suaveplan/api-plugin-auth/schemas/principal";
|
||||
|
||||
const parsed = PrincipalSchema.parse({
|
||||
id: "u1",
|
||||
emailVerified: true,
|
||||
roles: [],
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Compose the schema into route response envelopes via
|
||||
`@suaveplan/api-openapi`'s `successResponse` helper so the OpenAPI
|
||||
document carries the full principal shape on every route that returns it:
|
||||
|
||||
```ts
|
||||
import z from "zod";
|
||||
import {
|
||||
PrincipalSchema,
|
||||
SessionSchema,
|
||||
} from "@suaveplan/api-plugin-auth/schemas/principal";
|
||||
import { successResponse } from "@suaveplan/api-openapi";
|
||||
|
||||
const SignInResponseSchema = successResponse(
|
||||
z.object({ user: PrincipalSchema, session: SessionSchema })
|
||||
);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `PrincipalSchema`
|
||||
|
||||
Zod object with: `id` (non-empty string, required), `email` (email,
|
||||
optional), `emailVerified` (boolean, required), `roles` (string array,
|
||||
required), `tenantId` (non-empty string, optional), `metadata`
|
||||
(`Record<string, unknown>`, optional). Inferred type:
|
||||
`PrincipalSchemaType`.
|
||||
|
||||
### `SessionSchema`
|
||||
|
||||
Zod object mirroring the Better-Auth session row. Every field optional
|
||||
to tolerate plugin-extended sessions. Inferred type:
|
||||
`SessionSchemaType`.
|
||||
|
||||
### `AccountSchema`
|
||||
|
||||
Zod object mirroring the Better-Auth account row. `id`, `userId`,
|
||||
`providerId`, `accountId` are required; OAuth token fields and credential
|
||||
password are optional. Inferred type: `AccountSchemaType`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- `PrincipalSchema` enforces `email` is an RFC-5322-ish string when
|
||||
present — this matches Better-Auth's own validation surface and is
|
||||
forwarded to the OpenAPI spec as `format: email`.
|
||||
- `roles` is the canonical permission carrier; the schema accepts a
|
||||
plain string array (the runtime type is `readonly`, but Zod treats
|
||||
arrays uniformly so consumers can pass either).
|
||||
- `SessionSchema` accepts `expiresAt` as either ISO string or `Date` so
|
||||
it round-trips both wire and in-process shapes.
|
||||
- `AccountSchema` does NOT mark `password` as sensitive at the Zod
|
||||
layer; redaction is the responsibility of the route serialiser. The
|
||||
schema is exposed so test code can read/assert account rows from
|
||||
SurrealDB directly.
|
||||
@@ -0,0 +1,71 @@
|
||||
# schemas/routes/forgot-password
|
||||
|
||||
## Purpose
|
||||
|
||||
Schemas for `POST /auth/forgot-password`. The body schema accepts a
|
||||
single email and the response is `{ sent: true }`. The response shape
|
||||
is deliberately uninformative — leaking whether the email exists in the
|
||||
user table is a known enumeration vector, so the route returns the same
|
||||
"sent" token whether or not it actually emitted an email.
|
||||
|
||||
## Features
|
||||
|
||||
- `ForgotPasswordBodySchema` — `{ email: string (RFC-5322) }`.
|
||||
- `ForgotPasswordResponseSchema` — `{ sent: literal(true) }`.
|
||||
- Inferred companions `ForgotPasswordBody` and `ForgotPasswordResponse`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { ForgotPasswordBodySchema } from "@suaveplan/api-plugin-auth/schemas/routes";
|
||||
|
||||
const body = ForgotPasswordBodySchema.parse(await c.req.json());
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Combine with a rate-limit middleware so the route can't be used to
|
||||
probe email addresses by timing the response. The schema keeps the
|
||||
response shape opaque even when the request is rate-limited:
|
||||
|
||||
```ts
|
||||
import { POST, successResponse } from "@suaveplan/api-openapi";
|
||||
import {
|
||||
ForgotPasswordBodySchema,
|
||||
ForgotPasswordResponseSchema,
|
||||
} from "@suaveplan/api-plugin-auth/schemas/routes";
|
||||
|
||||
const forgotRoute = POST("/auth/forgot-password")
|
||||
.tags("auth")
|
||||
.summary("Send a password-reset email")
|
||||
.body(ForgotPasswordBodySchema)
|
||||
.response(successResponse(ForgotPasswordResponseSchema))
|
||||
.build();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `ForgotPasswordBodySchema`
|
||||
|
||||
Required: `email: string (email)`. Inferred type `ForgotPasswordBody`.
|
||||
|
||||
### `ForgotPasswordResponseSchema`
|
||||
|
||||
`{ sent: true }`. Inferred type `ForgotPasswordResponse`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The schema rejects malformed emails at the boundary so Better-Auth's
|
||||
email delivery layer never has to handle garbage input.
|
||||
- The response always returns `sent: true` regardless of whether the
|
||||
email exists in the user table — the canonical pattern for avoiding
|
||||
account enumeration through forgot-password endpoints.
|
||||
- The schema does not include locale or template selection; those are
|
||||
Better-Auth configuration concerns, not request-body concerns.
|
||||
- Operators that want different responses for known vs unknown emails
|
||||
(e.g. throttling differently) should configure rate limiting at the
|
||||
network edge rather than altering the schema — the constant-shape
|
||||
response is the privacy guarantee the schema exists to enforce.
|
||||
- The route mounts under the configured `routePrefix`, so the fully
|
||||
qualified path is typically `/auth/forgot-password` when the
|
||||
default prefix is used.
|
||||
@@ -0,0 +1,106 @@
|
||||
# schemas/routes/oauth
|
||||
|
||||
## Purpose
|
||||
|
||||
Schemas for the OAuth pair `GET /auth/oauth/:provider/start` and
|
||||
`GET /auth/oauth/:provider/callback`. The start endpoint initiates an
|
||||
OAuth flow (typically redirecting to the provider's authorization
|
||||
endpoint); the callback receives the provider's `code` + `state` and
|
||||
finishes the flow.
|
||||
|
||||
## Features
|
||||
|
||||
- `OAuthStartParamsSchema` — `{ provider: string (min 1) }` for the
|
||||
path parameter.
|
||||
- `OAuthStartQuerySchema` — `{ redirectTo?: string (url) }` so callers
|
||||
can request a specific post-login redirect.
|
||||
- `OAuthStartResponseSchema` — `{ redirectTo: string (url) }`; the
|
||||
body shape used when the response is a 200 + JSON pointer rather than
|
||||
a direct 302.
|
||||
- `OAuthCallbackParamsSchema` + `OAuthCallbackQuerySchema` — `{ code,
|
||||
state }` from the provider.
|
||||
- `OAuthCallbackResponseSchema` — `{ redirectTo: string (url) }` for
|
||||
the same 200 + JSON pointer variant.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { OAuthStartQuerySchema, OAuthCallbackQuerySchema } from "@suaveplan/api-plugin-auth/schemas/routes";
|
||||
|
||||
const startQuery = OAuthStartQuerySchema.parse({ redirectTo: "https://example.test/" });
|
||||
const callbackQuery = OAuthCallbackQuerySchema.parse({ code: "abc", state: "xyz" });
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Use the params + query + response shapes inside a `RouteBuilder` chain
|
||||
so the OpenAPI doc advertises a fully-typed OAuth flow including the
|
||||
required `state` field on the callback:
|
||||
|
||||
```ts
|
||||
import { GET, successResponse } from "@suaveplan/api-openapi";
|
||||
import {
|
||||
OAuthCallbackParamsSchema,
|
||||
OAuthCallbackQuerySchema,
|
||||
OAuthCallbackResponseSchema,
|
||||
OAuthStartParamsSchema,
|
||||
OAuthStartQuerySchema,
|
||||
OAuthStartResponseSchema,
|
||||
} from "@suaveplan/api-plugin-auth/schemas/routes";
|
||||
|
||||
const startRoute = GET("/auth/oauth/{provider}/start")
|
||||
.tags("auth", "oauth")
|
||||
.summary("Begin OAuth flow with the named provider")
|
||||
.params(OAuthStartParamsSchema)
|
||||
.query(OAuthStartQuerySchema)
|
||||
.response(successResponse(OAuthStartResponseSchema))
|
||||
.build();
|
||||
|
||||
const callbackRoute = GET("/auth/oauth/{provider}/callback")
|
||||
.tags("auth", "oauth")
|
||||
.summary("OAuth callback — exchange code for session")
|
||||
.params(OAuthCallbackParamsSchema)
|
||||
.query(OAuthCallbackQuerySchema)
|
||||
.response(successResponse(OAuthCallbackResponseSchema))
|
||||
.build();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `OAuthStartParamsSchema`
|
||||
|
||||
`{ provider: string (min 1) }`. Inferred type `OAuthStartParams`.
|
||||
|
||||
### `OAuthStartQuerySchema`
|
||||
|
||||
`{ redirectTo?: string (url) }`. Inferred type `OAuthStartQuery`.
|
||||
|
||||
### `OAuthStartResponseSchema`
|
||||
|
||||
`{ redirectTo: string (url) }`.
|
||||
|
||||
### `OAuthCallbackParamsSchema`
|
||||
|
||||
`{ provider: string (min 1) }`. Inferred type `OAuthCallbackParams`.
|
||||
|
||||
### `OAuthCallbackQuerySchema`
|
||||
|
||||
`{ code: string (min 1); state: string (min 1) }`. Inferred type
|
||||
`OAuthCallbackQuery`.
|
||||
|
||||
### `OAuthCallbackResponseSchema`
|
||||
|
||||
`{ redirectTo: string (url) }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The OAuth pair is documented as 200-with-JSON-redirect in OpenAPI for
|
||||
ergonomics — Better-Auth's runtime handler may return a 302 directly,
|
||||
which is compatible with the OpenAPI doc because consumers reading
|
||||
the spec see the "redirectTo" pointer either way.
|
||||
- The callback's `state` field is required (Better-Auth always emits
|
||||
it) so CSRF protection is enforced at the schema layer; callers that
|
||||
drop `state` fail Zod validation before Better-Auth ever runs.
|
||||
- Provider validation is structural — the route accepts any provider
|
||||
string; Better-Auth's configured providers map decides whether the
|
||||
call succeeds at runtime.
|
||||
@@ -0,0 +1,77 @@
|
||||
# schemas/routes/reset-password
|
||||
|
||||
## Purpose
|
||||
|
||||
Schemas for `POST /auth/reset-password`. The body accepts the
|
||||
single-use token previously emailed to the user plus the new password;
|
||||
the response is `{ success: true }`. Better-Auth's downstream handler
|
||||
validates the token, hashes the new password, and clears the
|
||||
verification row.
|
||||
|
||||
## Features
|
||||
|
||||
- `ResetPasswordBodySchema` — `{ token: string (min 1), password:
|
||||
string (min 8) }`.
|
||||
- `ResetPasswordResponseSchema` — `{ success: literal(true) }`.
|
||||
- Inferred companions `ResetPasswordBody` and `ResetPasswordResponse`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { ResetPasswordBodySchema } from "@suaveplan/api-plugin-auth/schemas/routes";
|
||||
|
||||
const body = ResetPasswordBodySchema.parse(await c.req.json());
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Combine the schema with the standard error response so the OpenAPI
|
||||
document advertises the 400 (invalid token) and 422 (password too weak)
|
||||
cases consumers need to handle:
|
||||
|
||||
```ts
|
||||
import { POST, successResponse } from "@suaveplan/api-openapi";
|
||||
import {
|
||||
ResetPasswordBodySchema,
|
||||
ResetPasswordResponseSchema,
|
||||
} from "@suaveplan/api-plugin-auth/schemas/routes";
|
||||
|
||||
const resetRoute = POST("/auth/reset-password")
|
||||
.tags("auth")
|
||||
.summary("Apply a password reset using a single-use token")
|
||||
.body(ResetPasswordBodySchema)
|
||||
.response(successResponse(ResetPasswordResponseSchema))
|
||||
.build();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `ResetPasswordBodySchema`
|
||||
|
||||
Required: `token: string (min 1)`, `password: string (min 8)`. Inferred
|
||||
type `ResetPasswordBody`.
|
||||
|
||||
### `ResetPasswordResponseSchema`
|
||||
|
||||
`{ success: true }`. Inferred type `ResetPasswordResponse`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The 8-character minimum on `password` matches Better-Auth's default
|
||||
password policy. Stricter policies are configured downstream; the
|
||||
schema is a floor, not a ceiling.
|
||||
- The token is opaque from the schema's perspective — its structure
|
||||
(HMAC, JWT, or random base64) is a Better-Auth implementation detail
|
||||
and the schema only ensures the field is non-empty.
|
||||
- The response is a literal `true` so the typed return narrows
|
||||
correctly on the client side.
|
||||
- Better-Auth's downstream handler enforces single-use semantics —
|
||||
the schema does not need to track whether the token was previously
|
||||
redeemed. Replaying a consumed token surfaces a 400 from
|
||||
Better-Auth, which the host's error-handler middleware translates
|
||||
into the standard error envelope.
|
||||
- Hashing the new password happens inside Better-Auth using its
|
||||
configured hashing strategy (bcrypt, argon2, scrypt). The schema
|
||||
only checks length; the policy layer (zxcvbn, breached-password
|
||||
checks) is configured upstream and runs before this route is
|
||||
invoked.
|
||||
@@ -0,0 +1,64 @@
|
||||
# schemas/routes/session
|
||||
|
||||
## Purpose
|
||||
|
||||
Response schema for `GET /auth/session`. Returns the current
|
||||
authenticated `Principal` and the matching Better-Auth session row, or
|
||||
`null` for both fields when the request is anonymous. The endpoint is
|
||||
the canonical "who am I" call for client SDKs and SSR hydration.
|
||||
|
||||
## Features
|
||||
|
||||
- `GetSessionResponseSchema` — `{ user: PrincipalSchema | null;
|
||||
session: SessionSchema | null }` so the response carries the exact
|
||||
same wire shape as `c.get("user")` plus the session row.
|
||||
- Anonymous callers see `{ user: null, session: null }` — a 200 with
|
||||
null fields rather than a 401, so client SDKs can probe identity
|
||||
without error handling.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { GetSessionResponseSchema } from "@suaveplan/api-plugin-auth/schemas/routes";
|
||||
|
||||
const parsed = GetSessionResponseSchema.parse({ user: null, session: null });
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Pair the schema with a route definition that lists both anonymous and
|
||||
authenticated cases in the OpenAPI document so consumers know the
|
||||
endpoint never 401s:
|
||||
|
||||
```ts
|
||||
import { GET, successResponse } from "@suaveplan/api-openapi";
|
||||
import {
|
||||
GetSessionResponseSchema,
|
||||
} from "@suaveplan/api-plugin-auth/schemas/routes";
|
||||
|
||||
const sessionRoute = GET("/auth/session")
|
||||
.tags("auth")
|
||||
.summary("Resolve the current session, if any")
|
||||
.response(successResponse(GetSessionResponseSchema))
|
||||
.build();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `GetSessionResponseSchema`
|
||||
|
||||
`{ user: PrincipalSchema | null; session: SessionSchema | null }`.
|
||||
Inferred type `GetSessionResponse`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- Returning `null` for the anonymous case keeps the endpoint cacheable
|
||||
per cookie — a 401 response would force the CDN to either skip caching
|
||||
or treat the page as private. Some host setups place this endpoint
|
||||
behind a Service-Worker cache.
|
||||
- The schema mirrors the runtime shape installed by the session
|
||||
middleware so consumers can use the same type to read either the HTTP
|
||||
response body or `c.get("user")` / `c.get("session")`.
|
||||
- Better-Auth's session row carries plugin-extended fields (org
|
||||
membership, impersonation) — those are tolerated via the loose schema
|
||||
shape on `SessionSchema` so the API is forward-compatible.
|
||||
@@ -0,0 +1,73 @@
|
||||
# schemas/routes/sign-in
|
||||
|
||||
## Purpose
|
||||
|
||||
Request/response Zod schemas for `POST /auth/sign-in`. The body schema
|
||||
validates email + password credential input; the response describes
|
||||
the user + session pair Better-Auth returns on a successful sign-in.
|
||||
These schemas form one half of the canonical credential lifecycle —
|
||||
sign-up and sign-in share the same response shape so client code can
|
||||
treat the two endpoints uniformly.
|
||||
|
||||
## Features
|
||||
|
||||
- `SignInBodySchema` — `email` (RFC-5322), `password` (min 1, since
|
||||
Better-Auth performs the policy check downstream and the schema only
|
||||
guards against empty strings here).
|
||||
- `SignInResponseSchema` — `{ user: PrincipalSchema; session:
|
||||
SessionSchema }` matching the wire shape Better-Auth produces.
|
||||
- Inferred companions `SignInBody` and `SignInResponse`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { SignInBodySchema } from "@suaveplan/api-plugin-auth/schemas/routes";
|
||||
|
||||
const body = SignInBodySchema.parse(await c.req.json());
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Combine the schema with the `requireAuth` middleware downstream so
|
||||
consumers can build a `/sign-in-then-call-private-route` flow with full
|
||||
type inference:
|
||||
|
||||
```ts
|
||||
import { POST, successResponse } from "@suaveplan/api-openapi";
|
||||
import {
|
||||
SignInBodySchema,
|
||||
SignInResponseSchema,
|
||||
} from "@suaveplan/api-plugin-auth/schemas/routes";
|
||||
|
||||
const signInRoute = POST("/auth/sign-in")
|
||||
.tags("auth")
|
||||
.summary("Email + password sign-in")
|
||||
.body(SignInBodySchema)
|
||||
.response(successResponse(SignInResponseSchema))
|
||||
.build();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `SignInBodySchema`
|
||||
|
||||
Required: `email: string (email)`, `password: string (min 1)`. Inferred
|
||||
type `SignInBody`.
|
||||
|
||||
### `SignInResponseSchema`
|
||||
|
||||
`{ user: PrincipalSchema; session: SessionSchema }`. Inferred type
|
||||
`SignInResponse`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The `password` field only requires a non-empty string — Better-Auth's
|
||||
policy layer enforces strength rules with full error reporting.
|
||||
Duplicating policy at the schema layer would force the client SDK to
|
||||
re-implement Better-Auth's exact rules.
|
||||
- The response intentionally mirrors `SignUpResponseSchema` so consumer
|
||||
code can use one type for both the post-sign-up and post-sign-in
|
||||
redirect handlers — a deliberate ergonomics choice.
|
||||
- The schema does not include rate-limit headers or CSRF tokens; those
|
||||
are handled by `@suaveplan/api`'s middleware chain, not the route
|
||||
schema itself.
|
||||
@@ -0,0 +1,65 @@
|
||||
# schemas/routes/sign-out
|
||||
|
||||
## Purpose
|
||||
|
||||
Schemas for `POST /auth/sign-out`. The body is optional (Better-Auth
|
||||
identifies the session from the cookie or `Authorization` header) and
|
||||
the response is a single `{ success: true }` literal so client code
|
||||
can branch on success without parsing additional fields.
|
||||
|
||||
## Features
|
||||
|
||||
- `SignOutBodySchema` — optional empty object; accepts both `undefined`
|
||||
and `{}` so clients can send a body or skip it.
|
||||
- `SignOutResponseSchema` — `{ success: literal(true) }` so the
|
||||
response type is `{ success: true }` rather than `{ success: boolean }`.
|
||||
- Inferred companion `SignOutResponse`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { SignOutResponseSchema } from "@suaveplan/api-plugin-auth/schemas/routes";
|
||||
|
||||
const parsed = SignOutResponseSchema.parse({ success: true });
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Pair the schema with the `requireAuth` middleware so the OpenAPI
|
||||
document advertises a 401 response for anonymous callers and a 200
|
||||
response with the typed body for authenticated ones:
|
||||
|
||||
```ts
|
||||
import { POST, successResponse } from "@suaveplan/api-openapi";
|
||||
import {
|
||||
SignOutResponseSchema,
|
||||
} from "@suaveplan/api-plugin-auth/schemas/routes";
|
||||
|
||||
const signOutRoute = POST("/auth/sign-out")
|
||||
.tags("auth")
|
||||
.summary("End the current session")
|
||||
.response(successResponse(SignOutResponseSchema))
|
||||
.build();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `SignOutBodySchema`
|
||||
|
||||
Optional empty object. Accepts `undefined` or `{}`.
|
||||
|
||||
### `SignOutResponseSchema`
|
||||
|
||||
`{ success: true }` literal. Inferred type `SignOutResponse`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The body schema is `optional` rather than absent so the OpenAPI
|
||||
document still advertises the route as accepting a JSON body. Clients
|
||||
may send `{}` to follow the route shape or omit the body entirely.
|
||||
- The response is a literal `true` rather than `boolean` so the typed
|
||||
return value at the client side narrows correctly — `if
|
||||
(response.success) { … }` is statically known to execute.
|
||||
- Better-Auth's downstream cookie clearing is a side effect of the
|
||||
handler, not part of the schema. The response only describes the JSON
|
||||
body so the schema stays orthogonal to the cookie machinery.
|
||||
@@ -0,0 +1,77 @@
|
||||
# schemas/routes/sign-up
|
||||
|
||||
## Purpose
|
||||
|
||||
Request/response Zod schemas for `POST /auth/sign-up`. The body schema
|
||||
constrains the email + password + optional name input; the response
|
||||
schema describes the typed user + session pair Better-Auth returns on
|
||||
a successful sign-up. Sibling plugins import these schemas directly so
|
||||
client SDK generators and downstream tests share one canonical wire
|
||||
shape.
|
||||
|
||||
## Features
|
||||
|
||||
- `SignUpBodySchema` — `email` (RFC-5322), `password` (min 8 chars),
|
||||
optional `name` (min 1 char).
|
||||
- `SignUpResponseSchema` — Better-Auth user + session payload typed
|
||||
via `PrincipalSchema` and `SessionSchema` from `../principal.ts` so
|
||||
the response carries the same wire-level user identity that
|
||||
`c.get("user")` produces on every other route.
|
||||
- Inferred TypeScript companions `SignUpBody` and `SignUpResponse`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { SignUpBodySchema } from "@suaveplan/api-plugin-auth/schemas/routes";
|
||||
|
||||
const body = SignUpBodySchema.parse(await c.req.json());
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Use the schemas inside a `RouteBuilder` chain so the OpenAPI document
|
||||
carries both the request body and the success response shapes:
|
||||
|
||||
```ts
|
||||
import { POST, successResponse } from "@suaveplan/api-openapi";
|
||||
import {
|
||||
SignUpBodySchema,
|
||||
SignUpResponseSchema,
|
||||
} from "@suaveplan/api-plugin-auth/schemas/routes";
|
||||
|
||||
const signUpRoute = POST("/auth/sign-up")
|
||||
.tags("auth")
|
||||
.summary("Email + password sign-up")
|
||||
.body(SignUpBodySchema)
|
||||
.response(successResponse(SignUpResponseSchema))
|
||||
.build();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `SignUpBodySchema`
|
||||
|
||||
Required: `email: string (email)`, `password: string (min 8)`.
|
||||
Optional: `name: string (min 1)`.
|
||||
|
||||
### `SignUpResponseSchema`
|
||||
|
||||
`{ user: PrincipalSchema; session: SessionSchema }`. Inferred type
|
||||
`SignUpResponse`.
|
||||
|
||||
### `SignUpBody`
|
||||
|
||||
`z.infer<typeof SignUpBodySchema>`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The 8-character minimum on `password` matches Better-Auth's default
|
||||
password policy; consumers can layer stricter policies via Better-Auth
|
||||
configuration without altering the schema.
|
||||
- The response shape uses the shared `PrincipalSchema` / `SessionSchema`
|
||||
rather than a route-local one — this is how the contract that
|
||||
`c.get("user")` returns the same `Principal` everywhere stays a
|
||||
single-source-of-truth thing.
|
||||
- Sensitive fields (`password`) are intentionally excluded from the
|
||||
response schema so the OpenAPI document never advertises a password
|
||||
surface to consumers.
|
||||
@@ -0,0 +1,72 @@
|
||||
# schemas/routes/verify-email
|
||||
|
||||
## Purpose
|
||||
|
||||
Schemas for `POST /auth/verify-email`. The body carries the
|
||||
single-use verification token (typically emailed from a sign-up
|
||||
event); the response is `{ verified: true }`. The route is idempotent
|
||||
in Better-Auth — submitting an already-verified token returns `verified:
|
||||
true` rather than failing.
|
||||
|
||||
## Features
|
||||
|
||||
- `VerifyEmailBodySchema` — `{ token: string (min 1) }`.
|
||||
- `VerifyEmailResponseSchema` — `{ verified: literal(true) }`.
|
||||
- Inferred companions `VerifyEmailBody` and `VerifyEmailResponse`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { VerifyEmailBodySchema } from "@suaveplan/api-plugin-auth/schemas/routes";
|
||||
|
||||
const body = VerifyEmailBodySchema.parse(await c.req.json());
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Wire the route into the host so the OpenAPI document advertises the 400
|
||||
case (invalid / expired token) and the 200 case (verified). The schema
|
||||
covers the 200 case; standard error responses come from
|
||||
`StandardResponses`:
|
||||
|
||||
```ts
|
||||
import { POST, successResponse } from "@suaveplan/api-openapi";
|
||||
import {
|
||||
VerifyEmailBodySchema,
|
||||
VerifyEmailResponseSchema,
|
||||
} from "@suaveplan/api-plugin-auth/schemas/routes";
|
||||
|
||||
const verifyRoute = POST("/auth/verify-email")
|
||||
.tags("auth")
|
||||
.summary("Verify an email address using a single-use token")
|
||||
.body(VerifyEmailBodySchema)
|
||||
.response(successResponse(VerifyEmailResponseSchema))
|
||||
.build();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `VerifyEmailBodySchema`
|
||||
|
||||
Required: `token: string (min 1)`. Inferred type `VerifyEmailBody`.
|
||||
|
||||
### `VerifyEmailResponseSchema`
|
||||
|
||||
`{ verified: true }`. Inferred type `VerifyEmailResponse`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The token is opaque to the schema — Better-Auth's downstream handler
|
||||
validates structure, expiry, and single-use semantics.
|
||||
- The response is the literal `true` so client code can branch on
|
||||
success without a wider type comparison.
|
||||
- Repeated successful verification calls return the same `verified:
|
||||
true` shape — the schema is intentionally idempotent so client retry
|
||||
loops do not need a special-case "already verified" code.
|
||||
- Token expiry is handled by Better-Auth's verification flow — the
|
||||
schema does not impose a TTL; expired tokens return a 400 from the
|
||||
Better-Auth handler, which the host's error-handler middleware
|
||||
translates to the standard error envelope.
|
||||
- The route is mounted under the configured `routePrefix` so the
|
||||
fully-qualified path is typically `/auth/verify-email` (or the
|
||||
caller's custom prefix when wiring the plugin).
|
||||
Reference in New Issue
Block a user