From 06872a1314f7d43a223ea94f0f84ad16b97c2844 Mon Sep 17 00:00:00 2001 From: ekzyis Date: Fri, 19 Sep 2025 17:55:34 +0200 Subject: [PATCH] Fix verification attempts not transactional (#2549) * Run verification token check in one transaction * Only fetch unexpired verification requests --- pages/api/auth/[...nextauth].js | 65 ++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/pages/api/auth/[...nextauth].js b/pages/api/auth/[...nextauth].js index 171c9a6b..8fcfb290 100644 --- a/pages/api/auth/[...nextauth].js +++ b/pages/api/auth/[...nextauth].js @@ -327,38 +327,43 @@ export const getAuthOptions = (req, res) => ({ return user }, useVerificationToken: async ({ identifier, token }) => { - // we need to find the most recent verification request for this email/identifier - const verificationRequest = await prisma.verificationToken.findFirst({ - where: { - identifier, - attempts: { - lt: 2 // count starts at 0 - } - }, - orderBy: { - createdAt: 'desc' + return await prisma.$transaction(async (tx) => { + const [verificationRequest] = await tx.$queryRaw` + UPDATE verification_requests + SET attempts = attempts + 1 + FROM ( + SELECT id FROM verification_requests + WHERE identifier = ${identifier} + AND created_at > NOW() - INTERVAL '5 minutes' + -- we need to find the most recent verification request for this email/identifier + ORDER BY created_at DESC + LIMIT 1 + FOR UPDATE + ) for_update + WHERE verification_requests.id = for_update.id + RETURNING * + ` + if (!verificationRequest) throw new Error('No verification request found') + + if (verificationRequest.token === token) { + // correct token was entered, delete the verification request because we no longer need it + await tx.verificationToken.delete({ + where: { id: verificationRequest.id } + }) + return verificationRequest } + + if (verificationRequest.attempts >= 3) { + // too many attempts, delete the verification request and redirect to error page by throwing an error + await tx.verificationToken.delete({ + where: { id: verificationRequest.id } + }) + throw new Error('too many attempts') + } + + // wrong code but can try again + return null }) - - if (!verificationRequest) throw new Error('No verification request found') - - if (verificationRequest.token === token) { // if correct delete the token and continue - await prisma.verificationToken.delete({ - where: { id: verificationRequest.id } - }) - return verificationRequest - } - - await prisma.verificationToken.update({ - where: { id: verificationRequest.id }, - data: { attempts: { increment: 1 } } - }) - - await prisma.verificationToken.deleteMany({ - where: { id: verificationRequest.id, attempts: { gte: 2 } } - }) - - return null } }, session: {