CORS returns a middleware that sets CORS headers and enforces origin restrictions. When dynamicResolver is non-nil and returns (origin, true), that origin is used for the request (e.g. per-host: scheme + "://" + X-Forwarded-Host). When it returns ("", false), the static origin string is used (singl
(origin string, dynamicResolver OriginResolver)
| 25 | // Internal requests (health checks, agent connections) that bypass nginx |
| 26 | // won't have X-Forwarded-Host and are not affected. |
| 27 | func CORS(origin string, dynamicResolver OriginResolver) func(http.Handler) http.Handler { |
| 28 | origins := parseOrigins(origin) |
| 29 | allowedHosts := buildAllowedHosts(origins) |
| 30 | wildcard := hasWildcard(origins) |
| 31 | |
| 32 | return func(next http.Handler) http.Handler { |
| 33 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 34 | var effectiveOrigins []string |
| 35 | var effectiveAllowedHosts map[string]bool |
| 36 | effectiveWildcard := wildcard |
| 37 | |
| 38 | if dynamicResolver != nil { |
| 39 | if dynOrigin, ok := dynamicResolver(r); ok && dynOrigin != "" { |
| 40 | effectiveOrigins = []string{dynOrigin} |
| 41 | effectiveAllowedHosts = buildAllowedHosts(effectiveOrigins) |
| 42 | effectiveWildcard = false |
| 43 | } |
| 44 | } |
| 45 | if len(effectiveOrigins) == 0 { |
| 46 | effectiveOrigins = origins |
| 47 | effectiveAllowedHosts = allowedHosts |
| 48 | } |
| 49 | |
| 50 | reqOrigin := r.Header.Get("Origin") |
| 51 | |
| 52 | allowOrigin := "" |
| 53 | for _, o := range effectiveOrigins { |
| 54 | if o == "*" { |
| 55 | allowOrigin = "*" |
| 56 | break |
| 57 | } |
| 58 | if o == reqOrigin { |
| 59 | allowOrigin = reqOrigin |
| 60 | break |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | // Enforce allowed hosts via X-Forwarded-Host on all requests when present. |
| 65 | // Blocks access from disallowed URLs before the login page loads. |
| 66 | // Internal requests (health checks, agents) have no X-Forwarded-Host and pass through. |
| 67 | if !effectiveWildcard { |
| 68 | if fwdHost := r.Header.Get("X-Forwarded-Host"); fwdHost != "" { |
| 69 | if !effectiveAllowedHosts[strings.ToLower(fwdHost)] { |
| 70 | // Set CORS headers so the browser allows the client to read this 403. |
| 71 | reflectOrigin := reqOrigin |
| 72 | if reflectOrigin == "" { |
| 73 | reflectOrigin = EffectiveOrigin(r, fwdHost) |
| 74 | } |
| 75 | if reflectOrigin != "" { |
| 76 | w.Header().Set("Access-Control-Allow-Origin", reflectOrigin) |
| 77 | } |
| 78 | w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") |
| 79 | w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-API-ID, X-API-KEY, X-Device-ID") |
| 80 | w.Header().Set("Access-Control-Allow-Credentials", "true") |
| 81 | w.Header().Set("Content-Type", "application/json") |
| 82 | w.WriteHeader(http.StatusForbidden) |
| 83 | _, _ = w.Write([]byte(`{"error":"CORS_ORIGIN mismatch. Access this app via the URL configured in CORS_ORIGIN (.env or Database settings).","code":"cors_mismatch"}`)) |
| 84 | return |
nothing calls this directly
no test coverage detected