fix(pitch-deck): refresh expired JWT from live DB session on cookie read
All checks were successful
Build pitch-deck / build-push-deploy (push) Successful in 1m13s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 37s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 34s
All checks were successful
Build pitch-deck / build-push-deploy (push) Successful in 1m13s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 37s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 34s
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 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { SignJWT, jwtVerify } from 'jose'
|
import { SignJWT, jwtVerify, decodeJwt } from 'jose'
|
||||||
import { randomBytes, createHash } from 'crypto'
|
import { randomBytes, createHash } from 'crypto'
|
||||||
import { cookies } from 'next/headers'
|
import { cookies } from 'next/headers'
|
||||||
import pool from './db'
|
import pool from './db'
|
||||||
@@ -125,7 +125,34 @@ export async function getSessionFromCookie(): Promise<JwtPayload | null> {
|
|||||||
const cookieStore = await cookies()
|
const cookieStore = await cookies()
|
||||||
const token = cookieStore.get(COOKIE_NAME)?.value
|
const token = cookieStore.get(COOKIE_NAME)?.value
|
||||||
if (!token) return null
|
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<JwtPayload>
|
||||||
|
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 {
|
export function getClientIp(request: Request): string | null {
|
||||||
|
|||||||
Reference in New Issue
Block a user