feat: route API requests by tenant hostname

Derive local API origins and tenant headers from the browser hostname, allow tenant CORS preflights, and document the multi-tenant contract.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Daniel Noulet 2026-10-01 17:49:42 +02:00
parent 5dbda53759
commit 98f47df8c1
7 changed files with 103 additions and 34 deletions

View file

@ -3,6 +3,7 @@ package router
import (
"encoding/json"
"net/http"
"net/url"
"strings"
"time"
@ -95,8 +96,11 @@ func authenticate(tokens security.ITokenManager) func(http.Handler) http.Handler
func cors(origin string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", origin)
w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type")
requestOrigin := r.Header.Get("Origin")
if allowedCORSOrigin(requestOrigin, origin) {
w.Header().Set("Access-Control-Allow-Origin", requestOrigin)
}
w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Tenant-Id")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
w.Header().Add("Vary", "Origin")
if r.Method == http.MethodOptions {
@ -107,3 +111,17 @@ func cors(origin string) func(http.Handler) http.Handler {
})
}
}
func allowedCORSOrigin(requestOrigin, configuredOrigin string) bool {
if requestOrigin == "" {
return false
}
if requestOrigin == configuredOrigin {
return true
}
parsed, err := url.Parse(requestOrigin)
if err != nil || (parsed.Scheme != "http" && parsed.Scheme != "https") {
return false
}
return strings.HasSuffix(strings.ToLower(parsed.Hostname()), ".guess.local")
}