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(';'); 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=/;`; } } } /** * Clear Keycloak session cookies * Keycloak cookies are typically set on Keycloak's domain, so we need to * try clearing them with different domain/path combinations */ export function clearKeycloakCookies() { const keycloakIssuer = process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER; if (!keycloakIssuer) return; try { // Extract domain from Keycloak issuer const keycloakUrl = new URL(keycloakIssuer); const keycloakDomain = keycloakUrl.hostname; // Common Keycloak cookie names const keycloakCookieNames = [ 'KEYCLOAK_SESSION', 'KEYCLOAK_SESSION_LEGACY', 'KEYCLOAK_IDENTITY', 'KEYCLOAK_IDENTITY_LEGACY', 'AUTH_SESSION_ID', 'KC_RESTART', 'KC_RESTART_LEGACY' ]; // Try to clear cookies with different domain/path combinations const domains = [keycloakDomain, `.${keycloakDomain}`, window.location.hostname]; const paths = ['/', '/realms/', keycloakUrl.pathname]; keycloakCookieNames.forEach(cookieName => { domains.forEach(domain => { paths.forEach(path => { // Try with domain document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; domain=${domain};`; // Try without domain (for same-origin cookies) document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path};`; }); }); }); console.log('Attempted to clear Keycloak cookies'); } catch (error) { console.error('Error clearing Keycloak cookies:', error); } }