security: fix all Medium findings from FLAWS_REPORT audit
- Auth: TOTP replay protection with counter tracking, constant-time backup code comparison, atomic lockout increment, per-token logout - Invoices/PDFs: net-based VAT calculation, dangerous URL scheme stripping in cleanQuillHtml, orders-pdf error handling - Orders: reject item changes on status transition, cascading delete cleanup, take:1 with orderBy - Projects: atomic rename collision handling, MIME/extension validation, empty customer name rejection - Attendance: Czech public holiday awareness in frontend fund calculation, leave_hours 0 handling, invalid date NaN guard, bounded per-month queries in workfund - Users/Admin: profile audit logging + password validation, session revocation guard, session ID validation, dashboard DB aggregation, soft-deleted record protection in scope templates - Frontend: FormField label linkage, Pagination ARIA, error handling in OrderConfirmationModal, 401 propagation, GPS emoji hidden from screen readers, table sort state fix, geolocation race/abort cleanup, Leaflet popup DOM safety, Vehicles toggleActive minimal body, CompanySettings ref mutation fix, OfferDetail unlock abort, AttendanceBalances combined fetches - Utils: env validation, Puppeteer concurrency mutex, invoice alert cron cleanup on shutdown, body limit alignment, TOTP error logging, trustProxy from env, symlink rejection, rate cache Map usage Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -153,11 +153,32 @@ export default async function authRoutes(
|
||||
return error(reply, "Uživatel nenalezen", 401);
|
||||
}
|
||||
|
||||
const isValid = OTPAuth.verify(user.totp_secret, totp_code);
|
||||
if (!isValid) {
|
||||
const verifyResult = OTPAuth.verify(user.totp_secret, totp_code);
|
||||
if (!verifyResult.valid) {
|
||||
return error(reply, "Neplatný TOTP kód", 401);
|
||||
}
|
||||
|
||||
// Reject replayed TOTP codes
|
||||
const replayCheck = await prisma.$transaction(async (tx) => {
|
||||
const rows = await tx.$queryRaw<
|
||||
Array<{ totp_last_used_counter: number | null }>
|
||||
>`SELECT totp_last_used_counter FROM users WHERE id = ${user.id} FOR UPDATE`;
|
||||
const lastCounter = rows[0]?.totp_last_used_counter ?? null;
|
||||
if (
|
||||
lastCounter !== null &&
|
||||
verifyResult.counter !== null &&
|
||||
verifyResult.counter <= lastCounter
|
||||
) {
|
||||
return { replay: true };
|
||||
}
|
||||
await tx.$executeRaw`UPDATE users SET totp_last_used_counter = ${verifyResult.counter} WHERE id = ${user.id}`;
|
||||
return { replay: false };
|
||||
});
|
||||
|
||||
if (replayCheck.replay) {
|
||||
return error(reply, "TOTP kód již byl použit", 401);
|
||||
}
|
||||
|
||||
// Reset failed attempts and update last login (TOTP verified = successful login)
|
||||
await prisma.users.update({
|
||||
where: { id: user.id },
|
||||
|
||||
Reference in New Issue
Block a user