import { Hono } from "jsr:@hono/hono@4"; import { serveStatic } from "jsr:@hono/hono@4/deno"; import { deleteCookie } from "jsr:@hono/hono@4/cookie"; import { sql } from "../server/db.ts"; import { valkey } from "../server/valkey.ts"; import { extractAllSessionIds, getAuthenticatedUser, getCookieDomain, isSafeRedirectUrl, } from "../server/auth-session.ts"; import { auditWrapper } from "../server/audit.ts"; import { requireUiAuth } from "./auth_checks.ts"; import { getAaguidAllowlist, getAdminApps, getAdminAuditLogs, getAdminInvites, getAdminRoles, getAdminUserDetails, getAdminUsers, getAllRoles, getDashboardApps, getSessionApps, getUserEventPasses, getUserGrants, getUserPasskeys, getUserSessions, } from "./db_queries.ts"; import { LoginPage } from "./components/LoginPage.tsx"; import { RegisterPage } from "./components/RegisterPage.tsx"; import { SessionsPage } from "./components/SessionsPage.tsx"; import { PasskeysPage } from "./components/PasskeysPage.tsx"; import { AuditLogPage } from "./components/AuditLogPage.tsx"; import { AdminUsersPage } from "./components/AdminUsersPage.tsx"; import { AdminUserDetailsPage } from "./components/AdminUserDetailsPage.tsx"; import { AAGUIDPage } from "./components/AAGUIDPage.tsx"; import { RecoveryPage } from "./components/RecoveryPage.tsx"; import { AdminAppsPage } from "./components/AdminAppsPage.tsx"; import { AdminRolesPage } from "./components/AdminRolesPage.tsx"; import { AdminInvitesPage } from "./components/AdminInvitesPage.tsx"; import { UnregisteredAppPage } from "./components/UnregisteredAppPage.tsx"; import { AppLaunchpadPage } from "./components/AppLaunchpadPage.tsx"; const uiApp: Hono = new Hono(); // Explicit Side Effect: Route rendering uiApp.get("/", (c) => { c.header("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0"); return c.redirect("/login"); }); uiApp.get("/logout", async (c) => { const sessionIds = extractAllSessionIds(c); const rawRedirect = c.req.query("redirect"); let safeRedirect = null; const userIp = c.req.header("x-forwarded-for") || "127.0.0.1"; let userId = null; if (sessionIds.length > 0) { try { // Get user ID for auditing before we delete the session const authUser = await getAuthenticatedUser(c); if (authUser) { userId = authUser.userId; } // Purge all candidate cookies sent by the browser to ensure ghosts are eradicated for (const sId of sessionIds) { try { await valkey.del(sId); await sql`DELETE FROM sessions WHERE id = ${sId}`; } catch (_e) {} } } catch (_e) { // Best effort cleanup } } if (rawRedirect) { if (isSafeRedirectUrl(rawRedirect)) { safeRedirect = rawRedirect; } else { auditWrapper.auditLog( userId, "open_redirect_intercepted", "logout_redirect", { raw_url: rawRedirect }, userIp, ); } } auditWrapper.auditLog( userId, "logout_success", "session", null, userIp, ); const rpID = Deno.env.get("RP_ID") || ""; const cookieDomain = getCookieDomain(rpID); if (cookieDomain) { deleteCookie(c, "session_id", { domain: cookieDomain, path: "/", httpOnly: true, secure: true, sameSite: "Lax", }); } deleteCookie(c, "session_id", { path: "/", httpOnly: true, secure: true, sameSite: "Lax", }); if (safeRedirect) { c.header("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0"); return c.redirect(safeRedirect); } c.header("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0"); return c.redirect("/login"); }); uiApp.get("/login", (c) => { return c.html(LoginPage()); }); uiApp.get("/errors/unregistered", (c) => { const host = c.req.query("host") || "unknown"; return c.html(UnregisteredAppPage({ host })); }); uiApp.get("/recovery", (c) => { return c.html(RecoveryPage()); }); uiApp.get("/register", (c) => { const initialCode = c.req.query("code") || ""; return c.html(RegisterPage({ initialCode })); }); uiApp.get("/dashboard", async (c) => { const authRes = await requireUiAuth(c); if (authRes instanceof Response) return authRes; const { auth, isAdmin } = authRes; const apps = await getDashboardApps(auth.userId, isAdmin, auth.customScopes); return c.html(AppLaunchpadPage({ apps: apps as any, isAdmin })); }); uiApp.get("/dashboard/sessions", async (c) => { const authRes = await requireUiAuth(c); if (authRes instanceof Response) return authRes; const { auth, isAdmin } = authRes; const apps = await getSessionApps(); const sessions = await getUserSessions(auth.userId); const eventPasses = await getUserEventPasses(auth.userId); return c.html( SessionsPage({ sessions, currentSessionId: auth.sessionId, apps: apps as any, isAdmin, eventPasses: eventPasses as any, }), ); }); uiApp.get("/dashboard/passkeys", async (c) => { const authRes = await requireUiAuth(c); if (authRes instanceof Response) return authRes; const { auth, isAdmin } = authRes; const passkeys = await getUserPasskeys(auth.userId); return c.html(PasskeysPage({ passkeys, isAdmin })); }); // Admin Routes uiApp.get("/admin", (c) => { return c.redirect("/admin/users"); }); uiApp.get("/admin/users", async (c) => { const authRes = await requireUiAuth(c); if (authRes instanceof Response) return authRes; const { isAdmin, isSessionAdminRole } = authRes; if (!isAdmin || !isSessionAdminRole) { return c.redirect("/dashboard", 302); } const users = await getAdminUsers(); return c.html(AdminUsersPage({ users })); }); uiApp.get("/admin/apps", async (c) => { const authRes = await requireUiAuth(c); if (authRes instanceof Response) return authRes; const { isAdmin, isSessionAdminRole } = authRes; if (!isAdmin || !isSessionAdminRole) { return c.redirect("/dashboard", 302); } const apps = await getAdminApps(); return c.html(AdminAppsPage({ apps })); }); uiApp.get("/admin/roles", async (c) => { const authRes = await requireUiAuth(c); if (authRes instanceof Response) return authRes; const { isAdmin, isSessionAdminRole } = authRes; if (!isAdmin || !isSessionAdminRole) { return c.redirect("/dashboard", 302); } const roles = await getAdminRoles(); const apps = await getSessionApps(); return c.html(AdminRolesPage({ roles, apps })); }); uiApp.get("/admin/invites", async (c) => { const authRes = await requireUiAuth(c); if (authRes instanceof Response) return authRes; const { isAdmin, isSessionAdminRole } = authRes; if (!isAdmin || !isSessionAdminRole) { return c.redirect("/dashboard", 302); } const invites = await getAdminInvites(); const apps = await getSessionApps(); const allRoles = await getAllRoles(); return c.html(AdminInvitesPage({ invites, apps, allRoles })); }); uiApp.get("/admin/aaguid", async (c) => { const authRes = await requireUiAuth(c); if (authRes instanceof Response) return authRes; const { isAdmin } = authRes; if (!isAdmin) { return c.redirect("/dashboard"); } const allowlist = await getAaguidAllowlist(); return c.html(AAGUIDPage({ allowlist })); }); uiApp.get("/admin/users/:id", async (c) => { const authRes = await requireUiAuth(c); if (authRes instanceof Response) return authRes; const { isAdmin, isSessionAdminRole } = authRes; if (!isAdmin || !isSessionAdminRole) { return c.redirect("/admin/users", 302); } const targetUserId = c.req.param("id"); const user = await getAdminUserDetails(targetUserId); if (!user) { return c.redirect("/admin/users"); } const sessions = await getUserSessions(targetUserId); const passkeys = await getUserPasskeys(targetUserId); const grants = await getUserGrants(targetUserId); const allApps = await getSessionApps(); const allRoles = await getAllRoles(); return c.html( AdminUserDetailsPage({ user, sessions, passkeys, grants, allApps, allRoles, }), ); }); uiApp.get("/admin/audit-logs", async (c) => { const authRes = await requireUiAuth(c); if (authRes instanceof Response) return authRes; const { isAdmin, isSessionAdminRole } = authRes; if (!isAdmin || !isSessionAdminRole) { return c.redirect("/dashboard", 302); } const logs = await getAdminAuditLogs(); return c.html(AuditLogPage({ logs })); }); // Explicit Side Effect: Serving static assets (client-side JS) with no-cache headers to prevent stale browser caches uiApp.use("/public/*", async (c, next) => { await next(); c.header("Cache-Control", "no-cache, no-store, must-revalidate"); }); uiApp.get( "/public/*", serveStatic({ root: "./ui", rewriteRequestPath: (path) => path.replace(/^\/public/, "/public"), }), ); export { uiApp };