mirror of
https://github.com/MeshCore-Beacon/beacon-server.git
synced 2026-10-16 20:20:07 +00:00
76 lines
2.7 KiB
Go
76 lines
2.7 KiB
Go
// Copyright 2026 Beacon Contributors
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
package middleware
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestBearerAuth(t *testing.T) {
|
|
const key = "synthetic-test-key"
|
|
for _, tc := range []struct {
|
|
name, key string
|
|
headers []string
|
|
status int
|
|
}{
|
|
{"disabled", "", nil, 503}, {"disabled with token", "", []string{"Bearer " + key}, 503},
|
|
{"missing", key, nil, 401}, {"empty", key, []string{""}, 401},
|
|
{"wrong scheme", key, []string{"Basic " + key}, 401}, {"wrong token", key, []string{"Bearer wrong"}, 401},
|
|
{"empty token", key, []string{"Bearer "}, 401}, {"no separator", key, []string{"Bearer" + key}, 401},
|
|
{"tab separator", key, []string{"Bearer\t" + key}, 401}, {"extra token", key, []string{"Bearer " + key + " extra"}, 401},
|
|
{"line break", key, []string{"Bearer " + key + "\r\n"}, 401},
|
|
{"duplicate", key, []string{"Bearer " + key, "Bearer " + key}, 401},
|
|
{"combined", key, []string{"Bearer " + key + ", Bearer " + key}, 401},
|
|
{"case sensitive token", key, []string{"Bearer SYNTHETIC-TEST-KEY"}, 401},
|
|
{"valid", key, []string{"Bearer " + key}, 200},
|
|
{"scheme case", key, []string{"bEaReR " + key}, 200},
|
|
{"multiple spaces", key, []string{"Bearer " + key}, 200},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
calls := 0
|
|
handler := BearerAuth(tc.key, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
calls++
|
|
w.WriteHeader(200)
|
|
}))
|
|
// Query and form tokens alone must never grant access.
|
|
req := httptest.NewRequest("POST", "/admin?access_token="+key, strings.NewReader("access_token="+key))
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
for _, value := range tc.headers {
|
|
req.Header.Add("Authorization", value)
|
|
}
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
if w.Code != tc.status || (calls == 1) != (tc.status == 200) {
|
|
t.Fatalf("status=%d downstream calls=%d", w.Code, calls)
|
|
}
|
|
if w.Header().Get("Cache-Control") != "no-store" || strings.Contains(w.Body.String(), key) {
|
|
t.Fatal("cache policy missing or credential reflected")
|
|
}
|
|
if tc.status == 200 {
|
|
return
|
|
}
|
|
var body struct {
|
|
Error struct{ Code, Message string }
|
|
}
|
|
if w.Header().Get("Content-Type") != "application/json" || json.Unmarshal(w.Body.Bytes(), &body) != nil || body.Error.Message == "" {
|
|
t.Fatal("invalid JSON error contract")
|
|
}
|
|
wantCode := "service_unavailable"
|
|
if tc.status == 401 {
|
|
wantCode = "unauthorized"
|
|
if w.Header().Get("WWW-Authenticate") != "Bearer" {
|
|
t.Fatal("missing bearer challenge")
|
|
}
|
|
}
|
|
if body.Error.Code != wantCode {
|
|
t.Fatalf("error code=%s", body.Error.Code)
|
|
}
|
|
})
|
|
}
|
|
}
|