gateway/modules/routes/routeGeneral.py
2025-05-19 01:14:51 +02:00

245 lines
No EOL
9.6 KiB
Python

from fastapi import APIRouter, HTTPException, Depends, Body, status, Response
from fastapi.responses import FileResponse
from fastapi.security import OAuth2PasswordRequestForm
from fastapi.staticfiles import StaticFiles
from typing import Dict, Any
from datetime import timedelta
import pathlib
import os
import logging
from modules.shared.configuration import APP_CONFIG
from modules.security.auth import (
createAccessToken,
getCurrentActiveUser,
getUserContext,
ACCESS_TOKEN_EXPIRE_MINUTES
)
import modules.interfaces.gatewayModel as gatewayModel
from modules.interfaces.gatewayInterface import getGatewayInterface
router = APIRouter()
# Static folder setup - using absolute path from app root
baseDir = pathlib.Path(__file__).parent.parent.parent # Go up to gateway root
staticFolder = baseDir / "static"
os.makedirs(staticFolder, exist_ok=True)
# Mount static files
router.mount("/static", StaticFiles(directory=str(staticFolder), html=True), name="static")
logger = logging.getLogger(__name__)
@router.get("/favicon.ico", tags=["General"])
async def favicon():
return FileResponse(str(staticFolder / "favicon.ico"), media_type="image/x-icon")
@router.get("/", tags=["General"])
async def root():
"""API status endpoint"""
return {"status": "online", "message": "Data Platform API is active"}
@router.get("/api/test", tags=["General"])
async def getTest():
return f"Status: OK. Alowed origins: {APP_CONFIG.get('APP_ALLOWED_ORIGINS')}"
@router.options("/{fullPath:path}", tags=["General"])
async def optionsRoute(fullPath: str):
return Response(status_code=200)
@router.get("/api/environment", tags=["General"])
async def get_environment():
"""Get environment configuration for frontend"""
return {
"apiBaseUrl": APP_CONFIG.get("APP_API_URL", ""),
"environment": APP_CONFIG.get("APP_ENV", "development"),
"instanceLabel": APP_CONFIG.get("APP_ENV_LABEL", "Development"),
# Add other environment variables the frontend might need
}
@router.post("/api/token", response_model=gatewayModel.Token, tags=["General"])
async def loginForAccessToken(formData: OAuth2PasswordRequestForm = Depends()):
# Get root mandate and admin user IDs
adminGateway = getGatewayInterface()
rootMandateId = adminGateway.getInitialId("mandates")
adminUserId = adminGateway.getInitialId("users")
if not rootMandateId or not adminUserId:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="System is not properly initialized with root mandate and admin user"
)
# Create a new gateway interface instance with admin context
adminGateway = getGatewayInterface(rootMandateId, adminUserId)
try:
# Authenticate user
user = adminGateway.authenticateUser(formData.username, formData.password)
# Create token with mandate ID and user ID
accessTokenExpires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
accessToken = createAccessToken(
data={
"sub": user["username"],
"_mandateId": str(user["_mandateId"]), # Ensure string
"_userId": str(user["id"]), # Ensure string
"authenticationAuthority": user.get("authenticationAuthority", "local") # Add auth authority
},
expiresDelta=accessTokenExpires
)
logger.info(f"User {user['username']} successfully logged in with context: _mandateId={user['_mandateId']}, _userId={user['id']}, auth={user.get('authenticationAuthority', 'local')}")
return {"accessToken": accessToken, "tokenType": "bearer"}
except ValueError as e:
# Handle authentication errors
error_msg = str(e)
logger.warning(f"Authentication failed for user {formData.username}: {error_msg}")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=error_msg,
headers={"WWW-Authenticate": "Bearer"},
)
except Exception as e:
# Handle other errors
error_msg = f"Login failed: {str(e)}"
logger.error(f"Unexpected error during login for user {formData.username}: {error_msg}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=error_msg
)
@router.get("/api/user/me", response_model=Dict[str, Any], tags=["General"])
async def readUserMe(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)):
return currentUser
@router.post("/api/users/register", response_model=Dict[str, Any], tags=["General"])
async def registerUser(userData: Dict[str, Any]):
"""Register a new user."""
try:
logger.info("Received registration request")
logger.info(f"Raw userData type: {type(userData)}")
logger.info(f"Raw userData content: {userData}")
# Get root mandate and admin user IDs
adminGateway = getGatewayInterface()
rootMandateId = adminGateway.getInitialId("mandates")
adminUserId = adminGateway.getInitialId("users")
if not rootMandateId or not adminUserId:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="System is not properly initialized with root mandate and admin user"
)
# Create a new gateway interface instance with admin context
adminGateway = getGatewayInterface(rootMandateId, adminUserId)
# Check required fields
if not userData or not isinstance(userData, dict):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid user data format"
)
if not userData.get("username") or not userData.get("password"):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Username and password are required"
)
# Create user data with mandate ID
userData = {
"username": userData["username"],
"password": userData["password"],
"email": userData.get("email"),
"fullName": userData.get("fullName"),
"language": userData.get("language", "de"),
"_mandateId": rootMandateId,
"disabled": False,
"privilege": "user"
}
# Create the user
createdUser = adminGateway.createUser(**userData)
if not createdUser:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to create user"
)
# Clear the users table from cache to ensure fresh data
if hasattr(adminGateway.db, '_tablesCache') and "users" in adminGateway.db._tablesCache:
del adminGateway.db._tablesCache["users"]
# Return the created user (without password)
if "hashedPassword" in createdUser:
del createdUser["hashedPassword"]
return createdUser
except ValueError as e:
logger.error(f"ValueError during registration: {str(e)}")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=str(e)
)
except PermissionError as e:
logger.error(f"PermissionError during registration: {str(e)}")
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=str(e)
)
except Exception as e:
logger.error(f"Error during user registration: {str(e)}")
logger.error(f"Error type: {type(e)}")
logger.error(f"Error details: {e.__dict__ if hasattr(e, '__dict__') else 'No details available'}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to register user"
)
@router.get("/api/user/available", response_model=Dict[str, Any], tags=["General"])
async def checkUsernameAvailability(
username: str,
authenticationAuthority: str = "local"
):
"""Check if a username is available for registration"""
try:
# Get root mandate and admin user IDs
adminGateway = getGatewayInterface()
rootMandateId = adminGateway.getInitialId("mandates")
adminUserId = adminGateway.getInitialId("users")
if not rootMandateId or not adminUserId:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="System is not properly initialized with root mandate and admin user"
)
# Create a new gateway interface instance with admin context
adminGateway = getGatewayInterface(rootMandateId, adminUserId)
# Check if user exists
existingUser = adminGateway.getUserByUsername(username)
if not existingUser:
return {"available": True}
# If user exists, check authentication authority
if existingUser.get("authenticationAuthority") == authenticationAuthority:
return {
"available": False,
"message": f"Username already exists with {authenticationAuthority} authentication"
}
else:
return {
"available": True,
"message": f"Username exists but with different authentication authority"
}
except Exception as e:
logger.error(f"Error checking username availability: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to check username availability: {str(e)}"
)