Skip to content

[STORY] User Management via MCP #486

@jsbattig

Description

@jsbattig

Part of: #477

Story: User Management via MCP

Feature Group: F9 - User Management via MCP

📖 Story Overview

User Story

As an admin using Claude, I want to manage team users and their permissions so that I can control access to the CIDX server without direct database access.

Objective

Implement admin-only MCP tools for managing users, including creation, role updates, and deletion, with proper validation and security controls through Claude.

User Value

  • 👥 Manage team access through Claude
  • 🔐 Control user permissions easily
  • 📊 View user activity and status
  • 🚫 Revoke access when needed
  • 📝 Audit trail for compliance

✅ Acceptance Criteria

Feature: User Management via MCP

  Background:
    Given user has ADMIN role
    And MCP connection established
    And UserManager service available

  Scenario: List all users
    When Claude calls list_users tool
    Then response includes all users:
      | username | User login name |
      | email | User email address |
      | role | Current role |
      | created_at | Account creation date |
      | last_login | Last authentication |
      | is_active | Account status |
      | repositories_activated | Count of activated repos |
    And users sorted by username
    And includes usage statistics

  Scenario: Filter users by criteria
    When Claude calls list_users:
      """
      {
        "filter": "inactive",
        "role": "VIEWER"
      }
      """
    Then only inactive VIEWER users returned
    And filter options include:
      | active/inactive | Activity status |
      | role | Specific role |
      | has_repositories | Has activated repos |
      | created_after | Recent accounts |

  Scenario: Create new user
    When Claude calls create_user:
      """
      {
        "username": "john.doe",
        "email": "john.doe@example.com",
        "password": "SecureP@ssw0rd!",
        "role": "POWER_USER",
        "send_welcome": true
      }
      """
    Then user account created
    And password complexity validated
    And role assigned correctly
    And welcome email queued if requested
    And response includes:
      | user_id | Generated user ID |
      | username | Confirmed username |
      | role | Assigned role |
      | temporary_password | If generated |

  Scenario: Auto-generate secure password
    When Claude calls create_user without password:
      """
      {
        "username": "jane.smith",
        "email": "jane@example.com",
        "role": "VIEWER",
        "auto_password": true
      }
      """
    Then secure password generated
    And password meets complexity requirements
    And temporary password returned
    And user marked for password reset

  Scenario: Update user role
    Given user "developer1" has VIEWER role
    When Claude calls update_user_role:
      """
      {
        "username": "developer1",
        "new_role": "POWER_USER"
      }
      """
    Then user role updated to POWER_USER
    And permissions refreshed
    And active sessions updated
    And audit log created
    And response confirms change

  Scenario: Deactivate user account
    When Claude calls delete_user:
      """
      {
        "username": "former.employee",
        "soft_delete": true
      }
      """
    Then user account deactivated
    And login disabled
    And active sessions terminated
    And repositories remain for audit
    And can be reactivated later

  Scenario: Hard delete user
    When Claude calls delete_user:
      """
      {
        "username": "test.account",
        "soft_delete": false,
        "cleanup_data": true
      }
      """
    Then user permanently deleted
    And all user workspaces removed
    And activated repositories cleaned
    And audit trail preserved
    And cannot be recovered

  Scenario: Handle duplicate username
    Given "existing.user" already exists
    When Claude attempts to create same username
    Then error indicates duplicate
    And suggests alternative username
    And no changes made

  Scenario: Validate email format
    When Claude creates user with invalid email:
      """
      {
        "username": "newuser",
        "email": "not-an-email"
      }
      """
    Then validation error returned
    And email format requirements shown
    And user not created

  Scenario: Password complexity validation
    When Claude creates user with weak password:
      """
      {
        "username": "weakuser",
        "password": "12345"
      }
      """
    Then password rejected
    And complexity requirements listed:
      | Minimum 8 characters |
      | Uppercase letter required |
      | Lowercase letter required |
      | Number required |
      | Special character required |

  Scenario: Bulk user operations
    When Claude calls create_user with multiple users:
      """
      {
        "users": [
          {"username": "user1", "email": "user1@example.com"},
          {"username": "user2", "email": "user2@example.com"},
          {"username": "user3", "email": "user3@example.com"}
        ],
        "default_role": "VIEWER",
        "auto_password": true
      }
      """
    Then all users created
    And passwords generated for each
    And summary report returned
    And failures don't affect others

  Scenario: View user details
    When Claude calls list_users:
      """
      {
        "username": "specific.user",
        "include_details": true
      }
      """
    Then detailed information returned:
      | Active repositories |
      | Recent activity |
      | Permission list |
      | Session count |
      | Storage usage |

  Scenario: Reset user password
    When Claude calls update_user_role:
      """
      {
        "username": "forgetful.user",
        "reset_password": true
      }
      """
    Then new temporary password generated
    And user marked for password change
    And active sessions terminated
    And temporary password returned

