security: fix all Critical and High findings from FLAWS_REPORT audit

- Auth: pessimistic locking on login tokens and refresh token rotation,
  backup code attempt counter, rate limiting verification
- Schema: unique constraints on business numbers, FK relations,
  unsigned/signed alignment, attendance duplicate prevention
- Invoices/PDFs: DOMPurify sanitization, bounded queries in stats
  and alerts, VAT rounding, Puppeteer error handling
- Orders/Offers: transactional parent+child creation, Zod NaN
  refinement, status enums, uniqueness checks
- Projects/Files: path traversal protection, streamed uploads,
  permission guards, query param validation
- Attendance/HR: duplicate checks, ownership validation, GPS
  restrictions, trip distance validation
- Frontend: modal lock reference counting, XSS escaping in print
  HTML, ref mutation fixes, accessibility attributes

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-04-24 00:58:35 +02:00
parent 122eee175e
commit 528e55991b
57 changed files with 2355 additions and 1010 deletions

View File

@@ -76,6 +76,7 @@ export default function Settings() {
const navigate = useNavigate();
const [loading, setLoading] = useState(true);
const [roles, setRoles] = useState<Role[]>([]);
const [users, setUsers] = useState<{ role_id: number }[]>([]);
const [, setAllPermissions] = useState<Permission[]>([]);
const [permissionGroups, setPermissionGroups] = useState<
Record<string, Permission[]>
@@ -161,12 +162,14 @@ export default function Settings() {
return;
}
try {
const [rolesRes, permsRes] = await Promise.all([
const [rolesRes, permsRes, usersRes] = await Promise.all([
apiFetch(`${API_BASE}/roles`),
apiFetch(`${API_BASE}/roles/permissions`),
apiFetch(`${API_BASE}/users`),
]);
const rolesResult = await rolesRes.json();
const permsResult = await permsRes.json();
const usersResult = await usersRes.json();
if (rolesResult.success) {
setRoles(Array.isArray(rolesResult.data) ? rolesResult.data : []);
@@ -188,6 +191,10 @@ export default function Settings() {
}
setPermissionGroups(groups);
}
if (usersResult.success) {
setUsers(Array.isArray(usersResult.data) ? usersResult.data : []);
}
} catch {
alert.error("Chyba připojení");
} finally {
@@ -808,7 +815,7 @@ export default function Settings() {
</td>
<td>
<span className="admin-badge admin-badge-secondary">
{0}
{users.filter((u) => u.role_id === role.id).length}
</span>
</td>
<td>
@@ -838,16 +845,21 @@ export default function Settings() {
}
className="admin-btn-icon danger"
title={
0 > 0
users.filter((u) => u.role_id === role.id)
.length > 0
? "Nelze smazat roli s přiřazenými uživateli"
: "Smazat"
}
aria-label={
0 > 0
users.filter((u) => u.role_id === role.id)
.length > 0
? "Nelze smazat roli s přiřazenými uživateli"
: "Smazat"
}
disabled={0 > 0}
disabled={
users.filter((u) => u.role_id === role.id)
.length > 0
}
>
<svg
width="16"