Phase 1 (tenant isolation):
- g.org_id set on require_auth; assert_tenant()/current_org_id() choke-point helpers.
- Multi-org provisioning: POST /api/admin/users {new_org:true} (super_admin) creates a
new org + its first admin; GET /api/admin/orgs (super_admin sees all, admin own).
- Fixed latent create_org double-id bug (dict id != store key).
- test_saas_tenant.py: org2 admin blocked from org1 group (403), can't list org1
groups/users, sees only own org; super_admin sees all.
Phase 2 (hardening):
- Rate limit login (per-IP + per-username) + chat send (per-user) to protect LLM cost
and slow brute force; services/rate_limit.py (in-memory + disk, no external deps).
- Audit log data/audit/audit.jsonl on org.create, user.promote_super_admin, analytics.export.
- CSV export now org-scoped (admin exports only own org).
All 8 backend suites pass.
47 lines
2.7 KiB
Markdown
47 lines
2.7 KiB
Markdown
# SaaS / Multi-tenant Plan — Sales Trainer
|
|
|
|
Product is being sold as SaaS. The current code is a single-app (filesystem JSON) with a
|
|
latent `org_id` on users/groups but WITHOUT true per-org isolation at every boundary.
|
|
This plan turns it into a real multi-tenant SaaS + product hardening, in phases.
|
|
|
|
## Current state (verified)
|
|
- `org_id` exists on: users (`users.py:30,78`), groups (`groups.py:33`), created at bootstrap
|
|
as `org-default` (`factory.py:19`).
|
|
- Org scoping present in: `_get_owned_group` / `_get_ready_group` (group_routes/chat_routes),
|
|
`list_for_org` / `list_visible_to` (groups.py), `list_users(org_id=...)` (users.py),
|
|
admin/me/analytics routes filter by `actor.org_id`.
|
|
- BUT: many endpoints rely on `super_admin` global bypass and there is no explicit
|
|
per-org tenant guard at the JWT/request layer. Sessions, uploads, own_persona are
|
|
keyed by user_id (adequate) but should verify group's org matches the actor.
|
|
|
|
## Phase 1 — Enforce tenant isolation (multi-tenant correctness) ✅ DONE
|
|
1. **Tenant context on auth**: `g.org_id` set in `require_auth`. ✅
|
|
2. **Single choke-point guard**: `assert_tenant()` + `current_org_id()` in helpers; existing
|
|
`_authorize_group` org check kept. super_admin is global; admins org-scoped. ✅
|
|
3. **Multi-org create**: `POST /api/admin/users {new_org:true}` (super_admin) creates a new
|
|
org + its first admin; `GET /api/admin/orgs` platform view (super_admin sees all,
|
|
admin sees own). Fixed `create_org` double-id bug. ✅
|
|
4. **Regression test** `test_saas_tenant.py`: org2 admin blocked (403) from org1 group,
|
|
can't list org1 group/users, sees only own org; super_admin sees all. ✅
|
|
|
|
## Phase 2 — Product hardening (provenance, abuse, secrets) ✅ DONE
|
|
1. **Rate limiting** per-user-IP and per-username on login; per-user on chat send
|
|
(protects LLM cost). New `services/rate_limit.py` (in-memory + disk, no deps). ✅
|
|
2. **Audit log** `data/audit/audit.jsonl` on org.create, user.promote_super_admin,
|
|
analytics.export. ✅
|
|
3. **CSV export org-scoped** — admin exports only own org's sessions. ✅
|
|
|
|
## Phase 3 — SaaS-launch readiness
|
|
1. **Account/plan model (lightweight)**: org has `plan` (trial/pay), `seats`, `active`
|
|
gating login if disabled. Pluggable later.
|
|
2. **ToS / legal page** + consc ent + privacy acceptance flag on setup.
|
|
3. **Signed, expiring tokens** for data export; per-org seats enforced on user creation.
|
|
4. Obfuscate persona "recipe" — move internal signal logic server-side so the client never
|
|
receives full latent recipe (longer-term; the strip in Phase 1 is the interim).
|
|
|
|
## Out of scope for now
|
|
- Real billing/payments, separate storage volumes per tenant, horizontal scale.
|
|
|
|
---
|
|
Status flags next to each phase item updated as work completes.
|