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:
@@ -104,8 +104,10 @@ export const getDatePart = (datetime: string | null | undefined): string => {
|
||||
|
||||
export const getTimePart = (datetime: string | null | undefined): string => {
|
||||
if (!datetime) return "";
|
||||
const d = new Date(datetime);
|
||||
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
||||
// Parse HH:MM directly from the string (no `new Date(...).getHours()`), so
|
||||
// the displayed time matches the stored wall-clock time regardless of the
|
||||
// browser timezone — same ethos as the sibling `extractTime` helper.
|
||||
return extractTime(datetime);
|
||||
};
|
||||
|
||||
export const calcProjectMinutesTotal = (
|
||||
@@ -252,7 +254,7 @@ export const calculateNightMinutes = (record: AttendanceRecord): number => {
|
||||
let intervals: Array<[number, number]> = [[startMs, endMs]];
|
||||
if (record.break_start && record.break_end) {
|
||||
const bStart = new Date(record.break_start).getTime();
|
||||
let bEnd = new Date(record.break_end).getTime();
|
||||
const bEnd = new Date(record.break_end).getTime();
|
||||
if (Number.isFinite(bStart) && Number.isFinite(bEnd) && bEnd > bStart) {
|
||||
const next: Array<[number, number]> = [];
|
||||
for (const [s, e] of intervals) {
|
||||
|
||||
@@ -4,13 +4,6 @@ export const LEAVE_TYPE_LABELS: Record<string, string> = {
|
||||
unpaid: "Neplacené volno",
|
||||
};
|
||||
|
||||
export const STATUS_DOT_CLASS: Record<string, string> = {
|
||||
in: "dash-status-in",
|
||||
away: "dash-status-away",
|
||||
out: "dash-status-out",
|
||||
leave: "dash-status-leave",
|
||||
};
|
||||
|
||||
export const STATUS_LABELS: Record<string, string> = {
|
||||
in: "Přítomen",
|
||||
away: "Přestávka",
|
||||
@@ -57,6 +50,10 @@ export function getCzechDate(): string {
|
||||
];
|
||||
const day = days[now.getDay()];
|
||||
const oneJan = new Date(now.getFullYear(), 0, 1);
|
||||
// Naive week-of-year (NOT true ISO-8601 week-numbering): can be off by one
|
||||
// around the year boundary. Intentionally left as-is — this string is
|
||||
// display-only in the dashboard header; a correct ISO-week implementation
|
||||
// (Thursday-anchored, week-year aware) is more than this cosmetic line warrants.
|
||||
const week = Math.ceil(
|
||||
((now.getTime() - oneJan.getTime()) / 86400000 + oneJan.getDay() + 1) / 7,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user