100 Commits
Author SHA1 Message Date
patrickandClaude Sonnet 5.5 c02338eb7a fix(security): Dienst als Systemuser auf 127.0.0.1, TLS-Erneuerung per sudo
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 00:20:26 +01:00
patrickandClaude Sonnet 5.5 8a55e183fd test(ha): Token deaktivierter Firma liefert 404
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 00:17:59 +01:00
patrickandClaude Sonnet 5.5 b6eda4e2d5 fix(security): Krankheitsdaten im Kalender maskieren, Art.-15-Export nur HR/Admin, Login-Enumeration, Session-Invalidierung bei Passwortwechsel
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 23:05:46 +01:00
patrickandClaude Sonnet 5.5 8672f92ee5 fix(security): Audit-Funde M-1,M-2,M-3,M-6,M-7,N-1..N-3 (Client-IP-Trust, AuditLog company_id, Token-Feeds, Mass-Assignment, Kiosk-Heartbeat)
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 23:04:07 +01:00
patrickandClaude Sonnet 5.5 a3470a135c fix(security): PDF-Export ohne Ressourcenzugriff, HTML-Escaping, CSV/XLSX Formula-Injection
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 23:02:39 +01:00
patrickandClaude Sonnet 5.5 43cdece01f docs: DEVLOG-Einträge ergänzt
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 14:55:35 +01:00
patrickandClaude Sonnet 5.5 73784248d4 test(busylight): Pull-Token deaktivierter Firma liefert 401
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 14:55:35 +01:00
patrickandClaude Sonnet 5.5 cd7aaa9ce2 fix(busylight): Pull-Token deaktivierter Firmen ablehnen
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 13:53:51 +01:00
patrickandClaude Sonnet 5.5 f0b436c91f feat(portal): read-only Firmensummen für das Buchhaltungsportal (Token-Endpunkt)
GET /portal/summary liefert Resturlaub und Überstundenguthaben als Firmensummen, keine Einzelpersonen und keine
Krankheitsdaten. Firmen-Token (SHA-256) wie beim Busylight, Verwaltung unter /companies/me/portal-token, Migration 0049.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-29 19:40:49 +01:00
patrickandClaude Sonnet 5.5 80cee20fe5 docs: DEVLOG-Einträge ergänzt
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 22:57:21 +01:00
patrickandClaude Sonnet 5.5 ed5c52d8b3 feat(ha): read-only Arbeitszeit-Status für Home Assistant (Token-URL)
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 22:33:54 +01:00
patrickandClaude Sonnet 5.5 2f75ebc5a7 docs: DEVLOG-Einträge ergänzt
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 22:25:37 +01:00
patrickandClaude Sonnet 5 1d163cc299 fix(security): Audit-Nachzug – Rate-Limit, X-Real-IP-Vertrauen, Indizes
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Security-Audit 2026-09-10 (siehe Ed25519-Reinstall-Commits):
- Rate-Limit 10/min auf POST /kiosk/my-qr-token (Q-1) – kompromittiertes
  Employee-JWT konnte sonst unbegrenzt QR-Login-Tokens erzeugen.
- _get_client_ip vertraut X-Real-IP nur noch, wenn die Verbindung selbst
  von 127.0.0.1 kommt (I-1) – sonst könnte ein Client den Header selbst
  setzen und die Kiosk-IP-Whitelist umgehen.
- Migration 0047: Unique-Index auf kiosk_devices.device_token_hash (B-2,
  war Seq-Scan pro Bearer-Request), Index auf time_entries.kiosk_device_id
  (K-1). RLS auf kiosk_devices bereits seit Migration 0024 vorhanden,
  K-1-Verdacht einer RLS-Lücke damit ausgeräumt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dzdm3EWeNdYFUuXFRJryA1
