Files
beacon-server/internal/api/middleware/auth_test.go
T

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)
}
})
}
}