feat(phase-5a): complete runtime switchover to src/main.ts and isolate cleanup task (#61)

This commit is contained in:
Tyler Gillispie 2026-08-27 20:52:45 -07:00 committed by GitHub
parent 58ac8a983d
commit ee02daba79
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 945 additions and 19 deletions

View File

@ -7,8 +7,8 @@
],
"license": "MIT OR Apache-2.0",
"tasks": {
"dev": "deno run --watch -A --unstable-ffi server/main.ts",
"start": "deno run -A --unstable-ffi server/main.ts",
"dev": "deno run --watch -A --unstable-ffi src/main.ts",
"start": "deno run -A --unstable-ffi src/main.ts",
"test": "deno test -A --unstable-ffi",
"lint": "deno lint && deno run -A scripts/lint_arch.ts",
"lint:arch": "deno run -A scripts/lint_arch.ts",

98
src/core/forward_auth.ts Normal file
View File

@ -0,0 +1,98 @@
/**
* Deterministic fast path prefix matcher for dynamic bypasses
*/
export function isPathBypassed(
requestPath: string,
bypassPaths?: string[],
): boolean {
if (!bypassPaths || bypassPaths.length === 0) return false;
for (const pattern of bypassPaths) {
if (pattern === requestPath) return true;
if (pattern.endsWith("/*")) {
const prefix = pattern.slice(0, -2);
if (requestPath === prefix || requestPath.startsWith(prefix + "/")) {
return true;
}
}
}
return false;
}
/**
* Pure native Deno bitwise CIDR matcher
*/
export function isIpAllowed(
clientIp: string,
allowedCidrs?: string[],
): boolean {
if (!allowedCidrs || allowedCidrs.length === 0) return false;
const parseIp4 = (ip: string) => {
const parts = ip.split(".");
if (parts.length !== 4) return null;
return parts.reduce((acc, octet) => (acc << 8) + parseInt(octet, 10), 0) >>>
0;
};
const primaryIp = clientIp.split(",")[0].trim();
const ipNum = parseIp4(primaryIp);
for (const cidr of allowedCidrs) {
const [subnet, maskStr] = cidr.split("/");
if (!maskStr) {
if (subnet === primaryIp) return true;
continue;
}
if (ipNum !== null && subnet.includes(".")) {
const subnetNum = parseIp4(subnet);
if (subnetNum !== null) {
const maskBits = parseInt(maskStr, 10);
const mask = maskBits === 0
? 0
: ((0xffffffff << (32 - maskBits)) >>> 0);
if ((ipNum & mask) === (subnetNum & mask)) {
return true;
}
}
} else if (subnet === clientIp) {
return true;
}
}
return false;
}
/**
* Validates a given URL to ensure it is safe to redirect to.
*/
export function isSafeRedirectUrl(
rawUrl: string,
customDomain?: string,
): boolean {
if (!rawUrl) return false;
if (rawUrl.startsWith("/") && !rawUrl.startsWith("//")) {
return true;
}
try {
const parsed = new URL(rawUrl);
const host = parsed.hostname;
const root = customDomain || Deno.env.get("RP_ID") || "atyg.org";
const cleanRoot = root.replace(/^\./, "");
if (
host === "localhost" ||
host === "atyg.org" ||
host.endsWith(".atyg.org") ||
host === cleanRoot ||
host.endsWith(`.${cleanRoot}`)
) {
return true;
}
} catch (_e) {
return false;
}
return false;
}

180
src/core/http_signatures.ts Normal file
View File

@ -0,0 +1,180 @@
import { valkey } from "./valkey.ts";
/**
* Computes the RFC 7638 JWK Thumbprint of an Ed25519 OKP key.
* This canonicalizes the JWK by ordering the keys: crv, kty, x
*/
export async function computeJwkThumbprint(
jwk: { kty: string; crv: string; x: string },
): Promise<string> {
if (jwk.kty !== "OKP" || jwk.crv !== "Ed25519" || !jwk.x) {
throw new Error("Invalid Ed25519 JWK");
}
const canonicalJwk = `{"crv":"Ed25519","kty":"OKP","x":"${jwk.x}"}`;
const data = new TextEncoder().encode(canonicalJwk);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
return hex;
}
/**
* Extracts and verifies an RFC 9421 HTTP Message Signature on a given Request.
* Returns the fingerprint if verified, or throws an error.
*/
export async function verifyHttpSignature(req: Request): Promise<string> {
const signatureInput = req.headers.get("Signature-Input");
const signature = req.headers.get("Signature");
if (!signatureInput || !signature) {
throw new Error("Missing HTTP Message Signature headers");
}
const sigMatch = signatureInput.match(/sig1=\(([^)]+)\)(.*)/);
if (!sigMatch) {
throw new Error("Invalid Signature-Input format");
}
const componentsStr = sigMatch[1];
const paramsStr = sigMatch[2];
const components = componentsStr.split(" ").map((s) => s.replace(/"/g, ""));
const params = new Map<string, string>();
const paramRegex = /;([a-z]+)=([^;]+)/g;
let match;
while ((match = paramRegex.exec(paramsStr)) !== null) {
let val = match[2];
if (val.startsWith('"') && val.endsWith('"')) {
val = val.slice(1, -1);
}
params.set(match[1], val);
}
// 1. Time bounds validation
const created = parseInt(params.get("created") || "0", 10);
const expires = parseInt(params.get("expires") || "0", 10);
if (!created || !expires) {
throw new Error("Missing created or expires timestamp");
}
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - created) > 30) {
throw new Error("Signature created timestamp out of drift bounds");
}
if (now > expires) {
throw new Error("Signature expired");
}
// 2. Parse HWK
const hwkBase64 = params.get("hwk");
if (!hwkBase64) {
throw new Error("Missing inline hwk parameter");
}
let hwk;
try {
const hwkJson = atob(hwkBase64.replace(/-/g, "+").replace(/_/g, "/"));
hwk = JSON.parse(hwkJson);
} catch (_err) {
throw new Error("Invalid base64url or JSON in hwk parameter");
}
if (hwk.kty !== "OKP" || hwk.crv !== "Ed25519" || !hwk.x) {
throw new Error("Only Ed25519 OKP keys are supported");
}
const fingerprint = await computeJwkThumbprint(hwk);
// 3. Check Valkey Authorization First
try {
const isMember = await valkey.sismember(
"auth:hwk:fingerprints",
fingerprint,
);
if (isMember !== 1) {
throw new Error("Fingerprint not authorized");
}
} catch (err: any) {
if (err.message?.includes("Fingerprint not authorized")) {
throw err;
}
console.error("Valkey error during signature verification:", err);
throw new Error("Internal server error");
}
// 4. Build Canonical Signature Base
const url = new URL(req.url);
let signatureBase = "";
const xForwardedMethod = req.headers.get("X-Forwarded-Method");
const xForwardedUri = req.headers.get("X-Forwarded-Uri");
const xForwardedHost = req.headers.get("X-Forwarded-Host");
const originalMethod = xForwardedMethod || req.method;
const originalPath = xForwardedUri
? new URL(xForwardedUri, "http://localhost").pathname +
new URL(xForwardedUri, "http://localhost").search
: url.pathname + url.search;
const originalHost = xForwardedHost || req.headers.get("host") || url.host;
for (const comp of components) {
if (comp === "@method") {
signatureBase += `"@method": ${originalMethod.toLowerCase()}\n`;
} else if (comp === "@path") {
signatureBase += `"@path": ${originalPath}\n`;
} else if (comp === "@authority") {
signatureBase += `"@authority": ${originalHost}\n`;
} else {
const headerVal = req.headers.get(comp);
if (headerVal === null) {
throw new Error(`Missing required signature component: ${comp}`);
}
signatureBase += `"${comp}": ${headerVal}\n`;
}
}
signatureBase += `"@signature-params": (${componentsStr})${paramsStr}`;
// 5. Verify the signature
const sigValueMatch = signature.match(/sig1=:([^:]+):/);
if (!sigValueMatch) {
throw new Error("Invalid Signature header format");
}
const rawSigValue = sigValueMatch[1];
let sigBytes;
try {
const sigStr = atob(rawSigValue.replace(/-/g, "+").replace(/_/g, "/"));
sigBytes = new Uint8Array(sigStr.length);
for (let i = 0; i < sigStr.length; i++) {
sigBytes[i] = sigStr.charCodeAt(i);
}
} catch {
throw new Error("Invalid base64 encoding in signature");
}
const importedKey = await crypto.subtle.importKey(
"jwk",
hwk,
{ name: "Ed25519" },
false,
["verify"],
);
const msgBytes = new TextEncoder().encode(signatureBase);
const isValid = await crypto.subtle.verify(
"Ed25519",
importedKey,
sigBytes,
msgBytes,
);
if (!isValid) {
throw new Error("Invalid cryptographic signature");
}
return fingerprint;
}

