CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
admin.ts
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| a4f3e24 | 1 | /** |
| 2 | * Admin middleware — blocks non-admin users. | |
| 3 | * Must be used AFTER softAuth or requireAuth. | |
| 36cc17a | 4 | * |
| 5 | * "Admin" means EITHER: | |
| 6 | * - the user is in the `site_admins` table (explicit grant), OR | |
| 7 | * - the `site_admins` table is empty and this user is the oldest | |
| 8 | * row in `users` (bootstrap rule from src/lib/admin.ts). | |
| 9 | * | |
| 10 | * The legacy `users.is_admin` column is ALSO honoured for backward | |
| 11 | * compatibility with accounts that pre-date the site_admins table. | |
| 12 | * | |
| 13 | * Until 2026-05-14 this middleware only checked `users.is_admin`, | |
| 14 | * which broke /import + every other requireAdmin-gated route for any | |
| 15 | * user who only had bootstrap-rule admin status (which is the case | |
| 16 | * after running scripts/reset-admin-password.ts). | |
| a4f3e24 | 17 | */ |
| 18 | ||
| 19 | import { createMiddleware } from "hono/factory"; | |
| 20 | import type { AuthEnv } from "./auth"; | |
| 36cc17a | 21 | import { isSiteAdmin } from "../lib/admin"; |
| c63b860 | 22 | // BLOCK O2 — polished 403 page renderer (standalone HTML, DB-free). |
| 23 | import { renderStandaloneErrorPage } from "../views/error-page"; | |
| a4f3e24 | 24 | |
| 25 | export const requireAdmin = createMiddleware<AuthEnv>(async (c, next) => { | |
| 26 | const user = c.get("user"); | |
| 27 | ||
| 28 | if (!user) { | |
| 29 | return c.redirect(`/login?redirect=${encodeURIComponent(c.req.path)}`); | |
| 30 | } | |
| 31 | ||
| 36cc17a | 32 | const admin = user.isAdmin === true || (await isSiteAdmin(user.id)); |
| 33 | if (!admin) { | |
| c63b860 | 34 | // BLOCK O2 — polished 403 page (was inline 80s-era markup). The |
| 35 | // standalone renderer returns a self-contained <!doctype html> | |
| 36 | // document so this middleware has no Layout dependency. | |
| a4f3e24 | 37 | return c.html( |
| c63b860 | 38 | renderStandaloneErrorPage({ |
| 39 | code: "403", | |
| 40 | eyebrow: "Forbidden", | |
| 41 | title: "Admin access required.", | |
| 42 | body: | |
| 43 | "This area is restricted to site admins. If you think this is " + | |
| 44 | "a mistake, contact a site admin or sign in as a different user.", | |
| 45 | signedIn: true, | |
| 46 | }), | |
| a4f3e24 | 47 | 403 |
| 48 | ); | |
| 49 | } | |
| 50 | ||
| 51 | return next(); | |
| 52 | }); |