🔧 Technical Requirements

Tool Definitions

# pseudocode tool registry entries
"list_users": {
    "description": "List and filter system users (ADMIN only)",
    "parameters": {
        "type": "object",
        "properties": {
            "username": {
                "type": "string",
                "description": "Specific username to retrieve"
            },
            "filter": {
                "type": "string",
                "enum": ["active", "inactive", "all"],
                "default": "all"
            },
            "role": {
                "type": "string",
                "enum": ["ADMIN", "POWER_USER", "VIEWER"],
                "description": "Filter by role"
            },
            "has_repositories": {
                "type": "boolean",
                "description": "Filter by repository activation"
            },
            "include_details": {
                "type": "boolean",
                "default": false,
                "description": "Include detailed information"
            },
            "sort_by": {
                "type": "string",
                "enum": ["username", "created_at", "last_login"],
                "default": "username"
            }
        }
    },
    "required_permission": Permissions.ADMIN
}

"create_user": {
    "description": "Create new user account (ADMIN only)",
    "parameters": {
        "type": "object",
        "properties": {
            "username": {
                "type": "string",
                "pattern": "^[a-zA-Z0-9._-]+$",
                "minLength": 3,
                "maxLength": 32
            },
            "email": {
                "type": "string",
                "format": "email"
            },
            "password": {
                "type": "string",
                "minLength": 8
            },
            "auto_password": {
                "type": "boolean",
                "default": false,
                "description": "Generate secure password"
            },
            "role": {
                "type": "string",
                "enum": ["ADMIN", "POWER_USER", "VIEWER"],
                "default": "VIEWER"
            },
            "send_welcome": {
                "type": "boolean",
                "default": false,
                "description": "Send welcome email"
            },
            "users": {
                "type": "array",
                "description": "Bulk create multiple users",
                "items": {
                    "type": "object",
                    "properties": {
                        "username": {"type": "string"},
                        "email": {"type": "string"}
                    }
                }
            }
        }
    },
    "required_permission": Permissions.ADMIN
}

"update_user_role": {
    "description": "Update user role or reset password (ADMIN only)",
    "parameters": {
        "type": "object",
        "properties": {
            "username": {
                "type": "string",
                "description": "User to update"
            },
            "new_role": {
                "type": "string",
                "enum": ["ADMIN", "POWER_USER", "VIEWER"],
                "description": "New role to assign"
            },
            "reset_password": {
                "type": "boolean",
                "default": false,
                "description": "Generate new password"
            }
        },
        "required": ["username"]
    },
    "required_permission": Permissions.ADMIN
}

"delete_user": {
    "description": "Delete or deactivate user account (ADMIN only)",
    "parameters": {
        "type": "object",
        "properties": {
            "username": {
                "type": "string",
                "description": "User to delete"
            },
            "soft_delete": {
                "type": "boolean",
                "default": true,
                "description": "Deactivate vs permanent delete"
            },
            "cleanup_data": {
                "type": "boolean",
                "default": false,
                "description": "Remove user data"
            },
            "transfer_to": {
                "type": "string",
                "description": "Transfer repositories to another user"
            }
        },
        "required": ["username"]
    },
    "required_permission": Permissions.ADMIN
}

