fix(audit-final): pre-merge hardening + expense receipt UI
Final audit pass on feat/berth-recommender (3 parallel Opus agents) caught 5 critical and ~12 high-severity findings. All addressed in-branch; medium/low items deferred to docs/audit-final-deferred.md. Critical: - Add filesystem-backend PUT handler at /api/storage/[token] so presigned uploads stop 405-ing in filesystem mode (every browser-driven berth-PDF + brochure upload was broken). Same token-verify + replay protection as GET, plus magic-byte gate when c=application/pdf. - Forward req.signal into streamExpensePdf so an aborted 1000-receipt export no longer keeps grinding for minutes. - Strengthen Content-Disposition filename sanitization: \s matches CR/LF which would let documentName forge headers; restrict to [\w. -]+ and add filename* RFC 5987 fallback. - Lock public berths feed behind an explicit slug allowlist instead of ?portSlug= enumeration. - Reject cross-port interest_berths upserts (defense-in-depth on top of the recommender SQL port filter). High: - Recommender: width-only feasibility now caps length via L/W ratio so a 200ft berth doesn't surface for a 30ft beam request; total_interest_count filters out junction rows whose interest is in another port. - Mooring normalization follow-up migration (0034) catches un-hyphenated padded forms (A01) the original 0024 WHERE missed. - Send-out rate limit moved AFTER validation and scoped per-(port, user) so typos don't burn a slot and a multi-port rep can't be DoS'd by another tenant. - Default-brochure path now blocks an archived row from sneaking through the partial unique index. - NocoDB import --update-snapshot honoured under --dry-run so reps can refresh the seed JSON without committing DB writes. - PDF export: orderBy desc(expenseDate); apply isNull(archivedAt) when expenseIds are passed (was bypassed); flag rate-unavailable rows with an amber footer instead of silently treating them as 1:1; skip the USD->EUR chain when source already matches target. - expense-form-dialog: revokeObjectURL captures the URL in the closure instead of revoking the still-displayed one; reset upload state on close. - scan/page: handleClearReceipt resets in-flight scan/upload mutations; Save disabled while upload pending. - updateExpense re-asserts receipt-or-acknowledgement at the merged row so PATCH can't slip past the create-time refine. Plus the in-progress receipt upload UI for the expense form dialog (receipt picker + "I have no receipt" checkbox + warning banner) and a noReceiptAcknowledged flag on ExpenseRow for edit-mode hydration. Includes the canonical plan doc (referenced in CLAUDE.md), the handoff prompt, and a deferred-findings index for follow-up issues. 1163/1163 vitest passing. Typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
-- Audit follow-up: 0024 only normalized rows that contained a literal
|
||||
-- hyphen (`A-01`), but the audit caught that legacy NocoDB exports also
|
||||
-- produced un-hyphenated padded forms (`A01`). Those rows skipped the
|
||||
-- 0024 rewrite and remained non-canonical, which would break the public
|
||||
-- /berths/:mooringNumber lookup (the route gates on `^[A-Z]+\d+$`).
|
||||
--
|
||||
-- This migration re-runs the rewrite with a WHERE clause broadened to
|
||||
-- catch BOTH variants:
|
||||
-- - hyphenated padded ("A-01") ← redundant after 0024 but harmless
|
||||
-- - un-hyphenated padded ("A01")
|
||||
-- Rows already in canonical form skip the UPDATE because the regex_replace
|
||||
-- output equals the input AND the WHERE filter excludes them via the
|
||||
-- "leading zero or hyphen" pattern.
|
||||
UPDATE berths
|
||||
SET mooring_number = regexp_replace(mooring_number, '^([A-Z]+)-?0*(\d+)$', '\1\2')
|
||||
WHERE mooring_number ~ '^[A-Z]+-?0*\d+$'
|
||||
AND mooring_number !~ '^[A-Z]+\d+$';
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
bad_count integer;
|
||||
BEGIN
|
||||
SELECT count(*) INTO bad_count
|
||||
FROM berths
|
||||
WHERE mooring_number !~ '^[A-Z]+\d+$';
|
||||
IF bad_count > 0 THEN
|
||||
RAISE NOTICE 'Post-rewrite: % rows still do not match ^[A-Z]+\d+$ - manual review needed', bad_count;
|
||||
END IF;
|
||||
END $$;
|
||||
@@ -239,6 +239,13 @@
|
||||
"when": 1777948521076,
|
||||
"tag": "0033_expense_no_receipt_acknowledged",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 34,
|
||||
"version": "7",
|
||||
"when": 1778000000000,
|
||||
"tag": "0034_normalize_mooring_numbers_broaden",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user