|
1 | 1 | import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; |
2 | 2 | import { getLucia } from '../../lib/lucia'; |
| 3 | +import { getDb, getSchema } from '../../db'; |
| 4 | +import { eq } from 'drizzle-orm'; |
3 | 5 |
|
4 | 6 | export default async function logoutRoute(fastify: FastifyInstance) { |
5 | 7 | fastify.post( |
6 | 8 | '/logout', |
7 | 9 | async (request: FastifyRequest, reply: FastifyReply) => { |
8 | | - const sessionId = getLucia().readSessionCookie(request.headers.cookie ?? ''); |
| 10 | + // The global authHook should have already populated request.session if a valid session exists. |
| 11 | + // It also handles creating a blank session cookie if the session was invalid. |
| 12 | + const lucia = getLucia(); |
9 | 13 |
|
10 | | - if (!sessionId) { |
11 | | - // No session cookie, so user is effectively logged out or was never logged in. |
12 | | - // It's good practice to still clear any potential lingering cookie on the client. |
13 | | - const sessionCookie = getLucia().createBlankSessionCookie(); |
14 | | - reply.setCookie(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); |
| 14 | + // Log session information for debugging |
| 15 | + fastify.log.info(`Logout attempt - Session exists: ${!!request.session}, Session ID: ${request.session?.id || 'none'}`); |
| 16 | + |
| 17 | + if (!request.session) { |
| 18 | + // No active session found by authHook, but let's check if there's a session cookie |
| 19 | + // and manually clean it up if Lucia validation failed |
| 20 | + const sessionId = lucia.readSessionCookie(request.headers.cookie ?? ''); |
| 21 | + |
| 22 | + if (sessionId) { |
| 23 | + fastify.log.info(`Found session cookie ${sessionId} but authHook couldn't validate it - attempting manual cleanup`); |
| 24 | + |
| 25 | + try { |
| 26 | + // Try to manually delete the session from database |
| 27 | + const db = getDb(); |
| 28 | + const schema = getSchema(); |
| 29 | + const authSessionTable = schema.authSession; |
| 30 | + |
| 31 | + // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| 32 | + const result = await (db as any).delete(authSessionTable).where(eq(authSessionTable.id, sessionId)); |
| 33 | + fastify.log.info(`Manually deleted session ${sessionId} from database`); |
| 34 | + } catch (dbError) { |
| 35 | + fastify.log.error(dbError, 'Failed to manually delete session from database'); |
| 36 | + } |
| 37 | + } |
| 38 | + |
| 39 | + // Send a blank cookie to ensure client-side cookie is cleared |
| 40 | + const blankCookie = lucia.createBlankSessionCookie(); |
| 41 | + reply.setCookie(blankCookie.name, blankCookie.value, blankCookie.attributes); |
| 42 | + fastify.log.info('No active session to logout - sending blank cookie'); |
15 | 43 | return reply.status(200).send({ message: 'No active session to logout or already logged out.' }); |
16 | 44 | } |
17 | 45 |
|
18 | 46 | try { |
19 | | - const { session } = await getLucia().validateSession(sessionId); |
20 | | - |
21 | | - if (session) { |
22 | | - await getLucia().invalidateSession(session.id); |
23 | | - } |
| 47 | + const sessionId = request.session.id; |
| 48 | + fastify.log.info(`Attempting to invalidate session: ${sessionId}`); |
| 49 | + |
| 50 | + // Invalidate the session identified by authHook. |
| 51 | + await lucia.invalidateSession(sessionId); |
| 52 | + fastify.log.info(`Session ${sessionId} invalidated successfully`); |
24 | 53 |
|
25 | | - // Always send a blank cookie to ensure client-side cookie is cleared |
26 | | - const sessionCookie = getLucia().createBlankSessionCookie(); |
27 | | - reply.setCookie(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); |
| 54 | + // Send a blank cookie to ensure client-side cookie is cleared. |
| 55 | + const blankCookie = lucia.createBlankSessionCookie(); |
| 56 | + reply.setCookie(blankCookie.name, blankCookie.value, blankCookie.attributes); |
| 57 | + fastify.log.info('Blank cookie sent to clear client session'); |
28 | 58 |
|
29 | 59 | return reply.status(200).send({ message: 'Logged out successfully.' }); |
30 | 60 |
|
31 | 61 | } catch (error) { |
32 | | - fastify.log.error(error, 'Error during logout:'); |
33 | | - // Even if there's an error (e.g., session already invalid), try to clear the cookie. |
34 | | - const sessionCookie = getLucia().createBlankSessionCookie(); |
35 | | - reply.setCookie(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); |
36 | | - return reply.status(500).send({ error: 'An error occurred during logout.' }); |
| 62 | + fastify.log.error(error, 'Error during logout (invalidating session from authHook):'); |
| 63 | + |
| 64 | + // If Lucia invalidation failed, try manual database cleanup |
| 65 | + const sessionId = request.session.id; |
| 66 | + try { |
| 67 | + const db = getDb(); |
| 68 | + const schema = getSchema(); |
| 69 | + const authSessionTable = schema.authSession; |
| 70 | + |
| 71 | + // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| 72 | + await (db as any).delete(authSessionTable).where(eq(authSessionTable.id, sessionId)); |
| 73 | + fastify.log.info(`Manually deleted session ${sessionId} after Lucia invalidation failed`); |
| 74 | + } catch (dbError) { |
| 75 | + fastify.log.error(dbError, 'Failed to manually delete session after Lucia error'); |
| 76 | + } |
| 77 | + |
| 78 | + // Even if there's an error, try to clear the cookie. |
| 79 | + const blankCookie = lucia.createBlankSessionCookie(); |
| 80 | + reply.setCookie(blankCookie.name, blankCookie.value, blankCookie.attributes); |
| 81 | + return reply.status(200).send({ message: 'Logged out successfully (with fallback cleanup).' }); |
37 | 82 | } |
38 | 83 | } |
39 | 84 | ); |
|
0 commit comments