Fix: Complete OIDC login flow - exchange code for tokens on frontend

The backend redirected to /?oidc_code=<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
This commit is contained in:
Dionisio
2026-02-13 21:33:45 +01:00
parent d0d5489994
commit 33ed3bd66c
2 changed files with 130 additions and 0 deletions
+74
View File
@@ -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);
+56
View File
@@ -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;