Files
next.orly.dev/app/handle-nip86.go
2025-11-23 08:15:06 +00:00

558 lines
15 KiB
Go

package app
import (
"encoding/json"
"io"
"net/http"
"lol.mleku.dev/chk"
"next.orly.dev/pkg/acl"
"next.orly.dev/pkg/database"
"git.mleku.dev/mleku/nostr/httpauth"
)
// NIP86Request represents a NIP-86 JSON-RPC request
type NIP86Request struct {
Method string `json:"method"`
Params []interface{} `json:"params"`
}
// NIP86Response represents a NIP-86 JSON-RPC response
type NIP86Response struct {
Result interface{} `json:"result,omitempty"`
Error string `json:"error,omitempty"`
}
// handleNIP86Management handles NIP-86 management API requests
func (s *Server) handleNIP86Management(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// Check Content-Type
contentType := r.Header.Get("Content-Type")
if contentType != "application/nostr+json+rpc" {
http.Error(w, "Content-Type must be application/nostr+json+rpc", http.StatusBadRequest)
return
}
// Validate NIP-98 authentication
valid, pubkey, err := httpauth.CheckAuth(r)
if chk.E(err) || !valid {
errorMsg := "NIP-98 authentication validation failed"
if err != nil {
errorMsg = err.Error()
}
http.Error(w, errorMsg, http.StatusUnauthorized)
return
}
// Check permissions - require owner level only
accessLevel := acl.Registry.GetAccessLevel(pubkey, r.RemoteAddr)
if accessLevel != "owner" {
http.Error(w, "Owner permission required", http.StatusForbidden)
return
}
// Check if managed ACL is active
if acl.Registry.Type() != "managed" {
http.Error(w, "Managed ACL mode is not active", http.StatusBadRequest)
return
}
// Get the managed ACL instance
var managedACL *database.ManagedACL
for _, aclInstance := range acl.Registry.ACL {
if aclInstance.Type() == "managed" {
if managed, ok := aclInstance.(*acl.Managed); ok {
managedACL = managed.GetManagedACL()
break
}
}
}
if managedACL == nil {
http.Error(w, "Managed ACL not available", http.StatusInternalServerError)
return
}
// Read and parse the request
body, err := io.ReadAll(r.Body)
if chk.E(err) {
http.Error(w, "Failed to read request body", http.StatusBadRequest)
return
}
var request NIP86Request
if err := json.Unmarshal(body, &request); chk.E(err) {
http.Error(w, "Invalid JSON request", http.StatusBadRequest)
return
}
// Set response headers
w.Header().Set("Content-Type", "application/json")
// Handle the request based on method
response := s.handleNIP86Method(request, managedACL)
// Send response
jsonData, err := json.Marshal(response)
if chk.E(err) {
http.Error(w, "Error generating response", http.StatusInternalServerError)
return
}
w.Write(jsonData)
}
// handleNIP86Method handles individual NIP-86 methods
func (s *Server) handleNIP86Method(request NIP86Request, managedACL *database.ManagedACL) NIP86Response {
switch request.Method {
case "supportedmethods":
return s.handleSupportedMethods()
case "banpubkey":
return s.handleBanPubkey(request.Params, managedACL)
case "listbannedpubkeys":
return s.handleListBannedPubkeys(managedACL)
case "allowpubkey":
return s.handleAllowPubkey(request.Params, managedACL)
case "listallowedpubkeys":
return s.handleListAllowedPubkeys(managedACL)
case "listeventsneedingmoderation":
return s.handleListEventsNeedingModeration(managedACL)
case "allowevent":
return s.handleAllowEvent(request.Params, managedACL)
case "banevent":
return s.handleBanEvent(request.Params, managedACL)
case "listbannedevents":
return s.handleListBannedEvents(managedACL)
case "changerelayname":
return s.handleChangeRelayName(request.Params, managedACL)
case "changerelaydescription":
return s.handleChangeRelayDescription(request.Params, managedACL)
case "changerelayicon":
return s.handleChangeRelayIcon(request.Params, managedACL)
case "allowkind":
return s.handleAllowKind(request.Params, managedACL)
case "disallowkind":
return s.handleDisallowKind(request.Params, managedACL)
case "listallowedkinds":
return s.handleListAllowedKinds(managedACL)
case "blockip":
return s.handleBlockIP(request.Params, managedACL)
case "unblockip":
return s.handleUnblockIP(request.Params, managedACL)
case "listblockedips":
return s.handleListBlockedIPs(managedACL)
default:
return NIP86Response{Error: "Unknown method: " + request.Method}
}
}
// handleSupportedMethods returns the list of supported methods
func (s *Server) handleSupportedMethods() NIP86Response {
methods := []string{
"supportedmethods",
"banpubkey",
"listbannedpubkeys",
"allowpubkey",
"listallowedpubkeys",
"listeventsneedingmoderation",
"allowevent",
"banevent",
"listbannedevents",
"changerelayname",
"changerelaydescription",
"changerelayicon",
"allowkind",
"disallowkind",
"listallowedkinds",
"blockip",
"unblockip",
"listblockedips",
}
return NIP86Response{Result: methods}
}
// handleBanPubkey bans a public key
func (s *Server) handleBanPubkey(params []interface{}, managedACL *database.ManagedACL) NIP86Response {
if len(params) < 1 {
return NIP86Response{Error: "Missing required parameter: pubkey"}
}
pubkey, ok := params[0].(string)
if !ok {
return NIP86Response{Error: "Invalid pubkey parameter"}
}
// Validate pubkey format
if len(pubkey) != 64 {
return NIP86Response{Error: "Invalid pubkey format"}
}
reason := ""
if len(params) > 1 {
if r, ok := params[1].(string); ok {
reason = r
}
}
if err := managedACL.SaveBannedPubkey(pubkey, reason); chk.E(err) {
return NIP86Response{Error: "Failed to ban pubkey: " + err.Error()}
}
return NIP86Response{Result: true}
}
// handleListBannedPubkeys returns the list of banned pubkeys
func (s *Server) handleListBannedPubkeys(managedACL *database.ManagedACL) NIP86Response {
banned, err := managedACL.ListBannedPubkeys()
if chk.E(err) {
return NIP86Response{Error: "Failed to list banned pubkeys: " + err.Error()}
}
// Convert to the expected format
result := make([]map[string]interface{}, len(banned))
for i, b := range banned {
result[i] = map[string]interface{}{
"pubkey": b.Pubkey,
"reason": b.Reason,
}
}
return NIP86Response{Result: result}
}
// handleAllowPubkey allows a public key
func (s *Server) handleAllowPubkey(params []interface{}, managedACL *database.ManagedACL) NIP86Response {
if len(params) < 1 {
return NIP86Response{Error: "Missing required parameter: pubkey"}
}
pubkey, ok := params[0].(string)
if !ok {
return NIP86Response{Error: "Invalid pubkey parameter"}
}
// Validate pubkey format
if len(pubkey) != 64 {
return NIP86Response{Error: "Invalid pubkey format"}
}
reason := ""
if len(params) > 1 {
if r, ok := params[1].(string); ok {
reason = r
}
}
if err := managedACL.SaveAllowedPubkey(pubkey, reason); chk.E(err) {
return NIP86Response{Error: "Failed to allow pubkey: " + err.Error()}
}
return NIP86Response{Result: true}
}
// handleListAllowedPubkeys returns the list of allowed pubkeys
func (s *Server) handleListAllowedPubkeys(managedACL *database.ManagedACL) NIP86Response {
allowed, err := managedACL.ListAllowedPubkeys()
if chk.E(err) {
return NIP86Response{Error: "Failed to list allowed pubkeys: " + err.Error()}
}
// Convert to the expected format
result := make([]map[string]interface{}, len(allowed))
for i, a := range allowed {
result[i] = map[string]interface{}{
"pubkey": a.Pubkey,
"reason": a.Reason,
}
}
return NIP86Response{Result: result}
}
// handleListEventsNeedingModeration returns events needing moderation
func (s *Server) handleListEventsNeedingModeration(managedACL *database.ManagedACL) NIP86Response {
events, err := managedACL.ListEventsNeedingModeration()
if chk.E(err) {
return NIP86Response{Error: "Failed to list events needing moderation: " + err.Error()}
}
// Convert to the expected format
result := make([]map[string]interface{}, len(events))
for i, e := range events {
result[i] = map[string]interface{}{
"id": e.ID,
"reason": e.Reason,
}
}
return NIP86Response{Result: result}
}
// handleAllowEvent allows an event
func (s *Server) handleAllowEvent(params []interface{}, managedACL *database.ManagedACL) NIP86Response {
if len(params) < 1 {
return NIP86Response{Error: "Missing required parameter: event_id"}
}
eventID, ok := params[0].(string)
if !ok {
return NIP86Response{Error: "Invalid event_id parameter"}
}
// Validate event ID format
if len(eventID) != 64 {
return NIP86Response{Error: "Invalid event_id format"}
}
reason := ""
if len(params) > 1 {
if r, ok := params[1].(string); ok {
reason = r
}
}
if err := managedACL.SaveAllowedEvent(eventID, reason); chk.E(err) {
return NIP86Response{Error: "Failed to allow event: " + err.Error()}
}
// Remove from moderation queue if it was there
managedACL.RemoveEventNeedingModeration(eventID)
return NIP86Response{Result: true}
}
// handleBanEvent bans an event
func (s *Server) handleBanEvent(params []interface{}, managedACL *database.ManagedACL) NIP86Response {
if len(params) < 1 {
return NIP86Response{Error: "Missing required parameter: event_id"}
}
eventID, ok := params[0].(string)
if !ok {
return NIP86Response{Error: "Invalid event_id parameter"}
}
// Validate event ID format
if len(eventID) != 64 {
return NIP86Response{Error: "Invalid event_id format"}
}
reason := ""
if len(params) > 1 {
if r, ok := params[1].(string); ok {
reason = r
}
}
if err := managedACL.SaveBannedEvent(eventID, reason); chk.E(err) {
return NIP86Response{Error: "Failed to ban event: " + err.Error()}
}
return NIP86Response{Result: true}
}
// handleListBannedEvents returns the list of banned events
func (s *Server) handleListBannedEvents(managedACL *database.ManagedACL) NIP86Response {
banned, err := managedACL.ListBannedEvents()
if chk.E(err) {
return NIP86Response{Error: "Failed to list banned events: " + err.Error()}
}
// Convert to the expected format
result := make([]map[string]interface{}, len(banned))
for i, b := range banned {
result[i] = map[string]interface{}{
"id": b.ID,
"reason": b.Reason,
}
}
return NIP86Response{Result: result}
}
// handleChangeRelayName changes the relay name
func (s *Server) handleChangeRelayName(params []interface{}, managedACL *database.ManagedACL) NIP86Response {
if len(params) < 1 {
return NIP86Response{Error: "Missing required parameter: name"}
}
name, ok := params[0].(string)
if !ok {
return NIP86Response{Error: "Invalid name parameter"}
}
config, err := managedACL.GetRelayConfig()
if chk.E(err) {
return NIP86Response{Error: "Failed to get relay config: " + err.Error()}
}
config.RelayName = name
if err := managedACL.SaveRelayConfig(config); chk.E(err) {
return NIP86Response{Error: "Failed to save relay config: " + err.Error()}
}
return NIP86Response{Result: true}
}
// handleChangeRelayDescription changes the relay description
func (s *Server) handleChangeRelayDescription(params []interface{}, managedACL *database.ManagedACL) NIP86Response {
if len(params) < 1 {
return NIP86Response{Error: "Missing required parameter: description"}
}
description, ok := params[0].(string)
if !ok {
return NIP86Response{Error: "Invalid description parameter"}
}
config, err := managedACL.GetRelayConfig()
if chk.E(err) {
return NIP86Response{Error: "Failed to get relay config: " + err.Error()}
}
config.RelayDescription = description
if err := managedACL.SaveRelayConfig(config); chk.E(err) {
return NIP86Response{Error: "Failed to save relay config: " + err.Error()}
}
return NIP86Response{Result: true}
}
// handleChangeRelayIcon changes the relay icon
func (s *Server) handleChangeRelayIcon(params []interface{}, managedACL *database.ManagedACL) NIP86Response {
if len(params) < 1 {
return NIP86Response{Error: "Missing required parameter: icon_url"}
}
iconURL, ok := params[0].(string)
if !ok {
return NIP86Response{Error: "Invalid icon_url parameter"}
}
config, err := managedACL.GetRelayConfig()
if chk.E(err) {
return NIP86Response{Error: "Failed to get relay config: " + err.Error()}
}
config.RelayIcon = iconURL
if err := managedACL.SaveRelayConfig(config); chk.E(err) {
return NIP86Response{Error: "Failed to save relay config: " + err.Error()}
}
return NIP86Response{Result: true}
}
// handleAllowKind allows an event kind
func (s *Server) handleAllowKind(params []interface{}, managedACL *database.ManagedACL) NIP86Response {
if len(params) < 1 {
return NIP86Response{Error: "Missing required parameter: kind"}
}
kindFloat, ok := params[0].(float64)
if !ok {
return NIP86Response{Error: "Invalid kind parameter"}
}
kind := int(kindFloat)
if err := managedACL.SaveAllowedKind(kind); chk.E(err) {
return NIP86Response{Error: "Failed to allow kind: " + err.Error()}
}
return NIP86Response{Result: true}
}
// handleDisallowKind disallows an event kind
func (s *Server) handleDisallowKind(params []interface{}, managedACL *database.ManagedACL) NIP86Response {
if len(params) < 1 {
return NIP86Response{Error: "Missing required parameter: kind"}
}
kindFloat, ok := params[0].(float64)
if !ok {
return NIP86Response{Error: "Invalid kind parameter"}
}
kind := int(kindFloat)
if err := managedACL.RemoveAllowedKind(kind); chk.E(err) {
return NIP86Response{Error: "Failed to disallow kind: " + err.Error()}
}
return NIP86Response{Result: true}
}
// handleListAllowedKinds returns the list of allowed kinds
func (s *Server) handleListAllowedKinds(managedACL *database.ManagedACL) NIP86Response {
kinds, err := managedACL.ListAllowedKinds()
if chk.E(err) {
return NIP86Response{Error: "Failed to list allowed kinds: " + err.Error()}
}
return NIP86Response{Result: kinds}
}
// handleBlockIP blocks an IP address
func (s *Server) handleBlockIP(params []interface{}, managedACL *database.ManagedACL) NIP86Response {
if len(params) < 1 {
return NIP86Response{Error: "Missing required parameter: ip"}
}
ip, ok := params[0].(string)
if !ok {
return NIP86Response{Error: "Invalid ip parameter"}
}
reason := ""
if len(params) > 1 {
if r, ok := params[1].(string); ok {
reason = r
}
}
if err := managedACL.SaveBlockedIP(ip, reason); chk.E(err) {
return NIP86Response{Error: "Failed to block IP: " + err.Error()}
}
return NIP86Response{Result: true}
}
// handleUnblockIP unblocks an IP address
func (s *Server) handleUnblockIP(params []interface{}, managedACL *database.ManagedACL) NIP86Response {
if len(params) < 1 {
return NIP86Response{Error: "Missing required parameter: ip"}
}
ip, ok := params[0].(string)
if !ok {
return NIP86Response{Error: "Invalid ip parameter"}
}
if err := managedACL.RemoveBlockedIP(ip); chk.E(err) {
return NIP86Response{Error: "Failed to unblock IP: " + err.Error()}
}
return NIP86Response{Result: true}
}
// handleListBlockedIPs returns the list of blocked IPs
func (s *Server) handleListBlockedIPs(managedACL *database.ManagedACL) NIP86Response {
blocked, err := managedACL.ListBlockedIPs()
if chk.E(err) {
return NIP86Response{Error: "Failed to list blocked IPs: " + err.Error()}
}
// Convert to the expected format
result := make([]map[string]interface{}, len(blocked))
for i, b := range blocked {
result[i] = map[string]interface{}{
"ip": b.IP,
"reason": b.Reason,
}
}
return NIP86Response{Result: result}
}