feat: add cors support

optionally control cross origin requests by origin and
method
This commit is contained in:
Enot (ded) Skelly
2026-06-09 14:54:00 -07:00
parent 432122fee4
commit a5bd8ccaf0
6 changed files with 71 additions and 2 deletions
+1 -1
View File
@@ -267,7 +267,7 @@ func main() {
}()
// ── HTTP server ──────────────────────────────────────────────────────────
r := router.New(h, reader, []*ingest.Worker{broker1, broker2}, maxConnsPerIP)
r := router.New(h, reader, []*ingest.Worker{broker1, broker2}, maxConnsPerIP, cfg.CORS)
srv := &http.Server{
Addr: addr,
+12
View File
@@ -75,6 +75,18 @@ packets:
websocket:
max_connections_per_ip: 5 # default: 5
# CORS configuration.
# Controls which origins, methods and headers are allowed for cross-origin requests.
# Defaults to allowing all origins with GET/HEAD/OPTIONS if omitted — appropriate
# for a public read-only API. Restrict allowed_origins if you expose write endpoints.
#cors:
# allowed_origins:
# - "https://app.example.com"
# allowed_methods: [GET, HEAD, OPTIONS, POST]
# allowed_headers: [Accept, Authorization, Content-Type]
# allow_credentials: false
# max_age: 300
# Redis caching layer.
# If REDIS_ADDR is not set, caching is disabled and all reads go directly to PostgreSQL.
# TTLs are specified as duration strings e.g. "30m", "1h", "2h".
+1
View File
@@ -19,6 +19,7 @@ require (
filippo.io/edwards25519 v1.2.0 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/go-chi/cors v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/spec v0.20.6 // indirect
+2
View File
@@ -14,6 +14,8 @@ github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2I
github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU=
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+28 -1
View File
@@ -12,10 +12,12 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
"github.com/MeshCore-Beacon/beacon-server/internal/api"
"github.com/MeshCore-Beacon/beacon-server/internal/api/handlers"
mw "github.com/MeshCore-Beacon/beacon-server/internal/api/middleware"
"github.com/MeshCore-Beacon/beacon-server/internal/config"
"github.com/MeshCore-Beacon/beacon-server/internal/hub"
"github.com/MeshCore-Beacon/beacon-server/internal/ingest"
"github.com/MeshCore-Beacon/beacon-server/internal/ws"
@@ -39,9 +41,34 @@ import (
//
// The private group is stubbed and ready for the auth middleware drop-in
// described in Future Features → Admin authentication.
func New(h *hub.Hub, reader api.Reader, workers []*ingest.Worker, maxConnsPerIP int) http.Handler {
func New(h *hub.Hub, reader api.Reader, workers []*ingest.Worker, maxConnsPerIP int, corsCfg config.CORSConfig) http.Handler {
r := chi.NewRouter()
// ── CORS ─────────────────────────────────────────────────────────────────
allowedOrigins := corsCfg.AllowedOrigins
if len(allowedOrigins) == 0 {
allowedOrigins = []string{"*"}
}
allowedMethods := corsCfg.AllowedMethods
if len(allowedMethods) == 0 {
allowedMethods = []string{"GET", "HEAD", "OPTIONS"}
}
allowedHeaders := corsCfg.AllowedHeaders
if len(allowedHeaders) == 0 {
allowedHeaders = []string{"Accept", "Authorization", "Content-Type"}
}
maxAge := corsCfg.MaxAge
if maxAge == 0 {
maxAge = 300
}
r.Use(cors.Handler(cors.Options{
AllowedOrigins: allowedOrigins,
AllowedMethods: allowedMethods,
AllowedHeaders: allowedHeaders,
AllowCredentials: corsCfg.AllowCredentials,
MaxAge: maxAge,
}))
// ── Global middleware ────────────────────────────────────────────────────
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
+27
View File
@@ -23,6 +23,33 @@ type Config struct {
Ingest IngestFilterConfig `yaml:"ingest"`
Scopes []ScopeConfig `yaml:"scopes"`
Cache CacheConfig `yaml:"cache"`
CORS CORSConfig `yaml:"cors"`
}
// CORSConfig controls Cross-Origin Resource Sharing behaviour.
// If omitted, Beacon defaults to allowing all origins, which is appropriate
// for a public read-only API. Operators exposing write endpoints should
// restrict AllowedOrigins to known frontends.
type CORSConfig struct {
// AllowedOrigins is the list of origins permitted to make cross-origin
// requests. Use ["*"] to allow all origins (default if omitted).
AllowedOrigins []string `yaml:"allowed_origins"`
// AllowedMethods is the list of HTTP methods allowed in CORS requests.
// Defaults to [GET, HEAD, OPTIONS] if omitted.
AllowedMethods []string `yaml:"allowed_methods"`
// AllowedHeaders is the list of request headers allowed in CORS requests.
// Defaults to [Accept, Authorization, Content-Type] if omitted.
AllowedHeaders []string `yaml:"allowed_headers"`
// AllowCredentials indicates whether the request can include user
// credentials (cookies, HTTP authentication). Defaults to false.
AllowCredentials bool `yaml:"allow_credentials"`
// MaxAge is the number of seconds the browser may cache a preflight
// response. Defaults to 300 if omitted.
MaxAge int `yaml:"max_age"`
}
// CacheConfig controls Redis caching behaviour.