mirror of
https://github.com/aljazceru/enclava.git
synced 2025-12-17 23:44:24 +01:00
mega changes
This commit is contained in:
@@ -85,16 +85,16 @@ async def list_users(
|
||||
is_active: Optional[bool] = Query(None),
|
||||
search: Optional[str] = Query(None),
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""List all users with pagination and filtering"""
|
||||
|
||||
|
||||
# Check permissions
|
||||
require_permission(current_user.get("permissions", []), "platform:users:read")
|
||||
|
||||
|
||||
# Build query
|
||||
query = select(User)
|
||||
|
||||
|
||||
# Apply filters
|
||||
if role:
|
||||
query = query.where(User.role == role)
|
||||
@@ -102,38 +102,42 @@ async def list_users(
|
||||
query = query.where(User.is_active == is_active)
|
||||
if search:
|
||||
query = query.where(
|
||||
(User.username.ilike(f"%{search}%")) |
|
||||
(User.email.ilike(f"%{search}%")) |
|
||||
(User.full_name.ilike(f"%{search}%"))
|
||||
(User.username.ilike(f"%{search}%"))
|
||||
| (User.email.ilike(f"%{search}%"))
|
||||
| (User.full_name.ilike(f"%{search}%"))
|
||||
)
|
||||
|
||||
|
||||
# Get total count
|
||||
total_query = select(User.id).select_from(query.subquery())
|
||||
total_result = await db.execute(total_query)
|
||||
total = len(total_result.fetchall())
|
||||
|
||||
|
||||
# Apply pagination
|
||||
offset = (page - 1) * size
|
||||
query = query.offset(offset).limit(size)
|
||||
|
||||
|
||||
# Execute query
|
||||
result = await db.execute(query)
|
||||
users = result.scalars().all()
|
||||
|
||||
|
||||
# Log audit event
|
||||
await log_audit_event(
|
||||
db=db,
|
||||
user_id=current_user['id'],
|
||||
user_id=current_user["id"],
|
||||
action="list_users",
|
||||
resource_type="user",
|
||||
details={"page": page, "size": size, "filters": {"role": role, "is_active": is_active, "search": search}}
|
||||
details={
|
||||
"page": page,
|
||||
"size": size,
|
||||
"filters": {"role": role, "is_active": is_active, "search": search},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
return UserListResponse(
|
||||
users=[UserResponse.model_validate(user) for user in users],
|
||||
total=total,
|
||||
page=page,
|
||||
size=size
|
||||
size=size,
|
||||
)
|
||||
|
||||
|
||||
@@ -141,34 +145,33 @@ async def list_users(
|
||||
async def get_user(
|
||||
user_id: str,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Get user by ID"""
|
||||
|
||||
|
||||
# Check permissions (users can view their own profile)
|
||||
if int(user_id) != current_user['id']:
|
||||
if int(user_id) != current_user["id"]:
|
||||
require_permission(current_user.get("permissions", []), "platform:users:read")
|
||||
|
||||
|
||||
# Get user
|
||||
query = select(User).where(User.id == int(user_id))
|
||||
result = await db.execute(query)
|
||||
user = result.scalar_one_or_none()
|
||||
|
||||
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="User not found"
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
|
||||
)
|
||||
|
||||
|
||||
# Log audit event
|
||||
await log_audit_event(
|
||||
db=db,
|
||||
user_id=current_user['id'],
|
||||
user_id=current_user["id"],
|
||||
action="get_user",
|
||||
resource_type="user",
|
||||
resource_id=user_id
|
||||
resource_id=user_id,
|
||||
)
|
||||
|
||||
|
||||
return UserResponse.model_validate(user)
|
||||
|
||||
|
||||
@@ -176,26 +179,26 @@ async def get_user(
|
||||
async def create_user(
|
||||
user_data: UserCreate,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Create a new user"""
|
||||
|
||||
|
||||
# Check permissions
|
||||
require_permission(current_user.get("permissions", []), "platform:users:create")
|
||||
|
||||
|
||||
# Check if user already exists
|
||||
query = select(User).where(
|
||||
(User.username == user_data.username) | (User.email == user_data.email)
|
||||
)
|
||||
result = await db.execute(query)
|
||||
existing_user = result.scalar_one_or_none()
|
||||
|
||||
|
||||
if existing_user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="User with this username or email already exists"
|
||||
detail="User with this username or email already exists",
|
||||
)
|
||||
|
||||
|
||||
# Create user
|
||||
hashed_password = get_password_hash(user_data.password)
|
||||
new_user = User(
|
||||
@@ -204,25 +207,29 @@ async def create_user(
|
||||
full_name=user_data.full_name,
|
||||
hashed_password=hashed_password,
|
||||
role=user_data.role,
|
||||
is_active=user_data.is_active
|
||||
is_active=user_data.is_active,
|
||||
)
|
||||
|
||||
|
||||
db.add(new_user)
|
||||
await db.commit()
|
||||
await db.refresh(new_user)
|
||||
|
||||
|
||||
# Log audit event
|
||||
await log_audit_event(
|
||||
db=db,
|
||||
user_id=current_user['id'],
|
||||
user_id=current_user["id"],
|
||||
action="create_user",
|
||||
resource_type="user",
|
||||
resource_id=str(new_user.id),
|
||||
details={"username": user_data.username, "email": user_data.email, "role": user_data.role}
|
||||
details={
|
||||
"username": user_data.username,
|
||||
"email": user_data.email,
|
||||
"role": user_data.role,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
logger.info(f"User created: {new_user.username} by {current_user['username']}")
|
||||
|
||||
|
||||
return UserResponse.model_validate(new_user)
|
||||
|
||||
|
||||
@@ -231,26 +238,25 @@ async def update_user(
|
||||
user_id: str,
|
||||
user_data: UserUpdate,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Update user"""
|
||||
|
||||
|
||||
# Check permissions (users can update their own profile with limited fields)
|
||||
is_self_update = int(user_id) == current_user['id']
|
||||
is_self_update = int(user_id) == current_user["id"]
|
||||
if not is_self_update:
|
||||
require_permission(current_user.get("permissions", []), "platform:users:update")
|
||||
|
||||
|
||||
# Get user
|
||||
query = select(User).where(User.id == int(user_id))
|
||||
result = await db.execute(query)
|
||||
user = result.scalar_one_or_none()
|
||||
|
||||
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="User not found"
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
|
||||
)
|
||||
|
||||
|
||||
# For self-updates, restrict what can be changed
|
||||
if is_self_update:
|
||||
allowed_fields = {"username", "email", "full_name"}
|
||||
@@ -259,41 +265,41 @@ async def update_user(
|
||||
if restricted_fields:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"Cannot update fields: {restricted_fields}"
|
||||
detail=f"Cannot update fields: {restricted_fields}",
|
||||
)
|
||||
|
||||
|
||||
# Store original values for audit
|
||||
original_values = {
|
||||
"username": user.username,
|
||||
"email": user.email,
|
||||
"role": user.role,
|
||||
"is_active": user.is_active
|
||||
"is_active": user.is_active,
|
||||
}
|
||||
|
||||
|
||||
# Update user fields
|
||||
update_data = user_data.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(user, field, value)
|
||||
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(user)
|
||||
|
||||
|
||||
# Log audit event
|
||||
await log_audit_event(
|
||||
db=db,
|
||||
user_id=current_user['id'],
|
||||
user_id=current_user["id"],
|
||||
action="update_user",
|
||||
resource_type="user",
|
||||
resource_id=user_id,
|
||||
details={
|
||||
"updated_fields": list(update_data.keys()),
|
||||
"before_values": original_values,
|
||||
"after_values": {k: getattr(user, k) for k in update_data.keys()}
|
||||
}
|
||||
"after_values": {k: getattr(user, k) for k in update_data.keys()},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
logger.info(f"User updated: {user.username} by {current_user['username']}")
|
||||
|
||||
|
||||
return UserResponse.model_validate(user)
|
||||
|
||||
|
||||
@@ -301,47 +307,46 @@ async def update_user(
|
||||
async def delete_user(
|
||||
user_id: str,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Delete user (soft delete by deactivating)"""
|
||||
|
||||
|
||||
# Check permissions
|
||||
require_permission(current_user.get("permissions", []), "platform:users:delete")
|
||||
|
||||
|
||||
# Prevent self-deletion
|
||||
if int(user_id) == current_user['id']:
|
||||
if int(user_id) == current_user["id"]:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Cannot delete your own account"
|
||||
detail="Cannot delete your own account",
|
||||
)
|
||||
|
||||
|
||||
# Get user
|
||||
query = select(User).where(User.id == int(user_id))
|
||||
result = await db.execute(query)
|
||||
user = result.scalar_one_or_none()
|
||||
|
||||
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="User not found"
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
|
||||
)
|
||||
|
||||
|
||||
# Soft delete by deactivating
|
||||
user.is_active = False
|
||||
await db.commit()
|
||||
|
||||
|
||||
# Log audit event
|
||||
await log_audit_event(
|
||||
db=db,
|
||||
user_id=current_user['id'],
|
||||
user_id=current_user["id"],
|
||||
action="delete_user",
|
||||
resource_type="user",
|
||||
resource_id=user_id,
|
||||
details={"username": user.username, "email": user.email}
|
||||
details={"username": user.username, "email": user.email},
|
||||
)
|
||||
|
||||
|
||||
logger.info(f"User deleted: {user.username} by {current_user['username']}")
|
||||
|
||||
|
||||
return {"message": "User deleted successfully"}
|
||||
|
||||
|
||||
@@ -350,50 +355,51 @@ async def change_password(
|
||||
user_id: str,
|
||||
password_data: PasswordChangeRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Change user password"""
|
||||
|
||||
|
||||
# Users can only change their own password, or admins can change any password
|
||||
is_self_update = int(user_id) == current_user['id']
|
||||
is_self_update = int(user_id) == current_user["id"]
|
||||
if not is_self_update:
|
||||
require_permission(current_user.get("permissions", []), "platform:users:update")
|
||||
|
||||
|
||||
# Get user
|
||||
query = select(User).where(User.id == int(user_id))
|
||||
result = await db.execute(query)
|
||||
user = result.scalar_one_or_none()
|
||||
|
||||
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="User not found"
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
|
||||
)
|
||||
|
||||
|
||||
# For self-updates, verify current password
|
||||
if is_self_update:
|
||||
if not verify_password(password_data.current_password, user.hashed_password):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Current password is incorrect"
|
||||
detail="Current password is incorrect",
|
||||
)
|
||||
|
||||
|
||||
# Update password
|
||||
user.hashed_password = get_password_hash(password_data.new_password)
|
||||
await db.commit()
|
||||
|
||||
|
||||
# Log audit event
|
||||
await log_audit_event(
|
||||
db=db,
|
||||
user_id=current_user['id'],
|
||||
user_id=current_user["id"],
|
||||
action="change_password",
|
||||
resource_type="user",
|
||||
resource_id=user_id,
|
||||
details={"target_user": user.username}
|
||||
details={"target_user": user.username},
|
||||
)
|
||||
|
||||
logger.info(f"Password changed for user: {user.username} by {current_user['username']}")
|
||||
|
||||
|
||||
logger.info(
|
||||
f"Password changed for user: {user.username} by {current_user['username']}"
|
||||
)
|
||||
|
||||
return {"message": "Password changed successfully"}
|
||||
|
||||
|
||||
@@ -402,40 +408,41 @@ async def reset_password(
|
||||
user_id: str,
|
||||
password_data: PasswordResetRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Reset user password (admin only)"""
|
||||
|
||||
|
||||
# Check permissions
|
||||
require_permission(current_user.get("permissions", []), "platform:users:update")
|
||||
|
||||
|
||||
# Get user
|
||||
query = select(User).where(User.id == int(user_id))
|
||||
result = await db.execute(query)
|
||||
user = result.scalar_one_or_none()
|
||||
|
||||
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="User not found"
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
|
||||
)
|
||||
|
||||
|
||||
# Reset password
|
||||
user.hashed_password = get_password_hash(password_data.new_password)
|
||||
await db.commit()
|
||||
|
||||
|
||||
# Log audit event
|
||||
await log_audit_event(
|
||||
db=db,
|
||||
user_id=current_user['id'],
|
||||
user_id=current_user["id"],
|
||||
action="reset_password",
|
||||
resource_type="user",
|
||||
resource_id=user_id,
|
||||
details={"target_user": user.username}
|
||||
details={"target_user": user.username},
|
||||
)
|
||||
|
||||
logger.info(f"Password reset for user: {user.username} by {current_user['username']}")
|
||||
|
||||
|
||||
logger.info(
|
||||
f"Password reset for user: {user.username} by {current_user['username']}"
|
||||
)
|
||||
|
||||
return {"message": "Password reset successfully"}
|
||||
|
||||
|
||||
@@ -443,20 +450,22 @@ async def reset_password(
|
||||
async def get_user_api_keys(
|
||||
user_id: str,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Get API keys for a user"""
|
||||
|
||||
|
||||
# Check permissions (users can view their own API keys)
|
||||
is_self_request = int(user_id) == current_user['id']
|
||||
is_self_request = int(user_id) == current_user["id"]
|
||||
if not is_self_request:
|
||||
require_permission(current_user.get("permissions", []), "platform:api-keys:read")
|
||||
|
||||
require_permission(
|
||||
current_user.get("permissions", []), "platform:api-keys:read"
|
||||
)
|
||||
|
||||
# Get API keys
|
||||
query = select(APIKey).where(APIKey.user_id == int(user_id))
|
||||
result = await db.execute(query)
|
||||
api_keys = result.scalars().all()
|
||||
|
||||
|
||||
# Return safe representation (no key values)
|
||||
return [
|
||||
{
|
||||
@@ -466,8 +475,12 @@ async def get_user_api_keys(
|
||||
"scopes": api_key.scopes,
|
||||
"is_active": api_key.is_active,
|
||||
"created_at": api_key.created_at.isoformat(),
|
||||
"expires_at": api_key.expires_at.isoformat() if api_key.expires_at else None,
|
||||
"last_used_at": api_key.last_used_at.isoformat() if api_key.last_used_at else None
|
||||
"expires_at": api_key.expires_at.isoformat()
|
||||
if api_key.expires_at
|
||||
else None,
|
||||
"last_used_at": api_key.last_used_at.isoformat()
|
||||
if api_key.last_used_at
|
||||
else None,
|
||||
}
|
||||
for api_key in api_keys
|
||||
]
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user