171
src/core/rpc.ts Normal file
View File

@ -0,0 +1,171 @@
import { AuthService } from "../../sdk/gen/auth_connect.ts";
import {
universalServerRequestFromFetch,
universalServerResponseToFetch,
} from "npm:@connectrpc/connect@^1.4.0/protocol";
import type { ConnectRouter } from "npm:@connectrpc/connect@^1.4.0";
import { createConnectRouter } from "npm:@connectrpc/connect@^1.4.0";
import type { Hono } from "jsr:@hono/hono@4";
import { spireWrapper } from "./spire_ffi.ts";
import { sqlWrapper } from "./db.ts";
import { valkey } from "./valkey.ts";
import { auditWrapper } from "./audit.ts";
export const connectRoutes = (router: ConnectRouter) => {
router.service(AuthService, {
async validateSession(req, context) {
try {
const spiffeId = spireWrapper.extractSpiffeIdFromCert(
context.requestHeader.get("x-peer-cert") || "",
);
if (!spiffeId) {
return {
valid: false,
uuid: "",
scopes: [],
error: "Validation failed",
};
}
const appRecord = await sqlWrapper
.sql`SELECT id FROM apps WHERE spiffe_id = ${spiffeId}`.then((
res: any,
) => res[0]);
if (!appRecord) {
auditWrapper.auditLog(null, "session_validation_failed", null, {
reason: "Unauthorized SPIFFE ID",
}, "internal-grpc");
return {
valid: false,
uuid: "",
scopes: [],
error: "Validation failed",
};
}
const token = req.token;
if (!token) {
return {
valid: false,
uuid: "",
scopes: [],
error: "Validation failed",
};
}
let sessionDataStr;
try {
sessionDataStr = await valkey.get(token);
} catch (err: unknown) {
console.error("Valkey error during validateSession:", err);
return {
valid: false,
uuid: "",
scopes: [],
error: "Internal server error",
};
}
if (!sessionDataStr) {
auditWrapper.auditLog(
null,
"session_validation_failed",
appRecord.id,
{
reason: "Session invalid or expired",
},
"internal-grpc",
);
return {
valid: false,
uuid: "",
scopes: [],
error: "Validation failed",
};
}
let sessionData;
try {
sessionData = JSON.parse(sessionDataStr);
} catch (err: unknown) {
console.error("JSON parse error during validateSession:", err);
return {
valid: false,
uuid: "",
scopes: [],
error: "Internal server error",
};
}
if (!sessionData || !sessionData.uuid) {
return {
valid: false,
uuid: "",
scopes: [],
error: "Internal server error",
};
}
const userId = sessionData.uuid;
const grantRecord = await sqlWrapper
.sql`SELECT role FROM user_grants WHERE user_id = ${userId} AND app_id = ${appRecord.id}`
.then((res: any) => res[0]);
if (!grantRecord) {
auditWrapper.auditLog(
userId,
"session_validation_failed",
appRecord.id,
{
reason: "Access denied (RBAC)",
},
"internal-grpc",
);
return {
valid: false,
uuid: "",
scopes: [],
error: "Validation failed",
};
}
return {
valid: true,
uuid: userId,
scopes: [grantRecord.role],
error: "",
};
} catch (err: unknown) {
console.error("Unexpected error in validateSession:", err);
return {
valid: false,
uuid: "",
scopes: [],
error: "Internal server error",
};
}
},
});
};
export const startConnectRpcServer = (app: Hono) => {
const router = createConnectRouter();
connectRoutes(router);
const handlers = router.handlers;
app.all("/auth.v1.AuthService/*", async (c) => {
const url = new URL(c.req.url);
const handler = handlers.find((h) => h.requestPath === url.pathname);
if (!handler) {
return new Response("Not Found", { status: 404 });
}
const uReq = universalServerRequestFromFetch(c.req.raw, {});
const uRes = await handler(uReq);
return universalServerResponseToFetch(uRes);
});
};

