Chat Integration¶
Obiettivo¶
SafeOps agisce solo come host applicativo e identity/context provider del microservizio chat esterno.
SafeOps: - identifica l'utente loggato - risolve il tenant effettivo - emette il JWT host lato server - passa il contesto business al microservizio chat - monta il widget in punti UI mirati
SafeOps non fa: - persistenza chat locale - websocket chat - unread locali - logica conversazioni
Architettura¶
Componenti introdotti:
- app/blueprints/chat_portal.py
- app/services/chat_handoff.py
- app/services/chat_context.py
Config:
- CHAT_INTEGRATION_ENABLED
- CHAT_ENABLED_TENANTS
- CHAT_APP_ID
- CHAT_SERVICE_BASE_URL
- CHAT_WIDGET_BASE_URL
- CHAT_HANDOFF_JWT_SECRET
- CHAT_HANDOFF_JWT_ALGORITHM
- CHAT_HANDOFF_JWT_AUDIENCE
- CHAT_HANDOFF_JWT_ISSUER
Flusso¶
- utente autenticato apre una schermata SafeOps abilitata
- il frontend server-rendered monta un wrapper minimo del widget
- il wrapper chiama
POST /api/chat/session/handoff - SafeOps risolve
tenant_id = effective_tenant_key() - SafeOps costruisce il JWT host lato server
- SafeOps restituisce:
tenant_idapp_idchat_base_urlwidget_base_urlhost_jwtcontext- il widget riceve bootstrap via
postMessage
Contratto backend¶
Endpoint:
- POST /api/chat/session/handoff
Input tipico:
{
"entity_type": "cliente",
"entity_id": "123",
"label": "Cliente ACME",
"url": "/cliente/show/123",
"metadata": {
"module": "anagrafiche"
}
}
Output:
{
"ok": true,
"tenant_id": "tenant-alpha",
"app_id": "safeops-web",
"chat_base_url": "http://chat-service/api/v1",
"widget_base_url": "http://chat-widget",
"host_jwt": "<jwt>",
"context": {
"entity_type": "cliente",
"entity_id": "123",
"label": "Cliente ACME",
"url": "/cliente/show/123",
"metadata": {
"module": "anagrafiche",
"entity_type": "cliente"
},
"room_key": "tenant-alpha/safeops-web/cliente/123"
}
}
Regole multi-tenant¶
Identità esterna sempre definita da:
- tenant_id
- app_id
- external_user_id
Mapping SafeOps:
- tenant_id = effective_tenant_key()
- app_id = safeops-web
- external_user_id = current_user.id
Regola non negoziabile:
- nessun fallback a default
- se effective_tenant_key() non risolve un tenant reale, il token chat non viene emesso
Claim JWT host¶
Claim emessi:
- tenant_id
- app_id
- external_user_id
- username
- display_name
- email
- roles
- scopes
- context opzionale
L'algoritmo di default è HS256.
Mapping contesti¶
Supporto attuale lato host:
- cliente
- documento
- ticket
- intervento
- ras
- dvr
Regola room:
- tenant_id / app_id / entity_type / entity_id
Quindi:
- tenant-a / safeops-web / cliente / 123
- tenant-b / safeops-web / cliente / 123
sono due room diverse.
Mapping ruoli -> scopes¶
Mapping pragmatico lato SafeOps:
Base utente autenticato:
- chat:read
- chat:write
- chat:context:open
Ruoli operativi con allegati:
- chat:attach
Tenant admin / platform admin:
- chat:moderate
- chat:admin
Il microservizio chat resta il punto finale di enforcement.
Feature flags¶
Flag principale:
- CHAT_INTEGRATION_ENABLED
Rollout per tenant:
- CHAT_ENABLED_TENANTS
Comportamento: - flag off: endpoint handoff rifiutato - tenant fuori allowlist: endpoint handoff rifiutato
Rollout¶
Fase 1¶
- backend handoff
- mount pilota solo su Cliente
- nessuna sostituzione della chat ticket legacy
Fase 2¶
- aggiunta Ticket
- convivenza con chat ticket legacy
- il widget esterno viene montato come pannello contestuale aggiuntivo nella vista ticket, senza sostituire il thread legacy
Fase 3¶
- aggiunta Documento
- aggiunta Intervento
- aggiunta RAS
- aggiunta DVR
- stato attuale: widget montato su
Documento,Intervento,RASeDVR RASeDVRusano il mount nella paginadynamic/compila, conentity_typecoerente aldoc_kind
UI¶
Approccio adottato: - server-rendered - wrapper JS minimale - nessun frontend monolitico nuovo
Il frontend:
- non conosce il secret
- chiama l'endpoint handoff di SafeOps
- riceve host_jwt e config dal backend
- passa il bootstrap al widget via postMessage
Convivenza con la chat ticket legacy¶
Legacy che resta attivo:
- chat ticket locale in app/modules/tickets/views.py
Nuova integrazione:
- esterna
- pilotata da feature flag
- oggi montata su Cliente, Ticket, Documento, Intervento, RAS e DVR
- su Ticket convive esplicitamente con la chat locale esistente
Strategia futura: - mantenere entrambe durante il rollout - spostare progressivamente i punti UI da legacy a microservizio - non toccare la persistenza ticket legacy finché non c'è decisione esplicita di migrazione
Limiti attuali¶
- mount UI su Cliente, Ticket, Documento, Intervento, RAS e DVR
- nessun unread locale
- nessuna moderazione locale
- nessuna persistenza messaggi in SafeOps
- nessun supporto widget avanzato oltre al bootstrap host