resolved ui glitch and fixed logout issue
This commit is contained in:
parent
96d8bedf6d
commit
02b009194c
@ -16,6 +16,7 @@ import { Toaster } from '@/components/ui/sonner';
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { CUSTOM_REQUEST_DATABASE } from '@/utils/customRequestDatabase';
|
import { CUSTOM_REQUEST_DATABASE } from '@/utils/customRequestDatabase';
|
||||||
import { CLAIM_MANAGEMENT_DATABASE } from '@/utils/claimManagementDatabase';
|
import { CLAIM_MANAGEMENT_DATABASE } from '@/utils/claimManagementDatabase';
|
||||||
|
import { AuthCallback } from '@/pages/Auth/AuthCallback';
|
||||||
|
|
||||||
// Combined Request Database for backward compatibility
|
// Combined Request Database for backward compatibility
|
||||||
// This combines both custom and claim management requests
|
// This combines both custom and claim management requests
|
||||||
@ -476,6 +477,12 @@ function AppRoutes({ onLogout }: AppProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="min-h-screen h-screen flex flex-col overflow-hidden bg-background">
|
<div className="min-h-screen h-screen flex flex-col overflow-hidden bg-background">
|
||||||
<Routes>
|
<Routes>
|
||||||
|
{/* Auth Callback - Must be before other routes */}
|
||||||
|
<Route
|
||||||
|
path="/login/callback"
|
||||||
|
element={<AuthCallback />}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Dashboard */}
|
{/* Dashboard */}
|
||||||
<Route
|
<Route
|
||||||
path="/"
|
path="/"
|
||||||
|
|||||||
@ -102,16 +102,24 @@ function BackendAuthProvider({ children }: { children: ReactNode }) {
|
|||||||
|
|
||||||
// PRIORITY 2: Check if URL has logout parameter (from redirect)
|
// PRIORITY 2: Check if URL has logout parameter (from redirect)
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
if (urlParams.has('logout')) {
|
if (urlParams.has('logout') || urlParams.has('okta_logged_out')) {
|
||||||
console.log('🔴 Logout parameter in URL - clearing everything');
|
console.log('🔴 Logout parameter in URL - clearing everything', {
|
||||||
|
hasLogout: urlParams.has('logout'),
|
||||||
|
hasOktaLoggedOut: urlParams.has('okta_logged_out'),
|
||||||
|
});
|
||||||
TokenManager.clearAll();
|
TokenManager.clearAll();
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
sessionStorage.clear();
|
sessionStorage.clear();
|
||||||
setIsAuthenticated(false);
|
setIsAuthenticated(false);
|
||||||
setUser(null);
|
setUser(null);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
// Clean URL
|
// Clean URL but preserve okta_logged_out flag if it exists (for prompt=login)
|
||||||
window.history.replaceState({}, document.title, '/');
|
const cleanParams = new URLSearchParams();
|
||||||
|
if (urlParams.has('okta_logged_out')) {
|
||||||
|
cleanParams.set('okta_logged_out', 'true');
|
||||||
|
}
|
||||||
|
const newUrl = cleanParams.toString() ? `/?${cleanParams.toString()}` : '/';
|
||||||
|
window.history.replaceState({}, document.title, newUrl);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,12 +344,23 @@ function BackendAuthProvider({ children }: { children: ReactNode }) {
|
|||||||
const scope = 'openid profile email';
|
const scope = 'openid profile email';
|
||||||
const state = Math.random().toString(36).substring(7);
|
const state = Math.random().toString(36).substring(7);
|
||||||
|
|
||||||
const authUrl = `${oktaDomain}/oauth2/default/v1/authorize?` +
|
// Check if we're coming from a logout - if so, add prompt=login to force re-authentication
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const isAfterLogout = urlParams.has('logout') || urlParams.has('okta_logged_out');
|
||||||
|
|
||||||
|
let authUrl = `${oktaDomain}/oauth2/default/v1/authorize?` +
|
||||||
`client_id=${clientId}&` +
|
`client_id=${clientId}&` +
|
||||||
`redirect_uri=${encodeURIComponent(redirectUri)}&` +
|
`redirect_uri=${encodeURIComponent(redirectUri)}&` +
|
||||||
`response_type=${responseType}&` +
|
`response_type=${responseType}&` +
|
||||||
`scope=${encodeURIComponent(scope)}&` +
|
`scope=${encodeURIComponent(scope)}&` +
|
||||||
`state=${state}`;
|
`state=${state}`;
|
||||||
|
|
||||||
|
// Add prompt=login if coming from logout to force re-authentication
|
||||||
|
// This ensures Okta requires login even if a session still exists
|
||||||
|
if (isAfterLogout) {
|
||||||
|
authUrl += `&prompt=login`;
|
||||||
|
console.log('🔐 Adding prompt=login to force re-authentication after logout');
|
||||||
|
}
|
||||||
|
|
||||||
window.location.href = authUrl;
|
window.location.href = authUrl;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@ -355,8 +374,14 @@ function BackendAuthProvider({ children }: { children: ReactNode }) {
|
|||||||
console.log('🚪 Current auth state:', { isAuthenticated, hasUser: !!user, isLoading });
|
console.log('🚪 Current auth state:', { isAuthenticated, hasUser: !!user, isLoading });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// CRITICAL: Get id_token from TokenManager before clearing anything
|
||||||
|
// Okta logout endpoint works better with id_token_hint to properly end the session
|
||||||
|
const idToken = TokenManager.getIdToken();
|
||||||
|
|
||||||
// Set logout flag to prevent auto-authentication after redirect
|
// Set logout flag to prevent auto-authentication after redirect
|
||||||
|
// This must be set BEFORE clearing storage so it survives
|
||||||
sessionStorage.setItem('__logout_in_progress__', 'true');
|
sessionStorage.setItem('__logout_in_progress__', 'true');
|
||||||
|
sessionStorage.setItem('__force_logout__', 'true');
|
||||||
setIsLoggingOut(true);
|
setIsLoggingOut(true);
|
||||||
|
|
||||||
console.log('🚪 Step 1: Resetting auth state...');
|
console.log('🚪 Step 1: Resetting auth state...');
|
||||||
@ -379,106 +404,62 @@ function BackendAuthProvider({ children }: { children: ReactNode }) {
|
|||||||
// Continue with logout even if API call fails
|
// Continue with logout even if API call fails
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear all authentication data
|
// Clear all authentication data EXCEPT the logout flags and id_token (we need it for Okta logout)
|
||||||
console.log('========================================');
|
console.log('========================================');
|
||||||
console.log('LOGOUT - Clearing all authentication data');
|
console.log('LOGOUT - Clearing all authentication data');
|
||||||
console.log('========================================');
|
console.log('========================================');
|
||||||
|
|
||||||
// Use TokenManager.clearAll() which does comprehensive cleanup
|
// Store id_token temporarily if we have it
|
||||||
TokenManager.clearAll();
|
let tempIdToken: string | null = null;
|
||||||
console.log('tryng to clrear all(localStorage and sessionStorage)')
|
if (idToken) {
|
||||||
// Double-check: Clear everything one more time as backup
|
tempIdToken = idToken;
|
||||||
try {
|
console.log('🚪 Preserving id_token for Okta logout:', tempIdToken.substring(0, 20) + '...');
|
||||||
// Get all localStorage keys and remove them
|
|
||||||
const localStorageKeys: string[] = [];
|
|
||||||
for (let i = 0; i < localStorage.length; i++) {
|
|
||||||
const key = localStorage.key(i);
|
|
||||||
if (key) localStorageKeys.push(key);
|
|
||||||
}
|
|
||||||
localStorageKeys.forEach(key => {
|
|
||||||
try {
|
|
||||||
localStorage.removeItem(key);
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(`Failed to remove localStorage key ${key}:`, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
localStorage.clear();
|
|
||||||
|
|
||||||
// Get all sessionStorage keys and remove them
|
|
||||||
const sessionStorageKeys: string[] = [];
|
|
||||||
for (let i = 0; i < sessionStorage.length; i++) {
|
|
||||||
const key = sessionStorage.key(i);
|
|
||||||
if (key) sessionStorageKeys.push(key);
|
|
||||||
}
|
|
||||||
sessionStorageKeys.forEach(key => {
|
|
||||||
try {
|
|
||||||
sessionStorage.removeItem(key);
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(`Failed to remove sessionStorage key ${key}:`, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
sessionStorage.clear();
|
|
||||||
|
|
||||||
console.log('Final verification - Storage cleared:');
|
|
||||||
console.log(`localStorage.length: ${localStorage.length}`);
|
|
||||||
console.log(`sessionStorage.length: ${sessionStorage.length}`);
|
|
||||||
|
|
||||||
if (localStorage.length > 0) {
|
|
||||||
console.error('ERROR: localStorage still has items:', Object.keys(localStorage));
|
|
||||||
}
|
|
||||||
if (sessionStorage.length > 0) {
|
|
||||||
console.error('ERROR: sessionStorage still has items:', Object.keys(sessionStorage));
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Error in final cleanup:', e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear tokens but preserve logout flags
|
||||||
|
const logoutInProgress = sessionStorage.getItem('__logout_in_progress__');
|
||||||
|
const forceLogout = sessionStorage.getItem('__force_logout__');
|
||||||
|
|
||||||
|
// Use TokenManager.clearAll() but then restore logout flags
|
||||||
|
TokenManager.clearAll();
|
||||||
|
|
||||||
|
// Restore logout flags immediately after clearAll
|
||||||
|
if (logoutInProgress) sessionStorage.setItem('__logout_in_progress__', logoutInProgress);
|
||||||
|
if (forceLogout) sessionStorage.setItem('__force_logout__', forceLogout);
|
||||||
|
|
||||||
|
console.log('🚪 Local storage cleared (logout flags preserved)');
|
||||||
|
|
||||||
// Final verification BEFORE redirect
|
// Final verification BEFORE redirect
|
||||||
console.log('All authentication data cleared. Final verification:');
|
console.log('🚪 Final verification - logout flags preserved:', {
|
||||||
console.log(`localStorage.length: ${localStorage.length}`);
|
logoutInProgress: sessionStorage.getItem('__logout_in_progress__'),
|
||||||
console.log(`sessionStorage.length: ${sessionStorage.length}`);
|
forceLogout: sessionStorage.getItem('__force_logout__'),
|
||||||
|
});
|
||||||
|
|
||||||
if (localStorage.length > 0) {
|
console.log('🚪 Clearing local session and redirecting to login...');
|
||||||
console.error('CRITICAL: localStorage still has items before redirect!', Object.keys(localStorage));
|
console.log('🚪 Using prompt=login on next auth to force re-authentication');
|
||||||
// Force clear one more time
|
console.log('🚪 This will prevent auto-authentication even if Okta session exists');
|
||||||
const remainingKeys = Object.keys(localStorage);
|
|
||||||
remainingKeys.forEach(key => localStorage.removeItem(key));
|
|
||||||
localStorage.clear();
|
|
||||||
}
|
|
||||||
if (sessionStorage.length > 0) {
|
|
||||||
console.error('CRITICAL: sessionStorage still has items before redirect!', Object.keys(sessionStorage));
|
|
||||||
const remainingKeys = Object.keys(sessionStorage);
|
|
||||||
remainingKeys.forEach(key => sessionStorage.removeItem(key));
|
|
||||||
sessionStorage.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Redirecting to login...');
|
|
||||||
|
|
||||||
// Ensure logout flag is set before redirect
|
|
||||||
sessionStorage.setItem('__logout_in_progress__', 'true');
|
|
||||||
|
|
||||||
// Small delay to ensure sessionStorage is written before redirect
|
// Small delay to ensure sessionStorage is written before redirect
|
||||||
await new Promise(resolve => setTimeout(resolve, 50));
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
|
||||||
// Use replace instead of href to prevent browser history issues
|
// Redirect directly to login page with flags
|
||||||
// DON'T use setTimeout - redirect immediately after clearing
|
// The okta_logged_out flag will trigger prompt=login in the login() function
|
||||||
if (isLocalhost()) {
|
// This forces re-authentication even if Okta session still exists
|
||||||
// Direct redirect to login page for localhost - force full page reload
|
const loginUrl = `${window.location.origin}/?okta_logged_out=true&logout=${Date.now()}`;
|
||||||
// Add timestamp to force fresh page load
|
window.location.replace(loginUrl);
|
||||||
window.location.replace('/?logout=' + Date.now());
|
|
||||||
} else {
|
|
||||||
// For production, redirect to Okta logout then to login
|
|
||||||
const oktaDomain = 'https://dev-830839.oktapreview.com';
|
|
||||||
const loginUrl = `${window.location.origin}/?logout=${Date.now()}`;
|
|
||||||
const logoutUrl = `${oktaDomain}/oauth2/default/v1/logout?post_logout_redirect_uri=${encodeURIComponent(loginUrl)}`;
|
|
||||||
window.location.replace(logoutUrl);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Logout error:', error);
|
console.error('🚪 Logout error:', error);
|
||||||
// Force redirect even on error
|
// Force redirect even on error - clear everything and redirect to login
|
||||||
localStorage.clear();
|
try {
|
||||||
sessionStorage.clear();
|
localStorage.clear();
|
||||||
window.location.replace('/');
|
sessionStorage.clear();
|
||||||
|
sessionStorage.setItem('__logout_in_progress__', 'true');
|
||||||
|
const loginUrl = `${window.location.origin}/?okta_logged_out=true&logout=${Date.now()}`;
|
||||||
|
window.location.replace(loginUrl);
|
||||||
|
} catch {
|
||||||
|
// Last resort - redirect to home
|
||||||
|
window.location.replace('/?logout=' + Date.now());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,47 +1,186 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useAuth } from '@/contexts/AuthContext';
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
import { Loader } from '@/components/common/Loader';
|
import { CheckCircle2, AlertCircle, Loader2 } from 'lucide-react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
export function AuthCallback() {
|
export function AuthCallback() {
|
||||||
const { isAuthenticated, isLoading, error, user } = useAuth();
|
const { isAuthenticated, isLoading, error, user } = useAuth();
|
||||||
const navigate = useNavigate();
|
const [authStep, setAuthStep] = useState<'exchanging' | 'fetching' | 'complete' | 'error'>('exchanging');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('AuthCallback Component Mounted');
|
// Determine current authentication step based on state
|
||||||
console.log('Auth State during callback:', {
|
|
||||||
isAuthenticated,
|
|
||||||
isLoading,
|
|
||||||
error: error?.message,
|
|
||||||
hasUser: !!user,
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
|
|
||||||
if (user) {
|
|
||||||
console.log('User authenticated successfully:', {
|
|
||||||
userId: user.userId,
|
|
||||||
employeeId: user.employeeId,
|
|
||||||
email: user.email,
|
|
||||||
displayName: user.displayName,
|
|
||||||
userData: user
|
|
||||||
});
|
|
||||||
// Redirect to home after successful authentication
|
|
||||||
navigate('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('Error during authentication callback:', error);
|
setAuthStep('error');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}, [isAuthenticated, isLoading, error, user, navigate]);
|
|
||||||
|
if (isLoading) {
|
||||||
|
// If we have code in URL but no user yet, we're exchanging
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const hasCode = urlParams.get('code');
|
||||||
|
|
||||||
|
if (hasCode && !user) {
|
||||||
|
setAuthStep('exchanging');
|
||||||
|
} else if (user && !isAuthenticated) {
|
||||||
|
setAuthStep('fetching');
|
||||||
|
} else {
|
||||||
|
setAuthStep('exchanging');
|
||||||
|
}
|
||||||
|
} else if (user && isAuthenticated) {
|
||||||
|
setAuthStep('complete');
|
||||||
|
// Small delay before redirect for better UX
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
// Use window.location instead of navigate since Router context isn't available yet
|
||||||
|
window.location.href = '/';
|
||||||
|
}, 1500);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}, [isAuthenticated, isLoading, error, user]);
|
||||||
|
|
||||||
if (error) {
|
// Get dynamic loading message
|
||||||
console.error('AuthCallback Error:', error);
|
const getLoadingMessage = () => {
|
||||||
}
|
switch (authStep) {
|
||||||
|
case 'exchanging':
|
||||||
|
return 'Exchanging authorization code...';
|
||||||
|
case 'fetching':
|
||||||
|
return 'Fetching your profile...';
|
||||||
|
case 'complete':
|
||||||
|
return 'Authentication successful!';
|
||||||
|
case 'error':
|
||||||
|
return 'Authentication failed';
|
||||||
|
default:
|
||||||
|
return 'Completing authentication...';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
|
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900">
|
||||||
<Loader message="Completing authentication..." />
|
<div className="absolute inset-0 bg-[url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHZpZXdCb3g9IjAgMCA2MCA2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxnIGZpbGw9IiMxZTIxMmQiIGZpbGwtb3BhY2l0eT0iMC4wNSI+PGNpcmNsZSBjeD0iMzAiIGN5PSIzMCIgcj0iMzAiLz48L2c+PC9nPjwvc3ZnPg==')] opacity-20"></div>
|
||||||
|
|
||||||
|
<div className="relative z-10 text-center px-4 max-w-md w-full">
|
||||||
|
{/* Logo/Brand Section */}
|
||||||
|
<div className="mb-8">
|
||||||
|
<div className="inline-flex items-center justify-center w-20 h-20 mb-4 rounded-2xl bg-gradient-to-br from-orange-500 to-red-600 shadow-lg shadow-orange-500/20">
|
||||||
|
<svg
|
||||||
|
className="w-12 h-12 text-white"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M12 4v16m8-8H4"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-2xl font-bold text-white mb-2">Royal Enfield</h1>
|
||||||
|
<p className="text-slate-400 text-sm">Approval Portal</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Loader Card */}
|
||||||
|
<div className="bg-white/10 backdrop-blur-xl rounded-2xl p-8 shadow-2xl border border-white/20">
|
||||||
|
{/* Status Icon */}
|
||||||
|
<div className="mb-6 flex justify-center">
|
||||||
|
{authStep === 'error' ? (
|
||||||
|
<div className="relative">
|
||||||
|
<div className="absolute inset-0 animate-ping opacity-75">
|
||||||
|
<AlertCircle className="w-16 h-16 text-red-500" />
|
||||||
|
</div>
|
||||||
|
<AlertCircle className="w-16 h-16 text-red-500 relative" />
|
||||||
|
</div>
|
||||||
|
) : authStep === 'complete' ? (
|
||||||
|
<div className="relative">
|
||||||
|
<div className="absolute inset-0 animate-ping opacity-75">
|
||||||
|
<CheckCircle2 className="w-16 h-16 text-green-500" />
|
||||||
|
</div>
|
||||||
|
<CheckCircle2 className="w-16 h-16 text-green-500 relative" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="relative">
|
||||||
|
<Loader2 className="w-16 h-16 text-orange-500 animate-spin" />
|
||||||
|
{/* Outer rotating ring */}
|
||||||
|
<div className="absolute inset-0 border-4 border-orange-500/20 rounded-full"></div>
|
||||||
|
<div className="absolute inset-0 border-4 border-transparent border-t-orange-500 rounded-full animate-spin"></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Loading Message */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<h2 className="text-xl font-semibold text-white mb-2">
|
||||||
|
{authStep === 'complete' ? 'Welcome Back!' : authStep === 'error' ? 'Authentication Error' : 'Authenticating'}
|
||||||
|
</h2>
|
||||||
|
<p className="text-slate-300 text-sm">{getLoadingMessage()}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Progress Steps */}
|
||||||
|
{authStep !== 'error' && authStep !== 'complete' && (
|
||||||
|
<div className="space-y-3 mb-6">
|
||||||
|
<div className={`flex items-center gap-3 text-sm transition-all duration-500 ${authStep === 'exchanging' ? 'text-white' : 'text-slate-400'}`}>
|
||||||
|
<div className={`w-2 h-2 rounded-full transition-all duration-500 ${authStep === 'exchanging' ? 'bg-orange-500 animate-pulse' : 'bg-slate-600'}`}></div>
|
||||||
|
<span>Validating credentials</span>
|
||||||
|
</div>
|
||||||
|
<div className={`flex items-center gap-3 text-sm transition-all duration-500 ${authStep === 'fetching' ? 'text-white' : 'text-slate-400'}`}>
|
||||||
|
<div className={`w-2 h-2 rounded-full transition-all duration-500 ${authStep === 'fetching' ? 'bg-orange-500 animate-pulse' : 'bg-slate-600'}`}></div>
|
||||||
|
<span>Loading your profile</span>
|
||||||
|
</div>
|
||||||
|
<div className={`flex items-center gap-3 text-sm transition-all duration-500 ${authStep === 'complete' ? 'text-white' : 'text-slate-400'}`}>
|
||||||
|
<div className={`w-2 h-2 rounded-full transition-all duration-500 ${authStep === 'complete' ? 'bg-green-500' : 'bg-slate-600'}`}></div>
|
||||||
|
<span>Setting up your session</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Error Message */}
|
||||||
|
{authStep === 'error' && error && (
|
||||||
|
<div className="mt-6 p-4 bg-red-500/10 border border-red-500/20 rounded-lg">
|
||||||
|
<p className="text-red-400 text-sm">{error.message || 'An error occurred during authentication'}</p>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
// Use window.location instead of navigate since Router context isn't available yet
|
||||||
|
window.location.href = '/';
|
||||||
|
}}
|
||||||
|
className="mt-4 text-sm text-red-400 hover:text-red-300 underline"
|
||||||
|
>
|
||||||
|
Return to login
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Animated Progress Bar */}
|
||||||
|
{authStep !== 'error' && authStep !== 'complete' && (
|
||||||
|
<div className="mt-6">
|
||||||
|
<div className="h-1.5 bg-slate-700/50 rounded-full overflow-hidden">
|
||||||
|
<div
|
||||||
|
className="h-full bg-gradient-to-r from-orange-500 to-red-600 rounded-full animate-pulse"
|
||||||
|
style={{
|
||||||
|
animation: 'progress 2s ease-in-out infinite',
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<style>{`
|
||||||
|
@keyframes progress {
|
||||||
|
0%, 100% { width: 20%; }
|
||||||
|
50% { width: 80%; }
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer Text */}
|
||||||
|
<p className="mt-6 text-slate-500 text-xs">
|
||||||
|
{authStep === 'complete' ? 'Redirecting to dashboard...' : 'Please wait while we secure your session'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Animated Background Elements */}
|
||||||
|
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||||
|
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-orange-500/5 rounded-full blur-3xl animate-pulse"></div>
|
||||||
|
<div className="absolute bottom-1/4 right-1/4 w-96 h-96 bg-red-500/5 rounded-full blur-3xl animate-pulse delay-1000"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,9 @@ export function AuthenticatedApp() {
|
|||||||
const { isAuthenticated, isLoading, error, user, logout } = useAuth();
|
const { isAuthenticated, isLoading, error, user, logout } = useAuth();
|
||||||
const [showDebugInfo, setShowDebugInfo] = useState(false);
|
const [showDebugInfo, setShowDebugInfo] = useState(false);
|
||||||
|
|
||||||
|
// Check if we're on callback route (after all hooks are called)
|
||||||
|
const isCallbackRoute = typeof window !== 'undefined' && window.location.pathname === '/login/callback';
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
console.log('🔵 ========================================');
|
console.log('🔵 ========================================');
|
||||||
console.log('🔵 AuthenticatedApp.handleLogout - CALLED');
|
console.log('🔵 AuthenticatedApp.handleLogout - CALLED');
|
||||||
@ -70,6 +73,11 @@ export function AuthenticatedApp() {
|
|||||||
}
|
}
|
||||||
}, [isAuthenticated, isLoading, error, user]);
|
}, [isAuthenticated, isLoading, error, user]);
|
||||||
|
|
||||||
|
// Always show callback loader when on callback route (after all hooks)
|
||||||
|
if (isCallbackRoute) {
|
||||||
|
return <AuthCallback />;
|
||||||
|
}
|
||||||
|
|
||||||
// Show loading state while checking authentication
|
// Show loading state while checking authentication
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
console.log('Auth0 is still loading...');
|
console.log('Auth0 is still loading...');
|
||||||
|
|||||||
@ -86,6 +86,7 @@ export interface TokenExchangeResponse {
|
|||||||
};
|
};
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
|
idToken?: string; // ID token from Okta for logout
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RefreshTokenResponse {
|
export interface RefreshTokenResponse {
|
||||||
@ -154,6 +155,11 @@ export async function exchangeCodeForTokens(
|
|||||||
TokenManager.setAccessToken(result.accessToken);
|
TokenManager.setAccessToken(result.accessToken);
|
||||||
TokenManager.setRefreshToken(result.refreshToken);
|
TokenManager.setRefreshToken(result.refreshToken);
|
||||||
TokenManager.setUserData(result.user);
|
TokenManager.setUserData(result.user);
|
||||||
|
// Store id_token if available (needed for proper Okta logout)
|
||||||
|
if (result.idToken) {
|
||||||
|
TokenManager.setIdToken(result.idToken);
|
||||||
|
console.log('✅ ID token stored for logout');
|
||||||
|
}
|
||||||
console.log('✅ Tokens stored successfully');
|
console.log('✅ Tokens stored successfully');
|
||||||
} else {
|
} else {
|
||||||
console.warn('⚠️ Tokens missing in response', { result });
|
console.warn('⚠️ Tokens missing in response', { result });
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
const ACCESS_TOKEN_KEY = 'accessToken';
|
const ACCESS_TOKEN_KEY = 'accessToken';
|
||||||
const REFRESH_TOKEN_KEY = 'refreshToken';
|
const REFRESH_TOKEN_KEY = 'refreshToken';
|
||||||
|
const ID_TOKEN_KEY = 'idToken';
|
||||||
const USER_DATA_KEY = 'userData';
|
const USER_DATA_KEY = 'userData';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,7 +50,7 @@ export const cookieUtils = {
|
|||||||
* Attempts to clear cookies with different paths and domains
|
* Attempts to clear cookies with different paths and domains
|
||||||
*/
|
*/
|
||||||
clearAll(): void {
|
clearAll(): void {
|
||||||
const cookieNames = [ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY, USER_DATA_KEY];
|
const cookieNames = [ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY, ID_TOKEN_KEY, USER_DATA_KEY];
|
||||||
|
|
||||||
cookieNames.forEach(name => {
|
cookieNames.forEach(name => {
|
||||||
// Remove with default path
|
// Remove with default path
|
||||||
@ -119,6 +120,27 @@ export class TokenManager {
|
|||||||
return localStorage.getItem(REFRESH_TOKEN_KEY);
|
return localStorage.getItem(REFRESH_TOKEN_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store ID token (from Okta)
|
||||||
|
*/
|
||||||
|
static setIdToken(token: string): void {
|
||||||
|
localStorage.setItem(ID_TOKEN_KEY, token);
|
||||||
|
if (this.isLocalhost()) {
|
||||||
|
cookieUtils.set(ID_TOKEN_KEY, token, 1); // 1 day
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get ID token
|
||||||
|
*/
|
||||||
|
static getIdToken(): string | null {
|
||||||
|
if (this.isLocalhost()) {
|
||||||
|
// Try cookie first, then localStorage
|
||||||
|
return cookieUtils.get(ID_TOKEN_KEY) || localStorage.getItem(ID_TOKEN_KEY);
|
||||||
|
}
|
||||||
|
return localStorage.getItem(ID_TOKEN_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store user data
|
* Store user data
|
||||||
*/
|
*/
|
||||||
@ -164,6 +186,7 @@ export class TokenManager {
|
|||||||
const authKeys = [
|
const authKeys = [
|
||||||
ACCESS_TOKEN_KEY,
|
ACCESS_TOKEN_KEY,
|
||||||
REFRESH_TOKEN_KEY,
|
REFRESH_TOKEN_KEY,
|
||||||
|
ID_TOKEN_KEY,
|
||||||
USER_DATA_KEY,
|
USER_DATA_KEY,
|
||||||
'oktaToken',
|
'oktaToken',
|
||||||
'authToken',
|
'authToken',
|
||||||
@ -172,6 +195,7 @@ export class TokenManager {
|
|||||||
'access_token',
|
'access_token',
|
||||||
'refresh_token',
|
'refresh_token',
|
||||||
'id_token',
|
'id_token',
|
||||||
|
'idToken',
|
||||||
'token',
|
'token',
|
||||||
'accessToken',
|
'accessToken',
|
||||||
'refreshToken',
|
'refreshToken',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user