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:
@@ -10,6 +10,7 @@ import { useAuth } from "../context/AuthContext";
|
||||
import Forbidden from "../components/Forbidden";
|
||||
import { useApiMutation } from "../lib/queries/mutations";
|
||||
import { todayLocalStr } from "../utils/formatters";
|
||||
import { combineDatetime } from "../utils/attendanceHelpers";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
@@ -41,6 +42,24 @@ interface CreateForm {
|
||||
notes: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wire payload for POST /attendance (CreateAttendanceSchema). The raw form
|
||||
* above is NOT sent directly: its separate date+time inputs are combined into
|
||||
* "YYYY-MM-DDTHH:MM:00" datetimes (same semantics as useAttendanceAdmin's
|
||||
* create modal), and unset optional fields are null — never "".
|
||||
*/
|
||||
interface CreatePayload {
|
||||
user_id: number;
|
||||
shift_date: string;
|
||||
leave_type: string;
|
||||
notes: string | null;
|
||||
leave_hours?: number;
|
||||
arrival_time?: string | null;
|
||||
departure_time?: string | null;
|
||||
break_start?: string | null;
|
||||
break_end?: string | null;
|
||||
}
|
||||
|
||||
export default function AttendanceCreate() {
|
||||
const alert = useAlert();
|
||||
const { hasPermission } = useAuth();
|
||||
@@ -52,7 +71,7 @@ export default function AttendanceCreate() {
|
||||
}));
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
const createMutation = useApiMutation<CreateForm, { message?: string }>({
|
||||
const createMutation = useApiMutation<CreatePayload, { message?: string }>({
|
||||
url: () => `${API_BASE}/attendance`,
|
||||
method: () => "POST",
|
||||
invalidate: ["attendance", "users"],
|
||||
@@ -88,7 +107,36 @@ export default function AttendanceCreate() {
|
||||
setSubmitting(true);
|
||||
|
||||
try {
|
||||
const result = await createMutation.mutateAsync(form);
|
||||
const isLeave = form.leave_type !== "work";
|
||||
const payload: CreatePayload = {
|
||||
user_id: Number(form.user_id),
|
||||
shift_date: form.shift_date,
|
||||
leave_type: form.leave_type,
|
||||
notes: form.notes || null,
|
||||
};
|
||||
|
||||
if (isLeave) {
|
||||
payload.leave_hours = form.leave_hours || 8;
|
||||
} else {
|
||||
payload.arrival_time = combineDatetime(
|
||||
form.arrival_date,
|
||||
form.arrival_time,
|
||||
);
|
||||
payload.departure_time = combineDatetime(
|
||||
form.departure_date,
|
||||
form.departure_time,
|
||||
);
|
||||
payload.break_start = combineDatetime(
|
||||
form.break_start_date,
|
||||
form.break_start_time,
|
||||
);
|
||||
payload.break_end = combineDatetime(
|
||||
form.break_end_date,
|
||||
form.break_end_time,
|
||||
);
|
||||
}
|
||||
|
||||
const result = await createMutation.mutateAsync(payload);
|
||||
alert.success(result?.message || "Uloženo");
|
||||
navigate(`/attendance/admin?month=${form.shift_date.substring(0, 7)}`);
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user