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,97 @@
# middleware/session
## Purpose
`sessionMiddleware` is the global Hono middleware that resolves every
incoming request's Better-Auth session and exposes a typed `Principal`
through `c.get("user")`. It is installed by `createAuthPlugin`'s
`onInit` against `ctx.app` so anonymous routes keep working while
protected handlers can rely on `c.get("user")` being either a real
`Principal` or `null` — never undefined.
## Features
- Calls `auth.api.getSession({ headers: c.req.raw.headers })` once per
request and caches the result on the context.
- Sets `c.set("user", principal | null)` and `c.set("session", session
| null)` via the augmented `ContextVariableMap` (see
`./context-augmentation.ts`).
- Default mapping uses `mapBetterAuthUserToPrincipal`; opt-out via
`principalMapping` for plugin-extended session shapes.
- Records `auth.sessions_resolved_total{outcome}` and
`auth.session_resolve_duration_ms` for every request, regardless of
outcome.
- Failures during session resolution set `c.get("user")` to `null`,
log at `warn`, and let the request proceed — protected routes still
throw `AUTH_UNAUTHENTICATED` via `requireAuth()` so the surface is
unaffected.
## Usage: Basic
```ts
import { sessionMiddleware } from "@suaveplan/api-plugin-auth/middleware/session";
import { betterAuth } from "better-auth";
const auth = betterAuth({ /* … */ });
app.use("*", sessionMiddleware({ auth }));
```
## Usage: Advanced
Pair the middleware with a custom mapping and a host-supplied logger
so role projection and trace correlation are wired in one place:
```ts
import { sessionMiddleware } from "@suaveplan/api-plugin-auth/middleware/session";
import { mapBetterAuthUserToPrincipal } from "@suaveplan/api-plugin-auth/principal/principal";
app.use(
"*",
sessionMiddleware({
auth,
logger: ctx.logger,
principalMapping: (view) => {
const base = mapBetterAuthUserToPrincipal(view);
const session = view.session as { roles?: string[] };
return { ...base, roles: [...base.roles, ...(session.roles ?? [])] };
},
})
);
```
## API Reference
### `function sessionMiddleware(options): MiddlewareHandler`
`options.auth` is required; `options.principalMapping` and
`options.logger` are optional. The returned middleware is async and
mutates `c`'s context variables — it never throws.
### `interface SessionMiddlewareOptions`
- `auth: Auth` — the Better-Auth instance.
- `principalMapping?: PrincipalMapping` — defaults to
`mapBetterAuthUserToPrincipal`.
- `logger?: LoggerLike` — defaults to a no-op logger.
### `type PrincipalMapping = (view: BetterAuthSessionView) => Principal`
### Module augmentation
`./context-augmentation.ts` declares `interface ContextVariableMap {
user: Principal | null; session: BetterAuthSessionResult }` so the
typed `c.get("user")` lookups work without a cast in every consumer.
## Implementation Notes
- The middleware does NOT throw on failure — anonymous routes must keep
working even when Better-Auth's session store is degraded. Protected
routes are responsible for raising `AUTH_UNAUTHENTICATED` via
`requireAuth()` only when they need an authenticated user.
- `performance.now()` is the duration source; the value is recorded in
milliseconds (matching the histogram's declared unit).
- The outcome label is bounded to `"authenticated"` / `"anonymous"` /
`"error"` so dashboards can rely on a fixed cardinality.
- Non-`Error` throwables from Better-Auth are normalised via
`String(err)` — the middleware does not assume Better-Auth's failure
modes are pure `Error` instances.