NeahNew/lib/session.ts
2026-01-04 01:29:05 +01:00

154 lines
4.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(';');
for (const cookie of cookies) {
const [name] = cookie.split('=');
const cookieName = name.trim();
// Only clear session cookies, NOT state/CSRF cookies needed for OAuth flow
// State cookies are: next-auth.csrf-token, next-auth.state, etc.
// Session cookies are: next-auth.session-token, __Secure-next-auth.session-token, etc.
if (cookieName.startsWith('next-auth.session-token') ||
cookieName.startsWith('__Secure-next-auth.session-token') ||
cookieName.startsWith('__Host-next-auth.session-token')) {
document.cookie = `${cookieName}=; 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);
}
}