fix: use transaction in refreshTokens to prevent race conditions

- Wrap token refresh logic in Prisma transaction
- Use deleteMany instead of delete to handle race conditions gracefully

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Consultoria AS
2026-01-31 03:09:32 +00:00
parent ba012254db
commit 1fe462764f

View File

@@ -147,7 +147,9 @@ export async function login(data: LoginRequest): Promise<LoginResponse> {
} }
export async function refreshTokens(token: string): Promise<{ accessToken: string; refreshToken: string }> { export async function refreshTokens(token: string): Promise<{ accessToken: string; refreshToken: string }> {
const storedToken = await prisma.refreshToken.findUnique({ // Use a transaction to prevent race conditions
return await prisma.$transaction(async (tx) => {
const storedToken = await tx.refreshToken.findUnique({
where: { token }, where: { token },
}); });
@@ -156,13 +158,13 @@ export async function refreshTokens(token: string): Promise<{ accessToken: strin
} }
if (storedToken.expiresAt < new Date()) { if (storedToken.expiresAt < new Date()) {
await prisma.refreshToken.delete({ where: { id: storedToken.id } }); await tx.refreshToken.deleteMany({ where: { id: storedToken.id } });
throw new AppError(401, 'Token expirado'); throw new AppError(401, 'Token expirado');
} }
const payload = verifyToken(token); const payload = verifyToken(token);
const user = await prisma.user.findUnique({ const user = await tx.user.findUnique({
where: { id: payload.userId }, where: { id: payload.userId },
include: { tenant: true }, include: { tenant: true },
}); });
@@ -171,7 +173,8 @@ export async function refreshTokens(token: string): Promise<{ accessToken: strin
throw new AppError(401, 'Usuario no encontrado o desactivado'); throw new AppError(401, 'Usuario no encontrado o desactivado');
} }
await prisma.refreshToken.delete({ where: { id: storedToken.id } }); // Use deleteMany to avoid error if already deleted (race condition)
await tx.refreshToken.deleteMany({ where: { id: storedToken.id } });
const newTokenPayload = { const newTokenPayload = {
userId: user.id, userId: user.id,
@@ -184,7 +187,7 @@ export async function refreshTokens(token: string): Promise<{ accessToken: strin
const accessToken = generateAccessToken(newTokenPayload); const accessToken = generateAccessToken(newTokenPayload);
const refreshToken = generateRefreshToken(newTokenPayload); const refreshToken = generateRefreshToken(newTokenPayload);
await prisma.refreshToken.create({ await tx.refreshToken.create({
data: { data: {
userId: user.id, userId: user.id,
token: refreshToken, token: refreshToken,
@@ -193,6 +196,7 @@ export async function refreshTokens(token: string): Promise<{ accessToken: strin
}); });
return { accessToken, refreshToken }; return { accessToken, refreshToken };
});
} }
export async function logout(token: string): Promise<void> { export async function logout(token: string): Promise<void> {