mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-09-11 20:45:33 +00:00
Part 1 of #1856. Part 2 (the hash migration reporting false success) is #1958. ## It has never worked `handlePostPacket` writes to the server's DB handle, and that handle is read-only. `cmd/server/db.go:106`: ```go dsn := fmt.Sprintf("file:%s?mode=ro&_journal_mode=WAL&_busy_timeout=5000", path) ``` Every call answered `500 attempt to write a readonly database`. **This is the second report.** #1196 raised it on 2026-06-13, a fix was merged that corrected v2 column names to v3, and the issue was closed. That fix could not have worked, because the column names were never why the write failed. Its comment is still sitting at `routes.go:1288`, next to code that has never executed successfully in production. ## Why remove rather than build a handoff **It cannot break a caller.** An endpoint that has only ever returned 500 has no working consumer. This is not a breaking API change, it is documentation catching up with reality. Nothing in `public/` calls it. **It was actively misleading.** `openapi.go` advertised it as "Ingest a packet" and it sits behind `requireAPIKey`, which reads as a live, protected write endpoint. **Its test hid the breakage.** `TestPostPacketPersistsV3Schema` asserted the observation row is written and passed for four months, because the test DB is opened read-write while production is not. That is how #1196 came to be closed as fixed. **Ingest is MQTT-only by design since #1283.** Re-adding an HTTP write path re-opens the invariant that change established. If manual injection is wanted later for testing or replay, it belongs on the ingestor side and deserves its own issue. The repository already has the handoff shape for that: the server writes `request-<id>.json` and the ingestor consumes it (`cmd/ingestor/prune_geofilter.go`). ## What went The route, `handlePostPacket` (103 lines), the now-unused `PacketIngestResponse` type, the `openapi.go` entry, the round-trip test, and the section plus table-of-contents line in `docs/api-spec.md`. The `packetpath` import in `routes.go` became unused and went with it. `+4/-225` across 6 files. ## The auth tests The four `requireAPIKey` tests used `"/api/packets"` only as a request path while building their own handler with `s.requireAPIKey(...)`, so they never touched the route. I checked that by **running them**, not by reading the code: ``` --- PASS: TestRequireAPIKey_RejectsWeakKey --- PASS: TestRequireAPIKey_AcceptsStrongKey --- PASS: TestRequireAPIKey_EmptyKeyDisablesEndpoints --- PASS: TestRequireAPIKey_WrongKeyUnauthorized ``` Their paths now point at `/api/admin/prune-geo-filter`, which still exists, so they no longer name a removed endpoint. Re-ran after that change: still 4 of 4. `/api/packets/observations` is a different endpoint and is untouched. Verified: `gofmt` clean, `go vet` clean, `cmd/server` suite ok in 62.9s. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
112 lines
3.1 KiB
Go
112 lines
3.1 KiB
Go
package main
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
func TestIsWeakAPIKey(t *testing.T) {
|
|
// Known defaults must be detected
|
|
for _, weak := range []string{
|
|
"your-secret-api-key-here", "change-me", "example", "test",
|
|
"password", "admin", "apikey", "api-key", "secret", "default",
|
|
} {
|
|
if !IsWeakAPIKey(weak) {
|
|
t.Errorf("expected %q to be weak", weak)
|
|
}
|
|
}
|
|
// Case-insensitive
|
|
if !IsWeakAPIKey("Password") {
|
|
t.Error("expected case-insensitive match for Password")
|
|
}
|
|
if !IsWeakAPIKey("YOUR-SECRET-API-KEY-HERE") {
|
|
t.Error("expected case-insensitive match")
|
|
}
|
|
|
|
// Short keys (<16 chars) are weak
|
|
if !IsWeakAPIKey("short") {
|
|
t.Error("expected short key to be weak")
|
|
}
|
|
if !IsWeakAPIKey("exactly15chars!") { // 15 chars
|
|
t.Error("expected 15-char key to be weak")
|
|
}
|
|
|
|
// Empty key is NOT weak (handled separately as "disabled")
|
|
if IsWeakAPIKey("") {
|
|
t.Error("empty key should not be flagged as weak")
|
|
}
|
|
|
|
// Strong keys pass
|
|
if IsWeakAPIKey("a-very-strong-key-1234") {
|
|
t.Error("expected strong key to pass")
|
|
}
|
|
if IsWeakAPIKey("xK9!mP2@nL5#qR8$") {
|
|
t.Error("expected 17-char random key to pass")
|
|
}
|
|
}
|
|
|
|
func TestRequireAPIKey_RejectsWeakKey(t *testing.T) {
|
|
s := &Server{cfg: &Config{APIKey: "test"}}
|
|
handler := s.requireAPIKey(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
|
|
req := httptest.NewRequest("POST", "/api/admin/prune-geo-filter", nil)
|
|
req.Header.Set("X-API-Key", "test")
|
|
rr := httptest.NewRecorder()
|
|
handler.ServeHTTP(rr, req)
|
|
|
|
if rr.Code != http.StatusForbidden {
|
|
t.Errorf("expected 403 for weak key, got %d", rr.Code)
|
|
}
|
|
}
|
|
|
|
func TestRequireAPIKey_AcceptsStrongKey(t *testing.T) {
|
|
strongKey := "a-very-strong-key-1234"
|
|
s := &Server{cfg: &Config{APIKey: strongKey}}
|
|
handler := s.requireAPIKey(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
|
|
req := httptest.NewRequest("POST", "/api/admin/prune-geo-filter", nil)
|
|
req.Header.Set("X-API-Key", strongKey)
|
|
rr := httptest.NewRecorder()
|
|
handler.ServeHTTP(rr, req)
|
|
|
|
if rr.Code != http.StatusOK {
|
|
t.Errorf("expected 200 for strong key, got %d", rr.Code)
|
|
}
|
|
}
|
|
|
|
func TestRequireAPIKey_EmptyKeyDisablesEndpoints(t *testing.T) {
|
|
s := &Server{cfg: &Config{APIKey: ""}}
|
|
handler := s.requireAPIKey(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
|
|
req := httptest.NewRequest("POST", "/api/admin/prune-geo-filter", nil)
|
|
rr := httptest.NewRecorder()
|
|
handler.ServeHTTP(rr, req)
|
|
|
|
if rr.Code != http.StatusForbidden {
|
|
t.Errorf("expected 403 for empty key, got %d", rr.Code)
|
|
}
|
|
}
|
|
|
|
func TestRequireAPIKey_WrongKeyUnauthorized(t *testing.T) {
|
|
s := &Server{cfg: &Config{APIKey: "a-very-strong-key-1234"}}
|
|
handler := s.requireAPIKey(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
|
|
req := httptest.NewRequest("POST", "/api/admin/prune-geo-filter", nil)
|
|
req.Header.Set("X-API-Key", "wrong-key-entirely-here")
|
|
rr := httptest.NewRecorder()
|
|
handler.ServeHTTP(rr, req)
|
|
|
|
if rr.Code != http.StatusUnauthorized {
|
|
t.Errorf("expected 401 for wrong key, got %d", rr.Code)
|
|
}
|
|
}
|