feat: tenant isolation Phase 3 — audit_logs, tree_shares, remaining RLS #135

Merged
chihlasm merged 2 commits from feat/tenant-isolation-phase-3 into main 2026-04-11 08:28:47 +00:00
chihlasm commented 2026-04-11 07:02:57 +00:00 (Migrated from github.com)

Summary

  • audit_logs: Added account_id NOT NULL (FK → accounts), backfilled from user_id → users.account_id. Updated log_audit() with optional account_id param + auto-lookup fallback so all ~40 call sites remain unchanged.
  • tree_shares: Added account_id NOT NULL (FK → accounts), backfilled from tree_id → trees.account_id (not created_by → users.account_id — shares must live in the tree owner's tenant so RLS covers them after cross-tenant super-admin shares). Fixed write path in trees.py to use tree.account_id, not current_user.account_id.
  • RLS Phase 3: Enabled ALTER TABLE ... ENABLE ROW LEVEL SECURITY + FORCE ROW LEVEL SECURITY + standard account_id = current_setting(...)::uuid policy on step_ratings, step_usage_log, target_lists, session_shares, audit_logs, tree_shares (6 tables).
  • target_lists: Dropped team_id column (superseded by account_id from Phase 2). Endpoint and schema updated to use account_id throughout.
  • Tests: 6 new RLS isolation tests in test_rls_isolation.py (one per Phase 3 table). Updated test_tree_sharing.py, test_target_lists.py, test_phase1_migrations.py for model changes.

Migration chain

70a5dd746e83  add_account_id_audit_logs
a05e1a1bea7c  add_account_id_tree_shares
04f013768235  enable_rls_phase3
172ad76d7d20  drop_team_id_target_lists

Key correctness note

tree_shares.account_id is derived from tree.account_id (the tree owner's tenant), not from current_user.account_id (the actor). A super admin in tenant A sharing tenant B's tree must produce a share row visible to tenant B's RLS context.

Test plan

  • pytest tests/test_rls_isolation.py — all Phase 3 isolation tests pass
  • pytest tests/test_tree_sharing.py — share account_id matches tree owner
  • pytest tests/test_target_lists.py — cross-account isolation enforced
  • alembic upgrade head on clean DB — migrations apply cleanly

🤖 Generated with Claude Code

## Summary - **`audit_logs`**: Added `account_id NOT NULL` (FK → `accounts`), backfilled from `user_id → users.account_id`. Updated `log_audit()` with optional `account_id` param + auto-lookup fallback so all ~40 call sites remain unchanged. - **`tree_shares`**: Added `account_id NOT NULL` (FK → `accounts`), backfilled from `tree_id → trees.account_id` (not `created_by → users.account_id` — shares must live in the *tree owner's* tenant so RLS covers them after cross-tenant super-admin shares). Fixed write path in `trees.py` to use `tree.account_id`, not `current_user.account_id`. - **RLS Phase 3**: Enabled `ALTER TABLE ... ENABLE ROW LEVEL SECURITY` + `FORCE ROW LEVEL SECURITY` + standard `account_id = current_setting(...)::uuid` policy on `step_ratings`, `step_usage_log`, `target_lists`, `session_shares`, `audit_logs`, `tree_shares` (6 tables). - **`target_lists`**: Dropped `team_id` column (superseded by `account_id` from Phase 2). Endpoint and schema updated to use `account_id` throughout. - **Tests**: 6 new RLS isolation tests in `test_rls_isolation.py` (one per Phase 3 table). Updated `test_tree_sharing.py`, `test_target_lists.py`, `test_phase1_migrations.py` for model changes. ## Migration chain ``` 70a5dd746e83 add_account_id_audit_logs a05e1a1bea7c add_account_id_tree_shares 04f013768235 enable_rls_phase3 172ad76d7d20 drop_team_id_target_lists ``` ## Key correctness note `tree_shares.account_id` is derived from **`tree.account_id`** (the tree owner's tenant), not from `current_user.account_id` (the actor). A super admin in tenant A sharing tenant B's tree must produce a share row visible to tenant B's RLS context. ## Test plan - [ ] `pytest tests/test_rls_isolation.py` — all Phase 3 isolation tests pass - [ ] `pytest tests/test_tree_sharing.py` — share account_id matches tree owner - [ ] `pytest tests/test_target_lists.py` — cross-account isolation enforced - [ ] `alembic upgrade head` on clean DB — migrations apply cleanly 🤖 Generated with [Claude Code](https://claude.com/claude-code)
railway-app[bot] commented 2026-04-11 07:03:16 +00:00 (Migrated from github.com)

🚅 Deployed to the resolutionflow-pr-135 environment in selfless-grace

Service Status Web Updated (UTC)
hopeful-liberation Success (View Logs) Apr 11, 2026 at 7:04 am
patherly Success (View Logs) Apr 11, 2026 at 7:03 am
<!-- railway-bot-comment-version=2 --> <!-- railway-project-id="22b9b58c-271b-42e5-a10e-6fdec8d00134" railway-project-name="selfless-grace" --> 🚅 Deployed to the [resolutionflow-pr-135](https://railway.com/project/22b9b58c-271b-42e5-a10e-6fdec8d00134?environmentId=edadebfe-03c6-40fa-9e31-1dc7e989d78d) environment in **[selfless-grace](https://railway.com/project/22b9b58c-271b-42e5-a10e-6fdec8d00134)** | **Service** | **Status** | **Web** | **Updated** (UTC) | | :--- | :--- | :--- | :--- | | hopeful-liberation | ✅ Success ([View Logs](https://railway.com/project/22b9b58c-271b-42e5-a10e-6fdec8d00134/service/e1db2ee3-d241-4f45-abe4-c9c5fdf483d5?id=6e8dfcea-1e9d-4c8c-8001-b397b62b776f&environmentId=edadebfe-03c6-40fa-9e31-1dc7e989d78d)) | | Apr 11, 2026 at 7:04 am | | patherly | ✅ Success ([View Logs](https://railway.com/project/22b9b58c-271b-42e5-a10e-6fdec8d00134/service/95f556ff-5264-4116-a0c2-618a2fc53ba4?id=21793434-6135-4f26-b090-8d2d17fd7496&environmentId=edadebfe-03c6-40fa-9e31-1dc7e989d78d)) | | Apr 11, 2026 at 7:03 am |
Sign in to join this conversation.