Update to fiber 3
Change-Id: I18a7b3ed55aef5cae67db6a613ec14e0f8a9281e
diff --git a/config/config.go b/config/config.go
index 9347994..e024c76 100644
--- a/config/config.go
+++ b/config/config.go
@@ -109,7 +109,7 @@
ServiceURL string `yaml:"serviceURL,omitempty"`
CookieName string `yaml:"cookieName,omitempty"`
BasePath string `yaml:"basePath,omitempty"` // restricts config file loading to this directory tree
- AllowOrigins string `yaml:"allowOrigins,omitempty"` // comma-separated list of allowed CORS origins
+ AllowOrigins []string `yaml:"allowOrigins,omitempty"`
Port int `yaml:"port,omitempty"`
LogLevel string `yaml:"loglevel,omitempty"`
RateLimit int `yaml:"rateLimit,omitempty"` // max requests per minute per IP (0 = use default 100)
@@ -117,6 +117,28 @@
Lists []MappingList `yaml:"lists,omitempty"`
}
+// UnmarshalYAML rejects the deprecated comma-separated string format for
+// allowOrigins and requires a YAML list instead.
+func (m *MappingConfig) UnmarshalYAML(value *yaml.Node) error {
+ if value.Kind == yaml.MappingNode {
+ for i := 0; i < len(value.Content)-1; i += 2 {
+ if value.Content[i].Value == "allowOrigins" && value.Content[i+1].Kind == yaml.ScalarNode {
+ return fmt.Errorf(
+ "allowOrigins must be a YAML list, not a comma-separated string; update your config:\n" +
+ " allowOrigins:\n" +
+ " - \"https://example.com\"")
+ }
+ }
+ }
+ type plain MappingConfig
+ var p plain
+ if err := value.Decode(&p); err != nil {
+ return err
+ }
+ *m = MappingConfig(p)
+ return nil
+}
+
// AllowedBasePath restricts file loading to a specific directory tree.
// When set, all file paths must resolve to a location at or below this
// directory (or under the system temp directory). Defaults to the CWD at
@@ -206,6 +228,8 @@
seenIDs[list.ID] = true
}
allLists = append(allLists, globalConfig.Lists...)
+ } else if strings.Contains(err.Error(), "allowOrigins must be") {
+ return nil, fmt.Errorf("failed to parse config file '%s': %w", configFile, err)
} else {
// Fall back to old format (direct list)
var lists []MappingList
@@ -309,8 +333,8 @@
// AllowOrigins defaults to the Server value. This avoids duplicating
// the server URL string and keeps CORS in sync with the deployment.
- if config.AllowOrigins == "" {
- config.AllowOrigins = config.Server
+ if len(config.AllowOrigins) == 0 {
+ config.AllowOrigins = []string{config.Server}
}
config.AllowOrigins = normalizeOrigins(config.AllowOrigins)
@@ -322,21 +346,24 @@
}
}
-// normalizeOrigins takes a comma-separated list of origin URLs and strips
-// any path components, returning only scheme + host (+ port when present).
-// The CORS middleware requires bare origins without paths; URLs like
+// normalizeOrigins strips path components from origin URLs, returning only
+// scheme + host (+ port when present). The CORS middleware requires bare
+// origins without paths; URLs like
// "https://example.com/instance/test" are pruned to "https://example.com".
-func normalizeOrigins(raw string) string {
- parts := strings.Split(raw, ",")
- for i, part := range parts {
- part = strings.TrimSpace(part)
- if u, err := url.Parse(part); err == nil && u.Host != "" {
- parts[i] = u.Scheme + "://" + u.Host
+func normalizeOrigins(origins []string) []string {
+ result := make([]string, 0, len(origins))
+ for _, origin := range origins {
+ origin = strings.TrimSpace(origin)
+ if origin == "" {
+ continue
+ }
+ if u, err := url.Parse(origin); err == nil && u.Host != "" {
+ result = append(result, u.Scheme+"://"+u.Host)
} else {
- parts[i] = strings.TrimRight(part, "/")
+ result = append(result, strings.TrimRight(origin, "/"))
}
}
- return strings.Join(parts, ",")
+ return result
}
// ApplyEnvOverrides overrides configuration fields from environment variables.
@@ -344,14 +371,13 @@
// Non-empty environment values override any previously loaded config values.
func ApplyEnvOverrides(config *MappingConfig) {
envMappings := map[string]*string{
- "KORAL_MAPPER_SERVER": &config.Server,
- "KORAL_MAPPER_SDK": &config.SDK,
- "KORAL_MAPPER_STYLESHEET": &config.Stylesheet,
- "KORAL_MAPPER_SERVICE_URL": &config.ServiceURL,
- "KORAL_MAPPER_COOKIE_NAME": &config.CookieName,
- "KORAL_MAPPER_LOG_LEVEL": &config.LogLevel,
- "KORAL_MAPPER_BASE_PATH": &config.BasePath,
- "KORAL_MAPPER_ALLOW_ORIGINS": &config.AllowOrigins,
+ "KORAL_MAPPER_SERVER": &config.Server,
+ "KORAL_MAPPER_SDK": &config.SDK,
+ "KORAL_MAPPER_STYLESHEET": &config.Stylesheet,
+ "KORAL_MAPPER_SERVICE_URL": &config.ServiceURL,
+ "KORAL_MAPPER_COOKIE_NAME": &config.CookieName,
+ "KORAL_MAPPER_LOG_LEVEL": &config.LogLevel,
+ "KORAL_MAPPER_BASE_PATH": &config.BasePath,
}
for envKey, field := range envMappings {
@@ -360,6 +386,10 @@
}
}
+ if val := os.Getenv("KORAL_MAPPER_ALLOW_ORIGINS"); val != "" {
+ config.AllowOrigins = strings.Split(val, ",")
+ }
+
if val := os.Getenv("KORAL_MAPPER_PORT"); val != "" {
if port, err := strconv.Atoi(val); err == nil {
config.Port = port
diff --git a/config/config_test.go b/config/config_test.go
index a7d121f..d8c7789 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -1191,31 +1191,31 @@
tests := []struct {
name string
- listRewrites *bool
+ listRewrites *bool
globalDefault bool
expected bool
}{
{
name: "nil per-list, global false",
- listRewrites: nil,
+ listRewrites: nil,
globalDefault: false,
expected: false,
},
{
name: "nil per-list, global true",
- listRewrites: nil,
+ listRewrites: nil,
globalDefault: true,
expected: true,
},
{
name: "per-list true, global false",
- listRewrites: &trueVal,
+ listRewrites: &trueVal,
globalDefault: false,
expected: true,
},
{
name: "per-list false, global true",
- listRewrites: &falseVal,
+ listRewrites: &falseVal,
globalDefault: true,
expected: false,
},
@@ -1425,7 +1425,7 @@
cfg := &MappingConfig{}
ApplyDefaults(cfg)
// AllowOrigins should derive from the Server default (trailing slash stripped)
- assert.Equal(t, "https://korap.ids-mannheim.de", cfg.AllowOrigins,
+ assert.Equal(t, []string{"https://korap.ids-mannheim.de"}, cfg.AllowOrigins,
"default AllowOrigins should derive from defaultServer")
}
@@ -1434,23 +1434,25 @@
Server: "https://custom.example.com/",
}
ApplyDefaults(cfg)
- assert.Equal(t, "https://custom.example.com", cfg.AllowOrigins,
+ assert.Equal(t, []string{"https://custom.example.com"}, cfg.AllowOrigins,
"AllowOrigins should derive from the configured Server (trailing slash stripped)")
}
func TestAllowOriginsExplicitNotOverriddenByServer(t *testing.T) {
cfg := &MappingConfig{
Server: "https://custom.example.com/",
- AllowOrigins: "https://explicit-origin.example.com",
+ AllowOrigins: []string{"https://explicit-origin.example.com"},
}
ApplyDefaults(cfg)
- assert.Equal(t, "https://explicit-origin.example.com", cfg.AllowOrigins,
+ assert.Equal(t, []string{"https://explicit-origin.example.com"}, cfg.AllowOrigins,
"explicit AllowOrigins should not be overridden by Server default")
}
func TestAllowOriginsFromYAML(t *testing.T) {
content := `
-allowOrigins: "https://custom.example.com,https://other.example.com"
+allowOrigins:
+ - "https://custom.example.com"
+ - "https://other.example.com"
lists:
- id: test-mapper
mappings:
@@ -1466,15 +1468,37 @@
cfg, err := LoadFromSources(tmpfile.Name(), nil)
require.NoError(t, err)
- assert.Equal(t, "https://custom.example.com,https://other.example.com",
+ assert.Equal(t, []string{"https://custom.example.com", "https://other.example.com"},
cfg.AllowOrigins)
}
+func TestAllowOriginsStringFormatRejected(t *testing.T) {
+ content := `
+allowOrigins: "https://custom.example.com,https://other.example.com"
+lists:
+ - id: test-mapper
+ mappings:
+ - "[A] <> [B]"
+`
+ tmpfile, err := os.CreateTemp("", "config-cors-reject-*.yaml")
+ require.NoError(t, err)
+ defer os.Remove(tmpfile.Name())
+
+ _, err = tmpfile.WriteString(content)
+ require.NoError(t, err)
+ require.NoError(t, tmpfile.Close())
+
+ _, err = LoadFromSources(tmpfile.Name(), nil)
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "allowOrigins must be a YAML list")
+}
+
func TestAllowOriginsEnvOverride(t *testing.T) {
t.Setenv("KORAL_MAPPER_ALLOW_ORIGINS", "https://env-origin.example.com")
content := `
-allowOrigins: "https://yaml-origin.example.com"
+allowOrigins:
+ - "https://yaml-origin.example.com"
lists:
- id: test-mapper
mappings:
@@ -1490,7 +1514,7 @@
cfg, err := LoadFromSources(tmpfile.Name(), nil)
require.NoError(t, err)
- assert.Equal(t, "https://env-origin.example.com", cfg.AllowOrigins,
+ assert.Equal(t, []string{"https://env-origin.example.com"}, cfg.AllowOrigins,
"KORAL_MAPPER_ALLOW_ORIGINS env var should override YAML value")
}
@@ -1499,16 +1523,16 @@
Server: "https://korap.ids-mannheim.de/instance/test",
}
ApplyDefaults(cfg)
- assert.Equal(t, "https://korap.ids-mannheim.de", cfg.AllowOrigins,
+ assert.Equal(t, []string{"https://korap.ids-mannheim.de"}, cfg.AllowOrigins,
"AllowOrigins should be pruned to host-level origin when Server contains a path")
}
func TestAllowOriginsExplicitWithPathsPruned(t *testing.T) {
cfg := &MappingConfig{
- AllowOrigins: "https://korap.ids-mannheim.de/instance/test,https://other.example.com/app",
+ AllowOrigins: []string{"https://korap.ids-mannheim.de/instance/test", "https://other.example.com/app"},
}
ApplyDefaults(cfg)
- assert.Equal(t, "https://korap.ids-mannheim.de,https://other.example.com", cfg.AllowOrigins,
+ assert.Equal(t, []string{"https://korap.ids-mannheim.de", "https://other.example.com"}, cfg.AllowOrigins,
"explicit AllowOrigins entries should be pruned to host-level origins")
}
@@ -1517,7 +1541,7 @@
Server: "https://korap.ids-mannheim.de:8080/instance/test",
}
ApplyDefaults(cfg)
- assert.Equal(t, "https://korap.ids-mannheim.de:8080", cfg.AllowOrigins,
+ assert.Equal(t, []string{"https://korap.ids-mannheim.de:8080"}, cfg.AllowOrigins,
"AllowOrigins should preserve port but strip path")
}