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:
@@ -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
@@ -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
@@ -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
@@ -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',
|
||||||
|
|||||||
Reference in New Issue
Block a user