package identityruntime import ( "context" "strings" "testing" "github.com/easyai/easyai-ai-gateway/apps/api/internal/identity" ) func TestRuntimeBuilderRejectsLoopbackHTTPOutsideLocalEnvironments(t *testing.T) { revision := identity.Revision{ AuthCenterURL: "https://auth.example.com", Issuer: "https://auth.example.com/issuer/easyai", PublicBaseURL: "https://api.example.com", WebBaseURL: "https://gateway.example.com", SecurityEventIssuer: "https://auth.example.com/ssf", SecurityEventConfigURL: "https://auth.example.com/.well-known/ssf-configuration/ssf", SessionRevocation: true, } production := &RuntimeBuilder{config: RuntimeBuilderConfig{AppEnv: "production"}} for _, test := range []struct { name string mutate func(*identity.Revision) }{ {name: "auth center", mutate: func(value *identity.Revision) { value.AuthCenterURL = "http://localhost:18000" }}, {name: "OIDC issuer", mutate: func(value *identity.Revision) { value.Issuer = "http://localhost:18003/issuer/easyai" }}, {name: "public base", mutate: func(value *identity.Revision) { value.PublicBaseURL = "http://127.0.0.1:18089" }}, {name: "web base", mutate: func(value *identity.Revision) { value.WebBaseURL = "http://localhost:5178" }}, {name: "SSF issuer", mutate: func(value *identity.Revision) { value.SecurityEventIssuer = "http://localhost:18004/ssf" }}, {name: "SSF configuration", mutate: func(value *identity.Revision) { value.SecurityEventConfigURL = "http://localhost:18004/.well-known/ssf-configuration/ssf" }}, } { t.Run(test.name, func(t *testing.T) { candidate := revision test.mutate(&candidate) if _, err := production.Build(context.Background(), candidate); err == nil || !strings.Contains(err.Error(), "HTTPS") { t.Fatalf("production runtime did not reject loopback HTTP %s URL: %v", test.name, err) } }) } localRevision := revision localRevision.AuthCenterURL = "http://localhost:18000" localRevision.Issuer = "http://localhost:18003/issuer/easyai" localRevision.PublicBaseURL = "http://127.0.0.1:18089" localRevision.WebBaseURL = "http://localhost:5178" localRevision.SecurityEventIssuer = "http://localhost:18004/ssf" localRevision.SecurityEventConfigURL = "http://localhost:18004/.well-known/ssf-configuration/ssf" development := &RuntimeBuilder{config: RuntimeBuilderConfig{AppEnv: "development"}} if _, err := development.Build(context.Background(), localRevision); err == nil || strings.Contains(err.Error(), "HTTPS") { t.Fatalf("development runtime did not pass URL policy before completeness checks: %v", err) } } func TestSecurityEventManagerConfigBindsRuntimeToTargetRevision(t *testing.T) { builder := &RuntimeBuilder{config: RuntimeBuilderConfig{AppEnv: "test"}} revision := identity.Revision{ ID: "revision-target", Issuer: "https://auth.example/issuer", TenantID: "tenant-public-id", MachineClientID: "gateway-machine", PublicBaseURL: "https://gateway.example", SecurityEventIssuer: "https://auth.example/ssf/", SecurityEventAudience: "urn:easyai:ssf:receiver:target", } runtimeConfig := builder.securityEventManagerConfig(revision, true) if !runtimeConfig.StrictRevisionBinding || runtimeConfig.ExpectedTransmitterIssuer != "https://auth.example/ssf" || runtimeConfig.ExpectedAudience != revision.SecurityEventAudience || runtimeConfig.ManagementClientID != revision.MachineClientID || runtimeConfig.ExpectedOwnerKey != "identity-pairing-ssf-revision-target" { t.Fatalf("runtime SSF binding config=%#v", runtimeConfig) } recoveryConfig := builder.securityEventManagerConfig(revision, false) if recoveryConfig.StrictRevisionBinding { t.Fatal("conflict recovery was incorrectly blocked by strict Revision binding") } if recoveryConfig.ExpectedOwnerKey != runtimeConfig.ExpectedOwnerKey { t.Fatal("recovery manager lost the target owner needed for explicit validation") } }