193 lines
5.3 KiB
TypeScript
193 lines
5.3 KiB
TypeScript
import { serialize, parse } from 'cookie';
|
|
import { IncomingMessage } from 'http';
|
|
import { NextApiRequestCookies } from 'next/dist/server/api-utils';
|
|
|
|
export interface CookieOptions {
|
|
maxAge?: number;
|
|
expires?: Date;
|
|
path?: string;
|
|
domain?: string;
|
|
secure?: boolean;
|
|
httpOnly?: boolean;
|
|
sameSite?: 'strict' | 'lax' | 'none';
|
|
}
|
|
|
|
// Default cookie options for auth-related cookies
|
|
export const DEFAULT_AUTH_COOKIE_OPTIONS: CookieOptions = {
|
|
maxAge: 30 * 24 * 60 * 60, // 30 days
|
|
path: '/',
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === 'production',
|
|
sameSite: 'lax'
|
|
};
|
|
|
|
// Cookie names
|
|
export const COOKIE_NAMES = {
|
|
// NextAuth cookies
|
|
AUTH_TOKEN: 'next-auth.session-token',
|
|
AUTH_CSRF_TOKEN: 'next-auth.csrf-token',
|
|
AUTH_CALLBACK_URL: 'next-auth.callback-url',
|
|
AUTH_PKCE_CODE_CHALLENGE: 'next-auth.pkce.code_challenge',
|
|
|
|
// Keycloak cookies
|
|
KEYCLOAK_SESSION: 'KEYCLOAK_SESSION',
|
|
KEYCLOAK_IDENTITY: 'KEYCLOAK_IDENTITY',
|
|
KEYCLOAK_REMEMBER_ME: 'KEYCLOAK_REMEMBER_ME',
|
|
KC_RESTART: 'KC_RESTART',
|
|
|
|
// Custom cookies
|
|
USER_PREFERENCES: 'user-preferences',
|
|
THEME: 'theme',
|
|
|
|
// Function to create a namespaced cookie name
|
|
namespaced: (name: string) => `neah-front9.${name}`
|
|
};
|
|
|
|
/**
|
|
* Set a cookie with the specified options
|
|
*/
|
|
export function setCookie(
|
|
name: string,
|
|
value: string,
|
|
options: CookieOptions = {}
|
|
): string {
|
|
// Merge with default options
|
|
const cookieOptions = {
|
|
...DEFAULT_AUTH_COOKIE_OPTIONS,
|
|
...options
|
|
};
|
|
|
|
// For security, ensure secure flag is set if sameSite is 'none'
|
|
if (cookieOptions.sameSite === 'none' && cookieOptions.secure === undefined) {
|
|
cookieOptions.secure = true;
|
|
}
|
|
|
|
// Create the cookie string
|
|
return serialize(name, value, cookieOptions as any);
|
|
}
|
|
|
|
/**
|
|
* Get all cookies from the request
|
|
*/
|
|
export function getCookies(req: {
|
|
headers: { cookie?: string };
|
|
}): Record<string, string> {
|
|
const cookie = req.headers?.cookie;
|
|
return parse(cookie || '');
|
|
}
|
|
|
|
/**
|
|
* Get a specific cookie value
|
|
*/
|
|
export function getCookie(
|
|
req: { headers: { cookie?: string } },
|
|
name: string
|
|
): string | undefined {
|
|
const cookies = getCookies(req);
|
|
return cookies[name];
|
|
}
|
|
|
|
/**
|
|
* Delete a cookie by setting its expiration to the past
|
|
*/
|
|
export function deleteCookie(
|
|
name: string,
|
|
options: CookieOptions = {}
|
|
): string {
|
|
return setCookie(name, '', {
|
|
...options,
|
|
maxAge: 0,
|
|
expires: new Date(0)
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper to generate SetCookie headers for multiple cookies
|
|
*/
|
|
export function createCookieHeaders(cookieStrings: string[]): [string, string][] {
|
|
return cookieStrings.map(cookie => ['Set-Cookie', cookie]);
|
|
}
|
|
|
|
/**
|
|
* Clear all auth-related cookies
|
|
*/
|
|
export function getAuthCookieClearingHeaders(): [string, string][] {
|
|
const authCookies = [
|
|
COOKIE_NAMES.AUTH_TOKEN,
|
|
COOKIE_NAMES.AUTH_CSRF_TOKEN,
|
|
COOKIE_NAMES.AUTH_CALLBACK_URL,
|
|
COOKIE_NAMES.AUTH_PKCE_CODE_CHALLENGE,
|
|
COOKIE_NAMES.KEYCLOAK_SESSION,
|
|
COOKIE_NAMES.KEYCLOAK_IDENTITY,
|
|
COOKIE_NAMES.KEYCLOAK_REMEMBER_ME,
|
|
COOKIE_NAMES.KC_RESTART,
|
|
// Also clear secure variants
|
|
`__Secure-${COOKIE_NAMES.AUTH_TOKEN}`,
|
|
`__Host-${COOKIE_NAMES.AUTH_TOKEN}`
|
|
];
|
|
|
|
// Create clearing headers for root path
|
|
const cookieHeaders = authCookies.flatMap(name => {
|
|
return [
|
|
deleteCookie(name, { path: '/' }),
|
|
deleteCookie(name, { path: '/auth' }),
|
|
deleteCookie(name, { path: '/api' })
|
|
];
|
|
});
|
|
|
|
return createCookieHeaders(cookieHeaders);
|
|
}
|
|
|
|
/**
|
|
* Client-side function to clear all auth cookies
|
|
*/
|
|
export function clearAuthCookiesClient(): void {
|
|
const authCookiePrefixes = [
|
|
'next-auth.',
|
|
'__Secure-next-auth.',
|
|
'__Host-next-auth.',
|
|
'KEYCLOAK_',
|
|
'KC_'
|
|
];
|
|
|
|
const specificCookies = [
|
|
COOKIE_NAMES.KEYCLOAK_SESSION,
|
|
COOKIE_NAMES.KEYCLOAK_IDENTITY,
|
|
COOKIE_NAMES.KEYCLOAK_REMEMBER_ME,
|
|
COOKIE_NAMES.KC_RESTART
|
|
];
|
|
|
|
const cookies = document.cookie.split(';');
|
|
|
|
for (const cookie of cookies) {
|
|
const [name] = cookie.split('=');
|
|
const trimmedName = name.trim();
|
|
|
|
const isAuthCookie =
|
|
authCookiePrefixes.some(prefix => trimmedName.startsWith(prefix)) ||
|
|
specificCookies.includes(trimmedName);
|
|
|
|
if (isAuthCookie) {
|
|
// Clear cookie for different paths and domains
|
|
const paths = ['/', '/auth', '/api'];
|
|
|
|
for (const path of paths) {
|
|
// Basic deletion
|
|
document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path};`;
|
|
|
|
// With Secure and SameSite
|
|
document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; SameSite=None; Secure;`;
|
|
|
|
// Try with domain
|
|
const domain = window.location.hostname;
|
|
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;`;
|
|
|
|
// Try with root domain
|
|
const rootDomain = `.${domain.split('.').slice(-2).join('.')}`;
|
|
document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; domain=${rootDomain};`;
|
|
document.cookie = `${trimmedName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; domain=${rootDomain}; SameSite=None; Secure;`;
|
|
}
|
|
}
|
|
}
|
|
}
|