2026-09-10 15:33:12 +02:00
patrickandClaude Sonnet 5 7873236de3 feat(time): kiosk_device_id an TimeEntry – dokumentiert Stempel-Standort
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Bisher wurde bei Kiosk-Stempelungen nur die grobe Quelle ("kiosk")
gespeichert, nicht welches Gerät/welcher Standort. Bei mehreren Eingängen/
Filialen war nicht nachvollziehbar, wo ein Mitarbeiter sich eingestempelt
hat. Migration 0046: time_entries.kiosk_device_id (FK kiosk_devices,
SET NULL). Wird nur beim Einstempeln gesetzt (Ursprung des Eintrags), nicht
bei Aus-/Pause-Aktionen überschrieben. TimeEntryOut.kiosk_device_id neu.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dzdm3EWeNdYFUuXFRJryA1
2026-09-10 15:06:15 +02:00
patrickandClaude Sonnet 5 71db3bdd57 feat(kiosk): QR-Login-Token-Endpunkt für Employee-App
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
POST /kiosk/my-qr-token (normale JWT-Auth) – Mitarbeiter fordert einen 5min
gültigen, einmalig einlösbaren QR-Token an, den die Employee-App als QR-Code
zeigt. Kiosk scannt ihn (POST /kiosk/auth/qr, bereits vorhanden) und meldet
den Mitarbeiter an. Backend-Funktion (generate_qr_token) existierte schon,
war aber nie über einen Endpunkt erreichbar. Test für den kompletten Ablauf
inkl. Einmaligkeit (zweiter Versuch mit selbem Token → 401).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dzdm3EWeNdYFUuXFRJryA1
2026-09-10 14:47:45 +02:00
patrickandClaude Sonnet 5 d80fc59f17 feat(kiosk): Bearer-Geräte-Erstellung im Portal (für QR-Einrichtung)
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Neuer Endpunkt POST /kiosk/devices/bearer (Admin-JWT statt CLI) – erzeugt
Token serverseitig, gibt ihn nur in dieser einen Antwort im Klartext
zurück (KioskBearerDeviceCreated{device, token}), DB speichert nur den
Hash. Ermöglicht QR-Code-Einrichtung direkt aus dem Portal statt CLI-only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dzdm3EWeNdYFUuXFRJryA1
2026-09-10 13:22:02 +02:00
patrickandClaude Sonnet 5 370358d117 fix(cli): RLS-Refresh-Bug in kiosk add/rotate-key
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Security Audit / Python Dependency Audit (push) Canceled after 0s
session.refresh() nach commit scheitert an RLS (SET LOCAL app.bypass_rls
verfällt mit dem commit, siehe project_rls_bypass_commit_pitfall) – trat
beim ersten echten Bearer-Geräte-Test auf ("Could not refresh instance").
Beide refresh()-Aufrufe entfernt, nicht nötig: alle in der Erfolgsmeldung
angezeigten Felder sind bereits im Python-Objekt gesetzt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dzdm3EWeNdYFUuXFRJryA1
2026-09-10 13:12:17 +02:00
patrickandClaude Sonnet 5 c15fe81498 fix(tests): RLS-Bypass + Refresh-Fehler in Bearer-Auth-Tests
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Direkte DB-Inserts über db_session brauchen SET LOCAL app.bypass_rls='on'
pro Transaktion (verfällt nach commit, siehe [[project_rls_bypass_commit_pitfall]]).
db_session.refresh() nach commit schlug aus demselben Grund fehl – entfernt,
nicht nötig. Bestehender test_heartbeat_missing_headers erwartete 422,
liefert jetzt korrekt 401 (Header sind seit Bearer-Auth-Modus optional).
229/229 Tests grün auf 137.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dzdm3EWeNdYFUuXFRJryA1
2026-09-10 02:16:34 +02:00
patrickandClaude Sonnet 5 f4ff11a80e feat(kiosk): Bearer-Token-Auth für Android-Tablets hinter VPN
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Neuer, einfacherer Geräte-Auth-Modus (key_algorithm="bearer_vpn") als
Alternative zum bestehenden Ed25519-Request-Signing. Bestehende PC-Terminals
(key_algorithm="ed25519") bleiben unverändert – verify_kiosk_request()
verzweigt jetzt am Authorization-Header: "Bearer <token>" → einfacher Pfad
(nur Token-Hash + Status + IP-Whitelist, kein Timestamp/Nonce/Signatur),
sonst weiterhin die 4 X-Kiosk-*-Header wie bisher.

