HTTP API: server, credential and the list of chats (closes #4)
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:
2026-09-29 04:55:49 +02:00
parent ac721390de
commit f10d820ed4
18 changed files with 1214 additions and 47 deletions
+46
View File
@@ -0,0 +1,46 @@
package api
import (
"cmp"
"net/http"
"slices"
)
// handleChats lists the bot's chats, ordered by id. The bot talks to
// people only one to one, so its chats are its contacts, and a chat's
// id is its contact's.
func (h *handlers) handleChats() http.HandlerFunc {
type chat struct {
ID int64 `json:"id"`
DisplayName string `json:"display_name"`
ContactDeleted bool `json:"contact_deleted"`
}
type response struct {
Chats []chat `json:"chats"`
}
return func(w http.ResponseWriter, r *http.Request) {
contacts, err := h.client.Contacts(r.Context(), h.userID)
if err != nil {
h.log.Error("listing the chats", "error", err)
h.respondError(w, http.StatusInternalServerError,
"the chats could not be read")
return
}
chats := make([]chat, 0, len(contacts))
for _, c := range contacts {
chats = append(chats, chat{
ID: c.ContactID,
DisplayName: c.Profile.DisplayName,
ContactDeleted: c.Deleted(),
})
}
slices.SortFunc(chats, func(a, b chat) int { return cmp.Compare(a.ID, b.ID) })
h.respond(w, http.StatusOK, response{Chats: chats})
}
}