| 1 | import type { Context, MiddlewareHandler } from "hono"; |
| 2 | import type { AppEnv } from "../env"; |
| 3 | import type { RegistryUser } from "../types"; |
| 4 | import { ApiError } from "./errors"; |
| 5 | |
| 6 | // Resolve identity by asking the account service, forwarding the caller's cookie |
| 7 | // and Authorization so both the web session and the CLI bearer resolve. The |
| 8 | // registry never holds SESSION_PEPPER; accounts stays the sole identity authority. |
| 9 | async function fetchAccountUser(c: Context<AppEnv>): Promise<RegistryUser | null> { |
| 10 | const cookie = c.req.header("cookie"); |
| 11 | const authz = c.req.header("authorization"); |
| 12 | if (!cookie && !authz) return null; |
| 13 | |
| 14 | const headers: Record<string, string> = {}; |
| 15 | if (cookie) headers["cookie"] = cookie; |
| 16 | if (authz) headers["authorization"] = authz; |
| 17 | |
| 18 | const res = await fetch(`${c.env.ACCOUNTS_ORIGIN}/me`, { headers }); |
| 19 | if (!res.ok) return null; |
| 20 | const body = (await res.json()) as { |
| 21 | user?: { id?: number; handle?: string; role?: string; emailVerified?: boolean }; |
| 22 | }; |
| 23 | const u = body.user; |
| 24 | if (!u || typeof u.id !== "number" || typeof u.handle !== "string") return null; |
| 25 | return { |
| 26 | id: u.id, |
| 27 | handle: u.handle, |
| 28 | role: u.role === "admin" ? "admin" : "member", |
| 29 | emailVerified: u.emailVerified === true, |
| 30 | }; |
| 31 | } |
| 32 | |
| 33 | // Gate for write routes. Public reads never call this, so list/detail never pay |
| 34 | // the account round-trip. |
| 35 | export const requireAuth: MiddlewareHandler<AppEnv> = async (c, next) => { |
| 36 | const user = await fetchAccountUser(c).catch(() => null); |
| 37 | if (!user) throw new ApiError(401, "unauthorized", "Sign in at id.reasonix.io to publish."); |
| 38 | c.set("user", user); |
| 39 | await next(); |
| 40 | }; |
| 41 | |
| 42 | // Gate for moderation routes: a resolved account with the admin role. |
| 43 | export const requireAdmin: MiddlewareHandler<AppEnv> = async (c, next) => { |
| 44 | const user = await fetchAccountUser(c).catch(() => null); |
| 45 | if (!user) throw new ApiError(401, "unauthorized", "Sign in at id.reasonix.io."); |
| 46 | if (user.role !== "admin") throw new ApiError(403, "forbidden", "Admins only."); |
| 47 | c.set("user", user); |
| 48 | await next(); |
| 49 | }; |
| 50 | |
| 51 | export function currentUser(c: Context<AppEnv>): RegistryUser { |
| 52 | return c.get("user"); |
| 53 | } |
| 54 |