135 lines
3.9 KiB
TypeScript
135 lines
3.9 KiB
TypeScript
import { Hono } from "jsr:@hono/hono@4";
|
|
import { serveStatic } from "jsr:@hono/hono@4/deno";
|
|
import { deleteCookie } from "jsr:@hono/hono@4/cookie";
|
|
import { MetadataService } from "jsr:@simplewebauthn/server@13";
|
|
|
|
import { initDb, sqlWrapper } from "./core/db.ts";
|
|
import { pingValkey, valkey } from "./core/valkey.ts";
|
|
import { contentNegotiation } from "./core/content_negotiation.ts";
|
|
import { payloadCapGuard } from "./core/auth_guards.ts";
|
|
import { startConnectRpcServer } from "./core/rpc.ts";
|
|
import {
|
|
extractAllSessionIds,
|
|
getAuthenticatedUser,
|
|
getCookieDomain,
|
|
} from "./core/session.ts";
|
|
import { isSafeRedirectUrl } from "./core/forward_auth.ts";
|
|
import { auditWrapper } from "./core/audit.ts";
|
|
|
|
import { authRoutes } from "./features/auth/routes.tsx";
|
|
import { adminRoutes } from "./features/admin/routes.tsx";
|
|
import { eventsRoutes } from "./features/events/routes.tsx";
|
|
import { sessionRoutes } from "./features/sessions/routes.tsx";
|
|
import { passRoutes } from "./features/sessions/pass_routes.ts";
|
|
import { forwardAuthRoutes } from "./features/forward_auth/routes.ts";
|
|
|
|
const app: Hono = new Hono();
|
|
|
|
app.use("*", payloadCapGuard);
|
|
app.use("*", contentNegotiation());
|
|
|
|
// Serve static assets (specifically Datastar, WebAuthn scripts, and stylesheets)
|
|
app.use("/public/*", serveStatic({ root: "./" }));
|
|
|
|
// Root, Navigation & Logout Endpoints
|
|
app.get("/", (c) => {
|
|
c.header("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0");
|
|
return c.redirect("/login");
|
|
});
|
|
|
|
app.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 {
|
|
const authUser = await getAuthenticatedUser(c);
|
|
if (authUser) {
|
|
userId = authUser.userId;
|
|
}
|
|
for (const sId of sessionIds) {
|
|
try {
|
|
await valkey.del(sId);
|
|
await sqlWrapper.sql`DELETE FROM sessions WHERE id = ${sId}`;
|
|
} catch (_e) {}
|
|
}
|
|
} catch (_e) {}
|
|
}
|
|
|
|
if (rawRedirect && isSafeRedirectUrl(rawRedirect)) {
|
|
safeRedirect = rawRedirect;
|
|
}
|
|
|
|
auditWrapper.auditLog(userId, "logout_success", "session", null, userIp);
|
|
|
|
const cookieDomain = getCookieDomain();
|
|
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",
|
|
});
|
|
|
|
c.header("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0");
|
|
return c.redirect(safeRedirect || "/login");
|
|
});
|
|
|
|
// Wire Sub-routers and Vertical Slices
|
|
app.route("/pass", passRoutes);
|
|
app.route("/", forwardAuthRoutes);
|
|
app.route("/", authRoutes);
|
|
app.route("/", eventsRoutes);
|
|
app.route("/", sessionRoutes);
|
|
app.route("/admin", adminRoutes);
|
|
app.route("/api/admin", adminRoutes);
|
|
|
|
// Workload Mesh ConnectRPC Daemon
|
|
startConnectRpcServer(app);
|
|
|
|
// Basic health check
|
|
app.get("/healthz", (c) => c.text("OK"));
|
|
|
|
if (import.meta.main) {
|
|
const rpID = Deno.env.get("RP_ID");
|
|
const origin = Deno.env.get("ORIGIN");
|
|
|
|
if (!rpID || !origin) {
|
|
console.warn(
|
|
"[Auth-Yes] Warning: RP_ID or ORIGIN not set in environment. Falling back to defaults.",
|
|
);
|
|
}
|
|
|
|
console.log("[Auth-Yes] Initializing FIDO MDS3 Metadata Blob...");
|
|
try {
|
|
await MetadataService.initialize();
|
|
console.log("[Auth-Yes] FIDO MDS3 Metadata Blob successfully loaded.");
|
|
} catch (error) {
|
|
console.warn(
|
|
"[Auth-Yes] Failed to initialize FIDO MDS3 Metadata Blob (offline mode):",
|
|
error,
|
|
);
|
|
}
|
|
|
|
console.log("[Auth-Yes] Initializing database and cache...");
|
|
await initDb();
|
|
await pingValkey();
|
|
|
|
const port = parseInt(Deno.env.get("PORT") || "8000", 10);
|
|
console.log(`[Auth-Yes] Hypermedia Server running on port ${port}`);
|
|
Deno.serve({ port }, app.fetch);
|
|
}
|
|
|
|
export default app;
|