fix: cors

This commit is contained in:
Julian Sommer
2026-03-11 11:07:44 +10:00
parent 0ec13f33eb
commit 2cac5e2c37
2 changed files with 85 additions and 2 deletions
+48 -2
View File
@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"log/slog"
"net"
"net/http"
"net/url"
"os"
@@ -106,10 +107,55 @@ func (gw *GatewayConfig) SetupRoutes() http.Handler {
return r
}
// corsMiddleware adds CORS headers for browser requests from the dev UI.
// corsAllowedOrigins builds the set of allowed CORS origins from localhost and
// all local non-loopback IPs on the hive-ui port (default 3000). This allows
// the UI to be accessed from any local address, not just localhost.
func corsAllowedOrigins() map[string]struct{} {
origins := map[string]struct{}{
"https://localhost:3000": {},
}
ifaces, err := net.Interfaces()
if err != nil {
return origins
}
for _, iface := range ifaces {
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
continue
}
addrs, err := iface.Addrs()
if err != nil {
continue
}
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip == nil || ip.IsLoopback() || ip.To4() == nil {
continue
}
origins[fmt.Sprintf("https://%s:3000", ip.String())] = struct{}{}
}
}
return origins
}
// corsMiddleware adds CORS headers for browser requests from the hive-ui.
func corsMiddleware(next http.Handler) http.Handler {
allowed := corsAllowedOrigins()
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "https://localhost:3000")
origin := r.Header.Get("Origin")
if origin == "" {
origin = "https://localhost:3000"
}
if _, ok := allowed[origin]; ok {
w.Header().Set("Access-Control-Allow-Origin", origin)
} else {
w.Header().Set("Access-Control-Allow-Origin", "https://localhost:3000")
}
w.Header().Set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,HEAD,OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "*")
w.Header().Set("Access-Control-Allow-Credentials", "true")
+37
View File
@@ -860,6 +860,43 @@ func TestCorsMiddleware_OPTIONSRequest(t *testing.T) {
assert.Equal(t, "https://localhost:3000", w.Header().Get("Access-Control-Allow-Origin"))
}
func TestCorsMiddleware_LocalIPOrigin(t *testing.T) {
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
})
handler := corsMiddleware(next)
// Request from a local IP should be reflected
allowed := corsAllowedOrigins()
for origin := range allowed {
if origin == "https://localhost:3000" {
continue
}
req := httptest.NewRequest("GET", "/", nil)
req.Header.Set("Origin", origin)
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
assert.Equal(t, origin, w.Header().Get("Access-Control-Allow-Origin"))
return // test one IP is enough
}
}
func TestCorsMiddleware_UnknownOrigin(t *testing.T) {
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
})
handler := corsMiddleware(next)
req := httptest.NewRequest("GET", "/", nil)
req.Header.Set("Origin", "https://evil.example.com")
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
// Unknown origins fall back to localhost
assert.Equal(t, "https://localhost:3000", w.Header().Get("Access-Control-Allow-Origin"))
}
func TestSlogRequestLogger_CallsNext(t *testing.T) {
nextCalled := false
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {