Files
felhom.eu/hub/internal/api/customers_auth_test.go
T

83 lines
3.2 KiB
Go

package api
import (
"net/http"
"strings"
"testing"
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
)
// R-951 (security review 2026-10-12): GET /customers, /customers/{id} and /customers/{id}/history answered with NO
// credential and returned every customer's name, dashboard address and stored box report. Since v0.148.0 they are
// operator/global key only. Red-proof (2026-10-12): with the requireGlobalKey call removed from handleCustomer,
// TestCustomersRoutes_NoCredentialIsRefused failed (GET /customers/c1 answered 200).
const customersTestName = "Teszt Csalad R935"
// newCustomersHandler: customer c1 (key ckey) with one stored report and a host h1 (key hkey) under it; a second
// customer c2 (key c2key).
func newCustomersHandler(t *testing.T) (*Handler, *store.Store) {
t.Helper()
h, st := newTwoCustomerHandler(t)
if err := st.UpsertHost(&store.Host{HostID: "h1", CustomerID: "c1", APIKey: "hkey"}); err != nil {
t.Fatalf("UpsertHost: %v", err)
}
rr := do(h, http.MethodPost, "/report", "ckey", `{"customer_id":"c1","customer_name":"`+customersTestName+`"}`)
if rr.Code != http.StatusOK {
t.Fatalf("seed report: status = %d, body=%s", rr.Code, rr.Body.String())
}
return h, st
}
var customersRoutes = []string{"/customers", "/customers/c1", "/customers/c1/history"}
// Scenario A — a stranger, no Authorization header.
func TestCustomersRoutes_NoCredentialIsRefused(t *testing.T) {
h, _ := newCustomersHandler(t)
for _, p := range customersRoutes {
rr := do(h, http.MethodGet, p, "", "")
if rr.Code != http.StatusUnauthorized && rr.Code != http.StatusForbidden {
t.Errorf("GET %s with no credential: status = %d, want 401/403", p, rr.Code)
}
if strings.Contains(rr.Body.String(), customersTestName) || strings.Contains(rr.Body.String(), `"c1"`) {
t.Errorf("GET %s with no credential: body names a customer: %q", p, rr.Body.String())
}
}
}
// Scenario B — a box's own keys (per-customer, per-host, another household's) never list customers.
func TestCustomersRoutes_BoxKeysAreRefused(t *testing.T) {
h, _ := newCustomersHandler(t)
for _, key := range []string{"ckey", "hkey", "c2key", "not-a-key"} {
for _, p := range customersRoutes {
if rr := do(h, http.MethodGet, p, key, ""); rr.Code != http.StatusForbidden {
t.Errorf("GET %s with box key %q: status = %d, want 403", p, key, rr.Code)
}
}
}
}
// Scenario C — the operator key gets what v0.147.0 returned to anyone.
func TestCustomersRoutes_GlobalKeyUnchanged(t *testing.T) {
h, st := newCustomersHandler(t)
rr := do(h, http.MethodGet, "/customers", globalKey, "")
if rr.Code != http.StatusOK || !strings.Contains(rr.Body.String(), customersTestName) {
t.Fatalf("GET /customers with global key: status = %d body=%s", rr.Code, rr.Body.String())
}
c, err := st.GetCustomer("c1")
if err != nil || c == nil {
t.Fatalf("GetCustomer: %v %v", c, err)
}
rr = do(h, http.MethodGet, "/customers/c1", globalKey, "")
if rr.Code != http.StatusOK || rr.Body.String() != c.ReportJSON {
t.Fatalf("GET /customers/c1 with global key: status = %d, body differs from the stored report", rr.Code)
}
if rr = do(h, http.MethodGet, "/customers/c1/history", globalKey, ""); rr.Code != http.StatusOK {
t.Fatalf("GET /customers/c1/history with global key: status = %d", rr.Code)
}
}