export const ADMIN_APP_SESSION_COOKIE = "xtablo-admin-app-session"; const ADMIN_ACCESS_PATH = "/__admin/access"; const ADMIN_LOGOUT_PATH = "/__admin/logout"; const SESSION_TTL_SECONDS = 60 * 60 * 12; type WorkerEnv = { ADMIN_APP_ACCESS_TOKEN: string; ADMIN_APP_SESSION_SECRET: string; ASSETS: { fetch: (request: Request) => Promise; }; }; function base64UrlEncode(bytes: Uint8Array) { return btoa(String.fromCharCode(...bytes)) .replace(/\+/g, "-") .replace(/\//g, "_") .replace(/=/g, ""); } function base64UrlDecode(value: string) { const normalized = value.replace(/-/g, "+").replace(/_/g, "/"); const padding = normalized.length % 4 === 0 ? "" : "=".repeat(4 - (normalized.length % 4)); const binary = atob(`${normalized}${padding}`); return Uint8Array.from(binary, (character) => character.charCodeAt(0)); } async function importSigningKey(secret: string) { return crypto.subtle.importKey( "raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign", "verify"] ); } async function signValue(value: string, secret: string) { const key = await importSigningKey(secret); const signature = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(value)); return base64UrlEncode(new Uint8Array(signature)); } async function verifyValueSignature(value: string, signature: string, secret: string) { const key = await importSigningKey(secret); return crypto.subtle.verify( "HMAC", key, base64UrlDecode(signature), new TextEncoder().encode(value) ); } function parseCookie(cookieHeader: string | null, cookieName: string) { if (!cookieHeader) { return null; } const match = cookieHeader.match(new RegExp(`(?:^|;\\s*)${cookieName}=([^;]+)`)); return match?.[1] ?? null; } export async function createSignedAdminAppSession(secret: string, now = Date.now()) { const expiresAt = Math.floor(now / 1000) + SESSION_TTL_SECONDS; const payload = `${expiresAt}`; const signature = await signValue(payload, secret); return `${payload}.${signature}`; } export async function hasValidAdminAppSession(request: Request, secret: string, now = Date.now()) { const sessionCookie = parseCookie(request.headers.get("cookie"), ADMIN_APP_SESSION_COOKIE); if (!sessionCookie) { return false; } const [expiresAtValue, signature] = sessionCookie.split("."); if (!expiresAtValue || !signature) { return false; } const expiresAt = Number.parseInt(expiresAtValue, 10); if (Number.isNaN(expiresAt) || expiresAt <= Math.floor(now / 1000)) { return false; } return verifyValueSignature(expiresAtValue, signature, secret); } function isHtmlRequest(request: Request) { const accept = request.headers.get("accept") ?? ""; return accept.includes("text/html") || accept.includes("*/*"); } function buildSessionCookie(session: string) { return `${ADMIN_APP_SESSION_COOKIE}=${session}; HttpOnly; Path=/; SameSite=Strict; Secure; Max-Age=${SESSION_TTL_SECONDS}`; } function buildExpiredSessionCookie() { return `${ADMIN_APP_SESSION_COOKIE}=; HttpOnly; Path=/; SameSite=Strict; Secure; Max-Age=0`; } export function buildAccessDeniedHtml(error?: string) { return ` XTablo Admin Access
Internal Only

Internal Admin Access

This app is firewalled behind a dedicated app-access token before any admin session can be established.

${error ? `
${error}
` : ""}

A second privileged token is still required inside the admin shell.

`; } function respondUnauthorized(request: Request, error?: string) { if (isHtmlRequest(request)) { return new Response(buildAccessDeniedHtml(error), { headers: { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store", }, status: 401, }); } return new Response("Unauthorized", { status: 401 }); } async function handleAccessRequest(request: Request, env: WorkerEnv) { const formData = await request.formData().catch(() => null); const accessToken = formData?.get("accessToken"); if (accessToken !== env.ADMIN_APP_ACCESS_TOKEN) { return respondUnauthorized(request, "Invalid app access token"); } const session = await createSignedAdminAppSession(env.ADMIN_APP_SESSION_SECRET); const location = new URL("/", request.url).toString(); return new Response(null, { headers: { Location: location, "Set-Cookie": buildSessionCookie(session), "Cache-Control": "no-store", }, status: 302, }); } async function handleLogoutRequest(request: Request) { return new Response(null, { headers: { Location: new URL("/", request.url).toString(), "Set-Cookie": buildExpiredSessionCookie(), "Cache-Control": "no-store", }, status: 302, }); } export default { async fetch(request: Request, env: WorkerEnv) { const url = new URL(request.url); if (request.method === "POST" && url.pathname === ADMIN_ACCESS_PATH) { return handleAccessRequest(request, env); } if (request.method === "POST" && url.pathname === ADMIN_LOGOUT_PATH) { return handleLogoutRequest(request); } const hasSession = await hasValidAdminAppSession(request, env.ADMIN_APP_SESSION_SECRET); if (!hasSession) { return respondUnauthorized(request); } return env.ASSETS.fetch(request); }, };