Virtual Care Service — Security Model
Status: populated Owner: TBD Last updated: 2026-04-18 Companion: Service Template · 13 security-compliance-tenancy
1. RBAC / ABAC Matrix
| Permission | Roles | Conditions |
|---|---|---|
virtual_care:session:create | clinician, scheduler, nurse_practitioner, admin | Tenant licensed; patient consent on file |
virtual_care:session:read | All authenticated | Own sessions or assigned patient |
virtual_care:session:end | clinician, admin | Session must be active or waiting |
virtual_care:session:admit | clinician, nurse_practitioner, admin | Provider must be in session |
virtual_care:config:read | admin, tenant_admin | Own tenant only |
virtual_care:config:write | tenant_admin, platform_admin | Own tenant; or platform admin |
virtual_care:async_visit:create | clinician, patient (via portal) | Patient consent on file |
virtual_care:ai:transcribe | clinician | Session in active state |
virtual_care:ai:accept_summary | clinician | Session in active or ended |
2. Consent Gates
| Gate | Required before |
|---|---|
| Telehealth consent | Any virtual session creation |
| Recording consent (separate) | Setting recordingEnabled=true; Jibri activation |
| AI transcription | Enabling STT; patient and clinician must both consent |
Consent records are checked via the ConsentCheckPort → access-policy service. Fail-closed: if consent service is unavailable, session creation is blocked.
3. Join Token Security
| Property | Value |
|---|---|
| Algorithm | HMAC-HS256 |
| Secret | Per-tenant, stored in KMS (never in DB) |
| Expiry | 15 min before scheduled start (configurable) |
| Payload | sessionId, participantId, role, iat, exp |
| Rotation | New token issued on each /join-token call; previous token invalidated |
| Validation | Server-side only; client cannot validate |
The join token endpoint (GET /sessions/join) is configured in Kong to bypass JWT auth and use HMAC token only.
4. Encryption Classes
| Data | Class | Mechanism |
|---|---|---|
| Jitsi JWT secret | Secret | KMS; never stored in DB |
| Zoom/Webex/Teams credentials | Secret | KMS-encrypted column |
| Session metadata (IDs, timestamps) | Standard | PostgreSQL AES-256 at rest |
| Audio streams (STT) | In-transit only | TLS 1.2+ via Kong |
| Recording files | PHI-Standard | Encrypted in document-service object storage |
| Chat content | PHI-Standard | Owned by communication-service; not stored here |
5. Audit Events
| Audit Event | Trigger |
|---|---|
SESSION_CREATED | Session created |
SESSION_STARTED | Provider joins; session active |
PARTICIPANT_ADMITTED | Patient admitted from waiting room |
PARTICIPANT_REMOVED | Participant removed |
SESSION_ENDED | Session ends |
SESSION_CANCELLED | Session cancelled |
SESSION_FAILED | Session failed (grace expired) |
FALLBACK_INITIATED | Async messaging fallback triggered |
RECORDING_ENABLED | Recording consent recorded + recording activated |
CONFIG_UPDATED | Tenant config changed (no credentials) |
AI_SUMMARY_ACCEPTED | Clinician accepted AI draft into chart |
CONSENT_GATE_BLOCKED | Session creation blocked due to missing consent |
TOKEN_INVALID | Invalid join token attempt logged |
CROSS_TENANT_VIOLATION | Cross-tenant access attempt |
6. PHI Controls
- Session metadata (IDs, timestamps, status, participant counts) are not PHI.
- Clinical content (chat, audio, notes) is PHI — never stored in
virtual-care-servicetables. - Chat content lives in
communication-service. - Audio transcripts and summaries pass through
ai-gateway-serviceand are written topatient-chart-serviceafter HITL acceptance; never persisted in this service's DB. - Recording files stored in
document-servicewith patient consent reference.
7. GDPR / Data Protection Participation
| Right | Handling |
|---|---|
| Right to erasure | Session rows soft-deleted; patientId pseudonymized; audit trail retained |
| Purpose limitation | Join tokens are scoped to specific sessions and expire |
| Data minimization | Minimal metadata in event payloads; no session content in events |
8. Tenant Isolation
- PostgreSQL RLS on
virtual_sessions,virtual_session_participants,async_visits. tenant_idextracted from JWT; set asapp.tenant_idsession variable at connection time.- Cross-tenant violations logged as security events and returned as 403.