""" Routes for Microsoft authentication. """ from fastapi import APIRouter, HTTPException, Depends, Request, Response, status, Cookie, Body from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse import logging import json from typing import Dict, Any, Optional, List from datetime import datetime, timedelta # Import auth module import modules.security.auth as auth # Import interfaces import modules.interfaces.msftInterface as msftInterface import modules.interfaces.gatewayInterface as gatewayInterface from modules.interfaces.msftModel import ( MsftToken, MsftUserInfo, MsftAuthStatus, MsftTokenResponse, MsftSaveTokenResponse ) # Configure logger logger = logging.getLogger(__name__) # Create router for Microsoft Auth endpoints router = APIRouter( prefix="/api/msft", tags=["Microsoft"], responses={ 404: {"description": "Not found"}, 400: {"description": "Bad request"}, 401: {"description": "Unauthorized"}, 403: {"description": "Forbidden"}, 500: {"description": "Internal server error"} } ) @router.get("/login") async def login(): """Initiate Microsoft login for the current user""" try: # Get Microsoft interface with root context for initial setup msft = msftInterface.getRootInterface() # Get login URL auth_url = msft.initiateLogin() if not auth_url: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to initiate Microsoft login" ) logger.info("Redirecting to Microsoft login") return RedirectResponse(auth_url) except Exception as e: logger.error(f"Error initiating Microsoft login: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to initiate Microsoft login: {str(e)}" ) @router.get("/auth/callback") async def auth_callback(code: str, state: str, request: Request): """Handle Microsoft OAuth callback""" try: # Get Microsoft interface with root context for initial setup msft = msftInterface.getRootInterface() # Handle auth callback token_response = msft.handleAuthCallback(code) if not token_response: return HTMLResponse( content=""" Authentication Failed

Authentication Failed

Could not acquire access token.

""", status_code=400 ) # Get gateway interface for user operations gateway = gatewayInterface.getRootInterface() # Check if user exists user = gateway.getUserByUsername(token_response.user_info["email"]) # If user doesn't exist, create a new user in the default mandate if not user: try: # Get the root mandate ID rootMandateId = gateway.getInitialId("mandates") if not rootMandateId: raise ValueError("Root mandate not found") # Create new user with Microsoft authentication user = gateway.createUser( username=token_response.user_info["email"], email=token_response.user_info["email"], fullName=token_response.user_info.get("name", token_response.user_info["email"]), mandateId=rootMandateId, authenticationAuthority="microsoft" ) logger.info(f"Created new user for Microsoft account: {token_response.user_info['email']}") # Verify user was created by retrieving it user = gateway.getUserByUsername(token_response.user_info["email"]) if not user: raise ValueError("Failed to retrieve created user") except Exception as e: logger.error(f"Failed to create user for Microsoft account: {str(e)}") return HTMLResponse( content=""" Registration Failed

Registration Failed

Could not create user account.

""", status_code=400 ) # Create backend token access_token_expires = timedelta(minutes=auth.ACCESS_TOKEN_EXPIRE_MINUTES) access_token = auth.createAccessToken( data={ "sub": user["username"], "mandateId": str(user["mandateId"]), "userId": str(user["id"]), "authenticationAuthority": "microsoft" }, expiresDelta=access_token_expires ) # Store tokens in session storage for the frontend to pick up response = HTMLResponse( content=f""" Authentication Successful

Authentication Successful

Welcome, {token_response.user_info.get('name', 'User')}!

This window will close automatically.

""" ) return response except Exception as e: logger.error(f"Error in auth callback: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Authentication failed: {str(e)}" ) @router.get("/status", response_model=MsftAuthStatus) async def auth_status(currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)): """Check Microsoft authentication status""" try: # For authenticated endpoints, use the current user's context msft = msftInterface.getInterface(currentUser) # Get current user token and info user_info, access_token = msft.getCurrentUserToken() if not user_info or not access_token: return MsftAuthStatus( authenticated=False, message="Not authenticated with Microsoft" ) # Convert user_info to MsftUserInfo model user_info_model = MsftUserInfo(**user_info) return MsftAuthStatus( authenticated=True, user=user_info_model ) except Exception as e: logger.error(f"Error checking authentication status: {str(e)}") return MsftAuthStatus( authenticated=False, message=f"Error checking authentication status: {str(e)}" ) @router.get("/token", response_model=MsftTokenResponse) async def get_token(currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)): """Get Microsoft token for current user.""" try: # For authenticated endpoints, use the current user's context msft = msftInterface.getInterface(currentUser) # Get token token_data = msft.getMsftToken() if not token_data: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="No token found" ) # Convert to MsftToken model token = MsftToken(**token_data) return MsftTokenResponse(token=token) except Exception as e: logger.error(f"Error getting token: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e) ) @router.post("/save-token", response_model=MsftSaveTokenResponse) async def save_token( token_data: MsftToken, currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser) ): """Save Microsoft token data from frontend""" try: # For authenticated endpoints, use the current user's context msft = msftInterface.getInterface(currentUser) # Save token success = msft.saveMsftToken(token_data.model_dump()) if success: return MsftSaveTokenResponse( success=True, message="Token saved successfully", token=token_data ) else: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to save token" ) except Exception as e: logger.error(f"Error saving token: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error saving token: {str(e)}" ) @router.post("/logout") async def logout(currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)): """Logout from Microsoft""" try: # For authenticated endpoints, use the current user's context msft = msftInterface.getInterface(currentUser) # Delete token success = msft.deleteMsftToken() if success: return JSONResponse({ "message": "Successfully logged out from Microsoft" }) else: return JSONResponse({ "message": "Failed to logout from Microsoft" }) except Exception as e: logger.error(f"Error during logout: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Logout failed: {str(e)}" )