fix: audit fix pass #1 — all 19 verified HIGH findings + critical dep cleanup
Fixes every CRITICAL/HIGH finding from the 2026-06-09 full-codebase audit
(REVIEW_FINDINGS.md); each fix went through independent spec + code-quality
review. Plan and per-task log: docs/superpowers/plans/2026-06-10-audit-high-fix-pass.md
- attendance: schemas accept the combined local datetimes the forms/service
use (new dateTimeString helpers in schemas/common.ts), breaks persist on
create, AttendanceCreate submit rebuilt — every submit 400'd since 519edce
- 2fa: backup codes wired to /totp/backup-verify (+ remember-me parity),
enrollment QR generated locally via qrcode (CSP-blocked external service
also leaked the secret), dashboard shows per-user enrollment, not policy
- invoices/orders: per-line VAT survives re-saves (numberOr 0-respecting
coercion in formatters.ts), billing_text persists on update, issued-order
status transitions update UI gates
- trips: real pagination on all 3 pages, GET /trips/stats server aggregate
(shared buildTripsWhere + legacy distance coalesce), vehicle_id applies on
PUT with both-vehicle odometer recompute, print rebuilt (sync window.open,
escaped template, server totals)
- orders api: attachment_data PDF blob excluded from all non-binary reads
- warehouse: unit field is a Select over UnitEnum, receipt attachments
downloadable via new authenticated GET route
- downloads: shared RFC 5987 contentDisposition helper — Czech filenames no
longer 500 (warehouse, received-invoices, orders endpoints)
- misc: block-env hook actually blocks (exit 2 + stderr), project create
works with empty dates, NaN filter guards on trips endpoints
- deps: remove unused concurrently (clears both critical advisories), pin
@hono/node-server >=1.19.13 via overrides (clears the 3 moderates without
the Prisma 6 downgrade), drop deprecated @types stubs
Gates: tsc -b clean - vitest 30 files / 342 tests (31 new) - eslint 0 errors
- build OK
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useState, useRef } from "react";
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import QRCode from "qrcode";
|
||||
import Box from "@mui/material/Box";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
@@ -134,6 +135,28 @@ export default function DashProfile({
|
||||
// locks <html> scroll like the kit dialogs.
|
||||
useDialogScrollLock(show2FASetup);
|
||||
|
||||
// Generate the enrollment QR LOCALLY from the otpauth URI. Never send the
|
||||
// URI to an external QR service: the production CSP (img-src) blocks it and
|
||||
// it would leak the TOTP secret to a third party. A data: URL is allowed by
|
||||
// the CSP. Derived via useQuery (not an effect) per repo conventions.
|
||||
const { data: totpQrDataUrl, isError: totpQrFailed } = useQuery({
|
||||
queryKey: ["totp", "qr", totpQrUri],
|
||||
enabled: !!totpQrUri,
|
||||
staleTime: Infinity,
|
||||
// The URI embeds the TOTP secret — drop it from the cache as soon as the
|
||||
// enrollment UI unmounts instead of keeping it for the default 5-min GC.
|
||||
gcTime: 0,
|
||||
retry: false,
|
||||
queryFn: async () => {
|
||||
try {
|
||||
return await QRCode.toDataURL(totpQrUri!, { width: 200, margin: 2 });
|
||||
} catch (err) {
|
||||
console.error("DashProfile: generování QR kódu selhalo", err);
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [formData, setFormData] = useState<ProfileFormData>({
|
||||
username: "",
|
||||
@@ -549,11 +572,11 @@ export default function DashProfile({
|
||||
Naskenujte QR kód v autentizační aplikaci (Google Authenticator,
|
||||
Authy, Microsoft Authenticator apod.)
|
||||
</Typography>
|
||||
{totpQrUri && (
|
||||
{totpQrUri && totpQrDataUrl && (
|
||||
<Box sx={{ textAlign: "center", mb: 2 }}>
|
||||
<Box
|
||||
component="img"
|
||||
src={`https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(totpQrUri)}`}
|
||||
src={totpQrDataUrl}
|
||||
alt="TOTP QR Code"
|
||||
sx={{
|
||||
width: 200,
|
||||
@@ -561,10 +584,23 @@ export default function DashProfile({
|
||||
borderRadius: 2,
|
||||
border: 1,
|
||||
borderColor: "divider",
|
||||
// The QR must stay scannable in dark mode — keep it on a
|
||||
// white tile instead of inheriting the dark paper bg.
|
||||
bgcolor: "common.white",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{totpQrUri && totpQrFailed && (
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="warning.main"
|
||||
sx={{ mb: 2, textAlign: "center" }}
|
||||
>
|
||||
QR kód se nepodařilo vygenerovat. Zadejte prosím klíč do
|
||||
aplikace ručně.
|
||||
</Typography>
|
||||
)}
|
||||
{totpSecret && (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography
|
||||
|
||||
Reference in New Issue
Block a user