security: require API key for POST /api/packets and /api/perf/reset

- New config.apiKey field — when set, POST endpoints require X-Api-Key header
- If apiKey not configured, endpoints remain open (dev/local mode)
- GET endpoints and /api/decode (read-only) remain public
- Closes the packet injection attack surface
This commit is contained in:
you
2026-03-21 18:40:06 +00:00
parent 804c39504c
commit 2170dd7743
2 changed files with 12 additions and 2 deletions
+1
View File
@@ -1,5 +1,6 @@
{
"port": 3000,
"apiKey": "your-secret-api-key-here",
"https": {
"cert": "/path/to/cert.pem",
"key": "/path/to/key.pem"
+11 -2
View File
@@ -29,6 +29,15 @@ const MAX_HOP_DIST_SERVER = config.maxHopDist || 1.8;
const crypto = require('crypto');
const PacketStore = require('./packet-store');
// API key middleware for write endpoints
const API_KEY = config.apiKey || null;
function requireApiKey(req, res, next) {
if (!API_KEY) return next(); // no key configured = open (dev mode)
const provided = req.headers['x-api-key'] || req.query.apiKey;
if (provided === API_KEY) return next();
return res.status(401).json({ error: 'Invalid or missing API key' });
}
// Compute a content hash from raw hex: header byte + payload (skipping path hops)
// This correctly groups retransmissions of the same packet (same content, different paths)
function computeContentHash(rawHex) {
@@ -317,7 +326,7 @@ app.get('/api/perf', (req, res) => {
});
});
app.post('/api/perf/reset', (req, res) => { perfStats.reset(); res.json({ ok: true }); });
app.post('/api/perf/reset', requireApiKey, (req, res) => { perfStats.reset(); res.json({ ok: true }); });
// --- Event Loop Lag Monitoring ---
let evtLoopLag = 0, evtLoopMax = 0, evtLoopSamples = [];
@@ -965,7 +974,7 @@ app.post('/api/decode', (req, res) => {
}
});
app.post('/api/packets', (req, res) => {
app.post('/api/packets', requireApiKey, (req, res) => {
try {
const { hex, observer, snr, rssi, region, hash } = req.body;
if (!hex) return res.status(400).json({ error: 'hex is required' });