diff --git a/docs/api/authentication-flow-diagrams.md b/docs/api/authentication-flow-diagrams.md index 4a78df28..75d5e4ee 100644 --- a/docs/api/authentication-flow-diagrams.md +++ b/docs/api/authentication-flow-diagrams.md @@ -3,30 +3,33 @@ ## 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 │ -└───────────────────────┘ └───────────────────────┘ +┌──────────────────────────────────────────────────────────────────────────────┐ +│ Browser │ +│ │ +│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ +│ │ Admin Area │ │ Vendor Area │ │ Shop Area │ │ +│ │ /admin/* │ │ /vendor/* │ │ /shop/* │ │ +│ │ │ │ │ │ │ │ +│ │ 🍪 admin_token │ │ 🍪 vendor_token │ │ 🍪 customer_ │ │ +│ │ Path: /admin │ │ Path: /vendor │ │ token │ │ +│ │ │ │ │ │ Path: /shop │ │ +│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │ +│ │ │ │ │ +│ ├──────────────────────┼─────────────────────┤ │ +│ │ ❌ No Cookie Mixing │ │ +│ │ │ │ │ +└───────────┼──────────────────────┼─────────────────────┼─────────────────────┘ + │ │ │ + ▼ ▼ ▼ +┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ +│ Admin Backend │ │ Vendor Backend │ │ Shop Backend │ +│ /admin/* │ │ /vendor/* │ │ /shop/* │ +│ │ │ │ │ │ +│ ✅ admin_token │ │ ✅ vendor_token │ │ ✅ customer_ │ +│ ❌ vendor_token │ │ ❌ admin_token │ │ token │ +│ ❌ customer_ │ │ ❌ customer_ │ │ ❌ admin_token │ +│ token │ │ token │ │ ❌ vendor_token │ +└──────────────────┘ └──────────────────┘ └──────────────────┘ ``` ## Login Flow - Admin @@ -63,15 +66,17 @@ └── API call to /api/v1/admin/vendors ───────┤ (Authorization: Bearer ) │ │ - ┌──────────────▼──────────────┐ - │ get_current_admin_user() │ - │ │ - │ 1. Check Auth header │ - │ 2. Check admin_token cookie │ - │ 3. Validate JWT │ - │ 4. Verify role == admin │ - │ ✅ Return User │ - └──────────────────────────────┘ + ┌────────────────────────────────────────┐ + │ HTML: get_current_admin_from_ │ + │ cookie_or_header() │ + │ API: get_current_admin_api() │ + │ │ + │ 1. Check Auth header │ + │ 2. Check admin_token cookie (HTML)│ + │ 3. Validate JWT │ + │ 4. Verify role == admin │ + │ ✅ Return User │ + └────────────────────────────────────┘ ``` ## Login Flow - Vendor @@ -109,54 +114,104 @@ └── API call to /api/v1/vendor/ACME/products ┤ (Authorization: Bearer ) │ │ - ┌──────────────▼──────────────┐ - │ get_current_vendor_user() │ - │ │ - │ 1. Check Auth header │ - │ 2. Check vendor_token cookie│ - │ 3. Validate JWT │ + ┌────────────────────────────────────────┐ + │ HTML: get_current_vendor_from_ │ + │ cookie_or_header() │ + │ API: get_current_vendor_api() │ + │ │ + │ 1. Check Auth header │ + │ 2. Check vendor_token cookie (HTML)│ + │ 3. Validate JWT │ │ 4. Block if admin │──> ❌ Error - │ 5. Verify vendor access │ - │ ✅ Return User │ - └──────────────────────────────┘ + │ 5. Verify vendor access │ + │ ✅ Return User │ + └────────────────────────────────────┘ +``` + +## Login Flow - Customer (Shop) + +``` +┌──────────┐ +│ Browser │ +└──────────┘ + │ + │ POST /api/v1/shop/auth/login + │ { email, password } + ▼ +┌─────────────────────────┐ +│ Customer Auth Endpoint │ +│ │ +│ 1. Validate credentials│ +│ 2. Check role = customer│ +│ 3. Generate JWT │ +└─────────────────────────┘ + │ + │ Set-Cookie: customer_token=; Path=/shop; HttpOnly; SameSite=Lax + │ Response: { access_token, user } + ▼ +┌──────────┐ +│ Browser │──────────────────────────────────────┐ +│ │ │ +│ 🍪 customer_token (Path=/shop) │ +│ 💾 localStorage.access_token │ +└──────────┘ │ + │ │ + ├── Navigate to /shop/account/dashboard ─────┤ + │ (Cookie sent automatically) │ + │ │ + └── API call to /api/v1/shop/orders ─────────┤ + (Authorization: Bearer ) │ + │ + ┌────────────────────────────────────────┐ + │ HTML: get_current_customer_from_ │ + │ cookie_or_header() │ + │ API: get_current_customer_api() │ + │ │ + │ 1. Check Auth header │ + │ 2. Check customer_token cookie (HTML)│ + │ 3. Validate JWT │ + │ 4. Verify role = customer │ + │ ✅ 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 + ┌─────────────────────┐ + │ Request Comes In │ + └──────────┬──────────┘ + │ + ┌──────────▼──────────┐ + │ What's the path? │ + └──────────┬──────────┘ + │ + ┌───────────────────────────┼───────────────────────────┐ + │ │ │ │ │ + Starts with Starts with Starts with Starts with Starts with + /admin/* /vendor/* /shop/* /api/* (public) + │ │ │ │ │ + ▼ ▼ ▼ ▼ ▼ +┌────────────────┐┌────────────────┐┌────────────────┐┌────────────────┐┌────────────────┐ +│ Check for: ││ Check for: ││ Check for: ││ Check for: ││ No Auth │ +│ - admin_token ││ - vendor_token ││ - customer_ ││ - Authorization││ Required │ +│ cookie ││ cookie ││ token cookie ││ header ││ │ +│ - OR Auth ││ - OR Auth ││ - OR Auth ││ (required) ││ Public pages │ +│ header ││ header ││ header ││ ││ & assets │ +└────────┬───────┘└────────┬───────┘└────────┬───────┘└────────┬───────┘└────────────────┘ + │ │ │ │ + ▼ ▼ ▼ ▼ +┌────────────────┐┌────────────────┐┌────────────────┐┌────────────────┐ +│ Validate: ││ Validate: ││ Validate: ││ Validate: │ +│ - JWT valid ││ - JWT valid ││ - JWT valid ││ - JWT valid │ +│ - User active ││ - User active ││ - User active ││ - User active │ +│ - Role = admin ││ - Role != admin││ - Role = ││ - Any role │ +│ ││ - Has vendor ││ customer ││ (depends on │ +│ ││ access ││ ││ endpoint) │ +└────────┬───────┘└────────┬───────┘└────────┬───────┘└────────┬───────┘ + │ │ │ │ + ▼ ▼ ▼ ▼ + ✅ Allowed ✅ Allowed ✅ Allowed ✅ Allowed ``` ## Cross-Context Prevention @@ -229,6 +284,55 @@ Admin cookie sent to vendor route: "Vendor authentication required" ``` +``` +Customer trying to access admin route: +┌──────────────────────────────────────────┐ +│ User: customer@example.com (role: customer)│ +│ Token: Valid JWT with customer role │ +│ Request: GET /admin/dashboard │ +└──────────────────────────────────────────┘ + │ + ▼ + ┌───────────────────────┐ + │ get_current_admin_ │ + │ from_cookie_or_header │ + └───────────┬───────────┘ + │ + ▼ + Check: role == "admin"? + │ + ▼ No + ❌ AdminRequiredException + "Admin privileges required" +``` + +``` +Customer cookie sent to shop route (allowed): +┌──────────────────────────────────────────┐ +│ Cookie: customer_token= (Path=/shop)│ +│ Request: GET /shop/account/orders │ +└──────────────────────────────────────────┘ + │ + ▼ + Browser checks cookie path + │ + ▼ + Path /shop matches /shop + │ + ▼ + ✅ Cookie SENT automatically + │ + ▼ + ┌───────────────────────┐ + │ get_current_customer_ │ + │ from_cookie_or_header │ + └───────────┬───────────┘ + │ + ▼ + ✅ Customer authenticated + Page loads successfully +``` + ## Cookie Lifecycle ``` @@ -236,9 +340,9 @@ LOGIN │ ├── Server generates JWT ├── Server sets cookie: - │ • Name: admin_token or vendor_token + │ • Name: admin_token, vendor_token, or customer_token │ • Value: JWT - │ • Path: /admin or /vendor + │ • Path: /admin, /vendor, or /shop (context-specific) │ • HttpOnly: true │ • Secure: true (production) │ • SameSite: Lax @@ -278,8 +382,9 @@ LOGOUT ## 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 +1. **Cookie Path Isolation** = Three separate cookies (admin_token, vendor_token, customer_token) with path-based isolation +2. **Role Checking** = Strict role validation at each boundary (admin, vendor, customer) +3. **Dual Auth Support** = Cookies for HTML pages, headers for API endpoints +4. **Security First** = HttpOnly, Secure, SameSite protection on all cookies +5. **Clear Boundaries** = Each context (admin/vendor/shop) is completely isolated +6. **Three User Types** = Admins manage platform, vendors manage stores, customers shop diff --git a/docs/architecture/diagrams/vendor-domain-diagrams.md b/docs/architecture/diagrams/vendor-domain-diagrams.md index 6d17e5da..40710e4a 100644 --- a/docs/architecture/diagrams/vendor-domain-diagrams.md +++ b/docs/architecture/diagrams/vendor-domain-diagrams.md @@ -106,7 +106,7 @@ │ │ 2. Pydantic Validation │ │ │ │ 3. Dependency Injection │ │ │ │ - get_db() │ │ -│ │ - get_current_admin_user() │ │ +│ │ - get_current_admin_api() │ │ │ └──────────────────────────────────────┘ │ └────────────────┬───────────────────────────┘ │ diff --git a/docs/backend/admin-feature-integration.md b/docs/backend/admin-feature-integration.md index 1b4f8831..646731c8 100644 --- a/docs/backend/admin-feature-integration.md +++ b/docs/backend/admin-feature-integration.md @@ -943,7 +943,7 @@ from typing import List from fastapi import APIRouter, Depends, Path, Body, Query from sqlalchemy.orm import Session -from app.api.deps import get_current_admin_user, get_db +from app.api.deps import get_current_admin_api, get_db from app.services.vendor_domain_service import vendor_domain_service from app.exceptions import VendorNotFoundException from models.schema.vendor_domain import ( @@ -992,7 +992,7 @@ def add_vendor_domain( vendor_id: int = Path(..., description="Vendor ID", gt=0), domain_data: VendorDomainCreate = Body(...), db: Session = Depends(get_db), - current_admin: User = Depends(get_current_admin_user), + current_admin: User = Depends(get_current_admin_api), ): """ Add a custom domain to vendor (Admin only). @@ -1040,7 +1040,7 @@ def add_vendor_domain( def list_vendor_domains( vendor_id: int = Path(..., description="Vendor ID", gt=0), db: Session = Depends(get_db), - current_admin: User = Depends(get_current_admin_user), + current_admin: User = Depends(get_current_admin_api), ): """ List all domains for a vendor (Admin only). @@ -1084,7 +1084,7 @@ def update_vendor_domain( domain_id: int = Path(..., description="Domain ID", gt=0), domain_update: VendorDomainUpdate = Body(...), db: Session = Depends(get_db), - current_admin: User = Depends(get_current_admin_user), + current_admin: User = Depends(get_current_admin_api), ): """ Update domain settings (Admin only). @@ -1127,7 +1127,7 @@ def update_vendor_domain( def delete_vendor_domain( domain_id: int = Path(..., description="Domain ID", gt=0), db: Session = Depends(get_db), - current_admin: User = Depends(get_current_admin_user), + current_admin: User = Depends(get_current_admin_api), ): """ Delete a custom domain (Admin only). @@ -1193,7 +1193,7 @@ router.include_router(users.router, tags=["admin-users"]) async def admin_vendor_domains_page( request: Request, vendor_code: str = Path(..., description="Vendor code"), - current_user: User = Depends(get_current_admin_user), + current_user: User = Depends(get_current_admin_from_cookie_or_header), db: Session = Depends(get_db) ): """ diff --git a/docs/backend/admin-integration-guide.md b/docs/backend/admin-integration-guide.md index a18d91e6..ec114fa4 100644 --- a/docs/backend/admin-integration-guide.md +++ b/docs/backend/admin-integration-guide.md @@ -145,10 +145,10 @@ Your API endpoints need to pass the current admin's ID to service methods: def create_vendor_with_owner( vendor_data: VendorCreate, db: Session = Depends(get_db), - current_admin: User = Depends(get_current_admin_user), + current_admin: User = Depends(get_current_admin_api), ): """Create vendor with audit logging.""" - + vendor, owner_user, temp_password = admin_service.create_vendor_with_owner( db=db, vendor_data=vendor_data, @@ -170,7 +170,7 @@ def create_vendor_with_owner( def toggle_vendor_status( vendor_id: int, db: Session = Depends(get_db), - current_admin: User = Depends(get_current_admin_user), + current_admin: User = Depends(get_current_admin_api), ): """Toggle vendor status with audit logging.""" vendor, message = admin_service.toggle_vendor_status( @@ -195,13 +195,13 @@ def delete_vendor( request: Request, # Add request parameter confirm: bool = Query(False), db: Session = Depends(get_db), - current_admin: User = Depends(get_current_admin_user), + current_admin: User = Depends(get_current_admin_api), ): """Delete vendor with full audit trail.""" - + if not confirm: raise HTTPException(status_code=400, detail="Confirmation required") - + # Get request metadata ip_address = request.client.host if request.client else None user_agent = request.headers.get("user-agent") diff --git a/docs/development/exception-handling.md b/docs/development/exception-handling.md index bad56856..dd82d327 100644 --- a/docs/development/exception-handling.md +++ b/docs/development/exception-handling.md @@ -195,7 +195,7 @@ Endpoints rely on service layer exceptions and don't handle HTTPException: def toggle_user_status( user_id: int, db: Session = Depends(get_db), - current_admin: User = Depends(get_current_admin_user), # May raise AdminRequiredException + current_admin: User = Depends(get_current_admin_api), # May raise AdminRequiredException ): # Service raises UserNotFoundException, CannotModifySelfException, etc. user, message = admin_service.toggle_user_status(db, user_id, current_admin.id) diff --git a/docs/frontend/admin/architecture.md b/docs/frontend/admin/architecture.md index ccc3b14e..ae035509 100644 --- a/docs/frontend/admin/architecture.md +++ b/docs/frontend/admin/architecture.md @@ -128,7 +128,7 @@ Example: @router.get("/admin/dashboard") async def admin_dashboard_page( request: Request, - current_user: User = Depends(get_current_admin_user) + current_user: User = Depends(get_current_admin_from_cookie_or_header) ): return templates.TemplateResponse( "admin/dashboard.html", @@ -364,10 +364,12 @@ Windmill Dashboard Theme: Auth Flow: 1. Login → POST /api/v1/admin/auth/login 2. API → Verify credentials + check admin role - 3. API → Return JWT token - 4. JavaScript → Store in localStorage - 5. API Client → Add to all requests - 6. Routes → Verify with get_current_admin_user + 3. API → Return JWT token + set admin_token cookie + 4. JavaScript → Store in localStorage (optional) + 5. HTML Pages → Use cookie (automatic) + 6. API Calls → Use Authorization header + 7. Routes → Verify with get_current_admin_from_cookie_or_header (HTML) + or get_current_admin_api (API endpoints) Protected Routes: • All /admin/* routes diff --git a/docs/frontend/admin/page-templates.md b/docs/frontend/admin/page-templates.md index 1c43c413..312ec852 100644 --- a/docs/frontend/admin/page-templates.md +++ b/docs/frontend/admin/page-templates.md @@ -862,7 +862,7 @@ pageLog.info('[Page Name] module loaded'); ```python from fastapi import APIRouter, Request, Depends -from app.core.auth import get_current_admin_user +from app.api.deps import get_current_admin_from_cookie_or_header from app.models.database.user import User router = APIRouter() @@ -870,12 +870,12 @@ router = APIRouter() @router.get("/admin/[page-route]") async def [page_name]_page( request: Request, - current_user: User = Depends(get_current_admin_user) + current_user: User = Depends(get_current_admin_from_cookie_or_header) ): """ [Page Name] page Displays [description] - + Requires admin authentication. """ return templates.TemplateResponse( diff --git a/docs/frontend/shared/sidebar.md b/docs/frontend/shared/sidebar.md index ea75d2fd..ce4f00a5 100644 --- a/docs/frontend/shared/sidebar.md +++ b/docs/frontend/shared/sidebar.md @@ -66,7 +66,7 @@ Update `app/api/v1/admin/pages.py` - add this route: @router.get("/icons", response_class=HTMLResponse, include_in_schema=False) async def admin_icons_page( request: Request, - current_user: User = Depends(get_current_admin_user), + current_user: User = Depends(get_current_admin_from_cookie_or_header), db: Session = Depends(get_db) ): """ diff --git a/docs/frontend/shared/ui-components.md b/docs/frontend/shared/ui-components.md index 05d884b1..829569ee 100644 --- a/docs/frontend/shared/ui-components.md +++ b/docs/frontend/shared/ui-components.md @@ -69,7 +69,7 @@ Update your `app/api/v1/admin/pages.py`: @router.get("/components", response_class=HTMLResponse, include_in_schema=False) async def admin_components_page( request: Request, - current_user: User = Depends(get_current_admin_user), + current_user: User = Depends(get_current_admin_from_cookie_or_header), db: Session = Depends(get_db) ): """