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:
@@ -147,52 +147,56 @@ export async function login(data: LoginRequest): Promise<LoginResponse> {
|
||||
}
|
||||
|
||||
export async function refreshTokens(token: string): Promise<{ accessToken: string; refreshToken: string }> {
|
||||
const storedToken = await prisma.refreshToken.findUnique({
|
||||
where: { token },
|
||||
});
|
||||
// Use a transaction to prevent race conditions
|
||||
return await prisma.$transaction(async (tx) => {
|
||||
const storedToken = await tx.refreshToken.findUnique({
|
||||
where: { token },
|
||||
});
|
||||
|
||||
if (!storedToken) {
|
||||
throw new AppError(401, 'Token inválido');
|
||||
}
|
||||
if (!storedToken) {
|
||||
throw new AppError(401, 'Token inválido');
|
||||
}
|
||||
|
||||
if (storedToken.expiresAt < new Date()) {
|
||||
await prisma.refreshToken.delete({ where: { id: storedToken.id } });
|
||||
throw new AppError(401, 'Token expirado');
|
||||
}
|
||||
if (storedToken.expiresAt < new Date()) {
|
||||
await tx.refreshToken.deleteMany({ where: { id: storedToken.id } });
|
||||
throw new AppError(401, 'Token expirado');
|
||||
}
|
||||
|
||||
const payload = verifyToken(token);
|
||||
const payload = verifyToken(token);
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: payload.userId },
|
||||
include: { tenant: true },
|
||||
});
|
||||
const user = await tx.user.findUnique({
|
||||
where: { id: payload.userId },
|
||||
include: { tenant: true },
|
||||
});
|
||||
|
||||
if (!user || !user.active) {
|
||||
throw new AppError(401, 'Usuario no encontrado o desactivado');
|
||||
}
|
||||
if (!user || !user.active) {
|
||||
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 = {
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
tenantId: user.tenantId,
|
||||
schemaName: user.tenant.schemaName,
|
||||
};
|
||||
|
||||
const accessToken = generateAccessToken(newTokenPayload);
|
||||
const refreshToken = generateRefreshToken(newTokenPayload);
|
||||
|
||||
await prisma.refreshToken.create({
|
||||
data: {
|
||||
const newTokenPayload = {
|
||||
userId: user.id,
|
||||
token: refreshToken,
|
||||
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
||||
},
|
||||
});
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
tenantId: user.tenantId,
|
||||
schemaName: user.tenant.schemaName,
|
||||
};
|
||||
|
||||
return { accessToken, refreshToken };
|
||||
const accessToken = generateAccessToken(newTokenPayload);
|
||||
const refreshToken = generateRefreshToken(newTokenPayload);
|
||||
|
||||
await tx.refreshToken.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
token: refreshToken,
|
||||
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
||||
},
|
||||
});
|
||||
|
||||
return { accessToken, refreshToken };
|
||||
});
|
||||
}
|
||||
|
||||
export async function logout(token: string): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user