From a407a108ed5d896967a35c1486a66a29979260ea Mon Sep 17 00:00:00 2001 From: Rene Luria Date: Tue, 19 Aug 2025 15:17:45 +0200 Subject: [PATCH] fix: improve user authentication and account handling Fixed issues with user display by fetching user info from userinfo endpoint Improved error handling for JSON responses in schedule endpoint Fixed account selection to use available accounts from config instead of default Enhanced frontend to properly handle API responses and errors --- index.html | 151 +++++++++++++++++++++++++++++++++++------------- myice/myice.py | 11 +++- myice/webapi.py | 8 ++- 3 files changed, 128 insertions(+), 42 deletions(-) diff --git a/index.html b/index.html index 8449f5f..8889a1c 100644 --- a/index.html +++ b/index.html @@ -74,6 +74,27 @@ let storedAccount = localStorage.getItem("account") || "default"; let userInfo = JSON.parse(localStorage.getItem("userInfo") || "null"); + // If we have an API key but no userInfo, fetch it from the server + if (storedApiKey && !userInfo) { + fetch(`${apiBaseUrl}/userinfo`, { + headers: { "Authorization": `Bearer ${storedApiKey}` } + }) + .then(response => response.json()) + .then(userData => { + userInfo = {email: userData.email || "Utilisateur connecté"}; + localStorage.setItem("userInfo", JSON.stringify(userInfo)); + renderLoginSection(); + }) + .catch(e => { + console.error("Error fetching user info:", e); + // If we can't fetch user info, proceed with rendering + renderLoginSection(); + }); + } else { + // Render the login section normally + renderLoginSection(); + } + // Handle the static "abc" key case - removed for security /* if (storedApiKey === "abc" && !userInfo) { @@ -101,41 +122,56 @@ localStorage.setItem("apikey", accessToken); storedApiKey = accessToken; - // Get user info from the token - try { - // First, try to parse as JWT - const tokenParts = accessToken.split('.'); - let userData = {}; - - if (tokenParts.length === 3) { - // Standard JWT format - const payload = tokenParts[1]; - if (payload) { - // Add padding if needed - const paddedPayload = payload.padEnd(payload.length + (4 - payload.length % 4) % 4, '='); - const decodedPayload = atob(paddedPayload); - userData = JSON.parse(decodedPayload); - } - } else { - // Non-JWT token, treat as opaque token - console.log("Non-JWT token received, using default user info"); - userData = {email: "utilisateur@" + window.location.hostname}; - } - + // Get user info from the userinfo endpoint + fetch(`${apiBaseUrl}/userinfo`, { + headers: { "Authorization": `Bearer ${accessToken}` } + }) + .then(response => response.json()) + .then(userData => { userInfo = {email: userData.email || "Utilisateur connecté"}; localStorage.setItem("userInfo", JSON.stringify(userInfo)); - } catch (e) { - console.error("Error decoding token:", e); - // Fallback to a generic user object - userInfo = {email: "Utilisateur connecté"}; - localStorage.setItem("userInfo", JSON.stringify(userInfo)); - } + + // Update UI + renderLoginSection(); + }) + .catch(e => { + console.error("Error fetching user info:", e); + // Fallback to parsing token as JWT + try { + // First, try to parse as JWT + const tokenParts = accessToken.split('.'); + let userData = {}; + + if (tokenParts.length === 3) { + // Standard JWT format + const payload = tokenParts[1]; + if (payload) { + // Add padding if needed + const paddedPayload = payload.padEnd(payload.length + (4 - payload.length % 4) % 4, '='); + const decodedPayload = atob(paddedPayload); + userData = JSON.parse(decodedPayload); + } + } else { + // Non-JWT token, treat as opaque token + console.log("Non-JWT token received, using default user info"); + userData = {email: "utilisateur@" + window.location.hostname}; + } + + userInfo = {email: userData.email || "Utilisateur connecté"}; + localStorage.setItem("userInfo", JSON.stringify(userInfo)); + } catch (parseError) { + console.error("Error decoding token:", parseError); + // Fallback to a generic user object + userInfo = {email: "Utilisateur connecté"}; + localStorage.setItem("userInfo", JSON.stringify(userInfo)); + } + + // Update UI + renderLoginSection(); + }); // Remove token from URL window.history.replaceState({}, document.title, "/"); - - // Update UI - renderLoginSection(); } function renderLoginSection() { @@ -153,9 +189,8 @@ eventFilters.style.display = "block"; updateAccountOptions(); - if (storedApiKey) { - fetchEvents(storedApiKey, storedAccount); - } + // Don't automatically fetch events on page load + // Wait for user to explicitly select an account or click fetch } else { // User is not logged in connectedUser.style.display = "none"; @@ -216,15 +251,35 @@ } accountSelect.innerHTML = ''; + + // If no accounts are available, add a default option + if (accounts.length === 0) { + const option = document.createElement("option"); + option.value = "default"; + option.textContent = "Default"; + accountSelect.appendChild(option); + return; + } + + // Add all available accounts accounts.forEach(account => { const option = document.createElement("option"); option.value = account.name; option.textContent = account.label; - if (account.name === storedAccount) { - option.selected = true; - } accountSelect.appendChild(option); }); + + // Select the stored account if it exists, otherwise select the first account + let accountToSelect = storedAccount; + if (!accounts.some(account => account.name === storedAccount)) { + accountToSelect = accounts[0].name; + // Update stored account + storedAccount = accountToSelect; + localStorage.setItem("account", accountToSelect); + } + + // Set the selected account in the dropdown + accountSelect.value = accountToSelect; }) .catch(error => { console.error("Erreur lors du chargement des comptes:", error); @@ -268,12 +323,28 @@ return; } if (!response.ok) { - return response.json().then(errorData => { - console.error("Schedule error response:", errorData); - throw new Error(`HTTP error! status: ${response.status}, message: ${errorData.detail || 'Unknown error'}`); + // Try to parse error response as JSON, but handle plain text as well + return response.text().then(errorText => { + let errorMessage = 'Unknown error'; + try { + const errorData = JSON.parse(errorText); + errorMessage = errorData.detail || errorData.message || errorText; + } catch (e) { + // If parsing fails, use the raw text + errorMessage = errorText || 'Unknown error'; + } + console.error("Schedule error response:", errorText); + throw new Error(`HTTP error! status: ${response.status}, message: ${errorMessage}`); }); } - return response.json(); + return response.text().then(text => { + try { + return JSON.parse(text); + } catch (e) { + console.error("Invalid JSON response:", text); + throw new Error("Le serveur a renvoyé une réponse invalide. Veuillez réessayer."); + } + }); }) .then(data => { if (data) { diff --git a/myice/myice.py b/myice/myice.py index 4691b58..c838220 100755 --- a/myice/myice.py +++ b/myice/myice.py @@ -635,7 +635,16 @@ def refresh_data(): return r.json() except json.JSONDecodeError: # If direct parsing fails, try with sanitization - return json.loads(sanitize_json_response(r.text)) + sanitized = sanitize_json_response(r.text) + try: + return json.loads(sanitized) + except json.JSONDecodeError: + # If sanitization also fails, log the raw response for debugging + print( + f"Failed to parse response as JSON. Raw response: {r.text[:500]}..." + ) + # Return an empty dict to avoid breaking the API + return {} @app.command("mobile-login") diff --git a/myice/webapi.py b/myice/webapi.py index 5dfa24d..350de8e 100644 --- a/myice/webapi.py +++ b/myice/webapi.py @@ -276,7 +276,10 @@ async def schedule( config_section=account ) except Exception as e: - raise HTTPException(400, detail=f"Configuration error: {str(e)}") + raise HTTPException( + 400, + detail=f"Configuration error: {str(e)}. Available accounts: isaac, leonard", + ) try: if existing_token: @@ -296,6 +299,9 @@ async def schedule( return [] except Exception as e: print(f"Error fetching schedule: {e}") + import traceback + + traceback.print_exc() # Return empty array instead of throwing an error return []