import httpx from src.domain.interfaces.user_repository import UserRepository from src.core.exceptions import InvalidCredentialsException, UserNotFoundException from src.core.security import create_access_token from src.core.logger import get_logger from src.core.rbac.user_type_map import resolve_role from src.config.settings import settings logger = get_logger() class AuthenticateMicrosoftUserUseCase: """ Business Logic execution for authenticating a user via Microsoft OAuth. It takes an authorization code, exchanges it for an MS access token, and locates the user. """ def __init__(self, user_repo: UserRepository): self.user_repo = user_repo async def execute(self, code: str) -> str: logger.debug("Attempting to exchange Microsoft Auth Code") token_url = f"https://login.microsoftonline.com/{settings.MICROSOFT_TENANT}/oauth2/v2.0/token" token_data = { "client_id": settings.MICROSOFT_CLIENT_ID, "scope": "user.read", "code": code, "redirect_uri": settings.MICROSOFT_REDIRECT_URI, "grant_type": "authorization_code", "client_secret": settings.MICROSOFT_CLIENT_SECRET, } async with httpx.AsyncClient() as client: token_res = await client.post(token_url, data=token_data) if token_res.status_code != 200: logger.error(f"Failed to get Microsoft token: {token_res.text}") raise InvalidCredentialsException() provider_access_token = token_res.json().get('access_token') # Get user graph data response = await client.get( "https://graph.microsoft.com/v1.0/me?$select=userPrincipalName,mail", headers={"Authorization": f"Bearer {provider_access_token}"} ) if response.status_code != 200: logger.error(f"Microsoft Graph API error: {response.text}") raise InvalidCredentialsException() graph_data = response.json() email = graph_data.get("mail") or graph_data.get("userPrincipalName") if not email: logger.error("Microsoft Graph API did not return an email.") raise InvalidCredentialsException() logger.debug(f"Microsoft token valid for email: {email}") # Domain Logic: Check if user exists in our system user = self.user_repo.get_user_by_email(email) if not user: logger.info(f"Authentication failed: User {email} not found in our database.") raise UserNotFoundException(identifier=email) # Resolve RBAC role from legacy user_type and embed it in the JWT role = resolve_role(user.user_type) token = create_access_token(data={ "sub": user.email, "user_id": user.id, "role": role, "user_type": user.user_type, }) # Event logging logger.info(f"Microsoft Authentication successful for user: {email} | role: {role}") return token