Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- fix(client): replace `typer.Option(Path(...))` with `typer.Option(default=...)` in all three subcommands to prevent the same Typer OptionInfo-as-default bug (LOT-011 / BUGF-002)

### Added
- docs: sync user/technical docs to the capability-aware bridge — Bearer + role-based auth and `dcs-tokens.yaml` in configuration, no-credential-in-browser web flow, catalogue-driven MCP tool list, corrected architecture/index/README (dropped stale `X-API-Key`/port `9999`/old MCP tool references) (LOT-018)
- feat(clients): catalogue-driven MCP + TUI/WEB catalogue presentation (ADR-0005 / LOT-018 ticket 08) — the MCP client now exposes a small, fixed tool set independent of catalogue size (`list_catalog`/`search_catalog`/`describe_action` discovery, a single generic `run_action`, `get_units`/`capabilities` reads, and `exec_lua` gated to `superuser`), retiring the ad-hoc per-action `spawn`/`spawn_unit`/`get_mission_info` tools. The TUI lists the mission's available actions on startup; the WEB client shows a capability-filtered actions panel fed by a new `/catalog` proxy (token stays server-side). Clients hold no domain knowledge — they proxy `/api/catalog` and `/api/action`. CLI reference updated (EN + FR)
- feat(transport): Bearer auth + ephemeral WebSocket tickets (ADR-0005 / LOT-018 ticket 07) — **breaking**: REST now authenticates via `Authorization: Bearer <token>` only (dropped the `X-API-Key` header and the `api_key` query param, so no credential appears in a URL or log). The WebSocket is opened with a single-use ~10 s ticket from the new `POST /api/ws-ticket` (`serve/security.py` `TicketStore`); `/ws/stream` takes `?ticket=` instead of `?api_key=`. The WEB client holds the durable token server-side, serves only host/port at `/config.json` (no key) and proxies tickets via `POST /ws-ticket`; the Leaflet page fetches a ticket before each connect — the key-in-URL leak is gone. MCP and TUI clients migrated to Bearer (+ ticket for the TUI stream). API reference updated (EN + FR)
- feat(serve): role-based security — roles, tokens, per-action enforcement (ADR-0005 / LOT-018 ticket 06) — new `serve/security.py` with roles `observer(0)/pilot(1)/operator(10)/administrator(90)/superuser(99)` aligned on VEAF levels, and a role-bearing token store (`{token, role, label, ucid?, expiry?}`, loaded from `dcs-tokens.yaml`) that replaces the single API key. Enforcement lives in the bridge: each action's minimum role is checked before execution (`403` below minimum), `POST /api/exec` is gated to `superuser`, read-only routes to `observer`, `/api/spawn` to `operator`. The caller's resolved VEAF level is propagated into VEAF-backed adapters (never a blanket `bypassSecurity`). Delegated WEB mode resolves UCID→role from `veaf-pilots.txt` server-side. The legacy `api_key` keeps working as a `superuser` token during the transition. API reference updated (EN + FR)
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ Generic bridge between DCS World and external consumers — TUI, web map, and AI
```
DCS World (Lua 5.1)
│ dcs-bridge.lua — unit positions + events
│ TCP JSON (port 9999)
│ TCP JSON (port 7777)
dcs-serve (Python asyncio)
in-memory snapshot · command bus · X-API-Key auth
│ snapshot · command bus · capability-aware actions · role-based auth (Bearer)
├─── HTTP REST ──► dcs-client tui (Textual terminal UI)
├─── WebSocket ──► dcs-client web (Leaflet map, real-time)
└─── HTTP REST ──► dcs-client mcp (MCP stdio → AI agents)
Expand All @@ -23,10 +23,10 @@ dcs-serve (Python asyncio)
## Features

- **Lua bridge** — non-blocking, exponential reconnect, full refresh every 5 s, `exec` and `spawn` commands
- **dcs-serve** — asyncio TCP + FastAPI, snapshot with staleness detection, command/response correlation
- **dcs-client tui** — Textual terminal UI: real-time unit table + Lua REPL
- **dcs-client web** — Leaflet map with coalition-coloured markers and hover tooltips
- **dcs-client mcp** — MCP server exposing `exec_lua`, `get_units`, `spawn_unit`, `get_mission_info`
- **dcs-serve** — asyncio TCP + FastAPI, snapshot with staleness detection, command/response correlation, **capability-aware semantic actions** (façade over DCS/MIST/CTLD/VMCT) with role-based auth
- **dcs-client tui** — Textual terminal UI: real-time unit table + Lua REPL + available-actions catalogue
- **dcs-client web** — Leaflet map with coalition-coloured markers, hover tooltips, and a capability-filtered actions panel
- **dcs-client mcp** — catalogue-driven MCP server: `list_catalog`/`search_catalog`/`describe_action`, a generic `run_action`, `get_units`/`capabilities`, and `exec_lua` (superuser)
- **Packaged** — one-file Windows executables via PyInstaller, also available on PyPI

## Quick start
Expand Down
7 changes: 4 additions & 3 deletions docs/guide/cli-reference.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ Options:
--help Show this message and exit
```

