msft auth integriert

This commit is contained in:
ValueOn AG 2025-05-19 01:14:51 +02:00
parent 40f82a3848
commit db6b5d7985
7 changed files with 397 additions and 218 deletions

View file

@ -394,7 +394,7 @@ class AgentEmail(AgentBase):
logger.error("No mydom interface available") logger.error("No mydom interface available")
return None, None return None, None
# Get token data from database # Get token data from database using LucyDOMInterface
token_data = self.mydom.getMsftToken() token_data = self.mydom.getMsftToken()
if not token_data: if not token_data:
logger.info("No Microsoft token found for user") logger.info("No Microsoft token found for user")
@ -409,7 +409,36 @@ class AgentEmail(AgentBase):
# Get updated token data after refresh # Get updated token data after refresh
token_data = self.mydom.getMsftToken() token_data = self.mydom.getMsftToken()
return token_data.get("user_info"), token_data.get("access_token") # Get user info from token data
user_info = token_data.get("user_info")
if not user_info:
# If user_info is not in token_data, try to get it from the token
headers = {
'Authorization': f'Bearer {token_data.get("access_token", "")}',
'Content-Type': 'application/json'
}
try:
response = requests.get('https://graph.microsoft.com/v1.0/me', headers=headers)
if response.status_code == 200:
user_data = response.json()
user_info = {
"name": user_data.get("displayName", ""),
"email": user_data.get("userPrincipalName", ""),
"id": user_data.get("id", "")
}
# Update token data with user info
token_data["user_info"] = user_info
self.mydom.saveMsftToken(token_data)
logger.info(f"Retrieved and stored user info for {user_info.get('name', 'Unknown User')}")
else:
logger.warning(f"Failed to get user info: {response.status_code} - {response.text}")
return None, None
except Exception as e:
logger.error(f"Error getting user info: {str(e)}")
return None, None
logger.info(f"Retrieved user info for {user_info.get('name', 'Unknown User')}")
return user_info, token_data.get("access_token")
except Exception as e: except Exception as e:
logger.error(f"Error getting current user token: {str(e)}") logger.error(f"Error getting current user token: {str(e)}")

View file

