Some checks failed
- Add Development URL Quick Reference section to url-routing overview with all login URLs, entry points, and full examples - Replace /shop/ path segments with /storefront/ across 50 docs files - Update file references: shop_pages.py → storefront_pages.py, templates/shop/ → templates/storefront/, api/v1/shop/ → api/v1/storefront/ - Preserve domain references (orion.shop) and /store/ staff dashboard paths - Archive docs left unchanged (historical) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
672 lines
19 KiB
Markdown
672 lines
19 KiB
Markdown
# Authentication & Role-Based Access Control (RBAC)
|
|
|
|
Complete guide to the authentication and authorization system powering the multi-tenant platform.
|
|
|
|
## Overview
|
|
|
|
The platform uses a JWT-based authentication system combined with role-based access control (RBAC) to secure all interfaces:
|
|
- **Admin** interface
|
|
- **Store** dashboard
|
|
- **Storefront**
|
|
- **REST API** endpoints
|
|
|
|
## Authentication System
|
|
|
|
### Technology Stack
|
|
|
|
- **JWT (JSON Web Tokens)**: Stateless authentication
|
|
- **bcrypt**: Secure password hashing
|
|
- **Jose**: JWT encoding/decoding library
|
|
- **FastAPI Security**: OAuth2 password bearer flow
|
|
|
|
### Authentication Flow
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant Client
|
|
participant API
|
|
participant AuthManager
|
|
participant Database
|
|
|
|
Client->>API: POST /api/v1/auth/login<br/>{username, password}
|
|
API->>AuthManager: authenticate_user()
|
|
AuthManager->>Database: Query user by username/email
|
|
Database-->>AuthManager: User record
|
|
AuthManager->>AuthManager: verify_password()
|
|
AuthManager-->>API: User object
|
|
API->>AuthManager: create_access_token()
|
|
AuthManager-->>API: JWT token
|
|
API-->>Client: {access_token, token_type, expires_in}
|
|
|
|
Note over Client: Store token
|
|
|
|
Client->>API: GET /api/v1/resource<br/>Authorization: Bearer <token>
|
|
API->>AuthManager: verify_token()
|
|
AuthManager->>AuthManager: Decode JWT
|
|
AuthManager->>Database: Query user by ID
|
|
Database-->>AuthManager: User object
|
|
AuthManager-->>API: Current user
|
|
API->>API: Process request
|
|
API-->>Client: Resource data
|
|
```
|
|
|
|
## User Roles
|
|
|
|
The platform uses a **4-value role enum** on the `User` model to distinguish user types:
|
|
|
|
```
|
|
User.role: "super_admin" | "platform_admin" | "merchant_owner" | "store_member"
|
|
```
|
|
|
|
### Customer Role (Separate Model)
|
|
|
|
**Access**: Public storefront and own account space
|
|
|
|
**Capabilities**:
|
|
- Browse store storefronts
|
|
- Place orders
|
|
- Manage their own account and order history
|
|
- View order status
|
|
- Update profile information
|
|
- Can register directly from storefront frontend
|
|
|
|
**Account Creation**: Self-registration via storefront frontend (email verification required)
|
|
|
|
**Authentication**: Standard JWT authentication (separate `Customer` model, not `User`)
|
|
|
|
### Merchant Owner (`role="merchant_owner"`)
|
|
|
|
**Access**: Full access to owned store dashboards
|
|
|
|
**Characteristics**:
|
|
- Has **ALL store permissions** automatically (no role record needed)
|
|
- Ownership determined by `Merchant.owner_user_id`
|
|
- `User.is_merchant_owner` property returns `True`
|
|
- `User.is_store_user` property returns `True`
|
|
- `User.is_owner_of(store_id)` checks ownership
|
|
|
|
**Capabilities**:
|
|
- Manage products and inventory
|
|
- Process orders
|
|
- View analytics and reports
|
|
- Configure storefront settings
|
|
- Manage team members (invite, remove, update roles)
|
|
- Access store-specific APIs
|
|
|
|
**Account Creation**: Created automatically when admin creates a store
|
|
|
|
### Store Member (`role="store_member"`)
|
|
|
|
**Access**: Store area based on assigned role permissions
|
|
|
|
**Characteristics**:
|
|
- Permissions come from `StoreUser.role_id -> Role.permissions`
|
|
- `User.is_store_user` property returns `True`
|
|
- Must be invited by merchant owner via email
|
|
|
|
**Capabilities**: Limited based on assigned role (Manager, Staff, Support, Viewer, Marketing, or custom)
|
|
|
|
**Account Creation**: Invited by merchant owner via email
|
|
|
|
**Permissions System**: Team members can have granular permissions for different areas (up to 75 permissions)
|
|
|
|
### Super Admin (`role="super_admin"`)
|
|
|
|
**Access**: Full platform administration across all platforms
|
|
|
|
**Characteristics**:
|
|
- `User.is_super_admin` property returns `True` (computed: `role == "super_admin"`)
|
|
- `User.is_admin` property returns `True`
|
|
- Can access all platforms without restriction
|
|
- Cannot access store portal (blocked by middleware)
|
|
|
|
**Capabilities**:
|
|
- Manage all stores across all platforms
|
|
- Create/manage store accounts
|
|
- Access system settings
|
|
- View all data across the platform
|
|
- Manage users of all types
|
|
- Access audit logs
|
|
- Platform-wide analytics
|
|
|
|
**Account Creation**: Created by existing super admins on the backend
|
|
|
|
### Platform Admin (`role="platform_admin"`)
|
|
|
|
**Access**: Platform administration scoped to assigned platforms
|
|
|
|
**Characteristics**:
|
|
- `User.is_platform_admin` property returns `True` (computed: `role == "platform_admin"`)
|
|
- `User.is_admin` property returns `True`
|
|
- Scoped to specific platforms via `AdminPlatform` association
|
|
- Cannot access store portal (blocked by middleware)
|
|
|
|
**Capabilities**:
|
|
- Manage stores within assigned platforms
|
|
- Access platform-scoped settings and analytics
|
|
- View data within assigned platforms
|
|
|
|
**Account Creation**: Created by super admins
|
|
|
|
## Application Areas & Access Control
|
|
|
|
The platform has three distinct areas with different access requirements:
|
|
|
|
| Area | URL Pattern | Access | Purpose |
|
|
|------|-------------|--------|---------|
|
|
| **Admin** | `/admin/*` or `admin.platform.com` | Super admins and platform admins (`is_admin`) | Platform administration and store management |
|
|
| **Store** | `/store/*` | Merchant owners and store members (`is_store_user`) | Store dashboard and storefront management |
|
|
| **Storefront** | `/storefront/*`, custom domains, subdomains | Customers and public | Public-facing eCommerce storefront |
|
|
| **API** | `/api/*` | All authenticated users (role-based) | REST API for all operations |
|
|
|
|
## Account Registration Flow
|
|
|
|
### Admin Accounts (Super Admin & Platform Admin)
|
|
- ❌ Cannot register from frontend
|
|
- ✅ **Super Admins** (`role="super_admin"`): Created by existing super admins
|
|
- ✅ **Platform Admins** (`role="platform_admin"`): Created by super admins
|
|
- Used for: Platform administration
|
|
|
|
### Store Accounts (Merchant Owner & Store Member)
|
|
- ❌ Cannot register from frontend
|
|
- ✅ **Merchant Owners** (`role="merchant_owner"`): Automatically created when admin creates a new store
|
|
- ✅ **Store Members** (`role="store_member"`): Invited by merchant owner via email invitation
|
|
- Activation: Upon clicking email verification link
|
|
|
|
### Customer Accounts
|
|
- ✅ Can register directly on store storefront
|
|
- Activation: Upon clicking registration email link
|
|
- Used for: Shopping and order management
|
|
|
|
## Role Enforcement Methods
|
|
|
|
The `AuthManager` class provides several methods for role-based access control:
|
|
|
|
### require_admin()
|
|
|
|
Restricts access to admin users only.
|
|
|
|
**Usage**:
|
|
```python
|
|
from fastapi import Depends
|
|
from models.database.user import User
|
|
from middleware.auth import auth_manager
|
|
|
|
@app.get("/admin/dashboard")
|
|
async def admin_dashboard(
|
|
current_user: User = Depends(auth_manager.require_admin)
|
|
):
|
|
return {"message": "Admin access"}
|
|
```
|
|
|
|
**Raises**: `AdminRequiredException` if user is not admin
|
|
|
|
### require_store()
|
|
|
|
Allows access to store users and admins.
|
|
|
|
**Usage**:
|
|
```python
|
|
@app.get("/store/products")
|
|
async def store_products(
|
|
current_user: User = Depends(auth_manager.require_store)
|
|
):
|
|
return {"products": [...]}
|
|
```
|
|
|
|
**Raises**: `InsufficientPermissionsException` if user is not store or admin
|
|
|
|
### require_customer()
|
|
|
|
Allows access to customer users and admins.
|
|
|
|
**Usage**:
|
|
```python
|
|
@app.get("/storefront/orders")
|
|
async def storefront_orders(
|
|
current_user: User = Depends(auth_manager.require_customer)
|
|
):
|
|
return {"orders": [...]}
|
|
```
|
|
|
|
**Raises**: `InsufficientPermissionsException` if user is not customer or admin
|
|
|
|
### require_role()
|
|
|
|
Custom role enforcement for specific roles. This method returns a decorator factory that creates role-checking decorators for any role name.
|
|
|
|
**Method Signature**:
|
|
```python
|
|
def require_role(self, required_role: str) -> Callable
|
|
```
|
|
|
|
**Parameters**:
|
|
- `required_role` (str): The exact role name required (e.g., `"super_admin"`, `"platform_admin"`, `"merchant_owner"`, `"store_member"`)
|
|
|
|
**Returns**: A decorator function that:
|
|
1. Accepts a function as input
|
|
2. Returns a wrapper that validates the current user's role
|
|
3. Raises `HTTPException(403)` if the role doesn't match
|
|
|
|
**Usage Example**:
|
|
```python
|
|
from fastapi import Depends, APIRouter
|
|
from middleware.auth import auth_manager
|
|
from models.database.user import User
|
|
|
|
router = APIRouter()
|
|
|
|
@router.get("/owner-only")
|
|
@auth_manager.require_role("merchant_owner")
|
|
async def owner_endpoint(current_user: User):
|
|
"""Only users with role='merchant_owner' can access this."""
|
|
return {"message": "Merchant owner access granted"}
|
|
|
|
# Can also be used with other specific roles
|
|
@router.get("/super-admin-only")
|
|
@auth_manager.require_role("super_admin")
|
|
async def super_admin_endpoint(current_user: User):
|
|
return {"data": "super admin content"}
|
|
```
|
|
|
|
**Error Response**:
|
|
```json
|
|
{
|
|
"detail": "Required role 'merchant_owner' not found. Current role: 'store_member'"
|
|
}
|
|
```
|
|
|
|
**Note**: For standard access patterns, prefer using the dedicated methods (`require_admin()`, `require_store()`, `require_customer()`) or the computed properties (`is_admin`, `is_store_user`) as they provide better error handling and custom exceptions.
|
|
|
|
### create_default_admin_user()
|
|
|
|
Creates a default super admin user if one doesn't already exist. This is typically used during initial application setup or database seeding.
|
|
|
|
**Method Signature**:
|
|
```python
|
|
def create_default_admin_user(self, db: Session) -> User
|
|
```
|
|
|
|
**Parameters**:
|
|
- `db` (Session): SQLAlchemy database session
|
|
|
|
**Returns**: `User` object (either the existing admin user or the newly created one)
|
|
|
|
**Behavior**:
|
|
1. Checks if a user with username "admin" already exists
|
|
2. If not found, creates a new super admin user with:
|
|
- Username: `admin`
|
|
- Email: `admin@example.com`
|
|
- Password: `admin123` (hashed with bcrypt)
|
|
- Role: `super_admin`
|
|
- Status: Active
|
|
3. If found, returns the existing user without modification
|
|
|
|
**Usage Example**:
|
|
```python
|
|
from app.core.database import SessionLocal
|
|
from middleware.auth import auth_manager
|
|
|
|
# During application startup or database initialization
|
|
db = SessionLocal()
|
|
try:
|
|
admin_user = auth_manager.create_default_admin_user(db)
|
|
print(f"Admin user ready: {admin_user.username}")
|
|
finally:
|
|
db.close()
|
|
```
|
|
|
|
**Security Warning**:
|
|
⚠️ The default credentials (`admin` / `admin123`) should be changed immediately after first login in production environments. Consider using environment variables for initial admin credentials:
|
|
|
|
```python
|
|
# Example: Custom admin creation with env variables
|
|
import os
|
|
|
|
def create_admin_from_env(db: Session):
|
|
admin_username = os.getenv("ADMIN_USERNAME", "admin")
|
|
admin_password = os.getenv("ADMIN_PASSWORD", "admin123")
|
|
admin_email = os.getenv("ADMIN_EMAIL", "admin@example.com")
|
|
|
|
# Check if admin exists
|
|
admin = db.query(User).filter(User.username == admin_username).first()
|
|
if not admin:
|
|
admin = User(
|
|
username=admin_username,
|
|
email=admin_email,
|
|
hashed_password=auth_manager.hash_password(admin_password),
|
|
role="super_admin",
|
|
is_active=True
|
|
)
|
|
db.add(admin)
|
|
db.commit()
|
|
return admin
|
|
```
|
|
|
|
**Typical Use Cases**:
|
|
- Initial database setup scripts
|
|
- Application bootstrap/initialization
|
|
- Development environment setup
|
|
- Testing fixtures
|
|
|
|
## JWT Token Structure
|
|
|
|
### Token Payload
|
|
|
|
```json
|
|
{
|
|
"sub": "123", // User ID (JWT standard claim)
|
|
"username": "testuser", // Username for display
|
|
"email": "user@example.com", // User email
|
|
"role": "merchant_owner", // User role (4-value enum)
|
|
"exp": 1700000000, // Expiration timestamp (JWT standard)
|
|
"iat": 1699999000 // Issued at timestamp (JWT standard)
|
|
}
|
|
```
|
|
|
|
### Token Configuration
|
|
|
|
| Variable | Description | Default |
|
|
|----------|-------------|---------|
|
|
| `JWT_SECRET_KEY` | Secret key for JWT signing | Development key (change in production!) |
|
|
| `JWT_EXPIRE_MINUTES` | Token expiration time in minutes | 30 |
|
|
|
|
**Environment Configuration**:
|
|
```bash
|
|
# .env
|
|
JWT_SECRET_KEY=your-super-secret-key-change-in-production
|
|
JWT_EXPIRE_MINUTES=30
|
|
```
|
|
|
|
## Permission Hierarchy
|
|
|
|
```mermaid
|
|
graph TD
|
|
A[Super Admin<br/>role=super_admin] --> B[Full Platform Access]
|
|
A --> C[All Platforms]
|
|
|
|
AA[Platform Admin<br/>role=platform_admin] --> D2[Scoped Platform Access]
|
|
AA --> D3[Assigned Platforms Only]
|
|
|
|
D[Merchant Owner<br/>role=merchant_owner] --> E[Store Dashboard]
|
|
D --> F[Team Management]
|
|
D --> G[Storefront Settings]
|
|
D --> H[All Store Permissions - 75]
|
|
|
|
I[Store Member<br/>role=store_member] --> E
|
|
I --> J[Role-Based Permissions]
|
|
|
|
K[Customer<br/>separate model] --> L[Storefront Access]
|
|
K --> M[Own Orders]
|
|
K --> N[Own Profile]
|
|
```
|
|
|
|
**Admin Override**: Admin users (`is_admin`: super admins and platform admins) have access to the admin portal. They cannot access the store portal directly -- these are separate security boundaries enforced by middleware.
|
|
|
|
**Note**: `is_super_admin` is no longer a database column. It is a computed property: `User.role == "super_admin"`. JWT tokens no longer include an `is_super_admin` claim; derive it from the `role` claim instead.
|
|
|
|
## Security Features
|
|
|
|
### Password Security
|
|
|
|
**Hashing**:
|
|
- Algorithm: bcrypt
|
|
- Automatic salt generation
|
|
- Configurable work factor
|
|
|
|
**Example**:
|
|
```python
|
|
from middleware.auth import auth_manager
|
|
|
|
# Hash password
|
|
hashed = auth_manager.hash_password("user_password")
|
|
|
|
# Verify password
|
|
is_valid = auth_manager.verify_password("user_password", hashed)
|
|
```
|
|
|
|
### Token Security
|
|
|
|
**Features**:
|
|
- Signed with secret key (prevents tampering)
|
|
- Includes expiration time
|
|
- Stateless (no server-side session storage)
|
|
- Short-lived (30 minutes default)
|
|
|
|
**Best Practices**:
|
|
- Use HTTPS in production
|
|
- Store tokens securely on client
|
|
- Implement token refresh mechanism
|
|
- Clear tokens on logout
|
|
|
|
### Protection Against Common Attacks
|
|
|
|
**SQL Injection**:
|
|
- ✅ SQLAlchemy ORM with parameterized queries
|
|
- ✅ Input validation with Pydantic
|
|
|
|
**XSS (Cross-Site Scripting)**:
|
|
- ✅ Jinja2 auto-escaping
|
|
- ✅ Content Security Policy headers
|
|
|
|
**CSRF (Cross-Site Request Forgery)**:
|
|
- ✅ JWT tokens in Authorization header (not cookies)
|
|
- ✅ SameSite cookie attribute for session cookies
|
|
|
|
**Brute Force**:
|
|
- ✅ Rate limiting on auth endpoints
|
|
- ✅ Account lockout after failed attempts (future)
|
|
|
|
## Authentication Endpoints
|
|
|
|
### Register User
|
|
|
|
```http
|
|
POST /api/v1/auth/register
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"email": "user@example.com",
|
|
"username": "testuser",
|
|
"password": "securepassword123"
|
|
}
|
|
```
|
|
|
|
**Response**:
|
|
```json
|
|
{
|
|
"id": 123,
|
|
"username": "testuser",
|
|
"email": "user@example.com",
|
|
"role": "customer",
|
|
"is_active": true
|
|
}
|
|
```
|
|
|
|
### Login
|
|
|
|
```http
|
|
POST /api/v1/auth/login
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"username": "testuser",
|
|
"password": "securepassword123"
|
|
}
|
|
```
|
|
|
|
**Response**:
|
|
```json
|
|
{
|
|
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
|
|
"token_type": "bearer",
|
|
"expires_in": 1800
|
|
}
|
|
```
|
|
|
|
### Using Authentication
|
|
|
|
Include the JWT token in the Authorization header:
|
|
|
|
```http
|
|
GET /api/v1/resource
|
|
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
### Authentication Errors
|
|
|
|
| Error | Status Code | Description |
|
|
|-------|-------------|-------------|
|
|
| `InvalidCredentialsException` | 401 | Username/password incorrect |
|
|
| `InvalidTokenException` | 401 | JWT token invalid or malformed |
|
|
| `TokenExpiredException` | 401 | JWT token has expired |
|
|
| `UserNotActiveException` | 403 | User account is inactive |
|
|
|
|
### Authorization Errors
|
|
|
|
| Error | Status Code | Description |
|
|
|-------|-------------|-------------|
|
|
| `AdminRequiredException` | 403 | Endpoint requires admin role |
|
|
| `InsufficientPermissionsException` | 403 | User lacks required permissions |
|
|
|
|
## Testing Authentication
|
|
|
|
### Unit Tests
|
|
|
|
```python
|
|
import pytest
|
|
from middleware.auth import AuthManager
|
|
|
|
def test_password_hashing():
|
|
auth_manager = AuthManager()
|
|
|
|
password = "test_password"
|
|
hashed = auth_manager.hash_password(password)
|
|
|
|
assert auth_manager.verify_password(password, hashed)
|
|
assert not auth_manager.verify_password("wrong_password", hashed)
|
|
|
|
def test_create_token():
|
|
auth_manager = AuthManager()
|
|
user = create_test_user(role="merchant_owner")
|
|
|
|
token_data = auth_manager.create_access_token(user)
|
|
|
|
assert "access_token" in token_data
|
|
assert "token_type" in token_data
|
|
assert token_data["token_type"] == "bearer"
|
|
```
|
|
|
|
### Integration Tests
|
|
|
|
```python
|
|
def test_login_flow(client):
|
|
# Register user
|
|
response = client.post("/api/v1/auth/register", json={
|
|
"username": "testuser",
|
|
"email": "test@example.com",
|
|
"password": "password123"
|
|
})
|
|
assert response.status_code == 200
|
|
|
|
# Login
|
|
response = client.post("/api/v1/auth/login", json={
|
|
"username": "testuser",
|
|
"password": "password123"
|
|
})
|
|
assert response.status_code == 200
|
|
token = response.json()["access_token"]
|
|
|
|
# Access protected endpoint
|
|
response = client.get(
|
|
"/api/v1/profile",
|
|
headers={"Authorization": f"Bearer {token}"}
|
|
)
|
|
assert response.status_code == 200
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### For Developers
|
|
|
|
1. **Always hash passwords**: Never store plain text passwords
|
|
2. **Use dependency injection**: Leverage FastAPI's `Depends` for auth
|
|
3. **Validate tokens**: Always validate tokens on protected endpoints
|
|
4. **Check permissions**: Verify user has required role/permissions
|
|
5. **Log auth events**: Track login, logout, failed attempts
|
|
|
|
### For Operations
|
|
|
|
1. **Strong secret keys**: Use long, random JWT secret keys
|
|
2. **HTTPS only**: Never send tokens over HTTP
|
|
3. **Token expiration**: Keep token lifetimes short
|
|
4. **Rotate secrets**: Periodically rotate JWT secret keys
|
|
5. **Monitor auth logs**: Watch for suspicious activity
|
|
|
|
### For Security
|
|
|
|
1. **Rate limiting**: Limit auth endpoint requests
|
|
2. **Account lockout**: Implement after N failed attempts
|
|
3. **Email verification**: Require email confirmation
|
|
4. **Password policies**: Enforce strong password requirements
|
|
5. **2FA support**: Consider adding two-factor authentication
|
|
|
|
## Examples
|
|
|
|
### Protecting an Endpoint
|
|
|
|
```python
|
|
from fastapi import Depends, APIRouter
|
|
from sqlalchemy.orm import Session
|
|
from middleware.auth import auth_manager
|
|
from app.core.database import get_db
|
|
from models.database.user import User
|
|
|
|
router = APIRouter()
|
|
|
|
@router.get("/stores")
|
|
async def get_stores(
|
|
current_user: User = Depends(auth_manager.require_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Only admins can list all stores."""
|
|
stores = db.query(Store).all()
|
|
return {"stores": stores}
|
|
```
|
|
|
|
### Multi-Role Access
|
|
|
|
```python
|
|
@router.get("/dashboard")
|
|
async def dashboard(
|
|
current_user: User = Depends(auth_manager.get_current_user),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Accessible by all authenticated users, but returns different data."""
|
|
if current_user.is_admin:
|
|
# Admin (super_admin or platform_admin) sees platform data
|
|
data = get_admin_dashboard(db)
|
|
elif current_user.is_store_user:
|
|
# Store user (merchant_owner or store_member) sees their store data
|
|
data = get_store_dashboard(db, current_user.id)
|
|
else:
|
|
# Customer sees their orders
|
|
data = get_customer_dashboard(db, current_user.id)
|
|
|
|
return data
|
|
```
|
|
|
|
## Related Documentation
|
|
|
|
- [Middleware Stack](middleware.md) - System-wide request processing
|
|
- [Error Handling](../api/error-handling.md) - Exception handling
|
|
- [Backend API Reference](../backend/middleware-reference.md) - Technical AuthManager docs
|
|
- [Testing Guide](../testing/testing-guide.md) - Testing authentication
|
|
|
|
## Technical Reference
|
|
|
|
For detailed API documentation of authentication classes and methods:
|
|
- [AuthManager API Reference](../backend/middleware-reference.md#authentication-authorization)
|