patrick
e83a3fbbdd
fix: agent-08 Kiosk-Härtung + 24h-Zeiteintrag-Bug
...
- fix: worked_minutes nutzt jetzt Sekunden statt Minuten für Overnight-Vergleich
(end < start statt end <= start) – verhindert 24h-Anzeige bei Schnell-Stempel
in derselben Minute (z.B. 23:34:46 → 23:34:48)
- fix: _check_arbzg() gleicher Sec-basierter Fix
- fix: KioskDeviceStatus Enum values_callable → kiosk list crasht nicht mehr
- feat: kiosk rotate-key CLI-Kommando (Status→pending, Re-Enrollment)
- feat: Kiosk-Settings in CompanyOut/CompanyUpdate Schema (require_approval,
track_current_user, heartbeat_interval_sec)
- feat: Kiosk-Terminal-Einstellungsblock in CompanySettingsPage (🖥️ )
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-05-25 01:42:08 +02:00
patrick
0dd736c220
fix: require_role in special_assignments router ohne extra Depends() wrapping
...
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-05-25 00:59:16 +02:00
patrick
d60349df67
feat: Sondervertretungs-Faktoren (special_assignments)
...
- Neues Model SpecialAssignment mit AssignmentMode (fza|payroll|both)
- CRUD-Endpunkte unter /users/{id}/special-assignments
- Payroll-Report: GET /reports/special-assignments/payroll?year=&month=
- Migration 0029: special_assignments Tabelle + btree_gist Overlap-Constraint
- _recalculate_overtime_balance berücksichtigt FZA-Faktoren
- Frontend: Sondervertretungs-Zeiträume im UsersPage Edit-Modal
- Frontend: ReportsPage neuer Tab 'Sondervertretungen' mit Payroll-Tabelle + CSV-Export
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-05-25 00:55:47 +02:00
patrick
1170e59e49
fix: AuditLog bei FZA-Stornierung mit korrektem old_status und fza_hours_refunded-Flag
...
- old_value enthält den tatsächlichen Status vor der Stornierung (pending/approved)
- new_value enthält cancelled_by_name für bessere Lesbarkeit
- fza_hours_refunded: true wird gesetzt wenn ein genehmigter FZA storniert wurde
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-05-25 00:22:39 +02:00
patrick
345002944e
feat: Freizeitausgleich-Lücken geschlossen (Gap 1-3) + konfigurierbare Schwellwerte
...
Gap-1: Überziehschutz für Überstundenkonto
- Company.overtime_overdraft_allowed (default: true) – blockiert FZA wenn deaktiviert
- Company.overtime_warning_threshold_hours (default: 0) – Warnung wenn Konto unter Schwelle fällt
- warnings[] jetzt in approve_absence Response (AbsenceApproveOut)
- Migration 0028_overtime_fza_config.py
Gap-2: total_hours wird bei Zeiteintrag-Genehmigung neu berechnet
- time_service.approve_entry() ruft _recalculate_overtime_balance() auf
- last_calculated Timestamp wird gesetzt
Gap-3: Stornierung genehmigter FZA-Anträge bucht taken_hours zurück
- _refund_overtime() Helfer hinzugefügt
- cancel_absence() erlaubt jetzt HR/Admin auch genehmigte Abwesenheiten zu stornieren
- DELETE /absences/{id} gibt jetzt AbsenceOut zurück (statt 204)
- Mitarbeiter können genehmigte FZA-Anträge nicht selbst stornieren (409)
Frontend:
- CompanySettingsPage: neuer Abschnitt 'Freizeitausgleich' mit Toggle + Schwellwert-Eingabe
Tests: backend/tests/test_fza.py mit 6 Tests (alle 3 Gaps)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-05-25 00:08:03 +02:00
patrick
c8804efbd0
feat: Admin-Toggle für mobile Zeiterfassung
...
Backend:
- Company.mobile_stamping_enabled (BOOLEAN DEFAULT TRUE)
- CompanyOut + CompanyUpdate: neues Feld
- Migration 0027: companies.mobile_stamping_enabled
Frontend Desktop (CompanySettingsPage):
- Abschnitt 'Mobile-Ansicht' mit Toggle-Switch
- Speichert via PATCH /companies/me
Frontend Mobile (MobileStampScreen):
- Lädt mobile_stamping_enabled aus GET /companies/me
- Deaktiviert: Hinweis-Banner statt Buttons
('Einstempeln nicht verfügbar – bitte Kiosk/Desktop nutzen')
- Aktiviert: normales Verhalten
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-05-24 23:52:46 +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
30828c69e9
feat: agent-02-kiosk Phase 2A - Auth endpoints (PIN/NFC/QR/List)
...
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-05-24 12:49:09 +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
1db7164837
fix(security): SSRF-Schutz für CalDAV-URLs
...
Neue _validate_caldav_url() Funktion in caldav_service.py:
- Prüft Schema (nur http/https erlaubt)
- Blockiert private IP-Ranges (RFC 1918, Loopback, Link-local)
- DNS-Auflösung + Prüfung der aufgelösten IP (DNS-Rebinding-Schutz)
- Blockiert: 10/8, 172.16/12, 192.168/16, 127/8, 169.254/16, fc00::/7 etc.
Validierung in allen drei HTTP-Helpers (_http_put, _http_delete, _http_propfind)
sowie beim Speichern in caldav.py Router (company + user config) – doppelter Schutz.
Getestet: 8 böse URLs geblockt, 2 legitime URLs erlaubt (10/10 OK)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-05-24 12:39:59 +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
981bde3dc1
feat(kiosk): Migration 0021 – Ed25519-Auth, Status-Enum, Heartbeat, IP-Whitelist
...
Migration 0021_kiosk_security (eingeklinkt zwischen 0020 und 0022):
- kiosk_devices: token_hash + is_active → status enum(pending/approved/revoked)
- kiosk_devices: public_key, key_algorithm, enrollment_token_hash/expires_at
- kiosk_devices: last_heartbeat_at, client_version, offline_queue_size
- kiosk_devices: current_user_id (DSGVO), ip_whitelist (CIDR)
- companies: kiosk_require_approval, kiosk_track_current_user, kiosk_heartbeat_interval_sec
Model KioskDevice: komplett überarbeitet (KioskDeviceStatus Enum)
Model Company: 3 neue Kiosk-Felder
Bestehende Geräte: status=revoked (müssen neu enrolled werden)
Existing servers: SQL manuell angewendet (Alembic skip bei inserted migrations)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-05-24 12:08:33 +02:00
patrick
62ef6c2a11
feat: Live-Stempel-Uhr, Break-UI, Balance-Widget, Approval-Queue + PDF-Export (WeasyPrint)
...
Frontend (TimeTrackingPage):
- Live-Arbeitsuhr (HH:MM:SS) während eingestempelt
- Break-Start/End-Buttons mit laufender Pausenuhr
- Wochen-Balance-Widget (gearbeitet / erwartet / überstunden)
- Approval-Queue Tab für Manager/HR/Admin (pending entries genehmigen/ablehnen)
Backend (Reports):
- weasyprint>=61.0 in requirements.txt
- 3 neue PDF-Export-Tests (Zeit, Abwesenheit, Überstunden)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-05-24 11:59:32 +02:00
patrick
eb122802b2
fix: 8 pre-existing Test-Fehler behoben
...
1. Krankmeldungen an Wochenenden erlaubt (working_days=0 für SICK)
Medizinisch korrekt: Krankmeldungen können auch Wochenendtage umfassen.
Behebt: test_create_absence_sick_auto_approved, test_quick_sick_*,
test_pull_includes_today_absence_with_category, test_sick_stats_*
2. Self-Approval-Schutz in Tests berücksichtigt
abs_approver_headers / time_approver_headers: zweiter Admin je Company.
Behebt: test_approve_absence, test_balance_deducted_after_approve, test_approve_entry
3. test_export_invalid_format: "pdf" → "xml" (pdf ist jetzt valides Format)
Ergebnis: 134/134 passed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-05-23 22:51:50 +02:00
patrick
dd3e069466
fix: router db.refresh() nach commit bricht RLS-Kontext
...
SET LOCAL Werte (bypass_rls, company_id) sind transaktions-gebunden.
Nach db.commit() ist der Kontext weg – ein nachfolgendes db.refresh()
läuft in einer neuen Transaktion ohne RLS-Kontext und liefert 0 Rows.
Da expire_on_commit=False gesetzt ist, sind alle Instanz-Attribute
nach dem Commit bereits im Speicher vorhanden. Die expliziten
db.refresh()-Aufrufe nach db.commit() in allen Routers sind daher
redundant und wurden entfernt.
test_rls.py: 6 neue Tests beweisen DB-seitige Mandanten-Isolation.
conftest.py: _apply_rls() wendet RLS-Policies auf Test-DB an.
migrations/0024: korrigiert auf op.execute(text()) API.
migrations/env.py: SET LOCAL außerhalb Transaktion entfernt.
Ergebnis: 8 failed (pre-existing), 126 passed – identisch zur Baseline vor RLS.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-05-23 22:34:48 +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
patrick
fbc04bc2c0
agent-07 phase 2: fix test isolation + CSV import UI
...
- Fix conftest.py: commit after each request in override_get_db so
preview_csv's rollback no longer wipes the shared registered_user
(root cause of 401 cascade across test_user_import + test_personnel_number)
- Fix limiter.enabled=False in client fixture (blocks rate-limit 429)
- Fix user_import_service: allow reactivation when personnel number
belongs to the same user being reactivated
- Fix test_personnel_number: use PATCH /companies/me (not /companies/{id})
and add try/finally cleanup for personnel_number_required flag
- Frontend UsersPage: add CSV import modal with template download,
preview/validation table, and guarded apply button
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-05-23 21:07:32 +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