From d548ce4199d29b0b1eb712a03128c2f85ebc3610 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com> Date: Thu, 16 Apr 2026 22:18:52 +0200 Subject: [PATCH] fix(pitch-deck): refresh expired JWT from live DB session on cookie read When jwtVerify fails (JWT expired), decode the token without expiry check to recover sessionId, validate it against the DB, and reissue a fresh 24h JWT. Fixes investors with old 1h JWTs being locked out on magic link re-click. Co-Authored-By: Claude Sonnet 4.6 --- pitch-deck/lib/auth.ts | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/pitch-deck/lib/auth.ts b/pitch-deck/lib/auth.ts index 79e0edb..97560cb 100644 --- a/pitch-deck/lib/auth.ts +++ b/pitch-deck/lib/auth.ts @@ -1,4 +1,4 @@ -import { SignJWT, jwtVerify } from 'jose' +import { SignJWT, jwtVerify, decodeJwt } from 'jose' import { randomBytes, createHash } from 'crypto' import { cookies } from 'next/headers' import pool from './db' @@ -125,7 +125,34 @@ export async function getSessionFromCookie(): Promise { const cookieStore = await cookies() const token = cookieStore.get(COOKIE_NAME)?.value if (!token) return null - return verifyJwt(token) + + // Fast path: valid non-expired JWT + const payload = await verifyJwt(token) + if (payload) return payload + + // Slow path: JWT may be expired but DB session could still be valid. + // Decode without signature/expiry check to recover sessionId + sub. + try { + const decoded = decodeJwt(token) as Partial + if (!decoded.sessionId || !decoded.sub) return null + + const valid = await validateSession(decoded.sessionId, decoded.sub) + if (!valid) return null + + // DB session still live — fetch email and reissue a fresh JWT + const { rows } = await pool.query( + `SELECT email FROM pitch_investors WHERE id = $1`, + [decoded.sub] + ) + if (rows.length === 0) return null + + const freshJwt = await createJwt({ sub: decoded.sub, email: rows[0].email, sessionId: decoded.sessionId }) + await setSessionCookie(freshJwt) + + return { sub: decoded.sub, email: rows[0].email, sessionId: decoded.sessionId } + } catch { + return null + } } export function getClientIp(request: Request): string | null {