fix: customer authentication and shop error page styling
## Customer Authentication Fixes - Fix get_current_customer_api to properly decode customer tokens (was using User model) - Add _validate_customer_token() helper for shared customer token validation - Add vendor validation: token.vendor_id must match request URL vendor - Block admin/vendor tokens from shop endpoints (type != "customer") - Update get_current_customer_optional to use proper customer token validation - Customer auth functions now return Customer object (not User) ## Shop Orders API - Update orders.py to receive Customer directly from auth dependency - Remove broken get_customer_from_user() helper - Use VendorNotFoundException instead of HTTPException ## Shop Error Pages - Fix all error templates (400, 401, 403, 404, 422, 429, 500, 502, generic) - Templates were using undefined CSS classes (.btn, .status-code, etc.) - Now properly extend base.html and override specific blocks - Use Tailwind utility classes for consistent styling ## Documentation - Update docs/api/authentication.md with new Customer return types - Document vendor validation security features - Update docs/api/authentication-quick-reference.md examples 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -467,30 +467,51 @@ current_user: User = Depends(get_current_vendor_api)
|
||||
|
||||
**Purpose:** Authenticate customer users for HTML pages
|
||||
**Accepts:** Cookie (`customer_token`) OR Authorization header
|
||||
**Returns:** `Customer` object
|
||||
**Returns:** `Customer` object (from `models.database.customer.Customer`)
|
||||
**Raises:**
|
||||
- `InvalidTokenException` - No token or invalid token
|
||||
- `InsufficientPermissionsException` - User is not customer (admin/vendor blocked)
|
||||
- `InvalidTokenException` - No token, invalid token, or not a customer token
|
||||
- `UnauthorizedVendorAccessException` - Token vendor_id doesn't match URL vendor
|
||||
|
||||
**Note:** The `InsufficientPermissionsException` raised here is from `app.exceptions.auth`, which provides general authentication permission checking. This is distinct from `InsufficientTeamPermissionsException` used for team-specific permissions.
|
||||
**Security Features:**
|
||||
- **Token type validation:** Only accepts tokens with `type: "customer"` - admin and vendor tokens are rejected
|
||||
- **Vendor validation:** Validates that `token.vendor_id` matches `request.state.vendor.id` (URL-based vendor)
|
||||
- Prevents cross-vendor token reuse (customer from Vendor A cannot use token on Vendor B's shop)
|
||||
|
||||
**Usage:**
|
||||
```python
|
||||
current_customer: Customer = Depends(get_current_customer_from_cookie_or_header)
|
||||
from models.database.customer import Customer
|
||||
|
||||
customer: Customer = Depends(get_current_customer_from_cookie_or_header)
|
||||
# Access customer.id, customer.email, customer.vendor_id, etc.
|
||||
```
|
||||
|
||||
#### `get_current_customer_api()`
|
||||
|
||||
**Purpose:** Authenticate customer users for API endpoints
|
||||
**Accepts:** Authorization header ONLY
|
||||
**Returns:** `Customer` object
|
||||
**Returns:** `Customer` object (from `models.database.customer.Customer`)
|
||||
**Raises:**
|
||||
- `InvalidTokenException` - No token or invalid token
|
||||
- `InsufficientPermissionsException` - User is not customer (admin/vendor blocked)
|
||||
- `InvalidTokenException` - No token, invalid token, or not a customer token
|
||||
- `UnauthorizedVendorAccessException` - Token vendor_id doesn't match URL vendor
|
||||
|
||||
**Security Features:**
|
||||
- **Token type validation:** Only accepts tokens with `type: "customer"` - admin and vendor tokens are rejected
|
||||
- **Vendor validation:** Validates that `token.vendor_id` matches `request.state.vendor.id` (URL-based vendor)
|
||||
- Prevents cross-vendor token reuse
|
||||
|
||||
**Usage:**
|
||||
```python
|
||||
current_customer: Customer = Depends(get_current_customer_api)
|
||||
from models.database.customer import Customer
|
||||
|
||||
@router.post("/orders")
|
||||
def place_order(
|
||||
request: Request,
|
||||
customer: Customer = Depends(get_current_customer_api),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
# customer is a Customer object, not User
|
||||
vendor = request.state.vendor # Already validated to match token
|
||||
order = order_service.create_order(db, vendor.id, customer.id, ...)
|
||||
```
|
||||
|
||||
#### `get_current_user()`
|
||||
@@ -581,20 +602,26 @@ async def vendor_login_page(
|
||||
**Purpose:** Check if customer user is authenticated (without enforcing)
|
||||
**Accepts:** Cookie (`customer_token`) OR Authorization header
|
||||
**Returns:**
|
||||
- `User` object with `role="customer"` if authenticated
|
||||
- `None` if no token, invalid token, or user is not customer
|
||||
- `Customer` object if authenticated with valid customer token
|
||||
- `None` if no token, invalid token, vendor mismatch, or not a customer token
|
||||
**Raises:** Never raises exceptions
|
||||
|
||||
**Security Features:**
|
||||
- Only accepts tokens with `type: "customer"` - admin and vendor tokens return `None`
|
||||
- Validates vendor_id in token matches URL vendor - mismatch returns `None`
|
||||
|
||||
**Usage:**
|
||||
```python
|
||||
from models.database.customer import Customer
|
||||
|
||||
# Shop login page redirect
|
||||
@router.get("/shop/account/login")
|
||||
async def customer_login_page(
|
||||
request: Request,
|
||||
current_user: Optional[User] = Depends(get_current_customer_optional)
|
||||
customer: Customer | None = Depends(get_current_customer_optional)
|
||||
):
|
||||
if current_user:
|
||||
# User already logged in, redirect to account page
|
||||
if customer:
|
||||
# Customer already logged in, redirect to account page
|
||||
return RedirectResponse(url="/shop/account", status_code=302)
|
||||
|
||||
# Not logged in, show login form
|
||||
|
||||
Reference in New Issue
Block a user