monacousa-portal/server/utils/session.ts

120 lines
2.9 KiB
TypeScript

import { serialize, parse } from 'cookie';
import { randomBytes } from 'crypto';
import type { SessionData } from '~/utils/types';
// In-memory session store (in production, you'd use Redis or a database)
const sessionStore = new Map<string, {
data: SessionData;
expiresAt: number;
rememberMe: boolean;
}>();
// Cleanup expired sessions every 5 minutes
setInterval(() => {
const now = Date.now();
let cleanedCount = 0;
for (const [sessionId, session] of sessionStore.entries()) {
if (now > session.expiresAt) {
sessionStore.delete(sessionId);
cleanedCount++;
}
}
if (cleanedCount > 0) {
console.log(`🧹 Cleaned up ${cleanedCount} expired sessions`);
}
}, 5 * 60 * 1000);
export class SessionManager {
private cookieName = 'monacousa-session';
constructor() {
// No encryption key needed since we're only storing session IDs
}
private generateSessionId(): string {
return randomBytes(32).toString('hex');
}
createSession(sessionData: SessionData, rememberMe: boolean = false): string {
const sessionId = this.generateSessionId();
const maxAge = rememberMe ? 60 * 60 * 24 * 30 : 60 * 60 * 24 * 7; // 30 days vs 7 days
const expiresAt = Date.now() + (maxAge * 1000);
// Store session data server-side
sessionStore.set(sessionId, {
data: sessionData,
expiresAt,
rememberMe
});
return serialize(this.cookieName, sessionId, {
httpOnly: true,
secure: true,
sameSite: 'none',
maxAge,
path: '/',
});
}
getSession(cookieHeader?: string): SessionData | null {
if (!cookieHeader) {
return null;
}
const cookies = parse(cookieHeader);
const sessionId = cookies[this.cookieName];
if (!sessionId) {
return null;
}
const sessionEntry = sessionStore.get(sessionId);
if (!sessionEntry) {
return null;
}
// Check if session is expired
if (Date.now() > sessionEntry.expiresAt) {
sessionStore.delete(sessionId);
return null;
}
// Update last activity
sessionEntry.data.lastActivity = Date.now();
return sessionEntry.data;
}
destroySession(cookieHeader?: string): string {
if (cookieHeader) {
const cookies = parse(cookieHeader);
const sessionId = cookies[this.cookieName];
if (sessionId && sessionStore.has(sessionId)) {
sessionStore.delete(sessionId);
console.log(`🗑️ Destroyed session: ${sessionId.substring(0, 8)}...`);
}
}
return serialize(this.cookieName, '', {
httpOnly: true,
secure: true,
sameSite: 'none',
maxAge: 0,
path: '/',
});
}
// Helper method to get session stats
getSessionStats() {
return {
totalSessions: sessionStore.size,
activeSessions: Array.from(sessionStore.values()).filter(s => Date.now() < s.expiresAt).length
};
}
}
export function createSessionManager(): SessionManager {
return new SessionManager();
}