this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 95 lines 2.9 kB view raw
1/** 2 * Admin allowlist + helpers. 3 * 4 * The list of admin DIDs is supplied via the `ADMIN_DIDS` env var 5 * (comma-separated). All `/admin/*` pages are gated by 6 * `routes/admin/_middleware.ts` which calls `requireAdmin`; admin API 7 * routes under `/api/admin/*` should call `requireAdminApi` directly 8 * because they need to return JSON 401/403 instead of an HTML redirect. 9 * 10 * Admins authenticate exactly like normal users (via the existing 11 * atproto OAuth flow); admin status is purely a server-side allowlist 12 * check on the resulting session DID. 13 */ 14import { ADMIN_DIDS } from "./env.ts"; 15 16export function isAdmin(did: string | null | undefined): boolean { 17 if (!did) return false; 18 return ADMIN_DIDS.includes(did); 19} 20 21/** Are any admins configured at all? Useful for hiding admin links from 22 * navigation in deployments that haven't opted in. */ 23export function adminConfigured(): boolean { 24 return ADMIN_DIDS.length > 0; 25} 26 27interface AdminCtxLike { 28 state: { user: { did: string } | null }; 29 req: Request; 30} 31 32/** 33 * Throws via redirect (302 → /oauth/login) when the request isn't from 34 * an admin. Page routes should `await requireAdmin(ctx)` at the top of 35 * their handler. Returns the verified admin DID on success. 36 */ 37export function requireAdmin(ctx: AdminCtxLike): string { 38 const user = ctx.state.user; 39 if (!user) { 40 throw redirectResponse(loginUrl(ctx.req.url)); 41 } 42 if (!isAdmin(user.did)) { 43 throw notFoundResponse(); 44 } 45 return user.did; 46} 47 48/** 49 * API-shaped admin gate. Returns `{ ok: false, response }` so handlers 50 * can early-return; or `{ ok: true, did }` to proceed. 51 */ 52export function requireAdminApi( 53 ctx: AdminCtxLike, 54): 55 | { ok: true; did: string } 56 | { ok: false; response: Response } { 57 const user = ctx.state.user; 58 if (!user) { 59 return { 60 ok: false, 61 response: jsonResponse(401, { error: "not_authenticated" }), 62 }; 63 } 64 if (!isAdmin(user.did)) { 65 return { ok: false, response: jsonResponse(403, { error: "forbidden" }) }; 66 } 67 return { ok: true, did: user.did }; 68} 69 70function loginUrl(currentUrl: string): string { 71 try { 72 const url = new URL(currentUrl); 73 const next = url.pathname + url.search; 74 return `/oauth/login?next=${encodeURIComponent(next)}`; 75 } catch { 76 return "/oauth/login"; 77 } 78} 79 80function redirectResponse(location: string): Response { 81 return new Response(null, { status: 303, headers: { location } }); 82} 83 84/** Surface admin routes as 404 to anonymous/non-admin users so we don't 85 * leak that the URL exists. (Logged-in non-admins also get 404.) */ 86function notFoundResponse(): Response { 87 return new Response("not found", { status: 404 }); 88} 89 90function jsonResponse(status: number, body: unknown): Response { 91 return new Response(JSON.stringify(body), { 92 status, 93 headers: { "content-type": "application/json; charset=utf-8" }, 94 }); 95}