File
Blob: src/worker/middleware/admin-allowlist.ts
| 1 | import type { MiddlewareHandler } from "hono"; |
| 2 | |
| 3 | import type { AppBindings } from "@/worker/hono"; |
| 4 | |
| 5 | // Better Auth's `admin` plugin exposes a wide surface (create-user, |
| 6 | // set-user-password, impersonate-user, remove-user, session revocation, ...) |
| 7 | // that tessera does not use. tessera registration is invite-only, so a |
| 8 | // signed-in admin should not be able to bypass invites by hitting the raw |
| 9 | // /api/auth/admin/create-user endpoint just because it remains mounted. |
| 10 | // |
| 11 | // Allowlist the routes the tessera admin UI actually drives, keyed on |
| 12 | // (path, method). Everything else under /api/auth/admin/* returns 404 |
| 13 | // before reaching Better Auth, including wrong-method calls on listed |
| 14 | // paths (POST /admin/list-users, GET /admin/set-role). |
| 15 | // |
| 16 | // `/api/auth/admin/ban-user` is intentionally excluded here because it |
| 17 | // is served by the tessera handler in `api/admin/users.ts` (mounted |
| 18 | // before this middleware runs in `worker/index.ts`); the cleanup needs |
| 19 | // to happen atomically with the ban flag flip. |
| 20 | type Method = "GET" | "POST"; |
| 21 | |
| 22 | const ADMIN_ALLOWLIST: ReadonlyMap<string, Method> = new Map([ |
| 23 | ["/api/auth/admin/list-users", "GET"], |
| 24 | ["/api/auth/admin/set-role", "POST"], |
| 25 | ["/api/auth/admin/unban-user", "POST"], |
| 26 | ]); |
| 27 | |
| 28 | export const adminRouteAllowlist: MiddlewareHandler<AppBindings> = async (c, next) => { |
| 29 | const allowedMethod = ADMIN_ALLOWLIST.get(c.req.path); |
| 30 | if (allowedMethod && c.req.method === allowedMethod) { |
| 31 | await next(); |
| 32 | return; |
| 33 | } |
| 34 | return new Response("Not Found", { status: 404 }); |
| 35 | }; |