List Users Implementation

# pseudocode list users handler
async def handle_list_users(params: dict, user: User) -> dict:
    # Verify admin role
    if not user.has_permission(Permissions.ADMIN):
        raise PermissionDeniedError("Admin access required")

    # Get users from UserManager
    if params.get("username"):
        # Get specific user
        target_user = await user_manager.get_user(params["username"])
        if not target_user:
            raise UserNotFoundError(params["username"])
        users = [target_user]
    else:
        # Get all users with filters
        users = await user_manager.list_users(
            is_active=params.get("filter") == "active",
            role=params.get("role"),
            has_repositories=params.get("has_repositories")
        )

    # Enrich with additional data
    enriched_users = []
    for u in users:
        user_data = {
            "username": u.username,
            "email": u.email,
            "role": u.role.value,
            "created_at": u.created_at.isoformat(),
            "last_login": u.last_login.isoformat() if u.last_login else None,
            "is_active": u.is_active
        }

        # Add repository count
        repo_count = await get_user_repository_count(u.id)
        user_data["repositories_activated"] = repo_count

        # Add detailed information if requested
        if params.get("include_details"):
            user_data["details"] = await get_user_details(u.id)

        enriched_users.append(user_data)

    # Sort results
    sort_by = params.get("sort_by", "username")
    enriched_users.sort(key=lambda x: x[sort_by] or "")

    return {
        "users": enriched_users,
        "total": len(enriched_users),
        "timestamp": datetime.utcnow().isoformat()
    }

Create User Implementation

# pseudocode create user handler
async def handle_create_user(params: dict, user: User) -> dict:
    # Verify admin role
    if not user.has_permission(Permissions.ADMIN):
        raise PermissionDeniedError("Admin access required")

    # Handle bulk creation
    if params.get("users"):
        return await bulk_create_users(params["users"], params, user)

    # Single user creation
    username = params["username"]
    email = params["email"]

    # Validate username format
    if not re.match(r"^[a-zA-Z0-9._-]+$", username):
        raise ValidationError("Invalid username format")

    # Check for duplicates
    existing = await user_manager.get_user(username)
    if existing:
        raise DuplicateUserError(f"Username already exists: {username}")

    # Validate email
    if not is_valid_email(email):
        raise ValidationError(f"Invalid email format: {email}")

    # Handle password
    if params.get("auto_password"):
        password = generate_secure_password()
        temporary = True
    else:
        password = params.get("password")
        if not password:
            raise ValidationError("Password required or set auto_password=true")

        # Validate password complexity
        validation = validate_password_complexity(password)
        if not validation.valid:
            raise ValidationError(f"Password requirements: {validation.requirements}")
        temporary = False

    # Create user
    new_user = await user_manager.create_user(
        username=username,
        email=email,
        password=password,
        role=UserRole[params.get("role", "VIEWER")],
        is_temporary_password=temporary
    )

    # Send welcome email if requested
    if params.get("send_welcome"):
        await queue_welcome_email(
            email=email,
            username=username,
            temporary_password=password if temporary else None
        )

    # Audit log
    await log_admin_action(
        user=user,
        action="create_user",
        target=username,
        details={"role": params.get("role", "VIEWER")}
    )

    response = {
        "user_id": new_user.id,
        "username": new_user.username,
        "email": new_user.email,
        "role": new_user.role.value,
        "created": True
    }

    if temporary:
        response["temporary_password"] = password
        response["requires_password_change"] = True

    return response

Update User Role Implementation

