From 9f7d573557c1242763233550a6c7e07b53275983 Mon Sep 17 00:00:00 2001 From: Jage9 Date: Sat, 28 Feb 2026 04:20:40 -0500 Subject: [PATCH] Persist auth session token in secure cookie storage --- client/public/version.js | 2 +- client/src/settings/settingsStore.ts | 47 +++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/client/public/version.js b/client/public/version.js index 7f69ba6..edbf503 100644 --- a/client/public/version.js +++ b/client/public/version.js @@ -1,5 +1,5 @@ // Maintainer-controlled web client version. // Format: YYYY.MM.DD Rn (example: 2026.02.20 R2) -window.CHGRID_WEB_VERSION = "2026.02.28 R314"; +window.CHGRID_WEB_VERSION = "2026.02.28 R315"; // Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid. window.CHGRID_TIME_ZONE = "America/Detroit"; diff --git a/client/src/settings/settingsStore.ts b/client/src/settings/settingsStore.ts index d6ada21..6dc299b 100644 --- a/client/src/settings/settingsStore.ts +++ b/client/src/settings/settingsStore.ts @@ -12,6 +12,9 @@ const MASTER_VOLUME_STORAGE_KEY = 'chatGridMasterVolume'; const PEER_LISTEN_GAINS_STORAGE_KEY = 'chatGridPeerListenGains'; const NICKNAME_STORAGE_KEY = 'spatialChatNickname'; const AUTH_USERNAME_STORAGE_KEY = 'chatGridAuthUsername'; +const LEGACY_AUTH_SESSION_TOKEN_STORAGE_KEY = 'chatGridAuthSessionToken'; +const AUTH_SESSION_COOKIE_NAME = 'chgrid_session_token'; +const AUTH_SESSION_MAX_AGE_SECONDS = 14 * 24 * 60 * 60; type DevicePreference = { id: string; @@ -27,6 +30,33 @@ type AudioDevicePreferences = { * Wraps localStorage reads/writes for client user settings. */ export class SettingsStore { + private readCookie(name: string): string { + const cookie = document.cookie || ''; + const parts = cookie.split(';'); + for (const part of parts) { + const [rawKey, ...rest] = part.trim().split('='); + if (rawKey !== name) continue; + const rawValue = rest.join('='); + try { + return decodeURIComponent(rawValue); + } catch { + return rawValue; + } + } + return ''; + } + + private writeCookie(name: string, value: string, maxAgeSeconds: number): void { + const encoded = encodeURIComponent(value); + const secure = window.location.protocol === 'https:' ? '; Secure' : ''; + document.cookie = `${name}=${encoded}; Path=/; Max-Age=${Math.max(0, Math.floor(maxAgeSeconds))}; SameSite=Lax${secure}`; + } + + private clearCookie(name: string): void { + const secure = window.location.protocol === 'https:' ? '; Secure' : ''; + document.cookie = `${name}=; Path=/; Max-Age=0; SameSite=Lax${secure}`; + } + loadEffectLevels(): Partial> | null { const raw = localStorage.getItem(EFFECT_LEVELS_STORAGE_KEY); if (!raw) return null; @@ -115,17 +145,18 @@ export class SettingsStore { } loadAuthSessionToken(): string { - // Session tokens are intentionally not persisted in browser storage. - // Remove any legacy stored token and force fresh auth on reload. - localStorage.removeItem('chatGridAuthSessionToken'); - return ''; + // Session token is persisted in cookie storage (not localStorage). + localStorage.removeItem(LEGACY_AUTH_SESSION_TOKEN_STORAGE_KEY); + return this.readCookie(AUTH_SESSION_COOKIE_NAME); } saveAuthSessionToken(token: string): void { - // Session tokens are intentionally not persisted in browser storage. - // Keep behavior explicit: always clear any legacy token slot. - void token; - localStorage.removeItem('chatGridAuthSessionToken'); + localStorage.removeItem(LEGACY_AUTH_SESSION_TOKEN_STORAGE_KEY); + if (token) { + this.writeCookie(AUTH_SESSION_COOKIE_NAME, token, AUTH_SESSION_MAX_AGE_SECONDS); + return; + } + this.clearCookie(AUTH_SESSION_COOKIE_NAME); } loadAuthUsername(): string {