Spaces:
Sleeping
Sleeping
| from fastapi import APIRouter, Depends, HTTPException, status | |
| from sqlalchemy.orm import Session | |
| from google.oauth2 import id_token | |
| from google.auth.transport import requests as google_requests | |
| from app.db.base import get_db | |
| from app.models.user import User, AuthProvider, UserRole | |
| from app.schemas.auth import ( | |
| RegisterRequest, | |
| LoginRequest, | |
| GoogleLoginRequest, | |
| RefreshRequest, | |
| TokenResponse, | |
| UserResponse, | |
| AuthResponse, | |
| ) | |
| from app.core.security import ( | |
| hash_password, | |
| verify_password, | |
| create_access_token, | |
| create_refresh_token, | |
| decode_token, | |
| get_current_user, | |
| get_optional_current_user, | |
| ) | |
| from app.core.config import settings | |
| router = APIRouter(prefix="/auth", tags=["Authentication"]) | |
| def _generate_tokens(user_id: int) -> dict: | |
| access_token = create_access_token({"sub": str(user_id)}) | |
| refresh_token = create_refresh_token({"sub": str(user_id)}) | |
| return { | |
| "access_token": access_token, | |
| "refresh_token": refresh_token, | |
| "token_type": "bearer", | |
| } | |
| async def register(request: RegisterRequest, db: Session = Depends(get_db)): | |
| existing = db.query(User).filter(User.email == request.email).first() | |
| if existing: | |
| raise HTTPException( | |
| status_code=status.HTTP_409_CONFLICT, | |
| detail="Email already registered", | |
| ) | |
| user = User( | |
| name=request.name, | |
| email=request.email, | |
| password_hash=hash_password(request.password), | |
| phone=request.phone, | |
| role=UserRole.CUSTOMER, | |
| auth_provider=AuthProvider.LOCAL, | |
| ) | |
| db.add(user) | |
| db.commit() | |
| db.refresh(user) | |
| tokens = _generate_tokens(user.id) | |
| user_data = UserResponse.model_validate(user).model_dump() | |
| user_data["created_at"] = user_data["created_at"].isoformat() | |
| return AuthResponse( | |
| isSuccess=True, | |
| value={"tokens": tokens, "user": user_data}, | |
| statusCode=201, | |
| ) | |
| async def login(request: LoginRequest, db: Session = Depends(get_db)): | |
| user = db.query(User).filter(User.email == request.email).first() | |
| if not user or not user.password_hash: | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Invalid email or password", | |
| ) | |
| if not verify_password(request.password, user.password_hash): | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Invalid email or password", | |
| ) | |
| tokens = _generate_tokens(user.id) | |
| user_data = UserResponse.model_validate(user).model_dump() | |
| user_data["created_at"] = user_data["created_at"].isoformat() | |
| return AuthResponse( | |
| isSuccess=True, | |
| value={"tokens": tokens, "user": user_data}, | |
| statusCode=200, | |
| ) | |
| async def admin_login(request: LoginRequest, db: Session = Depends(get_db)): | |
| try: | |
| user = db.query(User).filter(User.email == request.email).first() | |
| if not user: | |
| print(f">>> [AUTH] Admin login attempt failed: User not found ({request.email})") | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Invalid email or password", | |
| ) | |
| if not user.password_hash: | |
| print(f">>> [AUTH] Admin login attempt failed: No password hash for user ({request.email})") | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Invalid email or password", | |
| ) | |
| if not verify_password(request.password, user.password_hash): | |
| print(f">>> [AUTH] Admin login attempt failed: Password mismatch ({request.email})") | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Invalid email or password", | |
| ) | |
| # Crucial step: Verify admin role | |
| if user.role != UserRole.ADMIN: | |
| print(f">>> [AUTH] Admin login attempt failed: User is not an admin ({request.email}, role: {user.role})") | |
| raise HTTPException( | |
| status_code=status.HTTP_403_FORBIDDEN, | |
| detail="Access denied. Admin privileges required.", | |
| ) | |
| print(f">>> [AUTH] Admin login successful: {request.email}") | |
| tokens = _generate_tokens(user.id) | |
| user_data = UserResponse.model_validate(user).model_dump() | |
| user_data["created_at"] = user_data["created_at"].isoformat() | |
| return AuthResponse( | |
| isSuccess=True, | |
| value={"tokens": tokens, "user": user_data}, | |
| statusCode=200, | |
| ) | |
| except HTTPException: | |
| raise | |
| except Exception as e: | |
| import traceback | |
| traceback.print_exc() | |
| raise HTTPException( | |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| detail=f"Login error: {str(e)}", | |
| ) | |
| async def google_login(request: GoogleLoginRequest, db: Session = Depends(get_db)): | |
| """Authenticate user with Google ID token.""" | |
| if not settings.GOOGLE_CLIENT_ID: | |
| raise HTTPException( | |
| status_code=status.HTTP_503_SERVICE_UNAVAILABLE, | |
| detail="Google authentication is not configured", | |
| ) | |
| try: | |
| idinfo = id_token.verify_oauth2_token( | |
| request.credential, | |
| google_requests.Request(), | |
| settings.GOOGLE_CLIENT_ID, | |
| ) | |
| except ValueError: | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Invalid Google token", | |
| ) | |
| google_id = idinfo.get("sub") | |
| email = idinfo.get("email") | |
| name = idinfo.get("name", "") | |
| avatar_url = idinfo.get("picture", "") | |
| if not email: | |
| raise HTTPException( | |
| status_code=status.HTTP_400_BAD_REQUEST, | |
| detail="Google account does not have an email", | |
| ) | |
| # Check if user exists by google_id | |
| user = db.query(User).filter(User.google_id == google_id).first() | |
| if not user: | |
| # Check if user exists by email (link accounts) | |
| user = db.query(User).filter(User.email == email).first() | |
| if user: | |
| # Link existing account to Google | |
| user.google_id = google_id | |
| user.auth_provider = AuthProvider.GOOGLE | |
| if not user.avatar_url: | |
| user.avatar_url = avatar_url | |
| else: | |
| # Create new user | |
| user = User( | |
| name=name, | |
| email=email, | |
| google_id=google_id, | |
| avatar_url=avatar_url, | |
| auth_provider=AuthProvider.GOOGLE, | |
| role=UserRole.CUSTOMER, | |
| ) | |
| db.add(user) | |
| db.commit() | |
| db.refresh(user) | |
| tokens = _generate_tokens(user.id) | |
| user_data = UserResponse.model_validate(user).model_dump() | |
| user_data["created_at"] = user_data["created_at"].isoformat() | |
| return AuthResponse( | |
| isSuccess=True, | |
| value={"tokens": tokens, "user": user_data}, | |
| statusCode=200, | |
| ) | |
| async def refresh_token(request: RefreshRequest): | |
| payload = decode_token(request.refresh_token) | |
| if payload.get("type") != "refresh": | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Invalid token type — expected refresh token", | |
| ) | |
| user_id = payload.get("sub") | |
| tokens = _generate_tokens(int(user_id)) | |
| return AuthResponse( | |
| isSuccess=True, | |
| value={"tokens": tokens}, | |
| statusCode=200, | |
| ) | |
| async def get_me( | |
| user_id: int = Depends(get_current_user), | |
| db: Session = Depends(get_db), | |
| ): | |
| user = db.query(User).filter(User.id == user_id).first() | |
| if not user: | |
| raise HTTPException( | |
| status_code=status.HTTP_404_NOT_FOUND, | |
| detail="User not found", | |
| ) | |
| user_data = UserResponse.model_validate(user).model_dump() | |
| user_data["created_at"] = user_data["created_at"].isoformat() | |
| return AuthResponse( | |
| isSuccess=True, | |
| value={"user": user_data}, | |
| statusCode=200, | |
| ) | |
| async def get_current_admin_user( | |
| user_id: int = Depends(get_current_user), | |
| db: Session = Depends(get_db), | |
| ) -> User: | |
| user = db.query(User).filter(User.id == user_id).first() | |
| if not user: | |
| raise HTTPException( | |
| status_code=status.HTTP_404_NOT_FOUND, | |
| detail="User not found", | |
| ) | |
| if user.role != UserRole.ADMIN: | |
| raise HTTPException( | |
| status_code=status.HTTP_403_FORBIDDEN, | |
| detail="Admin privileges required", | |
| ) | |
| return user | |