> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Patient Portal — Administrator Guide

> For: Organization administrators and billing staff Last Updated: 2026-02-23 Permission Required: pm.portal.manage

**For:** Organization administrators and billing staff
**Last Updated:** 2026-02-23
**Permission Required:** `pm.portal.manage`

***

## Overview

The PM-12 Patient Portal provides patient self-service capabilities. This guide covers configuration, proxy management, and troubleshooting.

***

## Portal Architecture

### Authentication Flow

1. Patient registers with email matching their `pm_patients` record
2. Supabase Auth creates identity; `pf_profiles.portal_patient_id` links to patient
3. Login creates an audit row in `pm_portal_sessions` (append-only)
4. Account lockout triggers after 5 consecutive failed attempts

### Data Isolation

* **RLS:** `pm_portal_has_org_access()` and `pm_portal_visible_patient_ids()` (SECURITY DEFINER)
* **Application:** `usePortalPatientId()` hook scopes all portal queries
* **Double enforcement:** Both layers must pass for data access

***

## Proxy Authorization

### Granting Proxy Access

Staff with `pm.portal.manage` permission can authorize proxy access:

1. Navigate to the patient's record in PM
2. Add a proxy entry in `pm_portal_users` with:
   * `proxy_for_patient_id`: The patient being represented
   * `proxy_relationship`: `parent`, `legal_guardian`, `healthcare_decision_maker`, or `other`
   * `proxy_expires_at`: Expiration date (required)

### Revoking Proxy Access

Set `proxy_expires_at` to a past date or update `status` to `inactive`. The proxy user will immediately lose access to the linked patient's data.

### Proxy Rules

* Proxy access is always time-limited
* Expired proxies return no data (enforced at RLS and application layers)
* A user can be a proxy for multiple patients
* Proxy audit trail is maintained in `pm_portal_sessions`

***

## Session Management

### Audit Trail

All portal sessions are recorded in `pm_portal_sessions`:

* `portal_user_id`, `ip_address`, `user_agent`, `created_at`
* Sessions are **append-only** — no UPDATE or DELETE allowed
* `revoked_at` is set on sign-out

### Account Lockout

* After 5 failed login attempts, `pm_portal_users.status` is set to `locked`
* Staff can unlock by updating the status back to `active`
* Lockout does not affect the Supabase Auth account itself

***

## Security Configuration

### MFA

* Patients can self-enroll in TOTP MFA via the portal Security page
* `pm_portal_users.mfa_enabled` flag tracks enrollment status
* Supabase Auth handles TOTP factor management natively
* Backup codes are displayed once during enrollment

### Rate Limiting

* Portal login attempts are tracked per-user
* No IP-based rate limiting at the application layer (rely on Supabase Auth and infrastructure-level protections)

***

## Feature Integration Summary

| Feature        | Source Spec | Portal Capability                              |
| -------------- | ----------- | ---------------------------------------------- |
| Appointments   | PM-03       | View upcoming/past, cancel                     |
| Health Records | CL-\*       | View chart, medications, goals (consent-gated) |
| Forms          | PF-08       | Complete intake/consent forms                  |
| Demographics   | PM-01       | View info, submit update requests              |
| Billing        | PM-09       | View balance, payment history                  |
| Messages       | PM-14       | Secure messaging with care team                |

### SUD Consent & Clinical Records (CL-11)

When a patient's chart has SUD (Substance Use Disorder) indicated:

* The portal checks for active Part 2 consent via `cl_check_sud_consent` RPC
* Without consent, SUD-related data (e.g., risk level) is suppressed from the portal view
* Non-SUD clinical data (medications, treatment goals) remains accessible per the 21st Century Cures Act
* A consent restriction banner is shown to the patient
* This is enforced at both the RLS layer (database) and the application layer (`useConsentCheck`)

***

## Troubleshooting

| Issue                    | Resolution                                                              |
| ------------------------ | ----------------------------------------------------------------------- |
| Patient can't register   | Verify email matches `pm_patients` record                               |
| Portal shows no data     | Check `pf_profiles.portal_patient_id` is set                            |
| Proxy sees wrong patient | Verify `pm_portal_users.proxy_for_patient_id` and expiration            |
| Account locked           | Update `pm_portal_users.status` to `active`                             |
| MFA lost                 | Patient must contact admin; unenroll factor via Supabase Auth dashboard |
| Session audit missing    | Check `pm_portal_sessions` — rows are append-only                       |

***

## Database Tables

| Table                           | Purpose                            | RLS                          |
| ------------------------------- | ---------------------------------- | ---------------------------- |
| `pm_portal_users`               | Portal user metadata, proxy config | FORCE RLS                    |
| `pm_portal_sessions`            | Append-only session audit          | FORCE RLS (no UPDATE/DELETE) |
| `pf_profiles.portal_patient_id` | Links auth user to patient record  | Existing PF RLS              |

***

## Permissions

| Permission         | Description                              | Default Roles |
| ------------------ | ---------------------------------------- | ------------- |
| `pm.portal.view`   | View portal configuration and proxy list | org\_admin    |
| `pm.portal.manage` | Manage proxy authorization and settings  | org\_admin    |
