import { define } from "../utils.ts"; import { Context } from "fresh"; import * as Iron from "iron"; import { deleteStateKey, getStateKeys, setStateKey } from "./userService.ts"; import { getCookies, setCookie } from "$std/http/cookie.ts"; import { runStateKeyCleanup } from "./dbCleanup.ts"; import { generatePassword, isSecureRequest, persistWithEncryptedCookie, SessionCookie, } from "../routes/auth/callback.ts"; import { getE2ESession, isE2EEnabled } from "./e2eFixtures.ts"; const useDb = true; export function generateNonce(length = 32) { const array = new Uint8Array(length); crypto.getRandomValues(array); return btoa(String.fromCharCode(...array)).replace(/[^a-zA-Z0-9]/g, ""); } export interface SessionCookieParams { sessionCookie: string | null; stateId: string | null; } export function readSessionCookiesFromCookies( headers: Headers, ): SessionCookieParams { const cookies: Record = getCookies(headers); return { sessionCookie: cookies["session"] ?? null, stateId: cookies["xx_state_id"] ?? null, }; } export async function hashText(text: string): Promise { const encoder = new TextEncoder(); const data = encoder.encode(text); const hashBuffer = await crypto.subtle.digest("SHA-256", data); const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join( "", ); return hashHex; } export function InitiPGSessionMiddleware() { return define.middleware(async (ctx: Context) => { const url = new URL(ctx.req.url); if (isE2EEnabled()) { const e2eUser = ctx.req.headers.get("x-e2e-user") ?? url.searchParams.get("e2e-user"); const e2eSession = getE2ESession(e2eUser); if (e2eSession) { ctx.state.photoUrl = ""; ctx.state.data = e2eSession; ctx.state.host = url.href; ctx.state.urlRoute = url.pathname; ctx.state.stateId = "e2e-state"; return await ctx.next(); } } if (url.hostname === "cyberhybridhub.com") { return Response.redirect("https://portal.cyberhybridhub.com", 302); } if ( url.pathname.startsWith("/api/ai-images/file/") || url.pathname.startsWith("/api/audio-blob/") || url.pathname.startsWith("/api/image-blob/") || url.pathname === "/health" || url.pathname.startsWith("/auth/") || url.pathname.startsWith("/node_modules/") || url.pathname.startsWith("/.") || url.pathname.startsWith("/favicon.ico") ) { return await ctx.next(); } const timestampNow = Date.now(); let sessionData: SessionCookie | null = null; const sessionCookieParams = readSessionCookiesFromCookies(ctx.req.headers); if (url.searchParams.get("popstate") === "true") { console.log("popstate"); url.searchParams.delete("popstate"); if (sessionCookieParams.sessionCookie) { url.pathname = "/auth/logout"; return Response.redirect(url.toString(), 302); } else { return Response.redirect(url.toString(), 302); } } const xxStateId = sessionCookieParams.stateId; let theStateKey: string | null = null; if (xxStateId) { if (useDb) { const stateKeys = await getStateKeys(xxStateId); stateKeys.sort((a: any, b: any) => b.created_on - a.created_on); const timestamp = timestampNow - (1000 * 60 * 60); for (const stateKey of stateKeys) { if (stateKey.created_on < timestamp) { deleteStateKey(stateKey.state_id); } else if (sessionCookieParams.sessionCookie) { let stateData: SessionCookie | null = null; try { stateData = await Iron.unseal( sessionCookieParams.sessionCookie, stateKey.seal_key, Iron.defaults, ) as SessionCookie; } catch (_e) { console.log("DEBUG stateMiddleware: error unsealing cookie", _e); return new Response(null, { status: 302, headers: { Location: "/auth/logout" }, }); } if (stateData && stateData.user_id) { const thisHash = await hashText(JSON.stringify(stateData)); if (thisHash === stateKey.cookie_hash) { const renewWindow = timestampNow - (5 * 60 * 1000); const newKey = generatePassword(); if (stateKey.created_on < renewWindow) { stateData.started_on = timestampNow; const cookieHash = await hashText(JSON.stringify(stateData)); await setStateKey(newKey, xxStateId, cookieHash); theStateKey = newKey; } else { theStateKey = stateKey.seal_key; } sessionData = stateData; break; } else { console.log( "DEBUG stateMiddleware: hash mismatch, cookie invalid", ); } } else { console.log( "DEBUG stateMiddleware: stateData missing or no user_id", { hasStateData: !!stateData, user_id: stateData?.user_id, }, ); } } } } } ctx.state.photoUrl = ""; ctx.state.data = sessionData; ctx.state.host = url.href; ctx.state.urlRoute = url.pathname; ctx.state.stateId = sessionCookieParams.stateId; const preResp = await ctx.next(); if (sessionCookieParams.sessionCookie && !sessionData) { const expiredDate = new Date(0).toUTCString(); const isSecure = isSecureRequest(ctx.req); const deleteCookie = `session=; Expires=${expiredDate}; Path=/; HttpOnly=true; Secure=${isSecure}; SameSite=Lax`; preResp.headers.append("Set-Cookie", deleteCookie); } runStateKeyCleanup().catch((err) => { console.error("State key cleanup error:", err); }); return theStateKey && sessionData ? persistWithEncryptedCookie( sessionData as SessionCookie, theStateKey, preResp, ctx.req, xxStateId || "", ) : preResp; }); }