pediatric-ai-scribe-v3/docs/authentication.md
Daniel b97c5f146b v6.1: Turnstile bot protection, LiteLLM provider, PPTX tables, audio backup fixes, docs
- Add Cloudflare Turnstile to login, register, and password reset forms
- Switch AI provider to LiteLLM, transcription to OpenAI Whisper
- Change domain to scribe.pedshub.com
- Fix PPTX export: add tables, bold/italic, numbered lists, code blocks, blockquotes
- Fix announcement banner close button (CSP was blocking inline onclick)
- Fix auth middleware: empty Bearer token now falls through to cookie auth
- Fix audio backups: only save on transcription failure, stop auto-deleting on success
- Soften AI correction injection to prevent model hallucination from correction history
- Fix LiteLLM TTS model name handling (no incorrect openai/ prefix)
- Expand AI instructions textarea in Learning Hub CMS
- Update README for v6 with all features and providers
- Add comprehensive docs/: architecture, API reference, database schema,
  authentication, AI providers, speech, learning hub, configuration, deployment
2026-04-04 22:56:24 +02:00

147 lines
5.9 KiB
Markdown

# Authentication and Security
This document covers the complete authentication, authorization, and security system for the Pediatric AI Scribe application.
---
## Authentication Methods
### Local Authentication
- Passwords are hashed using **bcryptjs** with 12 salt rounds.
- On successful login, a **JSON Web Token (JWT)** is issued with a 7-day expiry.
- The token is stored in an **httpOnly cookie** named `ped_auth`.
- In production: `secure: true`, `sameSite: lax`.
- In development: `secure: false`, `sameSite: lax`.
### Auth Middleware
The authentication middleware checks credentials in the following order:
1. Looks for a `Bearer` token in the `Authorization` header.
2. If the token is empty or missing (including the case where the header is literally `"Bearer "` with no token), falls back to reading the `ped_auth` cookie.
This two-step approach was specifically fixed to handle empty Bearer strings gracefully, preventing false authentication failures from clients that send the header with no value.
### TOTP Two-Factor Authentication (2FA)
- Implemented using the **speakeasy** library.
- Setup flow: server generates a TOTP secret, encodes it as a QR code, and the user scans it with an authenticator app.
- Verification: 6-digit code, with `window=1` (accepts codes from the previous and next 30-second interval in addition to the current one).
### OIDC / SSO (Single Sign-On)
- Implements the **Authorization Code + PKCE** flow using the **openid-client** library.
- State parameters are stored **in-memory** with a 5-minute TTL to prevent replay attacks.
- On first login via OIDC, a local user account is **auto-created** using claims from the identity provider.
- Supported providers:
- Azure AD
- Okta
- Keycloak
- PocketID
- Google
### Email Verification
- A 32-byte random hex token is generated and sent to the user's email address.
- The token expires after **24 hours**.
### Password Reset
- A 32-byte random hex token is generated and sent to the user's email address.
- The token expires after **1 hour**.
---
## Cloudflare Turnstile (CAPTCHA)
Turnstile is applied to the following routes:
- User registration
- User login
- Password reset request
### Frontend
- The `cf-turnstile` widget is rendered with the configured site key.
- The form validates that the Turnstile challenge was completed before allowing submission.
- On failure, the widget resets so the user can retry.
### Backend
- The server sends a `POST` request to `https://challenges.cloudflare.com/turnstile/v0/siteverify` with the secret key and the client-provided token.
- Turnstile is **only enforced when `TURNSTILE_SECRET_KEY` is set** in the environment. If the variable is absent, the check is skipped entirely. This allows development and self-hosted environments to run without Cloudflare integration.
---
## Rate Limiting
Rate limiting is implemented using **express-rate-limit** with the following windows:
| Endpoint | Limit | Window |
|----------------------------------|---------------|----------|
| `/api/*` (general) | 60 requests | 1 minute |
| `/api/auth/login` | 10 requests | 15 minutes |
| `/api/auth/register` | 5 requests | 1 hour |
| `/api/auth/forgot-password` | 5 requests | 1 hour |
| `/api/auth/resend-verification` | 3 requests | 15 minutes |
---
## Content Security Policy (Helmet)
The application uses **Helmet** to set HTTP security headers. The Content Security Policy directives are configured as follows:
| Directive | Values |
|------------------|------------------------------------------------------------------------|
| `script-src` | `'self'`, `'wasm-unsafe-eval'`, `'unsafe-eval'`, `cdn.jsdelivr.net`, `challenges.cloudflare.com` |
| `script-src-attr`| `'none'` (blocks inline event handlers like `onclick`) |
| `style-src` | `'self'`, `'unsafe-inline'`, `fonts.googleapis.com`, `cdnjs.cloudflare.com` |
| `frame-src` | `'self'`, `challenges.cloudflare.com` |
| `connect-src` | `'self'` + CDN domains + HuggingFace + Cloudflare |
| `object-src` | `'none'` |
---
## CORS
- **Production** (when `APP_URL` is set): restricts the allowed origin to the value of `APP_URL`.
- **Development** (when `APP_URL` is not set): allows all origins.
- Requests with **no origin** (such as those from mobile apps or `curl`) are always permitted.
- `credentials: true` is set to allow cookies to be sent cross-origin.
---
## Roles and Authorization
The application defines three user roles:
| Role | Access Level |
|-------------|-------------------------------------------------------|
| `admin` | Full access to all features, including the Admin Panel. The **first registered user** is automatically promoted to admin. |
| `moderator` | Standard user access plus Learning Hub CMS management. |
| `user` | Standard access to patient encounters and AI features. |
---
## Audit Logging
All authentication-related events are recorded in the `audit_log` database table.
### Fields
| Column | Description |
|--------------|--------------------------------------------------|
| `user_id` | The ID of the user involved (null for failed attempts by unknown users) |
| `action` | The type of event (see below) |
| `ip_address` | The client IP address |
| `details` | A JSON object with additional context |
### Tracked Actions
- `register` -- new account created
- `login` -- successful login
- `login_failed` -- incorrect credentials
- `login_blocked` -- blocked by rate limiter or other policy
- `email_verified` -- user confirmed their email address
- Additional actions for password resets, 2FA changes, and OIDC logins