HTTP API: server, credential and the list of chats (closes #4)
check / check (push) Successful in 1m13s
check / check (push) Successful in 1m13s
The bot now serves an HTTP API on `PORT` (default 8080) beside the chat client, whose WebSocket stays on 127.0.0.1 inside the container. Every request needs `Authorization: Bearer` with the credential from the file named by `API_TOKEN_FILE`, compared in constant time; with no credential configured every request is refused, `OPTIONS *` included. `GET /api/v1/chats` lists the bot's chats. Responses carry the security headers from the repository policies; bodies, requests and the server are time- and size-bounded. The chat client stops only after the API has finished its requests. Disclosures: `contact_deleted` is an extra field; 404 and 405 answer in JSON; requests net/http cannot parse are refused by net/http without the security headers; three gosec findings are suppressed as false positives. Model: opus-5-5
This commit was merged in pull request #12.
This commit is contained in:
@@ -12,18 +12,28 @@ explanation instead of a result.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Build the image and run the bot, with its SimpleX profile on a named
|
||||
volume:
|
||||
Build the image, write a random API credential onto a named volume, and
|
||||
run the bot with its SimpleX profile on the same volume:
|
||||
|
||||
```sh
|
||||
git clone git@git.eeqj.de:clawbot/simplexcalc.git
|
||||
cd simplexcalc
|
||||
make docker
|
||||
docker run --rm -v simplexcalc-data:/var/lib/simplexcalc simplexcalc \
|
||||
sh -c 'umask 077 && od -An -N32 -tx1 /dev/urandom | tr -d " \n" \
|
||||
>/var/lib/simplexcalc/api-token'
|
||||
docker run -d --name simplexcalc --restart unless-stopped \
|
||||
-v simplexcalc-data:/var/lib/simplexcalc simplexcalc
|
||||
-v simplexcalc-data:/var/lib/simplexcalc \
|
||||
-p 127.0.0.1:8080:8080 -e API_TOKEN_FILE=/var/lib/simplexcalc/api-token \
|
||||
simplexcalc
|
||||
docker logs simplexcalc 2>&1 | grep '"msg":"ready"'
|
||||
```
|
||||
|
||||
The credential is 32 random bytes written as 64 hexadecimal characters
|
||||
to `/var/lib/simplexcalc/api-token`, readable only by the bot's user.
|
||||
`-p 127.0.0.1:8080:8080` makes the API reachable from this host only;
|
||||
see [API](#api).
|
||||
|
||||
The `ready` log line carries the bot's contact address: `address` is the
|
||||
short link to share, and `full_address` is the same address in the long
|
||||
form that older SimpleX clients need. Open the link in any SimpleX Chat
|
||||
@@ -68,6 +78,60 @@ variables that are absent.
|
||||
`/var/lib/simplexcalc`.
|
||||
- `DEBUG` — `true` or `false`, default `false`. `true` logs every event
|
||||
the chat client sends.
|
||||
- `PORT` — the TCP port the API listens on, on all interfaces: a whole
|
||||
number from 1 to 65535, default `8080`.
|
||||
- `API_TOKEN_FILE` — path of a file holding the API credential, at least
|
||||
32 characters not counting the whitespace around them. The file is
|
||||
read once, at startup; one that cannot be read, or holds a shorter
|
||||
credential, aborts startup. Absent, the API still listens but refuses
|
||||
every request, and startup logs a warning saying so.
|
||||
|
||||
## API
|
||||
|
||||
An HTTP API beside the chat client lets another program read the bot's
|
||||
chats. It speaks JSON on `PORT`.
|
||||
|
||||
**Authentication.** Every request carries the credential from
|
||||
`API_TOKEN_FILE`:
|
||||
|
||||
```
|
||||
Authorization: Bearer {credential}
|
||||
```
|
||||
|
||||
A request without it, or with a wrong one, gets `401` with
|
||||
`WWW-Authenticate: Bearer` and `{"error":"unauthorized"}`. No path is
|
||||
exempt. Without `API_TOKEN_FILE`, every request gets that answer. The
|
||||
file is read at startup, so a new credential takes a restart.
|
||||
|
||||
**Errors** are JSON with a short explanation, such as
|
||||
`{"error":"not found"}`; what went wrong inside goes to the bot's log.
|
||||
|
||||
### `GET /api/v1/chats`
|
||||
|
||||
The bot's chats, ordered by `id`. The bot talks to people only one to
|
||||
one, so each chat is one of its contacts.
|
||||
|
||||
```sh
|
||||
TOKEN=$(docker exec simplexcalc cat /var/lib/simplexcalc/api-token)
|
||||
curl -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8080/api/v1/chats
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"chats": [
|
||||
{ "id": 3, "display_name": "tester", "contact_deleted": false },
|
||||
{ "id": 4, "display_name": "tester", "contact_deleted": true }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- `id` — the chat's number, which is its contact's number in the chat
|
||||
client.
|
||||
- `display_name` — the name the contact gave themselves. Nothing makes
|
||||
it unique.
|
||||
- `contact_deleted` — `true` once the contact has deleted their chat
|
||||
with the bot. The chat stays in the list, but nothing more reaches
|
||||
them.
|
||||
|
||||
## Entrypoints
|
||||
|
||||
@@ -143,8 +207,8 @@ container.
|
||||
- **One process tree, one container.** `simplexcalc run` starts the
|
||||
SimpleX Chat command-line client, `simplex-chat`, as a child process,
|
||||
with its database under `$DATA_DIR/simplex` and its WebSocket API on
|
||||
`127.0.0.1:5225`. The API has no authentication, which is why it is
|
||||
never exposed outside the container.
|
||||
`127.0.0.1:5225`. That WebSocket has no authentication, which is why
|
||||
it is never exposed outside the container.
|
||||
- **The protocol** (`internal/simplex`) is JSON over that WebSocket: a
|
||||
command carries a correlation id, its response carries the same id,
|
||||
and anything without one is an event. Only the fields the bot reads
|
||||
@@ -155,6 +219,17 @@ container.
|
||||
accept every contact request and to greet each new contact. The first
|
||||
start creates the profile itself, a bot profile named `calc`. The
|
||||
address is logged in the `ready` line.
|
||||
- **The API** (`internal/api`): an HTTP server, routed with chi, that
|
||||
starts once set-up is done; if it cannot listen, the bot exits with an
|
||||
error, as when the chat client fails. Every request must carry the
|
||||
credential, compared in constant time; every response carries headers
|
||||
that forbid framing, content sniffing, caching and referrers, and a
|
||||
`Permissions-Policy` that denies the camera, microphone and location;
|
||||
a request body is capped at 64 KiB and a request's work at 10 seconds.
|
||||
Handlers call the chat client on the request's own goroutine, never on
|
||||
the one that delivers events, which also delivers the chat client's
|
||||
answers. When the bot stops, requests in progress get 5 seconds to
|
||||
finish before the chat client is stopped.
|
||||
- **Replies**: for each text message a contact sends in a direct chat,
|
||||
the bot sends back the result, as a reply quoting the message. Group
|
||||
messages, files and the bot's own messages are ignored.
|
||||
@@ -198,11 +273,13 @@ container.
|
||||
|
||||
## Operating it
|
||||
|
||||
**Backup.** Everything durable is the SimpleX database on the volume:
|
||||
`simplex_chat.db` and `simplex_agent.db`. Stop the container before
|
||||
copying them, since a copy taken from under the running client can be
|
||||
inconsistent. The database holds the bot's keys, so a copy lets its
|
||||
holder answer as the bot; keep it as private as the running instance.
|
||||
**Backup.** Everything durable is on the volume: the SimpleX database,
|
||||
`simplex_chat.db` and `simplex_agent.db`, and the API credential,
|
||||
`api-token`. Stop the container before copying the database, since a
|
||||
copy taken from under the running client can be inconsistent. The
|
||||
database holds the bot's keys, so a copy lets its holder answer as the
|
||||
bot, and the credential lets its holder use the API; keep both as
|
||||
private as the running instance.
|
||||
|
||||
**Upgrade.** Rebuild the image and recreate the container with the same
|
||||
volume. The chat client migrates its database on start. A newer
|
||||
|
||||
Reference in New Issue
Block a user