View File

@ -231,3 +231,44 @@ export async function requireAdmin(c: Context, next: () => Promise<void>) {
}
await next();
}
/**
* Resolves application record by domain with Valkey caching.
*/
export async function getAppByHost(host: string): Promise<AppRecord | null> {
const cacheKey = `auth:app_by_host:${host}`;
try {
const cached = await valkey.get(cacheKey);
if (cached) return JSON.parse(cached);
} catch (_e) {}
try {
const app = await sqlWrapper.sql`
SELECT id, name, domain, is_public, bypass_paths, allowed_cidrs
FROM apps WHERE domain = ${host} LIMIT 1
`.then((res: any) => res[0]);
if (app) {
await valkey.setex(cacheKey, 300, JSON.stringify(app)).catch(() => {});
return app;
}
} catch (_e) {}
return null;
}
/**
* Resolves role grant for a user on a given app.
*/
export async function getUserGrant(
userId: string,
appId: string,
): Promise<string | null> {
try {
const grant = await sqlWrapper.sql`
SELECT role FROM user_grants WHERE user_id = ${userId} AND app_id = ${appId} LIMIT 1
`.then((res: any) => res[0]);
return grant?.role || null;
} catch (_e) {
return null;
}
}

View File

@ -0,0 +1,31 @@
import { assertEquals } from "jsr:@std/assert@1";
import app from "../../main.ts";
import {
isIpAllowed,
isPathBypassed,
isSafeRedirectUrl,
} from "../../core/forward_auth.ts";
Deno.test("[ForwardAuth] GET /api/forward-auth without host returns 400", async () => {
const res = await app.fetch(new Request("http://localhost/api/forward-auth"));
assertEquals(res.status, 400);
});
Deno.test("[ForwardAuth] Helpers: isPathBypassed correctly matches prefixes", () => {
assertEquals(isPathBypassed("/public/styles.css", ["/public/*"]), true);
assertEquals(isPathBypassed("/api/healthz", ["/api/healthz"]), true);
assertEquals(isPathBypassed("/admin", ["/public/*"]), false);
});
Deno.test("[ForwardAuth] Helpers: isIpAllowed handles IPv4 CIDR matching", () => {
assertEquals(isIpAllowed("192.168.1.50", ["192.168.1.0/24"]), true);
assertEquals(isIpAllowed("10.0.0.1", ["192.168.1.0/24"]), false);
assertEquals(isIpAllowed("127.0.0.1", ["127.0.0.1"]), true);
});
Deno.test("[ForwardAuth] Helpers: isSafeRedirectUrl validates target destinations", () => {
assertEquals(isSafeRedirectUrl("/dashboard"), true);
assertEquals(isSafeRedirectUrl("https://auth.atyg.org/login"), true);
assertEquals(isSafeRedirectUrl("https://evil.com"), false);
assertEquals(isSafeRedirectUrl("//evil.com"), false);
});

