Medication Service — Security Model
Status: populated Owner: TBD Last updated: 2026-04-17 Companion: Service Template · 13 Security
1. Authentication
- JWT from identity-service.
tid(active tenant) claim mandatory. - Service-to-service: signed service JWTs (short TTL) from identity-service.
- Pharmacy portal: same JWT issuance; device binding enforced for offline.
2. Authorization — Scopes & Roles
| Scope | Description | Default roles |
|---|---|---|
medication:read | Read meds, prescriptions | clinician, nurse, pharmacist, portal-patient (self) |
medication:prescribe | Draft/sign/discontinue | prescriber |
mar:record | Record administration | nurse, prescriber |
mar:read | View MAR | clinician, nurse, pharmacist |
dispense:read | View queue + dispense details | pharmacist, pharmacy-tech |
dispense:create | Execute dispense | pharmacist |
dispense:countersign | Counter-sign Schedule II | licensed pharmacist (≠ dispenser) |
inventory:read | View stock | pharmacist, pharmacy-tech, inventory-manager |
inventory:write | GRN, adjust stock | pharmacy-tech, inventory-manager |
inventory:transfer | Transfer between nodes | pharmacy-tech, inventory-manager |
inventory:recall | Mark lot recalled | pharmacist-in-charge |
reconciliation:* | Reconciliation sessions | prescriber |
medication:cs:prescribe | Controlled-substance prescribe | prescriber + DEA/MoPH license + step-up MFA |
3. ABAC — Attribute Gates
| Attribute | Gate |
|---|---|
patient.tenantId == jwt.tid | Always (RLS backstop) |
prescription.encounterLocationNodeId ∈ jwt.locationScopes | For location-scoped roles |
isControlled == true → require jwt.amr includes mfa | Step-up authentication |
dispense.stockItem.nodeId == user.assignedPharmacyNodeId | Pharmacist dispensing scope |
counterSign.actor ≠ dispense.actor | Hard invariant |
4. Encryption Classes
| Data | At-rest | In-transit |
|---|---|---|
| Prescriptions | tenant-scoped column encryption on sig JSONB (sensitive instructions) | TLS 1.3 |
| DispensingEvents | Standard Postgres encryption | TLS 1.3 |
| Controlled-substance records | Additional AES-256 envelope encryption via KMS | TLS 1.3 |
| Drug KB snapshot artifacts | Signed SHA-256 hash recorded; blobs on encrypted S3 | TLS 1.3 |
| Device offline cache | Device-bound AES-256 using identity-service device key | n/a |
5. Audit Events (→ audit-service)
| Action | Audited | PHI level |
|---|---|---|
| prescription.sign | Always | PHI |
| prescription.discontinue | Always | PHI |
| alert.override | Always + override reason | PHI |
| dispense.completed | Always | PHI |
| controlled-substance.dispense | Always + disclosure-accounting record | sensitive |
| inventory.recall | Always | non-PHI |
| reconciliation.complete | Always | PHI |
| cross-tenant attempt (denied) | Always | security |
| bulk export | Always | PHI |
6. GDPR / Data Subject Rights
- Participates in erasure saga (SUBJ-ERASE): anonymizes patient FKs on prescriptions and dispenses beyond regulatory retention; does not delete rows required for controlled-substance ledger.
- Participates in export saga (SUBJ-EXPORT): produces
MedicationRequest,MedicationDispense,MedicationAdministration,MedicationStatementbundle.
7. Data Residency
- Afghanistan deployment: primary region
me-central-1(UAE) or local MoPH-approved region; controlled-substance records must not leave national boundary per MoPH data-residency. - Per-tenant override via
config-service.
8. Rate Limiting
| Route | Limit (per actor) |
|---|---|
POST /medications/{id}/sign | 30 rpm |
POST /dispenses | 120 rpm |
POST /medications | 60 rpm |
Rate-limit breach: 429 + Retry-After header.
9. Secrets
- Drug KB credentials, NCPDP SCRIPT certificates, KMS keys stored in Vault.
.env.exampleenumerates vars;src/infrastructure/config/env.tsvalidates with Zod and fails startup if missing.