Fix verification attempts not transactional (#2549)

* Run verification token check in one transaction

* Only fetch unexpired verification requests
This commit is contained in:
ekzyis 2025-09-19 17:55:34 +02:00 committed by GitHub
parent 919e71de79
commit 06872a1314
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -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) { // if correct delete the token and continue
await prisma.verificationToken.delete({
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
}
await prisma.verificationToken.update({
where: { id: verificationRequest.id },
data: { attempts: { increment: 1 } }
})
await prisma.verificationToken.deleteMany({
where: { id: verificationRequest.id, attempts: { gte: 2 } }
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
})
}
},
session: {