From 3655632e0f48d2aa970db497876b735fcbf81db6 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Tue, 24 Mar 2026 14:24:30 -0700 Subject: [PATCH] fix(auth): check cross-subdomain cookie in access gate and dashboard redirects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The access gate and space dashboard redirect scripts checked only localStorage, which is per-origin. When navigating between subdomains (e.g. demo → jeff), the session wasn't found. Now both scripts also check the eid_token cross-subdomain cookie and sync it to localStorage. Co-Authored-By: Claude Opus 4.6 --- server/landing.ts | 45 ++++++++++++++++++++++++++++++--------------- server/shell.ts | 22 ++++++++++++++++++++++ 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/server/landing.ts b/server/landing.ts index 9b1401f..e133fd0 100644 --- a/server/landing.ts +++ b/server/landing.ts @@ -339,24 +339,39 @@ export function renderSpaceDashboard(space: string, modules: ModuleInfo[]): stri import '/shell.js'; document.querySelector('rstack-app-switcher')?.setModules(${moduleListJSON}); + // Check session in localStorage OR cross-subdomain cookie + function _hasSession() { + try { + var raw = localStorage.getItem('encryptid_session'); + if (raw && JSON.parse(raw)?.accessToken) return true; + } catch(e) {} + try { + var m = document.cookie.match(/(?:^|; )eid_token=([^;]*)/); + if (!m) return false; + var tok = decodeURIComponent(m[1]); + var parts = tok.split('.'); + if (parts.length < 2) return false; + var b64 = parts[1].replace(/-/g,'+').replace(/_/g,'/'); + var pad = '='.repeat((4 - b64.length % 4) % 4); + var payload = JSON.parse(atob(b64 + pad)); + return payload.exp && Math.floor(Date.now()/1000) < payload.exp; + } catch(e) { return false; } + } + // Logged-in users: redirect to rSpace canvas instead of showing the grid // Non-demo spaces: redirect logged-out visitors to the main domain landing - try { - var raw = localStorage.getItem('encryptid_session'); - var loggedIn = raw && JSON.parse(raw)?.accessToken; - if (loggedIn) { - var dest = window.__rspaceNavUrl - ? window.__rspaceNavUrl('${escapeAttr(space)}', 'rspace') - : '/${escapeAttr(space)}/rspace'; - window.location.replace(dest); - } else if ('${escapeAttr(space)}' !== 'demo') { - // Don't show other users' space dashboards to logged-out visitors - var host = window.location.host.split(':')[0]; - if (host.endsWith('.rspace.online') || host === 'rspace.online') { - window.location.replace('https://rspace.online/'); - } + var loggedIn = _hasSession(); + if (loggedIn) { + var dest = window.__rspaceNavUrl + ? window.__rspaceNavUrl('${escapeAttr(space)}', 'rspace') + : '/${escapeAttr(space)}/rspace'; + window.location.replace(dest); + } else if ('${escapeAttr(space)}' !== 'demo') { + var host = window.location.host.split(':')[0]; + if (host.endsWith('.rspace.online') || host === 'rspace.online') { + window.location.replace('https://rspace.online/'); } - } catch(e) {} + } // Fix up dashboard links to be subdomain-aware if (window.__rspaceNavUrl) { diff --git a/server/shell.ts b/server/shell.ts index 3892309..9201888 100644 --- a/server/shell.ts +++ b/server/shell.ts @@ -627,6 +627,28 @@ export function renderShell(opts: ShellOptions): string { if (raw) session = JSON.parse(raw); } catch(e) {} + // Also check cross-subdomain cookie (localStorage is per-origin) + if (!session || !session.accessToken) { + try { + var m = document.cookie.match(/(?:^|; )eid_token=([^;]*)/); + if (m) { + var tok = decodeURIComponent(m[1]); + var parts = tok.split('.'); + if (parts.length >= 2) { + var b64 = parts[1].replace(/-/g,'+').replace(/_/g,'/'); + var pad = '='.repeat((4 - b64.length % 4) % 4); + var payload = JSON.parse(atob(b64 + pad)); + if (payload.exp && Math.floor(Date.now()/1000) < payload.exp) { + session = { accessToken: tok, claims: payload }; + // Sync to localStorage so downstream code sees it + localStorage.setItem('encryptid_session', JSON.stringify(session)); + if (payload.username) localStorage.setItem('rspace-username', payload.username); + } + } + } + } catch(e) {} + } + var hasToken = session && session.accessToken; // Permissioned spaces: only need a valid session