frontend error management enhancement

This commit is contained in:
2025-11-05 21:52:22 +01:00
parent e4bc438069
commit 79dfcab09f
66 changed files with 7781 additions and 922 deletions

View File

@@ -0,0 +1,285 @@
# Authentication Flow Diagrams
## Cookie Isolation Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ Browser │
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ Admin Area │ │ Vendor Area │ │
│ │ /admin/* │ │ /vendor/* │ │
│ │ │ │ │ │
│ │ 🍪 admin_token │ │ 🍪 vendor_token │ │
│ │ Path: /admin │ │ Path: /vendor │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │ │ │
│ ├───────────────────────────┤ │
│ │ ❌ No Cookie Mixing │ │
│ │ │ │
└───────────┼───────────────────────────┼──────────────────────┘
│ │
▼ ▼
┌───────────────────────┐ ┌───────────────────────┐
│ Admin Backend │ │ Vendor Backend │
│ /admin/* │ │ /vendor/* │
│ │ │ │
│ ✅ admin_token │ │ ✅ vendor_token │
│ ❌ vendor_token │ │ ❌ admin_token │
└───────────────────────┘ └───────────────────────┘
```
## Login Flow - Admin
```
┌──────────┐
│ Browser │
└──────────┘
│ 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; SameSite=Lax
│ Response: { access_token, user }
┌──────────┐
│ Browser │──────────────────────────────────────┐
│ │ │
│ 🍪 admin_token (Path=/admin) │
│ 💾 localStorage.access_token │
└──────────┘ │
│ │
├── Navigate to /admin/dashboard ────────────┤
│ (Cookie sent automatically) │
│ │
└── API call to /api/v1/admin/vendors ───────┤
(Authorization: Bearer <token>) │
┌──────────────▼──────────────┐
│ get_current_admin_user() │
│ │
│ 1. Check Auth header │
│ 2. Check admin_token cookie │
│ 3. Validate JWT │
│ 4. Verify role == admin │
│ ✅ Return User │
└──────────────────────────────┘
```
## Login Flow - Vendor
```
┌──────────┐
│ Browser │
└──────────┘
│ POST /api/v1/vendor/auth/login
│ { username, password }
┌─────────────────────────┐
│ Vendor Auth Endpoint │
│ │
│ 1. Validate credentials│
│ 2. Block if admin │──────> ❌ "Admins cannot access vendor portal"
│ 3. Check vendor access │
│ 4. Generate JWT │
└─────────────────────────┘
│ Set-Cookie: vendor_token=<JWT>; Path=/vendor; HttpOnly; SameSite=Lax
│ Response: { access_token, user, vendor }
┌──────────┐
│ Browser │──────────────────────────────────────┐
│ │ │
│ 🍪 vendor_token (Path=/vendor) │
│ 💾 localStorage.access_token │
└──────────┘ │
│ │
├── Navigate to /vendor/ACME/dashboard ──────┤
│ (Cookie sent automatically) │
│ │
└── API call to /api/v1/vendor/ACME/products ┤
(Authorization: Bearer <token>) │
┌──────────────▼──────────────┐
│ get_current_vendor_user() │
│ │
│ 1. Check Auth header │
│ 2. Check vendor_token cookie│
│ 3. Validate JWT │
│ 4. Block if admin │──> ❌ Error
│ 5. Verify vendor access │
│ ✅ Return User │
└──────────────────────────────┘
```
## Security Boundary Enforcement
```
┌─────────────────────┐
│ Request Comes In │
└──────────┬──────────┘
┌──────────▼──────────┐
│ What's the path? │
└──────────┬──────────┘
┌───────────────┼───────────────┐
│ │ │
Starts with Starts with Starts with
/admin/* /vendor/* /api/*
│ │ │
▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Check for: │ │ Check for: │ │ Check for: │
│ - admin_token │ │ - vendor_token │ │ - Authorization │
│ cookie │ │ cookie │ │ header │
│ - OR Auth header │ │ - OR Auth header │ │ (required) │
└────────┬─────────┘ └────────┬─────────┘ └────────┬─────────┘
│ │ │
▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Validate: │ │ Validate: │ │ Validate: │
│ - JWT valid │ │ - JWT valid │ │ - JWT valid │
│ - User active │ │ - User active │ │ - User active │
│ - Role = admin │ │ - Role != admin │ │ - Any role │
│ │ │ - Has vendor │ │ (depends on │
│ │ │ access │ │ endpoint) │
└────────┬─────────┘ └────────┬─────────┘ └────────┬─────────┘
│ │ │
▼ ▼ ▼
✅ Allowed ✅ Allowed ✅ Allowed
```
## Cross-Context Prevention
### ❌ What's Blocked
```
Admin trying to access vendor route:
┌──────────────────────────────────────────┐
│ User: admin@example.com (role: admin) │
│ Token: Valid JWT with admin role │
│ Request: GET /vendor/ACME/dashboard │
└──────────────────────────────────────────┘
┌───────────────────────┐
│ get_current_vendor_ │
│ from_cookie_or_header │
└───────────┬───────────┘
Check: role == "admin"?
▼ Yes
❌ InsufficientPermissionsException
"Vendor access only - admins cannot use vendor portal"
```
```
Vendor trying to access admin route:
┌──────────────────────────────────────────┐
│ User: vendor@acme.com (role: vendor) │
│ Token: Valid JWT with vendor role │
│ Request: GET /admin/dashboard │
└──────────────────────────────────────────┘
┌───────────────────────┐
│ get_current_admin_ │
│ from_cookie_or_header │
└───────────┬───────────┘
Check: role == "admin"?
▼ No
❌ AdminRequiredException
"Admin privileges required"
```
```
Admin cookie sent to vendor route:
┌──────────────────────────────────────────┐
│ Cookie: admin_token=<JWT> (Path=/admin) │
│ Request: GET /vendor/ACME/dashboard │
└──────────────────────────────────────────┘
Browser checks cookie path
Path /vendor does NOT match /admin
❌ Cookie NOT sent
Request has no authentication
❌ InvalidTokenException
"Vendor authentication required"
```
## Cookie Lifecycle
```
LOGIN
├── Server generates JWT
├── Server sets cookie:
│ • Name: admin_token or vendor_token
│ • Value: JWT
│ • Path: /admin or /vendor
│ • HttpOnly: true
│ • Secure: true (production)
│ • SameSite: Lax
│ • Max-Age: matches JWT expiry
└── Server returns JWT in response body
└── Client stores in localStorage (optional)
PAGE NAVIGATION
├── Browser automatically includes cookie
│ if path matches
├── Server reads cookie
├── Server validates JWT
└── Server returns page or 401
API CALL
├── Client reads token from localStorage
├── Client adds Authorization header
│ Authorization: Bearer <JWT>
├── Server reads header
├── Server validates JWT
└── Server returns data or 401
LOGOUT
├── Client calls logout endpoint
├── Server clears cookie:
│ response.delete_cookie(name, path)
└── Client clears localStorage
localStorage.removeItem('access_token')
```
## Key Takeaways
1. **Cookie Path Isolation** = No cross-context cookies
2. **Role Checking** = Admins blocked from vendor routes
3. **Dual Auth Support** = Cookies for pages, headers for API
4. **Security First** = HttpOnly, Secure, SameSite protection
5. **Clear Boundaries** = Each context is completely isolated

View File

@@ -0,0 +1,271 @@
# Authentication Quick Reference
**Version 1.0** | One-page reference for developers
---
## Function Cheat Sheet
### For HTML Pages (accept cookie OR header)
```python
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
)
# Admin page
@router.get("/admin/dashboard")
def admin_page(user: User = Depends(get_current_admin_from_cookie_or_header)):
pass
# Vendor page
@router.get("/vendor/{code}/dashboard")
def vendor_page(user: User = Depends(get_current_vendor_from_cookie_or_header)):
pass
# Customer page
@router.get("/shop/account/dashboard")
def customer_page(user: User = Depends(get_current_customer_from_cookie_or_header)):
pass
```
### For API Endpoints (header only - better security)
```python
from app.api.deps import (
get_current_admin_api,
get_current_vendor_api,
get_current_customer_api
)
# Admin API
@router.post("/api/v1/admin/vendors")
def admin_api(user: User = Depends(get_current_admin_api)):
pass
# Vendor API
@router.post("/api/v1/vendor/{code}/products")
def vendor_api(user: User = Depends(get_current_vendor_api)):
pass
# Customer API
@router.post("/api/v1/shop/orders")
def customer_api(user: User = Depends(get_current_customer_api)):
pass
```
---
## Three Authentication Contexts
| Context | Cookie | Path | Role | Routes |
|---------|--------|------|------|--------|
| **Admin** | `admin_token` | `/admin` | `admin` | `/admin/*` |
| **Vendor** | `vendor_token` | `/vendor` | `vendor` | `/vendor/*` |
| **Customer** | `customer_token` | `/shop` | `customer` | `/shop/account/*` |
---
## Access Control Matrix
| User | Admin Portal | Vendor Portal | Shop Catalog | Customer Account |
|------|--------------|---------------|--------------|------------------|
| Admin | ✅ | ❌ | ✅ (view) | ❌ |
| Vendor | ❌ | ✅ | ✅ (view) | ❌ |
| Customer | ❌ | ❌ | ✅ (view) | ✅ |
| Anonymous | ❌ | ❌ | ✅ (view) | ❌ |
---
## Login Endpoints
```bash
# Admin
POST /api/v1/admin/auth/login
Body: {"username": "...", "password": "..."}
# Vendor
POST /api/v1/vendor/auth/login
Body: {"username": "...", "password": "..."}
# Customer
POST /api/v1/public/vendors/{vendor_id}/customers/login
Body: {"username": "...", "password": "..."}
```
**Response:**
```json
{
"access_token": "eyJ0eXAi...",
"token_type": "Bearer",
"expires_in": 3600,
"user": {...}
}
```
Plus HTTP-only cookie is set automatically.
---
## Frontend Patterns
### Login (Store Token)
```javascript
const response = await fetch('/api/v1/admin/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await response.json();
// Cookie set automatically
// Optionally store for API calls
localStorage.setItem('token', data.access_token);
// Navigate (cookie automatic)
window.location.href = '/admin/dashboard';
```
### API Call (Use Token)
```javascript
const token = localStorage.getItem('token');
const response = await fetch('/api/v1/admin/vendors', {
headers: {
'Authorization': `Bearer ${token}`
}
});
```
### Logout
```javascript
await fetch('/api/v1/admin/auth/logout', { method: 'POST' });
localStorage.removeItem('token');
window.location.href = '/admin/login';
```
---
## Testing Commands
### curl Examples
```bash
# Login
TOKEN=$(curl -X POST http://localhost:8000/api/v1/admin/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin123"}' \
| jq -r '.access_token')
# Authenticated request
curl http://localhost:8000/api/v1/admin/vendors \
-H "Authorization: Bearer $TOKEN"
```
### Check Cookie in Browser
```javascript
// In DevTools console
document.cookie.split(';').forEach(c => console.log(c.trim()));
```
### Decode JWT
```javascript
function parseJwt(token) {
return JSON.parse(atob(token.split('.')[1]));
}
console.log(parseJwt(localStorage.getItem('token')));
```
---
## Common Errors
| Error | Meaning | Solution |
|-------|---------|----------|
| `INVALID_TOKEN` | No token or invalid | Re-login |
| `TOKEN_EXPIRED` | Token expired | Re-login |
| `ADMIN_REQUIRED` | Need admin role | Use correct account |
| `INSUFFICIENT_PERMISSIONS` | Wrong role for route | Use correct portal |
| `USER_NOT_ACTIVE` | Account disabled | Contact admin |
---
## Security Rules
1.**HTML pages** use `*_from_cookie_or_header` functions
2.**API endpoints** use `*_api` functions
3.**Admins** cannot access vendor/customer portals
4.**Vendors** cannot access admin/customer portals
5.**Customers** cannot access admin/vendor portals
6.**Public shop** (`/shop/products`) needs no auth
7.**Customer accounts** (`/shop/account/*`) need auth
---
## Cookie Security
All cookies have:
-`HttpOnly=true` - JavaScript cannot read (XSS protection)
-`Secure=true` - HTTPS only (production)
-`SameSite=Lax` - CSRF protection
- ✅ Path restriction - Context isolation
---
## Quick Debug
1. **Auth not working?**
- Check DevTools → Application → Cookies
- Verify cookie name and path match route
- Check token not expired
2. **Cross-context access denied?**
- This is intentional security
- Use correct portal for your role
3. **API call fails but page loads?**
- API needs `Authorization` header
- Page uses cookie (automatic)
- Add header to API calls
---
## File Locations
```
app/api/
├── deps.py # All auth functions here
├── v1/
├── admin/auth.py # Admin login
├── vendor/auth.py # Vendor login
└── public/vendors/auth.py # Customer login
```
---
## Environment Variables
```bash
JWT_SECRET_KEY=your-secret-key
JWT_ALGORITHM=HS256
JWT_EXPIRATION=3600 # 1 hour
ENVIRONMENT=production
```
---
**Full Documentation:** See `AUTHENTICATION_SYSTEM_DOCS.md`
**Questions?** Contact backend team
---
**Print this page for quick reference!**

View File

@@ -0,0 +1,943 @@
# Authentication System Documentation
**Version:** 1.0
**Last Updated:** November 2024
**Audience:** Development Team
---
## Table of Contents
1. [System Overview](#system-overview)
2. [Architecture](#architecture)
3. [Authentication Contexts](#authentication-contexts)
4. [Implementation Guide](#implementation-guide)
5. [API Reference](#api-reference)
6. [Security Model](#security-model)
7. [Testing Guidelines](#testing-guidelines)
8. [Troubleshooting](#troubleshooting)
---
## System Overview
The LetzShop platform uses a **context-based authentication system** with three isolated security domains:
- **Admin Portal** - Platform administration and management
- **Vendor Portal** - Multi-tenant shop management
- **Customer Shop** - Public storefront and customer accounts
Each context uses **dual authentication** supporting both cookie-based (for HTML pages) and header-based (for API calls) authentication with complete isolation between contexts.
### Key Features
- **Cookie Path Isolation** - Separate cookies per context prevent cross-context access
- **Role-Based Access Control** - Strict enforcement of user roles
- **JWT Token Authentication** - Stateless, secure token-based auth
- **HTTP-Only Cookies** - XSS protection for browser sessions
- **CSRF Protection** - SameSite cookie attribute
- **Comprehensive Logging** - Full audit trail of authentication events
---
## Architecture
### Authentication Flow
```
┌─────────────────────────────────────────────────────┐
│ Client Request │
└─────────────────┬───────────────────────────────────┘
┌───────▼────────┐
│ Route Handler │
└───────┬────────┘
┌───────▼────────────────────────────────┐
│ Authentication Dependency │
│ (from app/api/deps.py) │
└───────┬────────────────────────────────┘
┌─────────────┼─────────────┐
│ │ │
┌───▼───┐ ┌────▼────┐ ┌───▼────┐
│Cookie │ │ Header │ │ None │
└───┬───┘ └────┬────┘ └───┬────┘
│ │ │
└────────┬───┴────────────┘
┌──────▼───────┐
│ Validate JWT │
└──────┬───────┘
┌──────▼──────────┐
│ Check User Role │
└──────┬──────────┘
┌────────┴─────────┐
│ │
┌───▼────┐ ┌─────▼──────┐
│Success │ │ Auth Error │
│Return │ │ 401/403 │
│User │ └────────────┘
└────────┘
```
### Cookie Isolation
Each authentication context uses a separate cookie with path restrictions:
| Context | Cookie Name | Cookie Path | Access Scope |
|----------|------------------|-------------|--------------|
| Admin | `admin_token` | `/admin` | Admin routes only |
| Vendor | `vendor_token` | `/vendor` | Vendor routes only |
| Customer | `customer_token` | `/shop` | Shop routes only |
**Browser Behavior:**
- When requesting `/admin/*`, browser sends `admin_token` cookie only
- When requesting `/vendor/*`, browser sends `vendor_token` cookie only
- When requesting `/shop/*`, browser sends `customer_token` cookie only
This prevents cookie leakage between contexts.
---
## Authentication Contexts
### 1. Admin Context
**Routes:** `/admin/*`
**Role:** `admin`
**Cookie:** `admin_token` (path=/admin)
**Purpose:** Platform administration, vendor management, system configuration.
**Access Control:**
- ✅ Admin users only
- ❌ Vendor users blocked
- ❌ Customer users blocked
**Login Endpoint:**
```
POST /api/v1/admin/auth/login
```
### 2. Vendor Context
**Routes:** `/vendor/*`
**Role:** `vendor`
**Cookie:** `vendor_token` (path=/vendor)
**Purpose:** Vendor shop management, product catalog, orders, team management.
**Access Control:**
- ❌ Admin users blocked (admins use admin portal for vendor management)
- ✅ Vendor users (owners and team members)
- ❌ Customer users blocked
**Login Endpoint:**
```
POST /api/v1/vendor/auth/login
```
### 3. Customer Context
**Routes:** `/shop/account/*` (authenticated), `/shop/*` (public)
**Role:** `customer`
**Cookie:** `customer_token` (path=/shop)
**Purpose:** Product browsing (public), customer accounts, orders, profile management.
**Access Control:**
- **Public Routes** (`/shop/products`, `/shop/cart`, etc.):
- ✅ Anyone can access (no authentication)
- **Account Routes** (`/shop/account/*`):
- ❌ Admin users blocked
- ❌ Vendor users blocked
- ✅ Customer users only
**Login Endpoint:**
```
POST /api/v1/public/vendors/{vendor_id}/customers/login
```
---
## Implementation Guide
### Module Structure
```
app/api/
├── deps.py # Authentication dependencies
├── v1/
├── admin/
│ └── auth.py # Admin authentication endpoints
├── vendor/
│ └── auth.py # Vendor authentication endpoints
└── public/vendors/
└── auth.py # Customer authentication endpoints
```
### For HTML Pages (Server-Rendered)
Use the `*_from_cookie_or_header` functions for pages that users navigate to:
```python
from fastapi import APIRouter, Request, Depends
from fastapi.responses import HTMLResponse
from sqlalchemy.orm import Session
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_db
)
from models.database.user import User
router = APIRouter()
# Admin page
@router.get("/admin/dashboard", response_class=HTMLResponse)
async def admin_dashboard(
request: Request,
current_user: User = Depends(get_current_admin_from_cookie_or_header),
db: Session = Depends(get_db)
):
return templates.TemplateResponse("admin/dashboard.html", {
"request": request,
"user": current_user
})
# Vendor page
@router.get("/vendor/{vendor_code}/dashboard", response_class=HTMLResponse)
async def vendor_dashboard(
request: Request,
vendor_code: str,
current_user: User = Depends(get_current_vendor_from_cookie_or_header),
db: Session = Depends(get_db)
):
return templates.TemplateResponse("vendor/dashboard.html", {
"request": request,
"user": current_user,
"vendor_code": vendor_code
})
# Customer account page
@router.get("/shop/account/dashboard", response_class=HTMLResponse)
async def customer_dashboard(
request: Request,
current_user: User = Depends(get_current_customer_from_cookie_or_header),
db: Session = Depends(get_db)
):
return templates.TemplateResponse("shop/account/dashboard.html", {
"request": request,
"user": current_user
})
```
### For API Endpoints (JSON Responses)
Use the `*_api` functions for API endpoints to enforce header-based authentication:
```python
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.api.deps import (
get_current_admin_api,
get_current_vendor_api,
get_current_customer_api,
get_db
)
from models.database.user import User
router = APIRouter()
# Admin API
@router.post("/api/v1/admin/vendors")
def create_vendor(
vendor_data: VendorCreate,
current_user: User = Depends(get_current_admin_api),
db: Session = Depends(get_db)
):
# Only accepts Authorization header (no cookies)
# Better security - prevents CSRF attacks
return {"message": "Vendor created"}
# Vendor API
@router.post("/api/v1/vendor/{vendor_code}/products")
def create_product(
vendor_code: str,
product_data: ProductCreate,
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db)
):
return {"message": "Product created"}
# Customer API
@router.post("/api/v1/shop/orders")
def create_order(
order_data: OrderCreate,
current_user: User = Depends(get_current_customer_api),
db: Session = Depends(get_db)
):
return {"message": "Order created"}
```
### For Public Routes (No Authentication)
Simply don't use any authentication dependency:
```python
@router.get("/shop/products")
async def public_products(request: Request):
# No authentication required
return templates.TemplateResponse("shop/products.html", {
"request": request
})
```
---
## API Reference
### Authentication Dependencies
All authentication functions are in `app/api/deps.py`:
#### `get_current_admin_from_cookie_or_header()`
**Purpose:** Authenticate admin users for HTML pages
**Accepts:** Cookie (`admin_token`) OR Authorization header
**Returns:** `User` object with `role="admin"`
**Raises:**
- `InvalidTokenException` - No token or invalid token
- `AdminRequiredException` - User is not admin
**Usage:**
```python
current_user: User = Depends(get_current_admin_from_cookie_or_header)
```
#### `get_current_admin_api()`
**Purpose:** Authenticate admin users for API endpoints
**Accepts:** Authorization header ONLY
**Returns:** `User` object with `role="admin"`
**Raises:**
- `InvalidTokenException` - No token or invalid token
- `AdminRequiredException` - User is not admin
**Usage:**
```python
current_user: User = Depends(get_current_admin_api)
```
#### `get_current_vendor_from_cookie_or_header()`
**Purpose:** Authenticate vendor users for HTML pages
**Accepts:** Cookie (`vendor_token`) OR Authorization header
**Returns:** `User` object with `role="vendor"`
**Raises:**
- `InvalidTokenException` - No token or invalid token
- `InsufficientPermissionsException` - User is not vendor or is admin
**Usage:**
```python
current_user: User = Depends(get_current_vendor_from_cookie_or_header)
```
#### `get_current_vendor_api()`
**Purpose:** Authenticate vendor users for API endpoints
**Accepts:** Authorization header ONLY
**Returns:** `User` object with `role="vendor"`
**Raises:**
- `InvalidTokenException` - No token or invalid token
- `InsufficientPermissionsException` - User is not vendor or is admin
**Usage:**
```python
current_user: User = Depends(get_current_vendor_api)
```
#### `get_current_customer_from_cookie_or_header()`
**Purpose:** Authenticate customer users for HTML pages
**Accepts:** Cookie (`customer_token`) OR Authorization header
**Returns:** `User` object with `role="customer"`
**Raises:**
- `InvalidTokenException` - No token or invalid token
- `InsufficientPermissionsException` - User is not customer (admin/vendor blocked)
**Usage:**
```python
current_user: User = Depends(get_current_customer_from_cookie_or_header)
```
#### `get_current_customer_api()`
**Purpose:** Authenticate customer users for API endpoints
**Accepts:** Authorization header ONLY
**Returns:** `User` object with `role="customer"`
**Raises:**
- `InvalidTokenException` - No token or invalid token
- `InsufficientPermissionsException` - User is not customer (admin/vendor blocked)
**Usage:**
```python
current_user: User = Depends(get_current_customer_api)
```
#### `get_current_user()`
**Purpose:** Authenticate any user (no role checking)
**Accepts:** Authorization header ONLY
**Returns:** `User` object (any role)
**Raises:**
- `InvalidTokenException` - No token or invalid token
**Usage:**
```python
current_user: User = Depends(get_current_user)
```
### Login Responses
All login endpoints return:
```python
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"token_type": "Bearer",
"expires_in": 3600,
"user": {
"id": 1,
"username": "admin",
"email": "admin@example.com",
"role": "admin",
"is_active": true
}
}
```
Additionally, the response sets an HTTP-only cookie:
- Admin: `admin_token` (path=/admin)
- Vendor: `vendor_token` (path=/vendor)
- Customer: `customer_token` (path=/shop)
---
## Security Model
### Role-Based Access Control Matrix
| User Role | Admin Portal | Vendor Portal | Shop Catalog | Customer Account |
|-----------|--------------|---------------|--------------|------------------|
| Admin | ✅ Full | ❌ Blocked | ✅ View | ❌ Blocked |
| Vendor | ❌ Blocked | ✅ Full | ✅ View | ❌ Blocked |
| Customer | ❌ Blocked | ❌ Blocked | ✅ View | ✅ Full |
| Anonymous | ❌ Blocked | ❌ Blocked | ✅ View | ❌ Blocked |
### Cookie Security Settings
All authentication cookies use the following security attributes:
```python
response.set_cookie(
key="<context>_token",
value=jwt_token,
httponly=True, # JavaScript cannot access (XSS protection)
secure=True, # HTTPS only in production
samesite="lax", # CSRF protection
max_age=3600, # Matches JWT expiry
path="/<context>" # Path restriction for isolation
)
```
### Token Validation
JWT tokens include:
- `sub` - User ID
- `role` - User role (admin/vendor/customer)
- `exp` - Expiration timestamp
- `iat` - Issued at timestamp
Tokens are validated on every request:
1. Extract token from cookie or header
2. Verify JWT signature
3. Check expiration
4. Load user from database
5. Verify user is active
6. Check role matches route requirements
### HTTPS Requirement
**Production Environment:**
- All cookies have `secure=True`
- HTTPS required for all authenticated routes
- HTTP requests automatically redirect to HTTPS
**Development Environment:**
- Cookies have `secure=False` for local testing
- HTTP allowed (http://localhost:8000)
---
## Testing Guidelines
### Manual Testing with Browser
#### Test Admin Authentication
1. **Navigate to admin login:**
```
http://localhost:8000/admin/login
```
2. **Login with admin credentials:**
- Username: `admin`
- Password: `admin123` (or your configured admin password)
3. **Verify cookie in DevTools:**
- Open DevTools → Application → Cookies
- Look for `admin_token` cookie
- Verify `Path` is `/admin`
- Verify `HttpOnly` is checked
- Verify `SameSite` is `Lax`
4. **Test navigation:**
- Navigate to `/admin/dashboard` - Should work ✅
- Navigate to `/vendor/TESTVENDOR/dashboard` - Should fail (cookie not sent) ❌
- Navigate to `/shop/account/dashboard` - Should fail (cookie not sent) ❌
5. **Logout:**
```
POST /api/v1/admin/auth/logout
```
#### Test Vendor Authentication
1. **Navigate to vendor login:**
```
http://localhost:8000/vendor/{VENDOR_CODE}/login
```
2. **Login with vendor credentials**
3. **Verify cookie in DevTools:**
- Look for `vendor_token` cookie
- Verify `Path` is `/vendor`
4. **Test navigation:**
- Navigate to `/vendor/{VENDOR_CODE}/dashboard` - Should work ✅
- Navigate to `/admin/dashboard` - Should fail ❌
- Navigate to `/shop/account/dashboard` - Should fail ❌
#### Test Customer Authentication
1. **Navigate to customer login:**
```
http://localhost:8000/shop/account/login
```
2. **Login with customer credentials**
3. **Verify cookie in DevTools:**
- Look for `customer_token` cookie
- Verify `Path` is `/shop`
4. **Test navigation:**
- Navigate to `/shop/account/dashboard` - Should work ✅
- Navigate to `/admin/dashboard` - Should fail ❌
- Navigate to `/vendor/{CODE}/dashboard` - Should fail ❌
### API Testing with curl
#### Test Admin API
```bash
# Login
curl -X POST http://localhost:8000/api/v1/admin/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin123"}'
# Save the access_token from response
# Test authenticated endpoint
curl http://localhost:8000/api/v1/admin/vendors \
-H "Authorization: Bearer <access_token>"
# Test cross-context blocking
curl http://localhost:8000/api/v1/vendor/TESTVENDOR/products \
-H "Authorization: Bearer <admin_access_token>"
# Should return 403 Forbidden
```
#### Test Vendor API
```bash
# Login
curl -X POST http://localhost:8000/api/v1/vendor/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"vendor","password":"vendor123"}'
# Test authenticated endpoint
curl http://localhost:8000/api/v1/vendor/TESTVENDOR/products \
-H "Authorization: Bearer <vendor_access_token>"
# Test cross-context blocking
curl http://localhost:8000/api/v1/admin/vendors \
-H "Authorization: Bearer <vendor_access_token>"
# Should return 403 Forbidden
```
#### Test Customer API
```bash
# Login
curl -X POST http://localhost:8000/api/v1/public/vendors/1/customers/login \
-H "Content-Type: application/json" \
-d '{"username":"customer","password":"customer123"}'
# Test authenticated endpoint with token
curl http://localhost:8000/api/v1/shop/orders \
-H "Authorization: Bearer <customer_access_token>"
# Test cross-context blocking
curl http://localhost:8000/api/v1/admin/vendors \
-H "Authorization: Bearer <customer_access_token>"
# Should return 403 Forbidden
```
### Frontend JavaScript Testing
#### Login and Store Token
```javascript
// Admin login
async function loginAdmin(username, password) {
const response = await fetch('/api/v1/admin/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await response.json();
// Cookie is set automatically
// Optionally store token for API calls
localStorage.setItem('admin_token', data.access_token);
// Redirect to dashboard
window.location.href = '/admin/dashboard';
}
```
#### Make API Call with Token
```javascript
// API call with token
async function fetchVendors() {
const token = localStorage.getItem('admin_token');
const response = await fetch('/api/v1/admin/vendors', {
headers: {
'Authorization': `Bearer ${token}`
}
});
return response.json();
}
```
#### Page Navigation (Cookie Automatic)
```javascript
// Just navigate - cookie sent automatically
window.location.href = '/admin/dashboard';
// Browser automatically includes admin_token cookie
```
### Automated Testing
#### Test Cookie Isolation
```python
import pytest
from fastapi.testclient import TestClient
def test_admin_cookie_not_sent_to_vendor_routes(client: TestClient):
# Login as admin
response = client.post('/api/v1/admin/auth/login', json={
'username': 'admin',
'password': 'admin123'
})
# Try to access vendor route (cookie should not be sent)
response = client.get('/vendor/TESTVENDOR/dashboard')
# Should redirect to login or return 401
assert response.status_code in [302, 401]
def test_vendor_token_blocked_from_admin_api(client: TestClient):
# Login as vendor
response = client.post('/api/v1/vendor/auth/login', json={
'username': 'vendor',
'password': 'vendor123'
})
vendor_token = response.json()['access_token']
# Try to access admin API with vendor token
response = client.get(
'/api/v1/admin/vendors',
headers={'Authorization': f'Bearer {vendor_token}'}
)
# Should return 403 Forbidden
assert response.status_code == 403
```
---
## Troubleshooting
### Common Issues
#### "Invalid token" error when navigating to pages
**Symptom:** User is logged in but gets "Invalid token" error
**Causes:**
- Token expired (default: 1 hour)
- Cookie was deleted
- Wrong cookie being sent
**Solution:**
- Check cookie expiration in DevTools
- Re-login to get fresh token
- Verify correct cookie exists with correct path
#### Cookie not being sent to endpoints
**Symptom:** API calls work with Authorization header but pages don't load
**Causes:**
- Cookie path mismatch
- Cookie expired
- Wrong domain
**Solution:**
- Verify cookie path matches route (e.g., `/admin` cookie for `/admin/*` routes)
- Check cookie expiration
- Ensure cookie domain matches current domain
#### "Admin cannot access vendor portal" error
**Symptom:** Admin user cannot access vendor routes
**Explanation:** This is intentional security design. Admins have their own portal at `/admin`. To manage vendors, use admin routes:
- View vendors: `/admin/vendors`
- Edit vendor: `/admin/vendors/{code}/edit`
Admins should not log into vendor portal as this violates security boundaries.
#### "Customer cannot access admin/vendor routes" error
**Symptom:** Customer trying to access management interfaces
**Explanation:** Customers only have access to:
- Public shop routes: `/shop/products`, etc.
- Their account: `/shop/account/*`
Admin and vendor portals are not accessible to customers.
#### Token works in Postman but not in browser
**Cause:** Postman uses Authorization header, browser uses cookies
**Solution:**
- For API testing: Use Authorization header
- For browser testing: Rely on cookies (automatic)
- For JavaScript API calls: Add Authorization header manually
### Debugging Tips
#### Check Cookie in Browser
```javascript
// In browser console
document.cookie.split(';').forEach(c => console.log(c.trim()));
```
#### Decode JWT Token
```javascript
// In browser console
function parseJwt(token) {
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(atob(base64).split('').map(c => {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return JSON.parse(jsonPayload);
}
const token = localStorage.getItem('admin_token');
console.log(parseJwt(token));
```
#### Check Server Logs
The authentication system logs all auth events:
```
INFO: Admin login successful: admin
INFO: Request: GET /admin/dashboard from 127.0.0.1
INFO: Response: 200 for GET /admin/dashboard (0.045s)
```
Look for:
- Login attempts
- Token validation errors
- Permission denials
---
## Best Practices
### For Developers
1. **Use the right dependency for the job:**
- HTML pages → `get_current_<context>_from_cookie_or_header`
- API endpoints → `get_current_<context>_api`
2. **Don't mix authentication contexts:**
- Admin users should use admin portal
- Vendor users should use vendor portal
- Customers should use shop
3. **Always check user.is_active:**
```python
if not current_user.is_active:
raise UserNotActiveException()
```
4. **Use type hints:**
```python
def my_route(current_user: User = Depends(get_current_admin_api)):
# IDE will have autocomplete for current_user
```
5. **Handle exceptions properly:**
```python
try:
# Your logic
except InvalidTokenException:
# Handle auth failure
except InsufficientPermissionsException:
# Handle permission denial
```
### For Frontend
1. **Store tokens securely:**
- Tokens in localStorage/sessionStorage are vulnerable to XSS
- Prefer using cookies for page navigation
- Only use localStorage for explicit API calls
2. **Always send Authorization header for API calls:**
```javascript
const token = localStorage.getItem('token');
fetch('/api/v1/admin/vendors', {
headers: { 'Authorization': `Bearer ${token}` }
});
```
3. **Handle 401/403 responses:**
```javascript
if (response.status === 401) {
// Redirect to login
window.location.href = '/admin/login';
}
```
4. **Clear tokens on logout:**
```javascript
localStorage.removeItem('token');
// Logout endpoint will clear cookie
await fetch('/api/v1/admin/auth/logout', { method: 'POST' });
```
### Security Considerations
1. **Never log tokens** - They're sensitive credentials
2. **Use HTTPS in production** - Required for secure cookies
3. **Set appropriate token expiration** - Balance security vs UX
4. **Rotate secrets regularly** - JWT signing keys
5. **Monitor failed auth attempts** - Detect brute force attacks
---
## Configuration
### Environment Variables
```bash
# JWT Configuration
JWT_SECRET_KEY=your-secret-key-here
JWT_ALGORITHM=HS256
JWT_EXPIRATION=3600 # 1 hour in seconds
# Environment
ENVIRONMENT=production # or development
# When ENVIRONMENT=production:
# - Cookies use secure=True (HTTPS only)
# - Debug mode is disabled
# - CORS is stricter
```
### Cookie Expiration
Cookies expire when:
1. JWT token expires (default: 1 hour)
2. User logs out (cookie deleted)
3. Browser session ends (for session cookies)
To change expiration:
```python
# In auth endpoint
response.set_cookie(
max_age=7200 # 2 hours
)
```
---
## Support
For questions or issues:
1. Check this documentation first
2. Review server logs for error messages
3. Test with curl to isolate frontend issues
4. Check browser DevTools for cookie issues
5. Contact the backend team
---
## Changelog
### Version 1.0 (November 2024)
- Initial authentication system implementation
- Three-context isolation (admin, vendor, customer)
- Dual authentication support (cookie + header)
- Complete role-based access control
- Comprehensive logging
---
**End of Documentation**

File diff suppressed because it is too large Load Diff

View File

@@ -1,562 +0,0 @@
# Alpine.js Page Template - Quick Reference (WITH CENTRALIZED LOGGING)
## ✅ Correct Page Structure
```javascript
// static/admin/js/your-page.js (or vendor/shop)
// 1. ✅ Use centralized logger (ONE LINE!)
const yourPageLog = window.LogConfig.loggers.yourPage;
// OR create custom if not pre-configured
// const yourPageLog = window.LogConfig.createLogger('YOUR-PAGE', window.LogConfig.logLevel);
// 2. Create your Alpine.js component
function yourPageComponent() {
return {
// ✅ CRITICAL: Inherit base layout functionality
...data(),
// ✅ CRITICAL: Set page identifier
currentPage: 'your-page',
// Your page-specific state
items: [],
loading: false,
error: null,
// ✅ CRITICAL: Proper initialization with guard
async init() {
yourPageLog.info('=== YOUR PAGE INITIALIZING ===');
// Prevent multiple initializations
if (window._yourPageInitialized) {
yourPageLog.warn('Page already initialized, skipping...');
return;
}
window._yourPageInitialized = true;
// Load your data
await this.loadData();
yourPageLog.info('=== YOUR PAGE INITIALIZATION COMPLETE ===');
},
// Your methods
async loadData() {
yourPageLog.info('Loading data...');
this.loading = true;
this.error = null;
try {
const startTime = performance.now();
// Log API request
const url = '/your/endpoint';
window.LogConfig.logApiCall('GET', url, null, 'request');
// ✅ CRITICAL: Use lowercase apiClient
const response = await apiClient.get(url);
// Log API response
window.LogConfig.logApiCall('GET', url, response, 'response');
this.items = response.items || [];
// Log performance
const duration = performance.now() - startTime;
window.LogConfig.logPerformance('Load Data', duration);
yourPageLog.info(`Data loaded successfully`, {
count: this.items.length,
duration: `${duration}ms`
});
} catch (error) {
// Use centralized error logging
window.LogConfig.logError(error, 'Load Data');
this.error = error.message;
Utils.showToast('Failed to load data', 'error');
} finally {
this.loading = false;
}
},
// Format date helper (if needed)
formatDate(dateString) {
if (!dateString) return '-';
return Utils.formatDate(dateString);
},
// Your other methods...
};
}
yourPageLog.info('Your page module loaded');
```
---
## 🎯 Checklist for New Pages
### HTML Template
```jinja2
{# app/templates/admin/your-page.html #}
{% extends "admin/base.html" %}
{% block title %}Your Page{% endblock %}
{# ✅ CRITICAL: Link to your Alpine.js component #}
{% block alpine_data %}yourPageComponent(){% endblock %}
{% block content %}
{% endblock %}
{% block extra_scripts %}
{# ✅ CRITICAL: Load your JavaScript file #}
{% endblock %}
```
### JavaScript File Checklist
- [ ] ✅ Use centralized logger (ONE line instead of 15!)
- [ ] ✅ Function name matches `alpine_data` in template
- [ ]`...data(),` at start of return object
- [ ]`currentPage: 'your-page'` set
- [ ] ✅ Initialization guard in `init()`
- [ ] ✅ Use lowercase `apiClient` for API calls
- [ ] ✅ Use `window.LogConfig.logApiCall()` for API logging
- [ ] ✅ Use `window.LogConfig.logPerformance()` for performance
- [ ] ✅ Use `window.LogConfig.logError()` for errors
- [ ] ✅ Module loaded log at end
---
## 📦 Pre-configured Loggers by Frontend
### Admin Frontend
```javascript
window.LogConfig.loggers.vendors // Vendor management
window.LogConfig.loggers.vendorTheme // Theme customization
window.LogConfig.loggers.vendorUsers // Vendor users
window.LogConfig.loggers.products // Product management
window.LogConfig.loggers.inventory // Inventory
window.LogConfig.loggers.orders // Order management
window.LogConfig.loggers.users // User management
window.LogConfig.loggers.audit // Audit logs
window.LogConfig.loggers.dashboard // Dashboard
window.LogConfig.loggers.imports // Import operations
```
### Vendor Frontend
```javascript
window.LogConfig.loggers.dashboard // Vendor dashboard
window.LogConfig.loggers.products // Product management
window.LogConfig.loggers.inventory // Inventory
window.LogConfig.loggers.orders // Order management
window.LogConfig.loggers.theme // Theme customization
window.LogConfig.loggers.settings // Settings
window.LogConfig.loggers.analytics // Analytics
```
### Shop Frontend
```javascript
window.LogConfig.loggers.catalog // Product browsing
window.LogConfig.loggers.product // Product details
window.LogConfig.loggers.search // Search
window.LogConfig.loggers.cart // Shopping cart
window.LogConfig.loggers.checkout // Checkout
window.LogConfig.loggers.account // User account
window.LogConfig.loggers.orders // Order history
window.LogConfig.loggers.wishlist // Wishlist
```
---
## ❌ Common Mistakes to Avoid
### 1. Old Way vs New Way
```javascript
// ❌ OLD WAY - 15 lines of duplicate code
const YOUR_PAGE_LOG_LEVEL = 3;
const yourPageLog = {
error: (...args) => YOUR_PAGE_LOG_LEVEL >= 1 && console.error('❌ [YOUR_PAGE ERROR]', ...args),
warn: (...args) => YOUR_PAGE_LOG_LEVEL >= 2 && console.warn('⚠️ [YOUR_PAGE WARN]', ...args),
info: (...args) => YOUR_PAGE_LOG_LEVEL >= 3 && console.info(' [YOUR_PAGE INFO]', ...args),
debug: (...args) => YOUR_PAGE_LOG_LEVEL >= 4 && console.log('🔍 [YOUR_PAGE DEBUG]', ...args)
};
// ✅ NEW WAY - 1 line!
const yourPageLog = window.LogConfig.loggers.yourPage;
```
### 2. Missing Base Inheritance
```javascript
// ❌ WRONG
function myPage() {
return {
items: [],
// Missing ...data()
};
}
// ✅ CORRECT
function myPage() {
return {
...data(), // Must be first!
items: [],
};
}
```
### 3. Wrong API Client Name
```javascript
// ❌ WRONG - Capital letters
await ApiClient.get('/endpoint');
await API_CLIENT.get('/endpoint');
// ✅ CORRECT - lowercase
await apiClient.get('/endpoint');
```
### 4. Missing Init Guard
```javascript
// ❌ WRONG
async init() {
await this.loadData();
}
// ✅ CORRECT
async init() {
if (window._myPageInitialized) return;
window._myPageInitialized = true;
await this.loadData();
}
```
### 5. Missing currentPage
```javascript
// ❌ WRONG
return {
...data(),
items: [],
// Missing currentPage
};
// ✅ CORRECT
return {
...data(),
currentPage: 'my-page', // Must set this!
items: [],
};
```
---
## 🔧 API Client Pattern
### GET Request
```javascript
try {
const response = await apiClient.get('/endpoint');
this.data = response;
} catch (error) {
console.error('Failed:', error);
Utils.showToast('Failed to load', 'error');
}
```
### POST Request
```javascript
try {
const response = await apiClient.post('/endpoint', {
name: 'value',
// ... data
});
Utils.showToast('Created successfully', 'success');
} catch (error) {
console.error('Failed:', error);
Utils.showToast('Failed to create', 'error');
}
```
### PUT Request
```javascript
try {
const response = await apiClient.put('/endpoint/123', {
name: 'updated value'
});
Utils.showToast('Updated successfully', 'success');
} catch (error) {
console.error('Failed:', error);
Utils.showToast('Failed to update', 'error');
}
```
### DELETE Request
```javascript
try {
await apiClient.delete('/endpoint/123');
Utils.showToast('Deleted successfully', 'success');
await this.reloadData();
} catch (error) {
console.error('Failed:', error);
Utils.showToast('Failed to delete', 'error');
}
```
---
## 🔧 Centralized Logging Patterns
### Basic Logging
```javascript
const log = window.LogConfig.loggers.yourPage;
log.info('Page loaded');
log.warn('Connection slow');
log.error('Failed to load data', error);
log.debug('User data:', userData);
```
### Grouped Logging
```javascript
log.group('Loading Theme Data');
log.info('Fetching vendor...');
log.info('Fetching theme...');
log.info('Fetching presets...');
log.groupEnd();
```
### API Call Logging
```javascript
const url = '/api/vendors';
// Before request
window.LogConfig.logApiCall('GET', url, null, 'request');
// Make request
const data = await apiClient.get(url);
// After response
window.LogConfig.logApiCall('GET', url, data, 'response');
```
### Error Logging
```javascript
try {
await saveTheme();
} catch (error) {
window.LogConfig.logError(error, 'Save Theme');
}
```
### Performance Logging
```javascript
const start = performance.now();
await loadThemeData();
const duration = performance.now() - start;
window.LogConfig.logPerformance('Load Theme Data', duration);
```
### Table Logging
```javascript
log.table([
{ id: 1, name: 'Vendor A', status: 'active' },
{ id: 2, name: 'Vendor B', status: 'inactive' }
]);
```
---
## 📚 Benefits of Centralized Logging
| Aspect | Old Way | New Way |
|--------|---------|---------|
| **Lines of code** | ~15 per file | 1 line per file |
| **Consistency** | Varies by file | Unified across all frontends |
| **Maintenance** | Update each file | Update one shared file |
| **Features** | Basic logging | Advanced (groups, perf, API) |
| **Environment** | Manual config | Auto-detected |
| **Frontend aware** | No | Yes (admin/vendor/shop) |
| **Log levels** | Per file | Per frontend + environment |
---
## 🎨 Common UI Patterns
### Loading State
```javascript
async loadData() {
this.loading = true;
try {
const data = await apiClient.get('/endpoint');
this.items = data;
} finally {
this.loading = false;
}
}
```
### Refresh/Reload
```javascript
async refresh() {
console.info('Refreshing...');
await this.loadData();
Utils.showToast('Refreshed successfully', 'success');
}
```
---
## 📚 Available Utilities
### From `init-alpine.js` (via `...data()`)
- `this.dark` - Dark mode state
- `this.toggleTheme()` - Toggle theme
- `this.isSideMenuOpen` - Side menu state
- `this.toggleSideMenu()` - Toggle side menu
- `this.closeSideMenu()` - Close side menu
- `this.isNotificationsMenuOpen` - Notifications menu state
- `this.toggleNotificationsMenu()` - Toggle notifications
- `this.closeNotificationsMenu()` - Close notifications
- `this.isProfileMenuOpen` - Profile menu state
- `this.toggleProfileMenu()` - Toggle profile menu
- `this.closeProfileMenu()` - Close profile menu
- `this.isPagesMenuOpen` - Pages menu state
- `this.togglePagesMenu()` - Toggle pages menu
### From `Utils` (global)
- `Utils.showToast(message, type, duration)` - Show toast notification
- `Utils.formatDate(dateString)` - Format date for display
- `Utils.confirm(message, title)` - Show confirmation dialog (if available)
### From `apiClient` (global)
- `apiClient.get(url)` - GET request
- `apiClient.post(url, data)` - POST request
- `apiClient.put(url, data)` - PUT request
- `apiClient.delete(url)` - DELETE request
---
## 🎨 Complete Example
```javascript
// static/admin/js/vendor-theme.js
// 1. Use centralized logger
const themeLog = window.LogConfig.loggers.vendorTheme;
// 2. Create component
function adminVendorTheme() {
return {
...data(),
currentPage: 'vendor-theme',
vendor: null,
themeData: {},
loading: true,
async init() {
themeLog.info('Initializing vendor theme editor');
if (window._vendorThemeInitialized) {
themeLog.warn('Already initialized, skipping...');
return;
}
window._vendorThemeInitialized = true;
const startTime = performance.now();
try {
themeLog.group('Loading theme data');
await Promise.all([
this.loadVendor(),
this.loadTheme()
]);
themeLog.groupEnd();
const duration = performance.now() - startTime;
window.LogConfig.logPerformance('Theme Editor Init', duration);
themeLog.info('Theme editor initialized successfully');
} catch (error) {
window.LogConfig.logError(error, 'Theme Editor Init');
Utils.showToast('Failed to initialize', 'error');
} finally {
this.loading = false;
}
},
async loadVendor() {
const url = `/admin/vendors/${this.vendorCode}`;
window.LogConfig.logApiCall('GET', url, null, 'request');
const response = await apiClient.get(url);
this.vendor = response;
window.LogConfig.logApiCall('GET', url, response, 'response');
themeLog.debug('Vendor loaded:', this.vendor);
},
async saveTheme() {
themeLog.info('Saving theme changes');
try {
const url = `/admin/vendor-themes/${this.vendorCode}`;
window.LogConfig.logApiCall('PUT', url, this.themeData, 'request');
const response = await apiClient.put(url, this.themeData);
window.LogConfig.logApiCall('PUT', url, response, 'response');
themeLog.info('Theme saved successfully');
Utils.showToast('Theme saved', 'success');
} catch (error) {
window.LogConfig.logError(error, 'Save Theme');
Utils.showToast('Failed to save theme', 'error');
}
}
};
}
themeLog.info('Vendor theme editor module loaded');
```
---
## 🚀 Quick Start Template Files
Copy these to create a new page:
1. **Copy base file:** `dashboard.js` → rename to `your-page.js`
2. **Update logger:**
```javascript
// Change from:
const dashLog = window.LogConfig.loggers.dashboard;
// To:
const yourPageLog = window.LogConfig.loggers.yourPage;
// Or create custom:
const yourPageLog = window.LogConfig.createLogger('YOUR-PAGE');
```
3. **Replace function name:** `adminDashboard()` → `yourPageComponent()`
4. **Update init flag:** `_dashboardInitialized` → `_yourPageInitialized`
5. **Update page identifier:** `currentPage: 'dashboard'` → `currentPage: 'your-page'`
6. **Replace data loading logic** with your API endpoints
7. **Update HTML template** to use your function name:
```jinja2
{% block alpine_data %}yourPageComponent(){% endblock %}
```
8. **Load your script** in the template:
```jinja2
{% block extra_scripts %}
<script src="{{ url_for('static', path='admin/js/your-page.js') }}"></script>
{% endblock %}
```
Done! ✅

View File

@@ -1,239 +0,0 @@
╔══════════════════════════════════════════════════════════════════╗
║ ALPINE.JS PAGE ARCHITECTURE OVERVIEW ║
╚══════════════════════════════════════════════════════════════════╝
📂 FILE STRUCTURE
═════════════════════════════════════════════════════════════════
static/admin/js/
├── init-alpine.js ............. Base Alpine.js data & theme
├── dashboard.js ............... Dashboard page (✅ WORKING)
├── vendors.js ................. Vendor list page (✅ FIXED)
└── vendor-edit.js ............. Vendor edit page (✅ FIXED)
🔄 HOW PAGES INHERIT BASE FUNCTIONALITY
═════════════════════════════════════════════════════════════════
┌─────────────────────────────────────────────────────────────┐
│ init-alpine.js │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ function data() { │ │
│ │ return { │ │
│ │ dark: ..., ← Theme state │ │
│ │ toggleTheme() {...}, ← Theme toggle │ │
│ │ isSideMenuOpen: false, ← Side menu state │ │
│ │ toggleSideMenu() {...}, ← Side menu toggle │ │
│ │ isProfileMenuOpen: false, ← Profile menu state │ │
│ │ toggleProfileMenu() {...}, ← Profile menu toggle │ │
│ │ currentPage: '' ← Page identifier │ │
│ │ }; │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│ Uses ...data() spread operator
┌─────────────────────┼─────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ dashboard.js │ │ vendors.js │ │vendor-edit.js │
├───────────────┤ ├───────────────┤ ├───────────────┤
│function admin │ │function admin │ │function admin │
│Dashboard() { │ │Vendors() { │ │VendorEdit() { │
│ return { │ │ return { │ │ return { │
│ ...data(), │ │ ...data(), │ │ ...data(), │
│ │ │ │ │ │ │ │ │
│ └──────────┼───┼────┘ │ │ │ │
│ Inherits │ │ Inherits │ │ Inherits │
│ all base │ │ all base │ │ all base │
│ functions │ │ functions │ │ functions │
│ │ │ │ │ │
│ // Page │ │ // Page │ │ // Page │
│ specific │ │ specific │ │ specific │
│ state │ │ state │ │ state │
│ }; │ │ }; │ │ }; │
│} │ │} │ │} │
└───────────────┘ └───────────────┘ └───────────────┘
⚙️ API CLIENT USAGE PATTERN
═════════════════════════════════════════════════════════════════
All pages must use lowercase 'apiClient':
┌─────────────────────────────────────────────────────────────┐
│ ✅ CORRECT ❌ WRONG │
├─────────────────────────────────────────────────────────────┤
│ apiClient.get(url) │ ApiClient.get(url) │
│ apiClient.post(url, data) │ API_CLIENT.post(url, data) │
│ apiClient.put(url, data) │ Apiclient.put(url, data) │
│ apiClient.delete(url) │ APIClient.delete(url) │
└─────────────────────────────────────────────────────────────┘
🪵 LOGGING PATTERN
═════════════════════════════════════════════════════════════════
Each page has its own logger object:
┌─────────────────────────────────────────────────────────────┐
│ dashboard.js vendors.js vendor-edit.js │
├─────────────────────────────────────────────────────────────┤
│ const dashLog = { const vendorsLog = const editLog = { │
│ error: (...) => error: (...) => error: (...) => │
│ warn: (...) => warn: (...) => warn: (...) => │
│ info: (...) => info: (...) => info: (...) => │
│ debug: (...) => debug: (...) => debug: (...) => │
│ }; }; }; │
│ │
│ dashLog.info('...') vendorsLog.info() editLog.info() │
└─────────────────────────────────────────────────────────────┘
🔒 INITIALIZATION GUARD PATTERN
═════════════════════════════════════════════════════════════════
Prevents multiple Alpine.js initializations:
┌─────────────────────────────────────────────────────────────┐
│ async init() { │
│ // Check if already initialized │
│ if (window._yourPageInitialized) { │
│ log.warn('Already initialized, skipping...'); │
│ return; // Exit early │
│ } │
│ window._yourPageInitialized = true; // Set flag │
│ │
│ // Continue with initialization │
│ await this.loadData(); │
│ } │
└─────────────────────────────────────────────────────────────┘
📊 STATE MANAGEMENT
═════════════════════════════════════════════════════════════════
Alpine.js reactive state structure:
┌─────────────────────────────────────────────────────────────┐
│ function yourPage() { │
│ return { │
│ ...data(), ← Base UI state (inherited) │
│ currentPage: 'name', ← Page identifier │
│ │
│ // Loading states │
│ loading: false, ← General loading │
│ loadingItem: false, ← Specific item loading │
│ saving: false, ← Save operation state │
│ │
│ // Data │
│ items: [], ← List data │
│ item: null, ← Single item │
│ stats: {}, ← Statistics │
│ │
│ // Error handling │
│ error: null, ← Error message │
│ errors: {}, ← Field-specific errors │
│ │
│ // Methods │
│ async init() {...}, ← Initialization │
│ async loadData() {...}, ← Data loading │
│ async save() {...}, ← Save operation │
│ formatDate(d) {...} ← Helper functions │
│ }; │
│ } │
└─────────────────────────────────────────────────────────────┘
🎯 TEMPLATE BINDING
═════════════════════════════════════════════════════════════════
HTML template connects to Alpine.js component:
┌─────────────────────────────────────────────────────────────┐
│ vendor-edit.html │
├─────────────────────────────────────────────────────────────┤
│ {% extends "admin/base.html" %} │
│ │
│ {# This binds to the JavaScript function #} │
│ {% block alpine_data %}adminVendorEdit(){% endblock %} │
│ └──────────────────┐ │
│ {% block content %} │ │
│ <div x-show="loading">Loading...</div> │ │
│ <div x-show="!loading"> │ │
│ <p x-text="vendor.name"></p> ← Reactive binding │ │
│ </div> │ │
│ {% endblock %} │ │
│ │ │
│ {% block extra_scripts %} │ │
│ <script src="...vendor-edit.js"></script> ──────────┐ │ │
│ {% endblock %} │ │ │
└───────────────────────────────────────────────────────│──│─┘
│ │
│ │
┌───────────────────────────────────────────────────────│──│─┐
│ vendor-edit.js │ │ │
├───────────────────────────────────────────────────────│──│─┤
│ function adminVendorEdit() { ◄────────────────────────┘ │ │
│ return { │ │
│ ...data(), │ │
│ vendor: null, ← Bound to x-text="vendor.name"│ │
│ loading: false, ← Bound to x-show="loading" │ │
│ async init() {...} │ │
│ }; │ │
│ } │ │
└──────────────────────────────────────────────────────────┘
🔄 PAGE LIFECYCLE
═════════════════════════════════════════════════════════════════
1. Page Load
2. Alpine.js Initialization
3. x-data="yourPageComponent()" called
4. Component function executes
5. ...data() spreads base state
6. Page-specific state added
7. init() method runs
8. Check initialization guard
9. Load data from API
10. Reactive bindings update UI
✅ CHECKLIST FOR NEW PAGES
═════════════════════════════════════════════════════════════════
JavaScript File:
□ Create logger object (pageLog)
□ Define component function
□ Add ...data() at start of return object
□ Set currentPage: 'page-name'
□ Add initialization guard
□ Use lowercase apiClient for API calls
□ Add performance tracking (optional)
□ Use page-specific logger
HTML Template:
□ Extend admin/base.html
□ Set alpine_data block with function name
□ Add x-show for loading states
□ Add x-text for reactive data
□ Load JavaScript file in extra_scripts block
══════════════════════════════════════════════════════════════════
Your dashboard.js is perfect!
Use it as the template for all new pages.
══════════════════════════════════════════════════════════════════

View File

@@ -627,7 +627,7 @@ Location: app/exceptions/
Exception Hierarchy:
────────────────────────────────────────────────────────────────
LetzShopException (base)
WizamartException (base)
├── ValidationException (422)
├── AuthenticationException (401)
├── AuthorizationException (403)
@@ -693,7 +693,7 @@ Global Exception Handler:
Location: app/exceptions/handler.py
Handles:
LetzShopException → Custom JSON response
WizamartException → Custom JSON response
• HTTPException → Formatted JSON response
• RequestValidationError → Cleaned validation errors
• Exception → Generic 500 error

View File

@@ -0,0 +1,315 @@
# Error Handling System - Flow Diagram
## Request Processing Flow
```
┌─────────────────────────────────────────────────────────────────┐
│ Incoming HTTP Request │
└─────────────────────────────────┬───────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Vendor Context Middleware (FIRST) │
│ - Detects vendor from domain/subdomain/path │
│ - Sets request.state.vendor │
│ - Sets request.state.vendor_context │
└─────────────────────────────────┬───────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Context Detection Middleware (NEW) │
│ - Detects request context type │
│ - Sets request.state.context_type │
│ • API, ADMIN, VENDOR_DASHBOARD, SHOP, FALLBACK │
└─────────────────────────────────┬───────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Route Handler Execution │
│ - Process business logic │
│ - May throw exceptions │
└─────────────────────────────────┬───────────────────────────────┘
┌─────────────┴─────────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Success │ │ Exception │
│ Response │ │ Raised │
└──────────────┘ └──────┬───────┘
┌────────────────────────────────────────────────────────┐
│ Exception Handler │
│ - WizamartException │
│ - HTTPException │
│ - RequestValidationError │
│ - Generic Exception │
│ - 404 Not Found │
└────────────────────┬───────────────────────────────────┘
┌─────────────┴────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ Special Case: │ │ General Error │
│ 401 on HTML │ │ Handling │
│ → Redirect to │ │ │
│ Login │ │ │
└──────────────────┘ └─────────┬────────┘
┌─────────────┴─────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ API Request? │ │ HTML Request? │
│ /api/* path │ │ GET + text/html │
└────────┬─────────┘ └─────────┬────────┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────────────┐
│ Return JSON │ │ Error Page Renderer │
│ Response │ │ │
│ { │ │ - Detect context type │
│ error_code, │ │ - Select template │
│ message, │ │ - Prepare data │
│ status_code │ │ - Render HTML │
│ } │ └──────────┬───────────────┘
└──────────────────┘ │
┌──────────────────────────────┐
│ Template Selection │
│ │
│ Priority: │
│ 1. {context}/errors/{code} │
│ 2. {context}/errors/generic│
│ 3. fallback/{code}.html │
│ 4. fallback/generic.html │
└──────────┬───────────────────┘
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Admin │ │ Vendor │ │ Shop │
│ Error │ │ Error │ │ Error │
│ Page │ │ Page │ │ Page │
│ │ │ │ │ (Themed) │
└─────────────┘ └─────────────┘ └─────────────┘
```
---
## Context Detection Logic
```
┌──────────────────────────────────────────────────────────────────┐
│ Request Arrives │
│ (host + path) │
└────────────────────────────┬─────────────────────────────────────┘
┌──────────────────┐
│ Path starts │───YES───→ API Context
│ with /api/ ? │ (Always JSON)
└────────┬─────────┘
│ NO
┌──────────────────┐
│ Host starts │───YES───→ ADMIN Context
│ with admin. │ (Admin Portal)
│ OR path starts │
│ with /admin ? │
└────────┬─────────┘
│ NO
┌──────────────────┐
│ Path starts │───YES───→ VENDOR_DASHBOARD Context
│ with /vendor/ ? │ (Vendor Management)
└────────┬─────────┘
│ NO
┌──────────────────┐
│ Vendor object │───YES───→ SHOP Context
│ in request │ (Customer Storefront)
│ state? │
└────────┬─────────┘
│ NO
┌──────────────────┐
│ FALLBACK │
│ Context │
│ (Unknown) │
└──────────────────┘
```
---
## Template Selection Example
For a 404 error in ADMIN context:
```
1. Check: app/templates/admin/errors/404.html ✓ EXISTS → USE THIS
2. Check: app/templates/admin/errors/generic.html (skipped)
3. Check: app/templates/fallback/404.html (skipped)
4. Check: app/templates/fallback/generic.html (skipped)
```
For a 429 error in SHOP context (not created yet):
```
1. Check: app/templates/shop/errors/429.html ✗ Missing
2. Check: app/templates/shop/errors/generic.html ✗ Missing
3. Check: app/templates/fallback/429.html ✗ Missing
4. Check: app/templates/fallback/generic.html ✓ EXISTS → USE THIS
```
---
## Error Response Types
```
┌──────────────────────────────────────────────────────────────────┐
│ Request Type │
└───────────┬─────────────┬───────────────────┬─────────────────────┘
│ │ │
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌──────────────┐
│ API │ │ HTML Page │ │ 401 on HTML │
│ Request │ │ Request │ │ Page │
└──────┬─────┘ └──────┬─────┘ └──────┬───────┘
│ │ │
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌──────────────┐
│ JSON │ │ Rendered │ │ Redirect │
│ Response │ │ HTML │ │ to Login │
│ │ │ Error │ │ │
│ { │ │ Page │ │ 302 Found │
│ error_code│ │ │ │ Location: │
│ message │ │ Context- │ │ /admin/login│
│ status │ │ aware │ │ /vendor/login│
│ details │ │ template │ │ /shop/login │
│ } │ │ │ │ │
└────────────┘ └────────────┘ └──────────────┘
```
---
## Example Scenarios
### Scenario 1: API 404 Error
```
Request: GET /api/v1/admin/vendors/999
Context: API
Result: JSON { "error_code": "VENDOR_NOT_FOUND", ... }
```
### Scenario 2: Admin Page 404 Error
```
Request: GET /admin/nonexistent
Accept: text/html
Context: ADMIN
Result: HTML admin/errors/404.html
```
### Scenario 3: Shop Page 500 Error
```
Request: GET /products/123 (on vendor1.platform.com)
Accept: text/html
Context: SHOP (vendor detected)
Result: HTML shop/errors/500.html (with vendor theme)
```
### Scenario 4: Unauthorized Access to Admin
```
Request: GET /admin/settings
Accept: text/html
No valid session
Context: ADMIN
Result: 302 Redirect to /admin/login
```
---
## Debug Information Display
```
┌──────────────────────────────────────────────────────────────┐
│ Error Page Display │
└──────────────────────┬───────────────────────────────────────┘
┌───────────┴───────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Admin User │ │ Other Users │
│ (Context: │ │ (Vendor, │
│ ADMIN) │ │ Shop) │
└──────┬───────┘ └──────┬───────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Debug Info │ │ No Debug │
│ SHOWN: │ │ Info: │
│ • Path │ │ │
│ • Error code │ │ Only user- │
│ • Details │ │ friendly │
│ • Stack │ │ message │
└──────────────┘ └──────────────┘
```
---
## File Organization
```
app/
├── exceptions/
│ ├── handler.py # Exception handlers (refactored)
│ ├── error_renderer.py # NEW: Renders error pages
│ └── base.py # Base exceptions
├── templates/
│ ├── admin/
│ │ └── errors/ # NEW: Admin error pages
│ │ ├── base.html # Base template
│ │ ├── 404.html # Specific errors
│ │ └── generic.html # Catch-all
│ │
│ ├── vendor/
│ │ └── errors/ # TODO: Vendor error pages
│ │
│ ├── shop/
│ │ └── errors/ # TODO: Shop error pages (themed)
│ │
│ └── fallback/
│ └── (minimal pages) # NEW: Unknown context fallback
middleware/
├── vendor_context.py # Vendor detection (existing)
├── context_middleware.py # NEW: Context detection
└── theme_context.py # Theme loading (existing)
```
---
## Benefits Summary
**Separation of Concerns**: HTML templates separate from handler logic
**Context-Aware**: Different error pages for different areas
**Maintainable**: Easy to update individual error pages
**Scalable**: Easy to add new contexts or error types
**Professional**: Polished error pages matching area design
**Flexible**: Fallback mechanism ensures errors always render
**Secure**: Debug info only shown to admins
**Themed**: Shop errors can use vendor branding (Phase 3)
---
This flow ensures that:
1. API calls ALWAYS get JSON responses
2. HTML page requests get appropriate error pages
3. Each context (admin/vendor/shop) has its own error design
4. Fallback mechanism prevents broken error pages
5. 401 errors redirect to appropriate login pages