9 Commits

Author SHA1 Message Date
patrick 4dc69137dd security: H-1 settings-Whitelist + H-5 UUID-Guard + H-6 DNS-Pinning + H-7 Heartbeat-Timing
H-1: company.settings als typisiertes Sub-Schema
- schemas/company.py: CompanySettingsUpdate mit extra=forbid
- Nur bekannte Keys (carryover_expires_month/day) erlaubt
- Unbekannte Keys → HTTP 422

H-5: SQL-Injection defensiv absichern
- dependencies.py: UUID-Round-Trip str(_uuid.UUID(...)) + Sicherheitskommentar

H-6: CalDAV DNS-Rebinding-Schutz
- caldav_service.py: PinnedIPTransport — IP einmal auflösen, beim Request fixieren
- _validate_caldav_url gibt aufgelöste IP zurück
- Alle HTTP-Methoden nutzen PinnedIPTransport

H-7: Heartbeat-Timestamp nach Route-Logik
- kiosk_security.py: last_heartbeat_at-Update aus Dependency entfernt
- kiosk_service.py: Update erst in process_heartbeat() nach erfolgreicher Auth

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 11:35:18 +02:00
patrick 654258f13e security: M-2 HttpOnly-Cookie + M-4 TrustedHost-Warning + M-5 TOTP-Lockout + M-7 zentraler get_client_ip()
M-2: Refresh-Token als HttpOnly SameSite=Strict Cookie
- auth.py: _set_refresh_cookie/_delete_refresh_cookie Helpers
- Alle Auth-Endpoints (login, totp/login, refresh, logout) nutzen Cookie
- schemas/auth.py: refresh_token in Request/Response optional
- AuthContext.tsx: kein refresh_token in localStorage
- api/client.ts: credentials:include, kein Token-Body beim Refresh

M-4: TrustedHostMiddleware Warning in Production
- main.py: Startup-Warning wenn is_production + kein ALLOWED_HOSTS

M-5: TOTP-Fehlversuche Redis-Lockout
- auth.py: _check/_record/_clear_totp_lockout; 5 Versuche → 15 min Sperre

M-7: Zentraler get_client_ip()-Helper
- core/dependencies.py: get_client_ip() mit X-Real-IP → X-Forwarded-For → client.host
- hours_payouts.py, absences.py, busylight.py: request.client.host ersetzt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 11:25:24 +02:00
patrick 06bb1c1664 feat: FZA Einzelstunden + Security-Fixes (K-1–K-5, H-2–H-4, M-1/M-3/M-6)
FZA Einzelstunden:
- Absence.fza_hours (Numeric 5,2) — FZA in Stunden statt Tagen
- Migration 0032: fza_hours Spalte in absences
- AbsenceCreate/AbsenceOut Schema um fza_hours erweitert
- absence_service: _deduct/_refund_overtime nutzt fza_hours direkt wenn gesetzt
- Frontend: Tage/Stunden-Toggle im FZA-Antrag-Modal

Security K-1: Privilege Escalation via PATCH /users/{id}.role
- user_service: Whitelist für Rollenänderungen, SUPER_ADMIN nur durch SUPER_ADMIN
- Letzter COMPANY_ADMIN gegen Selbst-Demotion gesichert

Security K-2: Kiosk-IP-Whitelist hinter nginx
- kiosk_security: _get_client_ip() liest X-Real-IP statt request.client.host

Security K-3: Kiosk-PIN Brute-Force-Schutz
- kiosk_auth_service: Redis-Lockout nach 5 Fehlversuchen (15 min)

Security K-4: TOTP-Setup-Hijacking
- auth router: /totp/setup abgelehnt wenn TOTP bereits aktiv

Security K-5: Separater Fernet-Key
- config: SECRET_KEY_DATA Feld (optional, Fallback auf SECRET_KEY)
- crypto: get_fernet_key() mit Warning bei fehlendem SECRET_KEY_DATA

Security H-2: Vacation Balance nur HR/Admin
- absences router: PATCH /balance nur noch HR/COMPANY_ADMIN/SUPER_ADMIN + AuditLog

Security H-3: Rate-Limits auf /auth/refresh + /auth/logout
- auth router: 30/min auf refresh, 60/min auf logout

Security H-4: Login-Failure-Logging + Lockout
- auth_service: Redis-Counter, Lockout nach 10 Versuchen (15 min)
- AuditLog für login_success und login_failed

Security M-1: Nginx Security-Header
- nginx.conf: X-Frame-Options, X-Content-Type-Options, CSP, Referrer-Policy, X-XSS-Protection, Permissions-Policy

Security M-3: AuditLog bei Rollenänderungen
- user_service: action=role_changed mit old/new role

Security M-6: create_all nur in Development
- main.py: Base.metadata.create_all nur wenn not settings.is_production

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 11:13:42 +02:00
patrick 62c4e742ab security: 9 Findings aus Security-Audit behoben (CRITICAL + HIGH + MEDIUM)
CRITICAL:
- C-1: LDAP tls_verify Default False → True (MITM-Schutz)
- C-2: TOTP-Secret Fernet-verschlüsselt in DB (statt Plaintext)
  - core/crypto.py: encrypt_value() / decrypt_value() helper
  - Migration 0026: totp_secret VARCHAR(64→500), ldap tls_verify default=true
  - _totp_plain() helper mit Legacy-Fallback für bestehende Werte

