feat(config): CORS-Erlaubnisliste server.allowed_origins (TALK_ALLOWED_ORIGINS)

Exakte Origins wie https://chat.az-gruppe.com; Wildcard "*" nur explizit.
Bewusster Default: leer = keine CORS-Header (Tür zu). Env schlägt Datei
(kommagetrennt; "off" leert), leere Einträge sind klare Fehler.
This commit is contained in:
m3ta-chiron committed 2026-10-18 20:06:05 +02:00
1 parent 24ceff3f48
commit cef6e6b9f2
2 files changed
+147

No files matched your search

+103
View File
@@ -356,6 +356,109 @@ func TestLoadServerPort(t *testing.T) {
})
}
func TestLoadAllowedOrigins(t *testing.T) {
writeTOML := func(t *testing.T, content string) string {
t.Helper()
path := filepath.Join(t.TempDir(), "config.toml")
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatal(err)
}
return path
}
t.Run("Default: leere Liste (kein CORS)", func(t *testing.T) {
cfg, err := Load(Options{
File: filepath.Join(t.TempDir(), "gibt-es-nicht.toml"),
Getenv: env(nil),
})
if err != nil {
t.Fatalf("Load: %v", err)
}
if len(cfg.Server.AllowedOrigins) != 0 {
t.Errorf("AllowedOrigins = %v, want leer (Tür zu)", cfg.Server.AllowedOrigins)
}
})
t.Run("TOML-Liste", func(t *testing.T) {
cfg, err := Load(Options{
File: writeTOML(t, "[server]\nallowed_origins = [\"https://chat.az-gruppe.com\"]\n"),
Getenv: env(nil),
})
if err != nil {
t.Fatalf("Load: %v", err)
}
want := []string{"https://chat.az-gruppe.com"}
if strings.Join(cfg.Server.AllowedOrigins, "|") != strings.Join(want, "|") {
t.Errorf("AllowedOrigins = %v, want %v", cfg.Server.AllowedOrigins, want)
}
})
t.Run("TALK_ALLOWED_ORIGINS schlägt Datei (mehrere, mit Leerzeichen)", func(t *testing.T) {
cfg, err := Load(Options{
File: writeTOML(t, "[server]\nallowed_origins = [\"https://datei.example\"]\n"),
Getenv: env(map[string]string{"TALK_ALLOWED_ORIGINS": "https://chat.az-gruppe.com, https://grid.ag"}),
})
if err != nil {
t.Fatalf("Load: %v", err)
}
want := []string{"https://chat.az-gruppe.com", "https://grid.ag"}
if strings.Join(cfg.Server.AllowedOrigins, "|") != strings.Join(want, "|") {
t.Errorf("AllowedOrigins = %v, want %v", cfg.Server.AllowedOrigins, want)
}
})
t.Run("TALK_ALLOWED_ORIGINS=off leert die Datei-Liste", func(t *testing.T) {
cfg, err := Load(Options{
File: writeTOML(t, "[server]\nallowed_origins = [\"https://datei.example\"]\n"),
Getenv: env(map[string]string{"TALK_ALLOWED_ORIGINS": "off"}),
})
if err != nil {
t.Fatalf("Load: %v", err)
}
if len(cfg.Server.AllowedOrigins) != 0 {
t.Errorf("AllowedOrigins = %v, want leer (off)", cfg.Server.AllowedOrigins)
}
})
t.Run("Wildcard ist erlaubter Wert", func(t *testing.T) {
cfg, err := Load(Options{
File: filepath.Join(t.TempDir(), "gibt-es-nicht.toml"),
Getenv: env(map[string]string{"TALK_ALLOWED_ORIGINS": "*"}),
})
if err != nil {
t.Fatalf("Load: %v", err)
}
if strings.Join(cfg.Server.AllowedOrigins, "|") != "*" {
t.Errorf("AllowedOrigins = %v, want [*]", cfg.Server.AllowedOrigins)
}
})
t.Run("leere Einträge sind klare Fehler", func(t *testing.T) {
for _, tc := range []struct {
name string
file string
envv string
}{
{"env", "", "https://a.example,,https://b.example"},
{"datei", "[server]\nallowed_origins = [\"https://a.example\", \"\"]\n", ""},
} {
t.Run(tc.name, func(t *testing.T) {
file := filepath.Join(t.TempDir(), "gibt-es-nicht.toml")
if tc.file != "" {
file = writeTOML(t, tc.file)
}
_, err := Load(Options{
File: file,
Getenv: env(map[string]string{"TALK_ALLOWED_ORIGINS": tc.envv}),
})
if err == nil || !strings.Contains(err.Error(), "allowed_origins") && !strings.Contains(err.Error(), "ALLOWED_ORIGINS") {
t.Fatalf("Fehler = %v, will klaren allowed_origins-Fehler", err)
}
})
}
})
}
// TestLoadGesetze deckt die Task-05-Felder ab: Rate-Limit (Default 30 s,
// "0s" = unbegrenzt), High-Burst (Default an), Ruhezeiten (Default aus;
// Format validiert) samt Env-Overrides und klaren Fehlern.