HIPAA / PHI / PIPEDA Compliance
The following controls have been implemented to meet HIPAA, PHI handling, and PIPEDA requirements.
Security Headers
All responses include:
Strict-Transport-Security: max-age=63072000; includeSubDomains; preloadX-Frame-Options: DENYX-Content-Type-Options: nosniffX-XSS-Protection: 1; mode=blockReferrer-Policy: strict-origin-when-cross-originPermissions-Policy: camera=(self), geolocation=()Content-Security-Policy— restricts script/style/connect sources
CORS
Dynamic CORS origin validation — echoes back request Origin only if it appears in ALLOWED_ORIGINS. CORS preflight (OPTIONS) and response headers are handled inside the proxy function for /api/* routes.
Environment variable (required):
ALLOWED_ORIGINS=https://yourdomain.com,https://www.yourdomain.com
Per-environment examples:
.env.local→http://localhost:3000,http://127.0.0.1:3000.env.development→https://dev.yourdomain.com.env.production→https://yourdomain.com,https://www.yourdomain.com
If ALLOWED_ORIGINS is not set, falls back to localhost:3000 for local dev.
Password Policy
All password changes and resets enforce:
- Minimum 8 characters
- At least 1 uppercase letter
- At least 1 lowercase letter
- At least 1 number
- At least 1 special character
- Cannot reuse any of the last 5 passwords (history stored as bcrypt hashes in
User.passwordHistory)
Applied in:
app/api/account/password/route.ts(change password)app/api/auth/reset-password/route.ts(reset via token)
Password Reset Token Hashing
Reset tokens are SHA-256 hashed before storage. The plaintext token is only sent via email and never persisted — a DB breach cannot be used to perform account takeovers.
Files: app/api/auth/forgot-password/route.ts, app/api/auth/reset-password/route.ts
Rate Limiting
In-memory sliding-window rate limiter applied to:
| Endpoint | Limits |
|---|---|
POST /api/auth/forgot-password | 5 req / 15 min per IP, 3 req / 1 hr per email |
POST /api/auth/verify-2fa | 5 attempts per challenge token, 10 req / 15 min per IP |
For multi-instance / serverless deployments, swap the in-memory Map in
lib/rate-limit.tsfor a Redis store.
Inactivity Session Timeout
All portal layouts automatically log out the user after 15 minutes of inactivity (no mouse, keyboard, scroll, or click events).
READ/VIEW Audit Logging
PHI access events are now logged in addition to write events.
| Event | Trigger |
|---|---|
PATIENT_DASHBOARD_VIEWED | Patient opens dashboard |
PATIENT_APPOINTMENTS_VIEWED | Patient views appointment list |
PATIENT_RECORDS_VIEWED | Patient views labs / prescriptions / documents |
PATIENT_DATA_EXPORTED | Patient downloads full data export |
PATIENT_CONSENT_GIVEN | Patient accepts HIPAA / Privacy Policy |
All events recorded in AuditLog with userId, organizationId, entityId, ipAddress, userAgent.
Patient Consent Tracking
Schema: PatientConsent model — stores consent type, version, timestamp, IP, user agent, and optional revocation time.
API:
GET /api/patient/consents— returns all consents for the authenticated patientPOST /api/patient/consents— records a new consent{ type, version }
Consent types used:
HIPAA_NOTICEPRIVACY_POLICY
UI: On first login, the patient portal displays a blocking modal requiring the patient to check and accept both the HIPAA Notice and Privacy Policy before accessing any portal content. Once accepted, the modal does not reappear.
Patient Data Export — Right of Access
GET /api/patient/export returns a complete JSON export of all data the platform holds for the authenticated patient, as a downloadable file.
Includes: profile, face sheet, insurance, appointments, orders, invoices, subscriptions, prescriptions, lab results, document metadata, form responses, payment method metadata (no raw card numbers), and consent records.
The export is audit-logged as PATIENT_DATA_EXPORTED.
UI: Export button on the patient Profile page under "Download My Data".
TOTP (Authenticator App) 2FA
Users can add a TOTP authenticator app (Google Authenticator, Authy, etc.) as a second factor.
Setup flow:
GET /api/account/totp— generates a secret and QR code data URL; stores pending secret on user- User scans QR in their authenticator app
POST /api/account/totp { code }— verifies first code and activates TOTPDELETE /api/account/totp { code }— disables TOTP (requires current code as confirmation)
Login flow:
- If
totpEnabled = trueon the user, the 2FA verify step (POST /api/auth/verify-2fa) accepts a TOTP code from the authenticator app instead of an emailed OTP - Accepts codes from ±30-second window to handle clock skew
- Audit-logged as
LOGIN_TOTP_VERIFIED
Implementation:
lib/totp.ts— RFC 6238 TOTP using Node.js built-incrypto(no external library)app/api/account/totp/route.ts— setup/enable/disable endpoints- Schema:
User.totpEnabled (Boolean),User.totpSecret (String?)
PHI Guard (Workflow Templates)
Free-text content saved in workflow templates is scanned for common PHI patterns before being stored.
Detected patterns: SSN, date of birth, MRN, NPI, DEA number, ICD codes, phone numbers, email addresses.
Behavior: Soft warning — the template is saved but the API response includes a phiWarnings array if patterns are detected. The UI should surface these as warnings so the author can review.
Files:
lib/phi-guard.ts—detectPhiWarnings(),scanObjectForPhi()app/api/crm/workflows/route.ts(POST) — PHI scan on createapp/api/crm/workflows/[id]/route.ts(PUT) — PHI scan on update
Staff Training Acknowledgment
Staff, clinicians, and admins are required to acknowledge HIPAA and security training modules annually. A blocking modal appears on first login to each portal if any required acknowledgments are outstanding.
Required trainings (by role):
| Training | Roles |
|---|---|
| HIPAA Privacy Rule | CLINICIAN, STAFF, ORG_ADMIN |
| HIPAA Security Rule | CLINICIAN, STAFF, ORG_ADMIN |
| HITECH Compliance | ORG_ADMIN |
| Security Awareness | CLINICIAN, STAFF, ORG_ADMIN |
| Patient Data Handling | CLINICIAN, STAFF |
| Breach Response Procedures | ORG_ADMIN |
APIs:
GET /api/account/training— current user's acknowledgment recordsPOST /api/account/training { trainingType, version }— record acknowledgmentGET /api/admin/training— compliance matrix for all staff in the org (admin view)
UI: components/training-gate.tsx — blocking modal mounted in EMR, CRM, and Admin layouts.
Schema: StaffTrainingAcknowledgment — userId, trainingType, version, acknowledgedAt, ipAddress, userAgent, expiresAt
Data Retention Policy
Per-organization configurable data retention with HIPAA minimum enforcement.
Minimums enforced by the API:
- Audit logs: 6 years minimum (HIPAA requirement)
- Patient data: 6 years minimum
- Orders: 6 years minimum
- Inactive users: 30 days minimum
Defaults (conservative):
- Audit logs: 7 years (2555 days)
- Patient data: 10 years (3650 days)
- Orders: 7 years (2555 days)
- Inactive users: 1 year (365 days)
APIs:
GET /api/admin/data-retention— get current policy (or defaults if none configured)PUT /api/admin/data-retention— update policyPOST /api/admin/data-retention/run { dryRun: true|false }— trigger purge
What is purged:
- Audit logs older than
auditLogRetentionDays - Users with status
INACTIVEnot updated ininactiveUserRetentionDays(reported in dry-run; hard user deletion is intentionally not automated — referential integrity)
Schema: DataRetentionPolicy model; Organization.dataRetentionPolicy relation.
Field-Level PHI Masking
Clinical PHI fields in API responses are masked for non-clinical roles (STAFF, BILLING).
Unmasked roles: SUPER_ADMIN, ORG_ADMIN, CLINICIAN
Masked fields per resource:
| Resource | Masked fields |
|---|---|
| Consultation | chiefComplaint, subjective, objective, assessment, plan, diagnosis, clinicalNotes, internalNotes, treatmentNotes |
| Patient | ssn, dateOfBirth, insuranceMemberId, insuranceGroupNumber |
What is NOT covered (still pending)
- Annual penetration test — must be conducted by a third-party security firm; cannot be automated
Updated about 1 hour ago