# pseudocode update role handler
async def handle_update_user_role(params: dict, user: User) -> dict:
    # Verify admin role
    if not user.has_permission(Permissions.ADMIN):
        raise PermissionDeniedError("Admin access required")

    username = params["username"]

    # Get target user
    target_user = await user_manager.get_user(username)
    if not target_user:
        raise UserNotFoundError(username)

    changes = []

    # Update role if specified
    if params.get("new_role"):
        old_role = target_user.role
        new_role = UserRole[params["new_role"]]

        if old_role != new_role:
            await user_manager.update_user_role(target_user.id, new_role)
            changes.append(f"Role changed from {old_role.value} to {new_role.value}")

            # Refresh active sessions
            await refresh_user_sessions(target_user.id)

    # Reset password if requested
    temporary_password = None
    if params.get("reset_password"):
        temporary_password = generate_secure_password()
        await user_manager.reset_password(
            target_user.id,
            temporary_password,
            temporary=True
        )
        changes.append("Password reset to temporary")

        # Terminate active sessions
        await terminate_user_sessions(target_user.id)

    # Audit log
    await log_admin_action(
        user=user,
        action="update_user",
        target=username,
        details={"changes": changes}
    )

    response = {
        "username": username,
        "changes": changes,
        "success": True
    }

    if temporary_password:
        response["temporary_password"] = temporary_password

    return response

Delete User Implementation

# pseudocode delete user handler
async def handle_delete_user(params: dict, user: User) -> dict:
    # Verify admin role
    if not user.has_permission(Permissions.ADMIN):
        raise PermissionDeniedError("Admin access required")

    username = params["username"]

    # Prevent self-deletion
    if username == user.username:
        raise ValidationError("Cannot delete your own account")

    # Get target user
    target_user = await user_manager.get_user(username)
    if not target_user:
        raise UserNotFoundError(username)

    # Check for active resources
    active_repos = await get_user_repository_count(target_user.id)

    if params.get("soft_delete", True):
        # Soft delete - deactivate account
        await user_manager.deactivate_user(target_user.id)

        # Terminate sessions
        await terminate_user_sessions(target_user.id)

        action = "deactivated"
        details = {"method": "soft_delete", "repositories_retained": active_repos}

    else:
        # Hard delete - permanent removal
        if params.get("cleanup_data"):
            # Clean up user data
            await cleanup_user_workspaces(target_user.id)
            await remove_user_indexes(target_user.id)

        elif params.get("transfer_to"):
            # Transfer repositories to another user
            transfer_user = await user_manager.get_user(params["transfer_to"])
            if not transfer_user:
                raise UserNotFoundError(params["transfer_to"])

            await transfer_user_repositories(
                from_user=target_user.id,
                to_user=transfer_user.id
            )

        # Delete user record
        await user_manager.delete_user(target_user.id)

        action = "deleted"
        details = {
            "method": "hard_delete",
            "data_cleaned": params.get("cleanup_data", False),
            "transferred_to": params.get("transfer_to")
        }

    # Audit log
    await log_admin_action(
        user=user,
        action=f"{action}_user",
        target=username,
        details=details
    )

    return {
        "username": username,
        "action": action,
        "repositories_affected": active_repos,
        "success": True,
        "message": f"User {username} {action} successfully"
    }

Password and Validation Utilities

# pseudocode password utilities
def generate_secure_password(length: int = 16) -> str:
    """Generate cryptographically secure password"""
    chars = string.ascii_letters + string.digits + "!@#$%^&*"
    password = ''.join(secrets.choice(chars) for _ in range(length))

    # Ensure complexity requirements
    while not validate_password_complexity(password).valid:
        password = ''.join(secrets.choice(chars) for _ in range(length))

    return password

def validate_password_complexity(password: str) -> PasswordValidation:
    """Validate password meets complexity requirements"""
    requirements = []
    valid = True

    if len(password) < 8:
        requirements.append("Minimum 8 characters")
        valid = False

    if not re.search(r"[A-Z]", password):
        requirements.append("At least one uppercase letter")
        valid = False

    if not re.search(r"[a-z]", password):
        requirements.append("At least one lowercase letter")
        valid = False

    if not re.search(r"\d", password):
        requirements.append("At least one number")
        valid = False

    if not re.search(r"[!@#$%^&*]", password):
        requirements.append("At least one special character")
        valid = False

    return PasswordValidation(valid=valid, requirements=requirements)

