handle-nip86.go raw
1 package app
2
3 import (
4 "encoding/json"
5 "io"
6 "net/http"
7
8 "next.orly.dev/pkg/lol/chk"
9 "next.orly.dev/pkg/acl"
10 "next.orly.dev/pkg/database"
11 "next.orly.dev/pkg/nostr/httpauth"
12 )
13
14 // NIP86Request represents a NIP-86 JSON-RPC request
15 type NIP86Request struct {
16 Method string `json:"method"`
17 Params []interface{} `json:"params"`
18 }
19
20 // NIP86Response represents a NIP-86 JSON-RPC response
21 type NIP86Response struct {
22 Result interface{} `json:"result,omitempty"`
23 Error string `json:"error,omitempty"`
24 }
25
26 // handleNIP86Management handles NIP-86 management API requests
27 func (s *Server) handleNIP86Management(w http.ResponseWriter, r *http.Request) {
28 if r.Method != http.MethodPost {
29 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
30 return
31 }
32
33 // Check Content-Type
34 contentType := r.Header.Get("Content-Type")
35 if contentType != "application/nostr+json+rpc" {
36 http.Error(w, "Content-Type must be application/nostr+json+rpc", http.StatusBadRequest)
37 return
38 }
39
40 // Validate NIP-98 authentication
41 valid, pubkey, err := httpauth.CheckAuth(r)
42 if chk.E(err) || !valid {
43 errorMsg := "NIP-98 authentication validation failed"
44 if err != nil {
45 errorMsg = err.Error()
46 }
47 http.Error(w, errorMsg, http.StatusUnauthorized)
48 return
49 }
50
51 // Check permissions - require owner level only
52 accessLevel := acl.Registry.GetAccessLevel(pubkey, r.RemoteAddr)
53 if accessLevel != "owner" {
54 http.Error(w, "Owner permission required", http.StatusForbidden)
55 return
56 }
57
58 // Dispatch based on ACL mode
59 aclType := acl.Registry.Type()
60 switch aclType {
61 case "curating":
62 s.handleCuratingNIP86Request(w, r, pubkey)
63 return
64 case "managed":
65 // Continue with managed ACL handling below
66 default:
67 http.Error(w, "NIP-86 requires managed or curating ACL mode", http.StatusBadRequest)
68 return
69 }
70
71 // Get the managed ACL instance
72 var managedACL *database.ManagedACL
73 for _, aclInstance := range acl.Registry.ACLs() {
74 if aclInstance.Type() == "managed" {
75 if managed, ok := aclInstance.(*acl.Managed); ok {
76 managedACL = managed.GetManagedACL()
77 break
78 }
79 }
80 }
81
82 if managedACL == nil {
83 http.Error(w, "Managed ACL not available", http.StatusInternalServerError)
84 return
85 }
86
87 // Read and parse the request
88 body, err := io.ReadAll(r.Body)
89 if chk.E(err) {
90 http.Error(w, "Failed to read request body", http.StatusBadRequest)
91 return
92 }
93
94 var request NIP86Request
95 if err := json.Unmarshal(body, &request); chk.E(err) {
96 http.Error(w, "Invalid JSON request", http.StatusBadRequest)
97 return
98 }
99
100 // Set response headers
101 w.Header().Set("Content-Type", "application/json")
102
103 // Handle the request based on method
104 response := s.handleNIP86Method(request, managedACL)
105
106 // Send response
107 jsonData, err := json.Marshal(response)
108 if chk.E(err) {
109 http.Error(w, "Error generating response", http.StatusInternalServerError)
110 return
111 }
112
113 w.Write(jsonData)
114 }
115
116 // handleNIP86Method handles individual NIP-86 methods
117 func (s *Server) handleNIP86Method(request NIP86Request, managedACL *database.ManagedACL) NIP86Response {
118 switch request.Method {
119 case "supportedmethods":
120 return s.handleSupportedMethods()
121 case "banpubkey":
122 return s.handleBanPubkey(request.Params, managedACL)
123 case "listbannedpubkeys":
124 return s.handleListBannedPubkeys(managedACL)
125 case "allowpubkey":
126 return s.handleAllowPubkey(request.Params, managedACL)
127 case "listallowedpubkeys":
128 return s.handleListAllowedPubkeys(managedACL)
129 case "listeventsneedingmoderation":
130 return s.handleListEventsNeedingModeration(managedACL)
131 case "allowevent":
132 return s.handleAllowEvent(request.Params, managedACL)
133 case "banevent":
134 return s.handleBanEvent(request.Params, managedACL)
135 case "listbannedevents":
136 return s.handleListBannedEvents(managedACL)
137 case "changerelayname":
138 return s.handleChangeRelayName(request.Params, managedACL)
139 case "changerelaydescription":
140 return s.handleChangeRelayDescription(request.Params, managedACL)
141 case "changerelayicon":
142 return s.handleChangeRelayIcon(request.Params, managedACL)
143 case "allowkind":
144 return s.handleAllowKind(request.Params, managedACL)
145 case "disallowkind":
146 return s.handleDisallowKind(request.Params, managedACL)
147 case "listallowedkinds":
148 return s.handleListAllowedKinds(managedACL)
149 case "blockip":
150 return s.handleBlockIP(request.Params, managedACL)
151 case "unblockip":
152 return s.handleUnblockIP(request.Params, managedACL)
153 case "listblockedips":
154 return s.handleListBlockedIPs(managedACL)
155 default:
156 return NIP86Response{Error: "Unknown method: " + request.Method}
157 }
158 }
159
160 // handleSupportedMethods returns the list of supported methods
161 func (s *Server) handleSupportedMethods() NIP86Response {
162 methods := []string{
163 "supportedmethods",
164 "banpubkey",
165 "listbannedpubkeys",
166 "allowpubkey",
167 "listallowedpubkeys",
168 "listeventsneedingmoderation",
169 "allowevent",
170 "banevent",
171 "listbannedevents",
172 "changerelayname",
173 "changerelaydescription",
174 "changerelayicon",
175 "allowkind",
176 "disallowkind",
177 "listallowedkinds",
178 "blockip",
179 "unblockip",
180 "listblockedips",
181 }
182 return NIP86Response{Result: methods}
183 }
184
185 // handleBanPubkey bans a public key
186 func (s *Server) handleBanPubkey(params []interface{}, managedACL *database.ManagedACL) NIP86Response {
187 if len(params) < 1 {
188 return NIP86Response{Error: "Missing required parameter: pubkey"}
189 }
190
191 pubkey, ok := params[0].(string)
192 if !ok {
193 return NIP86Response{Error: "Invalid pubkey parameter"}
194 }
195
196 // Validate pubkey format
197 if len(pubkey) != 64 {
198 return NIP86Response{Error: "Invalid pubkey format"}
199 }
200
201 reason := ""
202 if len(params) > 1 {
203 if r, ok := params[1].(string); ok {
204 reason = r
205 }
206 }
207
208 if err := managedACL.SaveBannedPubkey(pubkey, reason); chk.E(err) {
209 return NIP86Response{Error: "Failed to ban pubkey: " + err.Error()}
210 }
211
212 return NIP86Response{Result: true}
213 }
214
215 // handleListBannedPubkeys returns the list of banned pubkeys
216 func (s *Server) handleListBannedPubkeys(managedACL *database.ManagedACL) NIP86Response {
217 banned, err := managedACL.ListBannedPubkeys()
218 if chk.E(err) {
219 return NIP86Response{Error: "Failed to list banned pubkeys: " + err.Error()}
220 }
221
222 // Convert to the expected format
223 result := make([]map[string]interface{}, len(banned))
224 for i, b := range banned {
225 result[i] = map[string]interface{}{
226 "pubkey": b.Pubkey,
227 "reason": b.Reason,
228 }
229 }
230
231 return NIP86Response{Result: result}
232 }
233
234 // handleAllowPubkey allows a public key
235 func (s *Server) handleAllowPubkey(params []interface{}, managedACL *database.ManagedACL) NIP86Response {
236 if len(params) < 1 {
237 return NIP86Response{Error: "Missing required parameter: pubkey"}
238 }
239
240 pubkey, ok := params[0].(string)
241 if !ok {
242 return NIP86Response{Error: "Invalid pubkey parameter"}
243 }
244
245 // Validate pubkey format
246 if len(pubkey) != 64 {
247 return NIP86Response{Error: "Invalid pubkey format"}
248 }
249
250 reason := ""
251 if len(params) > 1 {
252 if r, ok := params[1].(string); ok {
253 reason = r
254 }
255 }
256
257 if err := managedACL.SaveAllowedPubkey(pubkey, reason); chk.E(err) {
258 return NIP86Response{Error: "Failed to allow pubkey: " + err.Error()}
259 }
260
261 return NIP86Response{Result: true}
262 }
263
264 // handleListAllowedPubkeys returns the list of allowed pubkeys
265 func (s *Server) handleListAllowedPubkeys(managedACL *database.ManagedACL) NIP86Response {
266 allowed, err := managedACL.ListAllowedPubkeys()
267 if chk.E(err) {
268 return NIP86Response{Error: "Failed to list allowed pubkeys: " + err.Error()}
269 }
270
271 // Convert to the expected format
272 result := make([]map[string]interface{}, len(allowed))
273 for i, a := range allowed {
274 result[i] = map[string]interface{}{
275 "pubkey": a.Pubkey,
276 "reason": a.Reason,
277 }
278 }
279
280 return NIP86Response{Result: result}
281 }
282
283 // handleListEventsNeedingModeration returns events needing moderation
284 func (s *Server) handleListEventsNeedingModeration(managedACL *database.ManagedACL) NIP86Response {
285 events, err := managedACL.ListEventsNeedingModeration()
286 if chk.E(err) {
287 return NIP86Response{Error: "Failed to list events needing moderation: " + err.Error()}
288 }
289
290 // Convert to the expected format
291 result := make([]map[string]interface{}, len(events))
292 for i, e := range events {
293 result[i] = map[string]interface{}{
294 "id": e.ID,
295 "reason": e.Reason,
296 }
297 }
298
299 return NIP86Response{Result: result}
300 }
301
302 // handleAllowEvent allows an event
303 func (s *Server) handleAllowEvent(params []interface{}, managedACL *database.ManagedACL) NIP86Response {
304 if len(params) < 1 {
305 return NIP86Response{Error: "Missing required parameter: event_id"}
306 }
307
308 eventID, ok := params[0].(string)
309 if !ok {
310 return NIP86Response{Error: "Invalid event_id parameter"}
311 }
312
313 // Validate event ID format
314 if len(eventID) != 64 {
315 return NIP86Response{Error: "Invalid event_id format"}
316 }
317
318 reason := ""
319 if len(params) > 1 {
320 if r, ok := params[1].(string); ok {
321 reason = r
322 }
323 }
324
325 if err := managedACL.SaveAllowedEvent(eventID, reason); chk.E(err) {
326 return NIP86Response{Error: "Failed to allow event: " + err.Error()}
327 }
328
329 // Remove from moderation queue if it was there
330 managedACL.RemoveEventNeedingModeration(eventID)
331
332 return NIP86Response{Result: true}
333 }
334
335 // handleBanEvent bans an event
336 func (s *Server) handleBanEvent(params []interface{}, managedACL *database.ManagedACL) NIP86Response {
337 if len(params) < 1 {
338 return NIP86Response{Error: "Missing required parameter: event_id"}
339 }
340
341 eventID, ok := params[0].(string)
342 if !ok {
343 return NIP86Response{Error: "Invalid event_id parameter"}
344 }
345
346 // Validate event ID format
347 if len(eventID) != 64 {
348 return NIP86Response{Error: "Invalid event_id format"}
349 }
350
351 reason := ""
352 if len(params) > 1 {
353 if r, ok := params[1].(string); ok {
354 reason = r
355 }
356 }
357
358 if err := managedACL.SaveBannedEvent(eventID, reason); chk.E(err) {
359 return NIP86Response{Error: "Failed to ban event: " + err.Error()}
360 }
361
362 return NIP86Response{Result: true}
363 }
364
365 // handleListBannedEvents returns the list of banned events
366 func (s *Server) handleListBannedEvents(managedACL *database.ManagedACL) NIP86Response {
367 banned, err := managedACL.ListBannedEvents()
368 if chk.E(err) {
369 return NIP86Response{Error: "Failed to list banned events: " + err.Error()}
370 }
371
372 // Convert to the expected format
373 result := make([]map[string]interface{}, len(banned))
374 for i, b := range banned {
375 result[i] = map[string]interface{}{
376 "id": b.ID,
377 "reason": b.Reason,
378 }
379 }
380
381 return NIP86Response{Result: result}
382 }
383
384 // handleChangeRelayName changes the relay name
385 func (s *Server) handleChangeRelayName(params []interface{}, managedACL *database.ManagedACL) NIP86Response {
386 if len(params) < 1 {
387 return NIP86Response{Error: "Missing required parameter: name"}
388 }
389
390 name, ok := params[0].(string)
391 if !ok {
392 return NIP86Response{Error: "Invalid name parameter"}
393 }
394
395 config, err := managedACL.GetRelayConfig()
396 if chk.E(err) {
397 return NIP86Response{Error: "Failed to get relay config: " + err.Error()}
398 }
399
400 config.RelayName = name
401 if err := managedACL.SaveRelayConfig(config); chk.E(err) {
402 return NIP86Response{Error: "Failed to save relay config: " + err.Error()}
403 }
404
405 return NIP86Response{Result: true}
406 }
407
408 // handleChangeRelayDescription changes the relay description
409 func (s *Server) handleChangeRelayDescription(params []interface{}, managedACL *database.ManagedACL) NIP86Response {
410 if len(params) < 1 {
411 return NIP86Response{Error: "Missing required parameter: description"}
412 }
413
414 description, ok := params[0].(string)
415 if !ok {
416 return NIP86Response{Error: "Invalid description parameter"}
417 }
418
419 config, err := managedACL.GetRelayConfig()
420 if chk.E(err) {
421 return NIP86Response{Error: "Failed to get relay config: " + err.Error()}
422 }
423
424 config.RelayDescription = description
425 if err := managedACL.SaveRelayConfig(config); chk.E(err) {
426 return NIP86Response{Error: "Failed to save relay config: " + err.Error()}
427 }
428
429 return NIP86Response{Result: true}
430 }
431
432 // handleChangeRelayIcon changes the relay icon
433 func (s *Server) handleChangeRelayIcon(params []interface{}, managedACL *database.ManagedACL) NIP86Response {
434 if len(params) < 1 {
435 return NIP86Response{Error: "Missing required parameter: icon_url"}
436 }
437
438 iconURL, ok := params[0].(string)
439 if !ok {
440 return NIP86Response{Error: "Invalid icon_url parameter"}
441 }
442
443 config, err := managedACL.GetRelayConfig()
444 if chk.E(err) {
445 return NIP86Response{Error: "Failed to get relay config: " + err.Error()}
446 }
447
448 config.RelayIcon = iconURL
449 if err := managedACL.SaveRelayConfig(config); chk.E(err) {
450 return NIP86Response{Error: "Failed to save relay config: " + err.Error()}
451 }
452
453 return NIP86Response{Result: true}
454 }
455
456 // handleAllowKind allows an event kind
457 func (s *Server) handleAllowKind(params []interface{}, managedACL *database.ManagedACL) NIP86Response {
458 if len(params) < 1 {
459 return NIP86Response{Error: "Missing required parameter: kind"}
460 }
461
462 kindFloat, ok := params[0].(float64)
463 if !ok {
464 return NIP86Response{Error: "Invalid kind parameter"}
465 }
466
467 kind := int(kindFloat)
468 if err := managedACL.SaveAllowedKind(kind); chk.E(err) {
469 return NIP86Response{Error: "Failed to allow kind: " + err.Error()}
470 }
471
472 return NIP86Response{Result: true}
473 }
474
475 // handleDisallowKind disallows an event kind
476 func (s *Server) handleDisallowKind(params []interface{}, managedACL *database.ManagedACL) NIP86Response {
477 if len(params) < 1 {
478 return NIP86Response{Error: "Missing required parameter: kind"}
479 }
480
481 kindFloat, ok := params[0].(float64)
482 if !ok {
483 return NIP86Response{Error: "Invalid kind parameter"}
484 }
485
486 kind := int(kindFloat)
487 if err := managedACL.RemoveAllowedKind(kind); chk.E(err) {
488 return NIP86Response{Error: "Failed to disallow kind: " + err.Error()}
489 }
490
491 return NIP86Response{Result: true}
492 }
493
494 // handleListAllowedKinds returns the list of allowed kinds
495 func (s *Server) handleListAllowedKinds(managedACL *database.ManagedACL) NIP86Response {
496 kinds, err := managedACL.ListAllowedKinds()
497 if chk.E(err) {
498 return NIP86Response{Error: "Failed to list allowed kinds: " + err.Error()}
499 }
500
501 return NIP86Response{Result: kinds}
502 }
503
504 // handleBlockIP blocks an IP address
505 func (s *Server) handleBlockIP(params []interface{}, managedACL *database.ManagedACL) NIP86Response {
506 if len(params) < 1 {
507 return NIP86Response{Error: "Missing required parameter: ip"}
508 }
509
510 ip, ok := params[0].(string)
511 if !ok {
512 return NIP86Response{Error: "Invalid ip parameter"}
513 }
514
515 reason := ""
516 if len(params) > 1 {
517 if r, ok := params[1].(string); ok {
518 reason = r
519 }
520 }
521
522 if err := managedACL.SaveBlockedIP(ip, reason); chk.E(err) {
523 return NIP86Response{Error: "Failed to block IP: " + err.Error()}
524 }
525
526 return NIP86Response{Result: true}
527 }
528
529 // handleUnblockIP unblocks an IP address
530 func (s *Server) handleUnblockIP(params []interface{}, managedACL *database.ManagedACL) NIP86Response {
531 if len(params) < 1 {
532 return NIP86Response{Error: "Missing required parameter: ip"}
533 }
534
535 ip, ok := params[0].(string)
536 if !ok {
537 return NIP86Response{Error: "Invalid ip parameter"}
538 }
539
540 if err := managedACL.RemoveBlockedIP(ip); chk.E(err) {
541 return NIP86Response{Error: "Failed to unblock IP: " + err.Error()}
542 }
543
544 return NIP86Response{Result: true}
545 }
546
547 // handleListBlockedIPs returns the list of blocked IPs
548 func (s *Server) handleListBlockedIPs(managedACL *database.ManagedACL) NIP86Response {
549 blocked, err := managedACL.ListBlockedIPs()
550 if chk.E(err) {
551 return NIP86Response{Error: "Failed to list blocked IPs: " + err.Error()}
552 }
553
554 // Convert to the expected format
555 result := make([]map[string]interface{}, len(blocked))
556 for i, b := range blocked {
557 result[i] = map[string]interface{}{
558 "ip": b.IP,
559 "reason": b.Reason,
560 }
561 }
562
563 return NIP86Response{Result: result}
564 }
565