feat: implement customer authentication with JWT tokens
Implement secure customer authentication system with dedicated JWT tokens, separate from admin/vendor authentication. Backend Changes: - Add customer JWT token support in deps.py - New get_current_customer_from_cookie_or_header dependency - Validates customer-specific tokens with type checking - Returns Customer object instead of User for shop routes - Extend AuthService with customer token support - Add verify_password() method - Add create_access_token_with_data() for custom token payloads - Update CustomerService authentication - Generate customer-specific JWT tokens with type="customer" - Use vendor-scoped customer lookup - Enhance exception handler - Sanitize validation errors to prevent password leaks in logs - Fix shop login redirect to support multi-access routing - Improve vendor context detection from Referer header - Consistent "path" detection method for cookie path logic Schema Changes: - Rename UserLogin.username to email_or_username for flexibility - Update field validators accordingly API Changes: - Update admin/vendor auth endpoints to use email_or_username - Customer auth already uses email field correctly Route Changes: - Update shop account routes to use Customer dependency - Add /account redirect (without trailing slash) - Change parameter names from current_user to current_customer Frontend Changes: - Update login forms to use email_or_username in API calls - Change button text from "Log in" to "Sign in" for consistency - Improve loading spinner layout with flexbox Security Improvements: - Customer tokens scoped to vendor_id - Token type validation prevents cross-context token usage - Password inputs redacted from validation error logs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -38,7 +38,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import get_current_customer_from_cookie_or_header, get_db
|
||||
from app.services.content_page_service import content_page_service
|
||||
from models.database.user import User
|
||||
from models.database.customer import Customer
|
||||
|
||||
router = APIRouter()
|
||||
templates = Jinja2Templates(directory="app/templates")
|
||||
@@ -370,10 +370,11 @@ async def shop_forgot_password_page(request: Request):
|
||||
# CUSTOMER ACCOUNT - AUTHENTICATED ROUTES
|
||||
# ============================================================================
|
||||
|
||||
@router.get("/account", response_class=RedirectResponse, include_in_schema=False)
|
||||
@router.get("/account/", response_class=RedirectResponse, include_in_schema=False)
|
||||
async def shop_account_root(request: Request):
|
||||
"""
|
||||
Redirect /shop/account/ to dashboard.
|
||||
Redirect /shop/account or /shop/account/ to dashboard.
|
||||
"""
|
||||
logger.debug(
|
||||
f"[SHOP_HANDLER] shop_products_page REACHED",
|
||||
@@ -400,7 +401,7 @@ async def shop_account_root(request: Request):
|
||||
@router.get("/account/dashboard", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def shop_account_dashboard_page(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_customer_from_cookie_or_header),
|
||||
current_customer: Customer = Depends(get_current_customer_from_cookie_or_header),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
@@ -419,14 +420,14 @@ async def shop_account_dashboard_page(
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"shop/account/dashboard.html",
|
||||
get_shop_context(request, user=current_user)
|
||||
get_shop_context(request, user=current_customer)
|
||||
)
|
||||
|
||||
|
||||
@router.get("/account/orders", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def shop_orders_page(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_customer_from_cookie_or_header),
|
||||
current_customer: Customer = Depends(get_current_customer_from_cookie_or_header),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
@@ -445,7 +446,7 @@ async def shop_orders_page(
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"shop/account/orders.html",
|
||||
get_shop_context(request, user=current_user)
|
||||
get_shop_context(request, user=current_customer)
|
||||
)
|
||||
|
||||
|
||||
@@ -453,7 +454,7 @@ async def shop_orders_page(
|
||||
async def shop_order_detail_page(
|
||||
request: Request,
|
||||
order_id: int = Path(..., description="Order ID"),
|
||||
current_user: User = Depends(get_current_customer_from_cookie_or_header),
|
||||
current_customer: Customer = Depends(get_current_customer_from_cookie_or_header),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
@@ -472,14 +473,14 @@ async def shop_order_detail_page(
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"shop/account/order-detail.html",
|
||||
get_shop_context(request, user=current_user, order_id=order_id)
|
||||
get_shop_context(request, user=current_customer, order_id=order_id)
|
||||
)
|
||||
|
||||
|
||||
@router.get("/account/profile", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def shop_profile_page(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_customer_from_cookie_or_header),
|
||||
current_customer: Customer = Depends(get_current_customer_from_cookie_or_header),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
@@ -498,14 +499,14 @@ async def shop_profile_page(
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"shop/account/profile.html",
|
||||
get_shop_context(request, user=current_user)
|
||||
get_shop_context(request, user=current_customer)
|
||||
)
|
||||
|
||||
|
||||
@router.get("/account/addresses", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def shop_addresses_page(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_customer_from_cookie_or_header),
|
||||
current_customer: Customer = Depends(get_current_customer_from_cookie_or_header),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
@@ -524,14 +525,14 @@ async def shop_addresses_page(
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"shop/account/addresses.html",
|
||||
get_shop_context(request, user=current_user)
|
||||
get_shop_context(request, user=current_customer)
|
||||
)
|
||||
|
||||
|
||||
@router.get("/account/wishlist", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def shop_wishlist_page(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_customer_from_cookie_or_header),
|
||||
current_customer: Customer = Depends(get_current_customer_from_cookie_or_header),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
@@ -550,14 +551,14 @@ async def shop_wishlist_page(
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"shop/account/wishlist.html",
|
||||
get_shop_context(request, user=current_user)
|
||||
get_shop_context(request, user=current_customer)
|
||||
)
|
||||
|
||||
|
||||
@router.get("/account/settings", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def shop_settings_page(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_customer_from_cookie_or_header),
|
||||
current_customer: Customer = Depends(get_current_customer_from_cookie_or_header),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
@@ -576,7 +577,7 @@ async def shop_settings_page(
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"shop/account/settings.html",
|
||||
get_shop_context(request, user=current_user)
|
||||
get_shop_context(request, user=current_customer)
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user