Persist auth session token in secure cookie storage

This commit is contained in:
Jage9
2026-02-28 04:20:40 -05:00
parent 4ff070b2db
commit 9f7d573557
2 changed files with 40 additions and 9 deletions

View File

@@ -1,5 +1,5 @@
// Maintainer-controlled web client version. // Maintainer-controlled web client version.
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2) // 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. // Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
window.CHGRID_TIME_ZONE = "America/Detroit"; window.CHGRID_TIME_ZONE = "America/Detroit";

View File

@@ -12,6 +12,9 @@ const MASTER_VOLUME_STORAGE_KEY = 'chatGridMasterVolume';
const PEER_LISTEN_GAINS_STORAGE_KEY = 'chatGridPeerListenGains'; const PEER_LISTEN_GAINS_STORAGE_KEY = 'chatGridPeerListenGains';
const NICKNAME_STORAGE_KEY = 'spatialChatNickname'; const NICKNAME_STORAGE_KEY = 'spatialChatNickname';
const AUTH_USERNAME_STORAGE_KEY = 'chatGridAuthUsername'; 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 = { type DevicePreference = {
id: string; id: string;
@@ -27,6 +30,33 @@ type AudioDevicePreferences = {
* Wraps localStorage reads/writes for client user settings. * Wraps localStorage reads/writes for client user settings.
*/ */
export class SettingsStore { 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<Record<'reverb' | 'echo' | 'flanger' | 'high_pass' | 'low_pass' | 'off', number>> | null { loadEffectLevels(): Partial<Record<'reverb' | 'echo' | 'flanger' | 'high_pass' | 'low_pass' | 'off', number>> | null {
const raw = localStorage.getItem(EFFECT_LEVELS_STORAGE_KEY); const raw = localStorage.getItem(EFFECT_LEVELS_STORAGE_KEY);
if (!raw) return null; if (!raw) return null;
@@ -115,17 +145,18 @@ export class SettingsStore {
} }
loadAuthSessionToken(): string { loadAuthSessionToken(): string {
// Session tokens are intentionally not persisted in browser storage. // Session token is persisted in cookie storage (not localStorage).
// Remove any legacy stored token and force fresh auth on reload. localStorage.removeItem(LEGACY_AUTH_SESSION_TOKEN_STORAGE_KEY);
localStorage.removeItem('chatGridAuthSessionToken'); return this.readCookie(AUTH_SESSION_COOKIE_NAME);
return '';
} }
saveAuthSessionToken(token: string): void { saveAuthSessionToken(token: string): void {
// Session tokens are intentionally not persisted in browser storage. localStorage.removeItem(LEGACY_AUTH_SESSION_TOKEN_STORAGE_KEY);
// Keep behavior explicit: always clear any legacy token slot. if (token) {
void token; this.writeCookie(AUTH_SESSION_COOKIE_NAME, token, AUTH_SESSION_MAX_AGE_SECONDS);
localStorage.removeItem('chatGridAuthSessionToken'); return;
}
this.clearCookie(AUTH_SESSION_COOKIE_NAME);
} }
loadAuthUsername(): string { loadAuthUsername(): string {