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:
@@ -135,10 +135,19 @@ def setup_exception_handlers(app):
|
||||
async def validation_exception_handler(request: Request, exc: RequestValidationError):
|
||||
"""Handle Pydantic validation errors with consistent format."""
|
||||
|
||||
# Sanitize errors to remove sensitive data from logs
|
||||
sanitized_errors = []
|
||||
for error in exc.errors():
|
||||
sanitized_error = error.copy()
|
||||
# Remove 'input' field which may contain passwords
|
||||
if 'input' in sanitized_error:
|
||||
sanitized_error['input'] = '<redacted>'
|
||||
sanitized_errors.append(sanitized_error)
|
||||
|
||||
logger.error(
|
||||
f"Validation error in {request.method} {request.url}: {exc.errors()}",
|
||||
f"Validation error in {request.method} {request.url}: {len(sanitized_errors)} validation error(s)",
|
||||
extra={
|
||||
"validation_errors": exc.errors(),
|
||||
"validation_errors": sanitized_errors,
|
||||
"url": str(request.url),
|
||||
"method": request.method,
|
||||
"exception_type": "RequestValidationError",
|
||||
@@ -357,6 +366,7 @@ def _redirect_to_login(request: Request) -> RedirectResponse:
|
||||
Redirect to appropriate login page based on request context.
|
||||
|
||||
Uses context detection to determine admin vs vendor vs shop login.
|
||||
Properly handles multi-access routing (domain, subdomain, path-based).
|
||||
"""
|
||||
context_type = get_request_context(request)
|
||||
|
||||
@@ -368,8 +378,19 @@ def _redirect_to_login(request: Request) -> RedirectResponse:
|
||||
return RedirectResponse(url="/vendor/login", status_code=302)
|
||||
elif context_type == RequestContext.SHOP:
|
||||
# For shop context, redirect to shop login (customer login)
|
||||
logger.debug("Redirecting to /shop/login")
|
||||
return RedirectResponse(url="/shop/login", status_code=302)
|
||||
# Calculate base_url for proper routing (supports domain, subdomain, and path-based access)
|
||||
vendor = getattr(request.state, 'vendor', None)
|
||||
vendor_context = getattr(request.state, 'vendor_context', None)
|
||||
access_method = vendor_context.get('detection_method', 'unknown') if vendor_context else 'unknown'
|
||||
|
||||
base_url = "/"
|
||||
if access_method == "path" and vendor:
|
||||
full_prefix = vendor_context.get('full_prefix', '/vendor/') if vendor_context else '/vendor/'
|
||||
base_url = f"{full_prefix}{vendor.subdomain}/"
|
||||
|
||||
login_url = f"{base_url}shop/account/login"
|
||||
logger.debug(f"Redirecting to {login_url}")
|
||||
return RedirectResponse(url=login_url, status_code=302)
|
||||
else:
|
||||
# Fallback to root for unknown contexts
|
||||
logger.debug("Unknown context, redirecting to /")
|
||||
|
||||
Reference in New Issue
Block a user