main.go raw
1 package main
2
3 import (
4 "context"
5 "flag"
6 "fmt"
7 "os"
8 "strings"
9 "time"
10
11 "next.orly.dev/pkg/lol/chk"
12 "next.orly.dev/pkg/lol/log"
13 "next.orly.dev/pkg/nostr/interfaces/signer/p8k"
14 "next.orly.dev/pkg/nostr/encoders/event"
15 "next.orly.dev/pkg/nostr/encoders/filter"
16 "next.orly.dev/pkg/nostr/encoders/hex"
17 "next.orly.dev/pkg/nostr/encoders/kind"
18 "next.orly.dev/pkg/nostr/encoders/tag"
19 "next.orly.dev/pkg/nostr/ws"
20 )
21
22 func main() {
23 var err error
24 url := flag.String("url", "ws://127.0.0.1:34568", "relay websocket URL")
25 allowedPubkeyHex := flag.String("allowed-pubkey", "", "hex-encoded allowed pubkey")
26 allowedSecHex := flag.String("allowed-sec", "", "hex-encoded allowed secret key")
27 unauthorizedPubkeyHex := flag.String("unauthorized-pubkey", "", "hex-encoded unauthorized pubkey")
28 unauthorizedSecHex := flag.String("unauthorized-sec", "", "hex-encoded unauthorized secret key")
29 timeout := flag.Duration("timeout", 10*time.Second, "operation timeout")
30 flag.Parse()
31
32 if *allowedPubkeyHex == "" || *allowedSecHex == "" {
33 log.E.F("required flags: -allowed-pubkey and -allowed-sec")
34 os.Exit(1)
35 }
36 if *unauthorizedPubkeyHex == "" || *unauthorizedSecHex == "" {
37 log.E.F("required flags: -unauthorized-pubkey and -unauthorized-sec")
38 os.Exit(1)
39 }
40
41 // Decode keys
42 allowedSecBytes, err := hex.Dec(*allowedSecHex)
43 if err != nil {
44 log.E.F("failed to decode allowed secret key: %v", err)
45 os.Exit(1)
46 }
47 var allowedSigner *p8k.Signer
48 if allowedSigner, err = p8k.New(); chk.E(err) {
49 log.E.F("failed to create allowed signer: %v", err)
50 os.Exit(1)
51 }
52 if err = allowedSigner.InitSec(allowedSecBytes); chk.E(err) {
53 log.E.F("failed to initialize allowed signer: %v", err)
54 os.Exit(1)
55 }
56
57 unauthorizedSecBytes, err := hex.Dec(*unauthorizedSecHex)
58 if err != nil {
59 log.E.F("failed to decode unauthorized secret key: %v", err)
60 os.Exit(1)
61 }
62 var unauthorizedSigner *p8k.Signer
63 if unauthorizedSigner, err = p8k.New(); chk.E(err) {
64 log.E.F("failed to create unauthorized signer: %v", err)
65 os.Exit(1)
66 }
67 if err = unauthorizedSigner.InitSec(unauthorizedSecBytes); chk.E(err) {
68 log.E.F("failed to initialize unauthorized signer: %v", err)
69 os.Exit(1)
70 }
71
72 ctx, cancel := context.WithTimeout(context.Background(), *timeout)
73 defer cancel()
74
75 // Test 1: Authenticated as allowed pubkey - should work
76 fmt.Println("Test 1: Publishing event 30520 with allowed pubkey (authenticated)...")
77 if err := testWriteEvent(ctx, *url, 30520, allowedSigner, allowedSigner); err != nil {
78 fmt.Printf("❌ FAILED: %v\n", err)
79 os.Exit(1)
80 }
81 fmt.Println("✅ PASSED: Event published successfully")
82
83 // Test 2: Authenticated as allowed pubkey, then read event 10306 - should work
84 // First publish an event, then read it
85 fmt.Println("\nTest 2: Publishing and reading event 10306 with allowed pubkey (authenticated)...")
86 if err := testWriteEvent(ctx, *url, 10306, allowedSigner, allowedSigner); err != nil {
87 fmt.Printf("❌ FAILED to publish: %v\n", err)
88 os.Exit(1)
89 }
90 if err := testReadEvent(ctx, *url, 10306, allowedSigner); err != nil {
91 fmt.Printf("❌ FAILED to read: %v\n", err)
92 os.Exit(1)
93 }
94 fmt.Println("✅ PASSED: Event readable by allowed user")
95
96 // Test 3: Unauthenticated request - should be blocked
97 fmt.Println("\nTest 3: Publishing event 30520 without authentication...")
98 if err := testWriteEventUnauthenticated(ctx, *url, 30520, allowedSigner); err != nil {
99 fmt.Printf("✅ PASSED: Event correctly blocked (expected): %v\n", err)
100 } else {
101 fmt.Println("❌ FAILED: Event was allowed when it should have been blocked")
102 os.Exit(1)
103 }
104
105 // Test 4: Authenticated as unauthorized pubkey - should be blocked
106 fmt.Println("\nTest 4: Publishing event 30520 with unauthorized pubkey...")
107 if err := testWriteEvent(ctx, *url, 30520, unauthorizedSigner, unauthorizedSigner); err != nil {
108 fmt.Printf("✅ PASSED: Event correctly blocked (expected): %v\n", err)
109 } else {
110 fmt.Println("❌ FAILED: Event was allowed when it should have been blocked")
111 os.Exit(1)
112 }
113
114 // Test 5: Read event 10306 without authentication - should be blocked
115 // Event was published in test 2, so it exists in the database
116 fmt.Println("\nTest 5: Reading event 10306 without authentication (should be blocked)...")
117 // Wait a bit to ensure event is stored
118 time.Sleep(500 * time.Millisecond)
119 // If no error is returned, that means no events were received (which is correct)
120 // If an error is returned, it means an event was received (which is wrong)
121 if err := testReadEventUnauthenticated(ctx, *url, 10306); err != nil {
122 // If we got an error about receiving an event, that's a failure
123 if strings.Contains(err.Error(), "unexpected event received") {
124 fmt.Printf("❌ FAILED: %v\n", err)
125 os.Exit(1)
126 }
127 // Other errors (like connection errors) are also failures
128 fmt.Printf("❌ FAILED: Unexpected error: %v\n", err)
129 os.Exit(1)
130 }
131 fmt.Println("✅ PASSED: No events received (correctly filtered by policy)")
132
133 // Test 6: Read event 10306 with unauthorized pubkey - should be blocked
134 fmt.Println("\nTest 6: Reading event 10306 with unauthorized pubkey (should be blocked)...")
135 // If no error is returned, that means no events were received (which is correct)
136 // If an error is returned about receiving an event, that's a failure
137 if err := testReadEvent(ctx, *url, 10306, unauthorizedSigner); err != nil {
138 // Connection/subscription errors are failures
139 fmt.Printf("❌ FAILED: Unexpected error: %v\n", err)
140 os.Exit(1)
141 }
142 fmt.Println("✅ PASSED: No events received (correctly filtered by policy)")
143
144 fmt.Println("\n✅ All tests passed!")
145 }
146
147 func testWriteEvent(ctx context.Context, url string, kindNum uint16, eventSigner, authSigner *p8k.Signer) error {
148 rl, err := ws.RelayConnect(ctx, url)
149 if err != nil {
150 return fmt.Errorf("connect error: %w", err)
151 }
152 defer rl.Close()
153
154 // Send a REQ first to trigger AUTH challenge (when AuthToWrite is enabled)
155 // This is needed because challenges are sent on REQ, not on connect
156 limit := uint(1)
157 ff := filter.NewS(&filter.F{
158 Kinds: kind.NewS(kind.New(kindNum)),
159 Limit: &limit,
160 })
161 sub, err := rl.Subscribe(ctx, ff)
162 if err != nil {
163 return fmt.Errorf("subscription error (may be expected): %w", err)
164 }
165 // Wait a bit for challenge to arrive
166 time.Sleep(500 * time.Millisecond)
167 sub.Unsub()
168
169 // Authenticate
170 if err = rl.Auth(ctx, authSigner); err != nil {
171 return fmt.Errorf("auth error: %w", err)
172 }
173
174 // Create and sign event
175 ev := &event.E{
176 CreatedAt: time.Now().Unix(),
177 Kind: kind.K{K: kindNum}.K,
178 Tags: tag.NewS(),
179 Content: []byte(fmt.Sprintf("test event kind %d", kindNum)),
180 }
181 // Add p tag for privileged check
182 pTag := tag.NewFromAny("p", hex.Enc(authSigner.Pub()))
183 ev.Tags.Append(pTag)
184
185 // Add d tag for addressable events (kinds 30000-39999)
186 if kindNum >= 30000 && kindNum < 40000 {
187 dTag := tag.NewFromAny("d", "test")
188 ev.Tags.Append(dTag)
189 }
190
191 if err = ev.Sign(eventSigner); err != nil {
192 return fmt.Errorf("sign error: %w", err)
193 }
194
195 // Publish
196 if err = rl.Publish(ctx, ev); err != nil {
197 return fmt.Errorf("publish error: %w", err)
198 }
199
200 return nil
201 }
202
203 func testWriteEventUnauthenticated(ctx context.Context, url string, kindNum uint16, eventSigner *p8k.Signer) error {
204 rl, err := ws.RelayConnect(ctx, url)
205 if err != nil {
206 return fmt.Errorf("connect error: %w", err)
207 }
208 defer rl.Close()
209
210 // Do NOT authenticate
211
212 // Create and sign event
213 ev := &event.E{
214 CreatedAt: time.Now().Unix(),
215 Kind: kind.K{K: kindNum}.K,
216 Tags: tag.NewS(),
217 Content: []byte(fmt.Sprintf("test event kind %d (unauthenticated)", kindNum)),
218 }
219
220 // Add d tag for addressable events (kinds 30000-39999)
221 if kindNum >= 30000 && kindNum < 40000 {
222 dTag := tag.NewFromAny("d", "test")
223 ev.Tags.Append(dTag)
224 }
225
226 if err = ev.Sign(eventSigner); err != nil {
227 return fmt.Errorf("sign error: %w", err)
228 }
229
230 // Publish (should fail)
231 if err = rl.Publish(ctx, ev); err != nil {
232 return fmt.Errorf("publish error (expected): %w", err)
233 }
234
235 return nil
236 }
237
238 func testReadEvent(ctx context.Context, url string, kindNum uint16, authSigner *p8k.Signer) error {
239 rl, err := ws.RelayConnect(ctx, url)
240 if err != nil {
241 return fmt.Errorf("connect error: %w", err)
242 }
243 defer rl.Close()
244
245 // Send a REQ first to trigger AUTH challenge (when AuthToWrite is enabled)
246 // Then authenticate
247 ff := filter.NewS(&filter.F{
248 Kinds: kind.NewS(kind.New(kindNum)),
249 })
250 sub, err := rl.Subscribe(ctx, ff)
251 if err != nil {
252 return fmt.Errorf("subscription error: %w", err)
253 }
254 // Wait a bit for challenge to arrive
255 time.Sleep(500 * time.Millisecond)
256
257 // Authenticate
258 if err = rl.Auth(ctx, authSigner); err != nil {
259 sub.Unsub()
260 return fmt.Errorf("auth error: %w", err)
261 }
262
263 // Wait for events or timeout
264 // If we receive any events, return nil (success)
265 // If we don't receive events, also return nil (no events found, which may be expected)
266 select {
267 case ev := <-sub.Events:
268 if ev != nil {
269 sub.Unsub()
270 return nil // Event received
271 }
272 case <-sub.EndOfStoredEvents:
273 // EOSE received, no more events
274 sub.Unsub()
275 return nil
276 case <-time.After(5 * time.Second):
277 // No events received - this might be OK if no events exist or they're filtered
278 sub.Unsub()
279 return nil
280 case <-ctx.Done():
281 sub.Unsub()
282 return ctx.Err()
283 }
284
285 return nil
286 }
287
288 func testReadEventUnauthenticated(ctx context.Context, url string, kindNum uint16) error {
289 rl, err := ws.RelayConnect(ctx, url)
290 if err != nil {
291 return fmt.Errorf("connect error: %w", err)
292 }
293 defer rl.Close()
294
295 // Do NOT authenticate
296
297 // Subscribe to events
298 ff := filter.NewS(&filter.F{
299 Kinds: kind.NewS(kind.New(kindNum)),
300 })
301
302 sub, err := rl.Subscribe(ctx, ff)
303 if err != nil {
304 return fmt.Errorf("subscription error (may be expected): %w", err)
305 }
306 defer sub.Unsub()
307
308 // Wait for events or timeout
309 // If we receive any events, that's a failure (should be blocked)
310 select {
311 case ev := <-sub.Events:
312 if ev != nil {
313 return fmt.Errorf("unexpected event received: should have been blocked by policy (event ID: %s)", hex.Enc(ev.ID))
314 }
315 case <-sub.EndOfStoredEvents:
316 // EOSE received, no events (this is expected for unauthenticated privileged events)
317 return nil
318 case <-time.After(5 * time.Second):
319 // No events received - this is expected for unauthenticated requests
320 return nil
321 case <-ctx.Done():
322 return ctx.Err()
323 }
324
325 return nil
326 }
327
328