Launches a local HTTP server and opens the Leaflet map in the browser. The map connects
to `dcs-serve` using the `host`, `port` and `api_key` from `dcs-client.yaml` (served to
the browser via `GET /config.json`), so no manual URL editing is needed.
Launches a local HTTP server and opens the Leaflet map in the browser. The web server
reads `host`, `port` and `api_key` from `dcs-client.yaml`; it keeps the token
server-side and serves only `host`/`port` at `GET /config.json`, handing the browser
short-lived WebSocket tickets via `POST /ws-ticket` (no credential in the browser).

### dcs-client mcp

Expand Down
8 changes: 5 additions & 3 deletions docs/guide/cli-reference.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,11 @@ Options:
--help Afficher ce message et quitter
```

Lance un serveur HTTP local et ouvre la carte Leaflet dans le navigateur. La carte se
connecte à `dcs-serve` avec les valeurs `host`, `port` et `api_key` de `dcs-client.yaml`
(exposées au navigateur via `GET /config.json`), sans aucune URL à modifier à la main.
Lance un serveur HTTP local et ouvre la carte Leaflet dans le navigateur. Le serveur web
lit `host`, `port` et `api_key` depuis `dcs-client.yaml` ; il conserve le token côté
serveur et n'expose que `host`/`port` via `GET /config.json`, en fournissant au
navigateur des tickets WebSocket éphémères via `POST /ws-ticket` (aucun credential dans
le navigateur).

### dcs-client mcp

Expand Down
53 changes: 40 additions & 13 deletions docs/guide/configuration.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@ tcp_port: 7777
http_host: "0.0.0.0"
http_port: 8080

# API key (auto-generated on first launch if empty)
# Bearer token (auto-generated on first launch if empty). Kept as a superuser
# token during the transition to the role-based model (see dcs-tokens.yaml).
api_key: ""

# Path to the role-bearing token store (optional)
tokens_file: "dcs-tokens.yaml"

# Default Lua command timeout, in seconds
default_timeout: 10.0

Expand All @@ -30,10 +34,29 @@ stale_threshold: 15.0
|---|---|---|
| `tcp_port` | `7777` | TCP port the Lua script must target |
| `http_port` | `8080` | REST API and WebSocket port |
| `api_key` | *(auto)* | Key to pass to clients (`X-API-Key`) |
| `api_key` | *(auto)* | Durable Bearer token; used as a `superuser` token (ADR-0005) |
| `tokens_file` | `dcs-tokens.yaml` | Optional role-bearing token store |
| `default_timeout` | `10.0` | Timeout for `exec` and `spawn` commands |
| `stale_threshold` | `15.0` | Delay before marking the snapshot as stale |

### dcs-tokens.yaml (optional)

Role-bearing tokens replace the single API key (ADR-0005). Each token carries a
role (`observer`/`pilot`/`operator`/`administrator`/`superuser`, or a numeric VEAF
level) and optional `label`/`ucid`/`expiry`:

```yaml
- token: "observer-token"
role: observer
label: "read-only dashboard"
- token: "ops-token"
role: operator
label: "mission operator"
```

REST clients send their token as `Authorization: Bearer <token>`. If this file is
absent, the `api_key` above keeps working as a `superuser` token.

## dcs-client.yaml

This file configures the clients (TUI, web, MCP).
Expand All @@ -43,23 +66,27 @@ This file configures the clients (TUI, web, MCP).
host: "127.0.0.1"
port: 8080

# API key (must match dcs-serve.yaml)
api_key: "your-api-key"
# Bearer token (must match a token accepted by dcs-serve)
api_key: "your-token"

# Local port for the web client's Leaflet map
web_port: 8081
```

