Security overview
High-level picture of how Sophia protects the API, tenants, and sensitive data.
Auth flow
flowchart LR Client[Browser / Bot / Stripe] API[Sophia API] Firebase[Firebase Auth] DB[(MongoDB)] Client -->|Bearer ID token| API Client -->|Join / session tokens| API Client -->|x-internal-token| API Client -->|stripe-signature| API API --> Firebase API --> DB
Security controls
| Area | What exists |
|---|---|
| SaaS auth | Firebase ID token verification; active user + org; suspended org blocked |
| Roles | admin and member; admin-only routes for billing, team, some deletes |
| Candidate access | Hashed join tokens; optional password; session header; rate limit on failed passwords |
| Bot access | Shared secret header |
| Stripe webhooks | HMAC signature on raw body |
| Transport | Secure headers, CORS allowlist |
| Input | Zod validation on routes |
| Tokens at rest | SHA-256 hashes for join, session, and invite tokens |
| PII | AES-256-GCM for candidate phone; Pino redaction for logs |
| Secrets | Gitleaks in CI |
Tenant isolation
One shared database. Every tenant query filters on organizationId in repository layers. Auth resolves org from the user record, not from the request body.
Known gaps
- Only two roles — no fine-grained permissions matrix in middleware
- No API-wide rate limiting (except candidate password verify)
- Firebase ID tokens stay valid until expiry after logout or role change
- Encryption covers candidate phone only, not email or names
organizationId/userIdare not auto-attached to every log line (onlyrequestId)- No compliance certifications encoded in the product