From 33ed3bd66ceaf38ac08ec4ab508e4fe0007c97c9 Mon Sep 17 00:00:00 2001 From: Dionisio Date: Fri, 13 Feb 2026 21:33:45 +0100 Subject: [PATCH] Fix: Complete OIDC login flow - exchange code for tokens on frontend The backend redirected to /?oidc_code= after successful OIDC auth, but the frontend never exchanged this code for JWT tokens. The user was redirected back to the login page every time. - app.js: Detect oidc_code in URL params before token check, call POST /api/auth/oidc/exchange, store tokens, reload clean - auth.js: Fallback handler if oidc_code lands on login page Fixes #90 --- static/js/app.js | 74 +++++++++++++++++++++++++++++++++++++++++++++++ static/js/auth.js | 56 +++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) diff --git a/static/js/app.js b/static/js/app.js index acfe5b8c..137e073c 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -1597,12 +1597,86 @@ function checkAuthentication() { try { // Simplified authentication check - just verify token exists const TOKEN_KEY = 'oxicloud_token'; + const REFRESH_TOKEN_KEY = 'oxicloud_refresh_token'; + const TOKEN_EXPIRY_KEY = 'oxicloud_token_expiry'; const USER_DATA_KEY = 'oxicloud_user'; // Reset counters to prevent loops sessionStorage.removeItem('redirect_count'); localStorage.setItem('refresh_attempts', '0'); + // --- OIDC exchange code handling --- + // After OIDC login, the backend redirects here with ?oidc_code=... + const urlParams = new URLSearchParams(window.location.search); + const oidcCode = urlParams.get('oidc_code'); + + if (oidcCode) { + console.log('OIDC exchange code detected, exchanging for tokens...'); + try { + const exchangeResponse = await fetch('/api/auth/oidc/exchange', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ code: oidcCode }) + }); + + if (!exchangeResponse.ok) { + const errText = await exchangeResponse.text(); + console.error('OIDC token exchange failed:', exchangeResponse.status, errText); + window.location.href = '/login.html?source=oidc_error'; + return; + } + + const data = await exchangeResponse.json(); + console.log('OIDC token exchange successful'); + + // Store tokens (same logic as password login in auth.js) + const token = data.access_token || data.token; + const refreshToken = data.refresh_token || data.refreshToken; + + if (token) { + localStorage.setItem(TOKEN_KEY, token); + if (refreshToken) localStorage.setItem(REFRESH_TOKEN_KEY, refreshToken); + + // Parse JWT expiry + let parsedExpiry = false; + const tokenParts = token.split('.'); + if (tokenParts.length === 3) { + try { + const payload = JSON.parse(atob(tokenParts[1])); + if (payload.exp) { + const expiryDate = new Date(payload.exp * 1000); + if (!isNaN(expiryDate.getTime())) { + localStorage.setItem(TOKEN_EXPIRY_KEY, expiryDate.toISOString()); + parsedExpiry = true; + } + } + } catch (e) { + console.error('Error parsing JWT:', e); + } + } + if (!parsedExpiry) { + const expiry = new Date(); + expiry.setDate(expiry.getDate() + 30); + localStorage.setItem(TOKEN_EXPIRY_KEY, expiry.toISOString()); + } + + // Store user data + if (data.user) { + localStorage.setItem(USER_DATA_KEY, JSON.stringify(data.user)); + } + + // Clean URL and reload without the oidc_code param + window.history.replaceState({}, document.title, '/'); + window.location.reload(); + return; + } + } catch (err) { + console.error('OIDC exchange error:', err); + window.location.href = '/login.html?source=oidc_error'; + return; + } + } + // Simple token check - just verify it exists const token = localStorage.getItem(TOKEN_KEY); diff --git a/static/js/auth.js b/static/js/auth.js index 2aefdcbd..9ec6885b 100644 --- a/static/js/auth.js +++ b/static/js/auth.js @@ -531,6 +531,62 @@ document.addEventListener('DOMContentLoaded', () => { return; } + // --- OIDC exchange code handling (fallback if landing on login page) --- + const urlParams = new URLSearchParams(window.location.search); + const oidcCode = urlParams.get('oidc_code'); + + if (oidcCode) { + console.log('OIDC exchange code detected on login page, exchanging...'); + (async () => { + try { + const resp = await fetch('/api/auth/oidc/exchange', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ code: oidcCode }) + }); + + if (!resp.ok) { + console.error('OIDC exchange failed:', resp.status); + return; // Fall through to normal login page + } + + const data = await resp.json(); + const token = data.access_token || data.token; + const refreshToken = data.refresh_token || data.refreshToken; + + if (token) { + localStorage.setItem(TOKEN_KEY, token); + if (refreshToken) localStorage.setItem(REFRESH_TOKEN_KEY, refreshToken); + + // Parse JWT expiry + const tokenParts = token.split('.'); + if (tokenParts.length === 3) { + try { + const payload = JSON.parse(atob(tokenParts[1])); + if (payload.exp) { + const expiryDate = new Date(payload.exp * 1000); + if (!isNaN(expiryDate.getTime())) { + localStorage.setItem(TOKEN_EXPIRY_KEY, expiryDate.toISOString()); + } + } + } catch (e) { /* ignore parse errors */ } + } + + if (data.user) { + localStorage.setItem(USER_DATA_KEY, JSON.stringify(data.user)); + } + + // Redirect to main app + window.location.href = '/?no_redirect=true'; + return; + } + } catch (err) { + console.error('OIDC exchange error:', err); + } + })(); + return; // Don't initialize login page while exchanging + } + if (authInitialized) { console.log('Auth already initialized, skipping'); return;