fix(#106): [SECURITY] scope recent files and favorites per user

Root cause: localStorage keys 'oxicloud_recent_files' and
'oxicloud_favorites' were global — shared across all users on the same
browser. When user A logged out and user B logged in, user B could see
(and access) user A's recent files and favorites.

Fixes applied:

recent.js:
- Storage key now user-specific: 'oxicloud_recent_files_{username}'
- getStorageKey() derives key from current user in localStorage
- migrateFromLegacyKey() moves data from old global key on init
- Legacy global key is always removed after migration

favorites.js:
- Same pattern: 'oxicloud_favorites_{username}'
- getStorageKey() + migrateFromLegacyKey() added

auth.js (logout):
- Clears user-specific recent and favorites keys before removing
  user data, plus removes any legacy global keys

Bumps service worker cache to v13.
This commit is contained in:
Dionisio
2026-02-14 12:50:44 +01:00
parent ebb0aee84e
commit f25987e553
4 changed files with 105 additions and 13 deletions
+14
View File
@@ -1211,6 +1211,20 @@ function redirectToMainApp() {
* Logout - clear tokens and redirect to login * Logout - clear tokens and redirect to login
*/ */
function logout() { function logout() {
// Clear user-specific recent files and favorites before removing user data
try {
const userData = JSON.parse(localStorage.getItem(USER_DATA_KEY) || '{}');
if (userData.username) {
localStorage.removeItem(`oxicloud_recent_files_${userData.username}`);
localStorage.removeItem(`oxicloud_favorites_${userData.username}`);
}
} catch (e) {
// Ignore parse errors during cleanup
}
// Also remove any legacy global keys
localStorage.removeItem('oxicloud_recent_files');
localStorage.removeItem('oxicloud_favorites');
localStorage.removeItem(TOKEN_KEY); localStorage.removeItem(TOKEN_KEY);
localStorage.removeItem(REFRESH_TOKEN_KEY); localStorage.removeItem(REFRESH_TOKEN_KEY);
localStorage.removeItem(TOKEN_EXPIRY_KEY); localStorage.removeItem(TOKEN_EXPIRY_KEY);
+40 -4
View File
@@ -5,23 +5,59 @@
// Favorites Module // Favorites Module
const favorites = { const favorites = {
// Key for storing favorites in localStorage // Base key for storing favorites in localStorage (username is appended)
STORAGE_KEY: 'oxicloud_favorites', STORAGE_KEY_PREFIX: 'oxicloud_favorites',
// Legacy key (pre-fix, shared across all users)
LEGACY_STORAGE_KEY: 'oxicloud_favorites',
// Flag to indicate if backend API is available // Flag to indicate if backend API is available
backendApiAvailable: false, backendApiAvailable: false,
/**
* Get the user-specific storage key for favorites.
* Falls back to legacy global key if username is unavailable.
* @returns {string} localStorage key scoped to the current user
*/
getStorageKey() {
try {
const userData = JSON.parse(localStorage.getItem('oxicloud_user') || '{}');
if (userData.username) {
return `${this.STORAGE_KEY_PREFIX}_${userData.username}`;
}
} catch (e) {
console.warn('Could not determine current user for favorites key');
}
return this.LEGACY_STORAGE_KEY;
},
/** /**
* Initialize favorites module * Initialize favorites module
*/ */
init() { init() {
console.log('Initializing favorites module'); console.log('Initializing favorites module');
this.migrateFromLegacyKey();
this.loadFavorites(); this.loadFavorites();
// Check if backend favorites API is available // Check if backend favorites API is available
this.checkBackendAvailability(); this.checkBackendAvailability();
}, },
/**
* Migrate data from the old global key to the user-specific key.
*/
migrateFromLegacyKey() {
const userKey = this.getStorageKey();
if (userKey === this.LEGACY_STORAGE_KEY) return;
const legacyData = localStorage.getItem(this.LEGACY_STORAGE_KEY);
if (legacyData && !localStorage.getItem(userKey)) {
console.log('Migrating favorites from legacy global key to user-specific key');
localStorage.setItem(userKey, legacyData);
}
localStorage.removeItem(this.LEGACY_STORAGE_KEY);
},
/** /**
* Check if backend favorites API is available * Check if backend favorites API is available
*/ */
@@ -122,7 +158,7 @@ const favorites = {
*/ */
loadFavorites() { loadFavorites() {
try { try {
const stored = localStorage.getItem(this.STORAGE_KEY); const stored = localStorage.getItem(this.getStorageKey());
return stored ? JSON.parse(stored) : []; return stored ? JSON.parse(stored) : [];
} catch (error) { } catch (error) {
console.error('Error loading favorites:', error); console.error('Error loading favorites:', error);
@@ -136,7 +172,7 @@ const favorites = {
*/ */
saveFavorites(favorites) { saveFavorites(favorites) {
try { try {
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(favorites)); localStorage.setItem(this.getStorageKey(), JSON.stringify(favorites));
} catch (error) { } catch (error) {
console.error('Error saving favorites:', error); console.error('Error saving favorites:', error);
} }
+50 -8
View File
@@ -5,27 +5,69 @@
// Recent Files Module // Recent Files Module
const recent = { const recent = {
// Key for storing recent files in localStorage // Base key for storing recent files in localStorage (username is appended)
STORAGE_KEY: 'oxicloud_recent_files', STORAGE_KEY_PREFIX: 'oxicloud_recent_files',
// Legacy key (pre-fix, shared across all users)
LEGACY_STORAGE_KEY: 'oxicloud_recent_files',
// Maximum number of recent files to store // Maximum number of recent files to store
MAX_RECENT_FILES: 20, MAX_RECENT_FILES: 20,
/**
* Get the user-specific storage key for recent files.
* Falls back to legacy global key if username is unavailable.
* @returns {string} localStorage key scoped to the current user
*/
getStorageKey() {
try {
const userData = JSON.parse(localStorage.getItem('oxicloud_user') || '{}');
if (userData.username) {
return `${this.STORAGE_KEY_PREFIX}_${userData.username}`;
}
} catch (e) {
console.warn('Could not determine current user for recent files key');
}
// Should not happen in normal flow — user must be logged in
return this.LEGACY_STORAGE_KEY;
},
/** /**
* Initialize recent files module * Initialize recent files module
*/ */
init() { init() {
console.log('Initializing recent files module'); console.log('Initializing recent files module');
this.migrateFromLegacyKey();
this.ensureRecentFilesStorage(); this.ensureRecentFilesStorage();
this.setupEventListeners(); this.setupEventListeners();
}, },
/**
* Migrate data from the old global key to the user-specific key.
* This runs once: if the legacy key has data and the user-specific key
* does not yet exist, the data is moved.
*/
migrateFromLegacyKey() {
const userKey = this.getStorageKey();
// Only migrate if the key is actually user-specific
if (userKey === this.LEGACY_STORAGE_KEY) return;
const legacyData = localStorage.getItem(this.LEGACY_STORAGE_KEY);
if (legacyData && !localStorage.getItem(userKey)) {
console.log('Migrating recent files from legacy global key to user-specific key');
localStorage.setItem(userKey, legacyData);
}
// Always remove the legacy key so other users don't see stale data
localStorage.removeItem(this.LEGACY_STORAGE_KEY);
},
/** /**
* Make sure the recent files storage is initialized * Make sure the recent files storage is initialized
*/ */
ensureRecentFilesStorage() { ensureRecentFilesStorage() {
if (!localStorage.getItem(this.STORAGE_KEY)) { const key = this.getStorageKey();
localStorage.setItem(this.STORAGE_KEY, JSON.stringify([])); if (!localStorage.getItem(key)) {
localStorage.setItem(key, JSON.stringify([]));
} }
}, },
@@ -71,8 +113,8 @@ const recent = {
// Keep only the most recent files (limit to MAX_RECENT_FILES) // Keep only the most recent files (limit to MAX_RECENT_FILES)
const trimmedFiles = recentFiles.slice(0, this.MAX_RECENT_FILES); const trimmedFiles = recentFiles.slice(0, this.MAX_RECENT_FILES);
// Save back to localStorage // Save back to localStorage (user-scoped key)
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(trimmedFiles)); localStorage.setItem(this.getStorageKey(), JSON.stringify(trimmedFiles));
}, },
/** /**
@@ -81,7 +123,7 @@ const recent = {
*/ */
getRecentFiles() { getRecentFiles() {
try { try {
const recentFilesJson = localStorage.getItem(this.STORAGE_KEY); const recentFilesJson = localStorage.getItem(this.getStorageKey());
return recentFilesJson ? JSON.parse(recentFilesJson) : []; return recentFilesJson ? JSON.parse(recentFilesJson) : [];
} catch (error) { } catch (error) {
console.error('Error loading recent files:', error); console.error('Error loading recent files:', error);
@@ -93,7 +135,7 @@ const recent = {
* Clear all recent files * Clear all recent files
*/ */
clearRecentFiles() { clearRecentFiles() {
localStorage.setItem(this.STORAGE_KEY, JSON.stringify([])); localStorage.setItem(this.getStorageKey(), JSON.stringify([]));
}, },
/** /**
+1 -1
View File
@@ -1,5 +1,5 @@
// OxiCloud Service Worker // OxiCloud Service Worker
const CACHE_NAME = 'oxicloud-cache-v12'; const CACHE_NAME = 'oxicloud-cache-v13';
const ASSETS_TO_CACHE = [ const ASSETS_TO_CACHE = [
'/', '/',
'/index.html', '/index.html',