RBAC documentation
This commit is contained in:
474
docs/__REVAMPING/RBAC/RBAC_IMPLEMENTATION_SUMMARY.md
Normal file
474
docs/__REVAMPING/RBAC/RBAC_IMPLEMENTATION_SUMMARY.md
Normal file
@@ -0,0 +1,474 @@
|
||||
# RBAC Implementation Recommendations - Summary
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Your current authentication and authorization system is well-structured. Here are my recommendations to enhance it for proper role-based access control (RBAC) in your multi-tenant e-commerce platform.
|
||||
|
||||
## Key Recommendations
|
||||
|
||||
### 1. ✅ Clarify User.role Field
|
||||
|
||||
**Current Issue:** The `User.role` field is used for both platform-level and vendor-specific roles.
|
||||
|
||||
**Solution:**
|
||||
- `User.role` should ONLY contain: `"admin"` or `"vendor"`
|
||||
- Vendor-specific roles (manager, staff, etc.) belong in `VendorUser.role`
|
||||
- Customers are separate in the `Customer` model (already correct)
|
||||
|
||||
**Benefits:**
|
||||
- Clear separation of concerns
|
||||
- Easier to manage platform-level access
|
||||
- Prevents confusion between contexts
|
||||
|
||||
### 2. ✅ Add VendorUser.user_type Field
|
||||
|
||||
**Why:** Distinguish between vendor owners and team members at the database level.
|
||||
|
||||
**Implementation:**
|
||||
```python
|
||||
class VendorUserType(str, enum.Enum):
|
||||
OWNER = "owner"
|
||||
TEAM_MEMBER = "member"
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Owners have automatic full permissions
|
||||
- Team members use role-based permissions
|
||||
- Easy to query for owners vs. members
|
||||
- Prevents accidentally removing owners
|
||||
|
||||
### 3. ✅ Implement Invitation System
|
||||
|
||||
**Components Needed:**
|
||||
- `invitation_token` field in VendorUser
|
||||
- `invitation_sent_at` and `invitation_accepted_at` timestamps
|
||||
- Email sending service
|
||||
- Invitation acceptance endpoint
|
||||
|
||||
**Flow:**
|
||||
1. Owner invites team member via email
|
||||
2. System creates User account (inactive) and VendorUser (pending)
|
||||
3. Invitation email sent with unique token
|
||||
4. Team member clicks link, sets password, activates account
|
||||
5. VendorUser.is_active set to True
|
||||
|
||||
### 4. ✅ Define Permission Constants
|
||||
|
||||
**Create:** `app/core/permissions.py` with:
|
||||
- `VendorPermissions` enum (all available permissions)
|
||||
- `PermissionGroups` class (preset role permissions)
|
||||
- `PermissionChecker` utility class
|
||||
|
||||
**Example Permissions:**
|
||||
```python
|
||||
PRODUCTS_VIEW = "products.view"
|
||||
PRODUCTS_CREATE = "products.create"
|
||||
PRODUCTS_DELETE = "products.delete"
|
||||
ORDERS_VIEW = "orders.view"
|
||||
TEAM_INVITE = "team.invite" # Owner only
|
||||
SETTINGS_EDIT = "settings.edit" # Owner/Manager only
|
||||
```
|
||||
|
||||
### 5. ✅ Add Permission Checking Dependencies
|
||||
|
||||
**Create FastAPI dependencies:**
|
||||
- `require_vendor_permission(permission)` - Single permission
|
||||
- `require_vendor_owner()` - Owner only
|
||||
- `require_any_vendor_permission(*permissions)` - Any of list
|
||||
- `require_all_vendor_permissions(*permissions)` - All of list
|
||||
- `get_user_permissions()` - Get user's permission list
|
||||
|
||||
**Usage in Routes:**
|
||||
```python
|
||||
@router.post("/products")
|
||||
def create_product(
|
||||
user: User = Depends(require_vendor_permission("products.create"))
|
||||
):
|
||||
# User verified to have products.create permission
|
||||
...
|
||||
```
|
||||
|
||||
### 6. ✅ Create Vendor Team Service
|
||||
|
||||
**Service Responsibilities:**
|
||||
- `invite_team_member()` - Send invitations
|
||||
- `accept_invitation()` - Activate accounts
|
||||
- `remove_team_member()` - Deactivate members
|
||||
- `update_member_role()` - Change permissions
|
||||
- `get_team_members()` - List team
|
||||
|
||||
**Why Service Layer:**
|
||||
- Business logic separate from routes
|
||||
- Reusable across different contexts
|
||||
- Easier to test
|
||||
- Consistent error handling
|
||||
|
||||
### 7. ✅ Add Helper Methods to Models
|
||||
|
||||
**User Model:**
|
||||
```python
|
||||
@property
|
||||
def is_admin(self) -> bool
|
||||
def is_owner_of(self, vendor_id: int) -> bool
|
||||
def is_member_of(self, vendor_id: int) -> bool
|
||||
def get_vendor_role(self, vendor_id: int) -> str
|
||||
def has_vendor_permission(self, vendor_id: int, permission: str) -> bool
|
||||
```
|
||||
|
||||
**VendorUser Model:**
|
||||
```python
|
||||
@property
|
||||
def is_owner(self) -> bool
|
||||
def has_permission(self, permission: str) -> bool
|
||||
def get_all_permissions(self) -> list
|
||||
```
|
||||
|
||||
### 8. ✅ Enhanced Exception Handling
|
||||
|
||||
**Add Vendor-Specific Exceptions:**
|
||||
- `VendorAccessDeniedException` - No access to vendor
|
||||
- `InsufficientVendorPermissionsException` - Missing permission
|
||||
- `VendorOwnerOnlyException` - Owner-only operation
|
||||
- `CannotRemoveVendorOwnerException` - Prevent owner removal
|
||||
- `InvalidInvitationTokenException` - Bad invitation
|
||||
- `MaxTeamMembersReachedException` - Team size limit
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Platform Level │
|
||||
│ User.role: "admin" or "vendor" │
|
||||
│ - Admins: Full platform access │
|
||||
│ - Vendors: Access to their vendor(s) │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Vendor Level │
|
||||
│ VendorUser.user_type: "owner" or "member" │
|
||||
│ - Owners: Full vendor access (all permissions) │
|
||||
│ - Members: Role-based access (limited permissions) │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Role Level │
|
||||
│ Role.permissions: ["products.view", ...] │
|
||||
│ - Presets: Manager, Staff, Support, Viewer, Marketing │
|
||||
│ - Custom: Owner can define custom roles │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Permission Hierarchy
|
||||
|
||||
```
|
||||
Owner (VendorUser.user_type = "owner")
|
||||
└─> ALL permissions automatically
|
||||
|
||||
Team Member (VendorUser.user_type = "member")
|
||||
└─> Role.permissions (from VendorUser.role_id)
|
||||
├─> Manager: Most permissions except critical settings/team
|
||||
├─> Staff: Products, orders, customers (read/write)
|
||||
├─> Support: Orders, customers (support focus)
|
||||
├─> Viewer: Read-only access
|
||||
└─> Custom: Owner-defined permission sets
|
||||
```
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### 1. **Cookie Path Isolation** (Already Implemented ✓)
|
||||
- Admin: `/admin` path
|
||||
- Vendor: `/vendor` path
|
||||
- Customer: `/shop` path
|
||||
- Prevents cross-context cookie leakage
|
||||
|
||||
### 2. **Role Checking at Route Level**
|
||||
```python
|
||||
# Good - Check at route definition
|
||||
@router.post("/products")
|
||||
def create_product(
|
||||
user: User = Depends(require_vendor_permission("products.create"))
|
||||
):
|
||||
...
|
||||
|
||||
# Bad - Check inside handler
|
||||
@router.post("/products")
|
||||
def create_product(user: User):
|
||||
if not has_permission(...): # Don't do this
|
||||
raise Exception()
|
||||
```
|
||||
|
||||
### 3. **Block Admin from Vendor Routes**
|
||||
```python
|
||||
# In vendor auth endpoints
|
||||
if user.role == "admin":
|
||||
raise InvalidCredentialsException(
|
||||
"Admins cannot access vendor portal"
|
||||
)
|
||||
```
|
||||
|
||||
### 4. **Owner Cannot Be Removed**
|
||||
```python
|
||||
if vendor_user.is_owner:
|
||||
raise CannotRemoveVendorOwnerException(vendor.vendor_code)
|
||||
```
|
||||
|
||||
### 5. **Inactive Users Have No Permissions**
|
||||
```python
|
||||
if not user.is_active or not vendor_user.is_active:
|
||||
return False # No permissions
|
||||
```
|
||||
|
||||
## Frontend Integration
|
||||
|
||||
### 1. **Get User Permissions on Load**
|
||||
```javascript
|
||||
// On vendor dashboard load
|
||||
const response = await fetch('/api/v1/vendor/team/me/permissions');
|
||||
const { permissions } = await response.json();
|
||||
|
||||
// Store in state
|
||||
localStorage.setItem('permissions', JSON.stringify(permissions));
|
||||
```
|
||||
|
||||
### 2. **Show/Hide UI Elements**
|
||||
```javascript
|
||||
// Check permission before rendering
|
||||
function canViewProducts() {
|
||||
const permissions = JSON.parse(localStorage.getItem('permissions'));
|
||||
return permissions.includes('products.view');
|
||||
}
|
||||
|
||||
// In template
|
||||
{canViewProducts() && <ProductsList />}
|
||||
```
|
||||
|
||||
### 3. **Disable Actions Without Permission**
|
||||
```javascript
|
||||
<button
|
||||
disabled={!permissions.includes('products.delete')}
|
||||
onClick={handleDelete}
|
||||
>
|
||||
Delete Product
|
||||
</button>
|
||||
```
|
||||
|
||||
## Database Schema Summary
|
||||
|
||||
```
|
||||
users
|
||||
├── id (PK)
|
||||
├── email (unique)
|
||||
├── username (unique)
|
||||
├── role ('admin' | 'vendor') ← Platform role only
|
||||
├── is_active
|
||||
└── is_email_verified ← New field
|
||||
|
||||
vendors
|
||||
├── id (PK)
|
||||
├── vendor_code (unique)
|
||||
├── owner_user_id (FK → users.id)
|
||||
└── ...
|
||||
|
||||
vendor_users (junction table with role info)
|
||||
├── id (PK)
|
||||
├── vendor_id (FK → vendors.id)
|
||||
├── user_id (FK → users.id)
|
||||
├── user_type ('owner' | 'member') ← New field
|
||||
├── role_id (FK → roles.id, nullable) ← NULL for owners
|
||||
├── invitation_token ← New field
|
||||
├── invitation_sent_at ← New field
|
||||
├── invitation_accepted_at ← New field
|
||||
└── is_active
|
||||
|
||||
roles (vendor-specific)
|
||||
├── id (PK)
|
||||
├── vendor_id (FK → vendors.id)
|
||||
├── name (e.g., 'Manager', 'Staff')
|
||||
└── permissions (JSON: ["products.view", ...])
|
||||
|
||||
customers (separate, vendor-scoped)
|
||||
├── id (PK)
|
||||
├── vendor_id (FK → vendors.id)
|
||||
├── email (unique within vendor)
|
||||
└── ... (separate auth from User)
|
||||
```
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
### Phase 1: Database & Models
|
||||
- [ ] Add `is_email_verified` to User model
|
||||
- [ ] Update User.role to only accept 'admin'/'vendor'
|
||||
- [ ] Add `user_type` to VendorUser model
|
||||
- [ ] Add invitation fields to VendorUser
|
||||
- [ ] Make VendorUser.role_id nullable
|
||||
- [ ] Create permissions.py with constants
|
||||
- [ ] Add helper methods to User model
|
||||
- [ ] Add helper methods to VendorUser model
|
||||
- [ ] Run database migration
|
||||
|
||||
### Phase 2: Services & Logic
|
||||
- [ ] Create vendor_team_service.py
|
||||
- [ ] Implement invite_team_member()
|
||||
- [ ] Implement accept_invitation()
|
||||
- [ ] Implement remove_team_member()
|
||||
- [ ] Implement update_member_role()
|
||||
- [ ] Add vendor-specific exceptions
|
||||
- [ ] Set up email sending for invitations
|
||||
|
||||
### Phase 3: API & Dependencies
|
||||
- [ ] Add permission checking dependencies to deps.py
|
||||
- [ ] Update vendor auth endpoint to block admins
|
||||
- [ ] Create team management routes
|
||||
- [ ] Add permission checks to existing routes
|
||||
- [ ] Create invitation acceptance endpoint (public)
|
||||
- [ ] Add /me/permissions endpoint
|
||||
|
||||
### Phase 4: Testing
|
||||
- [ ] Test owner has all permissions
|
||||
- [ ] Test team members respect role permissions
|
||||
- [ ] Test invitation flow end-to-end
|
||||
- [ ] Test cannot remove owner
|
||||
- [ ] Test admin blocked from vendor routes
|
||||
- [ ] Test permission checking in all routes
|
||||
- [ ] Test inactive users have no access
|
||||
|
||||
### Phase 5: Frontend
|
||||
- [ ] Load user permissions on login
|
||||
- [ ] Show/hide UI based on permissions
|
||||
- [ ] Implement invitation acceptance page
|
||||
- [ ] Add team management UI (owner only)
|
||||
- [ ] Add role selector when inviting
|
||||
- [ ] Show permission lists for roles
|
||||
|
||||
## Migration Steps
|
||||
|
||||
1. **Backup database**
|
||||
2. **Run Alembic migration** (see migration guide)
|
||||
3. **Update User roles** (convert 'user' to 'vendor')
|
||||
4. **Set vendor owners** in vendor_users
|
||||
5. **Create default roles** for all vendors
|
||||
6. **Assign roles** to existing team members
|
||||
7. **Verify migration** with SQL queries
|
||||
8. **Test system** thoroughly
|
||||
9. **Deploy** to production
|
||||
|
||||
## Common Pitfalls to Avoid
|
||||
|
||||
### ❌ Don't: Mix Platform and Vendor Roles
|
||||
```python
|
||||
# Bad
|
||||
user.role = "manager" # Vendor role in User table
|
||||
```
|
||||
|
||||
```python
|
||||
# Good
|
||||
user.role = "vendor" # Platform role
|
||||
vendor_user.role.name = "manager" # Vendor role
|
||||
```
|
||||
|
||||
### ❌ Don't: Check Permissions in Business Logic
|
||||
```python
|
||||
# Bad
|
||||
def create_product(user, product_data):
|
||||
if not has_permission(user, "products.create"):
|
||||
raise Exception()
|
||||
# ... create product
|
||||
```
|
||||
|
||||
```python
|
||||
# Good
|
||||
@router.post("/products")
|
||||
def create_product(
|
||||
product_data: ProductCreate,
|
||||
user: User = Depends(require_vendor_permission("products.create"))
|
||||
):
|
||||
# Permission already checked by dependency
|
||||
return service.create_product(user, product_data)
|
||||
```
|
||||
|
||||
### ❌ Don't: Allow Owner Removal
|
||||
```python
|
||||
# Bad
|
||||
def remove_team_member(vendor, user_id):
|
||||
vendor_user = get_vendor_user(vendor, user_id)
|
||||
vendor_user.is_active = False
|
||||
```
|
||||
|
||||
```python
|
||||
# Good
|
||||
def remove_team_member(vendor, user_id):
|
||||
vendor_user = get_vendor_user(vendor, user_id)
|
||||
if vendor_user.is_owner:
|
||||
raise CannotRemoveVendorOwnerException(vendor.vendor_code)
|
||||
vendor_user.is_active = False
|
||||
```
|
||||
|
||||
### ❌ Don't: Forget to Check is_active
|
||||
```python
|
||||
# Bad
|
||||
def has_permission(user, vendor_id, permission):
|
||||
return permission in user.get_vendor_role(vendor_id).permissions
|
||||
```
|
||||
|
||||
```python
|
||||
# Good
|
||||
def has_permission(user, vendor_id, permission):
|
||||
if not user.is_active:
|
||||
return False
|
||||
|
||||
vendor_user = get_vendor_user(user, vendor_id)
|
||||
if not vendor_user or not vendor_user.is_active:
|
||||
return False
|
||||
|
||||
return vendor_user.has_permission(permission)
|
||||
```
|
||||
|
||||
## Questions & Answers
|
||||
|
||||
**Q: Can a user be a team member of multiple vendors?**
|
||||
A: Yes! A user can have multiple VendorUser entries, one per vendor. Each has independent role/permissions.
|
||||
|
||||
**Q: Can a vendor have multiple owners?**
|
||||
A: No. Each vendor has one owner (Vendor.owner_user_id). The owner can delegate management via Manager role, but there's only one true owner.
|
||||
|
||||
**Q: What happens to team members when owner changes?**
|
||||
A: Team members remain. Update Vendor.owner_user_id and both old/new owner's VendorUser.user_type.
|
||||
|
||||
**Q: Can admins access vendor dashboards?**
|
||||
A: No. Admins are blocked from vendor routes. This is intentional security.
|
||||
|
||||
**Q: How do customers authenticate?**
|
||||
A: Customers use the Customer model with separate authentication. They're vendor-scoped and can't access vendor/admin areas.
|
||||
|
||||
**Q: Can permissions be customized per team member?**
|
||||
A: Yes! When inviting, pass `custom_permissions` array to override role presets.
|
||||
|
||||
**Q: How do invitation links work?**
|
||||
A: Invitation token is a secure random string stored in VendorUser. Link format: `/vendor/invitation/accept?token=<token>`. Token is single-use and expires in 7 days.
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Review all generated files in `/home/claude/`
|
||||
2. Integrate changes into your codebase
|
||||
3. Create and run database migration
|
||||
4. Update existing routes with permission checks
|
||||
5. Implement team management UI
|
||||
6. Test thoroughly in development
|
||||
7. Deploy to staging for user acceptance testing
|
||||
8. Deploy to production with monitoring
|
||||
|
||||
## Support
|
||||
|
||||
All implementation files have been created in `/home/claude/`:
|
||||
- `user_model_improved.py` - Updated User model
|
||||
- `vendor_user_improved.py` - Updated VendorUser model
|
||||
- `permissions.py` - Permission system
|
||||
- `vendor_exceptions.py` - Vendor exceptions
|
||||
- `deps_permissions.py` - Permission dependencies
|
||||
- `vendor_team_service.py` - Team management service
|
||||
- `team_routes_example.py` - Example routes
|
||||
- `rbac_migration_guide.md` - Database migration guide
|
||||
|
||||
These are ready to be integrated into your project structure.
|
||||
Reference in New Issue
Block a user