Skip to content
File

Blob: src/worker/middleware/auth.ts

typescript46 lines
1import type { MiddlewareHandler } from "hono";
2 
3import { hasAdminRole } from "@/shared/role";
4import type { AppBindings, AppContext, AppSession } from "@/worker/hono";
5import { HttpError } from "@/worker/http";
6 
7// The `assertNotBanned` predicate inside `makeAuth`'s `hooks.before`
8// fires for every cookie-session route, including this server-side
9// `auth.api.getSession()` call. A banned user's session lookup throws
10// `APIError(FORBIDDEN, code=BANNED_USER)`; the APIError → HttpError
11// conversion in `app.onError` (`worker/index.ts`) reshapes it before
12// the client sees the response.
13const loadSession = async (c: AppContext, message = "Sign in first."): Promise<AppSession> => {
14 const session = await c.var.auth.api.getSession({ headers: c.req.raw.headers });
15 if (!session) {
16 throw new HttpError(401, "unauthorized", message);
17 }
18 c.set("session", session);
19 return session;
20};
21 
22export const requireUser: MiddlewareHandler<AppBindings> = async (c, next) => {
23 await loadSession(c);
24 await next();
25};
26 
27export const requireAdmin: MiddlewareHandler<AppBindings> = async (c, next) => {
28 const session = await loadSession(c, "Sign in required.");
29 if (!hasAdminRole(session.user.role)) {
30 throw new HttpError(403, "forbidden", "Admin role required.");
31 }
32 await next();
33};
34 
35// Handlers that depend on a session pull it directly from `c.var.session`
36// after `requireUser` / `requireAdmin` has run upstream. The non-null
37// assertion is safe at the gated callsite; if a future refactor drops
38// the gate, TypeScript flags the missing variable type.
39export const requireSession = (c: AppContext): AppSession => {
40 const session = c.var.session;
41 if (!session) {
42 throw new HttpError(401, "unauthorized", "Sign in required.");
43 }
44 return session;
45};