Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTrustedRealIPIgnoresHeadersWithoutAllowlist(t *testing.T) {
|
||||
t.Parallel()
|
||||
h := TrustedRealIP(nil)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.RemoteAddr != "203.0.113.10:12345" {
|
||||
t.Fatalf("RemoteAddr = %q, want peer unchanged", r.RemoteAddr)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
|
||||
req.RemoteAddr = "203.0.113.10:12345"
|
||||
req.Header.Set("X-Forwarded-For", "198.51.100.1")
|
||||
rec := httptest.NewRecorder()
|
||||
h.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusNoContent {
|
||||
t.Fatalf("status = %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrustedRealIPIgnoresHeadersFromUntrustedPeer(t *testing.T) {
|
||||
t.Parallel()
|
||||
h := TrustedRealIP([]string{"10.0.0.0/8"})(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.RemoteAddr != "203.0.113.10:12345" {
|
||||
t.Fatalf("RemoteAddr = %q, want peer unchanged", r.RemoteAddr)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
|
||||
req.RemoteAddr = "203.0.113.10:12345"
|
||||
req.Header.Set("X-Forwarded-For", "198.51.100.1")
|
||||
rec := httptest.NewRecorder()
|
||||
h.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusNoContent {
|
||||
t.Fatalf("status = %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrustedRealIPRewritesFromTrustedPeer(t *testing.T) {
|
||||
t.Parallel()
|
||||
h := TrustedRealIP([]string{"10.0.0.1"})(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.RemoteAddr != "198.51.100.1" {
|
||||
t.Fatalf("RemoteAddr = %q, want client IP from XFF", r.RemoteAddr)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
|
||||
req.RemoteAddr = "10.0.0.1:443"
|
||||
req.Header.Set("X-Forwarded-For", "198.51.100.1, 10.0.0.1")
|
||||
rec := httptest.NewRecorder()
|
||||
h.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusNoContent {
|
||||
t.Fatalf("status = %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecurityHeadersBaseline(t *testing.T) {
|
||||
t.Parallel()
|
||||
h := SecurityHeaders(false)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
rec := httptest.NewRecorder()
|
||||
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/healthz", nil))
|
||||
if got := rec.Header().Get("X-Content-Type-Options"); got != "nosniff" {
|
||||
t.Fatalf("X-Content-Type-Options = %q", got)
|
||||
}
|
||||
if got := rec.Header().Get("X-Frame-Options"); got != "DENY" {
|
||||
t.Fatalf("X-Frame-Options = %q", got)
|
||||
}
|
||||
if got := rec.Header().Get("Referrer-Policy"); got != "strict-origin-when-cross-origin" {
|
||||
t.Fatalf("Referrer-Policy = %q", got)
|
||||
}
|
||||
if got := rec.Header().Get("Content-Security-Policy"); got != apiContentSecurityPolicy {
|
||||
t.Fatalf("Content-Security-Policy = %q, want %q", got, apiContentSecurityPolicy)
|
||||
}
|
||||
if got := rec.Header().Get("Content-Security-Policy-Report-Only"); got != "" {
|
||||
t.Fatalf("unexpected Report-Only CSP: %q", got)
|
||||
}
|
||||
if got := rec.Header().Get("Strict-Transport-Security"); got != "" {
|
||||
t.Fatalf("HSTS unexpectedly set: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecurityHeadersAPIContentSecurityPolicy(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !strings.Contains(apiContentSecurityPolicy, "default-src 'none'") {
|
||||
t.Fatalf("API CSP missing default-src 'none': %q", apiContentSecurityPolicy)
|
||||
}
|
||||
if !strings.Contains(apiContentSecurityPolicy, "frame-ancestors 'none'") {
|
||||
t.Fatalf("API CSP missing frame-ancestors 'none': %q", apiContentSecurityPolicy)
|
||||
}
|
||||
if !strings.Contains(apiContentSecurityPolicy, "form-action 'none'") {
|
||||
t.Fatalf("API CSP missing form-action 'none': %q", apiContentSecurityPolicy)
|
||||
}
|
||||
if strings.Contains(apiContentSecurityPolicy, "'unsafe-inline'") || strings.Contains(apiContentSecurityPolicy, "'unsafe-eval'") {
|
||||
t.Fatalf("API CSP must not allow unsafe script: %q", apiContentSecurityPolicy)
|
||||
}
|
||||
if strings.Contains(apiContentSecurityPolicy, "ws:") || strings.Contains(apiContentSecurityPolicy, "wss:") {
|
||||
t.Fatalf("API CSP must not allow websocket schemes: %q", apiContentSecurityPolicy)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecurityHeadersHSTSWhenSecure(t *testing.T) {
|
||||
t.Parallel()
|
||||
h := SecurityHeaders(true)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
rec := httptest.NewRecorder()
|
||||
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/healthz", nil))
|
||||
if got := rec.Header().Get("Strict-Transport-Security"); got == "" {
|
||||
t.Fatal("expected HSTS when SessionSecure")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCORSAllowsConfiguredOriginOnly(t *testing.T) {
|
||||
t.Parallel()
|
||||
s := testAPIServer()
|
||||
s.Config.WebOrigin = "http://localhost:5174"
|
||||
h := s.Router()
|
||||
|
||||
ok := httptest.NewRecorder()
|
||||
reqOK := httptest.NewRequest(http.MethodOptions, "/api/auth/login", nil)
|
||||
reqOK.Header.Set("Origin", "http://localhost:5174")
|
||||
reqOK.Header.Set("Access-Control-Request-Method", "POST")
|
||||
h.ServeHTTP(ok, reqOK)
|
||||
if got := ok.Header().Get("Access-Control-Allow-Origin"); got != "http://localhost:5174" {
|
||||
t.Fatalf("allow origin = %q", got)
|
||||
}
|
||||
if got := ok.Header().Get("Access-Control-Allow-Credentials"); got != "true" {
|
||||
t.Fatalf("allow credentials = %q", got)
|
||||
}
|
||||
|
||||
twin := httptest.NewRecorder()
|
||||
reqTwin := httptest.NewRequest(http.MethodOptions, "/api/auth/login", nil)
|
||||
reqTwin.Header.Set("Origin", "http://127.0.0.1:5174")
|
||||
reqTwin.Header.Set("Access-Control-Request-Method", "POST")
|
||||
h.ServeHTTP(twin, reqTwin)
|
||||
if got := twin.Header().Get("Access-Control-Allow-Origin"); got != "http://127.0.0.1:5174" {
|
||||
t.Fatalf("loopback twin allow origin = %q", got)
|
||||
}
|
||||
|
||||
bad := httptest.NewRecorder()
|
||||
reqBad := httptest.NewRequest(http.MethodOptions, "/api/auth/login", nil)
|
||||
reqBad.Header.Set("Origin", "https://evil.example")
|
||||
reqBad.Header.Set("Access-Control-Request-Method", "POST")
|
||||
h.ServeHTTP(bad, reqBad)
|
||||
if got := bad.Header().Get("Access-Control-Allow-Origin"); got != "" {
|
||||
t.Fatalf("unexpected allow origin for evil: %q", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user