# 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=<JWT>
       │             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=<JWT>
       │             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=<JWT>
       │             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 <token> 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 <token> 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("/vendor/{code}/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: 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() && (
    <button onClick={handleCreateProduct}>
        Create Product
    </button>
)}

// Disable button if no permission
<button
    disabled={!canCreateProducts()}
    onClick={handleCreateProduct}
>
    Create Product
</button>
```

---

## 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') && (
    <DeleteButton />
)}

// Disable button if no permission
<button disabled={!hasPermission('products.edit')}>
    Edit
</button>
```

**❌ DON'T: Rely only on frontend checks**

Backend MUST always verify permissions. Frontend checks are for UX only.

---

## 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"]
```

---

## 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
```

---

## Quick Reference

See [RBAC Quick Reference](../backend/rbac-quick-reference.md) for a condensed cheat sheet of common imports, route patterns, and permission constants.

---

## Related Documentation

- [Authentication System](authentication.md) - JWT authentication implementation
- [Architecture Overview](../architecture/auth-rbac.md) - System-wide authentication and RBAC
- [Backend Development](../backend/overview.md) - Backend development guide
- [API Reference](../backend/middleware-reference.md) - Auto-generated API documentation

---

**Document Version:** 1.0
**Last Updated:** November 2025
**Maintained By:** Backend Team
