# app/services/auth_service.py """ Authentication service for user login and vendor access control. This module provides: - User authentication and JWT token generation - Vendor access verification - Password hashing utilities Note: Customer registration is handled by CustomerService. User (admin/vendor team) creation is handled by their respective services. """ import logging from typing import Any from sqlalchemy.orm import Session from app.exceptions import ( InvalidCredentialsException, UserNotActiveException, ) from middleware.auth import AuthManager from models.database.user import User from models.database.vendor import Vendor, VendorUser from models.schema.auth import UserLogin logger = logging.getLogger(__name__) class AuthService: """Service class for authentication operations.""" def __init__(self): """Initialize with AuthManager instance.""" self.auth_manager = AuthManager() def login_user(self, db: Session, user_credentials: UserLogin) -> dict[str, Any]: """ Login user and return JWT token with user data. Args: db: Database session user_credentials: User login credentials Returns: Dictionary containing access token data and user object Raises: InvalidCredentialsException: If authentication fails UserNotActiveException: If user account is not active """ user = self.auth_manager.authenticate_user( db, user_credentials.email_or_username, user_credentials.password ) if not user: raise InvalidCredentialsException("Incorrect username or password") if not user.is_active: raise UserNotActiveException("User account is not active") token_data = self.auth_manager.create_access_token(user) logger.info(f"User logged in: {user.username}") return {"token_data": token_data, "user": user} def hash_password(self, password: str) -> str: """ Hash a password. Args: password: Plain text password Returns: Hashed password string """ return self.auth_manager.hash_password(password) def get_vendor_by_code(self, db: Session, vendor_code: str) -> Vendor | None: """ Get active vendor by vendor code. Args: db: Database session vendor_code: Vendor code to look up Returns: Vendor if found and active, None otherwise """ return ( db.query(Vendor) .filter(Vendor.vendor_code == vendor_code.upper(), Vendor.is_active == True) .first() ) def get_user_vendor_role( self, db: Session, user: User, vendor: Vendor ) -> tuple[bool, str | None]: """ Check if user has access to vendor and return their role. Args: db: Database session user: User to check vendor: Vendor to check access for Returns: Tuple of (has_access: bool, role_name: str | None) """ # Check if user is vendor owner (via company ownership) if vendor.company and vendor.company.owner_user_id == user.id: return True, "Owner" # Check if user is team member vendor_user = ( db.query(VendorUser) .filter( VendorUser.user_id == user.id, VendorUser.vendor_id == vendor.id, VendorUser.is_active == True, ) .first() ) if vendor_user: return True, vendor_user.role.name return False, None def find_user_vendor(self, user: User) -> tuple[Vendor | None, str | None]: """ Find which vendor a user belongs to when no vendor context is provided. Checks owned companies first, then vendor memberships. Args: user: User to find vendor for Returns: Tuple of (vendor: Vendor | None, role: str | None) """ # Check owned vendors first (via company ownership) for company in user.owned_companies: if company.vendors: return company.vendors[0], "Owner" # Check vendor memberships if user.vendor_memberships: active_membership = next( (vm for vm in user.vendor_memberships if vm.is_active), None ) if active_membership: return active_membership.vendor, active_membership.role.name return None, None # Create service instance auth_service = AuthService()