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); } /** * Clears all authentication-related cookies from the browser */ export function clearAuthCookies() { // List of common auth-related cookies const authCookies = [ 'next-auth.session-token', 'next-auth.callback-url', 'next-auth.csrf-token', '__Secure-next-auth.session-token', '__Host-next-auth.csrf-token', 'next-auth.pkce.code_verifier', 'KEYCLOAK_SESSION', 'KEYCLOAK_IDENTITY', 'KEYCLOAK_REMEMBER_ME', 'KC_RESTART', 'AUTH_SESSION_ID', 'AUTH_SESSION_ID_LEGACY', 'JSESSIONID' ]; // Get all cookies to check for chunked auth cookies const cookies = document.cookie.split(';'); // Try multiple path and domain combinations for thorough cleanup const paths = ['/', '/auth', '/realms', '/admin']; const domain = window.location.hostname; const domains = [ domain, `.${domain}`, domain.split('.').slice(-2).join('.'), `.${domain.split('.').slice(-2).join('.')}` ]; // Clear main auth cookies with all path/domain combinations authCookies.forEach(cookieName => { // Basic deletion document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; SameSite=None; Secure`; // Try more aggressive deletion with different paths and domains paths.forEach(path => { document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; SameSite=None; Secure`; domains.forEach(domainValue => { document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; domain=${domainValue}; SameSite=None; Secure`; }); }); }); // Check for and clear any chunked cookies and cookies with dynamic names cookies.forEach(cookie => { const cookieName = cookie.split('=')[0].trim(); // Clear chunked cookies (end with a number) and any auth-related cookies if (cookieName.startsWith('next-auth.') || cookieName.includes('keycloak') || cookieName.includes('auth') || cookieName.includes('session') || /\.\d+$/.test(cookieName)) { // Basic deletion document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; SameSite=None; Secure`; // Try more aggressive deletion with different paths and domains paths.forEach(path => { document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; SameSite=None; Secure`; domains.forEach(domainValue => { document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; domain=${domainValue}; SameSite=None; Secure`; }); }); } }); // Clear localStorage items related to authentication try { const authItems = [ 'Meteor.loginToken', 'Meteor.userId', 'token', 'refreshToken', 'userId', 'userName', 'userEmail' ]; authItems.forEach(item => { localStorage.removeItem(item); }); // Also look for any items with auth-related names for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key && (key.includes('token') || key.includes('auth') || key.includes('session'))) { localStorage.removeItem(key); } } } catch (e) { console.error('Failed to clear localStorage:', e); } // Clear all sessionStorage try { sessionStorage.clear(); } catch (e) { console.error('Failed to clear sessionStorage:', e); } }