The file is looked up in the current directory or via the `--config` option.

`dcs-client web` uses `host`, `port` and `api_key` to connect the Leaflet map to
`dcs-serve`: it serves them to the browser at `GET /config.json`, so filling this file
is enough — no manual URL editing. `web_port` is the local port the map is served on.

!!! warning "API key exposure"
`/config.json` returns the API key to any client that can reach `web_port`. The web
client binds to `127.0.0.1` by default; only pass `--web-host 0.0.0.0` on a trusted
network.
`api_key` is the client's durable Bearer token; its role determines which actions
succeed (enforced by dcs-serve).

`dcs-client web` holds this token **server-side** and never exposes it to the
browser (ADR-0005): `GET /config.json` returns only `host`/`port`, and the Leaflet
page opens its WebSocket with a short-lived single-use ticket obtained from the web
server's `POST /ws-ticket` proxy. No credential appears in the page, a URL, or
`/config.json`. `web_port` is the local port the map is served on.

!!! note "No credential in the browser"
The durable token stays on the web server; the browser only ever receives
ephemeral WebSocket tickets. The web client binds to `127.0.0.1` by default;
only pass `--web-host 0.0.0.0` on a trusted network.

## Lua script

Expand Down
57 changes: 43 additions & 14 deletions docs/guide/configuration.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ tcp_port: 7777
http_host: "0.0.0.0"
http_port: 8080

# Clé API (générée automatiquement au premier lancement si vide)
# Token Bearer (généré automatiquement au premier lancement si vide). Conservé
# comme token superuser pendant la transition vers le modèle par rôles
# (voir dcs-tokens.yaml).
api_key: ""

# Chemin du magasin de tokens porteurs de rôle (optionnel)
tokens_file: "dcs-tokens.yaml"

# Timeout par défaut des commandes Lua, en secondes
default_timeout: 10.0

Expand All @@ -30,10 +35,30 @@ stale_threshold: 15.0
|---|---|---|
| `tcp_port` | `7777` | Port TCP que le script Lua doit cibler |
| `http_port` | `8080` | Port de l'API REST et WebSocket |
| `api_key` | *(auto)* | Clé à transmettre aux clients (`X-API-Key`) |
| `api_key` | *(auto)* | Token Bearer durable ; utilisé comme token `superuser` (ADR-0005) |
| `tokens_file` | `dcs-tokens.yaml` | Magasin de tokens porteurs de rôle (optionnel) |
| `default_timeout` | `10.0` | Timeout des commandes `exec` et `spawn` |
| `stale_threshold` | `15.0` | Délai avant de marquer le snapshot comme périmé |

### dcs-tokens.yaml (optionnel)

Les tokens porteurs de rôle remplacent la clé API unique (ADR-0005). Chaque token
porte un rôle (`observer`/`pilot`/`operator`/`administrator`/`superuser`, ou un
niveau VEAF numérique) et, en option, `label`/`ucid`/`expiry` :

```yaml
- token: "observer-token"
role: observer
label: "tableau de bord lecture seule"
- token: "ops-token"
role: operator
label: "opérateur de mission"
```

Les clients REST envoient leur token via `Authorization: Bearer <token>`. Si ce
fichier est absent, l'`api_key` ci-dessus continue de fonctionner comme token
`superuser`.

## dcs-client.yaml

Ce fichier configure les clients (TUI, web, MCP).
Expand All @@ -43,24 +68,28 @@ Ce fichier configure les clients (TUI, web, MCP).
host: "127.0.0.1"
port: 8080

# Clé API (doit correspondre à celle de dcs-serve.yaml)
api_key: "votre-cle-api"
# Token Bearer (doit correspondre à un token accepté par dcs-serve)
api_key: "votre-token"

