fix(repairs): normalize public status tokens

This commit is contained in:
Schubert Ferenc 2026-07-04 22:19:28 +02:00
parent 09cce1f2b6
commit 3ed6596bf0
11 changed files with 35 additions and 6 deletions

BIN
backend/.DS_Store vendored

Binary file not shown.

View file

@ -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=

View file

@ -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",

View file

@ -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)
)

View file

@ -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