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:
@@ -126,10 +126,14 @@ export default async function authRoutes(
|
|||||||
return { error: "Neplatný nebo expirovaný login token", status: 401 };
|
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({
|
const user = await tx.users.findUnique({
|
||||||
where: { id: storedToken.user_id },
|
where: { id: storedUserId },
|
||||||
include: { roles: true },
|
include: { roles: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -282,8 +282,12 @@ export default async function totpRoutes(
|
|||||||
return { error: "Neplatný nebo expirovaný login token", status: 401 };
|
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({
|
const user = await tx.users.findUnique({
|
||||||
where: { id: storedToken.user_id },
|
where: { id: storedUserId },
|
||||||
include: { roles: true },
|
include: { roles: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -315,7 +319,7 @@ export default async function totpRoutes(
|
|||||||
const newFailedAttempts = (user.failed_login_attempts ?? 0) + 1;
|
const newFailedAttempts = (user.failed_login_attempts ?? 0) + 1;
|
||||||
if (newFailedAttempts >= settings.max_login_attempts) {
|
if (newFailedAttempts >= settings.max_login_attempts) {
|
||||||
await tx.totp_login_tokens.delete({
|
await tx.totp_login_tokens.delete({
|
||||||
where: { id: storedToken.id },
|
where: { id: storedTokenId },
|
||||||
});
|
});
|
||||||
await tx.users.update({
|
await tx.users.update({
|
||||||
where: { id: user.id },
|
where: { id: user.id },
|
||||||
|
|||||||
@@ -274,7 +274,11 @@ export async function refreshAccessToken(
|
|||||||
return { type: "error", message: "Neplatný refresh token", status: 401 };
|
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) {
|
if (!authData) {
|
||||||
return { type: "error", message: "Uživatel nenalezen", status: 401 };
|
return { type: "error", message: "Uživatel nenalezen", status: 401 };
|
||||||
}
|
}
|
||||||
@@ -286,16 +290,19 @@ export async function refreshAccessToken(
|
|||||||
? config.jwt.refreshTokenRememberExpiry
|
? config.jwt.refreshTokenRememberExpiry
|
||||||
: config.jwt.refreshTokenSessionExpiry;
|
: 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({
|
await tx.refresh_tokens.update({
|
||||||
where: { id: storedToken.id },
|
where: { id: storedTokenId },
|
||||||
data: { replaced_at: new Date(), replaced_by_hash: newRefreshTokenHash },
|
data: { replaced_at: new Date(), replaced_by_hash: newRefreshTokenHash },
|
||||||
});
|
});
|
||||||
await tx.refresh_tokens.create({
|
await tx.refresh_tokens.create({
|
||||||
data: {
|
data: {
|
||||||
user_id: storedToken.user_id,
|
user_id: storedUserId,
|
||||||
token_hash: newRefreshTokenHash,
|
token_hash: newRefreshTokenHash,
|
||||||
expires_at: new Date(Date.now() + expiresIn * 1000),
|
expires_at: new Date(Date.now() + expiresIn * 1000),
|
||||||
remember_me: storedToken.remember_me ?? false,
|
remember_me: rememberMe,
|
||||||
ip_address: request.ip,
|
ip_address: request.ip,
|
||||||
user_agent: request.headers["user-agent"] ?? null,
|
user_agent: request.headers["user-agent"] ?? null,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user