feat: Enhance favorites management by binding session favorites to authenticated users and updating cache handling

This commit is contained in:
2026-04-17 23:25:02 +02:00
parent 9b7ba39702
commit fd6915a923
2 changed files with 32 additions and 5 deletions
+27 -1
View File
@@ -3733,6 +3733,13 @@ def login():
is_admin_user = bool(user.get('Admin', False)) is_admin_user = bool(user.get('Admin', False))
session['admin'] = is_admin_user session['admin'] = is_admin_user
session['is_admin'] = is_admin_user session['is_admin'] = is_admin_user
# Bind session favorites to the authenticated user to avoid cross-user leakage.
try:
session['favorites_owner'] = username
session['favorites'] = list(dict.fromkeys([str(f) for f in us.get_favorites(username)]))
except Exception:
session['favorites_owner'] = username
session['favorites'] = []
if is_admin_user: if is_admin_user:
permissions = us.get_effective_permissions(username) permissions = us.get_effective_permissions(username)
if _page_access_allowed(permissions, 'home_admin') and _action_access_allowed(permissions, 'can_manage_settings'): if _page_access_allowed(permissions, 'home_admin') and _action_access_allowed(permissions, 'can_manage_settings'):
@@ -3827,6 +3834,8 @@ def logout():
session.pop('username', None) session.pop('username', None)
session.pop('admin', None) session.pop('admin', None)
session.pop('is_admin', None) session.pop('is_admin', None)
session.pop('favorites', None)
session.pop('favorites_owner', None)
return redirect(url_for('login')) return redirect(url_for('login'))
@@ -3835,6 +3844,7 @@ def get_items():
"""Return items plus merged favorites (session + DB) and per-item favorite flag.""" """Return items plus merged favorites (session + DB) and per-item favorite flag."""
client = None client = None
try: try:
_ensure_session_favs()
username = session.get('username') username = session.get('username')
# Merge DB favorites into session if logged in # Merge DB favorites into session if logged in
if username: if username:
@@ -4081,8 +4091,23 @@ def api_booking_conflicts():
"""Favorites management endpoints (persistent + session cache).""" """Favorites management endpoints (persistent + session cache)."""
def _ensure_session_favs(): def _ensure_session_favs():
if 'favorites' not in session: username = session.get('username')
owner = session.get('favorites_owner')
if not username:
if 'favorites' not in session or not isinstance(session.get('favorites'), list):
session['favorites'] = []
return
if owner != username:
session['favorites_owner'] = username
session['favorites'] = [] session['favorites'] = []
session.modified = True
return
if 'favorites' not in session or not isinstance(session.get('favorites'), list):
session['favorites'] = []
session.modified = True
@app.route('/favorites', methods=['GET']) @app.route('/favorites', methods=['GET'])
def list_favorites(): def list_favorites():
@@ -4169,6 +4194,7 @@ def toggle_fav(item_id):
@app.route('/debug/favorites') @app.route('/debug/favorites')
def debug_favorites(): def debug_favorites():
"""Diagnostic endpoint: shows session favorites, DB favorites and merged output.""" """Diagnostic endpoint: shows session favorites, DB favorites and merged output."""
_ensure_session_favs()
username = session.get('username') username = session.get('username')
session_favs = list(session.get('favorites', [])) session_favs = list(session.get('favorites', []))
db_favs = [] db_favs = []
+5 -4
View File
@@ -843,7 +843,7 @@
const favoriteIds = new Set(data.favorites || []); const favoriteIds = new Set(data.favorites || []);
window.currentFavorites = favoriteIds; window.currentFavorites = favoriteIds;
try { sessionStorage.setItem('favoritesCache', JSON.stringify(Array.from(favoriteIds))); } catch(e){} try { sessionStorage.setItem('favoritesCache:' + ({{ session.get('username', '') | tojson }} || 'anon'), JSON.stringify(Array.from(favoriteIds))); } catch(e){}
pageItems.forEach(item => { pageItems.forEach(item => {
try { try {
const card = document.createElement('div'); const card = document.createElement('div');
@@ -4233,6 +4233,7 @@
<script> <script>
let favoritesOnly = false; let favoritesOnly = false;
let tableViewMode = false; let tableViewMode = false;
const favoritesCacheKey = 'favoritesCache:' + ({{ session.get('username', '') | tojson }} || 'anon');
function setViewModeState() { function setViewModeState() {
document.body.classList.toggle('table-view', tableViewMode); document.body.classList.toggle('table-view', tableViewMode);
@@ -4263,7 +4264,7 @@ function toggleFavorite(id, btn, card){
} }
if(!window.currentFavorites) window.currentFavorites = new Set(); if(!window.currentFavorites) window.currentFavorites = new Set();
if(isFav) window.currentFavorites.add(id); else window.currentFavorites.delete(id); if(isFav) window.currentFavorites.add(id); else window.currentFavorites.delete(id);
try { sessionStorage.setItem('favoritesCache', JSON.stringify(Array.from(window.currentFavorites))); } catch(e){} try { sessionStorage.setItem(favoritesCacheKey, JSON.stringify(Array.from(window.currentFavorites))); } catch(e){}
}) })
.catch(err=>console.error('Netzwerkfehler Favoriten', err)); .catch(err=>console.error('Netzwerkfehler Favoriten', err));
} }
@@ -4271,7 +4272,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
// Initialize favorites cache set if stored // Initialize favorites cache set if stored
try { try {
if(!window.currentFavorites){ if(!window.currentFavorites){
const cached = sessionStorage.getItem('favoritesCache'); const cached = sessionStorage.getItem(favoritesCacheKey);
if(cached){ window.currentFavorites = new Set(JSON.parse(cached)); } if(cached){ window.currentFavorites = new Set(JSON.parse(cached)); }
} }
} catch(e){} } catch(e){}
@@ -4310,7 +4311,7 @@ function openItemQuick(id){
if(item && !item.error){ if(item && !item.error){
// ensure favorites set available // ensure favorites set available
if(!window.currentFavorites){ if(!window.currentFavorites){
window.currentFavorites = new Set(JSON.parse(sessionStorage.getItem('favoritesCache')||'[]')); window.currentFavorites = new Set(JSON.parse(sessionStorage.getItem(favoritesCacheKey)||'[]'));
} }
openItemModal(item); openItemModal(item);
} }