Viewer Token e Webhook

Esta seção cobre endpoints de suporte à infraestrutura: geração de tokens temporários para o OHIF Viewer acessar o Orthanc via proxy CORS, e o webhook que o Orthanc chama quando um estudo DICOM é estabilizado.

POST /api/viewer/token

Gera um token temporário (10 min) que permite ao OHIF acessar o Orthanc através do proxy nginx autenticado. Auth: auth:api

Request body

study_id
uuid
required
ID do estudo (tabela studies). Precisa pertencer ao tenant do usuário autenticado.

Response 200

{
  "token": "550e8400-e29b-41d4-a716-446655440000"
}

Erros

StatusSituaçãoBody
401Token JWT inválido ou ausente{"message": "Unauthenticated."}
404Estudo não existe ou pertence a outro tenant{"error": "Study not found"}

Notas

  • O token é um UUID armazenado em Redis sob a chave viewer_token:{uuid} com TTL de 600 segundos (10 min)
  • Payload armazenado no Redis: { user_id, tenant_id, unit_id } (JSON)
  • O frontend passa o token na URL do OHIF (?token=uuid); o app-config.js do OHIF patcha XHR/fetch para injetar o header X-Viewer-Token em cada requisição ao proxy
  • Isolamento multi-tenant: como Study::find() usa o scope BelongsToTenantAndUnit, estudos de outro tenant retornam 404 automaticamente

GET /api/viewer/token/validate

Valida um token de viewer e o tenant do estudo que está sendo acessado. Endpoint interno, chamado pelo nginx via auth_request antes de proxear requisições ao Orthanc. Auth: público (não usa JWT — usa header custom X-Viewer-Token)

Request headers

HeaderDescrição
X-Viewer-TokenUUID do token gerado em POST /api/viewer/token (obrigatório)
X-Original-URIURI original que o nginx vai proxear (ex.: /dicom-web/studies/1.2.3.4.5). Usado para extrair o study_instance_uid e validar o tenant

Response 200

{
  "status": "ok"
}
O nginx só olha o código HTTP — o body é ignorado.

Erros

StatusSituaçãoBody
401Header X-Viewer-Token ausente{"error": "Missing token"}
401Token não encontrado no Redis (expirado ou inválido){"error": "Invalid or expired token"}
403study_instance_uid extraído da URI não pertence ao tenant_id do token{"error": "Forbidden"}

Notas

  • Extrai study_instance_uid da URI via regex — suporta dois formatos DicomWeb:
    • Path-based: /dicom-web/studies/1.2.3.4.5
    • Query-based: /dicom-web/studies?0020000D=1.2.3.4.5 ou ?StudyInstanceUIDs=1.2.3.4.5
  • Só valida tenant_idunit_id não é checado nesta camada
  • Não é chamado diretamente pelo frontend — é apenas para o auth_request do nginx

POST /api/orthanc/webhook

Recebe notificações do Orthanc quando um estudo DICOM é estabilizado (todas as imagens recebidas, 10s sem nova instância). Enfileira um job para processar o estudo. Auth: público — protegido via header X-Webhook-Secret

Request headers

HeaderDescrição
X-Webhook-SecretSecret compartilhado entre o hook Lua do Orthanc e o backend. Configurado via env ORTHANC_WEBHOOK_SECRET (tanto na raiz do projeto quanto em backend/.env)

Request body

{
  "ID": "orthanc_study_id_string",
  "...": "outras chaves do evento Orthanc"
}
O único campo usado pelo backend é ID (ID do estudo no Orthanc).

Response 200

{
  "status": "queued"
}

Erros

StatusSituaçãoBody
401Secret inválido ou ausente{"error": "Unauthorized"}
400Campo ID não fornecido no payload{"error": "ID not provided"}

Notas

  • Dispara o job ProcessStudyMetadata::dispatch($payload['ID']) assíncrono
  • O job:
    1. Busca detalhes do estudo no Orthanc via REST API
    2. Extrai RemoteAET e RemoteIP para identificar o aparelho emissor
    3. Resolve tenant/unit via AETitle cadastrado em modalities
    4. Fallback para tenant de “quarentena” se o AET não estiver cadastrado
    5. Cria/atualiza o Study em PostgreSQL
    6. Enfileira payload enxuto em pacs_python_queue (Redis) para o worker Python processar séries, instâncias e thumbnail
  • Implementação robusta: se o JSON vier malformado, tenta recuperar o ID a partir das chaves do body (edge case documentado no controller)