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.
2.7 KiB
2.7 KiB
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_idexists on: users (users.py:30,78), groups (groups.py:33), created at bootstrap asorg-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 byactor.org_id. - BUT: many endpoints rely on
super_adminglobal 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
- Tenant context on auth:
g.org_idset inrequire_auth. ✅ - Single choke-point guard:
assert_tenant()+current_org_id()in helpers; existing_authorize_grouporg check kept. super_admin is global; admins org-scoped. ✅ - Multi-org create:
POST /api/admin/users {new_org:true}(super_admin) creates a new org + its first admin;GET /api/admin/orgsplatform view (super_admin sees all, admin sees own). Fixedcreate_orgdouble-id bug. ✅ - 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
- 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). ✅ - Audit log
data/audit/audit.jsonlon org.create, user.promote_super_admin, analytics.export. ✅ - CSV export org-scoped — admin exports only own org's sessions. ✅
Phase 3 — SaaS-launch readiness
- Account/plan model (lightweight): org has
plan(trial/pay),seats,activegating login if disabled. Pluggable later. - ToS / legal page + consc ent + privacy acceptance flag on setup.
- Signed, expiring tokens for data export; per-org seats enforced on user creation.
- 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.