@ -115,6 +115,7 @@ class GatewayInterface:
"disabled": False, "disabled": False,
"language": "de", "language": "de",
"privilege": "sysadmin", "privilege": "sysadmin",
"authenticationAuthority": "local",
"hashedPassword": self._getPasswordHash("The 1st Poweron Admin") # Use a secure password in production! "hashedPassword": self._getPasswordHash("The 1st Poweron Admin") # Use a secure password in production!
} }
createdUser = self.db.recordCreate("users", adminUser) createdUser = self.db.recordCreate("users", adminUser)
@ -280,16 +281,25 @@ class GatewayInterface:
def getUserByUsername(self, username: str) -> Optional[Dict[str, Any]]: def getUserByUsername(self, username: str) -> Optional[Dict[str, Any]]:
"""Returns a user by username.""" """Returns a user by username."""
# Get all users without mandate filter try:
users = self.db.getRecordset("users") # Get users table
for user in users: users = self.db.getRecordset("users")
if user.get("username") == username: if not users:
# Log the fields present in the user record return None
logger.debug(f"Found user {username} with fields: {list(user.keys())}")
# Return a complete copy of the user record with all fields # Find user by username
return {**user} # Use dict unpacking to ensure we get a complete copy with all fields for user in users:
logger.debug(f"No user found with username {username}") if user.get("username") == username:
return None logger.info(f"Found user with username {username}")
logger.debug(f"User fields: {list(user.keys())}")
return user
logger.info(f"No user found with username {username}")
return None
except Exception as e:
logger.error(f"Error getting user by username: {str(e)}")
return None
def getUser(self, _userId: str) -> Optional[Dict[str, Any]]: def getUser(self, _userId: str) -> Optional[Dict[str, Any]]:
"""Returns a user by ID if user has access.""" """Returns a user by ID if user has access."""
@ -311,78 +321,70 @@ class GatewayInterface:
return user return user
def createUser(self, username: str, password: str, email: str = None, def createUser(self, username: str, password: str = None, email: str = None, fullName: str = None,
fullName: str = None, language: str = "de", _mandateId: str = None, language: str = "de", _mandateId: int = None, disabled: bool = False,
disabled: bool = False, privilege: str = "user") -> Dict[str, Any]: privilege: str = "user", authenticationAuthority: str = "local") -> Dict[str, Any]:
"""Creates a new user if current user has permission.""" """Create a new user"""
# Validate username try:
if not username or len(username) < 3: # Validate username
raise ValueError("Benutzername muss mindestens 3 Zeichen lang sein") if not username:
raise ValueError("Username is required")
# Validate password # Check if user already exists with the same authentication authority
if not password: existingUser = self.getUserByUsername(username)
raise ValueError("Passwort ist erforderlich") if existingUser and existingUser.get("authenticationAuthority") == authenticationAuthority:
raise ValueError(f"Username '{username}' already exists with {authenticationAuthority} authentication")
# Password requirements # Validate password for local authentication
if len(password) < 8: if authenticationAuthority == "local":
raise ValueError("Passwort muss mindestens 8 Zeichen lang sein") if not password:
if not any(c.isupper() for c in password): raise ValueError("Password is required for local authentication")
raise ValueError("Passwort muss mindestens einen Grossbuchstaben enthalten") if len(password) < 8:
if not any(c.islower() for c in password): raise ValueError("Password must be at least 8 characters long")
raise ValueError("Passwort muss mindestens einen Kleinbuchstaben enthalten")
if not any(c.isdigit() for c in password):
raise ValueError("Passwort muss mindestens eine Zahl enthalten")
if not any(c in "!@#$%^&*(),.?\":{}|<>" for c in password):
raise ValueError("Passwort muss mindestens ein Sonderzeichen enthalten")
# Validate email if provided # Create user data
if email: userData = {
import re "username": username,
email_pattern = r'^[^\s@]+@[^\s@]+\.[^\s@]+$' "email": email,
if not re.match(email_pattern, email): "fullName": fullName,
raise ValueError("Ungültiges E-Mail-Format") "language": language,
"_mandateId": _mandateId or self._mandateId,
"disabled": disabled,
"privilege": privilege,
"authenticationAuthority": authenticationAuthority
}
# Check if the username already exists # Add password hash for local authentication
existingUser = self.getUserByUsername(username) if authenticationAuthority == "local":
if existingUser: userData["hashedPassword"] = self._getPasswordHash(password)
raise ValueError(f"Benutzer '{username}' existiert bereits")
# Use the provided _mandateId or the current context # Create user record
userMandateId = _mandateId if _mandateId is not None else self._mandateId createdRecord = self.db.recordCreate("users", userData)
if not createdRecord or not createdRecord.get("id"):
raise ValueError("Failed to create user record")
# Check if user has access to the mandate # Get created user using the returned ID
if userMandateId != self._mandateId and self.currentUser.get("privilege") != "sysadmin": createdUser = self.db.getRecordset("users", recordFilter={"id": createdRecord["id"]})
raise PermissionError(f"Keine Berechtigung, Benutzer in Mandat {userMandateId} zu erstellen") if not createdUser or len(createdUser) == 0:
# Try to get user by username as fallback
createdUser = self.db.getRecordset("users", recordFilter={"username": userData["username"]})
if not createdUser or len(createdUser) == 0:
raise ValueError("Failed to retrieve created user")
if not self._canModify("users"): # Clear users table from cache
raise PermissionError("Keine Berechtigung, Benutzer zu erstellen") if hasattr(self.db, '_tablesCache') and "users" in self.db._tablesCache:
del self.db._tablesCache["users"]
# Check privilege escalation return createdUser[0]
if (privilege == "sysadmin" or
(privilege == "admin" and self.currentUser.get("privilege") == "user")):
raise PermissionError(f"Keine Berechtigung, Benutzer mit höherem Privileg zu erstellen: {privilege}")
userData = { except ValueError as e:
"_mandateId": userMandateId, logger.error(f"Error creating user: {str(e)}")
"username": username, raise
"email": email, except Exception as e:
"fullName": fullName, logger.error(f"Unexpected error creating user: {str(e)}")
"disabled": disabled, raise ValueError(f"Failed to create user: {str(e)}")
"language": language,
"privilege": privilege,
"hashedPassword": self._getPasswordHash(password)
}
createdUser = self.db.recordCreate("users", userData) def authenticateUser(self, username: str, password: str = None) -> Optional[Dict[str, Any]]:
# Clear the users table from cache to ensure fresh data
if "users" in self.db._tablesCache:
del self.db._tablesCache["users"]
# Return the complete user record
return createdUser
def authenticateUser(self, username: str, password: str) -> Optional[Dict[str, Any]]:
"""Authenticates a user by username and password.""" """Authenticates a user by username and password."""
# Clear the users table from cache and reload it # Clear the users table from cache and reload it
if "users" in self.db._tablesCache: if "users" in self.db._tablesCache:
@ -394,13 +396,25 @@ class GatewayInterface:
if not user: if not user:
raise ValueError("Benutzer nicht gefunden") raise ValueError("Benutzer nicht gefunden")
if not self._verifyPassword(password, user.get("hashedPassword", "")):
raise ValueError("Falsches Passwort")
# Check if the user is disabled # Check if the user is disabled
if user.get("disabled", False): if user.get("disabled", False):
raise ValueError("Benutzer ist deaktiviert") raise ValueError("Benutzer ist deaktiviert")
# Handle authentication based on authority
auth_authority = user.get("authenticationAuthority", "local")
if auth_authority == "local":
if not password:
raise ValueError("Passwort ist erforderlich")
if not self._verifyPassword(password, user.get("hashedPassword", "")):
raise ValueError("Falsches Passwort")
elif auth_authority == "microsoft":
# For Microsoft users, we don't verify the password here
# The authentication is handled by the Microsoft OAuth flow
pass
else:
raise ValueError(f"Unbekannte Authentifizierungsmethode: {auth_authority}")
# Create a copy without password hash # Create a copy without password hash
authenticatedUser = {**user} authenticatedUser = {**user}
if "hashedPassword" in authenticatedUser: if "hashedPassword" in authenticatedUser:

