From 3ed6596bf094eaa676169a136bf9585fbb03896f Mon Sep 17 00:00:00 2001 From: Schubert Ferenc Date: Sat, 4 Jul 2026 22:19:28 +0200 Subject: [PATCH] fix(repairs): normalize public status tokens --- .DS_Store | Bin 10244 -> 10244 bytes .env.example | 4 +++ ARCHITECTURE.md | 3 +++ README-DEV.md | 2 ++ backend/.DS_Store | Bin 6148 -> 6148 bytes backend/hermes/.env.example | 1 + backend/hermes/app/core/config.py | 1 + .../app/repositories/repair_repository.py | 2 ++ .../services/repair_public_link_service.py | 25 ++++++++++++++---- docker-compose.yml | 1 + funktechnik-schubert-website | 2 +- 11 files changed, 35 insertions(+), 6 deletions(-) diff --git a/.DS_Store b/.DS_Store index 3ff066b0858b464c49971b4718339d80c3ee2cab..93d72f91b40a1d3fb738c840f185876e103de066 100644 GIT binary patch delta 1359 zcmaKsyGz4R6vj_AnogP)1rdA{#KA#u5qwusP!|hzbg&Pt#Wo3T6m+?zI;#-f-JBdG zAc$gdQFQR}59lOr;;asyo7&{GX&UGayyyGvbTHkfdf z<6vHhW1NOR4wi)#io^~0I6M?8aQWDC`)xv1@!43WkGUAjDpm{?5KO^JWvc*L8U=}* zTOdTY0l41vPih$|g7sc0p!Z2WMnk8PAFXB;Rw08(GFHnxJQ{xZI92~z+ML5@L)7vS zmIj!62!j6BGHT=`kEi*+nR#W}2R}Pdm@syHdyOrWp^$$%&0(wZX(-sCRFFstbOx8r z5Vjf4$SFlm;QDxbpb2k?4162BB{Y0*;7MpWW~w$wO+Q5(zp&1T&S8!aitjSXF^BGq zR|+2y@nZ;`1QqcsT1o0JIhbvswE3%{cd>Z)D;A=7?yhp6PzgxGCgPP%ShZv-*C1T@ t!jVT23k4cmwEm17F8jFMU1dPwXET%!XWRZdL+t(*^yAhUW8QyV_Xm;6ReAsb delta 104 zcmZn(XbG6$FUrQiz`)4BAi%(o%8=O%IHnS`IVwrqjT57Yn#7ias DNt_*- diff --git a/.env.example b/.env.example index e6e06b3..e7aca6c 100644 --- a/.env.example +++ b/.env.example @@ -28,3 +28,7 @@ KNOWLEDGE_MAX_UPLOAD_MB=50 # Server-zu-Server Token fuer spaetere Website-Reparaturannahme. # Produktiv als langen Zufallswert setzen. Nicht im Frontend verwenden. OLYMPUS_REPAIR_INTAKE_TOKEN= + +# Optionale Website-Basis-URL fuer oeffentliche Reparaturstatuslinks. +# Beispiel: https://test.funktechnik-schubert.de/status +PUBLIC_REPAIR_STATUS_BASE_URL= diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 65f6edf..d98233b 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -893,6 +893,9 @@ docker-compose.yml `OLYMPUS_REPAIR_INTAKE_TOKEN` : Server-zu-Server Token fuer `POST /public/repair-intake`. Dieser Wert darf nicht im Frontend oder in Logs erscheinen. +`PUBLIC_REPAIR_STATUS_BASE_URL` +: Optionale Basis-URL fuer von Olympus erzeugte oeffentliche Reparaturstatuslinks, z. B. `https://test.funktechnik-schubert.de/status`. Wenn leer, gibt Hermes weiterhin relative Links im Format `/status/` zurueck. + ### Athena `HERMES_INTERNAL_URL` diff --git a/README-DEV.md b/README-DEV.md index 0ad90a3..f9c95d3 100644 --- a/README-DEV.md +++ b/README-DEV.md @@ -34,6 +34,7 @@ STORAGE_BASE_PATH=/data/storage STORAGE_MAX_UPLOAD_MB=50 STORAGE_HOST_PATH=./storage OLYMPUS_REPAIR_INTAKE_TOKEN= +PUBLIC_REPAIR_STATUS_BASE_URL= ``` Wenn `SECRET_KEY` Sonderzeichen wie `$` enthaelt, den Wert in der Shell oder Compose-Umgebung korrekt quoten. Secrets gehoeren nicht ins Git. @@ -159,6 +160,7 @@ Statuslink-Konzept: - RBAC-Permission: `repairs.public_link.manage`. - Tokens sind lang, zufaellig und werden nur gehasht gespeichert. - Der Klartexttoken wird nur einmal bei Erstellung zurueckgegeben. +- `PUBLIC_REPAIR_STATUS_BASE_URL` kann auf die Website-Route zeigen, z. B. `https://test.funktechnik-schubert.de/status`. - Oeffentliche Statusdaten kommen spaeter ueber `GET /public/repairs/status/{token}`. - Die Antwort enthaelt keine Kundendaten, keine internen Notizen und keine nicht freigegebenen Diagnosen. diff --git a/backend/.DS_Store b/backend/.DS_Store index 7ff34d41d8fdf143294a2b3adebef521fa5b7ed9..90bfa15157e6684f07ea491ff927cf1a5f690936 100644 GIT binary patch delta 203 zcmZoMXfc=|#>B`mu~2NHo+2ab!~pAw2O5BMk4!xqLk2@CLlHwRkSxwgHw;eB&n;j8 z0v79yg+K~LJ~!XRB`GIA2`J9-tEQgg-|yp&m}*i8swv2T*~1F7Xlla7jnQnI**W+* hfR5a_@jLTmei2Vtn|(xfFi&h?0RW~HG~)mO delta 73 zcmZoMXfc=|#>CJzu~2NHo+2aT!~knX#>qU4G7~cvZ$7~2&Ni{3bTc~#KL=3F=8cTs cnJ4p$=yEbLFfbedVur~!JkpzEM3yiE0Q)c#djJ3c diff --git a/backend/hermes/.env.example b/backend/hermes/.env.example index 33eff2d..eabbdf7 100644 --- a/backend/hermes/.env.example +++ b/backend/hermes/.env.example @@ -19,3 +19,4 @@ STORAGE_MAX_UPLOAD_MB=50 KNOWLEDGE_STORAGE_PATH=/data/knowledge KNOWLEDGE_MAX_UPLOAD_MB=50 OLYMPUS_REPAIR_INTAKE_TOKEN= +PUBLIC_REPAIR_STATUS_BASE_URL= diff --git a/backend/hermes/app/core/config.py b/backend/hermes/app/core/config.py index ba364cf..ab59fa3 100644 --- a/backend/hermes/app/core/config.py +++ b/backend/hermes/app/core/config.py @@ -24,6 +24,7 @@ class Settings(BaseSettings): knowledge_storage_path: str = "/data/knowledge" knowledge_max_upload_mb: int = 50 olympus_repair_intake_token: str | None = None + public_repair_status_base_url: str | None = None model_config = SettingsConfigDict( env_file=".env", diff --git a/backend/hermes/app/repositories/repair_repository.py b/backend/hermes/app/repositories/repair_repository.py index 6ec0e2d..51436f1 100644 --- a/backend/hermes/app/repositories/repair_repository.py +++ b/backend/hermes/app/repositories/repair_repository.py @@ -221,6 +221,7 @@ class RepairRepository: select(RepairPublicAccessToken) .where(RepairPublicAccessToken.repair_id == repair_id) .where(RepairPublicAccessToken.is_active.is_(True)) + .where(RepairPublicAccessToken.revoked_at.is_(None)) .where(or_(RepairPublicAccessToken.expires_at.is_(None), RepairPublicAccessToken.expires_at > now)) .order_by(RepairPublicAccessToken.created_at.desc(), RepairPublicAccessToken.id.desc()) .limit(1) @@ -234,6 +235,7 @@ class RepairRepository: .options(selectinload(RepairPublicAccessToken.repair)) .where(RepairPublicAccessToken.token_hash == token_hash) .where(RepairPublicAccessToken.is_active.is_(True)) + .where(RepairPublicAccessToken.revoked_at.is_(None)) .where(or_(RepairPublicAccessToken.expires_at.is_(None), RepairPublicAccessToken.expires_at > now)) .limit(1) ) diff --git a/backend/hermes/app/services/repair_public_link_service.py b/backend/hermes/app/services/repair_public_link_service.py index 5697582..c0d9bfc 100644 --- a/backend/hermes/app/services/repair_public_link_service.py +++ b/backend/hermes/app/services/repair_public_link_service.py @@ -22,14 +22,24 @@ from app.services.repair_notification_service import STATUS_LABELS from app.services.repair_service import repair_label +def normalize_token(token: str) -> str: + return token.strip() + + def public_status_path(token: str) -> str: - return f"/status/{token}" + normalized_token = normalize_token(token) + path = f"/status/{normalized_token}" + base_url = (settings.public_repair_status_base_url or "").strip().rstrip("/") + if not base_url: + return path + return f"{base_url}/{normalized_token}" class RepairPublicLinkService: @staticmethod def hash_token(token: str) -> str: - return hmac.new(settings.secret_key.encode("utf-8"), token.encode("utf-8"), hashlib.sha256).hexdigest() + normalized_token = normalize_token(token) + return hmac.new(settings.secret_key.encode("utf-8"), normalized_token.encode("utf-8"), hashlib.sha256).hexdigest() @staticmethod def create_token() -> str: @@ -100,14 +110,17 @@ class RepairPublicLinkService: @staticmethod def public_status(db: Session, token: str) -> RepairPublicStatusResponse: - public_link = RepairRepository.get_public_link_by_hash(db, RepairPublicLinkService.hash_token(token)) + normalized_token = normalize_token(token) + if not normalized_token: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Reparaturstatus nicht gefunden") + + public_link = RepairRepository.get_public_link_by_hash(db, RepairPublicLinkService.hash_token(normalized_token)) if public_link is None or public_link.repair is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Reparaturstatus nicht gefunden") - RepairRepository.mark_public_link_used(db, public_link) repair = public_link.repair history = RepairRepository.get_history_public(db, repair.id) - return RepairPublicStatusResponse( + response = RepairPublicStatusResponse( repair_number=repair.repair_number, public_status_label=STATUS_LABELS.get(repair.status, repair.status), device_manufacturer=repair.device_manufacturer, @@ -122,3 +135,5 @@ class RepairPublicLinkService: ], updated_at=repair.updated_at, ) + RepairRepository.mark_public_link_used(db, public_link) + return response diff --git a/docker-compose.yml b/docker-compose.yml index 36aa4e5..65426d1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -26,6 +26,7 @@ services: KNOWLEDGE_STORAGE_PATH: ${KNOWLEDGE_STORAGE_PATH:-/data/knowledge} KNOWLEDGE_MAX_UPLOAD_MB: ${KNOWLEDGE_MAX_UPLOAD_MB:-50} OLYMPUS_REPAIR_INTAKE_TOKEN: ${OLYMPUS_REPAIR_INTAKE_TOKEN:-} + PUBLIC_REPAIR_STATUS_BASE_URL: ${PUBLIC_REPAIR_STATUS_BASE_URL:-} volumes: - ${STORAGE_HOST_PATH:-./storage}:/data/storage diff --git a/funktechnik-schubert-website b/funktechnik-schubert-website index a284bbc..efdc4d5 160000 --- a/funktechnik-schubert-website +++ b/funktechnik-schubert-website @@ -1 +1 @@ -Subproject commit a284bbc1886b26091bea7ea7a5aacee71b4507fe +Subproject commit efdc4d5be10e921f15eaa6a8de7547e0d8893dc4