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,7 @@
|
||||
*/
|
||||
|
||||
import "../src/config/env";
|
||||
import fs from "fs";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { nasFinancialsManager } from "../src/services/nas-financials-manager";
|
||||
|
||||
@@ -51,6 +52,39 @@ async function main() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read-back verification before we null out the ONLY DB copy of the bytes.
|
||||
// The manager uses writeFileSync (synchronous) and returns a relative path;
|
||||
// resolve it back to disk and confirm the file exists and its size matches
|
||||
// the source buffer. A 0-byte/truncated/corrupt write would otherwise lose
|
||||
// the invoice permanently once file_data is set to null. If verification
|
||||
// fails we KEEP file_data and report the row as failed.
|
||||
const expectedSize = rec.file_data.length;
|
||||
const readBack = nasFinancialsManager.readReceivedInvoice(result.filePath);
|
||||
let writtenSize = -1;
|
||||
if (readBack) {
|
||||
writtenSize = readBack.data.length;
|
||||
} else {
|
||||
// Fall back to a direct stat in case readReceivedInvoice's path guard
|
||||
// rejects an otherwise-valid path; either way we need a confirmed size.
|
||||
try {
|
||||
writtenSize = fs.statSync(
|
||||
(nasFinancialsManager as unknown as { basePath: string }).basePath +
|
||||
"/" +
|
||||
result.filePath,
|
||||
).size;
|
||||
} catch {
|
||||
writtenSize = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (writtenSize !== expectedSize) {
|
||||
console.error(
|
||||
` FAIL id=${rec.id}: read-back mismatch (expected ${expectedSize} bytes, got ${writtenSize}); keeping DB copy`,
|
||||
);
|
||||
failed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
await prisma.received_invoices.update({
|
||||
where: { id: rec.id },
|
||||
data: { file_path: result.filePath, file_data: null },
|
||||
|
||||
Reference in New Issue
Block a user