From b4bb9cc727cd0ec05dc233e3e1f506f2e3633e34 Mon Sep 17 00:00:00 2001 From: "Rudy B." Date: Sat, 25 Apr 2026 18:39:08 +0200 Subject: [PATCH] fix(2FA): put disable 2FA cleanup in transaction --- src/app/api/2fa/disable/route.ts | 8 +++++--- src/lib/two-factor/replay-prevention.ts | 8 ++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/app/api/2fa/disable/route.ts b/src/app/api/2fa/disable/route.ts index 859deac6d..c33a6460a 100644 --- a/src/app/api/2fa/disable/route.ts +++ b/src/app/api/2fa/disable/route.ts @@ -101,9 +101,11 @@ export async function POST(request: Request) { }); } - await markOtpUsed(userId, token); - await prisma.client.twoFactorAuth.delete({ where: { userId } }); - await prisma.client.twoFactorBackupCode.deleteMany({ where: { userId } }); + await prisma.transaction(async tx => { + await markOtpUsed(userId, token, tx); + await tx.twoFactorAuth.delete({ where: { userId } }); + await tx.twoFactorBackupCode.deleteMany({ where: { userId } }); + }); await resetRateLimit(userId); return json({ ok: true }); diff --git a/src/lib/two-factor/replay-prevention.ts b/src/lib/two-factor/replay-prevention.ts index 46f8dc3f9..afaa7ce15 100644 --- a/src/lib/two-factor/replay-prevention.ts +++ b/src/lib/two-factor/replay-prevention.ts @@ -1,9 +1,13 @@ import prisma from '@/lib/prisma'; +import { Prisma } from '@/generated/prisma/client'; + +type TxClient = Prisma.TransactionClient; /** Records a one-time password as used so it cannot be reused within the next 90 seconds. */ -export async function markOtpUsed(userId: string, otp: string): Promise { +export async function markOtpUsed(userId: string, otp: string, tx?: TxClient): Promise { + const client = tx ?? prisma.client; const expiresAt = new Date(Date.now() + 90 * 1000); - await prisma.client.twoFactorOtpUsed.upsert({ + await client.twoFactorOtpUsed.upsert({ where: { userId_otp: { userId, otp } }, update: { expiresAt }, create: { userId, otp, expiresAt },