View File

@ -0,0 +1,164 @@
import { Hono } from "jsr:@hono/hono@4";
import { sqlWrapper } from "../../core/db.ts";
import {
getAppByHost,
getAuthenticatedUser,
getUserGrant,
isGlobalAdmin,
} from "../../core/session.ts";
import { isIpAllowed, isPathBypassed } from "../../core/forward_auth.ts";
import { verifyHttpSignature } from "../../core/http_signatures.ts";
export const forwardAuthRoutes = new Hono();
// ---------------------------------------------------------
// ForwardAuth Ingress Check (Traefik Ingress Middleware)
// ---------------------------------------------------------
forwardAuthRoutes.get("/api/forward-auth", async (c) => {
const host = c.req.header("X-Forwarded-Host");
if (!host) {
return c.text("Bad Request: Missing X-Forwarded-Host header", 400);
}
// 1. Resolve Target App (Valkey -> DB)
const appRecord = await getAppByHost(host);
if (!appRecord) {
const accept = c.req.header("Accept") || "";
if (accept.includes("text/html")) {
const rpID = Deno.env.get("RP_ID");
const loginDomain = rpID || "auth.atyg.org";
c.header(
"Cache-Control",
"no-store, no-cache, must-revalidate, max-age=0",
);
return c.redirect(
`https://${loginDomain}/errors/unregistered?host=${
encodeURIComponent(host)
}`,
302,
);
}
return c.json({ error: "Application not registered" }, 403);
}
// 1.5 Dynamic Bypass Check
const uri = c.req.header("X-Forwarded-Uri") || "/";
const clientIp = c.req.header("X-Forwarded-For") || "127.0.0.1";
const requestPath = new URL(uri, `http://${host}`).pathname;
if (
appRecord.is_public === true ||
isPathBypassed(requestPath, appRecord.bypass_paths) ||
isIpAllowed(clientIp, appRecord.allowed_cidrs)
) {
c.header("X-Forwarded-App-Id", appRecord.id);
return c.text("OK", 200);
}
// 2. Validate Session OR HTTP Signature
const signatureInput = c.req.header("Signature-Input");
const signature = c.req.header("Signature");
if (signatureInput && signature) {
try {
const fingerprint = await verifyHttpSignature(c.req.raw);
const serviceName = `service-node:${fingerprint.substring(0, 8)}`;
const serviceId = fingerprint;
const scopes = "edge-node,daemon";
c.header("X-Forwarded-User", serviceName);
c.header("X-Forwarded-User-Id", serviceId);
c.header("X-Forwarded-Scopes", scopes);
c.header("X-Forwarded-App-Id", appRecord.id);
return c.text("OK", 200);
} catch (err: any) {
return c.text(`Unauthorized: ${err.message}`, 401);
}
}
// Standard User Session Path
const auth = await getAuthenticatedUser(c);
if (!auth) {
const accept = c.req.header("Accept") || "";
const proto = c.req.header("X-Forwarded-Proto") || "https";
const uri = c.req.header("X-Forwarded-Uri") || "/";
const originalUrl = `${proto}://${host}${uri}`;
if (accept.includes("text/html")) {
const rpID = Deno.env.get("RP_ID");
const loginDomain = rpID || "auth.atyg.org";
c.header(
"Cache-Control",
"no-store, no-cache, must-revalidate, max-age=0",
);
return c.redirect(
`https://${loginDomain}/login?redirect=${
encodeURIComponent(originalUrl)
}`,
302,
);
}
return c.text("Unauthorized", 401);
}
if (auth.isPaused) {
return c.text("Forbidden: Session Paused by Host", 403);
}
const user = await sqlWrapper.sql`
SELECT id, username, account_status
FROM users
WHERE id = ${auth.userId}
`.then((res: any) => res[0]);
if (
!user ||
(user.account_status !== "active" && user.account_status !== "guest")
) {
return c.text("Forbidden: Account inactive", 403);
}
// 3. Resolve Grants and Roles
let scopes = "";
if (
user.account_status === "guest" ||
(auth.customScopes && auth.customScopes.length > 0)
) {
const customScopes = auth.customScopes || [];
const hasAppScope = customScopes.includes(`app:${appRecord.name}`) ||
customScopes.includes("*");
if (!hasAppScope) {
return c.text(
"Forbidden: Access denied to this application (Guest/Delegated)",
403,
);
}
scopes = customScopes.filter(Boolean).join(",") || "viewer";
} else {
const globalAdmin = await isGlobalAdmin(auth.userId);
const grantRole = await getUserGrant(auth.userId, appRecord.id);
if (!globalAdmin && !grantRole) {
return c.text("Forbidden: Access denied to this application", 403);
}
scopes = [
...new Set([grantRole, globalAdmin ? "admin" : null].filter(Boolean)),
].join(",");
}
// 4. Inject Headers
c.header("X-Forwarded-User", user.username);
c.header("X-Forwarded-User-Id", user.id);
c.header("X-Forwarded-Scopes", scopes);
c.header("X-Forwarded-App-Id", appRecord.id);
return c.text("OK", 200);
});

