290 lines
11 KiB
Python
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)}
|