import { deleteCookie, getSignedCookie, setSignedCookie } from "hono/cookie"; import { base64UrlDecode, base64UrlEncode, utf8Bytes, utf8String } from "@/worker/auth/bytes"; import { deriveHmacKeyBytes } from "@/worker/auth/crypto"; import type { AppContext } from "@/worker/types"; const oidcCookieName = "dab_oidc_tx"; const sessionCookieName = "dab_session"; const cookieInfo = "cookie-signing"; const oidcSalt = "dab:oidc-transaction:v1"; const sessionSalt = "dab:api-session:v1"; export const oidcTransactionTtlMs = 10 * 60 * 1000; export const apiSessionTtlMs = 6 * 60 * 60 * 1000; export interface OidcTransactionCookie { state: string; nonce: string; codeVerifier: string; returnTo: string; createdAtMs: number; expiresAtMs: number; } export interface SessionCookie { subjectId: string; storageId: string; sessionId: string; createdAtMs: number; expiresAtMs: number; } async function cookieSecret(secret: string, salt: string): Promise { return await deriveHmacKeyBytes(secret, salt, cookieInfo); } function decodePayload(value: string | false | undefined): T | null { if (!value) return null; try { return JSON.parse(utf8String(base64UrlDecode(value))) as T; } catch { return null; } } const hostCookieOptions = { prefix: "host", httpOnly: true, secure: true, sameSite: "Lax", path: "/", } as const; export async function setOidcTransactionCookie(c: AppContext, transaction: OidcTransactionCookie): Promise { const value = base64UrlEncode(utf8Bytes(JSON.stringify(transaction))); await setSignedCookie(c, oidcCookieName, value, await cookieSecret(c.env.TESSERA_OIDC_CLIENT_SECRET, oidcSalt), { ...hostCookieOptions, maxAge: Math.floor(oidcTransactionTtlMs / 1000), expires: new Date(transaction.expiresAtMs), }); } export async function getOidcTransactionCookie(c: AppContext): Promise { const value = await getSignedCookie( c, await cookieSecret(c.env.TESSERA_OIDC_CLIENT_SECRET, oidcSalt), oidcCookieName, "host", ); return decodePayload(value); } export function clearOidcTransactionCookie(c: AppContext): void { deleteCookie(c, oidcCookieName, { prefix: "host", path: "/" }); } export async function setSessionCookie(c: AppContext, session: SessionCookie): Promise { const value = base64UrlEncode(utf8Bytes(JSON.stringify(session))); await setSignedCookie(c, sessionCookieName, value, await cookieSecret(c.env.DAB_SESSION_SECRET, sessionSalt), { ...hostCookieOptions, maxAge: Math.floor((session.expiresAtMs - session.createdAtMs) / 1000), expires: new Date(session.expiresAtMs), }); } export async function getSessionCookie(c: AppContext): Promise { const value = await getSignedCookie( c, await cookieSecret(c.env.DAB_SESSION_SECRET, sessionSalt), sessionCookieName, "host", ); return decodePayload(value); } export function clearSessionCookie(c: AppContext): void { deleteCookie(c, sessionCookieName, { prefix: "host", path: "/" }); }