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

@@ -32,7 +32,7 @@ model attendance {
users users @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "attendance_ibfk_1")
attendance_project_logs attendance_project_logs[]
@@index([user_id, shift_date], map: "idx_attendance_user_date")
@@unique([user_id, shift_date], map: "idx_attendance_user_date")
@@index([user_id, departure_time], map: "idx_attendance_user_departure")
@@index([project_id], map: "idx_project_id")
}
@@ -46,6 +46,7 @@ model attendance_project_logs {
hours Int? @db.UnsignedInt
minutes Int? @db.UnsignedInt
attendance attendance @relation(fields: [attendance_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
projects projects? @relation(fields: [project_id], references: [id], onDelete: SetNull, onUpdate: NoAction)
@@index([attendance_id], map: "idx_attendance_project_logs_aid")
@@index([project_id], map: "idx_project_id")
@@ -104,7 +105,7 @@ model company_settings {
quotation_prefix String? @db.VarChar(20)
default_currency String? @default("CZK") @db.VarChar(10)
default_vat_rate Decimal? @default(21.00) @db.Decimal(5, 2)
uuid String? @db.VarChar(36)
uuid String? @unique @db.VarChar(36)
modified_at DateTime? @db.DateTime(0)
is_deleted Boolean? @default(false)
sync_version Int? @default(0)
@@ -165,7 +166,7 @@ model invoice_items {
model invoices {
id Int @id @default(autoincrement())
invoice_number String? @db.VarChar(50)
invoice_number String? @unique(map: "idx_invoices_number_unique") @db.VarChar(50)
order_id Int?
customer_id Int?
status String? @default("issued") @db.VarChar(30)
@@ -288,7 +289,7 @@ model order_sections {
model orders {
id Int @id @default(autoincrement())
order_number String? @db.VarChar(50)
order_number String? @unique(map: "idx_orders_number_unique") @db.VarChar(50)
customer_order_number String? @db.VarChar(100)
attachment_data Bytes?
attachment_name String? @db.VarChar(255)
@@ -340,7 +341,7 @@ model project_notes {
model projects {
id Int @id @default(autoincrement())
project_number String? @db.VarChar(50)
project_number String? @unique @db.VarChar(50)
name String? @db.VarChar(255)
customer_id Int?
responsible_user_id Int?
@@ -352,6 +353,7 @@ model projects {
notes String? @db.Text
created_at DateTime? @default(now()) @db.DateTime(0)
modified_at DateTime? @db.DateTime(0)
attendance_project_logs attendance_project_logs[]
project_notes project_notes[]
users users? @relation(fields: [responsible_user_id], references: [id], onUpdate: NoAction, map: "fk_projects_responsible_user")
customers customers? @relation(fields: [customer_id], references: [id], onUpdate: NoAction, map: "projects_ibfk_1")
@@ -385,7 +387,7 @@ model quotation_items {
model quotations {
id Int @id @default(autoincrement())
quotation_number String? @db.VarChar(50)
quotation_number String? @unique @db.VarChar(50)
project_code String? @db.VarChar(50)
customer_id Int?
created_at DateTime? @default(now()) @db.DateTime(0)
@@ -434,7 +436,7 @@ model received_invoices {
file_mime String? @db.VarChar(100)
file_size Int? @db.UnsignedInt
notes String? @db.Text
uploaded_by Int? @db.UnsignedInt
uploaded_by Int?
created_at DateTime @default(now()) @db.DateTime(0)
modified_at DateTime @default(now()) @db.DateTime(0)
@@ -446,7 +448,7 @@ model received_invoices {
/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments
model refresh_tokens {
id Int @id @default(autoincrement()) @db.UnsignedInt
user_id Int @db.UnsignedInt
user_id Int
token_hash String @unique(map: "token_hash") @db.VarChar(64)
expires_at DateTime @db.DateTime(0)
replaced_at DateTime? @db.DateTime(0)