"use client"; import { useEffect, useRef, useState } from 'react'; import { useSearchParams } from 'next/navigation'; import Link from 'next/link'; export default function LoggedOutPage() { const [sessionStatus, setSessionStatus] = useState<'checking' | 'cleared' | 'error'>('checking'); const [message, setMessage] = useState(''); const [keycloakLogoutUrl, setKeycloakLogoutUrl] = useState(null); const iframeRef = useRef(null); const searchParams = useSearchParams(); const preserveSso = searchParams.get('preserveSso') === 'true'; useEffect(() => { // Listen for messages from iframes const handleMessage = (event: MessageEvent) => { if (event.data && event.data.type === 'AUTH_STATUS') { console.log('Received auth status message:', event.data); } }; window.addEventListener('message', handleMessage); const clearSessions = async () => { try { console.log(`Clearing sessions (preserveSso: ${preserveSso})`); // Try to get any stored user IDs for server-side cleanup let userId = null; try { // Check localStorage first userId = localStorage.getItem('userId') || sessionStorage.getItem('userId'); // Try to get from sessionStorage as fallback if (!userId) { const sessionData = sessionStorage.getItem('nextauth.session-token'); if (sessionData) { try { const parsed = JSON.parse(atob(sessionData.split('.')[1])); userId = parsed.sub || parsed.id; } catch (e) { console.error('Failed to parse session data:', e); } } } } catch (e) { console.error('Error accessing storage:', e); } // If we found a user ID, call server-side cleanup if (userId) { console.log(`Found user ID: ${userId}, cleaning up server-side`); try { const response = await fetch('/api/auth/session-cleanup', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userId, preserveSso }), credentials: 'include' }); const result = await response.json(); console.log('Server cleanup result:', result); } catch (e) { console.error('Error during server-side cleanup:', e); } } // If not preserving SSO, get Keycloak logout URL if (!preserveSso) { try { const response = await fetch('/api/auth/full-logout', { credentials: 'include' }); const data = await response.json(); if (data.success && data.logoutUrl) { setKeycloakLogoutUrl(data.logoutUrl); console.log('Keycloak logout URL:', data.logoutUrl); } } catch (e) { console.error('Failed to get Keycloak logout URL:', e); } } // Clear cookies appropriately based on preserve SSO setting const cookies = document.cookie.split(';'); // Get all cookies names const cookieNames = cookies.map(cookie => cookie.split('=')[0].trim()); // Find chunked cookies const chunkedCookies = cookieNames .filter(name => /next-auth.*\.\d+$/.test(name)); // Define cookies to clear let cookiesToClear = []; if (preserveSso) { // Only clear app-specific cookies if preserving SSO cookiesToClear = [ 'next-auth.session-token', 'next-auth.csrf-token', 'next-auth.callback-url', '__Secure-next-auth.session-token', '__Host-next-auth.csrf-token', ...chunkedCookies ]; } else { // Clear ALL auth-related cookies for full logout const authCookies = cookieNames.filter(name => name.includes('auth') || name.includes('KEYCLOAK') || name.includes('KC_') || name.includes('session') ); cookiesToClear = [ ...authCookies, ...chunkedCookies, 'JSESSIONID', 'KEYCLOAK_SESSION', 'KEYCLOAK_IDENTITY', 'KC_RESTART' ]; } // Clear the cookies with all possible path and domain combinations const hostname = window.location.hostname; const baseDomain = hostname.split('.').slice(-2).join('.'); cookiesToClear.forEach(cookieName => { // Try various path and domain combinations to ensure complete cleanup document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${hostname};`; // Only try this for multi-part domains if (hostname !== baseDomain) { document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=.${baseDomain};`; document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=.${hostname};`; } // Add secure and SameSite attributes document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; SameSite=None; Secure;`; }); // Clear session storage try { sessionStorage.clear(); } catch (e) { console.error('Failed to clear sessionStorage:', e); } // Clear auth-related localStorage items try { localStorage.removeItem('userId'); localStorage.removeItem('userName'); localStorage.removeItem('userEmail'); localStorage.removeItem('token'); localStorage.removeItem('refreshToken'); // Rocket Chat items localStorage.removeItem('Meteor.loginToken'); localStorage.removeItem('Meteor.userId'); } catch (e) { console.error('Failed to clear localStorage items:', e); } // Notify parent window if we're in an iframe if (window !== window.parent) { try { window.parent.postMessage({ type: 'AUTH_STATUS', status: 'LOGGED_OUT', preserveSso }, '*'); } catch (e) { console.error('Failed to send logout message to parent:', e); } } setSessionStatus('cleared'); setMessage(preserveSso ? 'You have been logged out of this application, but your SSO session is still active for other services.' : 'You have been completely logged out of all services.' ); } catch (error) { console.error('Error during logout cleanup:', error); setSessionStatus('error'); setMessage('There was an error during the logout process.'); } }; clearSessions(); return () => { window.removeEventListener('message', handleMessage); }; }, [preserveSso]); return (

{preserveSso ? 'Application Sign Out' : 'Complete Sign Out'}

{sessionStatus === 'checking' && (

Cleaning up your session...

)} {sessionStatus === 'cleared' && ( <>

{message}

{preserveSso ? ( <>

You can continue using other applications without signing in again.

Return to Home Page
) : ( <>

You will need to sign in again to access any protected services.

{keycloakLogoutUrl && (