View File

@ -0,0 +1,13 @@
import { assertEquals } from "jsr:@std/assert@1";
import app from "../../main.ts";
Deno.test("[Passes] GET /pass without token redirects to /login?error=invalid_or_expired_pass", async () => {
const res = await app.fetch(new Request("http://localhost/pass"));
assertEquals(res.status, 302);
assertEquals(
res.headers.get("location")?.includes(
"/login?error=invalid_or_expired_pass",
),
true,
);
});

View File

@ -0,0 +1,129 @@
import { Hono } from "jsr:@hono/hono@4";
import { deleteCookie, setCookie } from "jsr:@hono/hono@4/cookie";
import { valkey } from "../../core/valkey.ts";
import { sqlWrapper } from "../../core/db.ts";
import { getCookieDomain } from "../../core/session.ts";
export const passRoutes = new Hono();
// ---------------------------------------------------------
// Ephemeral 1-Click Magic Link Redemption (/pass)
// ---------------------------------------------------------
passRoutes.get("/", async (c) => {
const token = c.req.query("token");
if (!token) {
return c.redirect("/login?error=invalid_or_expired_pass", 302);
}
// 1. Validate against Valkey, fallback to PostgreSQL
let sessionDataStr = null;
try {
sessionDataStr = await valkey.get(token);
} catch (_err) {}
let sessionInfo: any = null;
if (sessionDataStr) {
try {
sessionInfo = JSON.parse(sessionDataStr);
} catch (_err) {}
}
let expiresAtDate: Date | null = null;
let customScopes: string[] = [];
if (!sessionInfo || !sessionInfo.uuid) {
try {
const nowIso = new Date().toISOString();
const session = await sqlWrapper.sql`
SELECT s.user_id, s.expires_at, s.label, s.is_agent, s.custom_scopes, u.username
FROM sessions s
JOIN users u ON s.user_id = u.id
WHERE s.id = ${token} AND s.expires_at > ${nowIso}
`.then((res: any) => res[0]);
if (!session) {
return c.redirect("/login?error=invalid_or_expired_pass", 302);
}
sessionInfo = {
uuid: session.user_id,
username: session.username,
label: session.label,
isAgent: session.is_agent,
customScopes: session.custom_scopes,
};
expiresAtDate = new Date(session.expires_at);
customScopes = session.custom_scopes || [];
try {
const ttlSeconds = Math.max(
1,
Math.floor((expiresAtDate.getTime() - Date.now()) / 1000),
);
await valkey.setex(token, ttlSeconds, JSON.stringify(sessionInfo));
} catch (_e) {}
} catch (_err) {
return c.redirect("/login?error=invalid_or_expired_pass", 302);
}
} else {
try {
const ttl = await valkey.ttl(token);
if (ttl <= 0) {
return c.redirect("/login?error=invalid_or_expired_pass", 302);
}
expiresAtDate = new Date(Date.now() + ttl * 1000);
customScopes = sessionInfo.customScopes || sessionInfo.custom_scopes ||
[];
} catch (_err) {
return c.redirect("/login?error=invalid_or_expired_pass", 302);
}
}
if (!sessionInfo || !expiresAtDate) {
return c.redirect("/login?error=invalid_or_expired_pass", 302);
}
// 2. Cookie Scoping
deleteCookie(c, "session_id", { path: "/" });
const rpID = Deno.env.get("RP_ID");
const cookieDomain = getCookieDomain(rpID);
const ttlSeconds = Math.max(
1,
Math.floor((expiresAtDate.getTime() - Date.now()) / 1000),
);
setCookie(c, "session_id", token, {
path: "/",
domain: cookieDomain,
httpOnly: true,
secure: true,
sameSite: "Lax",
maxAge: ttlSeconds,
});
// 3. Redirect URL Resolution
let targetDomain = null;
if (Array.isArray(customScopes)) {
const appScope = customScopes.find((s: string) =>
typeof s === "string" && s.startsWith("app:")
);
if (appScope) {
const appName = appScope.substring(4);
try {
const appRecord = await sqlWrapper.sql`
SELECT domain FROM apps WHERE name = ${appName}
`.then((res: any) => res[0]);
if (appRecord && appRecord.domain) {
targetDomain = appRecord.domain;
}
} catch (_err) {}
}
}
if (targetDomain) {
return c.redirect(`https://${targetDomain}`, 302);
} else {
return c.redirect("/dashboard", 302);
}
});