HIGH:
- H-1: Kiosk Nonce-Cache asyncio.Lock (Race Condition behoben)
- H-2: File-Upload-Limit 10 MB (import_kimai.py + users.py CSV-Import)
- H-3: CORS allow_methods/allow_headers explizit eingeschränkt (war *)
- H-4: TrustedHostMiddleware aktiviert wenn ALLOWED_HOSTS gesetzt

MEDIUM:
- M-1: IP-Logging nutzt X-Forwarded-For hinter nginx-Proxy
- M-4: Audit-Log für password_changed, totp_enabled, totp_disabled
- M-5: CalDAV verify_ssl in Production erzwungen (_effective_verify_ssl)

152/152 Tests grün

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 19:45:09 +02:00
patrick 7e19311d2a feat: CALDAV_ALLOWED_CIDRS Whitelist für interne CalDAV/Nextcloud-Server
Interne Nextcloud-Instanzen im LAN können jetzt per .env-Variable
von der SSRF-Blockliste ausgenommen werden.

Beispiel in .env:
CALDAV_ALLOWED_CIDRS=192.168.1.0/24,10.10.5.50/32

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 12:53:22 +02:00
patrick 094863f94b feat: agent-02-kiosk Phase 1 - NFC UID migration + session service
- Migration 0025: kiosk_nfc_uid column on users table with partial unique index per company
- User model: kiosk_nfc_uid field after personnel_number
- New service: kiosk_session_service.py (Redis-based 15min sessions)
- New core module: app/core/redis.py (sync Redis client with ping-test)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 12:45:47 +02:00
patrick 0f83d13c0c feat(kiosk): Stufe 2 – Ed25519-Auth, CLI-Tool, neue KioskDevicesPage
2A – Backend Ed25519-Verifizierung:
- app/core/kiosk_security.py (NEU): verify_kiosk_request() Dependency
  - Timestamp-Check (30s Drift), Nonce-Cache (Redis/In-Memory), IP-Whitelist
  - Ed25519-Signatur über METHOD+PATH+TIMESTAMP+NONCE+sha256(BODY)
  - PEM + OpenSSH Key-Format unterstützt
- app/routers/kiosk.py: approve/revoke Endpunkte, POST /heartbeat (Ed25519-signiert)
- app/services/kiosk_service.py: token-basierte Methoden entfernt, approve/revoke/heartbeat
- app/schemas/kiosk.py: KioskDeviceOut mit heartbeat_status, HeartbeatRequest/Response

2B – CLI-Tool:
- cli.py (NEU, 529 Zeilen): Typer-CLI mit kiosk add/list/approve/revoke/info
  - Public-Key-Fingerprint (SHA256), Rich-Tabellen, CIDR-Validierung
  - Direkter DB-Zugriff mit RLS-Bypass

2C – Frontend:
- KioskDevicesPage.tsx: Zwei-Tab-Layout (Wartet/Aktiv), Status-Ampel,
  Auto-Refresh 30s, Ed25519-Workflow (kein Token mehr)
- Layout.tsx: KioskHealthBadge (online/total, 30s Refresh, nur COMPANY_ADMIN)

requirements.txt: typer>=0.12.0, rich>=13.7.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 12:13:46 +02:00
patrick 6d4b8a9f17 agent-rls: PostgreSQL Row Level Security für Mandanten-Isolation
- Migration 0024: RLS + FORCE RLS auf 18 Tabellen
  - Direkte company_id-Policies: users, departments, companies, absence_types,
    audit_logs, kiosk_devices, ldap_configs, smtp_configs, caldav_company_configs,
    work_schedules, overtime_balances
  - JOIN-Policies (user_id → company_id): absences, sessions, password_resets,
    time_entries, vacation_balances, caldav_user_configs
  - public_holidays ausgenommen (globale Referenztabelle)
- database.py: get_db setzt bypass_rls='on' als Default (Auth-Endpoints unverändert)
- dependencies.py: get_current_user setzt app.company_id + bypass_rls='off'
  für alle nicht-SUPER_ADMIN Rollen
- migrations/env.py: Alembic-Migrationen nutzen bypass_rls='on'
- tests/conftest.py: override_get_db setzt bypass_rls='on' für Test-Session

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 21:57:58 +02:00
sysops 1fedd683e0 Initial commit – TimeMaster Zeiterfassung & HR-Tool
Stand: agent-06 (Audit-Log), agent-05 (Krankmeldung), agent-07 Phase 1 (Personalnummer),
Busylight-Pull-Integration, TOTP/2FA, Abwesenheiten, Zeiterfassung, Kiosk-Grundgerüst.
Migrations 0001–0023 deployed auf 192.168.1.137 + .164.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 20:03:27 +02:00