diff --git a/docs/__REVAMPING/RBAC/INDEX.md b/docs/__REVAMPING/RBAC/INDEX.md new file mode 100644 index 00000000..41d68e5c --- /dev/null +++ b/docs/__REVAMPING/RBAC/INDEX.md @@ -0,0 +1,298 @@ +# RBAC Implementation - Deliverables Index + +## Overview + +This package contains a complete RBAC (Role-Based Access Control) implementation for your multi-tenant e-commerce platform. All files are ready to be integrated into your codebase. + +## šŸ“š Developer Documentation + +### For Your Development Team + +**[RBAC_DEVELOPER_GUIDE.md](RBAC_DEVELOPER_GUIDE.md)** ⭐ **Primary Documentation** +- Complete developer guide (800+ lines) +- Architecture overview and design principles +- Database schema with detailed explanations +- Permission system structure +- Authentication and authorization flows +- Team management workflows +- Extensive code examples for all scenarios +- Best practices and patterns +- Comprehensive testing guidelines +- Troubleshooting section +- **Give this to your developers** - Everything they need to know + +**[RBAC_QUICK_REFERENCE.md](RBAC_QUICK_REFERENCE.md)** šŸ“‹ **Quick Reference** +- One-page reference for daily development +- Common imports and patterns +- Permission constants lookup +- Helper methods cheat sheet +- Frontend integration snippets +- Testing patterns +- Debugging commands +- **Print and keep at desk** - For quick lookups + +### For Implementation Planning + +**[RBAC_IMPLEMENTATION_SUMMARY.md](RBAC_IMPLEMENTATION_SUMMARY.md)** +- Executive summary and key recommendations +- Architecture overview and permission hierarchy +- Security best practices +- Common pitfalls to avoid +- Implementation checklist +- Q&A section +- **For planning** - Read this first for the big picture + +**[RBAC_VISUAL_GUIDE.md](RBAC_VISUAL_GUIDE.md)** +- System architecture diagrams +- Team invitation flow +- Permission check flow +- Database relationship diagrams +- Permission naming conventions +- Role preset visualizations +- Security boundaries +- **For understanding** - Visual learners start here + +## šŸ—„ļø Database Models + +### **user_model_improved.py** +Updated User model with: +- Clarified role field (admin/vendor only) +- Email verification field +- Helper methods for permission checking +- Owner/member checking methods +- Vendor-specific role retrieval + +**Where to integrate:** Replace/update your `models/database/user.py` + +### **vendor_user_improved.py** +Enhanced VendorUser model with: +- `user_type` field (owner/member distinction) +- Invitation system fields +- Permission checking methods +- Owner identification + +**Where to integrate:** Update your `models/database/vendor.py` (VendorUser class) + +## šŸ” Permission System + +### **permissions.py** +Complete permission system including: +- `VendorPermissions` enum (all available permissions) +- `PermissionGroups` class (preset role configurations) +- `PermissionChecker` utility class +- Helper functions + +**Where to integrate:** Create as `app/core/permissions.py` + +## āš ļø Exception Handling + +### **vendor_exceptions.py** +Vendor-specific exceptions: +- `VendorAccessDeniedException` +- `InsufficientVendorPermissionsException` +- `VendorOwnerOnlyException` +- `CannotRemoveVendorOwnerException` +- `TeamMemberAlreadyExistsException` +- `InvalidInvitationTokenException` +- And more... + +**Where to integrate:** Add to your `app/exceptions/` directory + +## šŸ› ļø Dependencies & Route Guards + +### **deps_permissions.py** +FastAPI dependencies for permission checking: +- `require_vendor_permission(permission)` +- `require_vendor_owner()` +- `require_any_vendor_permission(*permissions)` +- `require_all_vendor_permissions(*permissions)` +- `get_user_permissions()` + +**Where to integrate:** Add to your existing `app/api/deps.py` + +## šŸ”§ Service Layer + +### **vendor_team_service.py** +Complete team management service: +- `invite_team_member()` - Send team invitations +- `accept_invitation()` - Activate invited accounts +- `remove_team_member()` - Remove team members +- `update_member_role()` - Change member roles +- `get_team_members()` - List all team members +- Helper methods for token generation and role management + +**Where to integrate:** Create as `app/services/vendor_team_service.py` + +## šŸ“” API Routes + +### **team_routes_example.py** +Complete team management API routes: +- GET `/team/members` - List team members +- POST `/team/invite` - Invite new member +- POST `/team/accept-invitation` - Accept invitation +- DELETE `/team/members/{user_id}` - Remove member +- PUT `/team/members/{user_id}/role` - Update member role +- GET `/team/me/permissions` - Get current user permissions +- Example product routes with permission checks + +**Where to integrate:** Create as `app/api/v1/vendor/team.py` + +## šŸ—ƒļø Database Migration + +### **rbac_migration_guide.md** +Comprehensive migration guide: +- Schema changes required +- Alembic migration script +- Data migration steps (6 steps with SQL) +- Post-migration checklist +- Verification queries +- Rollback plan + +**Use this:** Before deploying to production + +## šŸ“Š File Structure + +``` +your-project/ +│ +ā”œā”€ā”€ app/ +│ ā”œā”€ā”€ api/ +│ │ ā”œā”€ā”€ deps.py ← Add deps_permissions.py content +│ │ └── v1/ +│ │ ā”œā”€ā”€ admin/ +│ │ └── vendor/ +│ │ └── team.py ← Add team_routes_example.py +│ │ +│ ā”œā”€ā”€ core/ +│ │ └── permissions.py ← Add permissions.py +│ │ +│ ā”œā”€ā”€ exceptions/ +│ │ └── vendor.py ← Add vendor_exceptions.py content +│ │ +│ └── services/ +│ └── vendor_team_service.py ← Add vendor_team_service.py +│ +└── models/ + └── database/ + ā”œā”€ā”€ user.py ← Update with user_model_improved.py + └── vendor.py ← Update VendorUser from vendor_user_improved.py +``` + +## šŸš€ Quick Start Integration + +### Step 1: Review & Plan (30 minutes) +1. Read `RBAC_IMPLEMENTATION_SUMMARY.md` +2. Review `RBAC_VISUAL_GUIDE.md` for architecture +3. Check your current codebase against recommendations + +### Step 2: Database Migration (1-2 hours) +1. Follow `rbac_migration_guide.md` +2. Create Alembic migration +3. Test in development environment +4. Run migration + +### Step 3: Integrate Models (1 hour) +1. Update `models/database/user.py` with improved User model +2. Update `models/database/vendor.py` with improved VendorUser +3. Test model loading + +### Step 4: Add Permission System (30 minutes) +1. Create `app/core/permissions.py` +2. Import in your application +3. Test permission constants + +### Step 5: Add Exceptions (15 minutes) +1. Add vendor exceptions to `app/exceptions/` +2. Import in relevant modules + +### Step 6: Add Dependencies (30 minutes) +1. Add permission checking functions to `app/api/deps.py` +2. Test dependencies work + +### Step 7: Add Service Layer (30 minutes) +1. Create `app/services/vendor_team_service.py` +2. Test service methods + +### Step 8: Add Routes (30 minutes) +1. Create team management routes +2. Add permission checks to existing routes +3. Test all endpoints + +### Step 9: Frontend Integration (2-4 hours) +1. Update login flow to fetch permissions +2. Add UI elements for team management +3. Show/hide features based on permissions +4. Create invitation acceptance page + +### Step 10: Testing (2-3 hours) +1. Test all permission combinations +2. Test invitation flow +3. Test owner protections +4. Test admin blocking +5. Test multi-vendor access + +## šŸ“ Implementation Notes + +### Priority Changes (Must Do) + +1. **User.role clarification** - Critical for security +2. **VendorUser.user_type** - Required for owner distinction +3. **Permission checking in routes** - Security requirement +4. **Invitation system** - Required for team management + +### Optional Enhancements + +1. **Custom permissions UI** - Allow owners to create custom roles +2. **Permission analytics** - Track permission usage +3. **Team activity logs** - Audit trail for team actions +4. **Email templates** - Professional invitation emails + +## šŸ†˜ Support & Questions + +### Common Issues + +**Q: Migration fails?** +A: Check the verification queries in the migration guide. Likely data inconsistency. + +**Q: Permission checking not working?** +A: Ensure middleware sets `request.state.vendor` correctly. + +**Q: Owner can't access routes?** +A: Check that owner has `VendorUser` entry with `user_type='owner'`. + +**Q: Invitation emails not sending?** +A: Implement `_send_invitation_email()` in service (marked as TODO). + +### Next Steps for You + +1. āœ… Review all documentation +2. āœ… Plan integration timeline +3. āœ… Set up development environment +4. āœ… Run database migration in dev +5. āœ… Integrate code changes +6. āœ… Test thoroughly +7. āœ… Deploy to staging +8. āœ… User acceptance testing +9. āœ… Deploy to production + +## šŸ“ž Your Questions Answered + +Based on your original question: + +**āœ… Admin Creation:** Admins created by super admins on backend *(already correct)* + +**āœ… Vendor Owner Creation:** Auto-created when vendor is created *(implement in vendor creation logic)* + +**āœ… Team Member Invitation:** Email-based invitation system *(vendor_team_service.py provides this)* + +**āœ… Customer Registration:** Self-registration on shop *(separate Customer model is correct)* + +**āœ… Role-Based Access:** Full RBAC system *(permissions.py + dependencies)* + +**āœ… Multi-Tenant Isolation:** Vendor-scoped roles and permissions *(VendorUser + Role models)* + +## šŸŽ‰ You're Ready! + +You now have everything needed to implement a production-ready RBAC system. All code is written, tested patterns are provided, and comprehensive documentation is included. + +Good luck with your implementation! šŸš€ diff --git a/docs/__REVAMPING/RBAC/RBAC_DEVELOPER_GUIDE.md b/docs/__REVAMPING/RBAC/RBAC_DEVELOPER_GUIDE.md new file mode 100644 index 00000000..47f545c0 --- /dev/null +++ b/docs/__REVAMPING/RBAC/RBAC_DEVELOPER_GUIDE.md @@ -0,0 +1,2529 @@ +# Role-Based Access Control (RBAC) Developer Guide + +**Version:** 1.0 +**Last Updated:** November 2025 +**Audience:** Development Team + +--- + +## Table of Contents + +1. [Introduction](#introduction) +2. [RBAC Overview](#rbac-overview) +3. [System Architecture](#system-architecture) +4. [User Types & Contexts](#user-types--contexts) +5. [Database Schema](#database-schema) +6. [Permission System](#permission-system) +7. [Authentication Flow](#authentication-flow) +8. [Authorization Implementation](#authorization-implementation) +9. [Team Management](#team-management) +10. [Code Examples](#code-examples) +11. [Best Practices](#best-practices) +12. [Testing Guidelines](#testing-guidelines) +13. [Troubleshooting](#troubleshooting) + +--- + +## Introduction + +This guide documents the Role-Based Access Control (RBAC) system implemented in our multi-tenant e-commerce platform. The system provides granular access control across three distinct contexts: Platform Administration, Vendor Management, and Customer Shopping. + +### Purpose + +The RBAC system ensures that: +- Users can only access resources they're authorized to see +- Permissions are granular and context-specific +- Multi-tenancy is enforced at the database and application level +- Team collaboration is secure and auditable + +### Key Principles + +1. **Context Isolation** - Admin, vendor, and customer contexts are completely isolated +2. **Least Privilege** - Users have only the permissions they need +3. **Owner Authority** - Vendor owners have complete control over their vendor +4. **Team Flexibility** - Vendor teams can be structured with various role types +5. **Security First** - Cookie path isolation and role enforcement prevent unauthorized access + +--- + +## RBAC Overview + +### Three-Tier Permission Model + +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ PLATFORM LEVEL │ +│ User.role │ +│ │ +│ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ +│ │ Admin │ │ Vendor │ │ +│ │ (admin) │ │ (vendor) │ │ +│ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + ā–¼ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ VENDOR LEVEL │ +│ VendorUser.user_type │ +│ │ +│ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ +│ │ Owner │ │ Team Member │ │ +│ │ (owner) │ │ (member) │ │ +│ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + ā–¼ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ PERMISSION LEVEL │ +│ Role.permissions │ +│ │ +│ Manager, Staff, Support, Viewer, Marketing, Custom │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +``` + +### Context Separation + +The application operates in three isolated contexts: + +| Context | Routes | Authentication | User Type | +|---------|--------|----------------|-----------| +| **Admin** | `/admin/*` | `admin_token` cookie | Platform Admins | +| **Vendor** | `/vendor/*` | `vendor_token` cookie | Vendor Owners & Teams | +| **Shop** | `/shop/account/*` | `customer_token` cookie | Customers | + +**Important:** These contexts are security boundaries. Admin users cannot access vendor routes, vendor users cannot access admin routes, and customers are entirely separate. + +--- + +## System Architecture + +### High-Level Architecture + +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Request │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + ā–¼ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Middleware Layer │ +│ │ +│ • VendorContextMiddleware │ +│ • VendorDetectionMiddleware │ +│ • AuthenticationMiddleware │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + ā–¼ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ FastAPI Route Handler │ +│ │ +│ Dependencies: │ +│ • get_current_admin_from_cookie_or_header() │ +│ • get_current_vendor_from_cookie_or_header() │ +│ • require_vendor_permission("permission.name") │ +│ • require_vendor_owner() │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + ā–¼ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Service Layer │ +│ │ +│ • vendor_team_service │ +│ • auth_service │ +│ • customer_service │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + ā–¼ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Database Layer │ +│ │ +│ • User, VendorUser, Role, Customer │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +``` + +### Component Responsibilities + +#### Authentication Layer +- Validates JWT tokens +- Verifies cookie paths match routes +- Manages token lifecycle (creation, refresh, expiry) +- Handles dual storage (cookies + headers) + +#### Authorization Layer +- Checks user roles and permissions +- Enforces vendor ownership rules +- Validates team member access +- Blocks cross-context access + +#### Service Layer +- Implements business logic +- Manages team invitations +- Handles role assignments +- Provides reusable authorization checks + +--- + +## User Types & Contexts + +### Platform Admins + +**Characteristics:** +- `User.role = "admin"` +- Full access to `/admin/*` routes +- Manage all vendors and users +- Cannot access vendor or customer portals + +**Use Cases:** +- Platform configuration +- Vendor approval/verification +- User management +- System monitoring + +**Authentication:** +```python +# Login endpoint +POST /api/v1/admin/auth/login + +# Cookie set +admin_token (path=/admin, httponly=true) + +# Access routes +GET /admin/dashboard +GET /admin/vendors +POST /admin/users/{user_id}/suspend +``` + +### Vendor Owners + +**Characteristics:** +- `User.role = "vendor"` +- `VendorUser.user_type = "owner"` +- Automatic full permissions within their vendor +- Can invite and manage team members +- Cannot be removed from their vendor + +**Use Cases:** +- Complete vendor management +- Team administration +- Financial oversight +- Settings configuration + +**Special Privileges:** +```python +# Automatic permissions +def has_permission(self, permission: str) -> bool: + if self.is_owner: + return True # Owners bypass permission checks +``` + +### Vendor Team Members + +**Characteristics:** +- `User.role = "vendor"` +- `VendorUser.user_type = "member"` +- Permissions defined by `Role.permissions` +- Invited by vendor owner via email +- Can be assigned different roles (Manager, Staff, etc.) + +**Use Cases:** +- Day-to-day operations based on role +- Collaborative vendor management +- Specialized functions (marketing, support) + +**Role Examples:** +```python +# Manager - Nearly full access +permissions = [ + "products.view", "products.create", "products.edit", + "orders.view", "orders.edit", "orders.cancel", + "customers.view", "customers.edit", + "reports.view", "reports.financial" +] + +# Staff - Operational access +permissions = [ + "products.view", "products.create", "products.edit", + "orders.view", "orders.edit", + "customers.view" +] + +# Support - Customer service focus +permissions = [ + "orders.view", "orders.edit", + "customers.view", "customers.edit" +] +``` + +### Customers + +**Characteristics:** +- Separate `Customer` model (not in `User` table) +- Vendor-scoped authentication +- Can self-register on vendor shops +- Access only their own account + shop catalog + +**Use Cases:** +- Browse vendor products +- Place orders +- Manage account information +- View order history + +**Important:** Customers are NOT in the User table. They use a separate authentication system and cannot access admin or vendor portals. + +--- + +## Database Schema + +### Entity Relationship Diagram + +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ users │ +│ │ +│ id (PK) │◄──────┐ +│ email │ │ +│ username │ │ +│ role │ │ owner_user_id +│ ('admin' | │ │ +│ 'vendor') │ │ +│ is_active │ │ +│ is_email_ │ │ +│ verified │ │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ + │ │ + │ │ + │ │ + ā–¼ │ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ +│ vendor_users │ │ +│ │ │ +│ id (PK) │ │ +│ vendor_id (FK) ─┼───┐ │ +│ user_id (FK) ───┼─┐ │ │ +│ role_id (FK) │ │ │ │ +│ user_type │ │ │ │ +│ ('owner' | │ │ │ │ +│ 'member') │ │ │ │ +│ invitation_ │ │ │ │ +│ token │ │ │ │ +│ invitation_ │ │ │ │ +│ sent_at │ │ │ │ +│ invitation_ │ │ │ │ +│ accepted_at │ │ │ │ +│ invited_by (FK) │ │ │ │ +│ is_active │ │ │ │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ │ │ + │ │ │ │ + │ role_id │ │ │ + │ │ │ │ + ā–¼ │ │ │ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ │ │ +│ roles │ │ │ │ +│ │ │ │ │ +│ id (PK) │ │ │ │ +│ vendor_id (FK) ā”€ā”¼ā”€ā”˜ │ │ +│ name │ │ │ +│ permissions │ │ │ +│ (JSONB) │ │ │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ │ + │ │ + ā–¼ │ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ +│ vendors │ │ +│ │ │ +│ id (PK) │ │ +│ vendor_code │ │ +│ subdomain │ │ +│ name │ │ +│ owner_user_id ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +│ is_active │ +│ is_verified │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + │ + ā–¼ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ customers │ +│ (SEPARATE AUTH) │ +│ │ +│ id (PK) │ +│ vendor_id (FK) │ +│ email │ +│ hashed_password │ +│ customer_number │ +│ is_active │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +``` + +### Key Tables + +#### users + +Primary platform user table for admins and vendors. + +```python +class User(Base): + __tablename__ = "users" + + id = Column(Integer, primary_key=True) + email = Column(String, unique=True, nullable=False) + username = Column(String, unique=True, nullable=False) + hashed_password = Column(String, nullable=False) + role = Column(String, nullable=False) # 'admin' or 'vendor' + is_active = Column(Boolean, default=True) + is_email_verified = Column(Boolean, default=False) +``` + +**Important Fields:** +- `role`: Only contains `"admin"` or `"vendor"` (platform-level role) +- `is_email_verified`: Required for team member invitations + +#### vendors + +Vendor entities representing businesses on the platform. + +```python +class Vendor(Base): + __tablename__ = "vendors" + + id = Column(Integer, primary_key=True) + vendor_code = Column(String, unique=True, nullable=False) + subdomain = Column(String, unique=True, nullable=False) + name = Column(String, nullable=False) + owner_user_id = Column(Integer, ForeignKey("users.id"), nullable=False) + is_active = Column(Boolean, default=True) + is_verified = Column(Boolean, default=False) +``` + +**Important Fields:** +- `owner_user_id`: The user who owns this vendor (full permissions) +- `vendor_code`: Used in URLs for vendor context +- `subdomain`: For subdomain-based routing + +#### vendor_users + +Junction table linking users to vendors with role information. + +```python +class VendorUser(Base): + __tablename__ = "vendor_users" + + id = Column(Integer, primary_key=True) + vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False) + user_id = Column(Integer, ForeignKey("users.id"), nullable=False) + user_type = Column(String, nullable=False) # 'owner' or 'member' + role_id = Column(Integer, ForeignKey("roles.id"), nullable=True) + invited_by = Column(Integer, ForeignKey("users.id")) + invitation_token = Column(String, nullable=True) + invitation_sent_at = Column(DateTime, nullable=True) + invitation_accepted_at = Column(DateTime, nullable=True) + is_active = Column(Boolean, default=False) # Activated on acceptance +``` + +**Important Fields:** +- `user_type`: Distinguishes owners (`"owner"`) from team members (`"member"`) +- `role_id`: NULL for owners (they have all permissions), set for team members +- `invitation_*`: Fields for tracking invitation workflow +- `is_active`: FALSE until invitation accepted (for team members) + +#### roles + +Vendor-specific role definitions with permissions. + +```python +class Role(Base): + __tablename__ = "roles" + + id = Column(Integer, primary_key=True) + vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False) + name = Column(String, nullable=False) + permissions = Column(JSONB, default=[]) # PostgreSQL JSONB +``` + +**Important Fields:** +- `vendor_id`: Roles are vendor-scoped, not platform-wide +- `name`: Role name (e.g., "Manager", "Staff", "Support") +- `permissions`: Array of permission strings (e.g., `["products.view", "products.create"]`) + +#### customers + +Separate customer authentication system (vendor-scoped). + +```python +class Customer(Base): + __tablename__ = "customers" + + id = Column(Integer, primary_key=True) + vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False) + email = Column(String, nullable=False) # Unique within vendor + hashed_password = Column(String, nullable=False) + customer_number = Column(String, nullable=False) + is_active = Column(Boolean, default=True) +``` + +**Important Note:** Customers are NOT in the `users` table. They have a completely separate authentication system and are scoped to individual vendors. + +--- + +## Permission System + +### Permission Structure + +Permissions follow a hierarchical naming convention: `resource.action` + +```python +# Format +"{resource}.{action}" + +# Examples +"products.view" # View products +"products.create" # Create new products +"products.edit" # Edit existing products +"products.delete" # Delete products +"orders.cancel" # Cancel orders +"team.invite" # Invite team members (owner only) +"settings.edit" # Edit vendor settings +"reports.financial" # View financial reports +``` + +### Available Permissions + +#### Dashboard +```python +"dashboard.view" # View dashboard +``` + +#### Products +```python +"products.view" # View product list +"products.create" # Create new products +"products.edit" # Edit products +"products.delete" # Delete products +"products.import" # Import products from CSV/marketplace +"products.export" # Export products +``` + +#### Stock/Inventory +```python +"stock.view" # View stock levels +"stock.edit" # Adjust stock quantities +"stock.transfer" # Transfer stock between locations +``` + +#### Orders +```python +"orders.view" # View orders +"orders.edit" # Edit order details +"orders.cancel" # Cancel orders +"orders.refund" # Process refunds +``` + +#### Customers +```python +"customers.view" # View customer list +"customers.edit" # Edit customer details +"customers.delete" # Delete customers +"customers.export" # Export customer data +``` + +#### Marketing +```python +"marketing.view" # View marketing campaigns +"marketing.create" # Create campaigns +"marketing.send" # Send marketing emails +``` + +#### Reports +```python +"reports.view" # View basic reports +"reports.financial" # View financial reports +"reports.export" # Export report data +``` + +#### Settings +```python +"settings.view" # View settings +"settings.edit" # Edit basic settings +"settings.theme" # Edit theme/branding +"settings.domains" # Manage custom domains +``` + +#### Team Management +```python +"team.view" # View team members +"team.invite" # Invite new members (owner only) +"team.edit" # Edit member roles (owner only) +"team.remove" # Remove members (owner only) +``` + +#### Marketplace Imports +```python +"imports.view" # View import jobs +"imports.create" # Create import jobs +"imports.cancel" # Cancel import jobs +``` + +### Permission Constants + +All permissions are defined in `app/core/permissions.py`: + +```python +from enum import Enum + +class VendorPermissions(str, Enum): + """All available vendor permissions.""" + + # Dashboard + DASHBOARD_VIEW = "dashboard.view" + + # Products + PRODUCTS_VIEW = "products.view" + PRODUCTS_CREATE = "products.create" + PRODUCTS_EDIT = "products.edit" + PRODUCTS_DELETE = "products.delete" + PRODUCTS_IMPORT = "products.import" + PRODUCTS_EXPORT = "products.export" + + # ... (see permissions.py for complete list) +``` + +### Role Presets + +Pre-configured role templates for common team structures: + +```python +class PermissionGroups: + """Pre-defined permission sets for common roles.""" + + # Owner - All permissions (automatic) + OWNER = set(p.value for p in VendorPermissions) + + # Manager - Most permissions except team management + MANAGER = { + "dashboard.view", + "products.view", "products.create", "products.edit", "products.delete", + "stock.view", "stock.edit", "stock.transfer", + "orders.view", "orders.edit", "orders.cancel", "orders.refund", + "customers.view", "customers.edit", "customers.export", + "marketing.view", "marketing.create", "marketing.send", + "reports.view", "reports.financial", "reports.export", + "settings.view", "settings.theme", + "imports.view", "imports.create" + } + + # Staff - Day-to-day operations + STAFF = { + "dashboard.view", + "products.view", "products.create", "products.edit", + "stock.view", "stock.edit", + "orders.view", "orders.edit", + "customers.view" + } + + # Support - Customer service focused + SUPPORT = { + "dashboard.view", + "products.view", + "orders.view", "orders.edit", + "customers.view", "customers.edit" + } + + # Viewer - Read-only access + VIEWER = { + "dashboard.view", + "products.view", + "stock.view", + "orders.view", + "customers.view", + "reports.view" + } + + # Marketing - Marketing and customer communication + MARKETING = { + "dashboard.view", + "customers.view", "customers.export", + "marketing.view", "marketing.create", "marketing.send", + "reports.view" + } +``` + +### Custom Roles + +Owners can create custom roles with specific permission sets: + +```python +# Creating a custom role +custom_permissions = [ + "products.view", + "products.create", + "orders.view", + "customers.view" +] + +role = Role( + vendor_id=vendor.id, + name="Product Manager", + permissions=custom_permissions +) +``` + +--- + +## Authentication Flow + +### Admin Authentication + +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Client │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + │ POST /api/v1/admin/auth/login + │ { username, password } + ā–¼ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Admin Auth Endpoint │ +│ │ +│ 1. Validate credentials │ +│ 2. Check role == "admin" │ +│ 3. Generate JWT │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + │ Set-Cookie: admin_token= + │ Path=/admin + │ HttpOnly=true + │ Secure=true (prod) + │ SameSite=Lax + │ + │ Response: { access_token, user } + ā–¼ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Client │ +│ │ +│ šŸŖ admin_token (path=/admin) │ +│ šŸ’¾ localStorage.token │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +``` + +### Vendor Authentication + +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Client │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + │ POST /api/v1/vendor/auth/login + │ { username, password } + ā–¼ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Vendor Auth Endpoint │ +│ │ +│ 1. Validate credentials │ +│ 2. Block if admin │ +│ 3. Find vendor membership │ +│ 4. Get role (owner/member) │ +│ 5. Generate JWT │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + │ Set-Cookie: vendor_token= + │ Path=/vendor + │ HttpOnly=true + │ Secure=true (prod) + │ SameSite=Lax + │ + │ Response: { access_token, user, vendor, role } + ā–¼ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Client │ +│ │ +│ šŸŖ vendor_token (path=/vendor) │ +│ šŸ’¾ localStorage.token │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +``` + +### Customer Authentication + +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Client │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + │ POST /api/v1/public/vendors/{id}/customers/login + │ { username, password } + ā–¼ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Customer Auth Endpoint │ +│ │ +│ 1. Validate vendor │ +│ 2. Validate credentials │ +│ 3. Generate JWT │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + │ Set-Cookie: customer_token= + │ Path=/shop + │ HttpOnly=true + │ Secure=true (prod) + │ SameSite=Lax + │ + │ Response: { access_token, user } + ā–¼ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Client │ +│ │ +│ šŸŖ customer_token (path=/shop) │ +│ šŸ’¾ localStorage.token │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +``` + +### Cookie Path Isolation + +**Critical Security Feature:** + +Cookies are restricted by path to prevent cross-context authentication: + +```python +# Admin cookie +response.set_cookie( + key="admin_token", + value=jwt_token, + path="/admin", # Only sent to /admin/* routes + httponly=True, + secure=True, + samesite="lax" +) + +# Vendor cookie +response.set_cookie( + key="vendor_token", + value=jwt_token, + path="/vendor", # Only sent to /vendor/* routes + httponly=True, + secure=True, + samesite="lax" +) + +# Customer cookie +response.set_cookie( + key="customer_token", + value=jwt_token, + path="/shop", # Only sent to /shop/* routes + httponly=True, + secure=True, + samesite="lax" +) +``` + +**Why This Matters:** +- Admin cookies are never sent to vendor routes +- Vendor cookies are never sent to admin routes +- Customer cookies are never sent to admin/vendor routes +- Prevents accidental cross-context authorization + +### Dual Token Storage + +The system uses dual token storage for flexibility: + +1. **HTTP-Only Cookie** - For page navigation (automatic) +2. **localStorage** - For API calls (manual headers) + +```javascript +// Login stores both +const response = await fetch('/api/v1/vendor/auth/login', { + method: 'POST', + body: JSON.stringify({ username, password }) +}); + +const data = await response.json(); +// Cookie set automatically by server +// Store token for API calls +localStorage.setItem('token', data.access_token); + +// Page navigation - cookie sent automatically +window.location.href = '/vendor/ACME/dashboard'; + +// API call - use stored token +fetch('/api/v1/vendor/ACME/products', { + headers: { + 'Authorization': `Bearer ${localStorage.getItem('token')}` + } +}); +``` + +--- + +## Authorization Implementation + +### FastAPI Dependencies + +The system uses FastAPI dependencies for consistent authorization checks. + +#### Location + +All authorization dependencies are in `app/api/deps.py`. + +#### Basic Authentication Dependencies + +```python +from fastapi import Depends, Request +from sqlalchemy.orm import Session +from app.core.database import get_db +from models.database.user import User + +# Admin authentication (cookie OR header) +def get_current_admin_from_cookie_or_header( + request: Request, + db: Session = Depends(get_db) +) -> User: + """ + Get current admin user from cookie OR Authorization header. + + Checks: + 1. admin_token cookie (path=/admin) + 2. Authorization: Bearer header + 3. Validates role == "admin" + + Use for: Admin HTML pages + """ + # Implementation checks cookie first, then header + # Returns User object if authenticated as admin + # Raises AdminRequiredException if not admin + +# Vendor authentication (cookie OR header) +def get_current_vendor_from_cookie_or_header( + request: Request, + db: Session = Depends(get_db) +) -> User: + """ + Get current vendor user from cookie OR Authorization header. + + Checks: + 1. vendor_token cookie (path=/vendor) + 2. Authorization: Bearer header + 3. Blocks admin users + 4. Validates vendor membership + + Use for: Vendor HTML pages + """ + # Implementation checks cookie first, then header + # Returns User object if authenticated as vendor + # Raises InsufficientPermissionsException if admin + +# API-only authentication (header required) +def get_current_admin_api( + request: Request, + db: Session = Depends(get_db) +) -> User: + """ + Get current admin from Authorization header only. + + Use for: Admin API endpoints + """ + +def get_current_vendor_api( + request: Request, + db: Session = Depends(get_db) +) -> User: + """ + Get current vendor from Authorization header only. + + Use for: Vendor API endpoints + """ +``` + +#### Permission-Based Dependencies + +```python +from app.core.permissions import VendorPermissions + +def require_vendor_permission(permission: str): + """ + Dependency factory for requiring specific permission. + + Usage: + @router.post("/products") + def create_product( + user: User = Depends(require_vendor_permission("products.create")) + ): + # User verified to have products.create permission + ... + """ + def permission_checker( + request: Request, + db: Session = Depends(get_db), + current_user: User = Depends(get_current_vendor_from_cookie_or_header) + ) -> User: + vendor = request.state.vendor # Set by middleware + + if not current_user.has_vendor_permission(vendor.id, permission): + raise InsufficientVendorPermissionsException( + required_permission=permission, + vendor_code=vendor.vendor_code + ) + + return current_user + + return permission_checker + +def require_vendor_owner( + request: Request, + db: Session = Depends(get_db), + current_user: User = Depends(get_current_vendor_from_cookie_or_header) +) -> User: + """ + Require vendor owner role. + + Usage: + @router.post("/team/invite") + def invite_member( + user: User = Depends(require_vendor_owner) + ): + # User verified to be vendor owner + ... + """ + vendor = request.state.vendor + + if not current_user.is_owner_of(vendor.id): + raise VendorOwnerOnlyException( + operation="team management", + vendor_code=vendor.vendor_code + ) + + return current_user + +def require_any_vendor_permission(*permissions: str): + """ + Require ANY of the specified permissions. + + Usage: + @router.get("/dashboard") + def dashboard( + user: User = Depends(require_any_vendor_permission( + "dashboard.view", + "reports.view" + )) + ): + # User has at least one permission + ... + """ + +def require_all_vendor_permissions(*permissions: str): + """ + Require ALL of the specified permissions. + + Usage: + @router.post("/products/bulk-delete") + def bulk_delete( + user: User = Depends(require_all_vendor_permissions( + "products.view", + "products.delete" + )) + ): + # User has all permissions + ... + """ + +def get_user_permissions( + request: Request, + current_user: User = Depends(get_current_vendor_from_cookie_or_header) +) -> list: + """ + Get all permissions for current user. + + Returns list of permission strings. + + Usage: + @router.get("/me/permissions") + def my_permissions( + permissions: list = Depends(get_user_permissions) + ): + return {"permissions": permissions} + """ +``` + +### Model Helper Methods + +#### User Model + +```python +# In models/database/user.py + +class User(Base): + # ... fields ... + + @property + def is_admin(self) -> bool: + """Check if user is platform admin.""" + return self.role == "admin" + + @property + def is_vendor(self) -> bool: + """Check if user is vendor.""" + return self.role == "vendor" + + def is_owner_of(self, vendor_id: int) -> bool: + """Check if user owns a specific vendor.""" + return any(v.id == vendor_id for v in self.owned_vendors) + + def is_member_of(self, vendor_id: int) -> bool: + """Check if user is member of vendor (owner or team).""" + if self.is_owner_of(vendor_id): + return True + return any( + vm.vendor_id == vendor_id and vm.is_active + for vm in self.vendor_memberships + ) + + def get_vendor_role(self, vendor_id: int) -> str: + """Get role name within specific vendor.""" + if self.is_owner_of(vendor_id): + return "owner" + + for vm in self.vendor_memberships: + if vm.vendor_id == vendor_id and vm.is_active: + return vm.role.name if vm.role else "member" + + return None + + def has_vendor_permission(self, vendor_id: int, permission: str) -> bool: + """Check if user has specific permission in vendor.""" + # Owners have all permissions + if self.is_owner_of(vendor_id): + return True + + # Check team member permissions + for vm in self.vendor_memberships: + if vm.vendor_id == vendor_id and vm.is_active: + if vm.role and permission in vm.role.permissions: + return True + + return False +``` + +#### VendorUser Model + +```python +# In models/database/vendor.py + +class VendorUser(Base): + # ... fields ... + + @property + def is_owner(self) -> bool: + """Check if this is an owner membership.""" + return self.user_type == "owner" + + @property + def is_team_member(self) -> bool: + """Check if this is a team member (not owner).""" + return self.user_type == "member" + + @property + def is_invitation_pending(self) -> bool: + """Check if invitation is pending acceptance.""" + return ( + self.invitation_token is not None and + self.invitation_accepted_at is None + ) + + def has_permission(self, permission: str) -> bool: + """Check if this membership has specific permission.""" + # Owners have all permissions + if self.is_owner: + return True + + # Inactive users have no permissions + if not self.is_active: + return False + + # Check role permissions + if self.role and self.role.permissions: + return permission in self.role.permissions + + return False + + def get_all_permissions(self) -> list: + """Get all permissions for this membership.""" + if self.is_owner: + from app.core.permissions import VendorPermissions + return [p.value for p in VendorPermissions] + + if self.role and self.role.permissions: + return self.role.permissions + + return [] +``` + +--- + +## Team Management + +### Invitation Flow + +The system uses email-based invitations for team member onboarding. + +#### Complete Flow Diagram + +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ INVITATION WORKFLOW │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + +1. Owner initiates invitation + └─> POST /api/v1/vendor/{code}/team/invite + Body: { email, role } + +2. System creates/updates records + ā”œā”€> User record (if doesn't exist) + │ - email: from invitation + │ - username: auto-generated + │ - role: "vendor" + │ - is_active: FALSE + │ - is_email_verified: FALSE + │ + └─> VendorUser record + - vendor_id: current vendor + - user_id: from User + - user_type: "member" + - role_id: from role selection + - invitation_token: secure random string + - invitation_sent_at: now() + - invited_by: current user + - is_active: FALSE + +3. Email sent to invitee + └─> Contains: invitation link with token + Link: /vendor/invitation/accept?token={invitation_token} + +4. Invitee clicks link + └─> GET /vendor/invitation/accept?token={token} + Displays form: password, first_name, last_name + +5. Invitee submits form + └─> POST /api/v1/vendor/team/accept-invitation + Body: { invitation_token, password, first_name, last_name } + +6. System activates account + ā”œā”€> User updates: + │ - hashed_password: from form + │ - first_name, last_name: from form + │ - is_active: TRUE + │ - is_email_verified: TRUE + │ + └─> VendorUser updates: + - is_active: TRUE + - invitation_accepted_at: now() + - invitation_token: NULL (cleared) + +7. Member can now login + └─> POST /api/v1/vendor/auth/login + Redirect to vendor dashboard +``` + +### Service Layer Implementation + +Team management is handled by `VendorTeamService` in `app/services/vendor_team_service.py`. + +#### Key Methods + +```python +class VendorTeamService: + + def invite_team_member( + self, + db: Session, + vendor: Vendor, + inviter: User, + email: str, + role_name: str, + custom_permissions: Optional[List[str]] = None + ) -> Dict[str, Any]: + """ + Invite a new team member. + + Steps: + 1. Check team size limits + 2. Find or create User account + 3. Create or update VendorUser with invitation + 4. Generate secure invitation token + 5. Send invitation email + + Returns: + { + "invitation_token": str, + "email": str, + "role": str, + "existing_user": bool + } + """ + + def accept_invitation( + self, + db: Session, + invitation_token: str, + password: str, + first_name: Optional[str] = None, + last_name: Optional[str] = None + ) -> Dict[str, Any]: + """ + Accept team invitation and activate account. + + Steps: + 1. Validate invitation token + 2. Check token not expired (7 days) + 3. Update User (password, name, active status) + 4. Update VendorUser (active, accepted timestamp) + 5. Clear invitation token + + Returns: + { + "user": User, + "vendor": Vendor, + "role": str + } + """ + + def remove_team_member( + self, + db: Session, + vendor: Vendor, + user_id: int + ) -> bool: + """ + Remove team member (soft delete). + + Cannot remove owner. + Sets VendorUser.is_active = False + """ + + def update_member_role( + self, + db: Session, + vendor: Vendor, + user_id: int, + new_role_name: str, + custom_permissions: Optional[List[str]] = None + ) -> VendorUser: + """ + Update team member's role. + + Cannot change owner's role. + Creates new role if custom permissions provided. + """ + + def get_team_members( + self, + db: Session, + vendor: Vendor, + include_inactive: bool = False + ) -> List[Dict[str, Any]]: + """ + Get all team members for a vendor. + + Returns list of member info including: + - Basic user info + - Role and permissions + - Invitation status + - Active status + """ +``` + +### API Routes + +Complete team management routes in `app/api/v1/vendor/team.py`. + +```python +router = APIRouter(prefix="/team") + +# List team members +@router.get("/members") +def list_team_members( + request: Request, + user: User = Depends(require_vendor_permission("team.view")) +): + """List all team members.""" + +# Invite team member (owner only) +@router.post("/invite") +def invite_team_member( + invitation: InviteTeamMemberRequest, + user: User = Depends(require_vendor_owner) +): + """Send team invitation email.""" + +# Accept invitation (public, no auth) +@router.post("/accept-invitation") +def accept_invitation( + acceptance: AcceptInvitationRequest +): + """Accept invitation and activate account.""" + +# Remove team member (owner only) +@router.delete("/members/{user_id}") +def remove_team_member( + user_id: int, + user: User = Depends(require_vendor_owner) +): + """Remove team member from vendor.""" + +# Update member role (owner only) +@router.put("/members/{user_id}/role") +def update_member_role( + user_id: int, + role_update: UpdateMemberRoleRequest, + user: User = Depends(require_vendor_owner) +): + """Change team member's role.""" + +# Get current user's permissions +@router.get("/me/permissions") +def get_my_permissions( + permissions: list = Depends(get_user_permissions) +): + """Get current user's permission list.""" +``` + +### Security Considerations + +#### Owner Protection + +Owners cannot be removed or have their role changed: + +```python +# In remove_team_member +if vendor_user.is_owner: + raise CannotRemoveVendorOwnerException(vendor.vendor_code) + +# In update_member_role +if vendor_user.is_owner: + raise CannotRemoveVendorOwnerException(vendor.vendor_code) +``` + +#### Invitation Token Security + +- Tokens are 32-byte cryptographically secure random strings +- Single-use (cleared after acceptance) +- Expire after 7 days +- Unique per invitation + +```python +def _generate_invitation_token(self) -> str: + """Generate secure invitation token.""" + import secrets + return secrets.token_urlsafe(32) +``` + +#### Admin Blocking + +Admins are blocked from vendor routes: + +```python +# In vendor auth endpoint +if user.role == "admin": + raise InvalidCredentialsException( + "Admins cannot access vendor portal" + ) + +# In vendor dependencies +if current_user.role == "admin": + raise InsufficientPermissionsException( + "Vendor access only" + ) +``` + +--- + +## Code Examples + +### Example 1: Protected Route with Permission Check + +```python +from fastapi import APIRouter, Depends +from app.api.deps import require_vendor_permission +from app.core.permissions import VendorPermissions +from models.database.user import User + +router = APIRouter() + +@router.post("/products") +def create_product( + product_data: ProductCreate, + user: User = Depends(require_vendor_permission( + VendorPermissions.PRODUCTS_CREATE.value + )) +): + """ + Create a new product. + + Requires: products.create permission + + The dependency automatically: + 1. Authenticates the user + 2. Gets vendor from request.state + 3. Checks user has products.create permission + 4. Returns User if authorized + 5. Raises InsufficientVendorPermissionsException if not + """ + vendor = request.state.vendor + + # User is authenticated and authorized + # Proceed with business logic + product = product_service.create( + db=db, + vendor_id=vendor.id, + user_id=user.id, + data=product_data + ) + + return {"product": product} +``` + +### Example 2: Owner-Only Route + +```python +from app.api.deps import require_vendor_owner + +@router.delete("/team/members/{member_id}") +def remove_team_member( + member_id: int, + user: User = Depends(require_vendor_owner) +): + """ + Remove a team member. + + Requires: Vendor owner role + + The dependency automatically: + 1. Authenticates the user + 2. Checks user is owner of current vendor + 3. Returns User if owner + 4. Raises VendorOwnerOnlyException if not owner + """ + vendor = request.state.vendor + + # User is verified owner + vendor_team_service.remove_team_member( + db=db, + vendor=vendor, + user_id=member_id + ) + + return {"message": "Member removed"} +``` + +### Example 3: Multi-Permission Route + +```python +from app.api.deps import require_all_vendor_permissions + +@router.post("/products/bulk-import") +def bulk_import_products( + file: UploadFile, + user: User = Depends(require_all_vendor_permissions( + VendorPermissions.PRODUCTS_VIEW.value, + VendorPermissions.PRODUCTS_CREATE.value, + VendorPermissions.PRODUCTS_IMPORT.value + )) +): + """ + Bulk import products from CSV. + + Requires ALL of: + - products.view + - products.create + - products.import + + The dependency checks user has ALL specified permissions. + """ + vendor = request.state.vendor + + # User has all required permissions + result = import_service.process_csv( + db=db, + vendor_id=vendor.id, + file=file + ) + + return {"imported": result.success_count} +``` + +### Example 4: Service Layer Permission Check + +```python +# In service layer +class ProductService: + + def create_product( + self, + db: Session, + vendor: Vendor, + user: User, + data: ProductCreate + ) -> Product: + """ + Create a product. + + Note: Permission checking should be done at route level, + not in service layer. Services assume authorization + has already been verified. + """ + product = Product( + vendor_id=vendor.id, + created_by=user.id, + **data.dict() + ) + + db.add(product) + db.commit() + db.refresh(product) + + return product +``` + +**Important:** Permission checks belong in route dependencies, not service layers. Services assume the caller is authorized. + +### Example 5: Manual Permission Check + +Sometimes you need to check permissions programmatically: + +```python +@router.get("/dashboard") +def dashboard( + request: Request, + user: User = Depends(get_current_vendor_from_cookie_or_header) +): + """ + Dashboard with conditional content based on permissions. + """ + vendor = request.state.vendor + + # Get user's permissions + permissions = [] + if user.is_owner_of(vendor.id): + # Owners get all permissions + from app.core.permissions import VendorPermissions + permissions = [p.value for p in VendorPermissions] + else: + # Get from role + for vm in user.vendor_memberships: + if vm.vendor_id == vendor.id and vm.is_active: + permissions = vm.get_all_permissions() + break + + # Conditional data based on permissions + data = { + "basic_stats": get_basic_stats(vendor), + } + + if "reports.financial" in permissions: + data["financial_stats"] = get_financial_stats(vendor) + + if "team.view" in permissions: + data["team_stats"] = get_team_stats(vendor) + + return data +``` + +### Example 6: Frontend Permission Checking + +```javascript +// On login, fetch user's permissions +async function login(username, password) { + const response = await fetch('/api/v1/vendor/auth/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username, password }) + }); + + const data = await response.json(); + + // Store token + localStorage.setItem('token', data.access_token); + + // Fetch permissions + const permResponse = await fetch('/api/v1/vendor/team/me/permissions', { + headers: { + 'Authorization': `Bearer ${data.access_token}` + } + }); + + const { permissions } = await permResponse.json(); + + // Store permissions + localStorage.setItem('permissions', JSON.stringify(permissions)); + + // Navigate + window.location.href = '/vendor/dashboard'; +} + +// Check permission before showing UI element +function canCreateProducts() { + const permissions = JSON.parse(localStorage.getItem('permissions') || '[]'); + return permissions.includes('products.create'); +} + +// In React/Alpine.js component +{canCreateProducts() && ( + +)} + +// Disable button if no permission + +``` + +--- + +## Best Practices + +### 1. Route-Level Authorization + +**āœ… DO: Check permissions at route level using dependencies** + +```python +@router.post("/products") +def create_product( + data: ProductCreate, + user: User = Depends(require_vendor_permission("products.create")) +): + # Permission already verified + return product_service.create(data) +``` + +**āŒ DON'T: Check permissions in service layer** + +```python +# BAD +def create_product(db: Session, user: User, data: ProductCreate): + if not user.has_permission("products.create"): + raise Exception("No permission") + # ... +``` + +### 2. Use Type-Safe Permission Constants + +**āœ… DO: Use VendorPermissions enum** + +```python +from app.core.permissions import VendorPermissions + +require_vendor_permission(VendorPermissions.PRODUCTS_CREATE.value) +``` + +**āŒ DON'T: Use magic strings** + +```python +# BAD - typos won't be caught +require_vendor_permission("products.creat") # Typo! +``` + +### 3. Owner Permission Bypass + +**āœ… DO: Let owners bypass permission checks automatically** + +```python +def has_permission(self, permission: str) -> bool: + if self.is_owner: + return True # Owners have all permissions + # Check role permissions... +``` + +**āŒ DON'T: Explicitly check owner in every route** + +```python +# BAD - redundant +if not user.is_owner and not user.has_permission("products.create"): + raise Exception() +``` + +### 4. Service Layer Design + +**āœ… DO: Keep services authorization-agnostic** + +```python +# Service assumes caller is authorized +def create_product(db: Session, vendor_id: int, data: dict) -> Product: + product = Product(vendor_id=vendor_id, **data) + db.add(product) + db.commit() + return product +``` + +**āŒ DON'T: Mix authorization into services** + +```python +# BAD +def create_product(db: Session, user: User, data: dict): + if not user.is_active: + raise Exception() + # ... +``` + +### 5. Frontend Permission Checks + +**āœ… DO: Hide/disable UI elements without permission** + +```javascript +// Hide button if no permission +{hasPermission('products.delete') && ( + +)} + +// Disable button if no permission + +``` + +**āŒ DON'T: Rely only on frontend checks** + +Backend MUST always verify permissions. Frontend checks are for UX only. + +### 6. Error Handling + +**āœ… DO: Use specific exception types** + +```python +from app.exceptions import ( + InsufficientVendorPermissionsException, + VendorOwnerOnlyException, + VendorAccessDeniedException +) + +if not user.has_permission(permission): + raise InsufficientVendorPermissionsException( + required_permission=permission, + vendor_code=vendor.vendor_code + ) +``` + +**āŒ DON'T: Use generic exceptions** + +```python +# BAD +raise Exception("No permission") +``` + +### 7. Invitation Token Security + +**āœ… DO: Use cryptographically secure tokens** + +```python +import secrets + +def generate_token(): + return secrets.token_urlsafe(32) +``` + +**āŒ DON'T: Use weak token generation** + +```python +# BAD +import random +token = str(random.randint(1000000, 9999999)) +``` + +### 8. Context Detection + +**āœ… DO: Use middleware for vendor context** + +```python +# Middleware sets request.state.vendor +vendor = request.state.vendor +``` + +**āŒ DON'T: Extract vendor from URL in every route** + +```python +# BAD +@router.get("/vendor/{vendor_code}/products") +def list_products(vendor_code: str): + vendor = db.query(Vendor).filter_by(vendor_code=vendor_code).first() + # ... +``` + +### 9. Admin Access Restrictions + +**āœ… DO: Block admins from vendor routes** + +```python +if current_user.role == "admin": + raise InsufficientPermissionsException( + "Admins cannot access vendor portal" + ) +``` + +This prevents admins from accidentally accessing vendor areas. + +### 10. Testing Permissions + +**āœ… DO: Test all permission combinations** + +```python +def test_create_product_with_permission(): + """User with products.create can create products.""" + # Setup user with permission + # Make request + # Assert success + +def test_create_product_without_permission(): + """User without products.create cannot create products.""" + # Setup user without permission + # Make request + # Assert 403 Forbidden + +def test_owner_can_always_create_product(): + """Owners can create products regardless of role.""" + # Setup owner (no specific role) + # Make request + # Assert success +``` + +--- + +## Testing Guidelines + +### Unit Tests + +Test permission logic in isolation. + +```python +# tests/unit/test_permissions.py + +def test_owner_has_all_permissions(): + """Owners have all permissions automatically.""" + user = create_user() + vendor = create_vendor(owner=user) + vendor_user = create_vendor_user( + user=user, + vendor=vendor, + user_type="owner" + ) + + assert vendor_user.has_permission("products.create") + assert vendor_user.has_permission("orders.delete") + assert vendor_user.has_permission("team.invite") + # All permissions should return True + +def test_team_member_respects_role(): + """Team members have only their role's permissions.""" + user = create_user() + vendor = create_vendor() + role = create_role( + vendor=vendor, + name="Staff", + permissions=["products.view", "products.create"] + ) + vendor_user = create_vendor_user( + user=user, + vendor=vendor, + user_type="member", + role=role + ) + + assert vendor_user.has_permission("products.view") + assert vendor_user.has_permission("products.create") + assert not vendor_user.has_permission("products.delete") + assert not vendor_user.has_permission("team.invite") + +def test_inactive_user_has_no_permissions(): + """Inactive users have no permissions.""" + user = create_user() + vendor = create_vendor() + role = create_role( + vendor=vendor, + permissions=["products.view"] + ) + vendor_user = create_vendor_user( + user=user, + vendor=vendor, + role=role, + is_active=False # Inactive + ) + + assert not vendor_user.has_permission("products.view") +``` + +### Integration Tests + +Test full request/response cycles with authentication. + +```python +# tests/integration/test_product_routes.py + +def test_create_product_with_permission(client, auth_headers): + """Authenticated user with permission can create product.""" + # Setup: Create user with products.create permission + user = create_vendor_team_member( + permissions=["products.create"] + ) + token = create_auth_token(user) + + # Request + response = client.post( + "/api/v1/vendor/ACME/products", + json={"name": "Test Product", "price": 9.99}, + headers={"Authorization": f"Bearer {token}"} + ) + + # Assert + assert response.status_code == 201 + assert response.json()["name"] == "Test Product" + +def test_create_product_without_permission(client): + """User without permission cannot create product.""" + # Setup: Create user WITHOUT products.create + user = create_vendor_team_member( + permissions=["products.view"] # Can view but not create + ) + token = create_auth_token(user) + + # Request + response = client.post( + "/api/v1/vendor/ACME/products", + json={"name": "Test Product"}, + headers={"Authorization": f"Bearer {token}"} + ) + + # Assert + assert response.status_code == 403 + assert "INSUFFICIENT_VENDOR_PERMISSIONS" in response.json()["error_code"] + +def test_owner_bypasses_permission_check(client): + """Vendor owner can create products without explicit permission.""" + # Setup: Create owner (no specific role) + user, vendor = create_vendor_with_owner() + token = create_auth_token(user) + + # Request + response = client.post( + f"/api/v1/vendor/{vendor.vendor_code}/products", + json={"name": "Test Product"}, + headers={"Authorization": f"Bearer {token}"} + ) + + # Assert - Owner can create even without explicit permission + assert response.status_code == 201 + +def test_admin_blocked_from_vendor_route(client): + """Admins cannot access vendor routes.""" + # Setup: Create admin user + admin = create_admin_user() + token = create_auth_token(admin) + + # Request + response = client.get( + "/api/v1/vendor/ACME/products", + headers={"Authorization": f"Bearer {token}"} + ) + + # Assert + assert response.status_code == 403 + assert "INSUFFICIENT_PERMISSIONS" in response.json()["error_code"] +``` + +### System Tests + +Test complete workflows end-to-end. + +```python +# tests/system/test_team_invitation_workflow.py + +def test_complete_invitation_workflow(client, db): + """Test full team invitation and acceptance flow.""" + + # 1. Owner logs in + owner_token = login_as_owner(client, "owner@acme.com", "password") + + # 2. Owner invites team member + invite_response = client.post( + "/api/v1/vendor/ACME/team/invite", + json={ + "email": "newmember@example.com", + "role": "Staff" + }, + headers={"Authorization": f"Bearer {owner_token}"} + ) + assert invite_response.status_code == 200 + + # 3. Get invitation token from database (simulating email) + vendor_user = db.query(VendorUser).filter_by( + email="newmember@example.com" + ).first() + invitation_token = vendor_user.invitation_token + assert invitation_token is not None + assert vendor_user.is_active == False + + # 4. New member accepts invitation + accept_response = client.post( + "/api/v1/vendor/team/accept-invitation", + json={ + "invitation_token": invitation_token, + "password": "newpassword123", + "first_name": "New", + "last_name": "Member" + } + ) + assert accept_response.status_code == 200 + + # 5. Verify account is activated + db.refresh(vendor_user) + assert vendor_user.is_active == True + assert vendor_user.invitation_token is None + assert vendor_user.invitation_accepted_at is not None + + # 6. New member can log in + login_response = client.post( + "/api/v1/vendor/auth/login", + json={ + "username": "newmember@example.com", + "password": "newpassword123" + } + ) + assert login_response.status_code == 200 + assert "access_token" in login_response.json() + + # 7. New member has correct permissions + member_token = login_response.json()["access_token"] + perms_response = client.get( + "/api/v1/vendor/ACME/team/me/permissions", + headers={"Authorization": f"Bearer {member_token}"} + ) + assert "products.view" in perms_response.json()["permissions"] + assert "products.create" in perms_response.json()["permissions"] + assert "team.invite" not in perms_response.json()["permissions"] +``` + +### Performance Tests + +Test permission checking doesn't cause performance issues. + +```python +# tests/performance/test_permission_checks.py + +def test_permission_check_performance(client, db): + """Permission checks should be fast.""" + # Setup: Create vendor with many team members + vendor = create_vendor() + for i in range(100): + create_vendor_team_member(vendor) + + # Create test user + user = create_vendor_team_member( + vendor=vendor, + permissions=["products.view"] + ) + token = create_auth_token(user) + + # Test: Make many authenticated requests + import time + start = time.time() + + for _ in range(100): + response = client.get( + f"/api/v1/vendor/{vendor.vendor_code}/products", + headers={"Authorization": f"Bearer {token}"} + ) + assert response.status_code == 200 + + elapsed = time.time() - start + avg_per_request = elapsed / 100 + + # Permission check should be <10ms per request + assert avg_per_request < 0.01 +``` + +### Test Coverage Requirements + +- **Unit Tests:** 100% coverage of permission logic +- **Integration Tests:** All permission combinations for each route +- **System Tests:** Complete workflows (invitation, role changes, etc.) +- **Performance Tests:** Permission checks under load + +--- + +## Troubleshooting + +### Common Issues + +#### Issue: "INVALID_TOKEN" error + +**Symptoms:** +- API calls return 401 Unauthorized +- Error message: "Invalid token" + +**Causes:** +1. Token expired (default 30 minutes) +2. Token malformed +3. Token signature invalid + +**Solutions:** +```python +# Check token expiry +import jwt +token = "eyJ0eXAi..." +decoded = jwt.decode(token, verify=False) +print(decoded['exp']) # Unix timestamp + +# Compare with current time +import time +if decoded['exp'] < time.time(): + print("Token expired - user needs to re-login") + +# Verify token manually +from middleware.auth import AuthManager +auth = AuthManager() +try: + user_data = auth.verify_token(token) + print("Token valid") +except Exception as e: + print(f"Token invalid: {e}") +``` + +#### Issue: User can't access route despite having permission + +**Symptoms:** +- Route returns 403 Forbidden +- User believes they have required permission + +**Debug Steps:** + +```python +# 1. Check user's actual permissions +user = db.query(User).get(user_id) +vendor = db.query(Vendor).get(vendor_id) + +print(f"Is owner? {user.is_owner_of(vendor.id)}") + +if not user.is_owner_of(vendor.id): + # Get team membership + vendor_user = db.query(VendorUser).filter_by( + user_id=user.id, + vendor_id=vendor.id + ).first() + + print(f"Has membership? {vendor_user is not None}") + print(f"Is active? {vendor_user.is_active if vendor_user else 'N/A'}") + print(f"Role: {vendor_user.role.name if vendor_user and vendor_user.role else 'N/A'}") + print(f"Permissions: {vendor_user.role.permissions if vendor_user and vendor_user.role else []}") + +# 2. Check specific permission +permission = "products.create" +has_perm = user.has_vendor_permission(vendor.id, permission) +print(f"Has {permission}? {has_perm}") +``` + +#### Issue: Admin can't access vendor routes + +**Symptoms:** +- Admin user gets 403 on vendor routes + +**This is intentional!** Admins are blocked from vendor routes for security. + +**Solutions:** +1. Create separate vendor account for vendor management +2. Have admin create vendor, then use vendor owner account + +```python +# Admin workflow +# 1. Admin creates vendor (from admin portal) +# 2. System creates vendor owner user automatically +# 3. Admin logs out of admin portal +# 4. Vendor owner logs into vendor portal +``` + +#### Issue: Invitation link not working + +**Symptoms:** +- "Invalid invitation token" error +- Invitation expired error + +**Debug Steps:** + +```python +# Check invitation in database +token = "abc123..." +vendor_user = db.query(VendorUser).filter_by( + invitation_token=token +).first() + +if not vendor_user: + print("Token not found - may have been used already") +elif vendor_user.invitation_accepted_at: + print("Invitation already accepted") +else: + # Check expiry + from datetime import datetime, timedelta + sent_at = vendor_user.invitation_sent_at + expiry = sent_at + timedelta(days=7) + + if datetime.utcnow() > expiry: + print(f"Invitation expired on {expiry}") + else: + print("Invitation is valid") +``` + +#### Issue: Cookie not being sent + +**Symptoms:** +- Page navigation returns 401 +- API calls with header work fine + +**Causes:** +1. Cookie path doesn't match route +2. Cookie expired +3. Browser blocking cookies + +**Debug Steps:** + +```javascript +// Check cookies in browser console +document.cookie.split(';').forEach(c => console.log(c.trim())); + +// Verify correct cookie exists +// admin_token for /admin/* routes +// vendor_token for /vendor/* routes +// customer_token for /shop/* routes + +// Check cookie path +// In DevTools → Application → Cookies +// Path should match route prefix +``` + +#### Issue: Permission changes not taking effect + +**Symptoms:** +- Updated role permissions +- User still has old permissions + +**Causes:** +1. JWT token contains old permissions (cached) +2. User needs to re-login + +**Solution:** + +```python +# Permissions are NOT stored in JWT token +# They're fetched from database on each request +# So permission changes take effect immediately + +# If issue persists, check: +# 1. Database was actually updated +role = db.query(Role).get(role_id) +print(f"Current permissions: {role.permissions}") + +# 2. Cache invalidation if using caching +# Clear any permission caches + +# 3. User might need to refresh page +# Frontend may have cached permission list +``` + +### Debugging Tools + +#### Check User's Complete Access + +```python +def debug_user_access(user_id: int, vendor_id: int): + """Print complete access information for debugging.""" + user = db.query(User).get(user_id) + vendor = db.query(Vendor).get(vendor_id) + + print(f"\n=== User Access Debug ===") + print(f"User: {user.username} ({user.email})") + print(f"User Role: {user.role}") + print(f"User Active: {user.is_active}") + print(f"\nVendor: {vendor.name} ({vendor.vendor_code})") + print(f"Vendor Active: {vendor.is_active}") + + # Check ownership + is_owner = user.is_owner_of(vendor.id) + print(f"\nIs Owner: {is_owner}") + + if is_owner: + print("āœ“ Has ALL permissions (owner)") + return + + # Check membership + vendor_user = db.query(VendorUser).filter_by( + user_id=user.id, + vendor_id=vendor.id + ).first() + + if not vendor_user: + print("āœ— No vendor membership found") + return + + print(f"\nMembership Status: {vendor_user.user_type}") + print(f"Active: {vendor_user.is_active}") + print(f"Invited By: User #{vendor_user.invited_by}") + print(f"Invitation Accepted: {vendor_user.invitation_accepted_at}") + + if vendor_user.role: + print(f"\nRole: {vendor_user.role.name}") + print(f"Permissions ({len(vendor_user.role.permissions)}):") + for perm in vendor_user.role.permissions: + print(f" - {perm}") + else: + print("\nāœ— No role assigned") +``` + +#### Test Permission Check + +```python +def test_permission(user_id: int, vendor_id: int, permission: str): + """Test if user has specific permission.""" + user = db.query(User).get(user_id) + vendor = db.query(Vendor).get(vendor_id) + + has_perm = user.has_vendor_permission(vendor.id, permission) + + print(f"\n=== Permission Test ===") + print(f"User: {user.username}") + print(f"Vendor: {vendor.vendor_code}") + print(f"Permission: {permission}") + print(f"Result: {'āœ“ GRANTED' if has_perm else 'āœ— DENIED'}") + + # Show reason + if user.is_owner_of(vendor.id): + print("Reason: User is owner (has all permissions)") + elif has_perm: + vendor_user = db.query(VendorUser).filter_by( + user_id=user.id, + vendor_id=vendor.id + ).first() + print(f"Reason: Role '{vendor_user.role.name}' includes this permission") + else: + print("Reason: User does not have this permission") +``` + +#### Validate JWT Token + +```python +def validate_token(token: str): + """Validate and decode JWT token.""" + from middleware.auth import AuthManager + auth = AuthManager() + + print(f"\n=== Token Validation ===") + + try: + # Decode without verification first + import jwt + decoded = jwt.decode(token, verify=False) + + print(f"User ID: {decoded.get('sub')}") + print(f"Username: {decoded.get('username')}") + print(f"Email: {decoded.get('email')}") + print(f"Role: {decoded.get('role')}") + print(f"Issued At: {datetime.fromtimestamp(decoded.get('iat'))}") + print(f"Expires At: {datetime.fromtimestamp(decoded.get('exp'))}") + + # Now verify + user_data = auth.verify_token(token) + print("\nāœ“ Token is valid") + + except jwt.ExpiredSignatureError: + print("\nāœ— Token has expired") + except jwt.InvalidTokenError as e: + print(f"\nāœ— Token is invalid: {e}") +``` + +### Logging Best Practices + +Add comprehensive logging for troubleshooting: + +```python +import logging + +logger = logging.getLogger(__name__) + +# In authentication +logger.info(f"User login attempt: {username}") +logger.info(f"User {username} logged in successfully") +logger.warning(f"Failed login attempt for: {username}") + +# In authorization +logger.debug(f"Checking permission {permission} for user {user.id}") +logger.warning(f"Permission denied: {user.id} lacks {permission}") + +# In team management +logger.info(f"Team member invited: {email} to {vendor.vendor_code}") +logger.info(f"Invitation accepted: {user.id} joined {vendor.vendor_code}") +logger.warning(f"Failed to remove owner {user.id} from {vendor.vendor_code}") +``` + +--- + +## Conclusion + +This RBAC system provides comprehensive, secure access control for the multi-tenant e-commerce platform. Key features: + +- **Three-tier permission hierarchy** (Platform → Vendor → Permission) +- **Context isolation** (Admin, Vendor, Customer) +- **Flexible role system** with presets and custom roles +- **Secure invitation workflow** for team management +- **Owner authority** with automatic full permissions +- **Cookie path isolation** for security +- **Comprehensive testing** support + +### Quick Reference + +```python +# Authentication +from app.api.deps import ( + get_current_admin_from_cookie_or_header, + get_current_vendor_from_cookie_or_header, + get_current_customer_from_cookie_or_header, + get_current_admin_api, + get_current_vendor_api, + get_current_customer_api +) + +# Authorization +from app.api.deps import ( + require_vendor_permission, + require_vendor_owner, + require_any_vendor_permission, + require_all_vendor_permissions, + get_user_permissions +) + +# Permissions +from app.core.permissions import ( + VendorPermissions, + PermissionGroups, + PermissionChecker +) + +# Services +from app.services.vendor_team_service import vendor_team_service + +# Exceptions +from app.exceptions import ( + InsufficientVendorPermissionsException, + VendorOwnerOnlyException, + VendorAccessDeniedException, + InvalidInvitationTokenException, + CannotRemoveVendorOwnerException +) +``` + +### Support + +For questions or issues: +1. Check this guide first +2. Review code examples in relevant files +3. Check test files for usage patterns +4. Contact the backend team + +--- + +**Document Version:** 1.0 +**Last Updated:** November 2025 +**Maintained By:** Backend Team diff --git a/docs/__REVAMPING/RBAC/RBAC_IMPLEMENTATION_SUMMARY.md b/docs/__REVAMPING/RBAC/RBAC_IMPLEMENTATION_SUMMARY.md new file mode 100644 index 00000000..58c61a15 --- /dev/null +++ b/docs/__REVAMPING/RBAC/RBAC_IMPLEMENTATION_SUMMARY.md @@ -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() && } +``` + +### 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. diff --git a/docs/__REVAMPING/RBAC/RBAC_QUICK_REFERENCE.md b/docs/__REVAMPING/RBAC/RBAC_QUICK_REFERENCE.md new file mode 100644 index 00000000..516a2ab8 --- /dev/null +++ b/docs/__REVAMPING/RBAC/RBAC_QUICK_REFERENCE.md @@ -0,0 +1,513 @@ +# RBAC Quick Reference Card + +**For Daily Development** | Keep this handy while coding + +--- + +## Common Imports + +```python +# Authentication dependencies +from app.api.deps import ( + get_current_admin_from_cookie_or_header, + get_current_vendor_from_cookie_or_header, + require_vendor_permission, + require_vendor_owner, + get_user_permissions +) + +# Permission constants +from app.core.permissions import VendorPermissions + +# Exceptions +from app.exceptions import ( + InsufficientVendorPermissionsException, + VendorOwnerOnlyException +) + +# Services +from app.services.vendor_team_service import vendor_team_service +``` + +--- + +## Route Patterns + +### Admin Route (Cookie OR Header) +```python +@router.get("/admin/vendors") +def list_vendors( + user: User = Depends(get_current_admin_from_cookie_or_header) +): + # user is authenticated admin + ... +``` + +### Admin API (Header Only) +```python +@router.post("/api/v1/admin/vendors") +def create_vendor( + user: User = Depends(get_current_admin_api) +): + # user is authenticated admin (header required) + ... +``` + +### Vendor Route with Permission +```python +@router.post("/vendor/{code}/products") +def create_product( + user: User = Depends(require_vendor_permission( + VendorPermissions.PRODUCTS_CREATE.value + )) +): + # user has products.create permission + vendor = request.state.vendor + ... +``` + +### Owner-Only Route +```python +@router.post("/vendor/{code}/team/invite") +def invite_member( + user: User = Depends(require_vendor_owner) +): + # user is vendor owner + vendor = request.state.vendor + ... +``` + +### Multi-Permission Route +```python +@router.post("/vendor/{code}/products/bulk") +def bulk_operation( + user: User = Depends(require_all_vendor_permissions( + VendorPermissions.PRODUCTS_VIEW.value, + VendorPermissions.PRODUCTS_EDIT.value + )) +): + # user has ALL specified permissions + ... +``` + +--- + +## Permission Constants + +### Quick Lookup + +```python +# Dashboard +VendorPermissions.DASHBOARD_VIEW + +# Products +VendorPermissions.PRODUCTS_VIEW +VendorPermissions.PRODUCTS_CREATE +VendorPermissions.PRODUCTS_EDIT +VendorPermissions.PRODUCTS_DELETE +VendorPermissions.PRODUCTS_IMPORT +VendorPermissions.PRODUCTS_EXPORT + +# Stock +VendorPermissions.STOCK_VIEW +VendorPermissions.STOCK_EDIT +VendorPermissions.STOCK_TRANSFER + +# Orders +VendorPermissions.ORDERS_VIEW +VendorPermissions.ORDERS_EDIT +VendorPermissions.ORDERS_CANCEL +VendorPermissions.ORDERS_REFUND + +# Customers +VendorPermissions.CUSTOMERS_VIEW +VendorPermissions.CUSTOMERS_EDIT +VendorPermissions.CUSTOMERS_DELETE +VendorPermissions.CUSTOMERS_EXPORT + +# Marketing +VendorPermissions.MARKETING_VIEW +VendorPermissions.MARKETING_CREATE +VendorPermissions.MARKETING_SEND + +# Reports +VendorPermissions.REPORTS_VIEW +VendorPermissions.REPORTS_FINANCIAL +VendorPermissions.REPORTS_EXPORT + +# Settings +VendorPermissions.SETTINGS_VIEW +VendorPermissions.SETTINGS_EDIT +VendorPermissions.SETTINGS_THEME +VendorPermissions.SETTINGS_DOMAINS + +# Team +VendorPermissions.TEAM_VIEW +VendorPermissions.TEAM_INVITE +VendorPermissions.TEAM_EDIT +VendorPermissions.TEAM_REMOVE + +# Imports +VendorPermissions.IMPORTS_VIEW +VendorPermissions.IMPORTS_CREATE +VendorPermissions.IMPORTS_CANCEL +``` + +--- + +## User Helper Methods + +```python +# Check if admin +user.is_admin # bool + +# Check if vendor +user.is_vendor # bool + +# Check vendor ownership +user.is_owner_of(vendor_id) # bool + +# Check vendor membership +user.is_member_of(vendor_id) # bool + +# Get role in vendor +user.get_vendor_role(vendor_id) # str: "owner" | "member" | None + +# Check specific permission +user.has_vendor_permission(vendor_id, "products.create") # bool +``` + +--- + +## VendorUser Helper Methods + +```python +# Check if owner +vendor_user.is_owner # bool + +# Check if team member +vendor_user.is_team_member # bool + +# Check invitation status +vendor_user.is_invitation_pending # bool + +# Check permission +vendor_user.has_permission("products.create") # bool + +# Get all permissions +vendor_user.get_all_permissions() # list[str] +``` + +--- + +## Service Methods + +### Team Management + +```python +# Invite team member +vendor_team_service.invite_team_member( + db=db, + vendor=vendor, + inviter=current_user, + email="member@example.com", + role_name="Staff", + custom_permissions=None # Optional +) + +# Accept invitation +vendor_team_service.accept_invitation( + db=db, + invitation_token=token, + password="password123", + first_name="John", + last_name="Doe" +) + +# Remove team member +vendor_team_service.remove_team_member( + db=db, + vendor=vendor, + user_id=member_id +) + +# Update member role +vendor_team_service.update_member_role( + db=db, + vendor=vendor, + user_id=member_id, + new_role_name="Manager", + custom_permissions=None +) + +# Get team members +members = vendor_team_service.get_team_members( + db=db, + vendor=vendor, + include_inactive=False +) +``` + +--- + +## Exception Handling + +```python +from app.exceptions import ( + InsufficientVendorPermissionsException, + VendorOwnerOnlyException, + VendorAccessDeniedException, + InvalidInvitationTokenException, + CannotRemoveVendorOwnerException, + TeamMemberAlreadyExistsException +) + +# Raise permission error +raise InsufficientVendorPermissionsException( + required_permission="products.create", + vendor_code=vendor.vendor_code +) + +# Raise owner-only error +raise VendorOwnerOnlyException( + operation="team management", + vendor_code=vendor.vendor_code +) + +# Raise access denied +raise VendorAccessDeniedException( + vendor_code=vendor.vendor_code, + user_id=user.id +) +``` + +--- + +## Frontend Permission Checks + +### JavaScript/Alpine.js + +```javascript +// Check permission +function hasPermission(permission) { + const permissions = JSON.parse( + localStorage.getItem('permissions') || '[]' + ); + return permissions.includes(permission); +} + +// Conditional rendering +{hasPermission('products.create') && ( + +)} + +// Disable button + + +// Get permissions on login +async function getPermissions() { + const response = await fetch( + '/api/v1/vendor/team/me/permissions', + { + headers: { + 'Authorization': `Bearer ${token}` + } + } + ); + const data = await response.json(); + localStorage.setItem( + 'permissions', + JSON.stringify(data.permissions) + ); +} +``` + +--- + +## Testing Patterns + +### Unit Test +```python +def test_owner_has_all_permissions(): + vendor_user = create_vendor_user(user_type="owner") + assert vendor_user.has_permission("products.create") + assert vendor_user.has_permission("team.invite") +``` + +### Integration Test +```python +def test_create_product_with_permission(client): + user = create_user_with_permission("products.create") + token = create_token(user) + + response = client.post( + "/api/v1/vendor/ACME/products", + json={"name": "Test"}, + headers={"Authorization": f"Bearer {token}"} + ) + + assert response.status_code == 201 +``` + +--- + +## Common Mistakes to Avoid + +### āŒ DON'T: Check permissions in service layer +```python +# BAD +def create_product(user, data): + if not user.has_permission("products.create"): + raise Exception() +``` + +### āœ… DO: Check permissions at route level +```python +# GOOD +@router.post("/products") +def create_product( + user: User = Depends(require_vendor_permission("products.create")) +): + return service.create_product(data) +``` + +--- + +### āŒ DON'T: Use magic strings +```python +# BAD +require_vendor_permission("products.creat") # Typo! +``` + +### āœ… DO: Use constants +```python +# GOOD +require_vendor_permission(VendorPermissions.PRODUCTS_CREATE.value) +``` + +--- + +### āŒ DON'T: Mix contexts +```python +# BAD - Admin trying to access vendor route +# This will be blocked automatically +``` + +### āœ… DO: Use correct portal +```python +# GOOD - Admins use /admin/*, vendors use /vendor/* +``` + +--- + +## Debugging Commands + +### Check User Access +```python +user = db.query(User).get(user_id) +vendor = db.query(Vendor).get(vendor_id) + +print(f"Is owner: {user.is_owner_of(vendor.id)}") +print(f"Is member: {user.is_member_of(vendor.id)}") +print(f"Role: {user.get_vendor_role(vendor.id)}") +print(f"Has products.create: {user.has_vendor_permission(vendor.id, 'products.create')}") +``` + +### Decode JWT Token +```python +import jwt + +token = "eyJ0eXAi..." +decoded = jwt.decode(token, verify=False) +print(f"User ID: {decoded['sub']}") +print(f"Username: {decoded['username']}") +print(f"Role: {decoded['role']}") +print(f"Expires: {decoded['exp']}") +``` + +### Check Cookie +```javascript +// In browser console +document.cookie.split(';').forEach(c => console.log(c.trim())); +``` + +--- + +## Role Presets + +| Role | Typical Permissions | +|------|---------------------| +| **Owner** | ALL (automatic) | +| **Manager** | Most operations, no team management | +| **Staff** | Products, orders, customers (CRUD) | +| **Support** | Orders, customers (support focus) | +| **Viewer** | Read-only access | +| **Marketing** | Customers, marketing, reports | + +--- + +## File Locations + +``` +app/ +ā”œā”€ā”€ api/ +│ ā”œā”€ā”€ deps.py ← All auth dependencies +│ └── v1/ +│ ā”œā”€ā”€ admin/ +│ │ └── auth.py ← Admin login +│ ā”œā”€ā”€ vendor/ +│ │ ā”œā”€ā”€ auth.py ← Vendor login +│ │ └── team.py ← Team management +│ └── public/ +│ └── vendors/auth.py ← Customer login +│ +ā”œā”€ā”€ core/ +│ └── permissions.py ← Permission constants +│ +ā”œā”€ā”€ exceptions/ +│ ā”œā”€ā”€ admin.py +│ ā”œā”€ā”€ vendor.py +│ └── auth.py +│ +ā”œā”€ā”€ services/ +│ ā”œā”€ā”€ auth_service.py +│ └── vendor_team_service.py ← Team management +│ +└── models/ + └── database/ + ā”œā”€ā”€ user.py ← User model + ā”œā”€ā”€ vendor.py ← Vendor, VendorUser, Role + └── customer.py ← Customer model +``` + +--- + +## Status Codes + +| Code | Meaning | Common Cause | +|------|---------|--------------| +| 200 | OK | Success | +| 201 | Created | Resource created | +| 401 | Unauthorized | No/invalid token | +| 403 | Forbidden | No permission | +| 404 | Not Found | Resource not found | +| 422 | Validation Error | Invalid input | + +--- + +## Environment Variables + +```bash +JWT_SECRET_KEY=your-secret-key +JWT_ALGORITHM=HS256 +JWT_EXPIRATION=3600 # seconds (1 hour) +ENVIRONMENT=development|staging|production +``` + +--- + +**Print and keep at your desk!** + +For full documentation: See `RBAC_DEVELOPER_GUIDE.md` diff --git a/docs/__REVAMPING/RBAC/RBAC_VISUAL_GUIDE.md b/docs/__REVAMPING/RBAC/RBAC_VISUAL_GUIDE.md new file mode 100644 index 00000000..b2cb77ed --- /dev/null +++ b/docs/__REVAMPING/RBAC/RBAC_VISUAL_GUIDE.md @@ -0,0 +1,316 @@ +# RBAC Architecture Visual Guide + +## System Overview + +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ PLATFORM LEVEL │ +│ │ +│ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ +│ │ Admin Users │ │ Vendor Users │ │ +│ │ role="admin" │ │ role="vendor" │ │ +│ │ │ │ │ │ +│ │ • Full platform │ │ • Can own/join │ │ +│ │ access │ │ vendors │ │ +│ │ • Cannot access │ │ • Cannot access │ │ +│ │ vendor portal │ │ admin portal │ │ +│ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ +│ │ │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + ā–¼ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ VENDOR LEVEL │ +│ │ +│ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ +│ │ Vendor: ACME │ │ +│ │ │ │ +│ │ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ │ +│ │ │ Owner │ │ Team Members │ │ │ +│ │ │ user_type= │ │ user_type= │ │ │ +│ │ │ "owner" │ │ "member" │ │ │ +│ │ │ │ │ │ │ │ +│ │ │ • All perms │ │ • Role-based perms │ │ │ +│ │ │ • Can invite │ │ • Manager/Staff/etc │ │ │ +│ │ │ • Can remove │ │ • Can be invited │ │ │ +│ │ │ • Cannot be │ │ • Can be removed │ │ │ +│ │ │ removed │ │ │ │ │ +│ │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ │ +│ │ │ │ │ +│ │ ā–¼ │ │ +│ │ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ │ +│ │ │ Roles │ │ │ +│ │ │ │ │ │ +│ │ │ • Manager (many perms) │ │ │ +│ │ │ • Staff (moderate perms) │ │ │ +│ │ │ • Support (limited perms) │ │ │ +│ │ │ • Viewer (read-only) │ │ │ +│ │ │ • Custom (owner-defined) │ │ │ +│ │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ │ +│ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ +│ │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ CUSTOMER LEVEL │ +│ (Separate from Users) │ +│ │ +│ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ +│ │ Customers (per vendor) │ │ +│ │ │ │ +│ │ • Vendor-scoped authentication │ │ +│ │ • Can self-register │ │ +│ │ • Access own account + shop catalog │ │ +│ │ • Cannot access admin/vendor portals │ │ +│ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ +│ │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +``` + +## Team Invitation Flow + +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Owner │ +│ (ACME) │ +ā””ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + │ 1. Click "Invite Team Member" + │ Email: jane@example.com + │ Role: Manager + ā–¼ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ System Creates: │ +│ • User account │ +│ • VendorUser │ +│ • 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 │ +│ • VendorUser. │ +│ is_active │ +ā””ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + │ 4. Jane can now login + │ + ā–¼ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Jane logs in to │ +│ ACME vendor portal │ +│ with Manager perms │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +``` + +## Permission Check Flow + +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ User makes │ +│ request to: │ +│ POST /products │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + ā–¼ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ FastAPI Dependency: │ +│ require_vendor_permission( │ +│ "products.create" │ +│ ) │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + │ 1. Get vendor from request.state + │ 2. Get user from JWT + │ + ā–¼ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Is user a member of vendor? │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + ā”œā”€ No ──> āŒ VendorAccessDeniedException + │ + ā–¼ Yes +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Is user the owner? │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + ā”œā”€ Yes ──> āœ… Allow (owners have all perms) + │ + ā–¼ No +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Get user's role and │ +│ permissions from VendorUser │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + ā–¼ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Does role contain │ +│ "products.create"? │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + ā”œā”€ No ──> āŒ InsufficientVendorPermissionsException + │ + ā–¼ Yes +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ āœ… Allow request │ +│ Handler executes │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +``` + +## Database Relationships + +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ users │ +│ │ +│ id (PK) │◄────┐ +│ email │ │ +│ role │ │ +│ ('admin' or │ │ +│ 'vendor') │ │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ + │ │ + │ owner_user_id │ + │ │ + ā–¼ │ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ +│ vendors │ │ +│ │ │ +│ id (PK) │ │ +│ vendor_code │ │ +│ owner_user_id ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”˜ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + │ + │ + ā–¼ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ vendor_users │ │ roles │ +│ │ │ │ +│ id (PK) │ │ id (PK) │ +│ vendor_id (FK) │ │ vendor_id (FK) │ +│ user_id (FK) │ │ name │ +│ role_id (FK) ───┼────►│ permissions │ +│ user_type │ │ (JSON) │ +│ ('owner' or │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +│ 'member') │ +│ invitation_* │ +│ is_active │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + +Separate hierarchy: + +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ customers │ +│ │ +│ id (PK) │ +│ vendor_id (FK) │ +│ email │ +│ hashed_password │ +│ (vendor-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 + +Admin → Vendor Portal Admin → Admin Portal +Vendor → Admin Portal Vendor → Vendor Portal +Customer → Admin Portal Customer → Shop Catalog +Customer → Vendor Portal Customer → Own Account + +Cookie Isolation: +admin_token (path=/admin) ← Only sent to /admin/* +vendor_token (path=/vendor) ← Only sent to /vendor/* +customer_token (path=/shop) ← Only sent to /shop/* +```