package config import ( "strings" "testing" "time" ) func TestRateLimitDefaults(t *testing.T) { cfg := Default() if !cfg.ClientAPI.RateLimit.Enabled { t.Error("rate limiting must be on by default: login is worth brute-forcing") } for name, bucket := range map[string]RateLimitBucket{ "default": cfg.ClientAPI.RateLimit.Default, "login": cfg.ClientAPI.RateLimit.Login, "register": cfg.ClientAPI.RateLimit.Register, } { if bucket.Burst < 1 || bucket.Interval <= 0 { t.Errorf("the %s bucket is unusable by default: %+v", name, bucket) } } // Login and registration are deliberately tighter than the rest. if cfg.ClientAPI.RateLimit.Login.Interval <= cfg.ClientAPI.RateLimit.Default.Interval { t.Error("the login bucket should refill more slowly than the default one") } if cfg.ClientAPI.RateLimit.Register.Interval <= cfg.ClientAPI.RateLimit.Login.Interval { t.Error("the registration bucket should refill more slowly than the login one") } if cfg.ClientAPI.AccessTokenLifetime != 0 { t.Error("access tokens must not expire by default: clients without refresh support need that") } if cfg.ClientAPI.TrustXForwardedFor { t.Error("X-Forwarded-For must not be trusted by default: it is spoofable") } } func TestRateLimitCanBeDisabledFromTheEnvironment(t *testing.T) { cfg := Default() if err := ApplyEnv(cfg, func(key string) string { if key == "WARP_RATE_LIMIT_ENABLED" { return "false" } return "" }); err != nil { t.Fatalf("ApplyEnv: %v", err) } if cfg.ClientAPI.RateLimit.Enabled { t.Error("WARP_RATE_LIMIT_ENABLED=false did not take effect") } } func TestValidateRejectsUnusableRateLimits(t *testing.T) { cfg := Default() cfg.Global.ServerName = "warp.example" cfg.ClientAPI.RateLimit.Login.Burst = 0 cfg.ClientAPI.RateLimit.Register.Interval = 0 cfg.ClientAPI.AccessTokenLifetime = -time.Second err := cfg.Validate() if err == nil { t.Fatal("expected validation to fail") } for _, want := range []string{ "client_api.rate_limit.login.burst", "client_api.rate_limit.register.interval", "client_api.access_token_lifetime", } { if !strings.Contains(err.Error(), want) { t.Errorf("error should mention %s, got: %v", want, err) } } } // TestDisabledRateLimitsSkipValidation: a deployment that turned the limiter // off should not have to keep its buckets valid. func TestDisabledRateLimitsSkipValidation(t *testing.T) { cfg := Default() cfg.Global.ServerName = "warp.example" cfg.ClientAPI.RateLimit = RateLimit{Enabled: false} if err := cfg.Validate(); err != nil { t.Errorf("Validate with rate limiting off = %v, want nil", err) } } func TestRegistrationTokens(t *testing.T) { cfg := Default() if cfg.Registration.RequiresToken() { t.Error("no tokens configured means no token stage") } if cfg.Registration.TokenValid("") || cfg.Registration.TokenValid("anything") { t.Error("with no tokens configured, nothing may validate") } cfg.Registration.Tokens = []string{"first-token", "second-token"} if !cfg.Registration.RequiresToken() { t.Error("configured tokens must add the token stage") } for _, token := range []string{"first-token", "second-token"} { if !cfg.Registration.TokenValid(token) { t.Errorf("TokenValid(%q) = false, want true", token) } } for _, token := range []string{"", "FIRST-TOKEN", "first-toke", "first-tokenn", "nope"} { if cfg.Registration.TokenValid(token) { t.Errorf("TokenValid(%q) = true, want false", token) } } } func TestValidateRejectsEmptyRegistrationToken(t *testing.T) { cfg := Default() cfg.Global.ServerName = "warp.example" cfg.Registration.Tokens = []string{"fine", " "} err := cfg.Validate() if err == nil || !strings.Contains(err.Error(), "registration.tokens[1]") { t.Fatalf("expected an error naming the empty token, got: %v", err) } } func TestRegistrationTokensFromTheEnvironment(t *testing.T) { cfg := Default() if err := ApplyEnv(cfg, func(key string) string { if key == "WARP_REGISTRATION_TOKENS" { return "one, two" } return "" }); err != nil { t.Fatalf("ApplyEnv: %v", err) } if len(cfg.Registration.Tokens) != 2 || !cfg.Registration.TokenValid("two") { t.Errorf("tokens = %v", cfg.Registration.Tokens) } }