Hintergrund: Terminals+Tablets laufen jetzt hinter WireGuard-VPN, die
ursprüngliche MITM-Härtung (Ed25519-Signing) ist für Tablets keine
Kernanforderung mehr. Ermöglicht ein deutlich einfacheres Flutter-Kiosk-App-
Projekt ohne Client-seitige Krypto.

- Migration 0045: kiosk_devices.device_token_hash
- backend/cli.py: `kiosk add --auth-mode bearer` generiert Token, gibt es
  einmalig aus (analog Enrollment-Setup-URL bisher)
- 4 neue Tests in test_kiosk_security.py (gültig/ungültig/IP-Whitelist/revoked)

Vollständiger Plan: /home/sysops/.claude/plans/staged-marinating-dove.md

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dzdm3EWeNdYFUuXFRJryA1
2026-09-10 02:13:16 +02:00
patrickandClaude Sonnet 5 d5b2fc75d6 fix(kiosk): TOCTOU-Race beim QR-Login (Audit-Finding K-1)
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
login_qr() las den Redis-Token per GET und löschte ihn danach separat per
DELETE – zwei parallele Requests mit demselben QR-Token konnten beide vor
dem Löschen lesen und je eine Session erzeugen (Doppel-Login statt echtem
Einmal-Token). Fix: GETDEL (atomar seit Redis 6.2, Server läuft 8.0.2).

K-2 (Cross-Tenant-Redeem) aus demselben Audit betrifft keinen realen
Code-Pfad – company_id-Vergleich in login_qr() war bereits korrekt, und die
Kiosk-Geräte-Enrollment "QR-Redeem"-Annahme existiert nicht als HTTP-Flow
(Enrollment läuft ausschließlich über authentifizierte CLI). Verifiziert
vor Weiterplanung der Android-Tablet-Kiosk-Architektur.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dzdm3EWeNdYFUuXFRJryA1
2026-09-10 01:54:16 +02:00
patrickandClaude Sonnet 5 2e27a7fcae fix(time): Mikrosekunden bei Zeitstempeln kappen
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
start_time/end_time/break_start wurden mit vollen Mikrosekunden gespeichert
(datetime.now(timezone.utc).time() ohne microsecond=0), was Clients ohne
Mikrosekunden-Parsing brechen konnte (siehe Mobile-App-Crash). Sekunden-
Genauigkeit reicht für Zeiterfassung.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dzdm3EWeNdYFUuXFRJryA1
2026-09-09 23:59:25 +02:00
patrickandClaude Sonnet 5 fd752878b0 test: P1-Coverage für Scheduler-Idempotenz, Bradford-Fensterrand, FZA-Faktor+Kappung, Cross-Company-RLS
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Ahyx6D3r7G1EuAc42nezn
2026-09-03 02:10:19 +02:00
patrickandClaude Sonnet 5 9afc29a8bb fix(redis): Timeouts für Async-Pool + Start-Health-Check-Log
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
get_async_redis() hatte keine socket_connect_timeout/socket_timeout/
max_connections – ein gehängtes Redis hätte Requests unbegrenzt
blockiert statt schnell zu failen. Zusätzlich einmaliger Ping im
Lifespan-Startup, nur zur Log-Sichtbarkeit (blockiert Boot nicht).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Ahyx6D3r7G1EuAc42nezn
2026-09-03 01:58:33 +02:00
patrickandClaude Sonnet 5 107cba99f9 fix(redis): INCR+EXPIRE atomar via Pipeline (MULTI/EXEC)
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Login-, TOTP- und Kiosk-PIN-Lockout zählten Fehlversuche mit separaten
INCR/EXPIRE-Calls. Zwischen beiden konnte der Fail-Counter-Key kurzzeitig
ohne TTL bestehen bleiben (Crash-Fenster) - nicht atomar. Jetzt per
redis.pipeline(transaction=True) als MULTI/EXEC.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Ahyx6D3r7G1EuAc42nezn
2026-09-03 00:12:44 +02:00
patrickandClaude Sonnet 5 56f6f16e27 fix(redis): Pool-Rollout vervollständigen + TOTP-Lockout fail-closed mit 503
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Redis-Review deckte auf, dass der Pool-Fix vom letzten Commit nur
totp_login/kiosk_security erreichte. Login/Refresh (auth_service.py) und
PIN/NFC/QR-Kiosk-Login (kiosk_auth_service.py) öffneten weiterhin pro
Request eine neue aioredis-Verbindung. Zusätzlich nutzten
kiosk_session_service.py und public_stamp_session_service.py den
*sync* Redis-Client aus async-Code – blockierender Socket-Call im
Event-Loop bei jedem Kiosk-/Stempel-Request.

