fix: Prisma $queryRaw MySQL type coercion for BigInt and Boolean

$queryRaw on MySQL returns BigInt for integer columns and 0/1 for booleans.
Passing these raw values back to Prisma client methods causes validation errors:
- Expected Int, provided BigInt
- Expected Boolean, provided Int

Fixed in auth refresh, TOTP login, and TOTP backup code flows by wrapping
storedToken.id, storedToken.user_id with Number() and remember_me with Boolean().

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-04-24 11:18:38 +02:00
parent 8c278be941
commit a9bc82fac5
3 changed files with 23 additions and 8 deletions

View File

@@ -126,10 +126,14 @@ export default async function authRoutes(
return { error: "Neplatný nebo expirovaný login token", status: 401 };
}
await tx.totp_login_tokens.delete({ where: { id: storedToken.id } });
// $queryRaw on MySQL may return BigInt for integer columns
const storedTokenId = Number(storedToken.id);
const storedUserId = Number(storedToken.user_id);
await tx.totp_login_tokens.delete({ where: { id: storedTokenId } });
const user = await tx.users.findUnique({
where: { id: storedToken.user_id },
where: { id: storedUserId },
include: { roles: true },
});

View File

@@ -282,8 +282,12 @@ export default async function totpRoutes(
return { error: "Neplatný nebo expirovaný login token", status: 401 };
}
// $queryRaw on MySQL may return BigInt for integer columns
const storedTokenId = Number(storedToken.id);
const storedUserId = Number(storedToken.user_id);
const user = await tx.users.findUnique({
where: { id: storedToken.user_id },
where: { id: storedUserId },
include: { roles: true },
});
@@ -315,7 +319,7 @@ export default async function totpRoutes(
const newFailedAttempts = (user.failed_login_attempts ?? 0) + 1;
if (newFailedAttempts >= settings.max_login_attempts) {
await tx.totp_login_tokens.delete({
where: { id: storedToken.id },
where: { id: storedTokenId },
});
await tx.users.update({
where: { id: user.id },

View File

@@ -274,7 +274,11 @@ export async function refreshAccessToken(
return { type: "error", message: "Neplatný refresh token", status: 401 };
}
const authData = await loadAuthData(storedToken.user_id);
// $queryRaw on MySQL may return BigInt for integer columns
const storedTokenId = Number(storedToken.id);
const storedUserId = Number(storedToken.user_id);
const authData = await loadAuthData(storedUserId);
if (!authData) {
return { type: "error", message: "Uživatel nenalezen", status: 401 };
}
@@ -286,16 +290,19 @@ export async function refreshAccessToken(
? config.jwt.refreshTokenRememberExpiry
: config.jwt.refreshTokenSessionExpiry;
// $queryRaw on MySQL returns 0/1 for booleans; Prisma expects true/false
const rememberMe = Boolean(storedToken.remember_me);
await tx.refresh_tokens.update({
where: { id: storedToken.id },
where: { id: storedTokenId },
data: { replaced_at: new Date(), replaced_by_hash: newRefreshTokenHash },
});
await tx.refresh_tokens.create({
data: {
user_id: storedToken.user_id,
user_id: storedUserId,
token_hash: newRefreshTokenHash,
expires_at: new Date(Date.now() + expiresIn * 1000),
remember_me: storedToken.remember_me ?? false,
remember_me: rememberMe,
ip_address: request.ip,
user_agent: request.headers["user-agent"] ?? null,
},