- 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
5.9 KiB
5.9 KiB
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.
- In production:
Auth Middleware
The authentication middleware checks credentials in the following order:
- Looks for a
Bearertoken in theAuthorizationheader. - If the token is empty or missing (including the case where the header is literally
"Bearer "with no token), falls back to reading theped_authcookie.
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
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-turnstilewidget 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
POSTrequest tohttps://challenges.cloudflare.com/turnstile/v0/siteverifywith the secret key and the client-provided token. - Turnstile is only enforced when
TURNSTILE_SECRET_KEYis 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_URLis set): restricts the allowed origin to the value ofAPP_URL. - Development (when
APP_URLis not set): allows all origins. - Requests with no origin (such as those from mobile apps or
curl) are always permitted. credentials: trueis 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 createdlogin-- successful loginlogin_failed-- incorrect credentialslogin_blocked-- blocked by rate limiter or other policyemail_verified-- user confirmed their email address- Additional actions for password resets, 2FA changes, and OIDC logins