Compare commits
2 Commits
abhi1693/f
...
docs/opena
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b82c9960c3 | ||
|
|
d5c72f3592 |
112
backend/app/core/openapi.py
Normal file
112
backend/app/core/openapi.py
Normal file
@@ -0,0 +1,112 @@
|
||||
"""OpenAPI customization helpers.
|
||||
|
||||
Goal: make the generated OpenAPI spec accurately represent Mission Control auth modes.
|
||||
|
||||
Mission Control supports two primary auth mechanisms:
|
||||
- User (Clerk): Authorization: Bearer <token> (HTTP bearer)
|
||||
- Agent: X-Agent-Token: <token> (apiKey in header)
|
||||
|
||||
FastAPI's default OpenAPI generation only reflects dependencies that use built-in
|
||||
security helpers. For agent auth, we add an explicit `AgentToken` securityScheme
|
||||
and annotate operations that accept `X-Agent-Token`.
|
||||
|
||||
This module is intentionally *documentation-only*: it must not change runtime
|
||||
authentication behavior.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Final
|
||||
|
||||
from fastapi.openapi.utils import get_openapi
|
||||
|
||||
AGENT_TOKEN_HEADER: Final[str] = "X-Agent-Token"
|
||||
AGENT_TOKEN_SCHEME_NAME: Final[str] = "AgentToken"
|
||||
|
||||
|
||||
def _has_header_param(op: dict[str, Any], header_name: str) -> bool:
|
||||
for p in op.get("parameters", []) or []:
|
||||
if p.get("in") == "header" and p.get("name") == header_name:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def build_openapi(app_title: str, app_version: str, routes: Any) -> dict[str, Any]:
|
||||
"""Return a customized OpenAPI document.
|
||||
|
||||
Args:
|
||||
app_title: FastAPI app title.
|
||||
app_version: FastAPI app version.
|
||||
routes: FastAPI routes.
|
||||
|
||||
Returns:
|
||||
OpenAPI dict.
|
||||
"""
|
||||
|
||||
schema: dict[str, Any] = get_openapi(title=app_title, version=app_version, routes=routes)
|
||||
|
||||
components = schema.setdefault("components", {})
|
||||
security_schemes = components.setdefault("securitySchemes", {})
|
||||
|
||||
# Add AgentToken scheme (apiKey header).
|
||||
security_schemes.setdefault(
|
||||
AGENT_TOKEN_SCHEME_NAME,
|
||||
{"type": "apiKey", "in": "header", "name": AGENT_TOKEN_HEADER},
|
||||
)
|
||||
|
||||
# Add a small, explicit error schema we can reference for 401/403.
|
||||
schemas = components.setdefault("schemas", {})
|
||||
schemas.setdefault(
|
||||
"HTTPError",
|
||||
{
|
||||
"title": "HTTPError",
|
||||
"type": "object",
|
||||
"properties": {"detail": {"title": "Detail", "anyOf": [{"type": "string"}, {"type": "object"}]}},
|
||||
"required": ["detail"],
|
||||
"description": "Standard FastAPI HTTPException response body.",
|
||||
},
|
||||
)
|
||||
|
||||
def ensure_auth_responses(op: dict[str, Any]) -> None:
|
||||
responses = op.setdefault("responses", {})
|
||||
for code, desc in (("401", "Unauthorized"), ("403", "Forbidden")):
|
||||
responses.setdefault(
|
||||
code,
|
||||
{
|
||||
"description": desc,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/HTTPError"},
|
||||
"examples": {
|
||||
"example": {"value": {"detail": desc}},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
# Walk operations and attach AgentToken security where relevant.
|
||||
for _path, methods in (schema.get("paths") or {}).items():
|
||||
for _method, op in (methods or {}).items():
|
||||
if not isinstance(op, dict):
|
||||
continue
|
||||
|
||||
has_agent_header = _has_header_param(op, AGENT_TOKEN_HEADER)
|
||||
|
||||
# If the operation already declares bearer security, and it also accepts agent token,
|
||||
# represent "either bearer OR agent token".
|
||||
if has_agent_header:
|
||||
security = op.get("security") or []
|
||||
if security:
|
||||
# OR across entries (list). Preserve existing, add agent token as another option.
|
||||
if not any(AGENT_TOKEN_SCHEME_NAME in s for s in security if isinstance(s, dict)):
|
||||
security.append({AGENT_TOKEN_SCHEME_NAME: []})
|
||||
op["security"] = security
|
||||
else:
|
||||
op["security"] = [{AGENT_TOKEN_SCHEME_NAME: []}]
|
||||
|
||||
# If the operation requires *any* auth (bearer and/or agent token), document 401/403.
|
||||
if op.get("security"):
|
||||
ensure_auth_responses(op)
|
||||
|
||||
return schema
|
||||
@@ -94,6 +94,16 @@ app = FastAPI(
|
||||
openapi_tags=OPENAPI_TAGS,
|
||||
)
|
||||
|
||||
# OpenAPI customization (docs-only): reflect agent auth (X-Agent-Token) in the spec.
|
||||
from app.core.openapi import build_openapi # noqa: E402
|
||||
|
||||
|
||||
def custom_openapi() -> dict:
|
||||
return build_openapi(app_title=app.title, app_version=app.version, routes=app.routes)
|
||||
|
||||
|
||||
app.openapi = custom_openapi # type: ignore[assignment]
|
||||
|
||||
origins = [o.strip() for o in settings.cors_origins.split(",") if o.strip()]
|
||||
if origins:
|
||||
app.add_middleware(
|
||||
|
||||
140
docs/07-api-reference.md
Normal file
140
docs/07-api-reference.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# API / auth
|
||||
|
||||
This page documents how Mission Control’s API surface is organized and how authentication works.
|
||||
|
||||
For deeper backend architecture context, see:
|
||||
- [Architecture](05-architecture.md)
|
||||
|
||||
## Base path
|
||||
|
||||
Evidence: `backend/app/main.py`.
|
||||
|
||||
- All API routes are mounted under: `/api/v1/*`
|
||||
|
||||
## OpenAPI / Swagger UI
|
||||
|
||||
Mission Control is a FastAPI app, so it serves its OpenAPI schema and interactive docs:
|
||||
|
||||
- OpenAPI JSON: `GET /openapi.json`
|
||||
- Swagger UI: `GET /docs`
|
||||
- ReDoc: `GET /redoc`
|
||||
|
||||
### Exporting OpenAPI to a versioned artifact
|
||||
|
||||
Evidence: `backend/scripts/export_openapi.py`, `backend/README.md`.
|
||||
|
||||
From repo root:
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
uv sync --extra dev
|
||||
uv run python scripts/export_openapi.py
|
||||
# writes: backend/openapi.json
|
||||
```
|
||||
|
||||
## Auth model (two callers)
|
||||
|
||||
Mission Control has two primary actor types:
|
||||
|
||||
1) **User (Clerk)** — human UI/admin
|
||||
2) **Agent (`X-Agent-Token`)** — automation
|
||||
|
||||
### User auth (Clerk)
|
||||
|
||||
Evidence:
|
||||
- backend: `backend/app/core/auth.py`
|
||||
- config: `backend/app/core/config.py`
|
||||
|
||||
- Frontend calls backend using `Authorization: Bearer <token>`.
|
||||
- Backend validates requests using the Clerk Backend API SDK with `CLERK_SECRET_KEY`.
|
||||
|
||||
### Agent auth (`X-Agent-Token`)
|
||||
|
||||
Evidence:
|
||||
- `backend/app/core/agent_auth.py`
|
||||
- agent API surface: `backend/app/api/agent.py`
|
||||
|
||||
- Agents authenticate with `X-Agent-Token: <token>`.
|
||||
- Token is verified against the agent’s stored `agent_token_hash`.
|
||||
|
||||
OpenAPI note:
|
||||
- The OpenAPI schema models this as an `apiKey` security scheme named `AgentToken`.
|
||||
- Some endpoints allow **either** `Authorization: Bearer ...` (admin user) **or** `X-Agent-Token` (agent).
|
||||
|
||||
## Route groups (modules)
|
||||
|
||||
Evidence: `backend/app/main.py` includes routers from `backend/app/api/*`.
|
||||
|
||||
| Module | Prefix (under `/api/v1`) | Purpose |
|
||||
|---|---|---|
|
||||
| `activity.py` | `/activity` | Activity listing and task-comment feed endpoints. |
|
||||
| `agent.py` | `/agent` | Agent-scoped API routes for board operations and gateway coordination. |
|
||||
| `agents.py` | `/agents` | Thin API wrappers for async agent lifecycle operations. |
|
||||
| `approvals.py` | `/boards/{board_id}/approvals` | Approval listing, streaming, creation, and update endpoints. |
|
||||
| `auth.py` | `/auth` | Authentication bootstrap endpoints for the Mission Control API. |
|
||||
| `board_group_memory.py` | `/board-groups/{group_id}/memory` and `/boards/{board_id}/group-memory` | Board-group memory CRUD and streaming endpoints. |
|
||||
| `board_groups.py` | `/board-groups` | Board group CRUD, snapshot, and heartbeat endpoints. |
|
||||
| `board_memory.py` | `/boards/{board_id}/memory` | Board memory CRUD and streaming endpoints. |
|
||||
| `board_onboarding.py` | `/boards/{board_id}/onboarding` | Board onboarding endpoints for user/agent collaboration. |
|
||||
| `boards.py` | `/boards` | Board CRUD and snapshot endpoints. |
|
||||
| `gateway.py` | `/gateways` | Thin gateway session-inspection API wrappers. |
|
||||
| `gateways.py` | `/gateways` | Thin API wrappers for gateway CRUD and template synchronization. |
|
||||
| `metrics.py` | `/metrics` | Dashboard metric aggregation endpoints. |
|
||||
| `organizations.py` | `/organizations` | Organization management endpoints and membership/invite flows. |
|
||||
| `souls_directory.py` | `/souls-directory` | API routes for searching and fetching souls-directory markdown entries. |
|
||||
| `tasks.py` | `/boards/{board_id}/tasks` | Task API routes for listing, streaming, and mutating board tasks. |
|
||||
| `users.py` | `/users` | User self-service API endpoints for profile retrieval and updates. |
|
||||
|
||||
## Backend API layer notes (how modules are organized)
|
||||
|
||||
Evidence: `backend/app/main.py`, `backend/app/api/*`, `backend/app/api/deps.py`.
|
||||
|
||||
### Conventions
|
||||
|
||||
- Each file under `backend/app/api/*` typically declares an `APIRouter` (`router = APIRouter(...)`) and defines endpoints with decorators like `@router.get(...)`, `@router.post(...)`, etc.
|
||||
- Board-scoped modules embed `{board_id}` in the prefix (e.g. `/boards/{board_id}/tasks`).
|
||||
- Streaming endpoints usually expose **SSE** endpoints at `.../stream` (see `sse-starlette` usage).
|
||||
|
||||
### Where key behaviors live
|
||||
|
||||
- **Router wiring / base prefix**: `backend/app/main.py` mounts these routers under `/api/v1/*`.
|
||||
- **Auth / access control** is mostly expressed through dependencies (see `backend/app/api/deps.py`):
|
||||
- `require_admin_auth` — require an authenticated *admin user*.
|
||||
- `require_admin_or_agent` — allow either an admin user or an authenticated agent.
|
||||
- `get_board_for_actor_read` / `get_board_for_actor_write` — enforce board access for the calling actor.
|
||||
- `require_org_member` / `require_org_admin` — enforce org membership/admin for user callers.
|
||||
- **Agent-only surface**: `backend/app/api/agent.py` uses `get_agent_auth_context` (X-Agent-Token) and contains board/task/memory endpoints specifically for automation.
|
||||
|
||||
### Module-by-module map (prefix, key endpoints, and pointers)
|
||||
|
||||
This is a “where to look” index, not a full OpenAPI dump. For exact parameters and response shapes, see:
|
||||
- route module file (`backend/app/api/<module>.py`)
|
||||
- schemas (`backend/app/schemas/*`)
|
||||
- models (`backend/app/models/*`)
|
||||
- services (`backend/app/services/*`)
|
||||
|
||||
| Module | Prefix (under `/api/v1`) | Key endpoints (examples) | Main deps / auth | Pointers (schemas/models/services) |
|
||||
|---|---|---|---|---|
|
||||
| `activity.py` | `/activity` | `GET /activity` (events); `GET /activity/task-comments` + `/stream` | `require_admin_or_agent`, `require_org_member` | `app/models/activity_events.py`, `app/schemas/activity_events.py` |
|
||||
| `agent.py` | `/agent` | agent automation surface: boards/tasks/memory + gateway coordination | `get_agent_auth_context` (X-Agent-Token) | `backend/app/core/agent_auth.py`, `backend/app/services/openclaw/*` |
|
||||
| `agents.py` | `/agents` | agent lifecycle + SSE stream + heartbeat | org-admin gated for user callers; some endpoints allow agent access via deps | `app/schemas/agents.py`, `app/services/openclaw/provisioning_db.py` |
|
||||
| `approvals.py` | `/boards/{board_id}/approvals` | list/create/update approvals + `/stream` | `require_admin_or_agent` + board access deps | `app/models/approvals.py`, `app/schemas/approvals.py` |
|
||||
|
||||
## Where authorization is enforced
|
||||
|
||||
Evidence: `backend/app/api/deps.py`.
|
||||
|
||||
Most route modules don’t “hand roll” access checks; they declare dependencies:
|
||||
|
||||
- `require_admin_auth` — admin user only.
|
||||
- `require_admin_or_agent` — admin user OR authenticated agent.
|
||||
- `get_board_for_actor_read` / `get_board_for_actor_write` — board access for user/agent.
|
||||
- `require_org_member` / `require_org_admin` — org membership/admin for user callers.
|
||||
|
||||
## “Start here” pointers for maintainers
|
||||
|
||||
- Router wiring: `backend/app/main.py`
|
||||
- Access dependencies: `backend/app/api/deps.py`
|
||||
- User auth: `backend/app/core/auth.py`
|
||||
- Agent auth: `backend/app/core/agent_auth.py`
|
||||
- Agent automation surface: `backend/app/api/agent.py`
|
||||
Reference in New Issue
Block a user