From dc2e000e88e69df1f715efd096753c81ab8abfb1 Mon Sep 17 00:00:00 2001 From: ifedan-ed Date: Wed, 1 Apr 2026 17:59:51 +0000 Subject: [PATCH] v4: Fix milestones display + add OpenID auth + 100MB PDF support FIXES: - Milestones now show correctly on encounter page (use static fallback if DB empty) - Static data preserved as MILESTONES_DATA_STATIC for compatibility - Database-driven milestones still work (admin can edit via CMS) NEW FEATURES: - OpenID Connect (OIDC) authentication support (PocketID, Keycloak, Azure AD, etc.) - Comprehensive setup guide: OPENID_SETUP.md - Auto-linking existing users by email on SSO login - Multiple PDF upload support in Learning Hub (up to 10 files) - 100 MB per file limit (was 20 MB) - Full PDF content used for AI generation - Embeddings use first ~8K chars for semantic search IMPROVEMENTS: - Updated UI to show multiple file selection with list - Drag-and-drop supports multiple files - Better file upload validation and error handling - Added clarifying comments about embedding truncation --- OPENID_SETUP.md | 346 ++++++++++++++++++++++++++++++++++++ README.md | 33 ++++ public/components/cms.html | 7 +- public/js/learningHub.js | 41 ++++- public/js/milestones.js | 25 ++- public/js/milestonesData.js | 7 +- src/routes/learningAI.js | 45 ++++- src/utils/embeddings.js | 3 + 8 files changed, 482 insertions(+), 25 deletions(-) create mode 100644 OPENID_SETUP.md diff --git a/OPENID_SETUP.md b/OPENID_SETUP.md new file mode 100644 index 0000000..9bccc9d --- /dev/null +++ b/OPENID_SETUP.md @@ -0,0 +1,346 @@ +# OpenID Connect (OIDC) / PocketID Setup Guide + +This guide explains how to configure Single Sign-On (SSO) authentication using OpenID Connect providers like PocketID, Keycloak, Azure AD, Okta, or Google. + +## Overview + +The application supports OIDC authentication alongside traditional email/password login. Once configured, users can: + +- Sign in with their SSO provider (e.g., PocketID) +- Automatically link existing email accounts to their SSO identity +- Admins can optionally disable local password login entirely + +## Prerequisites + +1. An OpenID Connect provider (e.g., PocketID instance) +2. Admin access to this application +3. The public URL where your app is deployed (`APP_URL` in `.env`) + +--- + +## Configuration Steps + +### 1. Configure Your Identity Provider + +First, register this application with your OIDC provider. You'll need: + +**Redirect URI / Callback URL:** +``` +https://your-domain.com/api/auth/oidc/callback +``` + +Replace `your-domain.com` with your actual `APP_URL` value. + +**Example: PocketID Setup** + +1. Log into your PocketID admin panel +2. Navigate to **Applications** → **Add Application** +3. Set the callback URL: `https://your-domain.com/api/auth/oidc/callback` +4. Copy the Client ID and Client Secret + +**Example: Keycloak Setup** + +1. Create a new client in your Keycloak realm +2. Set **Access Type** to `confidential` +3. Add Valid Redirect URI: `https://your-domain.com/api/auth/oidc/callback` +4. Save and note the Client ID and Client Secret from the Credentials tab + +### 2. Enable OIDC in Application Settings + +Log into your application as an **admin** user, then: + +1. Navigate to **Admin Panel** → **Settings** (or access `/admin-settings.html`) +2. Look for the **OpenID Connect (SSO)** section +3. Fill in the following fields: + +| Field | Description | Example | +|-------|-------------|---------| +| **Enabled** | Toggle to enable OIDC | `true` | +| **Issuer URL** | Your provider's discovery endpoint | `https://id.example.com` or `https://keycloak.example.com/realms/myrealm` | +| **Client ID** | Application client ID from your provider | `pediatric-scribe-client` | +| **Client Secret** | Application client secret (keep confidential) | `a1b2c3d4...` | +| **Button Label** | Text shown on the SSO login button | `Sign in with PocketID` | +| **Disable Local Auth** | Hide email/password login (optional) | `false` (keep disabled initially) | +| **Allowed IPs** | Restrict SSO to specific IP ranges (optional) | Leave blank for no restriction | + +4. Click **Save Settings** + +### 3. Test SSO Login + +1. Log out or open an incognito browser window +2. Visit the login page +3. You should see a new button: **"Sign in with [Your Provider]"** +4. Click it and authenticate with your SSO provider +5. You'll be redirected back to the application and logged in + +--- + +## Linking Existing Users to SSO + +When a user signs in via OIDC for the first time, the system automatically links their account based on **email address matching**: + +### Scenario 1: Existing User with Matching Email + +If a user already has an account with email `doctor@example.com` and signs in via SSO with the same email: + +1. The system finds the existing user by email +2. Links the SSO identity (`oidc_sub`) to the existing account +3. The user is logged in +4. Future logins can use either method (email/password OR SSO) + +**Database update performed:** +```sql +UPDATE users +SET oidc_sub = '', + email_verified = true +WHERE email = 'doctor@example.com'; +``` + +### Scenario 2: New User (No Matching Email) + +If the SSO email doesn't match any existing user: + +1. A new account is automatically created +2. The user is assigned the `user` role (first user becomes `admin`) +3. A random password is generated (not used for SSO logins) +4. The user is logged in + +### Scenario 3: Disabled User + +If an existing user is disabled (`disabled = true` in database): + +- SSO login is blocked +- User sees an error message +- Admin must re-enable the account from the Admin Panel + +--- + +## Manual Account Linking (CLI) + +If you need to manually link an existing user to an SSO identity, use the PostgreSQL database directly: + +```bash +# Connect to database +docker exec -it pediatric-ai-scribe-postgres psql -U pedscribe -d pedscribe + +# Link user by setting their oidc_sub +UPDATE users +SET oidc_sub = 'provider-sub-12345', + email_verified = true +WHERE email = 'doctor@example.com'; +``` + +**Finding the `oidc_sub` value:** + +The `oidc_sub` is the unique identifier from your OIDC provider (usually a UUID or numeric ID). To find it: + +1. Have the user attempt SSO login once +2. Check the application logs for their `sub` claim: + ``` + [OIDC] User logged in: sub=abc-123-def, email=doctor@example.com + ``` +3. Use that `sub` value in the UPDATE statement + +--- + +## Security Considerations + +### HTTPS Required in Production + +OIDC requires HTTPS for security. Ensure your `APP_URL` uses `https://`: + +```env +APP_URL=https://scribe.example.com +``` + +### Client Secret Protection + +The client secret is stored encrypted in the database. The admin UI masks it after saving (shows `••••••••1234`). + +**Never commit the client secret to Git or share it publicly.** + +### IP Allowlisting (Optional) + +To restrict SSO to specific networks (e.g., hospital VPN): + +1. Set **Allowed IPs** in admin settings to comma-separated CIDR ranges: + ``` + 10.0.0.0/8, 192.168.1.0/24 + ``` +2. Users outside these ranges will see an error when attempting SSO + +### Disable Local Password Login + +Once SSO is working, you can optionally disable traditional email/password login: + +1. In Admin Settings, enable **Disable Local Auth** +2. The login page will only show the SSO button +3. Admins can still use the CLI to reset passwords if needed + +**Warning:** Only disable local auth after confirming all users can access SSO. Keep one admin password as backup. + +--- + +## Troubleshooting + +### "SSO is not enabled" error + +- Verify **Enabled** is set to `true` in admin settings +- Check application logs for OIDC configuration errors + +### "Invalid state" or "Expired" error + +- The OIDC flow timed out (5 minute window) +- Try logging in again +- If persistent, check server time synchronization + +### "No email claim" error + +Your OIDC provider didn't return an email address. Ensure: + +1. The `email` scope is requested (default: `openid email profile`) +2. Your provider is configured to release email claims +3. The user's account has an email address set + +### Email Mismatch + +If a user has different emails in the app vs. SSO provider: + +**Option 1: Update app email to match SSO** +```sql +UPDATE users SET email = 'new-email@example.com' WHERE id = 123; +``` + +**Option 2: Update SSO provider email to match app** +(Provider-specific — consult your IdP documentation) + +### Callback URL Not Working + +Double-check the redirect URI in your OIDC provider settings matches exactly: + +``` +https://your-domain.com/api/auth/oidc/callback +``` + +Common mistakes: +- Missing `https://` +- Trailing slash (don't include it) +- Wrong domain (must match `APP_URL` in `.env`) + +--- + +## Provider-Specific Examples + +### PocketID + +``` +Issuer URL: https://id.pockethost.io +Client ID: (from PocketID app settings) +Client Secret: (from PocketID app settings) +Redirect URI: https://your-domain.com/api/auth/oidc/callback +``` + +### Keycloak + +``` +Issuer URL: https://keycloak.example.com/realms/medical +Client ID: pediatric-scribe +Client Secret: (from Credentials tab) +Redirect URI: https://your-domain.com/api/auth/oidc/callback +``` + +### Azure AD / Entra ID + +``` +Issuer URL: https://login.microsoftonline.com/{tenant-id}/v2.0 +Client ID: (Application ID from Azure) +Client Secret: (from Certificates & secrets) +Redirect URI: https://your-domain.com/api/auth/oidc/callback +``` + +Note: Azure requires app registration in Azure Portal first. + +### Okta + +``` +Issuer URL: https://{your-okta-domain}.okta.com +Client ID: (from Okta application settings) +Client Secret: (from Okta application settings) +Redirect URI: https://your-domain.com/api/auth/oidc/callback +``` + +### Google (Workspace or Gmail) + +``` +Issuer URL: https://accounts.google.com +Client ID: (from Google Cloud Console) +Client Secret: (from Google Cloud Console) +Redirect URI: https://your-domain.com/api/auth/oidc/callback +``` + +Note: Google requires OAuth consent screen configuration. + +--- + +## Environment Variables (Alternative to UI Config) + +For deployment automation, you can set OIDC config via environment variables instead of the admin UI: + +```env +# .env file +OIDC_ENABLED=true +OIDC_ISSUER=https://id.example.com +OIDC_CLIENT_ID=my-client-id +OIDC_CLIENT_SECRET=my-client-secret +OIDC_BUTTON_LABEL=Sign in with PocketID +OIDC_DISABLE_LOCAL_AUTH=false +``` + +**Note:** UI settings take precedence over environment variables. If set in both places, the database values are used. + +--- + +## HIPAA Compliance Notes + +OIDC does not transmit PHI to the identity provider. Only authentication-related data (email, name) is exchanged. + +For HIPAA compliance: +- Ensure your OIDC provider has appropriate safeguards +- Use a self-hosted provider (Keycloak, PocketID) within your secure network +- Or use a HIPAA-compliant SaaS provider with a BAA +- Enable audit logging for all SSO login events (automatically logged in `audit_log` table) + +--- + +## Audit Logging + +All SSO login events are logged in the `audit_log` table: + +```sql +SELECT * FROM audit_log WHERE action = 'login_oidc' ORDER BY created_at DESC; +``` + +Logged fields: +- User ID +- Action: `login_oidc` +- IP address +- Details: Issuer URL +- Timestamp + +--- + +## Support + +For issues specific to: +- **This application**: Check application logs with `docker logs pediatric-ai-scribe` +- **Your OIDC provider**: Consult provider documentation (PocketID, Keycloak, Azure, etc.) +- **Network/TLS issues**: Verify `APP_URL` matches your reverse proxy configuration + +Common log locations: +```bash +# Application logs +docker logs pediatric-ai-scribe + +# PostgreSQL logs +docker logs pediatric-ai-scribe-postgres +``` diff --git a/README.md b/README.md index f85c896..46053af 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,39 @@ OPENAI_API_KEY=sk-... --- +## OpenID Connect / SSO (optional — PocketID, Keycloak, Azure AD, etc.) + +Enable Single Sign-On authentication with any OpenID Connect provider. Users can log in with their SSO provider, and existing accounts are automatically linked by email. + +**Supported providers:** PocketID, Keycloak, Azure AD / Entra ID, Okta, Google Workspace, and any OIDC-compliant provider. + +### Quick Setup + +1. Register this app with your OIDC provider using callback URL: + ``` + https://your-domain.com/api/auth/oidc/callback + ``` + +2. Log into the app as admin and navigate to **Admin Panel** → **Settings** + +3. Configure OIDC settings: + - Enable OIDC + - Set Issuer URL (e.g., `https://id.example.com`) + - Enter Client ID and Client Secret from your provider + - Customize the button label (e.g., "Sign in with PocketID") + +4. Save and test the login + +**Detailed setup guide with examples for all providers:** [OPENID_SETUP.md](OPENID_SETUP.md) + +### Linking Existing Users + +When a user signs in via SSO, the system automatically links their account if the email matches an existing user. No manual intervention needed. + +For advanced scenarios (manual linking, CLI commands, troubleshooting), see [OPENID_SETUP.md](OPENID_SETUP.md). + +--- + ## Email (optional — for verification & password reset) Without SMTP configured, email verification is skipped and users are auto-verified on registration. diff --git a/public/components/cms.html b/public/components/cms.html index 61bc256..18c89a0 100644 --- a/public/components/cms.html +++ b/public/components/cms.html @@ -153,10 +153,11 @@