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:
@@ -86,3 +86,13 @@ export const require2FAOptions = () =>
|
||||
queryFn: () =>
|
||||
jsonQuery<{ require_2fa: boolean }>("/api/admin/totp/required"),
|
||||
});
|
||||
|
||||
// Per-user 2FA enrollment status (users.totp_enabled) — NOT the company-wide
|
||||
// require_2fa policy above. Mutations that enable/disable 2FA must invalidate
|
||||
// the broad ["totp"] domain key.
|
||||
export const totpStatusOptions = () =>
|
||||
queryOptions({
|
||||
queryKey: ["totp", "status"],
|
||||
queryFn: () =>
|
||||
jsonQuery<{ totp_enabled: boolean }>("/api/admin/totp/status"),
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
import { jsonQuery } from "../apiAdapter";
|
||||
import { jsonQuery, paginatedJsonQuery } from "../apiAdapter";
|
||||
|
||||
export interface TripVehicle {
|
||||
id: number;
|
||||
@@ -59,7 +59,9 @@ export const tripListOptions = (filters: {
|
||||
if (filters.page) params.set("page", String(filters.page));
|
||||
if (filters.perPage) params.set("per_page", String(filters.perPage));
|
||||
const qs = params.toString();
|
||||
return jsonQuery<BackendTrip[]>(`/api/admin/trips${qs ? `?${qs}` : ""}`);
|
||||
return paginatedJsonQuery<BackendTrip>(
|
||||
`/api/admin/trips${qs ? `?${qs}` : ""}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -81,6 +83,8 @@ export const tripHistoryOptions = (filters: {
|
||||
month?: string;
|
||||
vehicleId?: number;
|
||||
userId?: number;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
}) =>
|
||||
queryOptions({
|
||||
queryKey: [
|
||||
@@ -90,6 +94,8 @@ export const tripHistoryOptions = (filters: {
|
||||
month: filters.month,
|
||||
vehicleId: filters.vehicleId,
|
||||
userId: filters.userId,
|
||||
page: filters.page,
|
||||
perPage: filters.perPage,
|
||||
},
|
||||
],
|
||||
queryFn: () => {
|
||||
@@ -98,7 +104,53 @@ export const tripHistoryOptions = (filters: {
|
||||
if (filters.vehicleId)
|
||||
params.set("vehicle_id", String(filters.vehicleId));
|
||||
if (filters.userId) params.set("user_id", String(filters.userId));
|
||||
if (filters.page) params.set("page", String(filters.page));
|
||||
if (filters.perPage) params.set("per_page", String(filters.perPage));
|
||||
const qs = params.toString();
|
||||
return jsonQuery<BackendTrip[]>(`/api/admin/trips${qs ? `?${qs}` : ""}`);
|
||||
return paginatedJsonQuery<BackendTrip>(
|
||||
`/api/admin/trips${qs ? `?${qs}` : ""}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export interface TripStats {
|
||||
count: number;
|
||||
total_km: number;
|
||||
business_km: number;
|
||||
private_km: number;
|
||||
}
|
||||
|
||||
// Count/km totals over the WHOLE filtered set (not one page) — hits
|
||||
// /trips/stats with the same filters the lists use. `month` accepts either a
|
||||
// numeric month (paired with `year`, TripsAdmin style) or a combined
|
||||
// "YYYY-MM" string (TripsHistory style); the server supports both.
|
||||
export const tripStatsOptions = (filters: {
|
||||
month?: number | string;
|
||||
year?: number;
|
||||
vehicleId?: number;
|
||||
userId?: number;
|
||||
}) =>
|
||||
queryOptions({
|
||||
queryKey: [
|
||||
"trips",
|
||||
"stats",
|
||||
{
|
||||
month: filters.month,
|
||||
year: filters.year,
|
||||
vehicleId: filters.vehicleId,
|
||||
userId: filters.userId,
|
||||
},
|
||||
],
|
||||
queryFn: () => {
|
||||
const params = new URLSearchParams();
|
||||
if (filters.month) params.set("month", String(filters.month));
|
||||
if (filters.year) params.set("year", String(filters.year));
|
||||
if (filters.vehicleId)
|
||||
params.set("vehicle_id", String(filters.vehicleId));
|
||||
if (filters.userId) params.set("user_id", String(filters.userId));
|
||||
const qs = params.toString();
|
||||
return jsonQuery<TripStats>(
|
||||
`/api/admin/trips/stats${qs ? `?${qs}` : ""}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user