package auth import ( "context" "testing" "time" ) func TestAPIKey_IsIPAllowed(t *testing.T) { tests := []struct { name string allowedIPs []string clientIP string want bool }{ { name: "no restrictions - any IP allowed", allowedIPs: nil, clientIP: "192.168.1.100", want: true, }, { name: "empty restrictions - any IP allowed", allowedIPs: []string{}, clientIP: "10.0.0.5", want: true, }, { name: "single IP match", allowedIPs: []string{"192.168.1.100"}, clientIP: "192.168.1.100", want: true, }, { name: "single IP no match", allowedIPs: []string{"192.168.1.100"}, clientIP: "192.168.1.101", want: false, }, { name: "CIDR match", allowedIPs: []string{"192.168.1.0/24"}, clientIP: "192.168.1.55", want: true, }, { name: "CIDR no match", allowedIPs: []string{"192.168.1.0/24"}, clientIP: "192.168.2.1", want: false, }, { name: "multiple CIDRs - first matches", allowedIPs: []string{"10.0.0.0/8", "192.168.0.0/16"}, clientIP: "10.50.25.100", want: true, }, { name: "multiple CIDRs - second matches", allowedIPs: []string{"10.0.0.0/8", "192.168.0.0/16"}, clientIP: "192.168.50.1", want: true, }, { name: "multiple CIDRs - none match", allowedIPs: []string{"10.0.0.0/8", "192.168.0.0/16"}, clientIP: "172.16.0.1", want: false, }, { name: "mixed IP and CIDR - IP matches", allowedIPs: []string{"10.0.0.0/8", "172.16.0.1"}, clientIP: "172.16.0.1", want: true, }, { name: "mixed IP and CIDR - CIDR matches", allowedIPs: []string{"10.0.0.0/8", "172.16.0.1"}, clientIP: "10.1.2.3", want: true, }, { name: "IPv6 CIDR", allowedIPs: []string{"2001:db8::/32"}, clientIP: "2001:db8:1234:5678::1", want: true, }, { name: "IPv6 no match", allowedIPs: []string{"2001:db8::/32"}, clientIP: "2001:db9::1", want: false, }, { name: "invalid client IP", allowedIPs: []string{"192.168.1.0/24"}, clientIP: "not-an-ip", want: false, }, { name: "invalid CIDR in allowlist (fallback to IP parse)", allowedIPs: []string{"invalid/cidr", "192.168.1.100"}, clientIP: "192.168.1.100", want: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { key := &APIKey{AllowedIPs: tt.allowedIPs} if got := key.IsIPAllowed(tt.clientIP); got != tt.want { t.Errorf("IsIPAllowed(%q) = %v, want %v", tt.clientIP, got, tt.want) } }) } } func TestService_CreateWithAllowedIPs(t *testing.T) { svc := newTestService(t, "admin-key") t.Run("creates key with IP restrictions", func(t *testing.T) { resp, err := svc.Create(context.Background(), CreateKeyRequest{ Name: "test-ip-key", Scopes: []Scope{ScopeProjectsRead}, AllowedIPs: []string{"192.168.1.0/24", "10.0.0.1"}, ExpiresIn: 24 * time.Hour, CreatedBy: "test", }) if err != nil { t.Fatalf("Create() error = %v", err) } if len(resp.Key.AllowedIPs) != 2 { t.Errorf("Key.AllowedIPs length = %d, want 2", len(resp.Key.AllowedIPs)) } key, err := svc.Get(context.Background(), string(resp.Key.ID)) if err != nil { t.Fatalf("Get() error = %v", err) } if len(key.AllowedIPs) != 2 { t.Errorf("Retrieved Key.AllowedIPs length = %d, want 2", len(key.AllowedIPs)) } validatedKey, err := svc.Validate(context.Background(), resp.Secret) if err != nil { t.Fatalf("Validate() error = %v", err) } if len(validatedKey.AllowedIPs) != 2 { t.Errorf("Validated Key.AllowedIPs length = %d, want 2", len(validatedKey.AllowedIPs)) } if !validatedKey.IsIPAllowed("192.168.1.50") { t.Error("IsIPAllowed should return true for IP in allowed CIDR") } if !validatedKey.IsIPAllowed("10.0.0.1") { t.Error("IsIPAllowed should return true for explicitly allowed IP") } if validatedKey.IsIPAllowed("172.16.0.1") { t.Error("IsIPAllowed should return false for IP not in allowed list") } }) t.Run("creates key with no IP restrictions", func(t *testing.T) { resp, err := svc.Create(context.Background(), CreateKeyRequest{ Name: "test-no-ip-key", Scopes: []Scope{ScopeProjectsRead}, ExpiresIn: 24 * time.Hour, CreatedBy: "test", }) if err != nil { t.Fatalf("Create() error = %v", err) } if len(resp.Key.AllowedIPs) != 0 { t.Errorf("Key.AllowedIPs should be empty, got %v", resp.Key.AllowedIPs) } validatedKey, err := svc.Validate(context.Background(), resp.Secret) if err != nil { t.Fatalf("Validate() error = %v", err) } if !validatedKey.IsIPAllowed("1.2.3.4") { t.Error("IsIPAllowed should return true when no restrictions set") } }) }