Alle auf get_async_redis() umgestellt. TOTP-Lockout wirft jetzt 503
statt eines ungefangenen 500 bei Redis-Ausfall (RedisError explizit
gefangen, eigene HTTPExceptions unberührt).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Ahyx6D3r7G1EuAc42nezn
2026-09-03 00:09:35 +02:00
patrickandClaude Sonnet 5 ac496a5f9b fix(absences): POST /absences/ reicht warnings jetzt an den Client durch
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
create_absence() gibt (absence, warnings) zurück, der Router verwarf die
Warnungen bisher (im Unterschied zu approve_absence, das sie via
AbsenceApproveOut.warnings durchreicht). Neues AbsenceCreateOut-Schema
analog dazu. Regressionstest über Überschneidungs-Warnung.

Gefunden während Coverage-Arbeit an absence_service.py.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Ahyx6D3r7G1EuAc42nezn
2026-09-02 23:54:02 +02:00
patrickandClaude Sonnet 5 3cf396b972 fix(tests): Testdaten für Jahreswechsel-Regressionstest korrigieren
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
01.01.2028 war ein Samstag – zählt schon als Wochenende, testete den
Fix damit nicht wirklich. 2028/2029-Grenze (Neujahr = Montag) verwendet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Ahyx6D3r7G1EuAc42nezn
2026-09-02 23:51:46 +02:00
patrickandClaude Sonnet 5 548738c9ea fix(absences): Feiertage über Jahreswechsel korrekt aus working_days rausrechnen
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
_get_holiday_dates() fragte nur PublicHoliday für data.start_date.year ab.
Ein Antrag über den Jahreswechsel (z.B. 28.12.-01.01.) rechnete den
Neujahrsfeiertag im Folgejahr daher nicht heraus -> Mitarbeiter verlor
einen Urlaubstag, den er nicht verbraucht hat. Neuer year_to-Parameter
deckt den Jahres-Range ab, beide Aufrufstellen (create_absence,
update_absence) angepasst. Regressionstest ergänzt.

