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:
BOHA
2026-06-09 06:45:26 +02:00
parent c454d1a3fc
commit 519edce373
179 changed files with 7179 additions and 2844 deletions

View File

@@ -60,31 +60,54 @@ export default async function rolesRoutes(
"/",
{ preHandler: requirePermission("settings.roles") },
async (request, reply) => {
// Privilege-escalation guard: only an admin may create roles (and thus
// attach arbitrary permission_ids). A non-admin `settings.roles` holder
// must not be able to mint a new high-privilege role.
if (request.authData!.roleName !== "admin") {
return error(reply, "Pouze administrátor může vytvářet role", 403);
}
const parsed = parseBody(CreateRoleSchema, request.body);
if ("error" in parsed) return error(reply, parsed.error, 400);
const body = parsed.data;
const role = await prisma.roles.create({
data: {
name: String(body.name),
display_name: String(body.display_name),
description: body.description ? String(body.description) : null,
},
});
const name = String(body.name);
if (Array.isArray(body.permission_ids)) {
await prisma.$transaction(
(body.permission_ids as number[]).map((pid) =>
prisma.role_permissions.create({
data: {
role_id: role.id,
permission_id: pid,
},
}),
),
);
// Never allow minting another "admin" role (would shadow / escalate).
if (name.toLowerCase() === "admin") {
return error(reply, "Nelze vytvořit roli s názvem admin", 400);
}
// Enforce role-name uniqueness explicitly (clear 409 instead of a raw
// Prisma constraint 500).
const duplicate = await prisma.roles.findFirst({ where: { name } });
if (duplicate) {
return error(reply, "Role s tímto názvem již existuje", 409);
}
// Role insert + permission assignment in a SINGLE transaction so a
// partial failure can never leave an orphan role with no permissions.
const role = await prisma.$transaction(async (tx) => {
const created = await tx.roles.create({
data: {
name,
display_name: String(body.display_name),
description: body.description ? String(body.description) : null,
},
});
if (Array.isArray(body.permission_ids) && body.permission_ids.length) {
await tx.role_permissions.createMany({
data: (body.permission_ids as number[]).map((pid) => ({
role_id: created.id,
permission_id: pid,
})),
});
}
return created;
});
await logAudit({
request,
authData: request.authData,
@@ -103,6 +126,12 @@ export default async function rolesRoutes(
"/:id",
{ preHandler: requirePermission("settings.roles") },
async (request, reply) => {
// Privilege-escalation guard: only an admin may modify roles or their
// permission sets.
if (request.authData!.roleName !== "admin") {
return error(reply, "Pouze administrátor může upravovat role", 403);
}
const id = parseId(request.params.id, reply);
if (id === null) return;
const parsed = parseBody(UpdateRoleSchema, request.body);
@@ -112,6 +141,13 @@ export default async function rolesRoutes(
const existing = await prisma.roles.findUnique({ where: { id } });
if (!existing) return error(reply, "Role nenalezena", 404);
// Mirror the DELETE guard: the built-in admin role's permission set is
// immutable (it is resolved as "all permissions" anyway). Block any
// attempt to rewrite it via permission_ids → no lockout / escalation.
if (existing.name === "admin" && Array.isArray(body.permission_ids)) {
return error(reply, "Oprávnění role admin nelze měnit", 400);
}
await prisma.roles.update({
where: { id },
data: {