View File

@ -1,14 +1,19 @@
import { Hono } from "jsr:@hono/hono@4";
import { serveStatic } from "jsr:@hono/hono@4/deno";
import { MetadataService } from "jsr:@simplewebauthn/server@13";
import { initDb } from "./core/db.ts";
import { pingValkey } 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 { 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();
@ -18,22 +23,48 @@ app.use("*", contentNegotiation());
// Serve static assets (specifically Datastar and client scripts)
app.use("/public/*", serveStatic({ root: "./" }));
// Wire Feature Slices
// 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);
// Basic health check for foundation
// Workload Mesh ConnectRPC Daemon
startConnectRpcServer(app);
// Basic health check
app.get("/healthz", (c) => c.text("OK"));
if (import.meta.main) {
console.log("[Auth-Yes Next] Bootstrapping core foundation...");
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);
}

View File

@ -3,27 +3,42 @@
## 1. Test Suite & Verification
- **`deno fmt`**: Passed (All 6 architectural test suites formatted).
- **`deno task lint`**: Passed (`deno lint` and `scripts/lint_arch.ts` passed with 0 errors; all files $\le 72$ lines, zero banned DOM API violations).
- **`deno task check`**: Passed across all workspace modules (`server/`, `sdk/`, `ui/`, `infra/`, `src/`).
- **`deno test -A --no-check`**: Passed (90 tests across 30 steps with 0 failures).
- **`deno task lint`**: Passed (`deno lint` and `scripts/lint_arch.ts` passed
with 0 errors; all files $\le 72$ lines, zero banned DOM API violations).
- **`deno task check`**: Passed across all workspace modules (`server/`, `sdk/`,
`ui/`, `infra/`, `src/`).
- **`deno test -A --no-check`**: Passed (90 tests across 30 steps with 0
failures).
## 2. Scope Implemented & Verified
1. **Transport Efficiency & Latency (`src/tests/arch/transport_efficiency.test.ts`):**
- Asserts non-streaming point-to-point actions (`/join`, `/login`) execute rapidly without SSE overhead.
- Asserts the 16KB payload ceiling guard rejects oversized bodies with 413/400.
2. **SSE Stream Lifecycle & Leak Teardown (`src/tests/arch/sse_lifecycle.test.ts`):**
- Asserts `streamDatastar` handles client `AbortSignal` disconnects gracefully and executes clean teardown logic.
1. **Transport Efficiency & Latency
(`src/tests/arch/transport_efficiency.test.ts`):**
- Asserts non-streaming point-to-point actions (`/join`, `/login`) execute
rapidly without SSE overhead.
- Asserts the 16KB payload ceiling guard rejects oversized bodies with
413/400.
2. **SSE Stream Lifecycle & Leak Teardown
(`src/tests/arch/sse_lifecycle.test.ts`):**
- Asserts `streamDatastar` handles client `AbortSignal` disconnects
gracefully and executes clean teardown logic.
3. **Proxy Buffering Invariant (`src/tests/arch/proxy_buffering.test.ts`):**
- Validates that streaming endpoints emit `X-Accel-Buffering: no` and `Cache-Control: no-cache` headers to bypass reverse-proxy buffering.
- Validates that streaming endpoints emit `X-Accel-Buffering: no` and
`Cache-Control: no-cache` headers to bypass reverse-proxy buffering.
4. **Error Fragment Morph Invariant (`src/tests/arch/error_fragment.test.ts`):**
- Validates that validation and route errors return HTML fragments targeting `#status-banner` or `.field-error`.
- Validates that validation and route errors return HTML fragments targeting
`#status-banner` or `.field-error`.
5. **XSS & Escape Fuzzing Harness (`src/tests/arch/xss_fuzzing.test.tsx`):**
- Fuzzes event names, user labels, and usernames with `<script>`, `onerror=`, and attribute breakout payloads to ensure Hono SSR JSX strictly escapes all dynamic entities into safe HTML entities.
6. **Dual-Mode Content Negotiation (`src/tests/arch/content_negotiation.test.ts`):**
- Tests `determineClientType()` across `datastar`, `browser`, `shell` (curl), and `cli` (JSON) request profiles.
- Fuzzes event names, user labels, and usernames with `<script>`, `onerror=`,
and attribute breakout payloads to ensure Hono SSR JSX strictly escapes all
dynamic entities into safe HTML entities.
6. **Dual-Mode Content Negotiation
(`src/tests/arch/content_negotiation.test.ts`):**
- Tests `determineClientType()` across `datastar`, `browser`, `shell` (curl),
and `cli` (JSON) request profiles.
7. **Core Decoupling & Self-Containment:**
- 100% of all imports in `src/` are internal to `src/core/` and `src/shared/ui/`.
- 100% of all imports in `src/` are internal to `src/core/` and
`src/shared/ui/`.
- Zero legacy `server/` imports remain.
## 3. Decision

