import { Session } from "next-auth"; import { JWT } from "next-auth/jwt"; import { DefaultSession } from "next-auth"; export interface ServiceToken { token: string; userId: string; expiresAt: number; } export interface ExtendedSession extends DefaultSession { user: { id: string; name?: string | null; email?: string | null; image?: string | null; username: string; first_name: string; last_name: string; role: string[]; }; accessToken: string; refreshToken?: string; serviceTokens: { rocketChat?: ServiceToken; leantime?: ServiceToken; calendar?: ServiceToken; mail?: ServiceToken; [key: string]: ServiceToken | undefined; }; expires: string; } export interface ExtendedJWT extends JWT { accessToken?: string; refreshToken?: string; accessTokenExpires?: number; role?: string[]; username?: string; first_name?: string; last_name?: string; name?: string | null; email?: string | null; serviceTokens: { rocketChat?: ServiceToken; leantime?: ServiceToken; calendar?: ServiceToken; mail?: ServiceToken; [key: string]: ServiceToken | undefined; }; } export async function invalidateServiceTokens(session: ExtendedSession) { const serviceEndpoints = { rocketChat: `${process.env.NEXT_PUBLIC_IFRAME_PAROLE_URL?.split('/channel')[0]}/api/v1/logout`, leantime: `${process.env.LEANTIME_API_URL}/api/jsonrpc`, // Add other service endpoints as needed }; const invalidatePromises = Object.entries(session.serviceTokens).map(async ([service, token]) => { if (!token) return; try { const endpoint = serviceEndpoints[service as keyof typeof serviceEndpoints]; if (!endpoint) return; await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(service === 'rocketChat' ? { 'X-Auth-Token': token.token, 'X-User-Id': token.userId, } : {}), ...(service === 'leantime' ? { 'X-API-Key': process.env.LEANTIME_TOKEN!, } : {}), }, body: service === 'leantime' ? JSON.stringify({ jsonrpc: '2.0', method: 'leantime.rpc.auth.logout', id: 1 }) : undefined, }); } catch (error) { console.error(`Error invalidating ${service} token:`, error); } }); await Promise.all(invalidatePromises); } export function clearAuthCookies() { const cookies = document.cookie.split(';'); console.log('Clearing all auth cookies'); // List of known auth-related cookie prefixes and specific cookies const authCookiePrefixes = [ 'next-auth.', '__Secure-next-auth.', '__Host-next-auth.', 'KEYCLOAK_', 'KC_', 'JSESSIONID', 'OAuth_Token_Request_State', 'OAUTH2_CLIENT_ID', 'OAUTH2_STATE', 'XSRF-TOKEN', 'AUTH_SESSION_', 'identity', 'session', 'connect.sid' ]; // Specific Keycloak cookies that need to be cleared const specificCookies = [ 'KEYCLOAK_SESSION', 'KEYCLOAK_IDENTITY', 'KEYCLOAK_REMEMBER_ME', 'KC_RESTART', 'KEYCLOAK_SESSION_LEGACY', 'KEYCLOAK_IDENTITY_LEGACY' ]; console.log(`Processing ${cookies.length} cookies`); for (const cookie of cookies) { const [name] = cookie.split('='); const trimmedName = name.trim(); // Check if this is an auth-related cookie const isAuthCookie = authCookiePrefixes.some(prefix => trimmedName.startsWith(prefix) ) || specificCookies.includes(trimmedName); // Also clear cookies with auth-related terms const containsAuthTerm = trimmedName.toLowerCase().includes('auth') || trimmedName.toLowerCase().includes('token') || trimmedName.toLowerCase().includes('session') || trimmedName.toLowerCase().includes('login') || trimmedName.toLowerCase().includes('id'); if (isAuthCookie || containsAuthTerm) { console.log(`Clearing cookie: ${trimmedName}`); // Try different combinations to ensure the cookie is cleared const paths = ['/', '/auth', '/realms', '/admin']; const domains = [ window.location.hostname, // Exact domain `.${window.location.hostname}`, // Domain with leading dot window.location.hostname.split('.').slice(-2).join('.') // Root domain ]; // Try each combination of path and domain for (const path of paths) { // Try without domain document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path};`; // Try with SameSite and Secure attributes document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; SameSite=None; Secure;`; // Try with different domains for (const domain of domains) { document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; domain=${domain};`; document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; domain=${domain}; SameSite=None; Secure;`; } } } } // Clear localStorage items that might be related to authentication try { const authLocalStoragePrefixes = ['token', 'auth', 'session', 'keycloak', 'kc', 'user', 'oidc', 'login']; console.log(`Checking localStorage (${localStorage.length} items)`); 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); } // Also try to clear sessionStorage try { console.log('Clearing sessionStorage'); sessionStorage.clear(); } catch (e) { console.error('Error clearing sessionStorage:', e); } // Check for any IndexedDB databases related to auth try { if (window.indexedDB) { window.indexedDB.databases().then(databases => { databases.forEach(db => { if (db.name && (db.name.includes('auth') || db.name.includes('keycloak') || db.name.includes('token'))) { console.log(`Deleting IndexedDB database: ${db.name}`); window.indexedDB.deleteDatabase(db.name); } }); }).catch(err => { console.error('Error accessing IndexedDB databases:', err); }); } } catch (e) { console.error('Error clearing IndexedDB:', e); } }