diff --git a/pkg/service/handlers/auth_middleware.go b/pkg/service/handlers/auth_middleware.go new file mode 100644 index 0000000..36b7908 --- /dev/null +++ b/pkg/service/handlers/auth_middleware.go @@ -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) + }) + } +} diff --git a/pkg/service/handlers/auth_middleware_test.go b/pkg/service/handlers/auth_middleware_test.go new file mode 100644 index 0000000..ce1ed8e --- /dev/null +++ b/pkg/service/handlers/auth_middleware_test.go @@ -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") + } +}