From 5f957ee8ede843de855f35761c8e20f4f04d7194 Mon Sep 17 00:00:00 2001 From: CCS Admin Date: Fri, 27 Feb 2026 11:13:32 +0000 Subject: [PATCH] fix: mask contact fields for non-admin users, require disclosure for visibility - Add strasse, plz, ort, email, telefonnummer, mobiltelefon, ansprechpartner to SENSITIVE_FIELDS in backend (nullified without disclosure) - Add visibleTo: 'admin' to all Kontakt fields in frontend fieldConfig - Consolidate _utcnow_naive() usage across all disclosure service functions for consistent naive datetime handling with MySQL Co-Authored-By: Claude Opus 4.6 --- backend/app/schemas/case.py | 5 ++++- backend/app/services/disclosure_service.py | 16 ++++++++-------- frontend/src/pages/cases/fieldConfig.ts | 14 +++++++------- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/backend/app/schemas/case.py b/backend/app/schemas/case.py index 8311e2d..5a14657 100644 --- a/backend/app/schemas/case.py +++ b/backend/app/schemas/case.py @@ -128,7 +128,10 @@ class CodingUpdate(BaseModel): ta_uebertherapie: bool = False -SENSITIVE_FIELDS = ("nachname", "vorname", "geburtsdatum", "anrede") +SENSITIVE_FIELDS = ( + "nachname", "vorname", "geburtsdatum", "anrede", + "strasse", "plz", "ort", "email", "telefonnummer", "mobiltelefon", "ansprechpartner", +) def mask_case_for_mitarbeiter(case_dict: dict, disclosure_granted: bool = False) -> dict: diff --git a/backend/app/services/disclosure_service.py b/backend/app/services/disclosure_service.py index e4614ae..a6b3ad1 100644 --- a/backend/app/services/disclosure_service.py +++ b/backend/app/services/disclosure_service.py @@ -10,6 +10,11 @@ from app.models.case import Case from app.models.user import User +def _utcnow_naive() -> datetime: + """Return current UTC time as a naive datetime, matching MySQL DATETIME columns.""" + return datetime.now(timezone.utc).replace(tzinfo=None) + + def has_active_disclosure(db: Session, case_id: int, user_id: int) -> tuple[bool, Optional[datetime]]: """Check if user has an active (approved, non-expired) disclosure for a case. @@ -21,7 +26,7 @@ def has_active_disclosure(db: Session, case_id: int, user_id: int) -> tuple[bool DisclosureRequest.case_id == case_id, DisclosureRequest.requester_id == user_id, DisclosureRequest.status == "approved", - DisclosureRequest.expires_at > datetime.now(timezone.utc), + DisclosureRequest.expires_at > _utcnow_naive(), ) .first() ) @@ -89,10 +94,10 @@ def review_disclosure_request( dr.status = new_status dr.reviewed_by = admin_id - dr.reviewed_at = datetime.now(timezone.utc) + dr.reviewed_at = _utcnow_naive() if new_status == "approved": - dr.expires_at = datetime.now(timezone.utc) + timedelta(hours=24) + dr.expires_at = _utcnow_naive() + timedelta(hours=24) # Notify requester case = db.query(Case).filter(Case.id == dr.case_id).first() @@ -113,11 +118,6 @@ def review_disclosure_request( return dr -def _utcnow_naive() -> datetime: - """Return current UTC time as a naive datetime, matching MySQL DATETIME columns.""" - return datetime.now(timezone.utc).replace(tzinfo=None) - - def revoke_disclosure(db: Session, request_id: int, user_id: int, *, admin: bool = False) -> DisclosureRequest: """Revoke an active disclosure by setting expires_at to now. diff --git a/frontend/src/pages/cases/fieldConfig.ts b/frontend/src/pages/cases/fieldConfig.ts index 81644e7..e5ae583 100644 --- a/frontend/src/pages/cases/fieldConfig.ts +++ b/frontend/src/pages/cases/fieldConfig.ts @@ -41,13 +41,13 @@ export const CASE_SECTIONS: SectionConfig[] = [ { title: 'Kontakt', fields: [ - { key: 'strasse', label: 'Straße', type: 'text', editableBy: 'admin', placeholder: 'Straße + Nr.' }, - { key: 'plz', label: 'PLZ', type: 'text', editableBy: 'admin', placeholder: 'PLZ' }, - { key: 'ort', label: 'Ort', type: 'text', editableBy: 'admin', placeholder: 'Ort' }, - { key: 'email', label: 'E-Mail', type: 'text', editableBy: 'admin', placeholder: 'E-Mail' }, - { key: 'telefonnummer', label: 'Telefon', type: 'text', editableBy: 'admin', placeholder: 'Telefonnummer' }, - { key: 'mobiltelefon', label: 'Mobil', type: 'text', editableBy: 'admin', placeholder: 'Mobilnummer' }, - { key: 'ansprechpartner', label: 'Ansprechpartner', type: 'text', editableBy: 'admin', colSpan: 2, placeholder: 'Ansprechpartner' }, + { key: 'strasse', label: 'Straße', type: 'text', editableBy: 'admin', visibleTo: 'admin', placeholder: 'Straße + Nr.' }, + { key: 'plz', label: 'PLZ', type: 'text', editableBy: 'admin', visibleTo: 'admin', placeholder: 'PLZ' }, + { key: 'ort', label: 'Ort', type: 'text', editableBy: 'admin', visibleTo: 'admin', placeholder: 'Ort' }, + { key: 'email', label: 'E-Mail', type: 'text', editableBy: 'admin', visibleTo: 'admin', placeholder: 'E-Mail' }, + { key: 'telefonnummer', label: 'Telefon', type: 'text', editableBy: 'admin', visibleTo: 'admin', placeholder: 'Telefonnummer' }, + { key: 'mobiltelefon', label: 'Mobil', type: 'text', editableBy: 'admin', visibleTo: 'admin', placeholder: 'Mobilnummer' }, + { key: 'ansprechpartner', label: 'Ansprechpartner', type: 'text', editableBy: 'admin', visibleTo: 'admin', colSpan: 2, placeholder: 'Ansprechpartner' }, ], }, {