package web import ( "net/http" "net/http/httptest" "strings" "testing" ) func TestSecurityHeaders(t *testing.T) { h := SecurityHeaders(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })) rec := httptest.NewRecorder() h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil)) want := map[string]string{ "X-Content-Type-Options": "nosniff", "Referrer-Policy": "no-referrer", "X-Frame-Options": "DENY", } for k, v := range want { if got := rec.Header().Get(k); got != v { t.Errorf("%s = %q, want %q", k, got, v) } } if hsts := rec.Header().Get("Strict-Transport-Security"); !strings.Contains(hsts, "max-age=") { t.Errorf("Strict-Transport-Security = %q", hsts) } csp := rec.Header().Get("Content-Security-Policy") for _, directive := range []string{"default-src 'self'", "frame-ancestors 'none'", "form-action 'self'"} { if !strings.Contains(csp, directive) { t.Errorf("CSP %q fehlt %q", csp, directive) } } if strings.Contains(csp, "unsafe-inline") || strings.Contains(csp, "unsafe-eval") { t.Errorf("CSP darf kein unsafe-* enthalten: %q", csp) } } func TestNoStore(t *testing.T) { rec := httptest.NewRecorder() NoStore(rec) if got := rec.Header().Get("Cache-Control"); !strings.Contains(got, "no-store") { t.Errorf("Cache-Control = %q", got) } } func TestClientIPIgnoresForwardedHeader(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/", nil) r.RemoteAddr = "10.1.20.34:51234" r.Header.Set("X-Forwarded-For", "1.2.3.4") // Das Portal terminiert TLS selbst; ein gefälschter Header darf das // Audit-Log nicht verfälschen. if got := ClientIP(r); got != "10.1.20.34" { t.Errorf("ClientIP = %q, want 10.1.20.34", got) } } func TestClientIPHandlesIPv6AndMalformed(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/", nil) r.RemoteAddr = "[2001:db8::1]:443" if got := ClientIP(r); got != "2001:db8::1" { t.Errorf("ClientIP = %q", got) } r.RemoteAddr = "kaputt" if got := ClientIP(r); got != "kaputt" { t.Errorf("ClientIP = %q, malformed muss unverändert durchgereicht werden", got) } } func TestStringsAreComplete(t *testing.T) { required := []string{ "login_title", "login_username", "login_password", "login_submit", "login_username_hint", "error_generic_login", "error_password_expired", "error_rate_limited", "error_session_expired", "overview_title", "overview_no_certs", "overview_expiry_warning", "download_ovpn", "download_viscosity", "logout", "guides_title", "error_service_unavailable", "error_forbidden", "logout_reminder", "valid_until", "vpn_instance", } for _, k := range required { v, ok := S[k] if !ok || strings.TrimSpace(v) == "" { t.Errorf("String %q fehlt oder ist leer", k) } } if T("gibt_es_nicht") != "gibt_es_nicht" { t.Error("T muss bei unbekanntem Schlüssel den Schlüssel liefern, nie einen leeren String") } }