351 lines
9.2 KiB
Markdown
351 lines
9.2 KiB
Markdown
|
|
# User Management System Setup Guide
|
||
|
|
|
||
|
|
This guide explains how to integrate the user management system into your MCP Gateway, enabling per-user API key generation and MCP access control.
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
The user management system provides:
|
||
|
|
|
||
|
|
- **User Creation & Management**: Create and manage users, enable/disable accounts
|
||
|
|
- **API Key Generation**: Generate unique API keys with optional TTL (time-to-live)
|
||
|
|
- **MCP Access Control**: Allow/block specific MCPs for each user (e.g., some users access only ERPNext, others access Wave + TrueNAS)
|
||
|
|
- **Persistent Storage**: User data stored in JSON file, survives container restarts
|
||
|
|
- **Web Dashboard**: Intuitive UI for managing users and API keys
|
||
|
|
|
||
|
|
## Files Created
|
||
|
|
|
||
|
|
1. **user_management.py** - Core user/API key management logic
|
||
|
|
2. **user_routes.py** - REST API endpoints for user management
|
||
|
|
3. **user_dashboard_ui.py** - Web dashboard with Vue.js UI
|
||
|
|
4. **gateway_proxy_user_integration.py** - Integration instructions
|
||
|
|
|
||
|
|
## Installation Steps
|
||
|
|
|
||
|
|
### Step 1: Copy Files
|
||
|
|
|
||
|
|
The files are already created in `gateway-proxy/`:
|
||
|
|
- `user_management.py`
|
||
|
|
- `user_routes.py`
|
||
|
|
- `user_dashboard_ui.py`
|
||
|
|
|
||
|
|
### Step 2: Update gateway_proxy.py
|
||
|
|
|
||
|
|
Add the import at line 33 (after existing imports):
|
||
|
|
|
||
|
|
```python
|
||
|
|
from user_management import user_manager
|
||
|
|
from user_routes import (
|
||
|
|
create_user, list_users, get_user, delete_user, toggle_user,
|
||
|
|
generate_api_key, revoke_api_key, set_mcp_access
|
||
|
|
)
|
||
|
|
from user_dashboard_ui import user_management_dashboard
|
||
|
|
```
|
||
|
|
|
||
|
|
### Step 3: Update validate_bearer_token() Function
|
||
|
|
|
||
|
|
Replace the `validate_bearer_token()` function (around line 254) with:
|
||
|
|
|
||
|
|
```python
|
||
|
|
def validate_bearer_token(request: Request) -> dict | None:
|
||
|
|
auth_header = request.headers.get("Authorization", "")
|
||
|
|
if not auth_header.startswith("Bearer "):
|
||
|
|
return None
|
||
|
|
token = auth_header[7:]
|
||
|
|
|
||
|
|
# Check static API key first
|
||
|
|
if STATIC_API_KEY and token == STATIC_API_KEY:
|
||
|
|
return {"client_id": "static", "scope": "mcp:tools", "user": "static"}
|
||
|
|
|
||
|
|
# Check user API key
|
||
|
|
user_info = user_manager.validate_api_key(token)
|
||
|
|
if user_info:
|
||
|
|
return {
|
||
|
|
"client_id": "api-key",
|
||
|
|
"scope": "mcp:tools",
|
||
|
|
"user": user_info['username'],
|
||
|
|
"user_id": user_info['user_id'],
|
||
|
|
"mcp_allowed": user_info['mcp_allowed'],
|
||
|
|
"mcp_blocked": user_info['mcp_blocked']
|
||
|
|
}
|
||
|
|
|
||
|
|
# Check OAuth token
|
||
|
|
token_hash = _hash(token)
|
||
|
|
info = ACCESS_TOKENS.get(token_hash)
|
||
|
|
if not info:
|
||
|
|
return None
|
||
|
|
if info["expires_at"] < time.time():
|
||
|
|
del ACCESS_TOKENS[token_hash]
|
||
|
|
return None
|
||
|
|
return info
|
||
|
|
```
|
||
|
|
|
||
|
|
### Step 4: Add MCP Access Control to handle_mcp()
|
||
|
|
|
||
|
|
In the `handle_mcp()` function, after retrieving `token_info`, add this check:
|
||
|
|
|
||
|
|
```python
|
||
|
|
# Check MCP access control
|
||
|
|
if token_info and 'user' in token_info and token_info.get('client_id') == 'api-key':
|
||
|
|
mcp_allowed = token_info.get('mcp_allowed', [])
|
||
|
|
mcp_blocked = token_info.get('mcp_blocked', [])
|
||
|
|
|
||
|
|
# Check if user can access any backend (basic check)
|
||
|
|
if not mcp_allowed and not mcp_blocked:
|
||
|
|
# User has no restrictions, allow all
|
||
|
|
pass
|
||
|
|
elif mcp_blocked and len(mcp_blocked) == len(BACKENDS):
|
||
|
|
return JSONResponse(
|
||
|
|
{'error': 'access_denied', 'message': 'All MCPs are blocked for your user'},
|
||
|
|
status_code=403
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
### Step 5: Update Routes List
|
||
|
|
|
||
|
|
Add these routes to the routes list before creating the app (around line 1012):
|
||
|
|
|
||
|
|
```python
|
||
|
|
routes = [
|
||
|
|
# ... existing well-known and OAuth routes ...
|
||
|
|
|
||
|
|
# MCP endpoint
|
||
|
|
Route("/mcp", handle_mcp, methods=["GET", "HEAD", "POST", "DELETE"]),
|
||
|
|
|
||
|
|
# Monitoring
|
||
|
|
Route("/health", health, methods=["GET"]),
|
||
|
|
Route("/status", status, methods=["GET"]),
|
||
|
|
|
||
|
|
# Dashboard
|
||
|
|
Route("/dashboard", dashboard, methods=["GET"]),
|
||
|
|
Route("/dashboard/status", dashboard_status, methods=["GET"]),
|
||
|
|
|
||
|
|
# User Management - Enhanced Dashboard
|
||
|
|
Route("/admin", user_management_dashboard, methods=["GET"]),
|
||
|
|
|
||
|
|
# User Management API
|
||
|
|
Route("/users", create_user, methods=["POST"]),
|
||
|
|
Route("/users", list_users, methods=["GET"]),
|
||
|
|
Route("/users/{username}", get_user, methods=["GET"]),
|
||
|
|
Route("/users/{username}", delete_user, methods=["DELETE"]),
|
||
|
|
Route("/users/{username}/enable", toggle_user, methods=["PATCH"]),
|
||
|
|
Route("/users/{username}/keys", generate_api_key, methods=["POST"]),
|
||
|
|
Route("/users/{username}/mcp-access", set_mcp_access, methods=["PUT"]),
|
||
|
|
Route("/keys/revoke", revoke_api_key, methods=["POST"]),
|
||
|
|
]
|
||
|
|
```
|
||
|
|
|
||
|
|
### Step 6: Update docker-compose.yml
|
||
|
|
|
||
|
|
Ensure the gateway service has a data volume for persistent storage:
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
services:
|
||
|
|
mcp-gateway:
|
||
|
|
image: mcp-gateway:latest
|
||
|
|
build:
|
||
|
|
context: ./gateway-proxy
|
||
|
|
dockerfile: Dockerfile
|
||
|
|
container_name: mcp-gateway
|
||
|
|
ports:
|
||
|
|
- "4444:4444"
|
||
|
|
environment:
|
||
|
|
# ... existing environment variables ...
|
||
|
|
USERS_DB_PATH: /data/users.json
|
||
|
|
volumes:
|
||
|
|
- ./data:/data
|
||
|
|
- ./gateway-proxy:/app
|
||
|
|
depends_on:
|
||
|
|
# ... existing dependencies ...
|
||
|
|
```
|
||
|
|
|
||
|
|
### Step 7: Create Data Directory
|
||
|
|
|
||
|
|
```bash
|
||
|
|
mkdir -p /sessions/vigilant-elegant-ramanujan/mnt/MCP\ Servers/mcp-gateway/data
|
||
|
|
```
|
||
|
|
|
||
|
|
### Step 8: Restart Gateway
|
||
|
|
|
||
|
|
```bash
|
||
|
|
docker-compose down
|
||
|
|
docker-compose up -d mcp-gateway
|
||
|
|
```
|
||
|
|
|
||
|
|
## Usage
|
||
|
|
|
||
|
|
### Access the Dashboard
|
||
|
|
|
||
|
|
Open your browser to:
|
||
|
|
|
||
|
|
```
|
||
|
|
http://10.0.0.25:4444/admin
|
||
|
|
```
|
||
|
|
|
||
|
|
### Create a User via API
|
||
|
|
|
||
|
|
```bash
|
||
|
|
curl -X POST http://10.0.0.25:4444/users \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-d '{
|
||
|
|
"username": "alice",
|
||
|
|
"email": "alice@example.com",
|
||
|
|
"description": "Engineering team"
|
||
|
|
}'
|
||
|
|
```
|
||
|
|
|
||
|
|
### Generate an API Key
|
||
|
|
|
||
|
|
```bash
|
||
|
|
curl -X POST http://10.0.0.25:4444/users/alice/keys \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-d '{
|
||
|
|
"key_name": "production-key",
|
||
|
|
"ttl_days": 90
|
||
|
|
}'
|
||
|
|
```
|
||
|
|
|
||
|
|
Response:
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"status": "created",
|
||
|
|
"username": "alice",
|
||
|
|
"api_key": "mcpgw_...",
|
||
|
|
"note": "Save this key immediately — it will not be shown again"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Configure MCP Access
|
||
|
|
|
||
|
|
```bash
|
||
|
|
curl -X PUT http://10.0.0.25:4444/users/alice/mcp-access \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-d '{
|
||
|
|
"allowed_mcps": ["erpnext", "wave"],
|
||
|
|
"blocked_mcps": []
|
||
|
|
}'
|
||
|
|
```
|
||
|
|
|
||
|
|
This allows alice to access only ERPNext and Wave Finance MCPs.
|
||
|
|
|
||
|
|
### Use the API Key
|
||
|
|
|
||
|
|
```bash
|
||
|
|
curl -X POST http://10.0.0.25:4444/mcp \
|
||
|
|
-H "Authorization: Bearer mcpgw_..." \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-d '{
|
||
|
|
"jsonrpc": "2.0",
|
||
|
|
"id": 1,
|
||
|
|
"method": "initialize",
|
||
|
|
"params": {
|
||
|
|
"protocolVersion": "2024-11-05",
|
||
|
|
"capabilities": {},
|
||
|
|
"clientInfo": {"name": "test-client", "version": "1.0"}
|
||
|
|
}
|
||
|
|
}'
|
||
|
|
```
|
||
|
|
|
||
|
|
## Access Control Examples
|
||
|
|
|
||
|
|
### Example 1: Limited Access User
|
||
|
|
|
||
|
|
Create a user with access to only ERPNext:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Create user
|
||
|
|
curl -X POST http://10.0.0.25:4444/users \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-d '{"username": "erp-user", "description": "ERP access only"}'
|
||
|
|
|
||
|
|
# Generate key
|
||
|
|
curl -X POST http://10.0.0.25:4444/users/erp-user/keys \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-d '{"key_name": "erp-only"}'
|
||
|
|
|
||
|
|
# Restrict to ERPNext
|
||
|
|
curl -X PUT http://10.0.0.25:4444/users/erp-user/mcp-access \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-d '{"allowed_mcps": ["erpnext"]}'
|
||
|
|
```
|
||
|
|
|
||
|
|
### Example 2: Block Specific MCPs
|
||
|
|
|
||
|
|
Create a user with access to everything except Home Assistant:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
curl -X PUT http://10.0.0.25:4444/users/alice/mcp-access \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-d '{"blocked_mcps": ["homeassistant"]}'
|
||
|
|
```
|
||
|
|
|
||
|
|
### Example 3: Revoke an API Key
|
||
|
|
|
||
|
|
```bash
|
||
|
|
curl -X POST http://10.0.0.25:4444/keys/revoke \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-d '{"api_key": "mcpgw_..."}'
|
||
|
|
```
|
||
|
|
|
||
|
|
## Database Schema
|
||
|
|
|
||
|
|
User data is stored in `/data/users.json` with the following structure:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"users": {
|
||
|
|
"alice": {
|
||
|
|
"user_id": "abc123...",
|
||
|
|
"email": "alice@example.com",
|
||
|
|
"description": "Engineering team",
|
||
|
|
"created_at": "2026-03-31T12:00:00.000000",
|
||
|
|
"enabled": true,
|
||
|
|
"api_keys": ["hash1", "hash2"],
|
||
|
|
"mcp_allowed": ["erpnext", "wave"],
|
||
|
|
"mcp_blocked": [],
|
||
|
|
"metadata": {}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"api_keys": {
|
||
|
|
"hash1": {
|
||
|
|
"user_id": "abc123...",
|
||
|
|
"username": "alice",
|
||
|
|
"key_name": "prod-key",
|
||
|
|
"created_at": "2026-03-31T12:00:00.000000",
|
||
|
|
"expires_at": null,
|
||
|
|
"revoked": false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Security Notes
|
||
|
|
|
||
|
|
1. **API Keys are Hashed**: Only SHA256 hashes are stored, never the actual key
|
||
|
|
2. **One-Time Display**: Keys are shown only once during generation
|
||
|
|
3. **TTL Support**: Keys can expire after N days
|
||
|
|
4. **Revocation**: Keys can be revoked at any time
|
||
|
|
5. **Per-User Access Control**: Each user has independent MCP restrictions
|
||
|
|
|
||
|
|
## Troubleshooting
|
||
|
|
|
||
|
|
### Users/Keys Not Persisting After Restart
|
||
|
|
|
||
|
|
Check that:
|
||
|
|
1. `/data` directory exists and is mounted in docker-compose.yml
|
||
|
|
2. `/data` directory has write permissions
|
||
|
|
3. `USERS_DB_PATH=/data/users.json` is set in environment
|
||
|
|
|
||
|
|
### API Key Not Working
|
||
|
|
|
||
|
|
1. Verify key hasn't been revoked: Check in dashboard
|
||
|
|
2. Verify key hasn't expired: Check `expires_at` timestamp
|
||
|
|
3. Verify user is enabled: Check `enabled` field in dashboard
|
||
|
|
4. Check MCP access: Verify the user has access to the MCP they're trying to use
|
||
|
|
|
||
|
|
## Next Steps
|
||
|
|
|
||
|
|
- Configure additional users for different teams/roles
|
||
|
|
- Set up automated key rotation policies
|
||
|
|
- Monitor API key usage via the dashboard
|
||
|
|
- Integrate with your CI/CD for automated deployments
|