Some checks failed
Consolidate User.role (2-value: admin/store) + User.is_super_admin (boolean) into a single 4-value UserRole enum: super_admin, platform_admin, merchant_owner, store_member. Drop stale StoreUser.user_type column. Fix role="user" bug in merchant creation. Key changes: - Expand UserRole enum from 2 to 4 values with computed properties (is_admin, is_super_admin, is_platform_admin, is_merchant_owner, is_store_user) - Add Alembic migration (tenancy_003) for data migration + column drops - Remove is_super_admin from JWT token payload - Update all auth dependencies, services, routes, templates, JS, and tests - Update all RBAC documentation 66 files changed, 1219 unit tests passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
340 lines
19 KiB
Markdown
340 lines
19 KiB
Markdown
# RBAC Architecture Visual Guide
|
|
|
|
## System Overview
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ PLATFORM LEVEL │
|
|
│ User.role (4-value enum) │
|
|
│ │
|
|
│ ┌──────────────────┐ ┌──────────────────┐ │
|
|
│ │ Super Admin │ │ Platform Admin │ │
|
|
│ │ role= │ │ role= │ │
|
|
│ │ "super_admin" │ │ "platform_admin"│ │
|
|
│ │ │ │ │ │
|
|
│ │ • Full platform │ │ • Scoped to │ │
|
|
│ │ access │ │ assigned │ │
|
|
│ │ • All platforms │ │ platforms │ │
|
|
│ │ • Cannot access │ │ • Cannot access │ │
|
|
│ │ store portal │ │ store portal │ │
|
|
│ └──────────────────┘ └──────────────────┘ │
|
|
│ │
|
|
│ ┌──────────────────┐ ┌──────────────────┐ │
|
|
│ │ Merchant Owner │ │ Store Member │ │
|
|
│ │ role= │ │ role= │ │
|
|
│ │ "merchant_owner"│ │ "store_member" │ │
|
|
│ │ │ │ │ │
|
|
│ │ • Owns stores │ │ • Invited to │ │
|
|
│ │ • All perms in │ │ stores │ │
|
|
│ │ own stores │ │ • Role-based │ │
|
|
│ │ • Cannot access │ │ permissions │ │
|
|
│ │ admin portal │ │ • Cannot access │ │
|
|
│ └──────────────────┘ │ admin portal │ │
|
|
│ └──────────────────┘ │
|
|
│ │ │
|
|
└──────────────────────────────────────────────┼──────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ STORE LEVEL │
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
│ │ Store: ACME │ │
|
|
│ │ │ │
|
|
│ │ ┌──────────────┐ ┌──────────────────────┐ │ │
|
|
│ │ │ Owner │ │ Team Members │ │ │
|
|
│ │ │ role= │ │ role= │ │ │
|
|
│ │ │ "merchant_ │ │ "store_member" │ │ │
|
|
│ │ │ owner" │ │ │ │ │
|
|
│ │ │ │ │ • Role-based perms │ │ │
|
|
│ │ │ • All perms │ │ • Manager/Staff/etc │ │ │
|
|
│ │ │ • Can invite │ │ • Can be invited │ │ │
|
|
│ │ │ • Can remove │ │ • Can be removed │ │ │
|
|
│ │ │ • Cannot be │ │ │ │ │
|
|
│ │ │ removed │ │ │ │ │
|
|
│ │ └──────────────┘ └──────────────────────┘ │ │
|
|
│ │ │ │ │
|
|
│ │ Ownership via Merchant.owner_user_id ▼ │ │
|
|
│ │ ┌──────────────────────────────┐ │ │
|
|
│ │ │ Roles │ │ │
|
|
│ │ │ │ │ │
|
|
│ │ │ • Manager (many perms) │ │ │
|
|
│ │ │ • Staff (moderate perms) │ │ │
|
|
│ │ │ • Support (limited perms) │ │ │
|
|
│ │ │ • Viewer (read-only) │ │ │
|
|
│ │ │ • Custom (owner-defined) │ │ │
|
|
│ │ └──────────────────────────────┘ │ │
|
|
│ └──────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ CUSTOMER LEVEL │
|
|
│ (Separate from Users) │
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
│ │ Customers (per store) │ │
|
|
│ │ │ │
|
|
│ │ • Store-scoped authentication │ │
|
|
│ │ • Can self-register │ │
|
|
│ │ • Access own account + shop catalog │ │
|
|
│ │ • Cannot access admin/store portals │ │
|
|
│ └──────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Team Invitation Flow
|
|
|
|
```
|
|
┌──────────┐
|
|
│ Owner │
|
|
│ (ACME) │
|
|
└────┬─────┘
|
|
│
|
|
│ 1. Click "Invite Team Member"
|
|
│ Email: jane@example.com
|
|
│ Role: Manager
|
|
▼
|
|
┌─────────────────────┐
|
|
│ System Creates: │
|
|
│ • User account │
|
|
│ • StoreUser │
|
|
│ • Invitation token │
|
|
└────┬────────────────┘
|
|
│
|
|
│ 2. Email sent to jane@example.com
|
|
│
|
|
▼
|
|
┌──────────────────────┐
|
|
│ Jane clicks link │
|
|
│ /invitation/accept? │
|
|
│ token=abc123... │
|
|
└────┬─────────────────┘
|
|
│
|
|
│ 3. Jane sets password
|
|
│ Enters name
|
|
│
|
|
▼
|
|
┌─────────────────────┐
|
|
│ Account Activated: │
|
|
│ • User.is_active │
|
|
│ • StoreUser. │
|
|
│ is_active │
|
|
└────┬────────────────┘
|
|
│
|
|
│ 4. Jane can now login
|
|
│
|
|
▼
|
|
┌──────────────────────┐
|
|
│ Jane logs in to │
|
|
│ ACME store portal │
|
|
│ with Manager perms │
|
|
└──────────────────────┘
|
|
```
|
|
|
|
## Permission Check Flow
|
|
|
|
```
|
|
┌────────────────┐
|
|
│ User makes │
|
|
│ request to: │
|
|
│ POST /products │
|
|
└───────┬────────┘
|
|
│
|
|
▼
|
|
┌────────────────────────────────┐
|
|
│ FastAPI Dependency: │
|
|
│ require_store_permission( │
|
|
│ "products.create" │
|
|
│ ) │
|
|
└───────┬────────────────────────┘
|
|
│
|
|
│ 1. Get store from request.state
|
|
│ 2. Get user from JWT
|
|
│
|
|
▼
|
|
┌────────────────────────────────┐
|
|
│ Is user a member of store? │
|
|
└───────┬────────────────────────┘
|
|
│
|
|
├─ No ──> ❌ StoreAccessDeniedException
|
|
│
|
|
▼ Yes
|
|
┌────────────────────────────────┐
|
|
│ Is user the owner? │
|
|
└───────┬────────────────────────┘
|
|
│
|
|
├─ Yes ──> ✅ Allow (owners have all perms)
|
|
│
|
|
▼ No
|
|
┌────────────────────────────────┐
|
|
│ Get user's role and │
|
|
│ permissions from StoreUser │
|
|
└───────┬────────────────────────┘
|
|
│
|
|
▼
|
|
┌────────────────────────────────┐
|
|
│ Does role contain │
|
|
│ "products.create"? │
|
|
└───────┬────────────────────────┘
|
|
│
|
|
├─ No ──> ❌ InsufficientStorePermissionsException
|
|
│
|
|
▼ Yes
|
|
┌────────────────────────────────┐
|
|
│ ✅ Allow request │
|
|
│ Handler executes │
|
|
└────────────────────────────────┘
|
|
```
|
|
|
|
## Database Relationships
|
|
|
|
```
|
|
┌──────────────────┐
|
|
│ users │
|
|
│ │
|
|
│ id (PK) │◄────┐
|
|
│ email │ │
|
|
│ role │ │
|
|
│ ('super_admin', │ │
|
|
│ 'platform_ │ │
|
|
│ admin', │ │
|
|
│ 'merchant_ │ │
|
|
│ owner', │ │
|
|
│ 'store_member') │ │
|
|
└──────────────────┘ │
|
|
│ │
|
|
│ owner_user_id │
|
|
│ │
|
|
▼ │
|
|
┌──────────────────┐ │
|
|
│ stores │ │
|
|
│ │ │
|
|
│ id (PK) │ │
|
|
│ store_code │ │
|
|
│ owner_user_id ──┼─────┘
|
|
└──────────────────┘
|
|
│
|
|
│
|
|
▼
|
|
┌──────────────────┐ ┌──────────────────┐
|
|
│ store_users │ │ roles │
|
|
│ │ │ │
|
|
│ id (PK) │ │ id (PK) │
|
|
│ store_id (FK) │ │ store_id (FK) │
|
|
│ user_id (FK) │ │ name │
|
|
│ role_id (FK) ───┼────►│ permissions │
|
|
│ invitation_* │ │ (JSON) │
|
|
│ is_active │ └──────────────────┘
|
|
└──────────────────┘
|
|
(no user_type column;
|
|
ownership via
|
|
Merchant.owner_user_id)
|
|
|
|
Separate hierarchy:
|
|
|
|
┌──────────────────┐
|
|
│ customers │
|
|
│ │
|
|
│ id (PK) │
|
|
│ store_id (FK) │
|
|
│ email │
|
|
│ hashed_password │
|
|
│ (store-scoped) │
|
|
└──────────────────┘
|
|
```
|
|
|
|
## Permission Naming Convention
|
|
|
|
```
|
|
Resource.Action
|
|
|
|
Examples:
|
|
✓ dashboard.view
|
|
✓ products.view
|
|
✓ products.create
|
|
✓ products.edit
|
|
✓ products.delete
|
|
✓ products.import
|
|
✓ products.export
|
|
✓ orders.view
|
|
✓ orders.edit
|
|
✓ orders.cancel
|
|
✓ orders.refund
|
|
✓ customers.view
|
|
✓ customers.edit
|
|
✓ reports.financial
|
|
✓ team.invite
|
|
✓ team.remove
|
|
✓ settings.edit
|
|
```
|
|
|
|
## Role Presets
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────────────────┐
|
|
│ OWNER │
|
|
│ ALL PERMISSIONS (automatic, not stored in role) │
|
|
└──────────────────────────────────────────────────────────────┘
|
|
|
|
┌──────────────────────────────────────────────────────────────┐
|
|
│ MANAGER │
|
|
│ Most permissions except: │
|
|
│ • team.invite/remove (owner only) │
|
|
│ • Critical settings (owner only) │
|
|
│ │
|
|
│ Has: products.*, orders.*, customers.*, reports.* │
|
|
└──────────────────────────────────────────────────────────────┘
|
|
|
|
┌──────────────────────────────────────────────────────────────┐
|
|
│ STAFF │
|
|
│ Day-to-day operations: │
|
|
│ • products.view/create/edit │
|
|
│ • stock.view/edit │
|
|
│ • orders.view/edit │
|
|
│ • customers.view │
|
|
└──────────────────────────────────────────────────────────────┘
|
|
|
|
┌──────────────────────────────────────────────────────────────┐
|
|
│ SUPPORT │
|
|
│ Customer service focus: │
|
|
│ • orders.view/edit │
|
|
│ • customers.view/edit │
|
|
│ • products.view (read-only) │
|
|
└──────────────────────────────────────────────────────────────┘
|
|
|
|
┌──────────────────────────────────────────────────────────────┐
|
|
│ VIEWER │
|
|
│ Read-only access: │
|
|
│ • *.view permissions only │
|
|
│ • No edit/create/delete │
|
|
└──────────────────────────────────────────────────────────────┘
|
|
|
|
┌──────────────────────────────────────────────────────────────┐
|
|
│ MARKETING │
|
|
│ Marketing & analytics focus: │
|
|
│ • customers.view/export │
|
|
│ • marketing.* (all marketing actions) │
|
|
│ • reports.view │
|
|
└──────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Security Boundaries
|
|
|
|
```
|
|
❌ BLOCKED ✅ ALLOWED
|
|
|
|
super_admin → Store Portal super_admin → Admin Portal
|
|
platform_admin → Store Portal platform_admin → Admin Portal
|
|
merchant_owner → Admin Portal merchant_owner → Store Portal
|
|
store_member → Admin Portal store_member → Store Portal
|
|
Customer → Admin Portal Customer → Shop Catalog
|
|
Customer → Store Portal Customer → Own Account
|
|
|
|
Cookie Isolation:
|
|
admin_token (path=/admin) ← Only sent to /admin/*
|
|
store_token (path=/store) ← Only sent to /store/*
|
|
customer_token (path=/shop) ← Only sent to /shop/*
|
|
```
|