def is_valid_email(email: str) -> bool:
    """Validate email format"""
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return bool(re.match(pattern, email))

Bulk Operations

# pseudocode bulk user creation
async def bulk_create_users(users: list, params: dict, admin: User):
    """Create multiple users in parallel"""

    default_role = params.get("default_role", "VIEWER")
    auto_password = params.get("auto_password", True)

    tasks = []
    for user_data in users:
        create_params = {
            "username": user_data["username"],
            "email": user_data["email"],
            "role": user_data.get("role", default_role),
            "auto_password": auto_password
        }
        tasks.append(handle_create_user(create_params, admin))

    # Execute in parallel
    results = await asyncio.gather(*tasks, return_exceptions=True)

    # Format results
    formatted = []
    passwords = {}
    for i, result in enumerate(results):
        if isinstance(result, Exception):
            formatted.append({
                "username": users[i]["username"],
                "status": "failed",
                "error": str(result)
            })
        else:
            formatted.append({
                "username": result["username"],
                "status": "created",
                "role": result["role"]
            })
            if result.get("temporary_password"):
                passwords[result["username"]] = result["temporary_password"]

    return {
        "results": formatted,
        "temporary_passwords": passwords if passwords else None,
        "summary": {
            "total": len(users),
            "created": sum(1 for r in formatted if r["status"] == "created"),
            "failed": sum(1 for r in formatted if r["status"] == "failed")
        }
    }

📋 Implementation Checklist

Tool Registration

  • Register all 4 user management tools
  • Define parameter schemas
  • Set ADMIN permission requirement
  • Map to handler functions

List Users

  • Implement user listing
  • Add filtering options
  • Include repository counts
  • Add detailed view option
  • Implement sorting

Create User

  • Username validation
  • Email validation
  • Password complexity check
  • Auto-password generation
  • Bulk creation support
  • Welcome email integration

Update User

  • Role update functionality
  • Password reset option
  • Session refresh logic
  • Audit logging

Delete User

  • Soft delete (deactivation)
  • Hard delete option
  • Data cleanup process
  • Repository transfer
  • Session termination

Security & Validation

  • Password complexity rules
  • Email format validation
  • Username format rules
  • Self-deletion prevention
  • Admin-only enforcement

🧪 Testing Requirements

Unit Tests

  • Test user listing filters
  • Test user creation validation
  • Test password complexity
  • Test role updates
  • Test deletion options
  • Test bulk operations

Integration Tests

  • Test UserManager integration
  • Test session management
  • Test email queue integration
  • Test audit logging
  • Test data cleanup
  • Test repository transfer

Security Tests

  • Test admin-only access
  • Test password requirements
  • Test injection prevention
  • Test session termination
  • Test audit trail

Edge Case Tests

  • Test duplicate usernames
  • Test invalid emails
  • Test self-deletion attempt
  • Test role escalation
  • Test concurrent operations

📝 Definition of Done

  • All 4 user management tools implemented
  • User listing with filters and sorting
  • User creation with validation
  • Password generation and complexity
  • Role updates working
  • Soft and hard delete options
  • Bulk operations supported
  • Admin-only access enforced
  • All unit tests passing (>90% coverage)
  • Integration tests complete
  • Security audit passed
  • Documentation includes examples
  • Audit logging comprehensive
  • Error messages informative

Story Metadata:

  • Type: Feature
  • Priority: Medium
  • Points: 5
  • Components: Users, Security, Admin
  • Labels: story, user-management, admin
  • Dependencies: Story 2 (MCP Protocol)
  • Blocked By: MCP protocol implementation
  • Blocks: None
  • Restrictions: ADMIN role only

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions