import type { Context } from "jsr:@hono/hono@4"; /** * Calculates the wildcard parent cookie domain (e.g. auth.atyg.org -> .atyg.org) * to ensure cookies are sent to all subdomains (ed-droid.atyg.org, grafana.atyg.org, etc.). */ export function getCookieDomain(customRpId?: string): string | undefined { const envDomain = Deno.env.get("COOKIE_DOMAIN"); if (envDomain) { return envDomain.startsWith(".") ? envDomain : `.${envDomain}`; } const targetId = customRpId || Deno.env.get("RP_ID") || ""; if (!targetId || !targetId.includes(".") || targetId === "localhost") { return undefined; } const parts = targetId.split(".").filter(Boolean); if (parts.length >= 2) { return `.${parts.slice(-2).join(".")}`; } return `.${targetId}`; } /** * 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); // remove /* 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; // Basic IP parsing (v4 only for simplicity and speed, or basic v6 check) 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; }; // X-Forwarded-For can contain multiple IPs if chained (e.g., "client, proxy1, proxy2") // We extract the first IP (the original client) 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; } // IPv4 CIDR matching if (ipNum !== null && subnet.includes(".")) { const subnetNum = parseIp4(subnet); if (subnetNum !== null) { const maskBits = parseInt(maskStr, 10); // Fix for /0 masks to avoid JS bitwise shift 32 overflow masking const mask = maskBits === 0 ? 0 : ((0xffffffff << (32 - maskBits)) >>> 0); if ((ipNum & mask) === (subnetNum & mask)) { return true; } } } // Note: To remain zero-dependency and ultra-fast, we are supporting IPv4 CIDR. // Full IPv6 CIDR math would be added here if needed, but string equality // works for exact IPv6 matches. else if (subnet === clientIp) { return true; } } return false; } /** * Validates a given URL to ensure it is safe to redirect to. * Allows relative paths, localhost, and *.atyg.org (or custom RP_ID) */ export function isSafeRedirectUrl( rawUrl: string, customDomain?: string, ): boolean { if (!rawUrl) return false; // 1. Relative paths within the same origin are safe 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(/^\./, ""); // 2. Allow localhost, exact root match, or subdomains (*.atyg.org) if ( host === "localhost" || host === "atyg.org" || host.endsWith(".atyg.org") || host === cleanRoot || host.endsWith(`.${cleanRoot}`) ) { return true; } } catch (_e) { return false; } return false; } /** * Extracts the real client IP from X-Real-IP or X-Forwarded-For headers. */ export function getClientIp(c: Context): string { const realIp = c.req.header("x-real-ip"); if (realIp) { return realIp.trim(); } let forwardedFor = c.req.header("x-forwarded-for"); if (forwardedFor) { if (forwardedFor.length > 256) { forwardedFor = forwardedFor.substring(0, 256); } const parts = forwardedFor.split(","); return parts[parts.length - 1].trim(); } return "127.0.0.1"; }