diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts index 30e0081b..eb742de6 100644 --- a/app/api/auth/[...nextauth]/route.ts +++ b/app/api/auth/[...nextauth]/route.ts @@ -141,8 +141,21 @@ export const authOptions: NextAuthOptions = { strategy: "jwt", maxAge: 30 * 24 * 60 * 60, // 30 days }, + cookies: { + sessionToken: { + name: `next-auth.session-token`, + options: { + httpOnly: true, + sameSite: 'none', + path: '/', + secure: true, + domain: process.env.NEXTAUTH_COOKIE_DOMAIN || undefined, + }, + }, + }, callbacks: { async jwt({ token, account, profile }) { + // Only include essential data in the JWT to reduce size if (account && profile) { const keycloakProfile = profile as KeycloakProfile; const roles = keycloakProfile.realm_access?.roles || []; @@ -177,7 +190,7 @@ export const authOptions: NextAuthOptions = { return token; } - return refreshAccessToken(token); + return refreshAccessToken(token as JWT); }, async session({ session, token }) { if (token.error) { @@ -185,6 +198,8 @@ export const authOptions: NextAuthOptions = { } const userRoles = Array.isArray(token.role) ? token.role : []; + + // Only include essential user data session.user = { id: token.sub ?? '', email: token.email ?? null, @@ -196,6 +211,8 @@ export const authOptions: NextAuthOptions = { role: userRoles, nextcloudInitialized: false, }; + + // Only pass the access token, not the entire token session.accessToken = token.accessToken; return session; diff --git a/app/components/responsive-iframe.tsx b/app/components/responsive-iframe.tsx index 70bf24b5..d1b128d1 100644 --- a/app/components/responsive-iframe.tsx +++ b/app/components/responsive-iframe.tsx @@ -41,6 +41,21 @@ export function ResponsiveIframe({ src, className = '', allow, style, token }: R iframe.src = iframeURL.toString(); } }; + + // Handle authentication messages from iframe + const handleMessage = (event: MessageEvent) => { + // Only accept messages from our iframe + if (iframe.contentWindow !== event.source) return; + + const { type, data } = event.data || {}; + + // Handle auth related messages + if (type === 'AUTH_ERROR' || type === 'SESSION_EXPIRED') { + console.log('Auth error in iframe:', data); + // Optionally redirect to login page + // window.location.href = '/signin'; + } + }; // Initial setup calculateHeight(); @@ -49,12 +64,14 @@ export function ResponsiveIframe({ src, className = '', allow, style, token }: R // Event listeners window.addEventListener('resize', calculateHeight); window.addEventListener('hashchange', handleHashChange); + window.addEventListener('message', handleMessage); iframe.addEventListener('load', calculateHeight); // Cleanup return () => { window.removeEventListener('resize', calculateHeight); window.removeEventListener('hashchange', handleHashChange); + window.removeEventListener('message', handleMessage); iframe.removeEventListener('load', calculateHeight); }; }, []); diff --git a/app/loggedout/page.tsx b/app/loggedout/page.tsx index a6aa2ad4..b6a8b4c9 100644 --- a/app/loggedout/page.tsx +++ b/app/loggedout/page.tsx @@ -7,6 +7,19 @@ import Link from "next/link"; export default function LoggedOut() { const [sessionStatus, setSessionStatus] = useState<'checking' | 'cleared' | 'error'>('checking'); + // Listen for any messages from iframes + useEffect(() => { + const messageHandler = (event: MessageEvent) => { + // Handle any auth-related messages from iframes + if (event.data && event.data.type === 'AUTH_ERROR') { + console.log('Received auth error from iframe:', event.data); + } + }; + + window.addEventListener('message', messageHandler); + return () => window.removeEventListener('message', messageHandler); + }, []); + // Clear auth cookies again on this page as an extra precaution useEffect(() => { const checkAndClearSessions = async () => { @@ -44,11 +57,36 @@ export default function LoggedOut() { console.error('Error clearing localStorage:', e); } - // Double check for Keycloak specific cookies - const keycloakCookies = ['KEYCLOAK_SESSION', 'KEYCLOAK_IDENTITY', 'KC_RESTART']; + // Double check for Keycloak specific cookies and chunked cookies + const cookies = document.cookie.split(';'); + const cookieNames = cookies.map(cookie => cookie.split('=')[0].trim()); + + // Look for chunked cookies + const chunkedCookies = cookieNames.filter(name => /\.\d+$/.test(name)); + + const keycloakCookies = [ + 'KEYCLOAK_SESSION', + 'KEYCLOAK_IDENTITY', + 'KC_RESTART', + ...chunkedCookies + ]; + for (const cookieName of keycloakCookies) { document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${window.location.hostname}; SameSite=None; Secure;`; document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; + + // Also try with root domain + const rootDomain = window.location.hostname.split('.').slice(-2).join('.'); + document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=.${rootDomain}; SameSite=None; Secure;`; + } + + // Notify any parent windows/iframes + try { + if (window.parent && window.parent !== window) { + window.parent.postMessage({ type: 'SESSION_CLEARED' }, '*'); + } + } catch (e) { + console.error('Error notifying parent window:', e); } setSessionStatus('cleared'); @@ -98,7 +136,7 @@ export default function LoggedOut() {
- Click below to sign in -
-Redirecting to login... - +
+ )} + + {showManualLoginButton ? ( + + ) : ( +