gateway/modules/routes/routeMsft.py

290 lines
11 KiB
Python

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
from modules.security.auth import getCurrentActiveUser, createAccessToken, ACCESS_TOKEN_EXPIRE_MINUTES, getRootInterface
from modules.interfaces.msftInterface import getInterface as getMsftInterface
# 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
msft = getMsftInterface({"_mandateId": "root", "id": "root"})
# 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
msft = getMsftInterface({"_mandateId": "root", "id": "root"})
# Handle auth callback
token_response = msft.handleAuthCallback(code)
if not token_response:
return HTMLResponse(
content="""
<html>
<head>
<title>Authentication Failed</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; margin-top: 50px; }
.error { color: red; }
</style>
</head>
<body>
<h1 class="error">Authentication Failed</h1>
<p>Could not acquire access token.</p>
<script>
setTimeout(() => window.close(), 3000);
</script>
</body>
</html>
""",
status_code=400
)
# Get gateway interface for user operations
gateway = 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="""
<html>
<head>
<title>Registration Failed</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; margin-top: 50px; }
.error { color: red; }
</style>
</head>
<body>
<h1 class="error">Registration Failed</h1>
<p>Could not create user account.</p>
<script>
setTimeout(() => window.close(), 3000);
</script>
</body>
</html>
""",
status_code=400
)
# Create backend token
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = 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"""
<html>
<head>
<title>Authentication Successful</title>
<style>
body {{ font-family: Arial, sans-serif; text-align: center; margin-top: 50px; }}
.success {{ color: green; }}
</style>
</head>
<body>
<h1 class="success">Authentication Successful</h1>
<p>Welcome, {token_response['user_info'].get('name', 'User')}!</p>
<p>This window will close automatically.</p>
<script>
// Store token data in session storage
sessionStorage.setItem('msft_token_data', JSON.stringify({json.dumps(token_response)}));
// Notify parent window of success
if (window.opener) {{
window.opener.postMessage({{
type: 'msft_auth_success',
user: {json.dumps(token_response['user_info'])},
token_data: {json.dumps(token_response)},
access_token: "{access_token}"
}}, '*');
}}
// Close window after 3 seconds
setTimeout(() => window.close(), 3000);
</script>
</body>
</html>
"""
)
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")
async def auth_status(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)):
"""Check Microsoft authentication status"""
try:
# Get Microsoft interface
msft = getMsftInterface(currentUser)
# Get current user token and info
user_info, access_token = msft.getCurrentUserToken()
if not user_info or not access_token:
return JSONResponse({
"authenticated": False,
"message": "Not authenticated with Microsoft"
})
return JSONResponse({
"authenticated": True,
"user": user_info
})
except Exception as e:
logger.error(f"Error checking authentication status: {str(e)}")
return JSONResponse({
"authenticated": False,
"message": f"Error checking authentication status: {str(e)}"
})
@router.post("/save-token")
async def save_token(token_data: Dict[str, Any], currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)):
"""Save Microsoft token data from frontend"""
try:
# Get Microsoft interface
msft = getMsftInterface(currentUser)
# Save token
success = msft.saveMsftToken(token_data)
if success:
return JSONResponse({
"success": True,
"message": "Token saved successfully"
})
else:
return JSONResponse({
"success": False,
"message": "Failed to save token"
})
except Exception as e:
logger.error(f"Error saving token: {str(e)}")
return JSONResponse({
"success": False,
"message": f"Error saving token: {str(e)}"
})
@router.post("/logout")
async def logout(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)):
"""Logout from Microsoft"""
try:
# Get Microsoft interface
msft = getMsftInterface(currentUser)
# Delete token
success = msft.db.deleteToken(currentUser["id"])
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)}"
)
@router.get("/token")
async def get_token(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)):
"""Get Microsoft token for current user."""
try:
# Get Microsoft interface
msft = getMsftInterface(currentUser)
# Get token
token = msft.getMsftToken()
if token:
return {"token": token}
return {"error": "No token found"}
except Exception as e:
logger.error(f"Error getting token: {str(e)}")
return {"error": str(e)}