policy_integration_test.go raw
1 package policy
2
3 import (
4 "encoding/json"
5 "fmt"
6 "os"
7 "path/filepath"
8 "testing"
9 "time"
10
11 "next.orly.dev/pkg/lol/chk"
12 "next.orly.dev/pkg/nostr/encoders/event"
13 "next.orly.dev/pkg/nostr/encoders/hex"
14 "next.orly.dev/pkg/nostr/encoders/kind"
15 "next.orly.dev/pkg/nostr/encoders/tag"
16 "next.orly.dev/pkg/nostr/interfaces/signer/p8k"
17 )
18
19 // TestPolicyIntegration runs the relay with policy enabled and tests event filtering
20 func TestPolicyIntegration(t *testing.T) {
21 if testing.Short() {
22 t.Skip("skipping integration test")
23 }
24
25 // Generate test keys
26 allowedSigner := p8k.MustNew()
27 if err := allowedSigner.Generate(); chk.E(err) {
28 t.Fatalf("Failed to generate allowed signer: %v", err)
29 }
30 allowedPubkeyHex := hex.Enc(allowedSigner.Pub())
31
32 unauthorizedSigner := p8k.MustNew()
33 if err := unauthorizedSigner.Generate(); chk.E(err) {
34 t.Fatalf("Failed to generate unauthorized signer: %v", err)
35 }
36
37 // Create temporary directory for policy config
38 tempDir := t.TempDir()
39 configDir := filepath.Join(tempDir, "ORLY_TEST")
40 if err := os.MkdirAll(configDir, 0755); chk.E(err) {
41 t.Fatalf("Failed to create config directory: %v", err)
42 }
43
44 // Create policy JSON with generated keys
45 policyJSON := map[string]interface{}{
46 "kind": map[string]interface{}{
47 "whitelist": []int{4678, 10306, 30520, 30919},
48 },
49 "rules": map[string]interface{}{
50 "4678": map[string]interface{}{
51 "description": "Zenotp message events",
52 "script": filepath.Join(configDir, "validate4678.js"), // Won't exist, should fall back to default
53 "privileged": true,
54 },
55 "10306": map[string]interface{}{
56 "description": "End user whitelist changes",
57 "read_allow": []string{allowedPubkeyHex},
58 "privileged": true,
59 },
60 "30520": map[string]interface{}{
61 "description": "Zenotp events",
62 "write_allow": []string{allowedPubkeyHex},
63 "privileged": true,
64 },
65 "30919": map[string]interface{}{
66 "description": "Zenotp events",
67 "write_allow": []string{allowedPubkeyHex},
68 "privileged": true,
69 },
70 },
71 }
72
73 policyJSONBytes, err := json.MarshalIndent(policyJSON, "", " ")
74 if err != nil {
75 t.Fatalf("Failed to marshal policy JSON: %v", err)
76 }
77
78 policyPath := filepath.Join(configDir, "policy.json")
79 if err := os.WriteFile(policyPath, policyJSONBytes, 0644); chk.E(err) {
80 t.Fatalf("Failed to write policy file: %v", err)
81 }
82
83 // Create events with proper signatures
84 // Event 1: Kind 30520 with allowed pubkey (should be allowed)
85 event30520Allowed := event.New()
86 event30520Allowed.CreatedAt = time.Now().Unix()
87 event30520Allowed.Kind = kind.K{K: 30520}.K
88 event30520Allowed.Content = []byte("test event 30520")
89 event30520Allowed.Tags = tag.NewS()
90 addPTag(event30520Allowed, allowedSigner.Pub()) // Add p tag for privileged check
91 if err := event30520Allowed.Sign(allowedSigner); chk.E(err) {
92 t.Fatalf("Failed to sign event30520Allowed: %v", err)
93 }
94
95 // Event 2: Kind 30520 with unauthorized pubkey (should be denied)
96 event30520Unauthorized := event.New()
97 event30520Unauthorized.CreatedAt = time.Now().Unix()
98 event30520Unauthorized.Kind = kind.K{K: 30520}.K
99 event30520Unauthorized.Content = []byte("test event 30520 unauthorized")
100 event30520Unauthorized.Tags = tag.NewS()
101 if err := event30520Unauthorized.Sign(unauthorizedSigner); chk.E(err) {
102 t.Fatalf("Failed to sign event30520Unauthorized: %v", err)
103 }
104
105 // Event 3: Kind 10306 with allowed pubkey (should be readable by allowed user)
106 event10306Allowed := event.New()
107 event10306Allowed.CreatedAt = time.Now().Unix()
108 event10306Allowed.Kind = kind.K{K: 10306}.K
109 event10306Allowed.Content = []byte("test event 10306")
110 event10306Allowed.Tags = tag.NewS()
111 addPTag(event10306Allowed, allowedSigner.Pub()) // Add p tag for privileged check
112 if err := event10306Allowed.Sign(allowedSigner); chk.E(err) {
113 t.Fatalf("Failed to sign event10306Allowed: %v", err)
114 }
115
116 // Event 4: Kind 4678 with allowed pubkey (script-based, should fall back to default)
117 event4678Allowed := event.New()
118 event4678Allowed.CreatedAt = time.Now().Unix()
119 event4678Allowed.Kind = kind.K{K: 4678}.K
120 event4678Allowed.Content = []byte("test event 4678")
121 event4678Allowed.Tags = tag.NewS()
122 addPTag(event4678Allowed, allowedSigner.Pub()) // Add p tag for privileged check
123 if err := event4678Allowed.Sign(allowedSigner); chk.E(err) {
124 t.Fatalf("Failed to sign event4678Allowed: %v", err)
125 }
126
127 // Test policy loading
128 policy, err := New(policyJSONBytes)
129 if err != nil {
130 t.Fatalf("Failed to create policy: %v", err)
131 }
132
133 // Verify policy loaded correctly
134 if len(policy.rules) != 4 {
135 t.Errorf("Expected 4 rules, got %d", len(policy.rules))
136 }
137
138 // Test policy checks directly
139 t.Run("policy checks", func(t *testing.T) {
140 // Test 1: Event 30520 with allowed pubkey should be allowed
141 allowed, err := policy.CheckPolicy("write", event30520Allowed, allowedSigner.Pub(), "127.0.0.1")
142 if err != nil {
143 t.Errorf("Unexpected error: %v", err)
144 }
145 if !allowed {
146 t.Error("Expected event30520Allowed to be allowed")
147 }
148
149 // Test 2: Event 30520 with unauthorized pubkey should be denied
150 allowed, err = policy.CheckPolicy("write", event30520Unauthorized, unauthorizedSigner.Pub(), "127.0.0.1")
151 if err != nil {
152 t.Errorf("Unexpected error: %v", err)
153 }
154 if allowed {
155 t.Error("Expected event30520Unauthorized to be denied")
156 }
157
158 // Test 3: Event 10306 should be readable by allowed user
159 allowed, err = policy.CheckPolicy("read", event10306Allowed, allowedSigner.Pub(), "127.0.0.1")
160 if err != nil {
161 t.Errorf("Unexpected error: %v", err)
162 }
163 if !allowed {
164 t.Error("Expected event10306Allowed to be readable by allowed user")
165 }
166
167 // Test 4: Event 10306 should NOT be readable by unauthorized user
168 allowed, err = policy.CheckPolicy("read", event10306Allowed, unauthorizedSigner.Pub(), "127.0.0.1")
169 if err != nil {
170 t.Errorf("Unexpected error: %v", err)
171 }
172 if allowed {
173 t.Error("Expected event10306Allowed to be denied for unauthorized user")
174 }
175
176 // Test 5: Event 10306 should NOT be readable without authentication
177 allowed, err = policy.CheckPolicy("read", event10306Allowed, nil, "127.0.0.1")
178 if err != nil {
179 t.Errorf("Unexpected error: %v", err)
180 }
181 if allowed {
182 t.Error("Expected event10306Allowed to be denied without authentication (privileged)")
183 }
184
185 // Test 6: Event 30520 should NOT be writable without authentication
186 allowed, err = policy.CheckPolicy("write", event30520Allowed, nil, "127.0.0.1")
187 if err != nil {
188 t.Errorf("Unexpected error: %v", err)
189 }
190 if allowed {
191 t.Error("Expected event30520Allowed to be denied without authentication (privileged)")
192 }
193
194 // Test 7: Event 4678 should fall back to default policy (allow) when script not running
195 allowed, err = policy.CheckPolicy("write", event4678Allowed, allowedSigner.Pub(), "127.0.0.1")
196 if err != nil {
197 t.Errorf("Unexpected error: %v", err)
198 }
199 if !allowed {
200 t.Error("Expected event4678Allowed to be allowed when script not running (falls back to default)")
201 }
202
203 // Test 8: Event 4678 write should be allowed without authentication (privileged doesn't affect write)
204 allowed, err = policy.CheckPolicy("write", event4678Allowed, nil, "127.0.0.1")
205 if err != nil {
206 t.Errorf("Unexpected error: %v", err)
207 }
208 if !allowed {
209 t.Error("Expected event4678Allowed to be allowed without authentication (privileged doesn't affect write operations)")
210 }
211 })
212
213 // Test with relay simulation (checking log output)
214 t.Run("relay simulation", func(t *testing.T) {
215 // Note: We can't easily capture log output in tests, so we just verify
216 // that policy checks work correctly
217
218 // Simulate policy checks that would happen in relay
219 // First, publish events (simulate write checks)
220 checks := []struct {
221 name string
222 event *event.E
223 loggedInPubkey []byte
224 access string
225 shouldAllow bool
226 shouldLog string // Expected log message substring, empty means no specific log expected
227 }{
228 {
229 name: "write 30520 with allowed pubkey",
230 event: event30520Allowed,
231 loggedInPubkey: allowedSigner.Pub(),
232 access: "write",
233 shouldAllow: true,
234 },
235 {
236 name: "write 30520 with unauthorized pubkey",
237 event: event30520Unauthorized,
238 loggedInPubkey: unauthorizedSigner.Pub(),
239 access: "write",
240 shouldAllow: false,
241 },
242 {
243 name: "read 10306 with allowed pubkey",
244 event: event10306Allowed,
245 loggedInPubkey: allowedSigner.Pub(),
246 access: "read",
247 shouldAllow: true,
248 },
249 {
250 name: "read 10306 with unauthorized pubkey",
251 event: event10306Allowed,
252 loggedInPubkey: unauthorizedSigner.Pub(),
253 access: "read",
254 shouldAllow: false,
255 },
256 {
257 name: "read 10306 without authentication",
258 event: event10306Allowed,
259 loggedInPubkey: nil,
260 access: "read",
261 shouldAllow: false,
262 },
263 {
264 name: "write 30520 without authentication",
265 event: event30520Allowed,
266 loggedInPubkey: nil,
267 access: "write",
268 shouldAllow: false,
269 },
270 {
271 name: "write 4678 with allowed pubkey",
272 event: event4678Allowed,
273 loggedInPubkey: allowedSigner.Pub(),
274 access: "write",
275 shouldAllow: true,
276 shouldLog: "", // Should not log "policy rule is inactive" if script is not configured
277 },
278 }
279
280 for _, check := range checks {
281 t.Run(check.name, func(t *testing.T) {
282 allowed, err := policy.CheckPolicy(check.access, check.event, check.loggedInPubkey, "127.0.0.1")
283 if err != nil {
284 t.Errorf("Unexpected error: %v", err)
285 return
286 }
287 if allowed != check.shouldAllow {
288 t.Errorf("Expected allowed=%v, got %v", check.shouldAllow, allowed)
289 }
290 })
291 }
292 })
293
294 // Test event IDs are regenerated correctly after signing
295 t.Run("event ID regeneration", func(t *testing.T) {
296 // Create a new event, sign it, then verify ID is correct
297 testEvent := event.New()
298 testEvent.CreatedAt = time.Now().Unix()
299 testEvent.Kind = kind.K{K: 30520}.K
300 testEvent.Content = []byte("test content")
301 testEvent.Tags = tag.NewS()
302
303 // Sign the event
304 if err := testEvent.Sign(allowedSigner); chk.E(err) {
305 t.Fatalf("Failed to sign test event: %v", err)
306 }
307
308 // Verify event ID is correct (should be SHA256 of serialized event)
309 if len(testEvent.ID) != 32 {
310 t.Errorf("Expected event ID to be 32 bytes, got %d", len(testEvent.ID))
311 }
312
313 // Verify signature is correct
314 if len(testEvent.Sig) != 64 {
315 t.Errorf("Expected event signature to be 64 bytes, got %d", len(testEvent.Sig))
316 }
317
318 // Verify signature validates using event's Verify method
319 valid, err := testEvent.Verify()
320 if err != nil {
321 t.Errorf("Failed to verify signature: %v", err)
322 }
323 if !valid {
324 t.Error("Event signature verification failed")
325 }
326 })
327
328 // Test WebSocket client simulation (for future integration)
329 t.Run("websocket client simulation", func(t *testing.T) {
330 // This test simulates what would happen if we connected via WebSocket
331 // For now, we'll just verify the events can be serialized correctly
332
333 events := []*event.E{
334 event30520Allowed,
335 event30520Unauthorized,
336 event10306Allowed,
337 event4678Allowed,
338 }
339
340 for i, ev := range events {
341 t.Run(fmt.Sprintf("event_%d", i), func(t *testing.T) {
342 // Serialize event
343 serialized := ev.Serialize()
344 if len(serialized) == 0 {
345 t.Error("Event serialization returned empty")
346 }
347
348 // Verify event can be parsed back (simplified check)
349 if len(ev.ID) != 32 {
350 t.Errorf("Event ID length incorrect: %d", len(ev.ID))
351 }
352 if len(ev.Pubkey) != 32 {
353 t.Errorf("Event pubkey length incorrect: %d", len(ev.Pubkey))
354 }
355 if len(ev.Sig) != 64 {
356 t.Errorf("Event signature length incorrect: %d", len(ev.Sig))
357 }
358 })
359 }
360 })
361 }
362
363 // TestPolicyWithRelay creates a comprehensive test that simulates relay behavior
364 func TestPolicyWithRelay(t *testing.T) {
365 if testing.Short() {
366 t.Skip("skipping integration test")
367 }
368
369 // Generate keys
370 allowedSigner := p8k.MustNew()
371 if err := allowedSigner.Generate(); chk.E(err) {
372 t.Fatalf("Failed to generate allowed signer: %v", err)
373 }
374 allowedPubkeyHex := hex.Enc(allowedSigner.Pub())
375
376 unauthorizedSigner := p8k.MustNew()
377 if err := unauthorizedSigner.Generate(); chk.E(err) {
378 t.Fatalf("Failed to generate unauthorized signer: %v", err)
379 }
380
381 // Create policy JSON
382 policyJSON := map[string]interface{}{
383 "kind": map[string]interface{}{
384 "whitelist": []int{4678, 10306, 30520, 30919},
385 },
386 "rules": map[string]interface{}{
387 "10306": map[string]interface{}{
388 "description": "End user whitelist changes",
389 "read_allow": []string{allowedPubkeyHex},
390 "privileged": true,
391 },
392 "30520": map[string]interface{}{
393 "description": "Zenotp events",
394 "write_allow": []string{allowedPubkeyHex},
395 "privileged": true,
396 },
397 "30919": map[string]interface{}{
398 "description": "Zenotp events",
399 "write_allow": []string{allowedPubkeyHex},
400 "privileged": true,
401 },
402 },
403 }
404
405 policyJSONBytes, err := json.Marshal(policyJSON)
406 if err != nil {
407 t.Fatalf("Failed to marshal policy JSON: %v", err)
408 }
409
410 policy, err := New(policyJSONBytes)
411 if err != nil {
412 t.Fatalf("Failed to create policy: %v", err)
413 }
414
415 // Create test event (kind 30520) with allowed pubkey
416 testEvent := event.New()
417 testEvent.CreatedAt = time.Now().Unix()
418 testEvent.Kind = kind.K{K: 30520}.K
419 testEvent.Content = []byte("test content")
420 testEvent.Tags = tag.NewS()
421 addPTag(testEvent, allowedSigner.Pub())
422 if err := testEvent.Sign(allowedSigner); chk.E(err) {
423 t.Fatalf("Failed to sign test event: %v", err)
424 }
425
426 // Test scenarios
427 scenarios := []struct {
428 name string
429 loggedInPubkey []byte
430 expectedResult bool
431 description string
432 }{
433 {
434 name: "authenticated as allowed pubkey",
435 loggedInPubkey: allowedSigner.Pub(),
436 expectedResult: true,
437 description: "Should allow when authenticated as allowed pubkey",
438 },
439 {
440 name: "unauthenticated",
441 loggedInPubkey: nil,
442 expectedResult: false,
443 description: "Should deny when not authenticated (privileged check)",
444 },
445 {
446 name: "authenticated as different pubkey",
447 loggedInPubkey: unauthorizedSigner.Pub(),
448 expectedResult: false,
449 description: "Should deny when authenticated as different pubkey",
450 },
451 }
452
453 for _, scenario := range scenarios {
454 t.Run(scenario.name, func(t *testing.T) {
455 allowed, err := policy.CheckPolicy("write", testEvent, scenario.loggedInPubkey, "127.0.0.1")
456 if err != nil {
457 t.Errorf("Unexpected error: %v", err)
458 return
459 }
460 if allowed != scenario.expectedResult {
461 t.Errorf("%s: Expected allowed=%v, got %v", scenario.description, scenario.expectedResult, allowed)
462 }
463 })
464 }
465
466 // Test read access for kind 10306
467 readEvent := event.New()
468 readEvent.CreatedAt = time.Now().Unix()
469 readEvent.Kind = kind.K{K: 10306}.K
470 readEvent.Content = []byte("test read event")
471 readEvent.Tags = tag.NewS()
472 addPTag(readEvent, allowedSigner.Pub())
473 if err := readEvent.Sign(allowedSigner); chk.E(err) {
474 t.Fatalf("Failed to sign read event: %v", err)
475 }
476
477 readScenarios := []struct {
478 name string
479 loggedInPubkey []byte
480 expectedResult bool
481 description string
482 }{
483 {
484 name: "read authenticated as allowed pubkey",
485 loggedInPubkey: allowedSigner.Pub(),
486 expectedResult: true,
487 description: "Should allow read when authenticated as allowed pubkey",
488 },
489 {
490 name: "read unauthenticated",
491 loggedInPubkey: nil,
492 expectedResult: false,
493 description: "Should deny read when not authenticated (privileged check)",
494 },
495 {
496 name: "read authenticated as different pubkey",
497 loggedInPubkey: unauthorizedSigner.Pub(),
498 expectedResult: false,
499 description: "Should deny read when authenticated as different pubkey",
500 },
501 }
502
503 for _, scenario := range readScenarios {
504 t.Run(scenario.name, func(t *testing.T) {
505 allowed, err := policy.CheckPolicy("read", readEvent, scenario.loggedInPubkey, "127.0.0.1")
506 if err != nil {
507 t.Errorf("Unexpected error: %v", err)
508 return
509 }
510 if allowed != scenario.expectedResult {
511 t.Errorf("%s: Expected allowed=%v, got %v", scenario.description, scenario.expectedResult, allowed)
512 }
513 })
514 }
515 }
516