revamped authentication system

This commit is contained in:
2025-11-02 18:40:03 +01:00
parent 9cc92e5fc4
commit e4bc438069
18 changed files with 674 additions and 636 deletions

View File

@@ -2,14 +2,18 @@
"""
Vendor team authentication endpoints.
This module provides:
- Vendor team member login
- Vendor owner login
- Vendor-scoped authentication
Implements dual token storage with path restriction:
- Sets HTTP-only cookie with path=/vendor (restricted to vendor routes only)
- Returns token in response for localStorage (API calls)
This prevents:
- Vendor cookies from being sent to admin routes
- Admin cookies from being sent to vendor routes
- Cross-context authentication confusion
"""
import logging
from fastapi import APIRouter, Depends, Request, HTTPException
from fastapi import APIRouter, Depends, Request, Response
from sqlalchemy.orm import Session
from app.core.database import get_db
@@ -18,7 +22,9 @@ from app.exceptions import InvalidCredentialsException
from middleware.vendor_context import get_current_vendor
from models.schema.auth import UserLogin
from models.database.vendor import Vendor, VendorUser, Role
from models.database.user import User
from pydantic import BaseModel
from app.core.config import settings
router = APIRouter(prefix="/auth")
logger = logging.getLogger(__name__)
@@ -38,6 +44,7 @@ class VendorLoginResponse(BaseModel):
def vendor_login(
user_credentials: UserLogin,
request: Request,
response: Response,
db: Session = Depends(get_db)
):
"""
@@ -45,6 +52,12 @@ def vendor_login(
Authenticates users who are part of a vendor team.
Validates against vendor context if available.
Sets token in two places:
1. HTTP-only cookie with path=/vendor (for browser page navigation)
2. Response body (for localStorage and API calls)
Prevents admin users from logging into vendor portal.
"""
# Try to get vendor from middleware first
vendor = get_current_vendor(request)
@@ -62,10 +75,10 @@ def vendor_login(
login_result = auth_service.login_user(db=db, user_credentials=user_credentials)
user = login_result["user"]
# Prevent admin users from using vendor login
# CRITICAL: Prevent admin users from using vendor login
if user.role == "admin":
logger.warning(f"Admin user attempted vendor login: {user.username}")
raise InvalidCredentialsException("Please use admin portal to login")
raise InvalidCredentialsException("Admins cannot access vendor portal. Please use admin portal.")
# Determine vendor and role
vendor_role = "Member"
@@ -120,6 +133,24 @@ def vendor_login(
f"for vendor {vendor.vendor_code} as {vendor_role}"
)
# Set HTTP-only cookie for browser navigation
# CRITICAL: path=/vendor restricts cookie to vendor routes only
response.set_cookie(
key="vendor_token",
value=login_result["token_data"]["access_token"],
httponly=True, # JavaScript cannot access (XSS protection)
secure=settings.environment == "production", # HTTPS only in production
samesite="lax", # CSRF protection
max_age=login_result["token_data"]["expires_in"], # Match JWT expiry
path="/vendor", # RESTRICTED TO VENDOR ROUTES ONLY
)
logger.debug(
f"Set vendor_token cookie with {login_result['token_data']['expires_in']}s expiry "
f"(path=/vendor, httponly=True, secure={settings.environment == 'production'})"
)
# Return full login response
return VendorLoginResponse(
access_token=login_result["token_data"]["access_token"],
token_type=login_result["token_data"]["token_type"],
@@ -144,10 +175,45 @@ def vendor_login(
@router.post("/logout")
def vendor_logout():
def vendor_logout(response: Response):
"""
Vendor team member logout.
Client should remove token from storage.
Clears the vendor_token cookie.
Client should also remove token from localStorage.
"""
return {"message": "Logged out successfully"}
logger.info("Vendor logout")
# Clear the cookie (must match path used when setting)
response.delete_cookie(
key="vendor_token",
path="/vendor",
)
logger.debug("Deleted vendor_token cookie")
return {"message": "Logged out successfully"}
@router.get("/me")
def get_current_vendor_user(
request: Request,
db: Session = Depends(get_db)
):
"""
Get current authenticated vendor user.
This endpoint can be called to verify authentication and get user info.
"""
from app.api.deps import get_current_vendor_api
# This will check both cookie and header
user = get_current_vendor_api(request, db=db)
return {
"id": user.id,
"username": user.username,
"email": user.email,
"role": user.role,
"is_active": user.is_active
}