2.3 KiB
2.3 KiB
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 andAUTH_EMAIL_NOT_VERIFIED(403) for unverified ones. - Strict boolean check:
emailVerified !== truefails, so undefined / null are treated as unverified.
Usage: Basic
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:
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) whenc.get("user")is null/undefined. - Throws
AuthError(AUTH_EMAIL_NOT_VERIFIED)(403) whenprincipal.emailVerified !== true. - Calls
next()otherwise.
Implementation Notes
- The check uses
!== truerather than=== falseso an explicitlyundefined/nullemailVerified(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: 403for 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).