# 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() && }
```
### 3. **Disable Actions Without Permission**
```javascript
```
## 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 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.