# middleware/require-email-verified ## Purpose `requireEmailVerified()` is the Hono middleware factory that gates a route on the principal's email having been verified. Routes that trigger sensitive operations (password change, billing portal, account deletion) use this guard so unverified accounts cannot move forward without first completing the verification flow. ## Features - Reads `c.get("user")` directly — no Better-Auth lookup per request. - Two failure modes: `AUTH_UNAUTHENTICATED` (401) for anonymous callers and `AUTH_EMAIL_NOT_VERIFIED` (403) for unverified ones. - Strict boolean check: `emailVerified !== true` fails, so undefined / null are treated as unverified. ## Usage: Basic ```ts import { requireAuth, requireEmailVerified, } from "@suaveplan/api-plugin-auth/middleware"; app.use("/billing/*", requireAuth(), requireEmailVerified()); ``` ## Usage: Advanced Use the middleware as a precondition for routes that trigger high-trust workflows. Combine with `requireRole` so admin routes require BOTH verified email AND the admin role: ```ts import { requireAuth, requireEmailVerified, requireRole, } from "@suaveplan/api-plugin-auth/middleware"; app.use( "/admin/users/:id/delete", requireAuth(), requireEmailVerified(), requireRole("admin") ); ``` ## API Reference ### `function requireEmailVerified(): MiddlewareHandler` - Throws `AuthError(AUTH_UNAUTHENTICATED)` (401) when `c.get("user")` is null/undefined. - Throws `AuthError(AUTH_EMAIL_NOT_VERIFIED)` (403) when `principal.emailVerified !== true`. - Calls `next()` otherwise. ## Implementation Notes - The check uses `!== true` rather than `=== false` so an explicitly `undefined` / `null` `emailVerified` (which the default principal mapping should not emit, but a custom mapping might) is treated as unverified. Failing closed is the right default for an auth gate. - The middleware does not auto-trigger a re-send of the verification email — that is the route handler's responsibility once the 403 reaches the client. - The 403 error carries `context.statusCode: 403` for the error handler; no other context fields are attached because the principal identity does not need to be re-emitted on a 403 (it is already available in the trace span).