From 80ab3caa5f579d7e164b071027211d2bbd7f44af Mon Sep 17 00:00:00 2001 From: mleku Date: Thu, 30 Oct 2025 17:51:15 +0000 Subject: [PATCH] Implement policy-based event filtering and add integration tests - Enhanced the HandleReq function to incorporate policy checks for privileged events, ensuring only authorized users can access sensitive data. - Introduced a new integration test suite for policy filtering, validating the behavior of event access based on user authentication and policy rules. - Added a script to automate the policy filter integration tests, improving testing efficiency and reliability. - Updated version to v0.20.2 to reflect the new features and improvements. --- app/handle-req.go | 51 ++ cmd/policyfiltertest/main.go | 319 +++++++++++++ pkg/policy/policy_integration_test.go | 516 +++++++++++++++++++++ pkg/policy/policy_test.go | 360 ++++++++++++++ pkg/version/version | 2 +- scripts/run-policy-filter-test.sh | 198 ++++++++ scripts/secp256k1 | 1 - scripts/sprocket/SPROCKET_TEST_README.md | 0 scripts/sprocket/test-sprocket-complete.sh | 0 scripts/sprocket/test-sprocket-demo.sh | 0 scripts/sprocket/test-sprocket-example.sh | 0 scripts/sprocket/test-sprocket-final.sh | 0 scripts/sprocket/test-sprocket-manual.sh | 0 scripts/sprocket/test-sprocket-simple.sh | 0 scripts/sprocket/test-sprocket-working.sh | 0 scripts/sprocket/test-sprocket.py | 0 scripts/sprocket/test-sprocket.sh | 0 17 files changed, 1445 insertions(+), 2 deletions(-) create mode 100644 cmd/policyfiltertest/main.go create mode 100644 pkg/policy/policy_integration_test.go create mode 100755 scripts/run-policy-filter-test.sh delete mode 160000 scripts/secp256k1 mode change 100644 => 100755 scripts/sprocket/SPROCKET_TEST_README.md mode change 100644 => 100755 scripts/sprocket/test-sprocket-complete.sh mode change 100644 => 100755 scripts/sprocket/test-sprocket-demo.sh mode change 100644 => 100755 scripts/sprocket/test-sprocket-example.sh mode change 100644 => 100755 scripts/sprocket/test-sprocket-final.sh mode change 100644 => 100755 scripts/sprocket/test-sprocket-manual.sh mode change 100644 => 100755 scripts/sprocket/test-sprocket-simple.sh mode change 100644 => 100755 scripts/sprocket/test-sprocket-working.sh mode change 100644 => 100755 scripts/sprocket/test-sprocket.py mode change 100644 => 100755 scripts/sprocket/test-sprocket.sh diff --git a/app/handle-req.go b/app/handle-req.go index 86dfbfd..ef1ebe2 100644 --- a/app/handle-req.go +++ b/app/handle-req.go @@ -357,6 +357,57 @@ func (l *Listener) HandleReq(msg []byte) (err error) { ) } } else { + // Check if policy defines this event as privileged (even if not in hardcoded list) + // Policy check will handle this later, but we can skip it here if not authenticated + // to avoid unnecessary processing + if l.policyManager != nil && l.policyManager.Manager != nil && l.policyManager.Manager.IsEnabled() { + rule, hasRule := l.policyManager.Rules[int(ev.Kind)] + if hasRule && rule.Privileged && accessLevel != "admin" { + pk := l.authedPubkey.Load() + if pk == nil { + // Not authenticated - cannot see policy-privileged events + log.T.C( + func() string { + return fmt.Sprintf( + "policy-privileged event %s denied - not authenticated", + ev.ID, + ) + }, + ) + continue + } + // Policy check will verify authorization later, but we need to check + // if user is party to the event here + authorized := false + if utils.FastEqual(ev.Pubkey, pk) { + authorized = true + } else { + // Check p tags + pTags := ev.Tags.GetAll([]byte("p")) + for _, pTag := range pTags { + var pt []byte + if pt, err = hexenc.Dec(string(pTag.Value())); chk.E(err) { + continue + } + if utils.FastEqual(pt, pk) { + authorized = true + break + } + } + } + if !authorized { + log.T.C( + func() string { + return fmt.Sprintf( + "policy-privileged event %s does not contain the logged in pubkey %0x", + ev.ID, pk, + ) + }, + ) + continue + } + } + } tmp = append(tmp, ev) } } diff --git a/cmd/policyfiltertest/main.go b/cmd/policyfiltertest/main.go new file mode 100644 index 0000000..21361e7 --- /dev/null +++ b/cmd/policyfiltertest/main.go @@ -0,0 +1,319 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "time" + + "lol.mleku.dev/chk" + "lol.mleku.dev/log" + "next.orly.dev/pkg/crypto/p256k" + "next.orly.dev/pkg/encoders/event" + "next.orly.dev/pkg/encoders/filter" + "next.orly.dev/pkg/encoders/hex" + "next.orly.dev/pkg/encoders/kind" + "next.orly.dev/pkg/encoders/tag" + "next.orly.dev/pkg/protocol/ws" +) + +func main() { + var err error + url := flag.String("url", "ws://127.0.0.1:34568", "relay websocket URL") + allowedPubkeyHex := flag.String("allowed-pubkey", "", "hex-encoded allowed pubkey") + allowedSecHex := flag.String("allowed-sec", "", "hex-encoded allowed secret key") + unauthorizedPubkeyHex := flag.String("unauthorized-pubkey", "", "hex-encoded unauthorized pubkey") + unauthorizedSecHex := flag.String("unauthorized-sec", "", "hex-encoded unauthorized secret key") + timeout := flag.Duration("timeout", 10*time.Second, "operation timeout") + flag.Parse() + + if *allowedPubkeyHex == "" || *allowedSecHex == "" { + log.E.F("required flags: -allowed-pubkey and -allowed-sec") + os.Exit(1) + } + if *unauthorizedPubkeyHex == "" || *unauthorizedSecHex == "" { + log.E.F("required flags: -unauthorized-pubkey and -unauthorized-sec") + os.Exit(1) + } + + // Decode keys + allowedSecBytes, err := hex.Dec(*allowedSecHex) + if err != nil { + log.E.F("failed to decode allowed secret key: %v", err) + os.Exit(1) + } + allowedSigner := &p256k.Signer{} + if err = allowedSigner.InitSec(allowedSecBytes); chk.E(err) { + log.E.F("failed to initialize allowed signer: %v", err) + os.Exit(1) + } + + unauthorizedSecBytes, err := hex.Dec(*unauthorizedSecHex) + if err != nil { + log.E.F("failed to decode unauthorized secret key: %v", err) + os.Exit(1) + } + unauthorizedSigner := &p256k.Signer{} + if err = unauthorizedSigner.InitSec(unauthorizedSecBytes); chk.E(err) { + log.E.F("failed to initialize unauthorized signer: %v", err) + os.Exit(1) + } + + ctx, cancel := context.WithTimeout(context.Background(), *timeout) + defer cancel() + + // Test 1: Authenticated as allowed pubkey - should work + fmt.Println("Test 1: Publishing event 30520 with allowed pubkey (authenticated)...") + if err := testWriteEvent(ctx, *url, 30520, allowedSigner, allowedSigner); err != nil { + fmt.Printf("❌ FAILED: %v\n", err) + os.Exit(1) + } + fmt.Println("✅ PASSED: Event published successfully") + + // Test 2: Authenticated as allowed pubkey, then read event 10306 - should work + // First publish an event, then read it + fmt.Println("\nTest 2: Publishing and reading event 10306 with allowed pubkey (authenticated)...") + if err := testWriteEvent(ctx, *url, 10306, allowedSigner, allowedSigner); err != nil { + fmt.Printf("❌ FAILED to publish: %v\n", err) + os.Exit(1) + } + if err := testReadEvent(ctx, *url, 10306, allowedSigner); err != nil { + fmt.Printf("❌ FAILED to read: %v\n", err) + os.Exit(1) + } + fmt.Println("✅ PASSED: Event readable by allowed user") + + // Test 3: Unauthenticated request - should be blocked + fmt.Println("\nTest 3: Publishing event 30520 without authentication...") + if err := testWriteEventUnauthenticated(ctx, *url, 30520, allowedSigner); err != nil { + fmt.Printf("✅ PASSED: Event correctly blocked (expected): %v\n", err) + } else { + fmt.Println("❌ FAILED: Event was allowed when it should have been blocked") + os.Exit(1) + } + + // Test 4: Authenticated as unauthorized pubkey - should be blocked + fmt.Println("\nTest 4: Publishing event 30520 with unauthorized pubkey...") + if err := testWriteEvent(ctx, *url, 30520, unauthorizedSigner, unauthorizedSigner); err != nil { + fmt.Printf("✅ PASSED: Event correctly blocked (expected): %v\n", err) + } else { + fmt.Println("❌ FAILED: Event was allowed when it should have been blocked") + os.Exit(1) + } + + // Test 5: Read event 10306 without authentication - should be blocked + // Event was published in test 2, so it exists in the database + fmt.Println("\nTest 5: Reading event 10306 without authentication (should be blocked)...") + // Wait a bit to ensure event is stored + time.Sleep(500 * time.Millisecond) + // If no error is returned, that means no events were received (which is correct) + // If an error is returned, it means an event was received (which is wrong) + if err := testReadEventUnauthenticated(ctx, *url, 10306); err != nil { + // If we got an error about receiving an event, that's a failure + if strings.Contains(err.Error(), "unexpected event received") { + fmt.Printf("❌ FAILED: %v\n", err) + os.Exit(1) + } + // Other errors (like connection errors) are also failures + fmt.Printf("❌ FAILED: Unexpected error: %v\n", err) + os.Exit(1) + } + fmt.Println("✅ PASSED: No events received (correctly filtered by policy)") + + // Test 6: Read event 10306 with unauthorized pubkey - should be blocked + fmt.Println("\nTest 6: Reading event 10306 with unauthorized pubkey (should be blocked)...") + // If no error is returned, that means no events were received (which is correct) + // If an error is returned about receiving an event, that's a failure + if err := testReadEvent(ctx, *url, 10306, unauthorizedSigner); err != nil { + // Connection/subscription errors are failures + fmt.Printf("❌ FAILED: Unexpected error: %v\n", err) + os.Exit(1) + } + fmt.Println("✅ PASSED: No events received (correctly filtered by policy)") + + fmt.Println("\n✅ All tests passed!") +} + +func testWriteEvent(ctx context.Context, url string, kindNum uint16, eventSigner, authSigner *p256k.Signer) error { + rl, err := ws.RelayConnect(ctx, url) + if err != nil { + return fmt.Errorf("connect error: %w", err) + } + defer rl.Close() + + // Send a REQ first to trigger AUTH challenge (when AuthToWrite is enabled) + // This is needed because challenges are sent on REQ, not on connect + limit := uint(1) + ff := filter.NewS(&filter.F{ + Kinds: kind.NewS(kind.New(kindNum)), + Limit: &limit, + }) + sub, err := rl.Subscribe(ctx, ff) + if err != nil { + return fmt.Errorf("subscription error (may be expected): %w", err) + } + // Wait a bit for challenge to arrive + time.Sleep(500 * time.Millisecond) + sub.Unsub() + + // Authenticate + if err = rl.Auth(ctx, authSigner); err != nil { + return fmt.Errorf("auth error: %w", err) + } + + // Create and sign event + ev := &event.E{ + CreatedAt: time.Now().Unix(), + Kind: kind.K{K: kindNum}.K, + Tags: tag.NewS(), + Content: []byte(fmt.Sprintf("test event kind %d", kindNum)), + } + // Add p tag for privileged check + pTag := tag.NewFromAny("p", hex.Enc(authSigner.Pub())) + ev.Tags.Append(pTag) + + // Add d tag for addressable events (kinds 30000-39999) + if kindNum >= 30000 && kindNum < 40000 { + dTag := tag.NewFromAny("d", "test") + ev.Tags.Append(dTag) + } + + if err = ev.Sign(eventSigner); err != nil { + return fmt.Errorf("sign error: %w", err) + } + + // Publish + if err = rl.Publish(ctx, ev); err != nil { + return fmt.Errorf("publish error: %w", err) + } + + return nil +} + +func testWriteEventUnauthenticated(ctx context.Context, url string, kindNum uint16, eventSigner *p256k.Signer) error { + rl, err := ws.RelayConnect(ctx, url) + if err != nil { + return fmt.Errorf("connect error: %w", err) + } + defer rl.Close() + + // Do NOT authenticate + + // Create and sign event + ev := &event.E{ + CreatedAt: time.Now().Unix(), + Kind: kind.K{K: kindNum}.K, + Tags: tag.NewS(), + Content: []byte(fmt.Sprintf("test event kind %d (unauthenticated)", kindNum)), + } + + // Add d tag for addressable events (kinds 30000-39999) + if kindNum >= 30000 && kindNum < 40000 { + dTag := tag.NewFromAny("d", "test") + ev.Tags.Append(dTag) + } + + if err = ev.Sign(eventSigner); err != nil { + return fmt.Errorf("sign error: %w", err) + } + + // Publish (should fail) + if err = rl.Publish(ctx, ev); err != nil { + return fmt.Errorf("publish error (expected): %w", err) + } + + return nil +} + +func testReadEvent(ctx context.Context, url string, kindNum uint16, authSigner *p256k.Signer) error { + rl, err := ws.RelayConnect(ctx, url) + if err != nil { + return fmt.Errorf("connect error: %w", err) + } + defer rl.Close() + + // Send a REQ first to trigger AUTH challenge (when AuthToWrite is enabled) + // Then authenticate + ff := filter.NewS(&filter.F{ + Kinds: kind.NewS(kind.New(kindNum)), + }) + sub, err := rl.Subscribe(ctx, ff) + if err != nil { + return fmt.Errorf("subscription error: %w", err) + } + // Wait a bit for challenge to arrive + time.Sleep(500 * time.Millisecond) + + // Authenticate + if err = rl.Auth(ctx, authSigner); err != nil { + sub.Unsub() + return fmt.Errorf("auth error: %w", err) + } + + // Wait for events or timeout + // If we receive any events, return nil (success) + // If we don't receive events, also return nil (no events found, which may be expected) + select { + case ev := <-sub.Events: + if ev != nil { + sub.Unsub() + return nil // Event received + } + case <-sub.EndOfStoredEvents: + // EOSE received, no more events + sub.Unsub() + return nil + case <-time.After(5 * time.Second): + // No events received - this might be OK if no events exist or they're filtered + sub.Unsub() + return nil + case <-ctx.Done(): + sub.Unsub() + return ctx.Err() + } + + return nil +} + +func testReadEventUnauthenticated(ctx context.Context, url string, kindNum uint16) error { + rl, err := ws.RelayConnect(ctx, url) + if err != nil { + return fmt.Errorf("connect error: %w", err) + } + defer rl.Close() + + // Do NOT authenticate + + // Subscribe to events + ff := filter.NewS(&filter.F{ + Kinds: kind.NewS(kind.New(kindNum)), + }) + + sub, err := rl.Subscribe(ctx, ff) + if err != nil { + return fmt.Errorf("subscription error (may be expected): %w", err) + } + defer sub.Unsub() + + // Wait for events or timeout + // If we receive any events, that's a failure (should be blocked) + select { + case ev := <-sub.Events: + if ev != nil { + return fmt.Errorf("unexpected event received: should have been blocked by policy (event ID: %s)", hex.Enc(ev.ID)) + } + case <-sub.EndOfStoredEvents: + // EOSE received, no events (this is expected for unauthenticated privileged events) + return nil + case <-time.After(5 * time.Second): + // No events received - this is expected for unauthenticated requests + return nil + case <-ctx.Done(): + return ctx.Err() + } + + return nil +} + diff --git a/pkg/policy/policy_integration_test.go b/pkg/policy/policy_integration_test.go new file mode 100644 index 0000000..47eeb13 --- /dev/null +++ b/pkg/policy/policy_integration_test.go @@ -0,0 +1,516 @@ +package policy + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "testing" + "time" + + "lol.mleku.dev/chk" + "next.orly.dev/pkg/crypto/p256k" + "next.orly.dev/pkg/encoders/event" + "next.orly.dev/pkg/encoders/hex" + "next.orly.dev/pkg/encoders/kind" + "next.orly.dev/pkg/encoders/tag" +) + +// TestPolicyIntegration runs the relay with policy enabled and tests event filtering +func TestPolicyIntegration(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + // Generate test keys + allowedSigner := &p256k.Signer{} + if err := allowedSigner.Generate(); chk.E(err) { + t.Fatalf("Failed to generate allowed signer: %v", err) + } + allowedPubkeyHex := hex.Enc(allowedSigner.Pub()) + + unauthorizedSigner := &p256k.Signer{} + if err := unauthorizedSigner.Generate(); chk.E(err) { + t.Fatalf("Failed to generate unauthorized signer: %v", err) + } + + // Create temporary directory for policy config + tempDir := t.TempDir() + configDir := filepath.Join(tempDir, "ORLY_TEST") + if err := os.MkdirAll(configDir, 0755); chk.E(err) { + t.Fatalf("Failed to create config directory: %v", err) + } + + // Create policy JSON with generated keys + policyJSON := map[string]interface{}{ + "kind": map[string]interface{}{ + "whitelist": []int{4678, 10306, 30520, 30919}, + }, + "rules": map[string]interface{}{ + "4678": map[string]interface{}{ + "description": "Zenotp message events", + "script": filepath.Join(configDir, "validate4678.js"), // Won't exist, should fall back to default + "privileged": true, + }, + "10306": map[string]interface{}{ + "description": "End user whitelist changes", + "read_allow": []string{allowedPubkeyHex}, + "privileged": true, + }, + "30520": map[string]interface{}{ + "description": "Zenotp events", + "write_allow": []string{allowedPubkeyHex}, + "privileged": true, + }, + "30919": map[string]interface{}{ + "description": "Zenotp events", + "write_allow": []string{allowedPubkeyHex}, + "privileged": true, + }, + }, + } + + policyJSONBytes, err := json.MarshalIndent(policyJSON, "", " ") + if err != nil { + t.Fatalf("Failed to marshal policy JSON: %v", err) + } + + policyPath := filepath.Join(configDir, "policy.json") + if err := os.WriteFile(policyPath, policyJSONBytes, 0644); chk.E(err) { + t.Fatalf("Failed to write policy file: %v", err) + } + + // Create events with proper signatures + // Event 1: Kind 30520 with allowed pubkey (should be allowed) + event30520Allowed := event.New() + event30520Allowed.CreatedAt = time.Now().Unix() + event30520Allowed.Kind = kind.K{K: 30520}.K + event30520Allowed.Content = []byte("test event 30520") + event30520Allowed.Tags = tag.NewS() + addPTag(event30520Allowed, allowedSigner.Pub()) // Add p tag for privileged check + if err := event30520Allowed.Sign(allowedSigner); chk.E(err) { + t.Fatalf("Failed to sign event30520Allowed: %v", err) + } + + // Event 2: Kind 30520 with unauthorized pubkey (should be denied) + event30520Unauthorized := event.New() + event30520Unauthorized.CreatedAt = time.Now().Unix() + event30520Unauthorized.Kind = kind.K{K: 30520}.K + event30520Unauthorized.Content = []byte("test event 30520 unauthorized") + event30520Unauthorized.Tags = tag.NewS() + if err := event30520Unauthorized.Sign(unauthorizedSigner); chk.E(err) { + t.Fatalf("Failed to sign event30520Unauthorized: %v", err) + } + + // Event 3: Kind 10306 with allowed pubkey (should be readable by allowed user) + event10306Allowed := event.New() + event10306Allowed.CreatedAt = time.Now().Unix() + event10306Allowed.Kind = kind.K{K: 10306}.K + event10306Allowed.Content = []byte("test event 10306") + event10306Allowed.Tags = tag.NewS() + addPTag(event10306Allowed, allowedSigner.Pub()) // Add p tag for privileged check + if err := event10306Allowed.Sign(allowedSigner); chk.E(err) { + t.Fatalf("Failed to sign event10306Allowed: %v", err) + } + + // Event 4: Kind 4678 with allowed pubkey (script-based, should fall back to default) + event4678Allowed := event.New() + event4678Allowed.CreatedAt = time.Now().Unix() + event4678Allowed.Kind = kind.K{K: 4678}.K + event4678Allowed.Content = []byte("test event 4678") + event4678Allowed.Tags = tag.NewS() + addPTag(event4678Allowed, allowedSigner.Pub()) // Add p tag for privileged check + if err := event4678Allowed.Sign(allowedSigner); chk.E(err) { + t.Fatalf("Failed to sign event4678Allowed: %v", err) + } + + // Test policy loading + policy, err := New(policyJSONBytes) + if err != nil { + t.Fatalf("Failed to create policy: %v", err) + } + + // Verify policy loaded correctly + if len(policy.Rules) != 4 { + t.Errorf("Expected 4 rules, got %d", len(policy.Rules)) + } + + // Test policy checks directly + t.Run("policy checks", func(t *testing.T) { + // Test 1: Event 30520 with allowed pubkey should be allowed + allowed, err := policy.CheckPolicy("write", event30520Allowed, allowedSigner.Pub(), "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if !allowed { + t.Error("Expected event30520Allowed to be allowed") + } + + // Test 2: Event 30520 with unauthorized pubkey should be denied + allowed, err = policy.CheckPolicy("write", event30520Unauthorized, unauthorizedSigner.Pub(), "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if allowed { + t.Error("Expected event30520Unauthorized to be denied") + } + + // Test 3: Event 10306 should be readable by allowed user + allowed, err = policy.CheckPolicy("read", event10306Allowed, allowedSigner.Pub(), "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if !allowed { + t.Error("Expected event10306Allowed to be readable by allowed user") + } + + // Test 4: Event 10306 should NOT be readable by unauthorized user + allowed, err = policy.CheckPolicy("read", event10306Allowed, unauthorizedSigner.Pub(), "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if allowed { + t.Error("Expected event10306Allowed to be denied for unauthorized user") + } + + // Test 5: Event 10306 should NOT be readable without authentication + allowed, err = policy.CheckPolicy("read", event10306Allowed, nil, "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if allowed { + t.Error("Expected event10306Allowed to be denied without authentication (privileged)") + } + + // Test 6: Event 30520 should NOT be writable without authentication + allowed, err = policy.CheckPolicy("write", event30520Allowed, nil, "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if allowed { + t.Error("Expected event30520Allowed to be denied without authentication (privileged)") + } + + // Test 7: Event 4678 should fall back to default policy (allow) when script not running + allowed, err = policy.CheckPolicy("write", event4678Allowed, allowedSigner.Pub(), "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if !allowed { + t.Error("Expected event4678Allowed to be allowed when script not running (falls back to default)") + } + + // Test 8: Event 4678 should be denied without authentication (privileged check) + allowed, err = policy.CheckPolicy("write", event4678Allowed, nil, "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if allowed { + t.Error("Expected event4678Allowed to be denied without authentication (privileged)") + } + }) + + // Test with relay simulation (checking log output) + t.Run("relay simulation", func(t *testing.T) { + // Note: We can't easily capture log output in tests, so we just verify + // that policy checks work correctly + + // Simulate policy checks that would happen in relay + // First, publish events (simulate write checks) + checks := []struct { + name string + event *event.E + loggedInPubkey []byte + access string + shouldAllow bool + shouldLog string // Expected log message substring, empty means no specific log expected + }{ + { + name: "write 30520 with allowed pubkey", + event: event30520Allowed, + loggedInPubkey: allowedSigner.Pub(), + access: "write", + shouldAllow: true, + }, + { + name: "write 30520 with unauthorized pubkey", + event: event30520Unauthorized, + loggedInPubkey: unauthorizedSigner.Pub(), + access: "write", + shouldAllow: false, + }, + { + name: "read 10306 with allowed pubkey", + event: event10306Allowed, + loggedInPubkey: allowedSigner.Pub(), + access: "read", + shouldAllow: true, + }, + { + name: "read 10306 with unauthorized pubkey", + event: event10306Allowed, + loggedInPubkey: unauthorizedSigner.Pub(), + access: "read", + shouldAllow: false, + }, + { + name: "read 10306 without authentication", + event: event10306Allowed, + loggedInPubkey: nil, + access: "read", + shouldAllow: false, + }, + { + name: "write 30520 without authentication", + event: event30520Allowed, + loggedInPubkey: nil, + access: "write", + shouldAllow: false, + }, + { + name: "write 4678 with allowed pubkey", + event: event4678Allowed, + loggedInPubkey: allowedSigner.Pub(), + access: "write", + shouldAllow: true, + shouldLog: "", // Should not log "policy rule is inactive" if script is not configured + }, + } + + for _, check := range checks { + t.Run(check.name, func(t *testing.T) { + allowed, err := policy.CheckPolicy(check.access, check.event, check.loggedInPubkey, "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + if allowed != check.shouldAllow { + t.Errorf("Expected allowed=%v, got %v", check.shouldAllow, allowed) + } + }) + } + }) + + // Test event IDs are regenerated correctly after signing + t.Run("event ID regeneration", func(t *testing.T) { + // Create a new event, sign it, then verify ID is correct + testEvent := event.New() + testEvent.CreatedAt = time.Now().Unix() + testEvent.Kind = kind.K{K: 30520}.K + testEvent.Content = []byte("test content") + testEvent.Tags = tag.NewS() + + // Sign the event + if err := testEvent.Sign(allowedSigner); chk.E(err) { + t.Fatalf("Failed to sign test event: %v", err) + } + + // Verify event ID is correct (should be SHA256 of serialized event) + if len(testEvent.ID) != 32 { + t.Errorf("Expected event ID to be 32 bytes, got %d", len(testEvent.ID)) + } + + // Verify signature is correct + if len(testEvent.Sig) != 64 { + t.Errorf("Expected event signature to be 64 bytes, got %d", len(testEvent.Sig)) + } + + // Verify signature validates using event's Verify method + valid, err := testEvent.Verify() + if err != nil { + t.Errorf("Failed to verify signature: %v", err) + } + if !valid { + t.Error("Event signature verification failed") + } + }) + + // Test WebSocket client simulation (for future integration) + t.Run("websocket client simulation", func(t *testing.T) { + // This test simulates what would happen if we connected via WebSocket + // For now, we'll just verify the events can be serialized correctly + + events := []*event.E{ + event30520Allowed, + event30520Unauthorized, + event10306Allowed, + event4678Allowed, + } + + for i, ev := range events { + t.Run(fmt.Sprintf("event_%d", i), func(t *testing.T) { + // Serialize event + serialized := ev.Serialize() + if len(serialized) == 0 { + t.Error("Event serialization returned empty") + } + + // Verify event can be parsed back (simplified check) + if len(ev.ID) != 32 { + t.Errorf("Event ID length incorrect: %d", len(ev.ID)) + } + if len(ev.Pubkey) != 32 { + t.Errorf("Event pubkey length incorrect: %d", len(ev.Pubkey)) + } + if len(ev.Sig) != 64 { + t.Errorf("Event signature length incorrect: %d", len(ev.Sig)) + } + }) + } + }) +} + +// TestPolicyWithRelay creates a comprehensive test that simulates relay behavior +func TestPolicyWithRelay(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + // Generate keys + allowedSigner := &p256k.Signer{} + if err := allowedSigner.Generate(); chk.E(err) { + t.Fatalf("Failed to generate allowed signer: %v", err) + } + allowedPubkeyHex := hex.Enc(allowedSigner.Pub()) + + unauthorizedSigner := &p256k.Signer{} + if err := unauthorizedSigner.Generate(); chk.E(err) { + t.Fatalf("Failed to generate unauthorized signer: %v", err) + } + + // Create policy JSON + policyJSON := map[string]interface{}{ + "kind": map[string]interface{}{ + "whitelist": []int{4678, 10306, 30520, 30919}, + }, + "rules": map[string]interface{}{ + "10306": map[string]interface{}{ + "description": "End user whitelist changes", + "read_allow": []string{allowedPubkeyHex}, + "privileged": true, + }, + "30520": map[string]interface{}{ + "description": "Zenotp events", + "write_allow": []string{allowedPubkeyHex}, + "privileged": true, + }, + "30919": map[string]interface{}{ + "description": "Zenotp events", + "write_allow": []string{allowedPubkeyHex}, + "privileged": true, + }, + }, + } + + policyJSONBytes, err := json.Marshal(policyJSON) + if err != nil { + t.Fatalf("Failed to marshal policy JSON: %v", err) + } + + policy, err := New(policyJSONBytes) + if err != nil { + t.Fatalf("Failed to create policy: %v", err) + } + + // Create test event (kind 30520) with allowed pubkey + testEvent := event.New() + testEvent.CreatedAt = time.Now().Unix() + testEvent.Kind = kind.K{K: 30520}.K + testEvent.Content = []byte("test content") + testEvent.Tags = tag.NewS() + addPTag(testEvent, allowedSigner.Pub()) + if err := testEvent.Sign(allowedSigner); chk.E(err) { + t.Fatalf("Failed to sign test event: %v", err) + } + + // Test scenarios + scenarios := []struct { + name string + loggedInPubkey []byte + expectedResult bool + description string + }{ + { + name: "authenticated as allowed pubkey", + loggedInPubkey: allowedSigner.Pub(), + expectedResult: true, + description: "Should allow when authenticated as allowed pubkey", + }, + { + name: "unauthenticated", + loggedInPubkey: nil, + expectedResult: false, + description: "Should deny when not authenticated (privileged check)", + }, + { + name: "authenticated as different pubkey", + loggedInPubkey: unauthorizedSigner.Pub(), + expectedResult: false, + description: "Should deny when authenticated as different pubkey", + }, + } + + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + allowed, err := policy.CheckPolicy("write", testEvent, scenario.loggedInPubkey, "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + if allowed != scenario.expectedResult { + t.Errorf("%s: Expected allowed=%v, got %v", scenario.description, scenario.expectedResult, allowed) + } + }) + } + + // Test read access for kind 10306 + readEvent := event.New() + readEvent.CreatedAt = time.Now().Unix() + readEvent.Kind = kind.K{K: 10306}.K + readEvent.Content = []byte("test read event") + readEvent.Tags = tag.NewS() + addPTag(readEvent, allowedSigner.Pub()) + if err := readEvent.Sign(allowedSigner); chk.E(err) { + t.Fatalf("Failed to sign read event: %v", err) + } + + readScenarios := []struct { + name string + loggedInPubkey []byte + expectedResult bool + description string + }{ + { + name: "read authenticated as allowed pubkey", + loggedInPubkey: allowedSigner.Pub(), + expectedResult: true, + description: "Should allow read when authenticated as allowed pubkey", + }, + { + name: "read unauthenticated", + loggedInPubkey: nil, + expectedResult: false, + description: "Should deny read when not authenticated (privileged check)", + }, + { + name: "read authenticated as different pubkey", + loggedInPubkey: unauthorizedSigner.Pub(), + expectedResult: false, + description: "Should deny read when authenticated as different pubkey", + }, + } + + for _, scenario := range readScenarios { + t.Run(scenario.name, func(t *testing.T) { + allowed, err := policy.CheckPolicy("read", readEvent, scenario.loggedInPubkey, "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + if allowed != scenario.expectedResult { + t.Errorf("%s: Expected allowed=%v, got %v", scenario.description, scenario.expectedResult, allowed) + } + }) + } +} + diff --git a/pkg/policy/policy_test.go b/pkg/policy/policy_test.go index 77f75f9..b13febb 100644 --- a/pkg/policy/policy_test.go +++ b/pkg/policy/policy_test.go @@ -1495,3 +1495,363 @@ func TestDefaultPolicyLogicWithRules(t *testing.T) { t.Error("Expected kind 2 to be allowed (no rule, default policy is allow)") } } + +func TestPolicyFilterProcessing(t *testing.T) { + // Test policy filter processing using the provided filter JSON specification + filterJSON := []byte(`{ + "kind": { + "whitelist": [4678, 10306, 30520, 30919] + }, + "rules": { + "4678": { + "description": "Zenotp message events", + "script": "/home/orly/.config/ORLY/validate4678.js", + "privileged": true + }, + "10306": { + "description": "End user whitelist changes", + "read_allow": [ + "04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5" + ], + "privileged": true + }, + "30520": { + "description": "Zenotp events", + "write_allow": [ + "04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5" + ], + "privileged": true + }, + "30919": { + "description": "Zenotp events", + "write_allow": [ + "04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5" + ], + "privileged": true + } + } + }`) + + // Create policy from JSON + policy, err := New(filterJSON) + if err != nil { + t.Fatalf("Failed to create policy from JSON: %v", err) + } + + // Verify whitelist is set correctly + if len(policy.Kind.Whitelist) != 4 { + t.Errorf("Expected whitelist to have 4 kinds, got %d", len(policy.Kind.Whitelist)) + } + expectedKinds := map[int]bool{4678: true, 10306: true, 30520: true, 30919: true} + for _, kind := range policy.Kind.Whitelist { + if !expectedKinds[kind] { + t.Errorf("Unexpected kind in whitelist: %d", kind) + } + } + + // Verify rules are loaded correctly + if len(policy.Rules) != 4 { + t.Errorf("Expected 4 rules, got %d", len(policy.Rules)) + } + + // Verify rule 4678 (script-based) + rule4678, ok := policy.Rules[4678] + if !ok { + t.Fatal("Rule 4678 not found") + } + if rule4678.Description != "Zenotp message events" { + t.Errorf("Expected description 'Zenotp message events', got '%s'", rule4678.Description) + } + if rule4678.Script != "/home/orly/.config/ORLY/validate4678.js" { + t.Errorf("Expected script path '/home/orly/.config/ORLY/validate4678.js', got '%s'", rule4678.Script) + } + if !rule4678.Privileged { + t.Error("Expected rule 4678 to be privileged") + } + + // Verify rule 10306 (read_allow) + rule10306, ok := policy.Rules[10306] + if !ok { + t.Fatal("Rule 10306 not found") + } + if rule10306.Description != "End user whitelist changes" { + t.Errorf("Expected description 'End user whitelist changes', got '%s'", rule10306.Description) + } + if len(rule10306.ReadAllow) != 1 { + t.Errorf("Expected 1 read_allow pubkey, got %d", len(rule10306.ReadAllow)) + } + if rule10306.ReadAllow[0] != "04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5" { + t.Errorf("Expected read_allow pubkey '04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5', got '%s'", rule10306.ReadAllow[0]) + } + if !rule10306.Privileged { + t.Error("Expected rule 10306 to be privileged") + } + + // Verify rule 30520 (write_allow) + rule30520, ok := policy.Rules[30520] + if !ok { + t.Fatal("Rule 30520 not found") + } + if rule30520.Description != "Zenotp events" { + t.Errorf("Expected description 'Zenotp events', got '%s'", rule30520.Description) + } + if len(rule30520.WriteAllow) != 1 { + t.Errorf("Expected 1 write_allow pubkey, got %d", len(rule30520.WriteAllow)) + } + if rule30520.WriteAllow[0] != "04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5" { + t.Errorf("Expected write_allow pubkey '04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5', got '%s'", rule30520.WriteAllow[0]) + } + if !rule30520.Privileged { + t.Error("Expected rule 30520 to be privileged") + } + + // Verify rule 30919 (write_allow) + rule30919, ok := policy.Rules[30919] + if !ok { + t.Fatal("Rule 30919 not found") + } + if rule30919.Description != "Zenotp events" { + t.Errorf("Expected description 'Zenotp events', got '%s'", rule30919.Description) + } + if len(rule30919.WriteAllow) != 1 { + t.Errorf("Expected 1 write_allow pubkey, got %d", len(rule30919.WriteAllow)) + } + if rule30919.WriteAllow[0] != "04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5" { + t.Errorf("Expected write_allow pubkey '04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5', got '%s'", rule30919.WriteAllow[0]) + } + if !rule30919.Privileged { + t.Error("Expected rule 30919 to be privileged") + } + + // Test kind whitelist filtering + t.Run("kind whitelist filtering", func(t *testing.T) { + eventSigner, eventPubkey := generateTestKeypair(t) + _, loggedInPubkey := generateTestKeypair(t) + + // Test whitelisted kind (should pass kind check, but may fail other checks) + whitelistedEvent := createTestEvent(t, eventSigner, "test content", 4678) + addPTag(whitelistedEvent, loggedInPubkey) + allowed, err := policy.CheckPolicy("write", whitelistedEvent, loggedInPubkey, "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + // Should be allowed because script isn't running (falls back to default "allow") + // and privileged check passes (loggedInPubkey is in p tag) + if !allowed { + t.Error("Expected whitelisted kind to be allowed when script not running and privileged check passes") + } + + // Test non-whitelisted kind (should be denied) + nonWhitelistedEvent := createTestEvent(t, eventSigner, "test content", 1) + allowed, err = policy.CheckPolicy("write", nonWhitelistedEvent, eventPubkey, "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if allowed { + t.Error("Expected non-whitelisted kind to be denied") + } + }) + + // Test read access for kind 10306 + t.Run("read access for kind 10306", func(t *testing.T) { + allowedPubkeyHex := "04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5" + allowedPubkeyBytes, err := hex.Dec(allowedPubkeyHex) + if err != nil { + t.Fatalf("Failed to decode allowed pubkey: %v", err) + } + + // Create event with allowed pubkey using a temporary signer + // Note: We can't actually sign with just the pubkey, so we'll create the event + // with a random signer and then manually set the pubkey for testing + tempSigner, _ := generateTestKeypair(t) + event10306 := createTestEvent(t, tempSigner, "test content", 10306) + event10306.Pubkey = allowedPubkeyBytes + // Re-sign won't work without private key, but policy checks use the pubkey field + // So we'll test with the pubkey set correctly + + // Test read access with allowed pubkey (logged in user matches event pubkey) + allowed, err := policy.CheckPolicy("read", event10306, allowedPubkeyBytes, "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if !allowed { + t.Error("Expected event to be allowed for read access with allowed pubkey") + } + + // Test read access with non-allowed pubkey + _, unauthorizedPubkey := generateTestKeypair(t) + allowed, err = policy.CheckPolicy("read", event10306, unauthorizedPubkey, "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if allowed { + t.Error("Expected event to be denied for read access with non-allowed pubkey") + } + + // Test read access without authentication (privileged check) + allowed, err = policy.CheckPolicy("read", event10306, nil, "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if allowed { + t.Error("Expected event to be denied for read access without authentication (privileged)") + } + }) + + // Test write access for kind 30520 + t.Run("write access for kind 30520", func(t *testing.T) { + allowedPubkeyHex := "04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5" + allowedPubkeyBytes, err := hex.Dec(allowedPubkeyHex) + if err != nil { + t.Fatalf("Failed to decode allowed pubkey: %v", err) + } + + // Create event with allowed pubkey using a temporary signer + // We'll set the pubkey manually for testing since we don't have the private key + tempSigner, _ := generateTestKeypair(t) + event30520 := createTestEvent(t, tempSigner, "test content", 30520) + event30520.Pubkey = allowedPubkeyBytes + + // Test write access with allowed pubkey (event pubkey matches write_allow) + allowed, err := policy.CheckPolicy("write", event30520, allowedPubkeyBytes, "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if !allowed { + t.Error("Expected event to be allowed for write access with allowed pubkey") + } + + // Test write access with non-allowed pubkey + unauthorizedSigner, unauthorizedPubkey := generateTestKeypair(t) + unauthorizedEvent := createTestEvent(t, unauthorizedSigner, "test content", 30520) + allowed, err = policy.CheckPolicy("write", unauthorizedEvent, unauthorizedPubkey, "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if allowed { + t.Error("Expected event to be denied for write access with non-allowed pubkey") + } + + // Test write access without authentication (privileged check) + allowed, err = policy.CheckPolicy("write", event30520, nil, "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if allowed { + t.Error("Expected event to be denied for write access without authentication (privileged)") + } + }) + + // Test write access for kind 30919 + t.Run("write access for kind 30919", func(t *testing.T) { + allowedPubkeyHex := "04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5" + allowedPubkeyBytes, err := hex.Dec(allowedPubkeyHex) + if err != nil { + t.Fatalf("Failed to decode allowed pubkey: %v", err) + } + + // Create event with allowed pubkey using a temporary signer + // We'll set the pubkey manually for testing since we don't have the private key + tempSigner, _ := generateTestKeypair(t) + event30919 := createTestEvent(t, tempSigner, "test content", 30919) + event30919.Pubkey = allowedPubkeyBytes + + // Test write access with allowed pubkey (event pubkey matches write_allow) + allowed, err := policy.CheckPolicy("write", event30919, allowedPubkeyBytes, "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if !allowed { + t.Error("Expected event to be allowed for write access with allowed pubkey") + } + + // Test write access with non-allowed pubkey + unauthorizedSigner, unauthorizedPubkey := generateTestKeypair(t) + unauthorizedEvent := createTestEvent(t, unauthorizedSigner, "test content", 30919) + allowed, err = policy.CheckPolicy("write", unauthorizedEvent, unauthorizedPubkey, "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if allowed { + t.Error("Expected event to be denied for write access with non-allowed pubkey") + } + + // Test write access without authentication (privileged check) + allowed, err = policy.CheckPolicy("write", event30919, nil, "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if allowed { + t.Error("Expected event to be denied for write access without authentication (privileged)") + } + }) + + // Test privileged flag behavior with p tags + t.Run("privileged flag with p tags", func(t *testing.T) { + eventSigner, _ := generateTestKeypair(t) + _, loggedInPubkey := generateTestKeypair(t) + allowedPubkeyHex := "04eeb1ed409c0b9205e722f8bf1780f553b61876ef323aff16c9f80a9d8ee9f5" + allowedPubkeyBytes, err := hex.Dec(allowedPubkeyHex) + if err != nil { + t.Fatalf("Failed to decode allowed pubkey: %v", err) + } + + // Create event with allowed pubkey and logged-in pubkey in p tag + event30520 := createTestEvent(t, eventSigner, "test content", 30520) + event30520.Pubkey = allowedPubkeyBytes + addPTag(event30520, loggedInPubkey) + + // Test that event is allowed when logged-in pubkey is in p tag (privileged) + // and event pubkey matches write_allow + allowed, err := policy.CheckPolicy("write", event30520, loggedInPubkey, "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if !allowed { + t.Error("Expected event to be allowed when event pubkey matches write_allow and logged-in pubkey is in p tag") + } + + // Test that event is denied when logged-in pubkey is not in p tag and doesn't match event pubkey + event30520NoPTag := createTestEvent(t, eventSigner, "test content", 30520) + event30520NoPTag.Pubkey = allowedPubkeyBytes + allowed, err = policy.CheckPolicy("write", event30520NoPTag, loggedInPubkey, "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if allowed { + t.Error("Expected event to be denied when logged-in pubkey is not in p tag (privileged check fails)") + } + }) + + // Test script-based rule (kind 4678) + t.Run("script-based rule for kind 4678", func(t *testing.T) { + eventSigner, _ := generateTestKeypair(t) + _, loggedInPubkey := generateTestKeypair(t) + + event4678 := createTestEvent(t, eventSigner, "test content", 4678) + addPTag(event4678, loggedInPubkey) + + // Test with script not running (should fall back to default policy) + allowed, err := policy.CheckPolicy("write", event4678, loggedInPubkey, "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + // Should allow because default policy is "allow" and script is not running + // and privileged check passes (loggedInPubkey is in p tag) + if !allowed { + t.Error("Expected event to be allowed when script is not running (falls back to default 'allow') and privileged check passes") + } + + // Test without authentication (privileged check should fail) + allowed, err = policy.CheckPolicy("write", event4678, nil, "127.0.0.1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + // Should be denied because privileged check fails without authentication + // The privileged check happens in checkRulePolicy before script check + // So it should be denied even though script is not running + if allowed { + t.Error("Expected event to be denied without authentication (privileged check)") + } + }) +} diff --git a/pkg/version/version b/pkg/version/version index 8c20c52..014ec61 100644 --- a/pkg/version/version +++ b/pkg/version/version @@ -1 +1 @@ -v0.20.0 \ No newline at end of file +v0.20.2 \ No newline at end of file diff --git a/scripts/run-policy-filter-test.sh b/scripts/run-policy-filter-test.sh new file mode 100755 index 0000000..9b71a82 --- /dev/null +++ b/scripts/run-policy-filter-test.sh @@ -0,0 +1,198 @@ +#!/bin/bash +set -euo pipefail + +# Policy Filter Integration Test +# This script runs the relay with the example policy and tests event filtering + +# Config +PORT=${PORT:-34568} +URL=${URL:-ws://127.0.0.1:${PORT}} +LOG=/tmp/orly-policy-filter.out +PID=/tmp/orly-policy-filter.pid +DATADIR=$(mktemp -d) +CONFIG_DIR="$HOME/.config/ORLY_POLICY_TEST" + +cleanup() { + trap - EXIT + if [[ -f "$PID" ]]; then + kill -INT "$(cat "$PID")" 2>/dev/null || true + rm -f "$PID" + fi + rm -rf "$DATADIR" + rm -rf "$CONFIG_DIR" +} +trap cleanup EXIT + +echo "🧪 Policy Filter Integration Test" +echo "==================================" + +# Create config directory +mkdir -p "$CONFIG_DIR" + +# Generate keys using Go helper +echo "🔑 Generating test keys..." +KEYGEN_TMP=$(mktemp) +cat > "$KEYGEN_TMP.go" <<'EOF' +package main + +import ( + "encoding/json" + "fmt" + "next.orly.dev/pkg/crypto/p256k" + "next.orly.dev/pkg/encoders/hex" +) + +func main() { + // Generate allowed signer + allowedSigner := &p256k.Signer{} + if err := allowedSigner.Generate(); err != nil { + panic(err) + } + allowedPubkeyHex := hex.Enc(allowedSigner.Pub()) + allowedSecHex := hex.Enc(allowedSigner.Sec()) + + // Generate unauthorized signer + unauthorizedSigner := &p256k.Signer{} + if err := unauthorizedSigner.Generate(); err != nil { + panic(err) + } + unauthorizedPubkeyHex := hex.Enc(unauthorizedSigner.Pub()) + unauthorizedSecHex := hex.Enc(unauthorizedSigner.Sec()) + + result := map[string]string{ + "allowedPubkey": allowedPubkeyHex, + "allowedSec": allowedSecHex, + "unauthorizedPubkey": unauthorizedPubkeyHex, + "unauthorizedSec": unauthorizedSecHex, + } + + jsonBytes, _ := json.Marshal(result) + fmt.Println(string(jsonBytes)) +} +EOF + +# Run from the project root directory +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +cd "$PROJECT_ROOT" +KEYS=$(go run -tags=cgo "$KEYGEN_TMP.go" 2>&1 | grep -E '^\{.*\}$' || true) +rm -f "$KEYGEN_TMP.go" +cd - > /dev/null + +ALLOWED_PUBKEY=$(echo "$KEYS" | jq -r '.allowedPubkey') +ALLOWED_SEC=$(echo "$KEYS" | jq -r '.allowedSec') +UNAUTHORIZED_PUBKEY=$(echo "$KEYS" | jq -r '.unauthorizedPubkey') +UNAUTHORIZED_SEC=$(echo "$KEYS" | jq -r '.unauthorizedSec') + +echo "✅ Generated keys:" +echo " Allowed pubkey: $ALLOWED_PUBKEY" +echo " Unauthorized pubkey: $UNAUTHORIZED_PUBKEY" + +# Create policy JSON with generated keys +echo "📝 Creating policy.json..." +cat > "$CONFIG_DIR/policy.json" <"$LOG" 2>&1 & echo $! >"$PID" + +# Wait for relay to start +sleep 3 +if ! ps -p "$(cat "$PID")" >/dev/null 2>&1; then + echo "❌ Relay failed to start; logs:" >&2 + sed -n '1,200p' "$LOG" >&2 + exit 1 +fi + +echo "✅ Relay started (PID: $(cat "$PID"))" + +# Build test client +echo "🔨 Building test client..." +go build -o cmd/policyfiltertest/policyfiltertest ./cmd/policyfiltertest + +# Export keys for test client +export ALLOWED_PUBKEY +export ALLOWED_SEC +export UNAUTHORIZED_PUBKEY +export UNAUTHORIZED_SEC + +# Run tests +echo "🧪 Running policy filter tests..." +set +e +cmd/policyfiltertest/policyfiltertest -url "${URL}" -allowed-pubkey "$ALLOWED_PUBKEY" -allowed-sec "$ALLOWED_SEC" -unauthorized-pubkey "$UNAUTHORIZED_PUBKEY" -unauthorized-sec "$UNAUTHORIZED_SEC" +TEST_RESULT=$? +set -e + +# Check logs for "policy rule is inactive" messages +echo "📋 Checking logs for policy rule inactivity..." +if grep -q "policy rule is inactive" "$LOG"; then + echo "⚠️ WARNING: Found 'policy rule is inactive' messages in logs" + grep "policy rule is inactive" "$LOG" | head -5 +else + echo "✅ No 'policy rule is inactive' messages found (good)" +fi + +# Check logs for policy filtered events +echo "📋 Checking logs for policy filtered events..." +if grep -q "policy filtered out event" "$LOG"; then + echo "✅ Found policy filtered events (expected):" + grep "policy filtered out event" "$LOG" | head -5 +fi + +if [ $TEST_RESULT -eq 0 ]; then + echo "✅ All tests passed!" + exit 0 +else + echo "❌ Tests failed with exit code $TEST_RESULT" + echo "📋 Last 50 lines of relay log:" + tail -50 "$LOG" + exit $TEST_RESULT +fi + diff --git a/scripts/secp256k1 b/scripts/secp256k1 deleted file mode 160000 index 0cdc758..0000000 --- a/scripts/secp256k1 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0cdc758a56360bf58a851fe91085a327ec97685a diff --git a/scripts/sprocket/SPROCKET_TEST_README.md b/scripts/sprocket/SPROCKET_TEST_README.md old mode 100644 new mode 100755 diff --git a/scripts/sprocket/test-sprocket-complete.sh b/scripts/sprocket/test-sprocket-complete.sh old mode 100644 new mode 100755 diff --git a/scripts/sprocket/test-sprocket-demo.sh b/scripts/sprocket/test-sprocket-demo.sh old mode 100644 new mode 100755 diff --git a/scripts/sprocket/test-sprocket-example.sh b/scripts/sprocket/test-sprocket-example.sh old mode 100644 new mode 100755 diff --git a/scripts/sprocket/test-sprocket-final.sh b/scripts/sprocket/test-sprocket-final.sh old mode 100644 new mode 100755 diff --git a/scripts/sprocket/test-sprocket-manual.sh b/scripts/sprocket/test-sprocket-manual.sh old mode 100644 new mode 100755 diff --git a/scripts/sprocket/test-sprocket-simple.sh b/scripts/sprocket/test-sprocket-simple.sh old mode 100644 new mode 100755 diff --git a/scripts/sprocket/test-sprocket-working.sh b/scripts/sprocket/test-sprocket-working.sh old mode 100644 new mode 100755 diff --git a/scripts/sprocket/test-sprocket.py b/scripts/sprocket/test-sprocket.py old mode 100644 new mode 100755 diff --git a/scripts/sprocket/test-sprocket.sh b/scripts/sprocket/test-sprocket.sh old mode 100644 new mode 100755