NeahNew/components/layout/layout-wrapper.tsx

140 lines
5.3 KiB
TypeScript

"use client";
import { useEffect } from "react";
import { useSession, signOut } from "next-auth/react";
import { MainNav } from "@/components/main-nav";
import { Footer } from "@/components/footer";
import { AuthCheck } from "@/components/auth/auth-check";
import { Toaster } from "@/components/ui/toaster";
import { useBackgroundImage } from "@/components/background-switcher";
import { clearAuthCookies, clearKeycloakCookies } from "@/lib/session";
interface LayoutWrapperProps {
children: React.ReactNode;
isSignInPage: boolean;
isAuthenticated: boolean;
}
export function LayoutWrapper({ children, isSignInPage, isAuthenticated }: LayoutWrapperProps) {
const { currentBackground, changeBackground } = useBackgroundImage();
const { data: session } = useSession();
// Global listener for logout messages from iframe applications
useEffect(() => {
if (isSignInPage) return; // Don't listen on signin page
const handleMessage = async (event: MessageEvent) => {
// Security: Validate message origin (in production, check against known iframe URLs)
// For now, we accept messages from any origin but validate the message structure
// Check if message is a logout request from iframe
if (event.data && typeof event.data === 'object') {
if (event.data.type === 'KEYCLOAK_LOGOUT' || event.data.type === 'LOGOUT') {
console.log('Received logout request from iframe application, triggering dashboard logout');
try {
// Mark logout in progress
sessionStorage.setItem('just_logged_out', 'true');
document.cookie = 'logout_in_progress=true; path=/; max-age=60';
// Clear cookies
clearAuthCookies();
clearKeycloakCookies();
// End SSO session using Admin API before signing out
// This ensures the realm-wide SSO session is cleared,
// not just the client session
try {
const ssoLogoutResponse = await fetch('/api/auth/end-sso-session', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
});
if (ssoLogoutResponse.ok) {
console.log('SSO session ended successfully');
} else {
const errorData = await ssoLogoutResponse.json().catch(() => ({}));
console.warn('Failed to end SSO session via Admin API, continuing with standard logout:', errorData);
// Continue with logout even if SSO session termination fails
}
} catch (error) {
console.error('Error ending SSO session:', error);
// Continue with logout even if SSO session termination fails
}
// Sign out from NextAuth
await signOut({
callbackUrl: '/signin?logout=true',
redirect: false
});
// Call Keycloak logout if we have ID token
const keycloakIssuer = process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER;
const idToken = session?.idToken;
if (keycloakIssuer && idToken) {
const keycloakLogoutUrl = new URL(
`${keycloakIssuer}/protocol/openid-connect/logout`
);
keycloakLogoutUrl.searchParams.append(
'post_logout_redirect_uri',
window.location.origin + '/signin?logout=true'
);
keycloakLogoutUrl.searchParams.append(
'id_token_hint',
idToken
);
// Add kc_action=LOGOUT to ensure SSO session is cleared
keycloakLogoutUrl.searchParams.append(
'kc_action',
'LOGOUT'
);
window.location.replace(keycloakLogoutUrl.toString());
} else {
// Fallback: redirect to signin
window.location.replace('/signin?logout=true');
}
} catch (error) {
console.error('Error handling iframe logout:', error);
window.location.replace('/signin?logout=true');
}
}
}
};
window.addEventListener('message', handleMessage);
return () => {
window.removeEventListener('message', handleMessage);
};
}, [isSignInPage, session]);
return (
<AuthCheck>
{!isSignInPage && isAuthenticated && <MainNav />}
<div
className={isSignInPage ? "min-h-screen" : "min-h-screen"}
style={
isSignInPage
? {} // No background style for signin page - let the page component handle it
: {
backgroundImage: `url('${currentBackground}')`,
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
backgroundAttachment: 'fixed',
cursor: 'pointer',
transition: 'background-image 0.5s ease-in-out'
}
}
onClick={!isSignInPage ? changeBackground : undefined}
>
<main>{children}</main>
</div>
{!isSignInPage && isAuthenticated && <Footer />}
<Toaster />
</AuthCheck>
);
}