View file

@ -46,6 +46,7 @@ class User(BaseModel):
language: str = Field(description="Preferred language of the user") language: str = Field(description="Preferred language of the user")
disabled: Optional[bool] = Field(False, description="Indicates whether the user is disabled") disabled: Optional[bool] = Field(False, description="Indicates whether the user is disabled")
privilege: str = Field(description="Permission level") #sysadmin,admin,user privilege: str = Field(description="Permission level") #sysadmin,admin,user
authenticationAuthority: str = Field(default="local", description="Authentication authority (local, microsoft)")
label: Label = Field( label: Label = Field(
default=Label(default="User", translations={"en": "User", "fr": "Utilisateur"}), default=Label(default="User", translations={"en": "User", "fr": "Utilisateur"}),
@ -62,6 +63,7 @@ class User(BaseModel):
"language": Label(default="Language", translations={"en": "Language", "fr": "Langue"}), "language": Label(default="Language", translations={"en": "Language", "fr": "Langue"}),
"disabled": Label(default="Disabled", translations={"en": "Disabled", "fr": "Désactivé"}), "disabled": Label(default="Disabled", translations={"en": "Disabled", "fr": "Désactivé"}),
"privilege": Label(default="Permission level", translations={"en": "Access level", "fr": "Niveau d'accès"}), "privilege": Label(default="Permission level", translations={"en": "Access level", "fr": "Niveau d'accès"}),
"authenticationAuthority": Label(default="Authentication Authority", translations={"en": "Authentication Authority", "fr": "Autorité d'authentification"})
} }

