From 267ed4b17b219554093c64cd5fc163e124fe7af7 Mon Sep 17 00:00:00 2001 From: Tyler Gillispie Date: Fri, 28 Aug 2026 00:01:08 -0700 Subject: [PATCH] fix(sso): enforce root wildcard cookie domain (.atyg.org) across all auth, join, and pass routes --- src/core/session.ts | 29 +++++++++++++++++++--------- src/features/auth/login_routes.ts | 18 +---------------- src/features/auth/register_routes.ts | 17 +--------------- 3 files changed, 22 insertions(+), 42 deletions(-) diff --git a/src/core/session.ts b/src/core/session.ts index e3f1821..2032367 100644 --- a/src/core/session.ts +++ b/src/core/session.ts @@ -201,17 +201,28 @@ export async function isSessionAdmin( } /** - * Computes root cookie domain from RP_ID or host. + * Computes root wildcard cookie domain from RP_ID, COOKIE_DOMAIN, or host. + * E.g., "auth.atyg.org" -> ".atyg.org" (allowing SSO across *.atyg.org). */ -export function getCookieDomain(customRpId?: string): string { - const rpId = customRpId || Deno.env.get("RP_ID"); - if (rpId) { - if (rpId.includes("localhost") || rpId.includes("127.0.0.1")) { - return rpId; - } - return `.${rpId}`; +export function getCookieDomain(customRpId?: string): string | undefined { + const envDomain = Deno.env.get("COOKIE_DOMAIN"); + if (envDomain) { + return envDomain.startsWith(".") ? envDomain : `.${envDomain}`; } - return ""; + const targetId = customRpId || Deno.env.get("RP_ID") || ""; + if ( + !targetId || + !targetId.includes(".") || + targetId.includes("localhost") || + targetId.includes("127.0.0.1") + ) { + return undefined; + } + const parts = targetId.split(".").filter(Boolean); + if (parts.length >= 2) { + return `.${parts.slice(-2).join(".")}`; + } + return `.${targetId}`; } /** diff --git a/src/features/auth/login_routes.ts b/src/features/auth/login_routes.ts index e5d9f49..e38333f 100644 --- a/src/features/auth/login_routes.ts +++ b/src/features/auth/login_routes.ts @@ -9,7 +9,7 @@ import type { AuthenticationResponseJSON } from "jsr:@simplewebauthn/server@13"; import { valkey } from "../../core/valkey.ts"; import { getClientIp, publicRateLimiter } from "../../core/middleware.ts"; -import { extractAllSessionIds } from "../../core/session.ts"; +import { extractAllSessionIds, getCookieDomain } from "../../core/session.ts"; import { auditWrapper } from "../../core/audit.ts"; import { @@ -30,22 +30,6 @@ const rpID = Deno.env.get("RP_ID") || const origin = Deno.env.get("ORIGIN") || (import.meta.main ? undefined : "http://localhost"); -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}`; -} - // UI Route loginRoutes.get("/login", (c) => { return c.html(LoginPageFragment()); diff --git a/src/features/auth/register_routes.ts b/src/features/auth/register_routes.ts index 032ba37..75cf5ef 100644 --- a/src/features/auth/register_routes.ts +++ b/src/features/auth/register_routes.ts @@ -10,6 +10,7 @@ import type { RegistrationResponseJSON } from "jsr:@simplewebauthn/server@13"; import { valkey } from "../../core/valkey.ts"; import { getClientIp, publicRateLimiter } from "../../core/middleware.ts"; import { auditWrapper } from "../../core/audit.ts"; +import { getCookieDomain } from "../../core/session.ts"; import { createPasskey, @@ -28,22 +29,6 @@ const rpID = Deno.env.get("RP_ID") || const origin = Deno.env.get("ORIGIN") || (import.meta.main ? undefined : "http://localhost"); -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}`; -} - // UI Route registerRoutes.get("/register", (c) => { const code = c.req.query("code") || "";