diff --git a/server/api/auth/direct-login.post.ts b/server/api/auth/direct-login.post.ts index 460b429..eb0119c 100644 --- a/server/api/auth/direct-login.post.ts +++ b/server/api/auth/direct-login.post.ts @@ -307,19 +307,23 @@ export default defineEventHandler(async (event) => { sessionSize: JSON.stringify(sessionData).length }); - // Create session with appropriate expiration + // Create session with server-side storage const sessionManager = createSessionManager(); - const maxAge = !!rememberMe ? 60 * 60 * 24 * 30 : 60 * 60 * 24 * 7; // 30 days vs 7 days try { - // Create the encrypted session data - const sessionData_json = JSON.stringify(sessionData); - const encrypted = sessionManager.encrypt(sessionData_json); + // Create session and get cookie string + const cookieString = sessionManager.createSession(sessionData, !!rememberMe); - console.log(`๐Ÿช Setting session cookie (Remember Me: ${!!rememberMe}), size: ${encrypted.length} chars`); + // Parse the cookie string to get the session ID + const cookieParts = cookieString.split(';')[0].split('='); + const sessionId = cookieParts[1]; - // Use Nuxt's setCookie helper directly with the encrypted value - setCookie(event, 'monacousa-session', encrypted, { + console.log(`๐Ÿช Setting session cookie (Remember Me: ${!!rememberMe}), session ID: ${sessionId.substring(0, 8)}...`); + console.log(`๐Ÿ“ Cookie size: ${sessionId.length} chars (much smaller!)`); + + // Set the cookie using Nuxt's setCookie helper + const maxAge = !!rememberMe ? 60 * 60 * 24 * 30 : 60 * 60 * 24 * 7; // 30 days vs 7 days + setCookie(event, 'monacousa-session', sessionId, { httpOnly: true, secure: true, sameSite: 'none', diff --git a/server/api/auth/logout.post.ts b/server/api/auth/logout.post.ts index effdd1b..80421b8 100644 --- a/server/api/auth/logout.post.ts +++ b/server/api/auth/logout.post.ts @@ -1,8 +1,15 @@ export default defineEventHandler(async (event) => { const sessionManager = createSessionManager(); - const destroyCookie = sessionManager.destroySession(); + const cookieHeader = getHeader(event, 'cookie'); + + console.log('๐Ÿšช Logout requested'); + + // Clear the session from server-side store + const destroyCookie = sessionManager.destroySession(cookieHeader); setHeader(event, 'Set-Cookie', destroyCookie); + console.log('โœ… Logout successful'); + return { success: true }; }); diff --git a/server/utils/session.ts b/server/utils/session.ts index 441c345..d80d019 100644 --- a/server/utils/session.ts +++ b/server/utils/session.ts @@ -1,85 +1,125 @@ import { serialize, parse } from 'cookie'; -import { createCipheriv, createDecipheriv, randomBytes } from 'crypto'; +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(); + +// Cleanup expired sessions every 5 minutes +setInterval(() => { + const now = Date.now(); + for (const [sessionId, session] of sessionStore.entries()) { + if (now > session.expiresAt) { + sessionStore.delete(sessionId); + console.log('๐Ÿงน Cleaned up expired session:', sessionId.substring(0, 8) + '...'); + } + } +}, 5 * 60 * 1000); + export class SessionManager { - private encryptionKey: Buffer; private cookieName = 'monacousa-session'; - constructor(encryptionKey: string) { - this.encryptionKey = Buffer.from(encryptionKey, 'hex'); + constructor() { + // No encryption key needed since we're only storing session IDs } - 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; + private generateSessionId(): string { + return randomBytes(32).toString('hex'); } createSession(sessionData: SessionData, rememberMe: boolean = false): string { - const data = JSON.stringify(sessionData); - const encrypted = this.encrypt(data); - + 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); - console.log(`๐Ÿช Creating session cookie (Remember Me: ${rememberMe}) without explicit domain`); + // Store session data server-side + sessionStore.set(sessionId, { + data: sessionData, + expiresAt, + rememberMe + }); - return serialize(this.cookieName, encrypted, { + console.log(`๐Ÿช Creating session cookie (Remember Me: ${rememberMe}) with session ID: ${sessionId.substring(0, 8)}...`); + console.log(`๐Ÿ“Š Session store size: ${sessionStore.size} sessions`); + + return serialize(this.cookieName, sessionId, { httpOnly: true, - secure: process.env.NODE_ENV === 'production', - sameSite: 'lax', + secure: true, + sameSite: 'none', maxAge, 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); + if (!cookieHeader) { + console.log('โŒ No cookie header provided'); return null; } + + const cookies = parse(cookieHeader); + const sessionId = cookies[this.cookieName]; + + if (!sessionId) { + console.log('โŒ No session cookie found'); + return null; + } + + console.log(`๐Ÿ” Looking up session: ${sessionId.substring(0, 8)}...`); + + const sessionEntry = sessionStore.get(sessionId); + if (!sessionEntry) { + console.log('โŒ Session not found in store'); + return null; + } + + // Check if session is expired + if (Date.now() > sessionEntry.expiresAt) { + console.log('โŒ Session expired, removing from store'); + sessionStore.delete(sessionId); + return null; + } + + // Update last activity + sessionEntry.data.lastActivity = Date.now(); + + console.log('โœ… Session found and valid for user:', sessionEntry.data.user.email); + return sessionEntry.data; } - destroySession(): string { + 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: 'lax', + 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 { - const config = useRuntimeConfig(); - return new SessionManager(config.encryptionKey); + return new SessionManager(); }