diff --git a/api/router.go b/api/router.go index 12d4218..5076caa 100644 --- a/api/router.go +++ b/api/router.go @@ -21,22 +21,37 @@ func RegisterRoutes(router Router, p *proxy.Proxy) { if router == nil || p == nil { return } - registerJSONGetRoute(router, "/1/summary", func() any { return p.SummaryDocument() }) - registerJSONGetRoute(router, "/1/workers", func() any { return p.WorkersDocument() }) - registerJSONGetRoute(router, "/1/miners", func() any { return p.MinersDocument() }) + registerJSONGetRoute(router, p, "/1/summary", func() any { return p.SummaryDocument() }) + registerJSONGetRoute(router, p, "/1/workers", func() any { return p.WorkersDocument() }) + registerJSONGetRoute(router, p, "/1/miners", func() any { return p.MinersDocument() }) } -func registerJSONGetRoute(router Router, pattern string, renderDocument func() any) { +func registerJSONGetRoute(router Router, authoriser *proxy.Proxy, pattern string, renderDocument func() any) { router.HandleFunc(pattern, func(w http.ResponseWriter, request *http.Request) { - if request.Method != http.MethodGet { - w.Header().Set("Allow", http.MethodGet) - w.WriteHeader(http.StatusMethodNotAllowed) + if status, ok := allowMonitoringRequest(authoriser, request); !ok { + switch status { + case http.StatusMethodNotAllowed: + w.Header().Set("Allow", http.MethodGet) + case http.StatusUnauthorized: + w.Header().Set("WWW-Authenticate", "Bearer") + } + w.WriteHeader(status) return } writeJSON(w, renderDocument()) }) } +func allowMonitoringRequest(authoriser *proxy.Proxy, request *http.Request) (int, bool) { + if request.Method != http.MethodGet { + return http.StatusMethodNotAllowed, false + } + if authoriser == nil { + return http.StatusServiceUnavailable, false + } + return authoriser.AllowMonitoringRequest(request) +} + func writeJSON(w http.ResponseWriter, payload any) { w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(payload) diff --git a/api/router_test.go b/api/router_test.go index f4cf1b9..006cdb5 100644 --- a/api/router_test.go +++ b/api/router_test.go @@ -102,3 +102,65 @@ func TestRegisterRoutes_GETMiners_Ugly(t *testing.T) { t.Fatalf("expected no miners in a new proxy, got %d", len(document.Miners)) } } + +func TestRegisterRoutes_GETSummaryAuthRequired_Bad(t *testing.T) { + config := &proxy.Config{ + Mode: "nicehash", + Workers: proxy.WorkersByRigID, + Bind: []proxy.BindAddr{{Host: "127.0.0.1", Port: 3333}}, + Pools: []proxy.PoolConfig{{URL: "pool.example:3333", Enabled: true}}, + HTTP: proxy.HTTPConfig{ + Enabled: true, + Restricted: true, + AccessToken: "secret", + }, + } + p, result := proxy.New(config) + if !result.OK { + t.Fatalf("new proxy: %v", result.Error) + } + + router := http.NewServeMux() + RegisterRoutes(router, p) + + request := httptest.NewRequest(http.MethodGet, "/1/summary", nil) + recorder := httptest.NewRecorder() + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusUnauthorized { + t.Fatalf("expected %d, got %d", http.StatusUnauthorized, recorder.Code) + } + if got := recorder.Header().Get("WWW-Authenticate"); got != "Bearer" { + t.Fatalf("expected bearer challenge, got %q", got) + } +} + +func TestRegisterRoutes_GETSummaryAuthGranted_Ugly(t *testing.T) { + config := &proxy.Config{ + Mode: "nicehash", + Workers: proxy.WorkersByRigID, + Bind: []proxy.BindAddr{{Host: "127.0.0.1", Port: 3333}}, + Pools: []proxy.PoolConfig{{URL: "pool.example:3333", Enabled: true}}, + HTTP: proxy.HTTPConfig{ + Enabled: true, + Restricted: true, + AccessToken: "secret", + }, + } + p, result := proxy.New(config) + if !result.OK { + t.Fatalf("new proxy: %v", result.Error) + } + + router := http.NewServeMux() + RegisterRoutes(router, p) + + request := httptest.NewRequest(http.MethodGet, "/1/summary", nil) + request.Header.Set("Authorization", "Bearer secret") + recorder := httptest.NewRecorder() + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("expected %d, got %d", http.StatusOK, recorder.Code) + } +} diff --git a/state_impl.go b/state_impl.go index ed6ea92..63bf4a5 100644 --- a/state_impl.go +++ b/state_impl.go @@ -669,6 +669,13 @@ func (p *Proxy) allowMonitoringRequest(r *http.Request) (int, bool) { return http.StatusOK, true } +// AllowMonitoringRequest applies the configured monitoring API access checks. +// +// status, ok := p.AllowMonitoringRequest(request) +func (p *Proxy) AllowMonitoringRequest(r *http.Request) (int, bool) { + return p.allowMonitoringRequest(r) +} + func (p *Proxy) writeJSONResponse(w http.ResponseWriter, payload any) { w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(payload)