558 lines
15 KiB
Go
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"
|
|
"next.orly.dev/pkg/protocol/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}
|
|
}
|