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 return user
}, },
useVerificationToken: async ({ identifier, token }) => { useVerificationToken: async ({ identifier, token }) => {
// we need to find the most recent verification request for this email/identifier return await prisma.$transaction(async (tx) => {
const verificationRequest = await prisma.verificationToken.findFirst({ const [verificationRequest] = await tx.$queryRaw`
where: { UPDATE verification_requests
identifier, SET attempts = attempts + 1
attempts: { FROM (
lt: 2 // count starts at 0 SELECT id FROM verification_requests
} WHERE identifier = ${identifier}
}, AND created_at > NOW() - INTERVAL '5 minutes'
orderBy: { -- we need to find the most recent verification request for this email/identifier
createdAt: 'desc' 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) throw new Error('No verification request found')
if (verificationRequest.token === token) { // if correct delete the token and continue if (verificationRequest.token === token) {
await prisma.verificationToken.delete({ // correct token was entered, delete the verification request because we no longer need it
await tx.verificationToken.delete({
where: { id: verificationRequest.id } where: { id: verificationRequest.id }
}) })
return verificationRequest return verificationRequest
} }
await prisma.verificationToken.update({ if (verificationRequest.attempts >= 3) {
where: { id: verificationRequest.id }, // too many attempts, delete the verification request and redirect to error page by throwing an error
data: { attempts: { increment: 1 } } await tx.verificationToken.delete({
}) where: { id: verificationRequest.id }
await prisma.verificationToken.deleteMany({
where: { id: verificationRequest.id, attempts: { gte: 2 } }
}) })
throw new Error('too many attempts')
}
// wrong code but can try again
return null return null
})
} }
}, },
session: { session: {