View File

@ -0,0 +1,53 @@
# Task: Legacy Directory Cleanup & Workspace Pruning (Phase 5B)
- **Status:** PENDING / QUEUED
- **Trigger:** Execute after runtime stability and smoke tests on `src/main.ts`
have been verified in staging/production.
- **Estimated Effort:** 10 minutes (Zero functional risk once `src/` is running
standalone).
---
## 1. Context & Objectives
The hypermedia architecture transition has rebuilt all features (`auth`,
`admin`, `events`, `sessions`, `forward_auth`) into vertical slices inside
`src/`. `src/` is 100% self-contained and has zero dependencies on legacy code.
This task removes the legacy `server/` and `ui/` directories and prunes
workspace entries from `deno.json`.
---
## 2. Step-by-Step Execution Plan
### Step 1: Remove Legacy Directories
```bash
git rm -rf server/ ui/
```
### Step 2: Prune `deno.json` Workspace
In root `deno.json`:
- Remove `"./server"` and `"./ui"` from `"workspace"`.
- Keep `"./sdk"` and `"./src"`.
- In `"check"` task, update paths to:
`deno check sdk/**/*.ts infra/**/*.ts src/**/*.ts src/**/*.tsx`.
### Step 3: Verify All Quality Gates
```bash
deno fmt
deno task lint
deno task check
deno test -A --no-check
```
### Step 4: Commit & Ship
```bash
git commit -m "chore(cleanup): remove legacy server/ and ui/ directories"
git push origin <branch-name>
```