diff --git a/src/api/auth_routes.py b/src/api/auth_routes.py index bc72ae6a..733e36ca 100644 --- a/src/api/auth_routes.py +++ b/src/api/auth_routes.py @@ -191,14 +191,36 @@ async def login_for_access_token(form_data: UserLogin, db: Session = Depends(get Raises: HTTPException: If credentials are invalid """ - user = authenticate_user(db, form_data.email, form_data.password) + user, reason = authenticate_user(db, form_data.email, form_data.password) if not user: - logger.warning(f"Login attempt with invalid credentials: {form_data.email}") - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Invalid email or password", - headers={"WWW-Authenticate": "Bearer"}, - ) + if reason == "user_not_found" or reason == "invalid_password": + logger.warning(f"Login attempt with invalid credentials: {form_data.email}") + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid email or password", + headers={"WWW-Authenticate": "Bearer"}, + ) + elif reason == "email_not_verified": + logger.warning(f"Login attempt with unverified email: {form_data.email}") + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Email not verified", + ) + elif reason == "inactive_user": + logger.warning(f"Login attempt with inactive user: {form_data.email}") + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="User account is inactive", + ) + else: + logger.warning( + f"Login attempt failed for {form_data.email} (reason: {reason})" + ) + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid email or password", + headers={"WWW-Authenticate": "Bearer"}, + ) access_token = create_access_token(user) logger.info(f"Login successful for user: {user.email}") diff --git a/src/services/user_service.py b/src/services/user_service.py index 177ddd35..1c398e1b 100644 --- a/src/services/user_service.py +++ b/src/services/user_service.py @@ -374,7 +374,9 @@ def get_user_by_email(db: Session, email: str) -> Optional[User]: return None -def authenticate_user(db: Session, email: str, password: str) -> Optional[User]: +def authenticate_user( + db: Session, email: str, password: str +) -> Tuple[Optional[User], str]: """ Authenticates a user with email and password @@ -384,16 +386,18 @@ def authenticate_user(db: Session, email: str, password: str) -> Optional[User]: password: User password Returns: - Optional[User]: Authenticated user or None + Tuple[Optional[User], str]: Authenticated user and reason (or None and reason) """ user = get_user_by_email(db, email) if not user: - return None + return None, "user_not_found" if not verify_password(password, user.password_hash): - return None + return None, "invalid_password" + if not user.email_verified: + return None, "email_not_verified" if not user.is_active: - return None - return user + return None, "inactive_user" + return user, "success" def get_admin_users(db: Session, skip: int = 0, limit: int = 100):