Big Sweep: Security + Race-Conditions + Tests + DSGVO + A11y, SW by-v1095

SECURITY (auth.py, routes/auth.py, database.py, main.py)
- JWT bekommt jti; Logout trägt in neue jwt_blacklist-Tabelle ein,
  decode_token() prüft → server-side Invalidierung
- JWT-Expiry default 30 → 7 Tage (ENV JWT_EXPIRY_DAYS überschreibt)
- Sliding-Refresh-Middleware: erneuert Cookie wenn >50% verbraucht
  (Schwelle via JWT_REFRESH_FRACTION, Default 2)
- Login-Lockout in DB-Tabelle login_attempts (5 Versuche / 15 Min,
  überlebt Container-Restart) — alte In-Memory-Lockouts ersetzt
- SMTP-Versand: alle 'except: pass' durch logger.exception ersetzt;
  Fehlversuche landen in failed_emails-Tabelle für späteres Retry
- Referral-Counter Race gefixt: UPDATE partner_codes SET uses=uses+1
  ... WHERE uses<max_uses RETURNING — atomar statt SELECT+UPDATE

RACE CONDITIONS (routes/invoices.py, database.py)
- Neue invoice_counters-Tabelle für atomare Nummernvergabe
- _next_invoice_number nutzt BEGIN IMMEDIATE + atomares UPDATE
- Funktioniert für RG- und ST-Prefixe (Stornorechnungen)
- Race-Test verifiziert (5 Threads × 20 Calls = 100 eindeutige Nummern)

VERSION + TESTS + ERROR-DIGEST (VERSION, Makefile, tests/, scheduler.py)
- Neue VERSION-Datei (Single Source of Truth) — main.py liest beim
  Startup
- Makefile-Target 'make bump' propagiert in sw.js, app.js, index.html
- Makefile-Target 'make test' setzt venv auf, läuft pytest
- 19 Smoke-Tests in tests/ (health, auth, diary, invoice) — alle grün
- Scheduler: täglicher _job_error_digest um 06:30 → schickt Error-
  Zusammenfassung an ADMIN_EMAIL (still wenn keine Errors)

DSGVO + A11Y + ERSTE-HILFE
- landing.html: 'HTML und ODS' → 'JSON' (tatsächlich implementiert)
- datenschutz.js: Sektion Account-Löschung erweitert (sofort gelöscht /
  anonymisiert / 10 Jahre für Rechnungen)
- erste-hilfe.js: prominentes Warning-Banner oben (ersetzt keine
  Tierarzt-Beratung); Notfallnummern gruppiert nach Land, TODO-Platz-
  halter für AT-Uni-Klinik, CH Tox Info Suisse, CH Tierspital Zürich
- ui.js Modal: ESC schließt, Focus-Trap, Auto-Focus erstes Element,
  Restore Focus auf vorigen Caller
- impressum.js Kontaktformular: Labels mit for=cf-name etc.

NEUE DB-TABELLEN (idempotent via CREATE TABLE IF NOT EXISTS)
- jwt_blacklist, login_attempts, failed_emails, invoice_counters

NEUE ENV-VARS
- JWT_REFRESH_FRACTION (Default 2)
- JWT_EXPIRY_DAYS Default geändert (30 → 7)
This commit is contained in:
rene 2026-05-26 20:12:01 +02:00
parent 6224044654
commit 9394bab1fb
23 changed files with 1208 additions and 78 deletions

View file

@ -27,7 +27,7 @@ TAR_EXCLUDE := --exclude='.git' \
--exclude='./.DS_Store'
.PHONY: help deploy deploy-clean staging release sync push restart build stop status \
logs logs-f shell db dev clean-cache check-ssh reports
logs logs-f shell db dev clean-cache check-ssh reports bump test
# ----------------------------------------------------------
# SSH-Prüfung — Abhängigkeit aller DS-Befehle
@ -272,6 +272,31 @@ reports: check-ssh
done
# ----------------------------------------------------------
# BUMP — zentrale Versions-Erhöhung (VERSION-Datei + sw.js + app.js + index.html)
# Aufruf:
# make bump → liest aus VERSION, erhöht +1, schreibt zurück, propagiert in alle Frontend-Stellen
# make bump APP_VER=2000 → setzt VERSION explizit auf 2000
# Backend liest APP_VER beim Startup aus VERSION (siehe main.py:_read_app_ver()).
# ----------------------------------------------------------
bump:
@if [ ! -f VERSION ]; then echo "0" > VERSION; fi
@CUR=$$(cat VERSION | tr -d '[:space:]'); \
if [ -n "$(APP_VER)" ]; then NEW="$(APP_VER)"; else NEW=$$(($$CUR + 1)); fi; \
printf "%s" "$$NEW" > VERSION; \
sed -i.bak -E "s/const VER[[:space:]]*=[[:space:]]*'[0-9]+'/const VER = '$$NEW'/" backend/static/sw.js && rm -f backend/static/sw.js.bak; \
sed -i.bak -E "s/const APP_VER[[:space:]]*=[[:space:]]*'[0-9]+'/const APP_VER = '$$NEW'/" backend/static/js/app.js && rm -f backend/static/js/app.js.bak; \
sed -i.bak -E "s/\?v=[0-9]+/?v=$$NEW/g" backend/static/index.html && rm -f backend/static/index.html.bak; \
echo " ✓ APP_VER $$CUR → $$NEW (VERSION, sw.js, app.js, index.html aktualisiert)"
# ----------------------------------------------------------
# TEST — Smoke-Tests gegen isolierte Test-DB (kein Docker, kein DS)
# ----------------------------------------------------------
test:
@cd backend && test -d venv || python3 -m venv venv
@backend/venv/bin/pip install -q -r backend/requirements.txt pytest pytest-asyncio
@backend/venv/bin/python -m pytest -q
# ----------------------------------------------------------
# CACHE leeren — SW-Version erhöhen, dann restart
# Nach größeren CSS/JS-Änderungen wenn SW gecacht hat