View file

@ -30,7 +30,7 @@ router.mount("/static", StaticFiles(directory=str(staticFolder), html=True), nam
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@router.get("/favicon.ico") @router.get("/favicon.ico", tags=["General"])
async def favicon(): async def favicon():
return FileResponse(str(staticFolder / "favicon.ico"), media_type="image/x-icon") return FileResponse(str(staticFolder / "favicon.ico"), media_type="image/x-icon")
@ -83,12 +83,13 @@ async def loginForAccessToken(formData: OAuth2PasswordRequestForm = Depends()):
data={ data={
"sub": user["username"], "sub": user["username"],
"_mandateId": str(user["_mandateId"]), # Ensure string "_mandateId": str(user["_mandateId"]), # Ensure string
"_userId": str(user["id"]) # Ensure string "_userId": str(user["id"]), # Ensure string
"authenticationAuthority": user.get("authenticationAuthority", "local") # Add auth authority
}, },
expiresDelta=accessTokenExpires expiresDelta=accessTokenExpires
) )
logger.info(f"User {user['username']} successfully logged in with context: _mandateId={user['_mandateId']}, _userId={user['id']}") 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"} return {"accessToken": accessToken, "tokenType": "bearer"}
except ValueError as e: except ValueError as e:
# Handle authentication errors # Handle authentication errors
@ -196,3 +197,49 @@ async def registerUser(userData: Dict[str, Any]):
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to register user" 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)}"
)

View file