Gefunden während Coverage-Arbeit an absence_service.py/report_service.py.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Ahyx6D3r7G1EuAc42nezn
2026-09-02 23:50:28 +02:00
patrickandClaude Sonnet 5 dd044793c6 fix(tests): compare UUID objects not str in DATEV coverage test
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
report_service.datev_monthly_report compares user.company_id (uuid.UUID)
against the passed company_id with != — a plain str from the JSON API
response never equals the UUID, so it always looked like a mismatch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Ahyx6D3r7G1EuAc42nezn
2026-09-02 23:34:12 +02:00
patrickandClaude Sonnet 5 55d54e0ff0 fix(tests): expire_all() before DATEV bypass query to avoid stale identity map
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Ahyx6D3r7G1EuAc42nezn
2026-09-02 23:32:02 +02:00
patrickandClaude Sonnet 5 21b65087d3 fix(tests): re-set RLS bypass per transaction in coverage gap tests
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
SET LOCAL app.bypass_rls only holds for the current transaction; a prior
commit() silently re-enabled RLS, causing db.get(User/Company, ...) to
return None on the raw db_session (no app.company_id context).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Ahyx6D3r7G1EuAc42nezn
2026-09-02 23:31:20 +02:00
patrickandClaude Sonnet 5 dc4982bb37 fix(tests): year-boundary + response-shape issues in coverage gap tests
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
create_absence's warnings are discarded by the router (only approve_absence
passes them through AbsenceOut.warnings) — test now calls the service
directly and documents this as a separate finding. DATEV test kept within
a single year since absence_service._get_holiday_dates only looks up
holidays for start_date.year (cross-year ranges miss the boundary holiday).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Ahyx6D3r7G1EuAc42nezn
2026-09-02 23:30:35 +02:00
patrickandClaude Sonnet 5 cbb5f467fe test: gezielte Coverage-Tests für kritische Pfade in absence_service/report_service
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Deckt Carryover-Expiry (Integrationspfad), kombinierte Teilzeit+Pro-rata-
Berechnung, FZA-Rückbuchung via Cancellation-Request-Flow, Zwei-Stufen-
Genehmigung inkl. Doppelgutschrift-/Schwellwert-Grenzfall, DATEV-Export
(Feiertag vs. Urlaub) und §3b-Zuschlagskategorisierung (Nachtschicht über
Mitternacht, Sonntag+Feiertag).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Ahyx6D3r7G1EuAc42nezn
2026-09-02 23:28:34 +02:00
patrickandClaude Sonnet 5 aba0b53449 fix(docs): StaticFiles-Mount-Pfad korrigieren (static statt app/static)
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Vorheriger Commit hat nur die Dateien verschoben, main.py zeigte noch
auf den alten Pfad app/static.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Ahyx6D3r7G1EuAc42nezn
2026-09-02 23:12:48 +02:00
patrickandClaude Sonnet 5 4e30c55b6c fix(docs): Swagger-Assets nach backend/static/ verschoben (nginx-Alias)
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Server-nginx hat bereits ein /static/-Alias auf backend/static/ (Uploads),
nicht backend/app/static/. Assets lagen am falschen Pfad -> 404 hinter nginx.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Ahyx6D3r7G1EuAc42nezn
2026-09-02 23:12:28 +02:00
patrickandClaude Sonnet 5 9f9dfb5be3 fix(docs): Swagger/ReDoc-Assets lokal statt von cdn.jsdelivr.net
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Security Audit / Python Dependency Audit (push) Canceled after 0s
/docs blieb leer wenn das CDN vom Client-Netzwerk aus nicht erreichbar
war (Firewall/Proxy) – Backend lieferte korrektes HTML, aber Swagger-UI-
JS/CSS und ReDoc-JS kamen nicht an. Jetzt unter app/static/swagger-ui/
gebündelt und über eigene /docs+/redoc-Routen ausgeliefert (nur Dev,
Production weiterhin ohne interaktive Docs).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Ahyx6D3r7G1EuAc42nezn
2026-09-02 23:11:08 +02:00
patrickandClaude Sonnet 5 9bdce25187 test(coverage): fail-under=50 Gate + XML-Report
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Baseline liegt bei 55% (verifiziert auf 137). Gate mit 5%-Puffer,
kein CI-Job dafür – Tests brauchen echtes Postgres+Redis mit RLS-Setup,
läuft nur remote (SSH), nicht in Gitea Actions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Ahyx6D3r7G1EuAc42nezn
2026-09-02 22:52:42 +02:00
patrickandClaude Sonnet 5 538cbb0437 test(coverage): pytest-cov einbauen (term-missing Report)
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
Kein --cov-fail-under noch gesetzt, erst Baseline auf 137 ermitteln.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Ahyx6D3r7G1EuAc42nezn
2026-09-02 22:47:31 +02:00
patrickandClaude Sonnet 5 c733ddfe40 fix(redis): gepoolten async-Redis-Client statt Connect/Close pro Request
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
TOTP-Login und Kiosk-Nonce-Check öffneten/schlossen bisher pro Request eine
neue aioredis-Verbindung. Neuer get_async_redis()-Pool in core/redis.py wird
von beiden Stellen genutzt, sauberer Shutdown im FastAPI-Lifespan.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Ahyx6D3r7G1EuAc42nezn
2026-09-02 22:29:08 +02:00
patrickandClaude Sonnet 5 52ecd9e5ce chore(routers): toten Router absence.py entfernen
159 Zeilen toter Code, weder in main.py registriert noch importiert.
Absence-Logik lebt in routers/absences.py + services/absence_service.py.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Ahyx6D3r7G1EuAc42nezn
2026-09-02 22:27:24 +02:00
patrickandClaude Sonnet 5 21c7c0bd14 test(kiosk): E2E-Test für Login + Stempel-Flow, Cross-Device-Session-Check
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gis16MnuwkYcivLrSxK1pD
2026-08-27 13:34:39 +02:00
patrickandClaude Sonnet 5 2ce363c422 feat(kiosk): Stempel-Endpunkte für Kiosk-Sessions (fehlten bisher komplett)
Login (PIN/NFC/QR/Liste) lieferte einen session_token, aber es gab keinen
Endpunkt der ihn tatsächlich zum Stempeln nutzt (X-Kiosk-Session-Token war
nur als Konzept in kiosk_session_service dokumentiert, nirgends verdrahtet).

