import { Hono } from "jsr:@hono/hono@4"; import { deleteCookie, setCookie } from "jsr:@hono/hono@4/cookie"; import { valkey } from "../valkey.ts"; import { sqlWrapper } from "../db.ts"; import { getCookieDomain } from "../auth-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); } });