Add POST /api/messages and enforce API token (#56)

This commit is contained in:
l5y
2025-09-15 13:13:47 +02:00
committed by GitHub
parent 3b097feaae
commit 4dc1227be7
2 changed files with 48 additions and 8 deletions

View File

@@ -20,11 +20,8 @@ what works:
* displaying new node notifications and chat messages in default channel in chat box
* displaying active node count and filtering nodes by name
* exposing nodes and messages to api endpoints
what does not work _(yet):_
* posting nodes and messages to the api endpoints _(wip)_
* posting nodes and messages to the api endpoints with authentication
## requirements
requires a meshtastic node connected (via serial) to gather mesh data and the meshtastic cli.
@@ -78,7 +75,7 @@ Puma starting in single mode...
* Listening on http://127.0.0.1:41447
```
set `API_TOKEN` required for authorizations on the api post-endpoints (wip).
set `API_TOKEN` required for authorizations on the api post-endpoints.
the web app can be configured with environment variables (defaults shown):
@@ -100,8 +97,10 @@ the web app contains an api:
* GET `/api/nodes?limit=1000` - returns the latest 1000 nodes reported to the app
* GET `/api/messages?limit=1000` - returns the latest 1000 messages
* POST `/api/nodes` - upserts nodes provided as JSON object mapping node ids to node data (requires `Authorization: Bearer <API_TOKEN>`)
* POST `/api/messages` - appends messages provided as a JSON object or array (requires `Authorization: Bearer <API_TOKEN>`)
the `POST` apis are _currently being worked on (tm)._
the `API_TOKEN` environment variable must be set to a non-empty value and match the token supplied in the `Authorization` header for `POST` requests.
## license

View File

@@ -141,7 +141,29 @@ end
def require_token!
token = ENV["API_TOKEN"]
provided = request.env["HTTP_AUTHORIZATION"].to_s.sub(/^Bearer\s+/i, "")
halt 403, { error: "Forbidden" }.to_json unless token && provided == token
halt 403, { error: "Forbidden" }.to_json unless token && !token.empty? && provided == token
end
def insert_message(db, m)
rx_time = m["rx_time"]&.to_i || Time.now.to_i
rx_iso = m["rx_iso"] || Time.at(rx_time).utc.iso8601
row = [
rx_time,
rx_iso,
m["from_id"],
m["to_id"],
m["channel"],
m["portnum"],
m["text"],
m["snr"],
m["rssi"],
m["hop_limit"],
m["raw_json"],
]
db.execute <<~SQL, row
INSERT INTO messages(rx_time,rx_iso,from_id,to_id,channel,portnum,text,snr,rssi,hop_limit,raw_json)
VALUES (?,?,?,?,?,?,?,?,?,?,?)
SQL
end
post "/api/nodes" do
@@ -162,6 +184,25 @@ ensure
db&.close
end
post "/api/messages" do
require_token!
content_type :json
begin
data = JSON.parse(request.body.read)
rescue JSON::ParserError
halt 400, { error: "invalid JSON" }.to_json
end
messages = data.is_a?(Array) ? data : [data]
halt 400, { error: "too many messages" }.to_json if messages.size > 1000
db = SQLite3::Database.new(DB_PATH)
messages.each do |msg|
insert_message(db, msg)
end
{ status: "ok" }.to_json
ensure
db&.close
end
get "/" do
erb :index, locals: {
site_name: SITE_NAME,