P3a: org carries plan/seats/active/created_at; create_user enforces seats + rejects inactive org; verify blocks login for inactive orgs; PATCH /api/admin/orgs (super_admin) updates plan/seats/active with audit. Fixed verify swallowing its AuthError. P3b: export/token issues a 5-min HMAC one-time CSV link; export accepts ?token=. P3c: setup requires accepted_terms (consent stored); Setup.vue consent checkbox. All 8 backend suites pass. Rebuilt dist.
3.1 KiB
3.1 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 ✅ DONE
- Account/plan model: org has
plan(trial/paid/enterprise),seats,active,created_at.create_userenforces seats + active;verifyblocks login for inactive orgs.PATCH /api/admin/orgs/<id>(super_admin) sets plan/seats/active + audit. - ToS/consent: setup now requires
accepted_terms(stored as accepted_terms_at); Setup.vue shows a ToS/Privacy consent checkbox (links /legal stub). ✅ - Signed expiring export link:
/api/analytics/export/tokenissues a 5-min HMAC signed one-time link;/api/analytics/export?token=accepts it (Bearer JWT still works). Seats/active enforced on user creation. ✅ - Persona "recipe" server-side obfuscation: interim since strip (Phase 1); full move of internal logic server-side remains a longer-term item (out of v1 launch).
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.