diff --git a/app/signin/page.tsx b/app/signin/page.tsx index 9a947eb9..d0564a92 100644 --- a/app/signin/page.tsx +++ b/app/signin/page.tsx @@ -1,15 +1,23 @@ "use client"; import { signIn, useSession } from "next-auth/react"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; +import { useSearchParams } from "next/navigation"; export default function SignIn() { const { data: session } = useSession(); + const searchParams = useSearchParams(); + const signedOut = searchParams.get('signedOut') === 'true'; + const [isRedirecting, setIsRedirecting] = useState(false); useEffect(() => { - // Trigger Keycloak sign-in - signIn("keycloak", { callbackUrl: "/" }); - }, []); + // Only automatically sign in if not explicitly signed out + if (!signedOut && !isRedirecting) { + setIsRedirecting(true); + // Trigger Keycloak sign-in + signIn("keycloak", { callbackUrl: "/" }); + } + }, [signedOut, isRedirecting]); useEffect(() => { if (session?.user && !session.user.nextcloudInitialized) { @@ -38,9 +46,28 @@ export default function SignIn() { >
-

- Redirecting to login... -

+ {signedOut ? ( + <> +

+ You have been signed out +

+

+ Click below to sign in again +

+
+ +
+ + ) : ( +

+ Redirecting to login... +

+ )}
diff --git a/components/auth/signout-handler.tsx b/components/auth/signout-handler.tsx index bd28645c..dd1ebce4 100644 --- a/components/auth/signout-handler.tsx +++ b/components/auth/signout-handler.tsx @@ -1,24 +1,58 @@ "use client"; import { useEffect } from "react"; -import { signOut } from "next-auth/react"; +import { signOut, useSession } from "next-auth/react"; import { clearAuthCookies } from "@/lib/session"; export function SignOutHandler() { + const { data: session } = useSession(); + useEffect(() => { const handleSignOut = async () => { - // Clear only auth-related cookies - clearAuthCookies(); - - // Then sign out from NextAuth - await signOut({ - callbackUrl: "/signin", - redirect: true - }); + try { + // Clear all auth-related cookies + clearAuthCookies(); + + // First sign out from NextAuth with redirect false + await signOut({ + redirect: false + }); + + // Then redirect to Keycloak logout with proper parameters + if (process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER) { + const keycloakLogoutUrl = new URL( + `${process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER}/protocol/openid-connect/logout` + ); + + // Add required parameters + keycloakLogoutUrl.searchParams.append( + 'post_logout_redirect_uri', + `${window.location.origin}/signin?signedOut=true` + ); + + // Add id_token_hint if available + if (session?.accessToken) { + keycloakLogoutUrl.searchParams.append( + 'id_token_hint', + session.accessToken + ); + } + + // Redirect to Keycloak logout + window.location.href = keycloakLogoutUrl.toString(); + } else { + // Fallback if no Keycloak issuer is configured + window.location.href = '/signin?signedOut=true'; + } + } catch (error) { + console.error('Error during logout:', error); + // Fallback if something goes wrong + window.location.href = '/signin?signedOut=true'; + } }; handleSignOut(); - }, []); + }, [session]); return null; } \ No newline at end of file diff --git a/lib/session.ts b/lib/session.ts index 8456e090..0bc8eeaa 100644 --- a/lib/session.ts +++ b/lib/session.ts @@ -92,11 +92,68 @@ export async function invalidateServiceTokens(session: ExtendedSession) { export function clearAuthCookies() { const cookies = document.cookie.split(';'); + console.log('Clearing all auth cookies'); + + // List of known auth-related cookie prefixes + const authCookiePrefixes = [ + 'next-auth.', + '__Secure-next-auth.', + '__Host-next-auth.', + 'KEYCLOAK_', + 'KC_', + 'JSESSIONID', + 'OAuth_Token_Request_State', + 'OAUTH2_CLIENT_ID', + 'OAUTH2_STATE', + 'XSRF-TOKEN' + ]; + for (const cookie of cookies) { const [name] = cookie.split('='); - // Only clear auth-related cookies - if (name.trim().startsWith('next-auth.') || name.trim().startsWith('__Secure-next-auth.') || name.trim().startsWith('__Host-next-auth.')) { - document.cookie = `${name.trim()}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; + const trimmedName = name.trim(); + + // Check if this is an auth-related cookie + const isAuthCookie = authCookiePrefixes.some(prefix => + trimmedName.startsWith(prefix) + ); + + // Also clear cookies with auth-related terms + const containsAuthTerm = + trimmedName.toLowerCase().includes('auth') || + trimmedName.toLowerCase().includes('token') || + trimmedName.toLowerCase().includes('session'); + + if (isAuthCookie || containsAuthTerm) { + console.log(`Clearing cookie: ${trimmedName}`); + + // Clear the cookie with various domain/path combinations + // Standard path + document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; + + // Root domain + const domain = window.location.hostname.split('.').slice(-2).join('.'); + document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${domain};`; + + // Full domain + document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${window.location.hostname};`; } } + + // Clear localStorage items that might be related to authentication + try { + const authLocalStoragePrefixes = ['token', 'auth', 'session', 'keycloak', 'kc', 'user']; + + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key) { + const keyLower = key.toLowerCase(); + if (authLocalStoragePrefixes.some(prefix => keyLower.includes(prefix))) { + console.log(`Clearing localStorage: ${key}`); + localStorage.removeItem(key); + } + } + } + } catch (e) { + console.error('Error clearing localStorage:', e); + } } \ No newline at end of file