200 lines
5.8 KiB
TypeScript
200 lines
5.8 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);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
}
|