@ -62,6 +62,13 @@ async def save_token_to_file(token_data, currentUser: Dict[str, Any]):
logger.error("No LucyDOM interface available for token storage") logger.error("No LucyDOM interface available for token storage")
return False return False
# Ensure user info is preserved
if "user_info" not in token_data:
# Try to get user info from the token
user_info = get_user_info_from_token(token_data.get("access_token", ""))
if user_info:
token_data["user_info"] = user_info
# Save token to database # Save token to database
success = mydom.saveMsftToken(token_data) success = mydom.saveMsftToken(token_data)
if success: if success:
@ -217,18 +224,18 @@ async def login():
async def auth_callback(code: str, state: str, request: Request): async def auth_callback(code: str, state: str, request: Request):
"""Handle Microsoft OAuth callback""" """Handle Microsoft OAuth callback"""
try: try:
# Create MSAL app instance # Create a confidential client application
app = msal.ConfidentialClientApplication( msal_app = msal.ConfidentialClientApplication(
client_id=CLIENT_ID, app_config["client_id"],
client_credential=CLIENT_SECRET, authority=app_config["authority"],
authority=AUTHORITY client_credential=app_config["client_credential"]
) )
# Exchange code for token # Exchange the authorization code for tokens
token_response = app.acquire_token_by_authorization_code( token_response = msal_app.acquire_token_by_authorization_code(
code=code, code,
scopes=SCOPES, SCOPES,
redirect_uri=REDIRECT_URI redirect_uri=app_config["redirect_uri"]
) )
if "error" in token_response: if "error" in token_response:
@ -245,7 +252,7 @@ async def auth_callback(code: str, state: str, request: Request):
</head> </head>
<body> <body>
<h1 class="error">Authentication Failed</h1> <h1 class="error">Authentication Failed</h1>
<p>Please try again.</p> <p>Could not acquire access token.</p>
<script> <script>
setTimeout(() => window.close(), 3000); setTimeout(() => window.close(), 3000);
</script> </script>
@ -255,8 +262,9 @@ async def auth_callback(code: str, state: str, request: Request):
status_code=400 status_code=400
) )
# Get user info from token # Get user info from the token
user_info = get_user_info_from_token(token_response["access_token"]) user_info = get_user_info_from_token(token_response["access_token"])
if not user_info: if not user_info:
logger.error("Failed to get user info from token") logger.error("Failed to get user info from token")
return HTMLResponse( return HTMLResponse(
@ -281,7 +289,72 @@ async def auth_callback(code: str, state: str, request: Request):
status_code=400 status_code=400
) )
# Add user info to token data # Get gateway interface for user operations
gateway = getGatewayInterface()
# Check if user exists
user = gateway.getUserByUsername(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=user_info["email"],
email=user_info["email"],
fullName=user_info.get("name", user_info["email"]),
_mandateId=rootMandateId,
authenticationAuthority="microsoft"
)
logger.info(f"Created new user for Microsoft account: {user_info['email']}")
# Verify user was created by retrieving it
user = gateway.getUserByUsername(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
)
# Add user info to token response
token_response["user_info"] = user_info token_response["user_info"] = user_info
# Store tokens in session storage for the frontend to pick up # Store tokens in session storage for the frontend to pick up
@ -308,7 +381,8 @@ async def auth_callback(code: str, state: str, request: Request):
window.opener.postMessage({{ window.opener.postMessage({{
type: 'msft_auth_success', type: 'msft_auth_success',
user: {json.dumps(user_info)}, user: {json.dumps(user_info)},
token_data: {json.dumps(token_response)} token_data: {json.dumps(token_response)},
access_token: "{access_token}"
}}, '*'); }}, '*');
}} }}
// Close window after 3 seconds // Close window after 3 seconds
@ -322,27 +396,10 @@ async def auth_callback(code: str, state: str, request: Request):
return response return response
except Exception as e: except Exception as e:
logger.error(f"Authentication failed: {str(e)}") logger.error(f"Error in auth callback: {str(e)}")
return HTMLResponse( raise HTTPException(
content=""" status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
<html> detail=f"Authentication failed: {str(e)}"
<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>An error occurred during authentication.</p>
<script>
setTimeout(() => window.close(), 3000);
</script>
</body>
</html>
""",
status_code=500
) )
@router.get("/status") @router.get("/status")
@ -368,8 +425,9 @@ async def auth_status(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser
"message": "Not authenticated with Microsoft" "message": "Not authenticated with Microsoft"
}) })
# Verify token is still valid # Verify token is still valid and get user info
if not verify_token(token_data["access_token"]): user_info = get_user_info_from_token(token_data["access_token"])
if not user_info:
logger.info("Token invalid, attempting refresh") logger.info("Token invalid, attempting refresh")
# Try to refresh the token # Try to refresh the token
if not await refresh_token(_userId, currentUser): if not await refresh_token(_userId, currentUser):
@ -380,15 +438,13 @@ async def auth_status(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser
}) })
# Reload token data after refresh # Reload token data after refresh
token_data = await load_token_from_file(currentUser) token_data = await load_token_from_file(currentUser)
# Get user info again after refresh
# Get user info from token data user_info = get_user_info_from_token(token_data["access_token"])
user_info = token_data.get("user_info") if not user_info:
if not user_info: return JSONResponse({
logger.info("No user info found in token data") "authenticated": False,
return JSONResponse({ "message": "Could not get user info after token refresh"
"authenticated": False, })
"message": "No user information available"
})
logger.info(f"User {user_info.get('name')} is authenticated") logger.info(f"User {user_info.get('name')} is authenticated")
return JSONResponse({ return JSONResponse({

View file

@ -76,8 +76,7 @@ async def registerUser(request: Request):
"""Register a new user.""" """Register a new user."""
try: try:
# Get request data # Get request data
data = await request.json() userData = await request.json()
logger.info(f"Registration request data: {data}")
# Get root mandate and admin user IDs # Get root mandate and admin user IDs
adminGateway = getGatewayInterface() adminGateway = getGatewayInterface()
@ -86,91 +85,110 @@ async def registerUser(request: Request):
if not rootMandateId or not adminUserId: if not rootMandateId or not adminUserId:
raise HTTPException( raise HTTPException(
status_code=500, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="System is not properly initialized with root mandate and admin user" detail="System is not properly initialized with root mandate and admin user"
) )
# Create a new gateway interface instance with admin context # Create a new gateway interface instance with admin context
adminGateway = getGatewayInterface(rootMandateId, adminUserId) adminGateway = getGatewayInterface(rootMandateId, adminUserId)
# Check required fields # Set default values if not provided
if not data.get("username") or not data.get("password"): if "language" not in userData:
logger.error("Missing required fields in registration request") userData["language"] = "en"
raise HTTPException(status_code=400, detail="Username and password are required") if "authenticationAuthority" not in userData:
userData["authenticationAuthority"] = "local"
# Create user data # Validate authentication authority
userData = { if userData["authenticationAuthority"] not in ["local", "microsoft"]:
"username": data["username"], raise HTTPException(
"password": data["password"], status_code=status.HTTP_400_BAD_REQUEST,
"email": data.get("email"), detail=f"Invalid authentication authority: {userData['authenticationAuthority']}"
"fullName": data.get("fullName"), )
"language": data.get("language", "de"),
"_mandateId": rootMandateId, # Validate password for local authentication
"disabled": False, if userData["authenticationAuthority"] == "local":
"privilege": "user" if "password" not in userData:
} raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Password is required for local authentication"
)
# Create the user # Create the user
logger.info(f"Attempting to create user with data: {userData}")
createdUser = adminGateway.createUser(**userData)
logger.info(f"User created successfully: {createdUser}")
# Add a small delay to ensure database consistency
time.sleep(0.5)
# Verify the user was created and password was stored
if "hashedPassword" not in createdUser:
logger.error("Password not stored in user record")
# Try to delete the user
try:
adminGateway.deleteUser(createdUser["id"])
logger.info("Successfully deleted user after password storage failure")
except Exception as e:
logger.error(f"Failed to delete user after password storage failure: {str(e)}")
raise HTTPException(status_code=500, detail="Password storage failed")
logger.info("User verification successful")
# Test authentication
try: try:
authResult = adminGateway.authenticateUser(userData["username"], userData["password"]) createdUser = adminGateway.createUser(
if not authResult: username=userData["username"],
logger.error("Authentication test failed after user creation") password=userData.get("password"),
email=userData.get("email"),
fullName=userData.get("fullName"),
language=userData["language"],
_mandateId=userData.get("_mandateId", rootMandateId),
authenticationAuthority=userData["authenticationAuthority"]
)
except ValueError as e:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=str(e)
)
# Verify the user was created
if not createdUser:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to create user"
)
# For local authentication, verify password was stored
if userData["authenticationAuthority"] == "local":
if "hashedPassword" not in createdUser:
logger.error("Password not stored in user record")
# Try to delete the user
try:
adminGateway.deleteUser(createdUser["id"])
logger.info("Successfully deleted user after password storage failure")
except Exception as e:
logger.error(f"Failed to delete user after password storage failure: {str(e)}")
raise HTTPException(status_code=500, detail="Password storage failed")
logger.info("User verification successful")
# Test authentication
try:
authResult = adminGateway.authenticateUser(userData["username"], userData["password"])
if not authResult:
logger.error("Authentication test failed after user creation")
# Try to delete the user
try:
adminGateway.deleteUser(createdUser["id"])
logger.info("Successfully deleted user after authentication test failure")
except Exception as e:
logger.error(f"Failed to delete user after authentication test failure: {str(e)}")
raise HTTPException(status_code=500, detail="Authentication test failed")
except ValueError as e:
logger.error(f"Authentication test failed: {str(e)}")
# Try to delete the user # Try to delete the user
try: try:
adminGateway.deleteUser(createdUser["id"]) adminGateway.deleteUser(createdUser["id"])
logger.info("Successfully deleted user after authentication test failure") logger.info("Successfully deleted user after authentication test failure")
except Exception as e: except Exception as e:
logger.error(f"Failed to delete user after authentication test failure: {str(e)}") logger.error(f"Failed to delete user after authentication test failure: {str(e)}")
raise HTTPException(status_code=500, detail="Authentication test failed") raise HTTPException(status_code=500, detail=f"Authentication test failed: {str(e)}")
except ValueError as e:
logger.error(f"Authentication test failed: {str(e)}")
# Try to delete the user
try:
adminGateway.deleteUser(createdUser["id"])
logger.info("Successfully deleted user after authentication test failure")
except Exception as e:
logger.error(f"Failed to delete user after authentication test failure: {str(e)}")
raise HTTPException(status_code=500, detail=f"Authentication test failed: {str(e)}")
logger.info("Authentication test successful") logger.info("Authentication test successful")
# Return success response # Remove sensitive data from response
return { if "hashedPassword" in createdUser:
"message": "User registered successfully", del createdUser["hashedPassword"]
"userId": createdUser["id"]
}
except ValueError as e: return createdUser
logger.error(f"Validation error during registration: {str(e)}")
raise HTTPException(status_code=400, detail=str(e)) except HTTPException:
except PermissionError as e: raise
logger.error(f"Permission error during registration: {str(e)}")
raise HTTPException(status_code=403, detail=str(e))
except Exception as e: except Exception as e:
logger.error(f"Unexpected error during registration: {str(e)}") logger.error(f"Unexpected error during user registration: {str(e)}")
logger.error(traceback.format_exc()) raise HTTPException(
raise HTTPException(status_code=500, detail="Internal server error") status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Registration failed: {str(e)}"
)
@router.post("/register-with-msal", response_model=Dict[str, Any]) @router.post("/register-with-msal", response_model=Dict[str, Any])
async def registerUserWithMsal(userData: dict = Body(...)): async def registerUserWithMsal(userData: dict = Body(...)):

View file

@ -106,26 +106,11 @@ async def getCurrentUser(token: str = Depends(oauth2Scheme)) -> Dict[str, Any]:
logger.error(f"User context mismatch: token(_mandateId={_mandateId}, _userId={_userId}) vs user(_mandateId={user.get('_mandateId')}, id={user.get('id')})") logger.error(f"User context mismatch: token(_mandateId={_mandateId}, _userId={_userId}) vs user(_mandateId={user.get('_mandateId')}, id={user.get('id')})")
raise credentialsException raise credentialsException
# Add authentication authority to user data
user["authenticationAuthority"] = user.get("authenticationAuthority", "local")
return user return user
async def getCurrentActiveUser(currentUser: Dict[str, Any] = Depends(getCurrentUser)) -> Dict[str, Any]:
"""
Ensures that the user is active.
Args:
currentUser: Current user data
Returns:
User data
Raises:
HTTPException: If the user is disabled
"""
if currentUser.get("disabled", False):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User is disabled")
return currentUser
async def getUserContext(currentUser: Dict[str, Any]) -> Tuple[str, str]: async def getUserContext(currentUser: Dict[str, Any]) -> Tuple[str, str]:
""" """
Extracts the mandate ID and user ID from the current user. Extracts the mandate ID and user ID from the current user.
@ -171,3 +156,31 @@ def getInitialContext() -> tuple[str, str]:
mandateId = gateway.getInitialId("mandates") mandateId = gateway.getInitialId("mandates")
userId = gateway.getInitialId("users") userId = gateway.getInitialId("users")
return mandateId, userId return mandateId, userId
async def getCurrentActiveUser(currentUser: Dict[str, Any] = Depends(getCurrentUser)) -> Dict[str, Any]:
"""
Gets the current active user and verifies their authentication authority.
Args:
currentUser: The current user from getCurrentUser
Returns:
The current user data
Raises:
HTTPException: If user is disabled or has invalid authentication authority
"""
if currentUser.get("disabled", False):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="User is disabled"
)
auth_authority = currentUser.get("authenticationAuthority", "local")
if auth_authority not in ["local", "microsoft"]:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"Invalid authentication authority: {auth_authority}"
)
return currentUser