Neu: POST /kiosk/stamp/{in,out,break-start,break-end,status}. Gerät wird
weiterhin per Ed25519 verifiziert (verify_kiosk_request), zusätzlich validiert
_user_from_session() dass der session_token zu genau diesem Gerät gehört und
der User noch aktiv/in der richtigen Firma ist. Reuse von time_service
(stamp_in/out/break_start/break_end/get_today), source=KIOSK.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gis16MnuwkYcivLrSxK1pD
2026-08-27 13:32:46 +02:00
patrickandClaude Sonnet 5 3650da8022 fix(kiosk): NFC-Login verlangt Pflicht-PIN als Zweitfaktor (Security-Audit K-3)
Bisher genügte die reine NFC-UID zum Einstempeln. Getestete Reader-Hardware
(günstiger USB-HID-RFID-Leser, EM4100 125kHz) liefert nur eine unverschlüsselte,
trivial klonbare Chip-ID - identisch zum in security_audit_kiosk_qr_nfc_2026_05_26
(K-3) beschriebenen Risiko, das bisher offen war.

- login_nfc() verlangt jetzt PIN, nutzt denselben Brute-Force-Lockout wie
  login_pin (keyed auf nfc_uid statt Personalnummer)
- Neuer Endpunkt POST /users/{id}/kiosk-nfc (Admin/HR) zum Zuordnen einer
  Karte zu einem Mitarbeiter - existierte bisher gar nicht, kiosk_nfc_uid
  war nur im Model vorhanden, nirgends setzbar
- Company-interner Unique-Check (eine Karte = ein Mitarbeiter)

Kein bestehendes Frontend nutzt NFC-Login bisher, daher kein Breaking Change.
Höhere Sicherheitsstufe (NTAG424 SUN, klon-resistent) bleibt vorgemerkt für
späteren Hardware-Wechsel (aktueller Reader kann keine Kryptografie).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gis16MnuwkYcivLrSxK1pD
2026-08-27 13:24:19 +02:00
patrickandClaude Sonnet 5 cfa707d8f9 fix(time): break_minutes DB-Default ergänzen + Retention-Tests korrigiert
Gleiches Muster wie hours_payouts.status (7166bbb): Model hatte nur
Python-default, kein server_default -> Migration 0044 zieht DB-Default
nach. Retention-Tests nutzten fehlenden Wert bei Raw-SQL-Insert und
fehlenden RLS-Bypass zwischen den Transaktionsgrenzen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gis16MnuwkYcivLrSxK1pD
2026-08-27 09:06:59 +02:00
patrickandClaude Sonnet 5 5a9e432a80 feat(dsgvo): Löschkonzept/Aufbewahrungsfristen (Auto-Purge)
Neuer retention_service.py: Lohn-/zeitrelevante Daten (time_entries,
hours_payouts) werden nach konfigurierbarer Frist gelöscht
(company.settings.retention_lohn_years, Default 10 Jahre). Technische
Tabellen mit fester Frist: audit_logs (3 Jahre), abgelaufene
sessions/password_resets (sofort).

