fix: 2026-06-09 full-codebase audit hardening
Validation: shared NaN-guarded Zod coercion helpers in schemas/common.ts replace the raw number|string transform idiom across every schema (the root-cause NaN bug class); emailOrEmpty + lenient isoDateString/timeString. Security: roles privilege-escalation closed; refresh-token family revocation on reuse; TOTP uses config params; read endpoints permission-guarded; received-invoices gross VAT on all paths; orders-pdf custom-items authz. Concurrency: $queryRaw SELECT...FOR UPDATE locks in ascending-id order (warehouse confirm/cancel, attendance lockUserRow); uniqueness checks moved into create transactions (TOCTOU -> 409); deterministic id tiebreak on second-precision timestamp ordering (plan resolveCell/resolveGrid, warehouse FIFO). Frontend: Rules-of-Hooks fixed across ~14 pages + PlanCellModal; UTC-date persisted fields; dashboard invalidation gaps; stale-closure confirm bugs. Tooling/tests: ESLint flat config (react-hooks/rules-of-hooks = error) + Prettier; tsconfig.test.json so tsc -b type-checks the tests; removed 3 dead deps; npm audit fix (8 -> 3). Suite 195 -> 247 (happy-path auth, FIFO oldest-first, flakiness fixes), isolated on app_test via .env.test with a hard-throw setup guard. Gates: tsc 0 | build 0 | vitest 247/247 | eslint 0 errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,18 @@ import { config } from "../config/env";
|
||||
/**
|
||||
* NAS storage for financial documents.
|
||||
* Structure: {basePath}/Vydané/YYYY/MM/ and {basePath}/Přijaté/YYYY/MM/
|
||||
*
|
||||
* PERFORMANCE NOTE (audit finding — DEFERRED, intentional):
|
||||
* Every method here uses SYNCHRONOUS fs calls (mkdirSync/writeFileSync/
|
||||
* readFileSync/readdirSync/statSync). When basePath is a network (SMB) share, a
|
||||
* slow or hung NAS will BLOCK the Node event loop for the duration of the call,
|
||||
* stalling all other requests in the same process. This is a known trade-off:
|
||||
* the file sizes are small (invoice PDFs), the call sites are low-frequency
|
||||
* admin operations, and the synchronous API lets callers treat a returned path
|
||||
* as a verified, durable write (see migrate-received-invoices-to-nas.ts, which
|
||||
* relies on this to safely null out the DB BLOB). Converting to the async
|
||||
* fs.promises API is a large, cross-cutting change touching every caller and
|
||||
* their error handling — deliberately NOT done here to keep this fix scoped.
|
||||
*/
|
||||
|
||||
const DIR_ISSUED = "Vydané";
|
||||
@@ -21,6 +33,9 @@ class NasFinancialsManager {
|
||||
isConfigured(): boolean {
|
||||
if (!this.basePath) return false;
|
||||
try {
|
||||
// Intentional side effect: ensure the base directory exists as part of the
|
||||
// configured-check so the first save doesn't fail on a missing root. This
|
||||
// mirrors nas-offers-manager.isConfigured() (established convention).
|
||||
fs.mkdirSync(this.basePath, { recursive: true });
|
||||
return fs.statSync(this.basePath).isDirectory();
|
||||
} catch (err) {
|
||||
@@ -78,6 +93,11 @@ class NasFinancialsManager {
|
||||
month: number,
|
||||
pdfBuffer: Buffer,
|
||||
): string | null {
|
||||
// Guard parity with saveReceivedInvoice: without this, an unconfigured
|
||||
// (empty) basePath would let ensureDir build a RELATIVE path and write the
|
||||
// PDF into the process CWD instead of the NAS. Bail out instead.
|
||||
if (!this.isConfigured()) return null;
|
||||
|
||||
const dir = this.ensureDir(DIR_ISSUED, year, month);
|
||||
if (!dir) return null;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user