Neah/lib/session.ts
2025-05-02 11:08:02 +02:00

219 lines
6.6 KiB
TypeScript

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);
}
}