Täglicher Scheduler-Job (03:00 Uhr, Redis-Tageslock analog Reminder-Jobs)
plus manuelle Trigger: POST /companies/me/run-retention-purge
(COMPANY_ADMIN/HR, nur eigene Firma) und POST /admin/run-retention-purge
(SUPER_ADMIN, global inkl. technischer Tabellen).

Letzter offener Punkt aus dem DSGVO-Löschkonzept (Art. 15/17 bereits erledigt).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gis16MnuwkYcivLrSxK1pD
2026-08-27 09:04:27 +02:00
patrickandClaude Sonnet 5 7166bbb5aa fix(payouts): status-Spalte mit echtem DB-Default statt nur Python-Default
Model hatte nur default= (ORM-seitig), kein server_default. Test-DB nutzt
Base.metadata.create_all statt Alembic-Migrationen -> Spalte war dort NOT
NULL ohne DB-Default, raw-SQL-Inserts (test_rls.py) schlugen fehl. Prod-DB
hat den Default bereits aus Migration 0042 - hier nur Model nachgezogen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gis16MnuwkYcivLrSxK1pD
2026-08-27 00:30:20 +02:00
patrickandClaude Sonnet 5 456ca1526b fix(security): RLS für special_assignments nachziehen (DSGVO)
special_assignments (Migration 0029) hatte keine Row-Level-Security in
Produktion, obwohl company_id/user_id geführt werden - Tests liefen
trotzdem grün, da conftest.py die Policy bereits simuliert hatte
(gefunden durch postgres-expert-Review). Analog 0039 (hours_payouts)
nachgezogen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gis16MnuwkYcivLrSxK1pD
2026-08-27 00:20:00 +02:00
patrickandClaude Sonnet 5 23f8e634d1 feat(dsgvo): Art. 15 Auskunftsrecht – strukturierter Datenexport pro User
GET /users/{id}/export-data liefert alle personenbezogenen Daten
(Profil, Zeiterfassung, Abwesenheiten, Kommentare, Urlaubs-/Überstunden-
konto, Auszahlungen, AuditLog-Einträge über den User) als JSON-Download.
Eigenes Konto immer erlaubt, fremde Konten nur HR/Admin/Manager der
eigenen Firma. Ergänzt die bestehende Art.-17-Anonymisierung.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CviFgc739S89xS97pvjszj
2026-08-07 08:19:15 +02:00
patrickandClaude Sonnet 5 8a2f512112 fix(reports): DATEV-Export nutzt falschen FK-Namen (absence_type_id statt type_id)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CviFgc739S89xS97pvjszj
2026-08-06 22:20:50 +02:00
patrickandClaude Sonnet 5 1dcc04ae07 feat(reports): DATEV-Monatsblatt-Export (Blanko-Vorlage) für Steuerberater
Neuer PDF-Export GET /reports/datev-monthly/export: ein Blatt pro
Mitarbeiter/Monat im Layout der DATEV-Stundenaufzeichnungs-Vorlage
(Beginn/Pause/Ende/Dauer + K/U/UU/F/SA/SU-Kürzel + Bemerkungen +
Summe + Unterschriftsfelder). Führt time_entries, Absences und
Feiertage pro Kalendertag zusammen.

Außerdem: Mustervorlagen für neue Backend-Module (Model/Schema/Router/
Migration/Test) und eine Frontend-Page-Vorlage, abgeleitet vom
hours_payouts-Modul als aktuellstem sauberen Muster.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CviFgc739S89xS97pvjszj
2026-08-06 22:19:54 +02:00