# Port local de la carte Leaflet du client web
web_port: 8081
```

Le fichier est recherché dans le répertoire courant ou via l'option `--config`.

`dcs-client web` utilise `host`, `port` et `api_key` pour connecter la carte Leaflet à
`dcs-serve` : ces valeurs sont exposées au navigateur via `GET /config.json`, donc
remplir ce fichier suffit — aucune URL à modifier à la main. `web_port` est le port
local sur lequel la carte est servie.

!!! warning "Exposition de la clé API"
`/config.json` renvoie la clé API à tout client capable d'atteindre `web_port`. Le
client web écoute sur `127.0.0.1` par défaut ; ne passez `--web-host 0.0.0.0` que sur
un réseau de confiance.
`api_key` est le token Bearer durable du client ; son rôle détermine quelles
actions aboutissent (appliqué par dcs-serve).

`dcs-client web` conserve ce token **côté serveur** et ne l'expose jamais au
navigateur (ADR-0005) : `GET /config.json` ne renvoie que `host`/`port`, et la page
Leaflet ouvre son WebSocket avec un ticket éphémère à usage unique obtenu via le
proxy `POST /ws-ticket` du serveur web. Aucun credential n'apparaît dans la page,
une URL ou `/config.json`. `web_port` est le port local sur lequel la carte est
servie.

!!! note "Aucun credential dans le navigateur"
Le token durable reste sur le serveur web ; le navigateur ne reçoit que des
tickets WebSocket éphémères. Le client web écoute sur `127.0.0.1` par défaut ;
ne passez `--web-host 0.0.0.0` que sur un réseau de confiance.

## Script Lua

Expand Down
2 changes: 1 addition & 1 deletion docs/index.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ graph LR
## Features

- **Lua bridge** — injected into DCS World, streams unit positions and events in real time
- **dcs-serve** — TCP/HTTP/WebSocket server, in-memory snapshot, API key authentication
- **dcs-serve** — TCP/HTTP/WebSocket server, in-memory snapshot, capability-aware actions, role-based (Bearer) authentication
- **dcs-client tui** — Textual terminal UI with real-time unit table and Lua REPL
- **dcs-client web** — Leaflet map with coalition-coloured markers, real-time updates
- **dcs-client mcp** — MCP server exposing `exec_lua`, `get_units`, `spawn_unit`, `get_mission_info`
Expand Down
2 changes: 1 addition & 1 deletion docs/index.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ graph LR
## Fonctionnalités

- **Bridge Lua** — injecté dans DCS World, envoie les positions des unités et les événements en temps réel
- **dcs-serve** — serveur TCP/HTTP/WebSocket, snapshot en mémoire, authentification par clé API
- **dcs-serve** — serveur TCP/HTTP/WebSocket, snapshot en mémoire, actions capability-aware, authentification par rôle (token Bearer)
- **dcs-client tui** — interface terminal Textual avec tableau des unités en temps réel et REPL Lua
- **dcs-client web** — carte Leaflet avec marqueurs colorés par coalition, mise à jour en temps réel
- **dcs-client mcp** — serveur MCP exposant `exec_lua`, `get_units`, `spawn_unit`, `get_mission_info`
Expand Down
2 changes: 1 addition & 1 deletion docs/technical/architecture.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Python asyncio server with two services co-located in the same event loop:
| `Snapshot` | In-memory cache of active units, staleness detection |
| `CommandBus` | Command/response correlation by `id` with timeout |
| `EventBroadcaster` | Thread-safe fan-out of events to WebSocket clients |
| `FastAPI app` | REST routes + WebSocket, X-API-Key middleware |
| `FastAPI app` | REST routes + WebSocket, Bearer-token role-based auth |

### dcs-client

Expand Down
2 changes: 1 addition & 1 deletion docs/technical/architecture.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Serveur asyncio Python avec deux services co-localisés dans la même boucle d'
| `Snapshot` | Cache en mémoire des unités actives, détection de péremption |
| `CommandBus` | Corrélation commande/réponse par `id` avec timeout |
| `EventBroadcaster` | Fan-out thread-safe des événements vers les WebSocket clients |
| `FastAPI app` | Routes REST + WebSocket, middleware X-API-Key |
| `FastAPI app` | Routes REST + WebSocket, auth par rôle via token Bearer |

### dcs-client

Expand Down
Loading