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; preload
  • X-Frame-Options: DENY
  • X-Content-Type-Options: nosniff
  • X-XSS-Protection: 1; mode=block
  • Referrer-Policy: strict-origin-when-cross-origin
  • Permissions-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.localhttp://localhost:3000,http://127.0.0.1:3000
  • .env.developmenthttps://dev.yourdomain.com
  • .env.productionhttps://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:

EndpointLimits
POST /api/auth/forgot-password5 req / 15 min per IP, 3 req / 1 hr per email
POST /api/auth/verify-2fa5 attempts per challenge token, 10 req / 15 min per IP

For multi-instance / serverless deployments, swap the in-memory Map in lib/rate-limit.ts for 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.

EventTrigger
PATIENT_DASHBOARD_VIEWEDPatient opens dashboard
PATIENT_APPOINTMENTS_VIEWEDPatient views appointment list
PATIENT_RECORDS_VIEWEDPatient views labs / prescriptions / documents
PATIENT_DATA_EXPORTEDPatient downloads full data export
PATIENT_CONSENT_GIVENPatient 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 patient
  • POST /api/patient/consents — records a new consent { type, version }

Consent types used:

  • HIPAA_NOTICE
  • PRIVACY_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:

  1. GET /api/account/totp — generates a secret and QR code data URL; stores pending secret on user
  2. User scans QR in their authenticator app
  3. POST /api/account/totp { code } — verifies first code and activates TOTP
  4. DELETE /api/account/totp { code } — disables TOTP (requires current code as confirmation)

Login flow:

  • If totpEnabled = true on 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-in crypto (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.tsdetectPhiWarnings(), scanObjectForPhi()
  • app/api/crm/workflows/route.ts (POST) — PHI scan on create
  • app/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):

TrainingRoles
HIPAA Privacy RuleCLINICIAN, STAFF, ORG_ADMIN
HIPAA Security RuleCLINICIAN, STAFF, ORG_ADMIN
HITECH ComplianceORG_ADMIN
Security AwarenessCLINICIAN, STAFF, ORG_ADMIN
Patient Data HandlingCLINICIAN, STAFF
Breach Response ProceduresORG_ADMIN

APIs:

  • GET /api/account/training — current user's acknowledgment records
  • POST /api/account/training { trainingType, version } — record acknowledgment
  • GET /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: StaffTrainingAcknowledgmentuserId, 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 policy
  • POST /api/admin/data-retention/run { dryRun: true|false } — trigger purge

What is purged:

  • Audit logs older than auditLogRetentionDays
  • Users with status INACTIVE not updated in inactiveUserRetentionDays (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:

ResourceMasked fields
ConsultationchiefComplaint, subjective, objective, assessment, plan, diagnosis, clinicalNotes, internalNotes, treatmentNotes
Patientssn, dateOfBirth, insuranceMemberId, insuranceGroupNumber


What is NOT covered (still pending)

  • Annual penetration test — must be conducted by a third-party security firm; cannot be automated