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