mirror of
https://github.com/MeshCore-Beacon/beacon-server.git
synced 2026-10-22 22:21:27 +00:00
feat(admin): expose selected startup configuration (#147)
This commit is contained in:
1 parent
dab228e9bb
commit
6540b9253d
10 files changed
+487
-4
No files matched your search
@@ -127,7 +127,15 @@ With no key, admin requests return JSON 503 while public reads and WebSockets
|
||||
continue normally. With a key, missing, incorrect or duplicate Authorization
|
||||
headers return JSON 401 with `WWW-Authenticate: Bearer`.
|
||||
|
||||
Admin operations are not implemented yet: a valid key currently reaches a 404.
|
||||
`GET /api/v1/admin/config` returns selected startup settings: CORS options with
|
||||
Beacon defaults applied, `auth.configured`, and `ingest.broker_count` (configured
|
||||
broker workers, not connection status or a tunable processing-worker pool).
|
||||
The CORS lists are the options supplied to the middleware; its normal matching
|
||||
normalization still applies. The response is a startup snapshot and excludes
|
||||
credential fields, broker addresses, channel material, database settings and
|
||||
other configuration. Changes require a restart. Configuration writes and account
|
||||
operations are not implemented; unknown admin paths return 404 and unsupported
|
||||
methods on the config endpoint return 405 after authentication.
|
||||
Global CORS preflights remain public. Use a long, randomly generated key, keep
|
||||
it out of source control and logs, and send it only in the Authorization header,
|
||||
never the URL or request body. Require HTTPS at the reverse proxy and restrict
|
||||
|
||||
@@ -55,6 +55,11 @@ var version = "dev"
|
||||
|
||||
// @schemes http https
|
||||
|
||||
// @securityDefinitions.apikey AdminKey
|
||||
// @in header
|
||||
// @name Authorization
|
||||
// @description Enter Bearer followed by the configured operator key. Use HTTPS.
|
||||
|
||||
// @tag.name IATAs
|
||||
// @tag.description Airport/location codes that group observers and packets
|
||||
// @tag.name Regions
|
||||
|
||||
+111
@@ -22,6 +22,49 @@ const docTemplate = `{
|
||||
"host": "{{.Host}}",
|
||||
"basePath": "{{.BasePath}}",
|
||||
"paths": {
|
||||
"/admin/config": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"AdminKey": []
|
||||
}
|
||||
],
|
||||
"description": "Returns CORS startup options with Beacon defaults, auth configuration status and configured broker count. Credential fields and other configuration are excluded. Changes require restart; this endpoint is read-only.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Inspect selected startup configuration",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.AdminConfig"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/internal_api_handlers.APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"503": {
|
||||
"description": "Service Unavailable",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/internal_api_handlers.APIError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/brokers": {
|
||||
"get": {
|
||||
"produces": [
|
||||
@@ -2423,6 +2466,66 @@ const docTemplate = `{
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"github_com_MeshCore-Beacon_beacon-server_internal_api.AdminAuthConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"configured": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"github_com_MeshCore-Beacon_beacon-server_internal_api.AdminCORSConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allow_credentials": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"allowed_headers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"allowed_methods": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"allowed_origins": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"max_age": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"github_com_MeshCore-Beacon_beacon-server_internal_api.AdminConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"auth": {
|
||||
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.AdminAuthConfig"
|
||||
},
|
||||
"cors": {
|
||||
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.AdminCORSConfig"
|
||||
},
|
||||
"ingest": {
|
||||
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.AdminIngestConfig"
|
||||
}
|
||||
}
|
||||
},
|
||||
"github_com_MeshCore-Beacon_beacon-server_internal_api.AdminIngestConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"broker_count": {
|
||||
"description": "BrokerCount counts configured broker workers, not active MQTT connections\nor a configurable processing-worker pool.",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"github_com_MeshCore-Beacon_beacon-server_internal_api.AdvertObservation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -4102,6 +4205,14 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"securityDefinitions": {
|
||||
"AdminKey": {
|
||||
"description": "Enter Bearer followed by the configured operator key. Use HTTPS.",
|
||||
"type": "apiKey",
|
||||
"name": "Authorization",
|
||||
"in": "header"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"description": "Airport/location codes that group observers and packets",
|
||||
|
||||
@@ -20,6 +20,49 @@
|
||||
"host": "localhost:8080",
|
||||
"basePath": "/api/v1",
|
||||
"paths": {
|
||||
"/admin/config": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"AdminKey": []
|
||||
}
|
||||
],
|
||||
"description": "Returns CORS startup options with Beacon defaults, auth configuration status and configured broker count. Credential fields and other configuration are excluded. Changes require restart; this endpoint is read-only.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Inspect selected startup configuration",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.AdminConfig"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/internal_api_handlers.APIError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"503": {
|
||||
"description": "Service Unavailable",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/internal_api_handlers.APIError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/brokers": {
|
||||
"get": {
|
||||
"produces": [
|
||||
@@ -2421,6 +2464,66 @@
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"github_com_MeshCore-Beacon_beacon-server_internal_api.AdminAuthConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"configured": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"github_com_MeshCore-Beacon_beacon-server_internal_api.AdminCORSConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allow_credentials": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"allowed_headers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"allowed_methods": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"allowed_origins": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"max_age": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"github_com_MeshCore-Beacon_beacon-server_internal_api.AdminConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"auth": {
|
||||
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.AdminAuthConfig"
|
||||
},
|
||||
"cors": {
|
||||
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.AdminCORSConfig"
|
||||
},
|
||||
"ingest": {
|
||||
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.AdminIngestConfig"
|
||||
}
|
||||
}
|
||||
},
|
||||
"github_com_MeshCore-Beacon_beacon-server_internal_api.AdminIngestConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"broker_count": {
|
||||
"description": "BrokerCount counts configured broker workers, not active MQTT connections\nor a configurable processing-worker pool.",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"github_com_MeshCore-Beacon_beacon-server_internal_api.AdvertObservation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -4100,6 +4203,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"securityDefinitions": {
|
||||
"AdminKey": {
|
||||
"description": "Enter Bearer followed by the configured operator key. Use HTTPS.",
|
||||
"type": "apiKey",
|
||||
"name": "Authorization",
|
||||
"in": "header"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"description": "Airport/location codes that group observers and packets",
|
||||
|
||||
@@ -1,5 +1,46 @@
|
||||
basePath: /api/v1
|
||||
definitions:
|
||||
github_com_MeshCore-Beacon_beacon-server_internal_api.AdminAuthConfig:
|
||||
properties:
|
||||
configured:
|
||||
type: boolean
|
||||
type: object
|
||||
github_com_MeshCore-Beacon_beacon-server_internal_api.AdminCORSConfig:
|
||||
properties:
|
||||
allow_credentials:
|
||||
type: boolean
|
||||
allowed_headers:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
allowed_methods:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
allowed_origins:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
max_age:
|
||||
type: integer
|
||||
type: object
|
||||
github_com_MeshCore-Beacon_beacon-server_internal_api.AdminConfig:
|
||||
properties:
|
||||
auth:
|
||||
$ref: '#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.AdminAuthConfig'
|
||||
cors:
|
||||
$ref: '#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.AdminCORSConfig'
|
||||
ingest:
|
||||
$ref: '#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.AdminIngestConfig'
|
||||
type: object
|
||||
github_com_MeshCore-Beacon_beacon-server_internal_api.AdminIngestConfig:
|
||||
properties:
|
||||
broker_count:
|
||||
description: |-
|
||||
BrokerCount counts configured broker workers, not active MQTT connections
|
||||
or a configurable processing-worker pool.
|
||||
type: integer
|
||||
type: object
|
||||
github_com_MeshCore-Beacon_beacon-server_internal_api.AdvertObservation:
|
||||
properties:
|
||||
heardAt:
|
||||
@@ -1191,6 +1232,35 @@ info:
|
||||
title: MeshCore Beacon API
|
||||
version: 1.6.0
|
||||
paths:
|
||||
/admin/config:
|
||||
get:
|
||||
description: Returns CORS startup options with Beacon defaults, auth configuration
|
||||
status and configured broker count. Credential fields and other configuration
|
||||
are excluded. Changes require restart; this endpoint is read-only.
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.AdminConfig'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
additionalProperties:
|
||||
$ref: '#/definitions/internal_api_handlers.APIError'
|
||||
type: object
|
||||
"503":
|
||||
description: Service Unavailable
|
||||
schema:
|
||||
additionalProperties:
|
||||
$ref: '#/definitions/internal_api_handlers.APIError'
|
||||
type: object
|
||||
security:
|
||||
- AdminKey: []
|
||||
summary: Inspect selected startup configuration
|
||||
tags:
|
||||
- Admin
|
||||
/brokers:
|
||||
get:
|
||||
produces:
|
||||
@@ -2808,6 +2878,12 @@ paths:
|
||||
schemes:
|
||||
- http
|
||||
- https
|
||||
securityDefinitions:
|
||||
AdminKey:
|
||||
description: Enter Bearer followed by the configured operator key. Use HTTPS.
|
||||
in: header
|
||||
name: Authorization
|
||||
type: apiKey
|
||||
swagger: "2.0"
|
||||
tags:
|
||||
- description: Airport/location codes that group observers and packets
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2026 Beacon Contributors
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
package api
|
||||
|
||||
// AdminConfig is a whitelist of nonsecret settings captured when the router starts.
|
||||
// It is not a serialization of the full file or environment configuration.
|
||||
type AdminConfig struct {
|
||||
Auth AdminAuthConfig `json:"auth"`
|
||||
CORS AdminCORSConfig `json:"cors"`
|
||||
Ingest AdminIngestConfig `json:"ingest"`
|
||||
}
|
||||
|
||||
type AdminAuthConfig struct {
|
||||
Configured bool `json:"configured"`
|
||||
}
|
||||
|
||||
// AdminCORSConfig reports the startup options supplied to the CORS middleware,
|
||||
// with Beacon defaults applied, before that library's matching normalization.
|
||||
type AdminCORSConfig struct {
|
||||
AllowedOrigins []string `json:"allowed_origins"`
|
||||
AllowedMethods []string `json:"allowed_methods"`
|
||||
AllowedHeaders []string `json:"allowed_headers"`
|
||||
AllowCredentials bool `json:"allow_credentials"`
|
||||
MaxAge int `json:"max_age"`
|
||||
}
|
||||
|
||||
type AdminIngestConfig struct {
|
||||
// BrokerCount counts configured broker workers, not active MQTT connections
|
||||
// or a configurable processing-worker pool.
|
||||
BrokerCount int `json:"broker_count"`
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2026 Beacon Contributors
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/MeshCore-Beacon/beacon-server/internal/api"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
// AdminRouter mounts read-only operator endpoints. Its caller must wrap the
|
||||
// entire subrouter with BearerAuth, including unknown paths and methods.
|
||||
func AdminRouter(snapshot api.AdminConfig) http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/config", getAdminConfig(snapshot))
|
||||
return r
|
||||
}
|
||||
|
||||
// getAdminConfig godoc
|
||||
//
|
||||
// @Summary Inspect selected startup configuration
|
||||
// @Description Returns CORS startup options with Beacon defaults, auth configuration status and configured broker count. Credential fields and other configuration are excluded. Changes require restart; this endpoint is read-only.
|
||||
// @Tags Admin
|
||||
// @Produce json
|
||||
// @Security AdminKey
|
||||
// @Success 200 {object} api.AdminConfig
|
||||
// @Failure 401 {object} map[string]APIError
|
||||
// @Failure 503 {object} map[string]APIError
|
||||
// @Router /admin/config [get]
|
||||
func getAdminConfig(snapshot api.AdminConfig) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
respond(w, http.StatusOK, snapshot)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2026 Beacon Contributors
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
package router
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/MeshCore-Beacon/beacon-server/internal/config"
|
||||
"github.com/MeshCore-Beacon/beacon-server/internal/ingest"
|
||||
)
|
||||
|
||||
func TestAdminConfig(t *testing.T) {
|
||||
const key = "admin-key-sentinel"
|
||||
for _, variable := range []string{"POSTGRES_DSN", "MQTT_BROKER_1_URL", "MQTT_BROKER_1_USERNAME", "MQTT_BROKER_1_PASSWORD", "REDIS_PASSWORD"} {
|
||||
t.Setenv(variable, "environment-secret-sentinel")
|
||||
}
|
||||
for _, custom := range []bool{false, true} {
|
||||
cfg := config.CORSConfig{}
|
||||
want := map[string]any{
|
||||
"allowed_origins": []any{"*"}, "allowed_methods": []any{"GET", "HEAD", "OPTIONS"},
|
||||
"allowed_headers": []any{"Accept", "Authorization", "Content-Type"}, "allow_credentials": false, "max_age": float64(300),
|
||||
}
|
||||
if custom {
|
||||
cfg = config.CORSConfig{AllowedOrigins: []string{"https://example.test"}, AllowedMethods: []string{"GET"},
|
||||
AllowedHeaders: []string{"Authorization", "X-Operator"}, AllowCredentials: true, MaxAge: 600}
|
||||
want = map[string]any{"allowed_origins": []any{"https://example.test"}, "allowed_methods": []any{"GET"},
|
||||
"allowed_headers": []any{"Authorization", "X-Operator"}, "allow_credentials": true, "max_age": float64(600)}
|
||||
}
|
||||
handler := New(nil, nil, []*ingest.Worker{{}, {}}, 5, cfg, config.ServerConfig{}, config.AuthConfig{APIKey: key}, config.ResolvedRateLimitConfig{})
|
||||
if custom {
|
||||
cfg.AllowedOrigins[0] = "https://changed.test"
|
||||
cfg.AllowedMethods[0] = "DELETE"
|
||||
cfg.AllowedHeaders[0] = "X-Changed"
|
||||
}
|
||||
request := func(method, path string, headers map[string]string) *httptest.ResponseRecorder {
|
||||
req := httptest.NewRequest(method, path, nil)
|
||||
for name, value := range headers {
|
||||
req.Header.Set(name, value)
|
||||
}
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, req)
|
||||
return response
|
||||
}
|
||||
headers := map[string]string{"Authorization": "Bearer " + key}
|
||||
response := request("GET", "/api/v1/admin/config", headers)
|
||||
if response.Code != 200 || response.Header().Get("Content-Type") != "application/json" || response.Header().Get("Cache-Control") != "no-store" {
|
||||
t.Fatalf("config read status/headers: %d %v", response.Code, response.Header())
|
||||
}
|
||||
var body map[string]any
|
||||
if err := json.Unmarshal(response.Body.Bytes(), &body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := map[string]any{"cors": want, "auth": map[string]any{"configured": true}, "ingest": map[string]any{"broker_count": float64(2)}}
|
||||
if !reflect.DeepEqual(body, expected) {
|
||||
t.Fatalf("unexpected config shape or values: %v", body)
|
||||
}
|
||||
if strings.Contains(response.Body.String(), "sentinel") {
|
||||
t.Fatal("credential setting was exposed")
|
||||
}
|
||||
for _, auth := range []string{"", "Bearer wrong"} {
|
||||
denied := request("GET", "/api/v1/admin/config", map[string]string{"Authorization": auth})
|
||||
if denied.Code != 401 || strings.Contains(denied.Body.String(), "allowed_origins") {
|
||||
t.Fatal("config escaped authentication")
|
||||
}
|
||||
}
|
||||
if request("PUT", "/api/v1/admin/config", headers).Code != http.StatusMethodNotAllowed {
|
||||
t.Fatal("config writes were accepted")
|
||||
}
|
||||
again := request("GET", "/api/v1/admin/config", headers)
|
||||
if again.Body.String() != response.Body.String() {
|
||||
t.Fatal("startup snapshot changed")
|
||||
}
|
||||
preflight := request("OPTIONS", "/api/v1/admin/config", map[string]string{
|
||||
"Origin": "https://example.test", "Access-Control-Request-Method": "GET", "Access-Control-Request-Headers": "Authorization",
|
||||
})
|
||||
if preflight.Code != 200 || preflight.Header().Get("Access-Control-Allow-Origin") == "" || preflight.Header().Get("Access-Control-Max-Age") != map[bool]string{false: "300", true: "600"}[custom] {
|
||||
t.Fatal("CORS behavior does not match reported options")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,12 @@ func TestAdminAuthBoundary(t *testing.T) {
|
||||
want = 401
|
||||
if token == key {
|
||||
want = 404
|
||||
if path == "/api/v1/admin/config" || path == "/api/v1/admin//config" {
|
||||
want = 405
|
||||
if method == "GET" {
|
||||
want = 200
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if w.Code != want {
|
||||
|
||||
@@ -38,7 +38,7 @@ import (
|
||||
// /regions → regions subrouter
|
||||
// /stats → stats subrouter
|
||||
//
|
||||
// Admin handlers are added separately; the reserved subtree is protected now.
|
||||
// Admin endpoints require a configured bearer key.
|
||||
func New(h *hub.Hub, reader api.Reader, workers []*ingest.Worker, maxConnsPerIP int, corsCfg config.CORSConfig, serverCfg config.ServerConfig, authCfg config.AuthConfig, rateLimitCfg config.ResolvedRateLimitConfig) http.Handler {
|
||||
r := chi.NewRouter()
|
||||
|
||||
@@ -67,6 +67,19 @@ func New(h *hub.Hub, reader api.Reader, workers []*ingest.Worker, maxConnsPerIP
|
||||
AllowCredentials: corsCfg.AllowCredentials,
|
||||
MaxAge: maxAge,
|
||||
}))
|
||||
// Capture only values used by this router. Do not retain Config, credentials
|
||||
// or caller-owned slices in the admin response.
|
||||
adminConfig := api.AdminConfig{
|
||||
Auth: api.AdminAuthConfig{Configured: authCfg.APIKey != ""},
|
||||
CORS: api.AdminCORSConfig{
|
||||
AllowedOrigins: append([]string{}, allowedOrigins...),
|
||||
AllowedMethods: append([]string{}, allowedMethods...),
|
||||
AllowedHeaders: append([]string{}, allowedHeaders...),
|
||||
AllowCredentials: corsCfg.AllowCredentials,
|
||||
MaxAge: maxAge,
|
||||
},
|
||||
Ingest: api.AdminIngestConfig{BrokerCount: len(workers)},
|
||||
}
|
||||
|
||||
// ── Global middleware ────────────────────────────────────────────────────
|
||||
r.Use(middleware.RequestID)
|
||||
@@ -108,8 +121,7 @@ func New(h *hub.Hub, reader api.Reader, workers []*ingest.Worker, maxConnsPerIP
|
||||
})
|
||||
|
||||
// Protect the entire subtree, including its root and unknown paths.
|
||||
// Replace the empty router with the admin handlers when they are added.
|
||||
r.Mount("/admin", mw.BearerAuth(authCfg.APIKey, chi.NewRouter()))
|
||||
r.Mount("/admin", mw.BearerAuth(authCfg.APIKey, handlers.AdminRouter(adminConfig)))
|
||||
})
|
||||
|
||||
return r
|
||||
|
||||
Reference in new issue
Block a user