first commit
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
# middleware/require-auth
|
||||
|
||||
## Purpose
|
||||
|
||||
`requireAuth()` is the canonical Hono middleware factory for gating a
|
||||
route or sub-route on an authenticated principal. It reads
|
||||
`c.get("user")` and, if the value is `null` (or absent), throws
|
||||
`AuthError(AUTH_UNAUTHENTICATED)` carrying `context.statusCode: 401`
|
||||
so the `@suaveplan/api` error-handler middleware translates it to an
|
||||
HTTP 401 response.
|
||||
|
||||
## Features
|
||||
|
||||
- Depends only on the typed `c.get("user")` slot installed by
|
||||
`sessionMiddleware` — no dynamic service lookup, no per-request
|
||||
Better-Auth call.
|
||||
- Single failure mode: `AUTH_UNAUTHENTICATED` with `statusCode: 401`
|
||||
attached to the error context.
|
||||
- Idempotent — applying the middleware twice in the same chain costs
|
||||
one type narrow + one truthy check.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { requireAuth } from "@suaveplan/api-plugin-auth/middleware/require-auth";
|
||||
|
||||
app.use("/private/*", requireAuth());
|
||||
app.get("/private/me", (c) => c.json(c.get("user")));
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Compose `requireAuth` with `requireRole` and `requireEmailVerified` to
|
||||
build a sliding scale of access controls. Each middleware throws its
|
||||
own typed `AuthError`, so the error handler can emit specific OpenAPI
|
||||
4xx responses per stage:
|
||||
|
||||
```ts
|
||||
import {
|
||||
requireAuth,
|
||||
requireRole,
|
||||
requireEmailVerified,
|
||||
} from "@suaveplan/api-plugin-auth/middleware";
|
||||
|
||||
app.use("/admin/*", requireAuth(), requireRole("admin"));
|
||||
app.use("/billing/*", requireAuth(), requireEmailVerified());
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `function requireAuth(): MiddlewareHandler`
|
||||
|
||||
Returns a Hono middleware handler. Throws
|
||||
`AuthError(AUTH_UNAUTHENTICATED, "Authentication required",
|
||||
{ context: { statusCode: 401 } })` when `c.get("user")` is null or
|
||||
undefined. Calls `next()` otherwise.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The check uses `=== null || === undefined` rather than a truthy
|
||||
check so a fully-populated `Principal` with falsy values (e.g.
|
||||
`email: ""` after the principal mapping normalised it out) still
|
||||
passes. `Principal` objects are never falsy.
|
||||
- Throwing — not returning a 401 response directly — keeps the error
|
||||
shape consistent with `requireRole` and `requireEmailVerified`; all
|
||||
three flow through the `@suaveplan/api` error-handler, which both
|
||||
serialises the error envelope and emits the OTel span event.
|
||||
- The `statusCode: 401` field is read by the error-handler middleware
|
||||
installed by `@suaveplan/api`; the auth plugin does not own status
|
||||
mapping itself.
|
||||
Reference in New Issue
Block a user