mirror of
https://github.com/MeshCore-Beacon/beacon-server.git
synced 2026-10-17 09:20:00 +00:00
100 lines
3.5 KiB
Go
100 lines
3.5 KiB
Go
// Copyright 2026 Beacon Contributors
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"mime"
|
|
"net/http"
|
|
|
|
"github.com/MeshCore-Beacon/beacon-server/internal/api"
|
|
mw "github.com/MeshCore-Beacon/beacon-server/internal/api/middleware"
|
|
"github.com/go-chi/chi/v5"
|
|
)
|
|
|
|
// AdminRouter mounts operator endpoints. Its caller must wrap the
|
|
// entire subrouter with BearerAuth, including unknown paths and methods.
|
|
func AdminRouter(runtime *mw.RuntimeConfig, routes map[string]http.Handler) http.Handler {
|
|
r := chi.NewRouter()
|
|
r.Get("/config", getAdminConfig(runtime))
|
|
r.Put("/config", updateAdminConfig(runtime))
|
|
for path, handler := range routes {
|
|
r.Mount(path, handler)
|
|
}
|
|
return r
|
|
}
|
|
|
|
// getAdminConfig godoc
|
|
//
|
|
// @Summary Inspect selected running configuration
|
|
// @Description Returns current CORS options, auth configuration status and configured broker count. Credential fields and other configuration are excluded. Runtime origin updates are lost on restart.
|
|
// @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(runtime *mw.RuntimeConfig) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
respond(w, http.StatusOK, runtime.Snapshot())
|
|
}
|
|
}
|
|
|
|
// updateAdminConfig godoc
|
|
// @Summary Update runtime CORS origins
|
|
// @Description Replaces only cors.allowed_origins immediately. Updates are serialized; concurrent valid updates are applied one at a time. Already-running requests may finish with the previous policy. Nothing is persisted; restart reloads saved configuration.
|
|
// @Tags Admin
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security AdminKey
|
|
// @Param config body api.UpdateAdminConfigRequest true "1-32 ASCII HTTP(S) origins, at most 512 bytes each; one hostname wildcard is supported, or a sole *. Empty/null lists and unsupported fields are rejected. Body at most 16 KiB."
|
|
// @Success 200 {object} api.UpdateAdminConfigResponse
|
|
// @Failure 400,401,413,415,503 {object} map[string]APIError
|
|
// @Router /admin/config [put]
|
|
func updateAdminConfig(runtime *mw.RuntimeConfig) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
media, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
|
if err != nil || media != "application/json" {
|
|
respondError(w, 415, "Content-Type must be application/json")
|
|
return
|
|
}
|
|
r.Body = http.MaxBytesReader(w, r.Body, 16*1024)
|
|
decoder := json.NewDecoder(r.Body)
|
|
decoder.DisallowUnknownFields()
|
|
var input api.UpdateAdminConfigRequest
|
|
err = decoder.Decode(&input)
|
|
if err == nil {
|
|
var extra any
|
|
err = decoder.Decode(&extra)
|
|
if errors.Is(err, io.EOF) {
|
|
err = nil
|
|
} else if err == nil {
|
|
err = errors.New("multiple JSON values")
|
|
}
|
|
}
|
|
if err != nil {
|
|
var sizeError *http.MaxBytesError
|
|
if errors.As(err, &sizeError) {
|
|
respondError(w, 413, "request body exceeds 16 KiB")
|
|
} else {
|
|
respondError(w, 400, "invalid configuration JSON")
|
|
}
|
|
return
|
|
}
|
|
if input.CORS == nil || input.CORS.AllowedOrigins == nil {
|
|
respondError(w, 400, "cors.allowed_origins is required")
|
|
return
|
|
}
|
|
config, err := runtime.UpdateOrigins(*input.CORS.AllowedOrigins)
|
|
if err != nil {
|
|
respondError(w, 400, mw.ErrInvalidOrigins.Error())
|
|
return
|
|
}
|
|
respond(w, 200, api.UpdateAdminConfigResponse{Config: config, Persisted: false, RequiresRestart: false})
|
|
}
|
|
}
|