diff --git a/src/routes/login/index.tsx b/src/routes/login/index.tsx index 89495cd..ebc9ea4 100644 --- a/src/routes/login/index.tsx +++ b/src/routes/login/index.tsx @@ -547,12 +547,7 @@ export default function LoginPage() {
- +
Remember Me
diff --git a/src/server/session-helpers.ts b/src/server/session-helpers.ts index f7dfcaf..60f08c4 100644 --- a/src/server/session-helpers.ts +++ b/src/server/session-helpers.ts @@ -204,6 +204,14 @@ export async function createAuthSession( rememberMe }; + console.log("[Session Create] Creating session with data:", { + userId, + sessionId, + isAdmin, + hasRefreshToken: !!refreshToken, + rememberMe + }); + // Update Vinxi session with dynamic maxAge based on rememberMe const configWithMaxAge = { ...sessionConfig, @@ -216,10 +224,17 @@ export async function createAuthSession( const session = await updateSession(event, configWithMaxAge, sessionData); + console.log("[Session Create] updateSession returned:", { + hasData: !!session?.data, + dataKeys: session?.data ? Object.keys(session.data) : [] + }); + // Explicitly seal/flush the session to ensure cookie is written // This is important in serverless environments where response might stream early const { sealSession } = await import("vinxi/http"); - sealSession(event, configWithMaxAge); + await sealSession(event, configWithMaxAge); + + console.log("[Session Create] Session sealed"); // Set a separate sessionId cookie for DB fallback (in case main session cookie fails) setCookie(event, "session_id", sessionId, { @@ -230,16 +245,31 @@ export async function createAuthSession( maxAge: configWithMaxAge.maxAge }); + console.log("[Session Create] session_id cookie set"); + // Verify session was actually set by reading it back try { const cookieName = sessionConfig.name || "session"; const cookieValue = getCookie(event, cookieName); + console.log( + "[Session Create] Verification - cookie name:", + cookieName, + "has value:", + !!cookieValue + ); + // Try reading back the session immediately using the same config const verifySession = await getSession( event, configWithMaxAge ); + + console.log("[Session Create] Verification - read session back:", { + hasData: !!verifySession?.data, + hasUserId: !!verifySession?.data?.userId, + hasSessionId: !!verifySession?.data?.sessionId + }); } catch (verifyError) { console.error("[Session Create] Failed to verify session:", verifyError); } @@ -288,18 +318,51 @@ export async function getAuthSession( const session = await unsealSession(event, sessionConfig, cookieValue); if (!session?.data || typeof session.data !== "object") { + console.log( + "[Session Get] skipUpdate: session data is empty/invalid" + ); + // Try DB restoration before giving up + const sessionIdCookie = getCookie(event, "session_id"); + if (sessionIdCookie) { + console.log( + "[Session Get] Attempting restore from DB (empty data)..." + ); + const restored = await restoreSessionFromDB(event, sessionIdCookie); + if (restored) { + console.log( + "[Session Get] Successfully restored session from DB" + ); + return restored; + } + } return null; } const data = session.data as SessionData; if (!data.userId || !data.sessionId) { + console.log( + "[Session Get] Session data missing userId or sessionId:", + { + hasUserId: !!data.userId, + hasSessionId: !!data.sessionId + } + ); + // Fallback: Try to restore from DB using session_id cookie const sessionIdCookie = getCookie(event, "session_id"); + console.log("[Session Get] session_id cookie:", sessionIdCookie); + if (sessionIdCookie) { + console.log("[Session Get] Attempting restore from DB..."); const restored = await restoreSessionFromDB(event, sessionIdCookie); if (restored) { + console.log( + "[Session Get] Successfully restored session from DB" + ); return restored; + } else { + console.log("[Session Get] Failed to restore session from DB"); } } @@ -315,7 +378,28 @@ export async function getAuthSession( return isValid ? data : null; } catch (err) { - console.error("[Session Get] Error in skipUpdate path:", err); + console.error( + "[Session Get] Error in skipUpdate path (likely decryption failure):", + err + ); + // If decryption failed (after server restart), try DB restoration + const sessionIdCookie = getCookie(event, "session_id"); + if (sessionIdCookie) { + console.log( + "[Session Get] Attempting restore from DB after decryption error..." + ); + const restored = await restoreSessionFromDB(event, sessionIdCookie); + if (restored) { + console.log( + "[Session Get] Successfully restored session from DB after error" + ); + return restored; + } else { + console.log( + "[Session Get] Failed to restore session from DB after error" + ); + } + } return null; } } @@ -324,13 +408,34 @@ export async function getAuthSession( const session = await getSession(event, sessionConfig); + console.log("[Session Get] Got session from Vinxi:", { + hasData: !!session.data, + hasUserId: !!session.data?.userId, + hasSessionId: !!session.data?.sessionId + }); + if (!session.data || !session.data.userId || !session.data.sessionId) { // Fallback: Try to restore from DB using session_id cookie const sessionIdCookie = getCookie(event, "session_id"); + console.log( + "[Session Get] Normal path - session_id cookie:", + sessionIdCookie + ); + if (sessionIdCookie) { + console.log( + "[Session Get] Attempting restore from DB (normal path)..." + ); const restored = await restoreSessionFromDB(event, sessionIdCookie); if (restored) { + console.log( + "[Session Get] Successfully restored session from DB (normal path)" + ); return restored; + } else { + console.log( + "[Session Get] Failed to restore session from DB (normal path)" + ); } } @@ -383,6 +488,7 @@ async function restoreSessionFromDB( sessionId: string ): Promise { try { + console.log("[Session Restore] Starting restore for sessionId:", sessionId); const conn = ConnectionFactory(); // Query DB for session with all necessary data including is_admin @@ -395,25 +501,40 @@ async function restoreSessionFromDB( args: [sessionId] }); + console.log( + "[Session Restore] DB query returned rows:", + result.rows.length + ); + if (result.rows.length === 0) { + console.log("[Session Restore] No session found in DB"); return null; } const dbSession = result.rows[0]; + console.log("[Session Restore] Found session:", { + userId: dbSession.user_id, + revoked: dbSession.revoked, + expiresAt: dbSession.expires_at + }); // Validate session is still valid if (dbSession.revoked === 1) { + console.log("[Session Restore] Session is revoked"); return null; } const expiresAt = new Date(dbSession.expires_at as string); if (expiresAt < new Date()) { + console.log("[Session Restore] Session is expired"); return null; } // We can't restore the refresh token (it's hashed in DB) // So we need to generate a new one and rotate the session + console.log("[Session Restore] Creating new rotated session..."); + // Get IP and user agent const { getRequestIP } = await import("vinxi/http"); const ipAddress = getRequestIP(event) || "unknown"; @@ -430,6 +551,7 @@ async function restoreSessionFromDB( dbSession.token_family as string // Reuse family ); + console.log("[Session Restore] Successfully created new session"); return newSession; } catch (error) { console.error("[Session Restore] Error restoring session:", error);