2025-08-06 14:31:16 +02:00
|
|
|
import { serialize, parse } from 'cookie';
|
|
|
|
|
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
|
|
|
|
|
import type { SessionData } from '~/utils/types';
|
|
|
|
|
|
|
|
|
|
export class SessionManager {
|
|
|
|
|
private encryptionKey: Buffer;
|
|
|
|
|
private cookieName = 'monacousa-session';
|
|
|
|
|
|
|
|
|
|
constructor(encryptionKey: string) {
|
|
|
|
|
this.encryptionKey = Buffer.from(encryptionKey, 'hex');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private encrypt(data: string): string {
|
|
|
|
|
const iv = randomBytes(16);
|
|
|
|
|
const cipher = createCipheriv('aes-256-cbc', this.encryptionKey, iv);
|
|
|
|
|
let encrypted = cipher.update(data, 'utf8', 'hex');
|
|
|
|
|
encrypted += cipher.final('hex');
|
|
|
|
|
return iv.toString('hex') + ':' + encrypted;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private decrypt(encryptedData: string): string {
|
|
|
|
|
const [ivHex, encrypted] = encryptedData.split(':');
|
|
|
|
|
const iv = Buffer.from(ivHex, 'hex');
|
|
|
|
|
const decipher = createDecipheriv('aes-256-cbc', this.encryptionKey, iv);
|
|
|
|
|
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
|
|
|
decrypted += decipher.final('utf8');
|
|
|
|
|
return decrypted;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-07 03:43:25 +02:00
|
|
|
createSession(sessionData: SessionData, rememberMe: boolean = false): string {
|
2025-08-06 14:31:16 +02:00
|
|
|
const data = JSON.stringify(sessionData);
|
|
|
|
|
const encrypted = this.encrypt(data);
|
|
|
|
|
|
2025-08-07 03:17:25 +02:00
|
|
|
const cookieDomain = process.env.COOKIE_DOMAIN || undefined;
|
2025-08-07 03:43:25 +02:00
|
|
|
const maxAge = rememberMe ? 60 * 60 * 24 * 30 : 60 * 60 * 24 * 7; // 30 days vs 7 days
|
|
|
|
|
|
|
|
|
|
console.log(`🍪 Creating session cookie (Remember Me: ${rememberMe}) with domain:`, cookieDomain);
|
2025-08-07 03:17:25 +02:00
|
|
|
|
2025-08-06 14:31:16 +02:00
|
|
|
return serialize(this.cookieName, encrypted, {
|
|
|
|
|
httpOnly: true,
|
2025-08-07 03:17:25 +02:00
|
|
|
secure: process.env.NODE_ENV === 'production',
|
2025-08-06 14:31:16 +02:00
|
|
|
sameSite: 'lax',
|
2025-08-07 03:17:25 +02:00
|
|
|
domain: cookieDomain,
|
2025-08-07 03:43:25 +02:00
|
|
|
maxAge,
|
2025-08-06 14:31:16 +02:00
|
|
|
path: '/',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getSession(cookieHeader?: string): SessionData | null {
|
|
|
|
|
if (!cookieHeader) return null;
|
|
|
|
|
|
|
|
|
|
const cookies = parse(cookieHeader);
|
|
|
|
|
const sessionCookie = cookies[this.cookieName];
|
|
|
|
|
|
|
|
|
|
if (!sessionCookie) return null;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const decrypted = this.decrypt(sessionCookie);
|
|
|
|
|
const sessionData = JSON.parse(decrypted) as SessionData;
|
|
|
|
|
|
|
|
|
|
// Check if session is expired
|
|
|
|
|
if (Date.now() > sessionData.tokens.expiresAt) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return sessionData;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Failed to decrypt session:', error);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
destroySession(): string {
|
|
|
|
|
return serialize(this.cookieName, '', {
|
|
|
|
|
httpOnly: true,
|
|
|
|
|
secure: true,
|
|
|
|
|
sameSite: 'lax',
|
|
|
|
|
maxAge: 0,
|
|
|
|
|
path: '/',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function createSessionManager(): SessionManager {
|
|
|
|
|
const config = useRuntimeConfig();
|
|
|
|
|
return new SessionManager(config.encryptionKey);
|
|
|
|
|
}
|