feat: add Basic Auth middleware for management API

This commit is contained in:
Tim Van Wassenhove
2026-02-21 00:21:52 +01:00
committed by Tobias Gesellchen
parent a87783d8c6
commit be7e44e14b
2 changed files with 125 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
package handlers
import (
"crypto/subtle"
"net/http"
)
// BasicAuthMiddleware returns a chi middleware that enforces HTTP Basic Authentication.
func BasicAuthMiddleware(username, password string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
u, p, ok := r.BasicAuth()
if !ok ||
subtle.ConstantTimeCompare([]byte(u), []byte(username)) != 1 ||
subtle.ConstantTimeCompare([]byte(p), []byte(password)) != 1 {
w.Header().Set("WWW-Authenticate", `Basic realm="Management API"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
}
@@ -0,0 +1,102 @@
package handlers
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestBasicAuthMiddleware_ValidCredentials(t *testing.T) {
handler := BasicAuthMiddleware("admin", "secret123")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("OK"))
}))
req := httptest.NewRequest(http.MethodGet, "/mgmt/test", nil)
req.SetBasicAuth("admin", "secret123")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Errorf("expected status %d, got %d", http.StatusOK, rr.Code)
}
if rr.Body.String() != "OK" {
t.Errorf("expected body 'OK', got %q", rr.Body.String())
}
}
func TestBasicAuthMiddleware_WrongUsername(t *testing.T) {
handler := BasicAuthMiddleware("admin", "secret123")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Error("handler should not be called with wrong username")
}))
req := httptest.NewRequest(http.MethodGet, "/mgmt/test", nil)
req.SetBasicAuth("wrong", "secret123")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusUnauthorized {
t.Errorf("expected status %d, got %d", http.StatusUnauthorized, rr.Code)
}
if rr.Header().Get("WWW-Authenticate") == "" {
t.Error("expected WWW-Authenticate header to be set")
}
}
func TestBasicAuthMiddleware_WrongPassword(t *testing.T) {
handler := BasicAuthMiddleware("admin", "secret123")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Error("handler should not be called with wrong password")
}))
req := httptest.NewRequest(http.MethodGet, "/mgmt/test", nil)
req.SetBasicAuth("admin", "wrongpass")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusUnauthorized {
t.Errorf("expected status %d, got %d", http.StatusUnauthorized, rr.Code)
}
if rr.Header().Get("WWW-Authenticate") == "" {
t.Error("expected WWW-Authenticate header to be set")
}
}
func TestBasicAuthMiddleware_MissingAuthHeader(t *testing.T) {
handler := BasicAuthMiddleware("admin", "secret123")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Error("handler should not be called without auth header")
}))
req := httptest.NewRequest(http.MethodGet, "/mgmt/test", nil)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusUnauthorized {
t.Errorf("expected status %d, got %d", http.StatusUnauthorized, rr.Code)
}
if rr.Header().Get("WWW-Authenticate") == "" {
t.Error("expected WWW-Authenticate header to be set")
}
}
func TestBasicAuthMiddleware_EmptyCredentials(t *testing.T) {
handler := BasicAuthMiddleware("admin", "secret123")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Error("handler should not be called with empty credentials")
}))
req := httptest.NewRequest(http.MethodGet, "/mgmt/test", nil)
req.SetBasicAuth("", "")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusUnauthorized {
t.Errorf("expected status %d, got %d", http.StatusUnauthorized, rr.Code)
}
if rr.Header().Get("WWW-Authenticate") == "" {
t.Error("expected WWW-Authenticate header to be set")
}
}