security: fix all Medium findings from FLAWS_REPORT audit

- Auth: TOTP replay protection with counter tracking, constant-time
  backup code comparison, atomic lockout increment, per-token logout
- Invoices/PDFs: net-based VAT calculation, dangerous URL scheme
  stripping in cleanQuillHtml, orders-pdf error handling
- Orders: reject item changes on status transition, cascading
  delete cleanup, take:1 with orderBy
- Projects: atomic rename collision handling, MIME/extension
  validation, empty customer name rejection
- Attendance: Czech public holiday awareness in frontend fund
  calculation, leave_hours 0 handling, invalid date NaN guard,
  bounded per-month queries in workfund
- Users/Admin: profile audit logging + password validation, session
  revocation guard, session ID validation, dashboard DB aggregation,
  soft-deleted record protection in scope templates
- Frontend: FormField label linkage, Pagination ARIA, error
  handling in OrderConfirmationModal, 401 propagation, GPS emoji
  hidden from screen readers, table sort state fix, geolocation
  race/abort cleanup, Leaflet popup DOM safety, Vehicles toggleActive
  minimal body, CompanySettings ref mutation fix, OfferDetail unlock
  abort, AttendanceBalances combined fetches
- Utils: env validation, Puppeteer concurrency mutex, invoice alert
  cron cleanup on shutdown, body limit alignment, TOTP error logging,
  trustProxy from env, symlink rejection, rate cache Map usage

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-04-24 08:24:14 +02:00
parent 528e55991b
commit 4f4b12f039
33 changed files with 442 additions and 211 deletions

View File

@@ -45,6 +45,11 @@ export default function useListData<T = unknown>(
const abortRef = useRef<AbortController | null>(null);
const debouncedSearch = useDebounce(search, 300);
const extraParamsKey = Object.entries(extraParams)
.sort((a, b) => a[0].localeCompare(b[0]))
.map(([k, v]) => `${k}=${v}`)
.join("&");
const fetchData = useCallback(async () => {
if (abortRef.current) abortRef.current.abort();
const controller = new AbortController();
@@ -66,7 +71,10 @@ export default function useListData<T = unknown>(
? `${endpoint}?${params}`
: `${API_BASE}/${endpoint}?${params}`;
const response = await apiFetch(url, { signal: controller.signal });
if (response.status === 401) return;
if (response.status === 401) {
window.location.href = "/login";
return;
}
const result = await response.json();
if (result.success) {
const data = dataKey
@@ -105,8 +113,8 @@ export default function useListData<T = unknown>(
page,
perPage,
dataKey,
JSON.stringify(extraParams),
]); // eslint-disable-line react-hooks/exhaustive-deps
extraParamsKey,
]);
useEffect(() => {
fetchData();