223 lines
9.2 KiB
TypeScript
223 lines
9.2 KiB
TypeScript
"use client";
|
|
|
|
import { signIn, useSession } from "next-auth/react";
|
|
import { useEffect, useState, useRef } from "react";
|
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
|
|
export default function SignIn() {
|
|
const { data: session, status } = useSession();
|
|
const router = useRouter();
|
|
const searchParams = useSearchParams();
|
|
const [initializationStatus, setInitializationStatus] = useState<string | null>(null);
|
|
const hasAttemptedLogin = useRef(false);
|
|
const isLogoutRedirect = useRef(false);
|
|
|
|
// Check if this is a logout redirect (from Keycloak post_logout_redirect_uri)
|
|
// OR if session was invalidated (e.g., from iframe logout)
|
|
useEffect(() => {
|
|
// Check URL parameters or session storage for logout flag
|
|
const logoutParam = searchParams.get('logout');
|
|
const fromLogout = sessionStorage.getItem('just_logged_out');
|
|
const sessionInvalidated = sessionStorage.getItem('session_invalidated');
|
|
|
|
// Check if there's a NextAuth session cookie that's now invalid
|
|
// This indicates the session was invalidated (e.g., by iframe logout)
|
|
const hasInvalidSessionCookie = document.cookie
|
|
.split(';')
|
|
.some(c => c.trim().startsWith('next-auth.session-token=') ||
|
|
c.trim().startsWith('__Secure-next-auth.session-token=') ||
|
|
c.trim().startsWith('__Host-next-auth.session-token='));
|
|
|
|
// If session was invalidated or this is a logout redirect, prevent auto-login
|
|
if (logoutParam === 'true' || fromLogout === 'true' || sessionInvalidated === 'true') {
|
|
isLogoutRedirect.current = true;
|
|
sessionStorage.removeItem('just_logged_out');
|
|
sessionStorage.removeItem('session_invalidated');
|
|
|
|
// Clear any OAuth parameters from URL to prevent callback processing
|
|
const url = new URL(window.location.href);
|
|
const hasOAuthParams = url.searchParams.has('code') ||
|
|
url.searchParams.has('state') ||
|
|
url.searchParams.has('error');
|
|
|
|
if (hasOAuthParams) {
|
|
// Remove OAuth parameters but keep logout=true
|
|
url.searchParams.delete('code');
|
|
url.searchParams.delete('state');
|
|
url.searchParams.delete('error');
|
|
url.searchParams.delete('error_description');
|
|
url.searchParams.set('logout', 'true');
|
|
// Replace URL without OAuth params
|
|
window.history.replaceState({}, '', url.toString());
|
|
}
|
|
|
|
// Don't auto-trigger login after logout
|
|
return;
|
|
}
|
|
|
|
// Detect session invalidation: if status becomes unauthenticated
|
|
// and we had a session cookie, it means the session was invalidated
|
|
if (status === 'unauthenticated' && hasInvalidSessionCookie && !hasAttemptedLogin.current) {
|
|
console.log('Session invalidation detected (likely from iframe logout), preventing auto-login');
|
|
sessionStorage.setItem('session_invalidated', 'true');
|
|
isLogoutRedirect.current = true;
|
|
// Don't auto-login - user must manually click "Se connecter"
|
|
return;
|
|
}
|
|
}, [searchParams, status]);
|
|
|
|
useEffect(() => {
|
|
// If user is already authenticated, redirect to home
|
|
if (status === "authenticated" && session?.user) {
|
|
router.push("/");
|
|
return;
|
|
}
|
|
|
|
// Don't auto-login if this is a logout redirect, session was invalidated, or we've already attempted login
|
|
const sessionInvalidated = sessionStorage.getItem('session_invalidated') === 'true';
|
|
if (isLogoutRedirect.current || sessionInvalidated || hasAttemptedLogin.current) {
|
|
return;
|
|
}
|
|
|
|
// Don't auto-login if status is still loading (might be processing OAuth callback)
|
|
if (status === "loading") {
|
|
return;
|
|
}
|
|
|
|
// Only trigger Keycloak sign-in if not authenticated, not loading, and not from logout
|
|
// AND only if this is a truly new user (never logged in), not a session invalidation
|
|
if (status === "unauthenticated") {
|
|
// Check if there's evidence of a previous session (session cookie exists but invalid)
|
|
// This indicates session was invalidated, not a new user
|
|
const hasSessionCookie = document.cookie
|
|
.split(';')
|
|
.some(c => {
|
|
const cookie = c.trim();
|
|
return cookie.startsWith('next-auth.session-token=') ||
|
|
cookie.startsWith('__Secure-next-auth.session-token=') ||
|
|
cookie.startsWith('__Host-next-auth.session-token=');
|
|
});
|
|
|
|
// If there's a session cookie but status is unauthenticated, session was invalidated
|
|
// Don't auto-login in this case
|
|
if (hasSessionCookie) {
|
|
console.log('Session cookie detected but status is unauthenticated - session was invalidated, preventing auto-login');
|
|
sessionStorage.setItem('session_invalidated', 'true');
|
|
isLogoutRedirect.current = true;
|
|
return;
|
|
}
|
|
|
|
// Only auto-login for truly new users (no session cookie)
|
|
hasAttemptedLogin.current = true;
|
|
// Longer delay to ensure we're not in a logout redirect flow or OAuth callback
|
|
const timer = setTimeout(() => {
|
|
// Double-check we're still unauthenticated and not in a logout flow
|
|
const stillInvalidated = sessionStorage.getItem('session_invalidated') === 'true';
|
|
if (!isLogoutRedirect.current && !stillInvalidated) {
|
|
// Trigger Keycloak sign-in only for new users
|
|
signIn("keycloak", { callbackUrl: "/" });
|
|
}
|
|
}, 1000);
|
|
|
|
return () => clearTimeout(timer);
|
|
}
|
|
}, [status, session, router]);
|
|
|
|
useEffect(() => {
|
|
if (session?.user) {
|
|
console.log("Session available, initializing storage");
|
|
|
|
// Initialize storage using direct API
|
|
const initializeStorage = async () => {
|
|
try {
|
|
setInitializationStatus("initializing");
|
|
const response = await fetch('/api/storage/init', {
|
|
method: 'POST'
|
|
});
|
|
|
|
if (!response.ok) {
|
|
console.error('Failed to initialize storage:', await response.text());
|
|
setInitializationStatus("failed");
|
|
} else {
|
|
console.log('Storage initialized successfully');
|
|
setInitializationStatus("success");
|
|
|
|
// Force reload to ensure session is updated
|
|
setTimeout(() => {
|
|
window.location.href = "/";
|
|
}, 1000);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error initializing storage:', error);
|
|
setInitializationStatus("failed");
|
|
}
|
|
};
|
|
|
|
initializeStorage();
|
|
}
|
|
}, [session]);
|
|
|
|
// Show logout message if coming from logout or session was invalidated
|
|
const sessionInvalidated = sessionStorage.getItem('session_invalidated') === 'true';
|
|
const showLogoutMessage = isLogoutRedirect.current ||
|
|
searchParams.get('logout') === 'true' ||
|
|
sessionInvalidated;
|
|
|
|
return (
|
|
<div
|
|
className="min-h-screen flex items-center justify-center"
|
|
style={{
|
|
backgroundImage: "url('/SignIn.jpg')",
|
|
backgroundSize: 'cover',
|
|
backgroundPosition: 'center',
|
|
backgroundRepeat: 'no-repeat'
|
|
}}
|
|
>
|
|
<div className="w-full max-w-md space-y-8 bg-white/90 backdrop-blur-sm p-8 rounded-xl shadow-xl">
|
|
<div>
|
|
<h2 className="mt-6 text-center text-3xl font-bold tracking-tight text-gray-900">
|
|
{showLogoutMessage
|
|
? (sessionInvalidated
|
|
? "Votre session a expiré. Veuillez vous reconnecter."
|
|
: "Vous avez été déconnecté avec succès")
|
|
: initializationStatus === "initializing"
|
|
? "Initialisation de votre espace..."
|
|
: initializationStatus === "success"
|
|
? "Initialisation réussie, redirection..."
|
|
: initializationStatus === "failed"
|
|
? "Échec de l'initialisation. Veuillez réessayer."
|
|
: "Redirection vers la page de connexion..."}
|
|
</h2>
|
|
{showLogoutMessage && (
|
|
<div className="mt-4 text-center">
|
|
<button
|
|
onClick={() => {
|
|
// Clear all logout/invalidation flags before logging in
|
|
hasAttemptedLogin.current = false;
|
|
isLogoutRedirect.current = false;
|
|
sessionStorage.removeItem('just_logged_out');
|
|
sessionStorage.removeItem('session_invalidated');
|
|
// Force login prompt by adding prompt=login parameter
|
|
// This ensures credentials are asked even if SSO session exists
|
|
signIn("keycloak", {
|
|
callbackUrl: "/",
|
|
// Note: prompt=login is already set in authOptions, but we ensure it here too
|
|
});
|
|
}}
|
|
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
|
>
|
|
Se connecter
|
|
</button>
|
|
</div>
|
|
)}
|
|
{initializationStatus === "initializing" && (
|
|
<div className="flex justify-center mt-4">
|
|
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|