fix(auth): token-based handoff for cross-subdomain SSO

Domain=.sami cookies are silently rejected by real browsers - .sami is an
unregistered custom TLD, so browsers treat sami itself as the effective
public suffix and refuse to set a cookie scoped to it (the same rule that
stops a site from setting a supercookie for all of .com). Confirmed via
curl verbose (cookie dropped, domain must not set cookies for sami) and
via the Firefox console on the actual device (Cookie rejected for invalid
domain) for the same cookie. The session cookie set on status.sami after
TOTP verify could never reach plex.sami/jellyfin.sami/emby.sami/chat.sami
no matter how the cookie itself was built - prior fixes tonight left this
mechanism untouched, which is why the loop persisted.

Fix: /totp/verify mints a short-lived (60s) single-use opaque token. The
status.sami frontend appends it to the redirect URL when bouncing the
user back to a gated service. That services login page exchanges the
token via the new public GET /api/v1/auth/sso-exchange for a host-only
session cookie (no Domain attribute - always accepted). isSessionValid
only checks the cookies HMAC signature, never its Domain, so the host-only
cookie validates identically to the cross-domain one on every existing
check with zero changes to that logic.
This commit is contained in:
Krystie
2026-07-24 05:15:03 -07:00
parent f42e761e52
commit 10f2bf707b
7 changed files with 170 additions and 43 deletions
+13 -1
View File
@@ -91,7 +91,19 @@
const redirect = safeSessionGet('totp_redirect');
if (redirect) {
try { sessionStorage.removeItem('totp_redirect'); } catch (_) {}
window.location.href = redirect;
// .sami is an unregistered TLD, so browsers silently drop the
// Domain=.sami session cookie on any OTHER *.sami subdomain (they
// treat "sami" as the effective public suffix, same protection
// that blocks a Domain=.com supercookie). The target service can't
// see our session cookie no matter how it's built, so instead we
// hand it a one-time token in the URL; its login page exchanges
// that for its own host-only cookie via /auth/sso-exchange.
let target = redirect;
if (data.ssoToken) {
const sep = redirect.includes('?') ? '&' : '?';
target = redirect + sep + 'dc_token=' + encodeURIComponent(data.ssoToken);
}
window.location.href = target;
return;
}
// Initialize dashboard