diff --git a/app.py b/app.py index 015eb01..7d5f1f2 100644 --- a/app.py +++ b/app.py @@ -109,12 +109,13 @@ def create_app(config_class=app_config.Config): app.register_blueprint(video_master_bp) logger.info("Video Master blueprint (BETA) registered at /video-master") - # Register root route (redirect to reporting) + # Register root route - render reporting page directly (no 302 redirect). + # A redirect would strip the URL hash fragment (#code=...) that MSAL + # needs to process after returning from Microsoft login. @app.route('/') def root(): - """Redirect root to reporting module.""" - from flask import redirect, url_for - return redirect(url_for('reporting.index')) + """Render reporting index directly at root.""" + return render_template('reporting/index.html', active_tab='reporting') # Register error handlers register_error_handlers(app) diff --git a/static/js/auth.js b/static/js/auth.js index e785e6d..de42254 100644 --- a/static/js/auth.js +++ b/static/js/auth.js @@ -1,6 +1,7 @@ /** * Authentication Module for HM QC Report Dashboard * Uses Microsoft MSAL Browser library for Azure AD authentication + * Uses redirect flow (not popup) to avoid cross-window timing issues. */ // Azure AD Configuration @@ -101,7 +102,9 @@ async function checkAuthStatus() { } /** - * Sign in with Microsoft + * Sign in with Microsoft using redirect flow. + * The page navigates to Microsoft login, then redirects back. + * On return, handleRedirectPromise() in initAuth() processes the response. */ async function signIn() { if (!msalInstance) { @@ -111,35 +114,13 @@ async function signIn() { try { showLoading(); - - // Perform popup login - const loginResponse = await msalInstance.loginPopup(loginRequest); - - if (loginResponse && loginResponse.idToken) { - // Send token to backend for validation and cookie storage - const success = await submitTokenToBackend(loginResponse.idToken); - - if (success) { - currentUser = loginResponse.account; - isAuthenticated = true; - showAuthenticatedState(); - updateUserInfo(); - } else { - throw new Error('Failed to validate token with backend'); - } - } + // Redirect the whole page to Microsoft login. + // After auth, Microsoft redirects back to redirectUri. + // initAuth() -> handleRedirectPromise() will pick up the response. + await msalInstance.loginRedirect(loginRequest); } catch (error) { - console.error('Login failed:', error); - - if (error.errorCode === 'popup_window_error') { - showError('Popup was blocked. Please allow popups for this site.'); - } else if (error.errorCode === 'user_cancelled') { - console.log('User cancelled login'); - showUnauthenticatedState(); - } else { - showError('Login failed: ' + (error.errorMessage || error.message || 'Unknown error')); - } - + console.error('Login redirect failed:', error); + showError('Login failed: ' + (error.errorMessage || error.message || 'Unknown error')); showUnauthenticatedState(); } } @@ -181,7 +162,7 @@ async function signOut() { try { showLoading(); - // Clear backend cookie + // Clear backend session await fetch('/auth/logout', { method: 'POST', credentials: 'include' @@ -189,12 +170,10 @@ async function signOut() { // Clear MSAL cache if (msalInstance) { - const currentAccount = msalInstance.getActiveAccount(); - if (currentAccount) { - await msalInstance.logoutPopup({ - account: currentAccount, - postLogoutRedirectUri: window.location.origin - }); + const accounts = msalInstance.getAllAccounts(); + if (accounts.length > 0) { + // Clear local MSAL state without redirecting to Microsoft logout + msalInstance.clearCache(); } } @@ -273,7 +252,10 @@ function showError(message) { if (!errorDiv) { errorDiv = document.createElement('div'); errorDiv.className = 'alert alert-danger mt-3'; - authRequired.querySelector('.text-center').appendChild(errorDiv); + const textCenter = authRequired.querySelector('.text-center'); + if (textCenter) { + textCenter.appendChild(errorDiv); + } } errorDiv.textContent = message; } else { @@ -329,14 +311,44 @@ async function initAuth() { return; } + // Handle redirect response from Microsoft login. + // After loginRedirect(), the page reloads with auth code in the URL hash. + // handleRedirectPromise() extracts the code, exchanges it for tokens, + // and cleans up the URL hash. + try { + const redirectResponse = await msalInstance.handleRedirectPromise(); + + if (redirectResponse && redirectResponse.idToken) { + console.log('Redirect login successful, sending token to backend...'); + + // Send token to backend for session creation + const success = await submitTokenToBackend(redirectResponse.idToken); + + if (success) { + currentUser = redirectResponse.account; + isAuthenticated = true; + + // Setup event listeners and show authenticated state + setupEventListeners(); + showAuthenticatedState(); + updateUserInfo(); + return; + } else { + console.error('Backend token validation failed after redirect'); + } + } + } catch (error) { + console.error('Error handling redirect response:', error); + } + // Setup event listeners setupEventListeners(); - // Check authentication status + // No redirect response - check if we have an existing backend session const authenticated = await checkAuthStatus(); if (authenticated) { - console.log('User is authenticated'); + console.log('User is authenticated (existing session)'); } else { console.log('User is not authenticated'); }