auth-yes/ui/mod.ts

342 lines
8.4 KiB
TypeScript

import { Hono } from "jsr:@hono/hono@4";
import { serveStatic } from "jsr:@hono/hono@4/deno";
import { deleteCookie, getCookie } from "jsr:@hono/hono@4/cookie";
import { sql } from "../server/db.ts";
import { valkey } from "../server/valkey.ts";
import { getAuthenticatedUser, isGlobalAdmin } from "../server/auth-session.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";
const uiApp: Hono = new Hono();
// Explicit Side Effect: Route rendering
uiApp.get("/", (c) => {
return c.redirect("/login");
});
uiApp.get("/logout", async (c) => {
const sessionId = getCookie(c, "session_id");
if (sessionId) {
try {
await valkey.del(sessionId);
await sql`DELETE FROM sessions WHERE id = ${sessionId}`;
} catch (_e) {
// Best effort cleanup
}
}
const rpID = Deno.env.get("RP_ID") || "";
const cookieDomain = Deno.env.get("COOKIE_DOMAIN") ||
(rpID.includes(".") ? `.${rpID}` : undefined);
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",
});
return c.redirect("/login");
});
uiApp.get("/login", (c) => {
return c.html(LoginPage());
});
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", (c) => {
return c.redirect("/dashboard/sessions");
});
uiApp.get("/dashboard/sessions", async (c) => {
const auth = await getAuthenticatedUser(c);
if (!auth) {
return c.redirect("/login");
}
const isAdmin = await isGlobalAdmin(auth.userId);
const sessions = await sql`
SELECT id, created_at, expires_at
FROM sessions
WHERE user_id = ${auth.userId} AND expires_at > NOW()
ORDER BY created_at DESC
`;
return c.html(
SessionsPage({ sessions, currentSessionId: auth.sessionId, isAdmin }),
);
});
uiApp.get("/dashboard/passkeys", async (c) => {
const auth = await getAuthenticatedUser(c);
if (!auth) {
return c.redirect("/login");
}
const isAdmin = await isGlobalAdmin(auth.userId);
const passkeys = await sql`
SELECT id, credential_id, counter
FROM passkeys
WHERE user_id = ${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 auth = await getAuthenticatedUser(c);
if (!auth) {
return c.redirect("/login");
}
const isAdmin = await isGlobalAdmin(auth.userId);
if (!isAdmin) {
return c.redirect("/dashboard");
}
const users = await sql`
SELECT id, username, display_name, account_status
FROM users
ORDER BY username ASC
`;
return c.html(AdminUsersPage({ users }));
});
uiApp.get("/admin/apps", async (c) => {
const auth = await getAuthenticatedUser(c);
if (!auth) {
return c.redirect("/login");
}
const isAdmin = await isGlobalAdmin(auth.userId);
if (!isAdmin) {
return c.redirect("/dashboard");
}
const apps = await sql`
SELECT a.id, a.name, a.spiffe_id, a.description, a.created_at,
COUNT(g.id) AS active_grants_count
FROM apps a
LEFT JOIN grants g ON a.id = g.app_id
GROUP BY a.id, a.name, a.spiffe_id, a.description, a.created_at
ORDER BY a.created_at ASC
`;
return c.html(AdminAppsPage({ apps }));
});
uiApp.get("/admin/roles", async (c) => {
const auth = await getAuthenticatedUser(c);
if (!auth) {
return c.redirect("/login");
}
const isAdmin = await isGlobalAdmin(auth.userId);
if (!isAdmin) {
return c.redirect("/dashboard");
}
const roles = await sql`
SELECT r.id, r.name, r.description, r.app_id, r.created_at,
a.name AS app_name
FROM roles r
LEFT JOIN apps a ON r.app_id = a.id
ORDER BY r.app_id NULLS FIRST, r.name ASC
`;
const apps = await sql`
SELECT id, name, spiffe_id FROM apps ORDER BY name ASC
`;
return c.html(AdminRolesPage({ roles, apps }));
});
uiApp.get("/admin/invites", async (c) => {
const auth = await getAuthenticatedUser(c);
if (!auth) {
return c.redirect("/login");
}
const isAdmin = await isGlobalAdmin(auth.userId);
if (!isAdmin) {
return c.redirect("/dashboard");
}
const invites = await sql`
SELECT i.id, i.code, i.role, i.max_uses, i.uses_count, i.auto_activate, i.expires_at, i.created_at, i.used_at,
a.name AS app_name, a.id AS app_id,
u.username AS used_by_username
FROM invites i
LEFT JOIN apps a ON i.app_id = a.id
LEFT JOIN users u ON i.used_by = u.id
ORDER BY i.created_at DESC
`;
const apps = await sql`
SELECT id, name, spiffe_id FROM apps ORDER BY name ASC
`;
const allRoles = await sql`
SELECT id, name, description, app_id FROM roles ORDER BY name ASC
`;
return c.html(AdminInvitesPage({ invites, apps, allRoles }));
});
uiApp.get("/admin/aaguid", async (c) => {
const auth = await getAuthenticatedUser(c);
if (!auth) {
return c.redirect("/login");
}
const isAdmin = await isGlobalAdmin(auth.userId);
if (!isAdmin) {
return c.redirect("/dashboard");
}
const allowlist = await sql`
SELECT id, aaguid, description, created_at
FROM aaguid_allowlist
ORDER BY created_at DESC
`;
return c.html(AAGUIDPage({ allowlist }));
});
uiApp.get("/admin/users/:id", async (c) => {
const auth = await getAuthenticatedUser(c);
if (!auth) {
return c.redirect("/login");
}
const isAdmin = await isGlobalAdmin(auth.userId);
if (!isAdmin) {
return c.redirect("/admin/users");
}
const targetUserId = c.req.param("id");
const user = await sql`
SELECT id, username, display_name, account_status
FROM users
WHERE id = ${targetUserId}
`.then((res) => res[0]);
if (!user) {
return c.redirect("/admin/users");
}
const sessions = await sql`
SELECT id, created_at, expires_at
FROM sessions
WHERE user_id = ${targetUserId} AND expires_at > NOW()
ORDER BY created_at DESC
`;
const passkeys = await sql`
SELECT id, credential_id, counter
FROM passkeys
WHERE user_id = ${targetUserId}
`;
const grants = await sql`
SELECT g.id, g.app_id, g.role, g.created_at, a.name AS app_name, a.spiffe_id
FROM grants g
JOIN apps a ON g.app_id = a.id
WHERE g.user_id = ${targetUserId}
ORDER BY a.name ASC
`;
const allApps = await sql`
SELECT id, name, spiffe_id FROM apps ORDER BY name ASC
`;
const allRoles = await sql`
SELECT id, name, description, app_id FROM roles ORDER BY name ASC
`;
return c.html(
AdminUserDetailsPage({
user,
sessions,
passkeys,
grants,
allApps,
allRoles,
}),
);
});
uiApp.get("/admin/audit-logs", async (c) => {
const auth = await getAuthenticatedUser(c);
if (!auth) {
return c.redirect("/login");
}
const isAdmin = await isGlobalAdmin(auth.userId);
if (!isAdmin) {
return c.redirect("/dashboard");
}
const logs = await sql`
SELECT a.id, a.action, a.resource, a.details, a.ip_address, a.created_at, u.username as user
FROM audit_records a
LEFT JOIN users u ON a.user_id = u.id
ORDER BY a.created_at DESC
LIMIT 100
`;
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 };