HTTP API: server, credential and the list of chats (closes #4)
check / check (push) Successful in 1m1s
check / check (push) Successful in 1m1s
The bot now serves an HTTP API on PORT (default 8080) beside the chat client. Every request needs the credential read at startup from the file named by API_TOKEN_FILE, sent as a bearer token; without one, every request is refused. Responses carry the security headers, bodies are capped at 64 KiB and each request's work at 10 seconds. GET /api/v1/chats lists the bot's contacts from the chat client's /_contacts command, ordered by id, and marks the contacts who deleted their chat with the bot, which the chat client keeps listing. bot.Run starts the API after set-up and stops it within 5 seconds; a listener failure ends the bot as a chat client failure does. Model: opus-5-5
This commit is contained in:
@@ -11,18 +11,28 @@ else gets a short 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
|
||||
@@ -67,6 +77,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
|
||||
|
||||
@@ -142,8 +206,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
|
||||
@@ -154,6 +218,16 @@ 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; 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.
|
||||
- **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.
|
||||
|
||||
Reference in New Issue
Block a user