From 1fa4c3e3ad9a130a33217cae3f65ee309220aee5 Mon Sep 17 00:00:00 2001 From: Adolfo Delorenzo Date: Tue, 24 Mar 2026 13:57:17 -0600 Subject: [PATCH] =?UTF-8?q?docs(04-rbac-01):=20complete=20RBAC=20foundatio?= =?UTF-8?q?n=20plan=20=E2=80=94=20migration,=20guards,=20invitations,=20te?= =?UTF-8?q?sts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .planning/REQUIREMENTS.md | 20 ++-- .planning/ROADMAP.md | 2 +- .planning/STATE.md | 19 ++-- .planning/phases/04-rbac/04-01-SUMMARY.md | 131 ++++++++++++++++++++++ 4 files changed, 154 insertions(+), 18 deletions(-) create mode 100644 .planning/phases/04-rbac/04-01-SUMMARY.md diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 157c172..0eadd0e 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -49,12 +49,12 @@ Requirements for beta-ready release. Each maps to roadmap phases. ### RBAC & User Management -- [ ] **RBAC-01**: Platform admin role with full access to all tenants, agents, users, and platform settings -- [ ] **RBAC-02**: Customer admin role scoped to a single tenant with full control over agents, channels, billing, API keys, and user management -- [ ] **RBAC-03**: Customer operator role scoped to a single tenant with read-only access to agents, conversations, and usage dashboards -- [ ] **RBAC-04**: Customer admin can invite users (admin or operator) by email — invitee receives activation link to set password and enable access +- [x] **RBAC-01**: Platform admin role with full access to all tenants, agents, users, and platform settings +- [x] **RBAC-02**: Customer admin role scoped to a single tenant with full control over agents, channels, billing, API keys, and user management +- [x] **RBAC-03**: Customer operator role scoped to a single tenant with read-only access to agents, conversations, and usage dashboards +- [x] **RBAC-04**: Customer admin can invite users (admin or operator) by email — invitee receives activation link to set password and enable access - [ ] **RBAC-05**: Portal navigation, pages, and UI elements adapt based on user role (platform admin sees tenant picker, customer admin sees their tenant, operator sees read-only views) -- [ ] **RBAC-06**: API endpoints enforce role-based authorization — unauthorized actions return 403 Forbidden, not just hidden UI +- [x] **RBAC-06**: API endpoints enforce role-based authorization — unauthorized actions return 403 Forbidden, not just hidden UI ## v2 Requirements @@ -129,12 +129,12 @@ Which phases cover which requirements. Updated during roadmap creation. | PRTA-04 | Phase 3 | Complete | | PRTA-05 | Phase 3 | Complete | | PRTA-06 | Phase 3 | Complete | -| RBAC-01 | Phase 4 | Pending | -| RBAC-02 | Phase 4 | Pending | -| RBAC-03 | Phase 4 | Pending | -| RBAC-04 | Phase 4 | Pending | +| RBAC-01 | Phase 4 | Complete | +| RBAC-02 | Phase 4 | Complete | +| RBAC-03 | Phase 4 | Complete | +| RBAC-04 | Phase 4 | Complete | | RBAC-05 | Phase 4 | Pending | -| RBAC-06 | Phase 4 | Pending | +| RBAC-06 | Phase 4 | Complete | **Coverage:** - v1 requirements: 25 total (all complete) diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 739b887..87524b1 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -103,7 +103,7 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4 | 1. Foundation | 4/4 | Complete | 2026-03-23 | | 2. Agent Features | 6/6 | Complete | 2026-03-24 | | 3. Operator Experience | 5/5 | Complete | 2026-03-24 | -| 4. RBAC | 0/3 | Planned | — | +| 4. RBAC | 1/3 | In Progress| | --- diff --git a/.planning/STATE.md b/.planning/STATE.md index 3a43c6e..aad3dcd 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,14 +3,14 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: milestone status: completed -stopped_at: Phase 4 context gathered -last_updated: "2026-03-24T19:09:47.443Z" +stopped_at: Completed 04-rbac-01-PLAN.md +last_updated: "2026-03-24T19:57:06.246Z" last_activity: 2026-03-23 — Completed 03-02 onboarding wizard, Slack OAuth, BYO API keys progress: total_phases: 4 completed_phases: 3 - total_plans: 15 - completed_plans: 15 + total_plans: 18 + completed_plans: 16 percent: 100 --- @@ -67,6 +67,7 @@ Progress: [██████████] 100% | Phase 03-operator-experience P03 | 8min | 2 tasks | 6 files | | Phase 03-operator-experience P04 | 10min | 2 tasks | 8 files | | Phase 03-operator-experience P05 | 2min | 2 tasks | 6 files | +| Phase 04-rbac P01 | 8min | 3 tasks | 14 files | ## Accumulated Context @@ -136,6 +137,10 @@ Recent decisions affecting current work: - [Phase 03-operator-experience]: Usage nav links to /usage tenant picker (not hardcoded tenantId) — supports multi-tenant operators - [Phase 03-operator-experience]: BudgetAlertBadge renders neutral 'No limit set' for null budget_limit_usd — prevents false alarms - [Phase 03-operator-experience]: All Phase 3 portal routers (portal, billing, channels, llm_keys, usage, webhook) mounted directly on gateway FastAPI app +- [Phase 04-rbac]: Role stored as TEXT+CHECK (not sa.Enum) per Phase 1 ADR to avoid Alembic DDL conflicts +- [Phase 04-rbac]: SHA-256 hash of raw invite token stored in DB — token_to_hash enables O(1) lookup without exposing token +- [Phase 04-rbac]: platform_admin bypasses tenant membership check entirely (no DB query) for simpler, faster guard logic +- [Phase 04-rbac]: Celery invite email task dispatched via lazy local import in invitations.py to avoid shared->orchestrator circular dep ### Roadmap Evolution @@ -151,6 +156,6 @@ None — all phases complete. ## Session Continuity -Last session: 2026-03-24T19:09:47.440Z -Stopped at: Phase 4 context gathered -Resume file: .planning/phases/04-rbac/04-CONTEXT.md +Last session: 2026-03-24T19:57:06.244Z +Stopped at: Completed 04-rbac-01-PLAN.md +Resume file: None diff --git a/.planning/phases/04-rbac/04-01-SUMMARY.md b/.planning/phases/04-rbac/04-01-SUMMARY.md new file mode 100644 index 0000000..1da3d97 --- /dev/null +++ b/.planning/phases/04-rbac/04-01-SUMMARY.md @@ -0,0 +1,131 @@ +--- +phase: 04-rbac +plan: 01 +subsystem: rbac +tags: [rbac, auth, invitations, migration, orm, guards] +dependency_graph: + requires: [] + provides: + - RBAC guard dependencies (require_platform_admin, require_tenant_admin, require_tenant_member) + - Migration 006 (user_tenant_roles, portal_invitations tables) + - HMAC invite token generation and validation + - Invitation CRUD API (create, accept, resend, list) + - SMTP email utility for invitations + - auth/verify returning role + tenant_ids (replaces is_admin) + affects: + - packages/shared/shared/models/auth.py (PortalUser.is_admin removed) + - packages/shared/shared/api/portal.py (AuthVerifyResponse shape changed) + - packages/gateway/gateway/main.py (invitations_router mounted) +tech_stack: + added: [] + patterns: + - TEXT + CHECK constraint for role column (per Phase 1 ADR, avoids sa.Enum DDL issues) + - HMAC-SHA256 with hmac.compare_digest for timing-safe token verification + - SHA-256(token) stored in DB — raw token never persisted + - Celery fire-and-forget via lazy local import in API handler (avoids circular dep) + - platform_admin bypasses all tenant membership checks (no DB query) +key_files: + created: + - migrations/versions/006_rbac_roles.py + - packages/shared/shared/api/rbac.py + - packages/shared/shared/invite_token.py + - packages/shared/shared/email.py + - packages/shared/shared/api/invitations.py + - tests/unit/test_rbac_guards.py + - tests/unit/test_invitations.py + - tests/unit/test_portal_auth.py + modified: + - packages/shared/shared/models/auth.py + - packages/shared/shared/api/portal.py + - packages/shared/shared/api/__init__.py + - packages/shared/shared/config.py + - packages/gateway/gateway/main.py + - packages/orchestrator/orchestrator/tasks.py +decisions: + - "Role stored as TEXT + CHECK (not sa.Enum) — per Phase 1 ADR to avoid Alembic DDL conflicts" + - "SHA-256 hash of raw token stored in DB — token_hash enables O(1) lookup without exposing token" + - "platform_admin bypasses tenant membership check without DB query — simpler and faster" + - "Celery task dispatch uses lazy local import in invitations.py — avoids shared->orchestrator circular dep" + - "portal_url reused for invite link construction — not duplicated as portal_base_url" +metrics: + duration: "8 minutes" + completed: "2026-03-24" + tasks_completed: 3 + files_created: 8 + files_modified: 6 +--- + +# Phase 4 Plan 01: RBAC Foundation Summary + +**One-liner:** 3-tier RBAC (platform_admin/customer_admin/customer_operator) with DB migration, FastAPI guard dependencies, HMAC invite tokens, and invite-only onboarding API. + +## What Was Built + +### Task 1: DB Migration + ORM Models + Config +Migration 006 (`migrations/versions/006_rbac_roles.py`) adds the RBAC schema to PostgreSQL: +- Adds `role TEXT + CHECK` column to `portal_users`, backfills `is_admin` values, drops `is_admin` +- Creates `user_tenant_roles` table (user_id FK, tenant_id FK, UNIQUE constraint) +- Creates `portal_invitations` table (token_hash UNIQUE, status, expires_at, all FKs) + +`packages/shared/shared/models/auth.py` gains: +- `UserRole` string enum (PLATFORM_ADMIN, CUSTOMER_ADMIN, CUSTOMER_OPERATOR) +- `UserTenantRole` ORM model with CASCADE deletes +- `PortalInvitation` ORM model +- `PortalUser.role` replaces `PortalUser.is_admin` + +`packages/shared/shared/config.py` gains: `invite_secret`, `smtp_host`, `smtp_port`, `smtp_username`, `smtp_password`, `smtp_from_email`. + +### Task 2: RBAC Guards + Invite Token + Email + Invitation API + +**`packages/shared/shared/api/rbac.py`** — FastAPI dependency guards: +- `PortalCaller` dataclass (user_id, role, tenant_id from request headers) +- `get_portal_caller` — parses X-Portal-User-Id/Role/Tenant-Id headers, raises 401 on bad UUID +- `require_platform_admin` — raises 403 for non-platform_admin +- `require_tenant_admin` — platform_admin bypasses; customer_admin checked against UserTenantRole; operator always 403 +- `require_tenant_member` — platform_admin bypasses; customer_admin/operator checked against UserTenantRole + +**`packages/shared/shared/invite_token.py`** — HMAC token utilities: +- `generate_invite_token(invitation_id)` — HMAC-SHA256, base64url-encoded, embeds `{id}:{timestamp}` +- `validate_invite_token(token)` — timing-safe compare_digest, 48h TTL check, returns invitation_id +- `token_to_hash(token)` — SHA-256 hex digest for DB storage + +**`packages/shared/shared/email.py`** — SMTP email sender (sync, for Celery): +- Sends HTML+text multipart invite email +- Skips silently if smtp_host is empty (dev-friendly) + +**`packages/shared/shared/api/invitations.py`** — Invitation CRUD router: +- `POST /api/portal/invitations` — create invitation (requires tenant admin), returns raw token +- `POST /api/portal/invitations/accept` — validate token, create PortalUser + UserTenantRole, mark accepted +- `POST /api/portal/invitations/{id}/resend` — regenerate token, extend expiry +- `GET /api/portal/invitations` — list pending invitations for caller's tenant + +**`packages/shared/shared/api/portal.py`** — auth/verify updated: +- `AuthVerifyResponse` now returns `role`, `tenant_ids`, `active_tenant_id` (replaced `is_admin`) +- platform_admin returns all tenant IDs; customer roles return their UserTenantRole tenant IDs +- `/auth/register` gated behind `require_platform_admin` with deprecation comment + +**`packages/orchestrator/orchestrator/tasks.py`** — added `send_invite_email_task` Celery task. + +**`packages/gateway/gateway/main.py`** — `invitations_router` mounted. + +### Task 3: Unit Tests (27 passing) + +- `tests/unit/test_rbac_guards.py` (11 tests): RBAC guard pass/reject scenarios, platform_admin bypass +- `tests/unit/test_invitations.py` (11 tests): HMAC token roundtrip, tamper/expiry, invitation CRUD +- `tests/unit/test_portal_auth.py` (7 tests): auth/verify returns role+tenant_ids+active_tenant_id + +## Deviations from Plan + +None — plan executed exactly as written. + +## Commits + +| Hash | Description | +|------|-------------| +| f710c9c | feat(04-rbac-01): DB migration 006 + RBAC ORM models + config fields | +| d59f85c | feat(04-rbac-01): RBAC guards + invite token + email + invitation API | +| 7b0594e | test(04-rbac-01): unit tests for RBAC guards, invitation system, portal auth | + +## Self-Check: PASSED + +All files created and verified before this summary was written.