msft auth integriert
This commit is contained in:
parent
40f82a3848
commit
db6b5d7985
7 changed files with 397 additions and 218 deletions
|
|
@ -394,7 +394,7 @@ class AgentEmail(AgentBase):
|
|||
logger.error("No mydom interface available")
|
||||
return None, None
|
||||
|
||||
# Get token data from database
|
||||
# Get token data from database using LucyDOMInterface
|
||||
token_data = self.mydom.getMsftToken()
|
||||
if not token_data:
|
||||
logger.info("No Microsoft token found for user")
|
||||
|
|
@ -409,7 +409,36 @@ class AgentEmail(AgentBase):
|
|||
# Get updated token data after refresh
|
||||
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:
|
||||
logger.error(f"Error getting current user token: {str(e)}")
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ class GatewayInterface:
|
|||
"disabled": False,
|
||||
"language": "de",
|
||||
"privilege": "sysadmin",
|
||||
"authenticationAuthority": "local",
|
||||
"hashedPassword": self._getPasswordHash("The 1st Poweron Admin") # Use a secure password in production!
|
||||
}
|
||||
createdUser = self.db.recordCreate("users", adminUser)
|
||||
|
|
@ -280,16 +281,25 @@ class GatewayInterface:
|
|||
|
||||
def getUserByUsername(self, username: str) -> Optional[Dict[str, Any]]:
|
||||
"""Returns a user by username."""
|
||||
# Get all users without mandate filter
|
||||
users = self.db.getRecordset("users")
|
||||
for user in users:
|
||||
if user.get("username") == username:
|
||||
# Log the fields present in the user record
|
||||
logger.debug(f"Found user {username} with fields: {list(user.keys())}")
|
||||
# Return a complete copy of the user record with all fields
|
||||
return {**user} # Use dict unpacking to ensure we get a complete copy with all fields
|
||||
logger.debug(f"No user found with username {username}")
|
||||
return None
|
||||
try:
|
||||
# Get users table
|
||||
users = self.db.getRecordset("users")
|
||||
if not users:
|
||||
return None
|
||||
|
||||
# Find user by username
|
||||
for user in users:
|
||||
if user.get("username") == username:
|
||||
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]]:
|
||||
"""Returns a user by ID if user has access."""
|
||||
|
|
@ -311,78 +321,70 @@ class GatewayInterface:
|
|||
|
||||
return user
|
||||
|
||||
def createUser(self, username: str, password: str, email: str = None,
|
||||
fullName: str = None, language: str = "de", _mandateId: str = None,
|
||||
disabled: bool = False, privilege: str = "user") -> Dict[str, Any]:
|
||||
"""Creates a new user if current user has permission."""
|
||||
# Validate username
|
||||
if not username or len(username) < 3:
|
||||
raise ValueError("Benutzername muss mindestens 3 Zeichen lang sein")
|
||||
def createUser(self, username: str, password: str = None, email: str = None, fullName: str = None,
|
||||
language: str = "de", _mandateId: int = None, disabled: bool = False,
|
||||
privilege: str = "user", authenticationAuthority: str = "local") -> Dict[str, Any]:
|
||||
"""Create a new user"""
|
||||
try:
|
||||
# Validate username
|
||||
if not username:
|
||||
raise ValueError("Username is required")
|
||||
|
||||
# Validate password
|
||||
if not password:
|
||||
raise ValueError("Passwort ist erforderlich")
|
||||
# Check if user already exists with the same authentication authority
|
||||
existingUser = self.getUserByUsername(username)
|
||||
if existingUser and existingUser.get("authenticationAuthority") == authenticationAuthority:
|
||||
raise ValueError(f"Username '{username}' already exists with {authenticationAuthority} authentication")
|
||||
|
||||
# Password requirements
|
||||
if len(password) < 8:
|
||||
raise ValueError("Passwort muss mindestens 8 Zeichen lang sein")
|
||||
if not any(c.isupper() for c in password):
|
||||
raise ValueError("Passwort muss mindestens einen Grossbuchstaben enthalten")
|
||||
if not any(c.islower() for c in password):
|
||||
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 password for local authentication
|
||||
if authenticationAuthority == "local":
|
||||
if not password:
|
||||
raise ValueError("Password is required for local authentication")
|
||||
if len(password) < 8:
|
||||
raise ValueError("Password must be at least 8 characters long")
|
||||
|
||||
# Validate email if provided
|
||||
if email:
|
||||
import re
|
||||
email_pattern = r'^[^\s@]+@[^\s@]+\.[^\s@]+$'
|
||||
if not re.match(email_pattern, email):
|
||||
raise ValueError("Ungültiges E-Mail-Format")
|
||||
# Create user data
|
||||
userData = {
|
||||
"username": username,
|
||||
"email": email,
|
||||
"fullName": fullName,
|
||||
"language": language,
|
||||
"_mandateId": _mandateId or self._mandateId,
|
||||
"disabled": disabled,
|
||||
"privilege": privilege,
|
||||
"authenticationAuthority": authenticationAuthority
|
||||
}
|
||||
|
||||
# Check if the username already exists
|
||||
existingUser = self.getUserByUsername(username)
|
||||
if existingUser:
|
||||
raise ValueError(f"Benutzer '{username}' existiert bereits")
|
||||
# Add password hash for local authentication
|
||||
if authenticationAuthority == "local":
|
||||
userData["hashedPassword"] = self._getPasswordHash(password)
|
||||
|
||||
# Use the provided _mandateId or the current context
|
||||
userMandateId = _mandateId if _mandateId is not None else self._mandateId
|
||||
# Create user record
|
||||
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
|
||||
if userMandateId != self._mandateId and self.currentUser.get("privilege") != "sysadmin":
|
||||
raise PermissionError(f"Keine Berechtigung, Benutzer in Mandat {userMandateId} zu erstellen")
|
||||
# Get created user using the returned ID
|
||||
createdUser = self.db.getRecordset("users", recordFilter={"id": createdRecord["id"]})
|
||||
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"):
|
||||
raise PermissionError("Keine Berechtigung, Benutzer zu erstellen")
|
||||
# Clear users table from cache
|
||||
if hasattr(self.db, '_tablesCache') and "users" in self.db._tablesCache:
|
||||
del self.db._tablesCache["users"]
|
||||
|
||||
# Check privilege escalation
|
||||
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}")
|
||||
return createdUser[0]
|
||||
|
||||
userData = {
|
||||
"_mandateId": userMandateId,
|
||||
"username": username,
|
||||
"email": email,
|
||||
"fullName": fullName,
|
||||
"disabled": disabled,
|
||||
"language": language,
|
||||
"privilege": privilege,
|
||||
"hashedPassword": self._getPasswordHash(password)
|
||||
}
|
||||
except ValueError as e:
|
||||
logger.error(f"Error creating user: {str(e)}")
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error creating user: {str(e)}")
|
||||
raise ValueError(f"Failed to create user: {str(e)}")
|
||||
|
||||
createdUser = self.db.recordCreate("users", userData)
|
||||
|
||||
# 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]]:
|
||||
def authenticateUser(self, username: str, password: str = None) -> Optional[Dict[str, Any]]:
|
||||
"""Authenticates a user by username and password."""
|
||||
# Clear the users table from cache and reload it
|
||||
if "users" in self.db._tablesCache:
|
||||
|
|
@ -394,13 +396,25 @@ class GatewayInterface:
|
|||
if not user:
|
||||
raise ValueError("Benutzer nicht gefunden")
|
||||
|
||||
if not self._verifyPassword(password, user.get("hashedPassword", "")):
|
||||
raise ValueError("Falsches Passwort")
|
||||
|
||||
# Check if the user is disabled
|
||||
if user.get("disabled", False):
|
||||
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
|
||||
authenticatedUser = {**user}
|
||||
if "hashedPassword" in authenticatedUser:
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ class User(BaseModel):
|
|||
language: str = Field(description="Preferred language of the user")
|
||||
disabled: Optional[bool] = Field(False, description="Indicates whether the user is disabled")
|
||||
privilege: str = Field(description="Permission level") #sysadmin,admin,user
|
||||
authenticationAuthority: str = Field(default="local", description="Authentication authority (local, microsoft)")
|
||||
|
||||
label: Label = Field(
|
||||
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"}),
|
||||
"disabled": Label(default="Disabled", translations={"en": "Disabled", "fr": "Désactivé"}),
|
||||
"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"})
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ router.mount("/static", StaticFiles(directory=str(staticFolder), html=True), nam
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@router.get("/favicon.ico")
|
||||
@router.get("/favicon.ico", tags=["General"])
|
||||
async def favicon():
|
||||
return FileResponse(str(staticFolder / "favicon.ico"), media_type="image/x-icon")
|
||||
|
||||
|
|
@ -83,12 +83,13 @@ async def loginForAccessToken(formData: OAuth2PasswordRequestForm = Depends()):
|
|||
data={
|
||||
"sub": user["username"],
|
||||
"_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
|
||||
)
|
||||
|
||||
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"}
|
||||
except ValueError as e:
|
||||
# Handle authentication errors
|
||||
|
|
@ -196,3 +197,49 @@ async def registerUser(userData: Dict[str, Any]):
|
|||
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)}"
|
||||
)
|
||||
|
|
@ -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")
|
||||
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
|
||||
success = mydom.saveMsftToken(token_data)
|
||||
if success:
|
||||
|
|
@ -217,18 +224,18 @@ async def login():
|
|||
async def auth_callback(code: str, state: str, request: Request):
|
||||
"""Handle Microsoft OAuth callback"""
|
||||
try:
|
||||
# Create MSAL app instance
|
||||
app = msal.ConfidentialClientApplication(
|
||||
client_id=CLIENT_ID,
|
||||
client_credential=CLIENT_SECRET,
|
||||
authority=AUTHORITY
|
||||
# Create a confidential client application
|
||||
msal_app = msal.ConfidentialClientApplication(
|
||||
app_config["client_id"],
|
||||
authority=app_config["authority"],
|
||||
client_credential=app_config["client_credential"]
|
||||
)
|
||||
|
||||
# Exchange code for token
|
||||
token_response = app.acquire_token_by_authorization_code(
|
||||
code=code,
|
||||
scopes=SCOPES,
|
||||
redirect_uri=REDIRECT_URI
|
||||
# Exchange the authorization code for tokens
|
||||
token_response = msal_app.acquire_token_by_authorization_code(
|
||||
code,
|
||||
SCOPES,
|
||||
redirect_uri=app_config["redirect_uri"]
|
||||
)
|
||||
|
||||
if "error" in token_response:
|
||||
|
|
@ -245,7 +252,7 @@ async def auth_callback(code: str, state: str, request: Request):
|
|||
</head>
|
||||
<body>
|
||||
<h1 class="error">Authentication Failed</h1>
|
||||
<p>Please try again.</p>
|
||||
<p>Could not acquire access token.</p>
|
||||
<script>
|
||||
setTimeout(() => window.close(), 3000);
|
||||
</script>
|
||||
|
|
@ -255,8 +262,9 @@ async def auth_callback(code: str, state: str, request: Request):
|
|||
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"])
|
||||
|
||||
if not user_info:
|
||||
logger.error("Failed to get user info from token")
|
||||
return HTMLResponse(
|
||||
|
|
@ -281,7 +289,72 @@ async def auth_callback(code: str, state: str, request: Request):
|
|||
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
|
||||
|
||||
# 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({{
|
||||
type: 'msft_auth_success',
|
||||
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
|
||||
|
|
@ -322,27 +396,10 @@ async def auth_callback(code: str, state: str, request: Request):
|
|||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Authentication failed: {str(e)}")
|
||||
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>An error occurred during authentication.</p>
|
||||
<script>
|
||||
setTimeout(() => window.close(), 3000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
""",
|
||||
status_code=500
|
||||
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")
|
||||
|
|
@ -368,8 +425,9 @@ async def auth_status(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser
|
|||
"message": "Not authenticated with Microsoft"
|
||||
})
|
||||
|
||||
# Verify token is still valid
|
||||
if not verify_token(token_data["access_token"]):
|
||||
# Verify token is still valid and get user info
|
||||
user_info = get_user_info_from_token(token_data["access_token"])
|
||||
if not user_info:
|
||||
logger.info("Token invalid, attempting refresh")
|
||||
# Try to refresh the token
|
||||
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
|
||||
token_data = await load_token_from_file(currentUser)
|
||||
|
||||
# Get user info from token data
|
||||
user_info = token_data.get("user_info")
|
||||
if not user_info:
|
||||
logger.info("No user info found in token data")
|
||||
return JSONResponse({
|
||||
"authenticated": False,
|
||||
"message": "No user information available"
|
||||
})
|
||||
# Get user info again after refresh
|
||||
user_info = get_user_info_from_token(token_data["access_token"])
|
||||
if not user_info:
|
||||
return JSONResponse({
|
||||
"authenticated": False,
|
||||
"message": "Could not get user info after token refresh"
|
||||
})
|
||||
|
||||
logger.info(f"User {user_info.get('name')} is authenticated")
|
||||
return JSONResponse({
|
||||
|
|
|
|||
|
|
@ -76,8 +76,7 @@ async def registerUser(request: Request):
|
|||
"""Register a new user."""
|
||||
try:
|
||||
# Get request data
|
||||
data = await request.json()
|
||||
logger.info(f"Registration request data: {data}")
|
||||
userData = await request.json()
|
||||
|
||||
# Get root mandate and admin user IDs
|
||||
adminGateway = getGatewayInterface()
|
||||
|
|
@ -86,91 +85,110 @@ async def registerUser(request: Request):
|
|||
|
||||
if not rootMandateId or not adminUserId:
|
||||
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"
|
||||
)
|
||||
|
||||
# Create a new gateway interface instance with admin context
|
||||
adminGateway = getGatewayInterface(rootMandateId, adminUserId)
|
||||
|
||||
# Check required fields
|
||||
if not data.get("username") or not data.get("password"):
|
||||
logger.error("Missing required fields in registration request")
|
||||
raise HTTPException(status_code=400, detail="Username and password are required")
|
||||
# Set default values if not provided
|
||||
if "language" not in userData:
|
||||
userData["language"] = "en"
|
||||
if "authenticationAuthority" not in userData:
|
||||
userData["authenticationAuthority"] = "local"
|
||||
|
||||
# Create user data
|
||||
userData = {
|
||||
"username": data["username"],
|
||||
"password": data["password"],
|
||||
"email": data.get("email"),
|
||||
"fullName": data.get("fullName"),
|
||||
"language": data.get("language", "de"),
|
||||
"_mandateId": rootMandateId,
|
||||
"disabled": False,
|
||||
"privilege": "user"
|
||||
}
|
||||
# Validate authentication authority
|
||||
if userData["authenticationAuthority"] not in ["local", "microsoft"]:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"Invalid authentication authority: {userData['authenticationAuthority']}"
|
||||
)
|
||||
|
||||
# Validate password for local authentication
|
||||
if userData["authenticationAuthority"] == "local":
|
||||
if "password" not in userData:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Password is required for local authentication"
|
||||
)
|
||||
|
||||
# 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:
|
||||
authResult = adminGateway.authenticateUser(userData["username"], userData["password"])
|
||||
if not authResult:
|
||||
logger.error("Authentication test failed after user creation")
|
||||
createdUser = adminGateway.createUser(
|
||||
username=userData["username"],
|
||||
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:
|
||||
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:
|
||||
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)}")
|
||||
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
|
||||
return {
|
||||
"message": "User registered successfully",
|
||||
"userId": createdUser["id"]
|
||||
}
|
||||
# Remove sensitive data from response
|
||||
if "hashedPassword" in createdUser:
|
||||
del createdUser["hashedPassword"]
|
||||
|
||||
except ValueError as e:
|
||||
logger.error(f"Validation error during registration: {str(e)}")
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except PermissionError as e:
|
||||
logger.error(f"Permission error during registration: {str(e)}")
|
||||
raise HTTPException(status_code=403, detail=str(e))
|
||||
return createdUser
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error during registration: {str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
logger.error(f"Unexpected error during user registration: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Registration failed: {str(e)}"
|
||||
)
|
||||
|
||||
@router.post("/register-with-msal", response_model=Dict[str, Any])
|
||||
async def registerUserWithMsal(userData: dict = Body(...)):
|
||||
|
|
|
|||
|
|
@ -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')})")
|
||||
raise credentialsException
|
||||
|
||||
# Add authentication authority to user data
|
||||
user["authenticationAuthority"] = user.get("authenticationAuthority", "local")
|
||||
|
||||
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]:
|
||||
"""
|
||||
Extracts the mandate ID and user ID from the current user.
|
||||
|
|
@ -171,3 +156,31 @@ def getInitialContext() -> tuple[str, str]:
|
|||
mandateId = gateway.getInitialId("mandates")
|
||||
userId = gateway.getInitialId("users")
|
||||
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
|
||||
Loading…
Reference in a new issue