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:
BOHA
2026-06-10 09:59:47 +02:00
parent d08e55a41a
commit 1826fc7976
44 changed files with 2812 additions and 622 deletions

View File

@@ -5,6 +5,7 @@ import { config } from "../../config/env";
import { requireAuth, requirePermission } from "../../middleware/auth";
import { logAudit } from "../../services/audit";
import { success, error, parseId, paginated } from "../../utils/response";
import { contentDisposition } from "../../utils/content-disposition";
import { parsePagination, buildPaginationMeta } from "../../utils/pagination";
import { parseBody } from "../../schemas/common";
import { nasInvoicesManager } from "../../services/nas-financials-manager";
@@ -1294,6 +1295,41 @@ export default async function warehouseRoutes(
},
);
// GET /receipts/:id/attachments/:attachmentId — download attachment from NAS
fastify.get<{ Params: { id: string; attachmentId: string } }>(
"/receipts/:id/attachments/:attachmentId",
{ preHandler: requirePermission("warehouse.view") },
async (request, reply) => {
const id = parseId(request.params.id, reply);
if (id === null) return;
const attachmentId = parseId(request.params.attachmentId, reply);
if (attachmentId === null) return;
const attachment = await prisma.sklad_receipt_attachments.findUnique({
where: { id: attachmentId },
});
// 404 (not 400) when the attachment belongs to a different receipt:
// a read endpoint should not confirm that the id exists elsewhere.
if (!attachment || attachment.receipt_id !== id) {
return error(reply, "Příloha nenalezena", 404);
}
const nasFile = nasInvoicesManager.readReceived(attachment.file_path);
if (!nasFile) return error(reply, "Soubor na NAS nenalezen", 404);
const mime = attachment.file_mime || "application/octet-stream";
// "attachment": the UI always downloads via blob + a.download, never
// renders inline — and attachment is safer with client-controlled mime.
return reply
.type(mime)
.header(
"Content-Disposition",
contentDisposition("attachment", attachment.file_name),
)
.send(nasFile.data);
},
);
// POST /receipts/:id/attachments — upload attachment
fastify.post<{ Params: { id: string } }>(
"/receipts/:id/attachments",