153 lines
6.1 KiB
TypeScript
153 lines
6.1 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect } from "react";
|
|
import { useSession, signOut } from "next-auth/react";
|
|
import { clearAuthCookies } from "@/lib/session";
|
|
|
|
export function SignOutHandler() {
|
|
const { data: session } = useSession();
|
|
|
|
useEffect(() => {
|
|
const handleSignOut = async () => {
|
|
try {
|
|
// Store the user ID before signout clears the session
|
|
const userId = session?.user?.id;
|
|
console.log('Starting comprehensive logout process');
|
|
|
|
// First trigger server-side session cleanup
|
|
if (userId) {
|
|
console.log('Triggering server-side session cleanup');
|
|
try {
|
|
const cleanupResponse = await fetch('/api/auth/session-cleanup', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ userId }),
|
|
credentials: 'include'
|
|
});
|
|
|
|
const cleanupResult = await cleanupResponse.json();
|
|
console.log('Server cleanup result:', cleanupResult);
|
|
} catch (cleanupError) {
|
|
console.error('Error during server-side cleanup:', cleanupError);
|
|
// Continue with logout even if cleanup fails
|
|
}
|
|
}
|
|
|
|
// Then, attempt to sign out from NextAuth explicitly
|
|
await signOut({ redirect: false });
|
|
|
|
// Clear Rocket Chat authentication tokens
|
|
try {
|
|
console.log('Clearing Rocket Chat tokens');
|
|
// Remove cookies
|
|
document.cookie = `rc_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${window.location.hostname}; SameSite=None; Secure`;
|
|
document.cookie = `rc_uid=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${window.location.hostname}; SameSite=None; Secure`;
|
|
|
|
// Remove localStorage items
|
|
localStorage.removeItem('Meteor.loginToken');
|
|
localStorage.removeItem('Meteor.userId');
|
|
|
|
// Try to send logout to Rocket Chat server
|
|
const rocketChatBaseUrl = process.env.NEXT_PUBLIC_IFRAME_PAROLE_URL?.split('/channel')[0];
|
|
if (rocketChatBaseUrl) {
|
|
// This is a best-effort logout - we don't wait for it to complete
|
|
fetch(`${rocketChatBaseUrl}/api/v1/logout`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
}).catch(e => console.error('Failed to notify Rocket Chat server about logout:', e));
|
|
}
|
|
} catch (e) {
|
|
console.error('Error clearing Rocket Chat tokens:', e);
|
|
}
|
|
|
|
// Then clear all auth-related cookies to ensure we break any local sessions
|
|
clearAuthCookies();
|
|
|
|
// Get Keycloak logout URL
|
|
if (process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER) {
|
|
console.log('Preparing complete Keycloak logout');
|
|
|
|
// Create a proper Keycloak logout URL with all required parameters for front-channel logout
|
|
const keycloakBaseUrl = process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER;
|
|
const logoutEndpoint = `${keycloakBaseUrl}/protocol/openid-connect/logout`;
|
|
|
|
// Create form for POST logout (more reliable)
|
|
const form = document.createElement('form');
|
|
form.method = 'POST';
|
|
form.action = logoutEndpoint;
|
|
|
|
// Add id_token_hint if available
|
|
if (session?.accessToken) {
|
|
const tokenInput = document.createElement('input');
|
|
tokenInput.type = 'hidden';
|
|
tokenInput.name = 'id_token_hint';
|
|
tokenInput.value = session.accessToken;
|
|
form.appendChild(tokenInput);
|
|
}
|
|
|
|
// Add client_id parameter - CRITICAL for proper logout
|
|
const clientIdInput = document.createElement('input');
|
|
clientIdInput.type = 'hidden';
|
|
clientIdInput.name = 'client_id';
|
|
clientIdInput.value = process.env.NEXT_PUBLIC_KEYCLOAK_CLIENT_ID || 'lab';
|
|
form.appendChild(clientIdInput);
|
|
|
|
// Add post_logout_redirect_uri pointing to our logged out page
|
|
const redirectInput = document.createElement('input');
|
|
redirectInput.type = 'hidden';
|
|
redirectInput.name = 'post_logout_redirect_uri';
|
|
redirectInput.value = `${window.location.origin}/loggedout`;
|
|
form.appendChild(redirectInput);
|
|
|
|
// Add logout_hint=server to explicitly request server-side session cleanup
|
|
const logoutHintInput = document.createElement('input');
|
|
logoutHintInput.type = 'hidden';
|
|
logoutHintInput.name = 'logout_hint';
|
|
logoutHintInput.value = 'server';
|
|
form.appendChild(logoutHintInput);
|
|
|
|
// Notify iframe parents before logging out
|
|
try {
|
|
// Attempt to notify any iframes that might be using this authentication
|
|
if (window.parent && window.parent !== window) {
|
|
window.parent.postMessage({ type: 'LOGOUT_EVENT' }, '*');
|
|
}
|
|
} catch (e) {
|
|
console.error('Error notifying parent of logout:', e);
|
|
}
|
|
|
|
// Append to body and submit
|
|
document.body.appendChild(form);
|
|
console.log('Submitting Keycloak logout form with server-side logout');
|
|
form.submit();
|
|
} else {
|
|
console.log('No Keycloak configuration found, performing simple redirect');
|
|
window.location.href = '/loggedout';
|
|
}
|
|
} catch (error) {
|
|
console.error('Error during logout:', error);
|
|
window.location.href = '/loggedout';
|
|
}
|
|
};
|
|
|
|
// Add a slight delay to ensure useSession has loaded
|
|
const timer = setTimeout(() => {
|
|
handleSignOut();
|
|
}, 100);
|
|
|
|
return () => clearTimeout(timer);
|
|
}, [session]);
|
|
|
|
return (
|
|
<div className="w-full h-full flex items-center justify-center">
|
|
<div className="text-center">
|
|
<h2 className="text-2xl font-bold">Logging out...</h2>
|
|
<p className="text-gray-500 mt-2">Please wait while we sign you out completely.</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|