split nip11 handler into its own file and implement nip86 (relay management api).

This commit is contained in:
fiatjaf
2024-07-08 15:41:13 -03:00
parent 0004c041e0
commit 535f4c90e0
6 changed files with 257 additions and 36 deletions

8
go.mod
View File

@@ -4,8 +4,8 @@ go 1.21.4
require (
github.com/fasthttp/websocket v1.5.7
github.com/fiatjaf/eventstore v0.3.8
github.com/nbd-wtf/go-nostr v0.30.0
github.com/fiatjaf/eventstore v0.5.0
github.com/nbd-wtf/go-nostr v0.34.2
github.com/puzpuzpuz/xsync/v3 v3.0.2
github.com/rs/cors v1.7.0
)
@@ -37,7 +37,7 @@ require (
github.com/google/flatbuffers v23.5.26+incompatible // indirect
github.com/jmoiron/sqlx v1.3.5 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.17.3 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-sqlite3 v1.14.18 // indirect
@@ -51,6 +51,6 @@ require (
go.opencensus.io v0.24.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/net v0.18.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/sys v0.20.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
)

20
go.sum
View File

@@ -47,8 +47,8 @@ github.com/fasthttp/websocket v1.5.7 h1:0a6o2OfeATvtGgoMKleURhLT6JqWPg7fYfWnH4KH
github.com/fasthttp/websocket v1.5.7/go.mod h1:bC4fxSono9czeXHQUVKxsC0sNjbm7lPJR04GDFqClfU=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fiatjaf/eventstore v0.3.8 h1:q4jcN95O2CVA+wP47V25BcVSNvjfOiPPIWgPmQ6hTRk=
github.com/fiatjaf/eventstore v0.3.8/go.mod h1:Qsm5loQICkazpsj8tQmcOK95AVkQQNF09Xx/NS/Biow=
github.com/fiatjaf/eventstore v0.5.0 h1:s+oROGUylAJhntIAPLgLekpTtxpExNd+QhSw0tby7Es=
github.com/fiatjaf/eventstore v0.5.0/go.mod h1:A3SgQ8hwDjZuhZ1aFT250BA70EsWsTIw0KRjm6PDh0w=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
@@ -103,8 +103,8 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA=
github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
@@ -113,8 +113,8 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/nbd-wtf/go-nostr v0.30.0 h1:rN085pe4IxmSBVht8LChZbWLggonjA8hPIk8l4/+Hjk=
github.com/nbd-wtf/go-nostr v0.30.0/go.mod h1:tiKJY6fWYSujbTQb201Y+IQ3l4szqYVt+fsTnsm7FCk=
github.com/nbd-wtf/go-nostr v0.34.2 h1:9b4qZ29DhQf9xEWN8/7zfDD868r1jFbpjrR3c+BHc+E=
github.com/nbd-wtf/go-nostr v0.34.2/go.mod h1:NZQkxl96ggbO8rvDpVjcsojJqKTPwqhP4i82O7K5DJs=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -133,8 +133,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
@@ -184,8 +184,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@@ -5,7 +5,6 @@ import (
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"net/http"
"strings"
@@ -28,6 +27,8 @@ func (rl *Relay) ServeHTTP(w http.ResponseWriter, r *http.Request) {
rl.HandleWebsocket(w, r)
} else if r.Header.Get("Accept") == "application/nostr+json" {
cors.AllowAll().Handler(http.HandlerFunc(rl.HandleNIP11)).ServeHTTP(w, r)
} else if r.Header.Get("Accept") == "application/nostr+json+rpc" {
cors.AllowAll().Handler(http.HandlerFunc(rl.HandleNIP86)).ServeHTTP(w, r)
} else {
rl.serveMux.ServeHTTP(w, r)
}
@@ -277,22 +278,3 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
}
}()
}
func (rl *Relay) HandleNIP11(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/nostr+json")
info := *rl.Info
if len(rl.DeleteEvent) > 0 {
info.SupportedNIPs = append(info.SupportedNIPs, 9)
}
if len(rl.CountEvents) > 0 {
info.SupportedNIPs = append(info.SupportedNIPs, 45)
}
for _, ovw := range rl.OverwriteRelayInformation {
info = ovw(r.Context(), r, info)
}
json.NewEncoder(w).Encode(info)
}

25
nip11.go Normal file
View File

@@ -0,0 +1,25 @@
package khatru
import (
"encoding/json"
"net/http"
)
func (rl *Relay) HandleNIP11(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/nostr+json")
info := *rl.Info
if len(rl.DeleteEvent) > 0 {
info.SupportedNIPs = append(info.SupportedNIPs, 9)
}
if len(rl.CountEvents) > 0 {
info.SupportedNIPs = append(info.SupportedNIPs, 45)
}
for _, ovw := range rl.OverwriteRelayInformation {
info = ovw(r.Context(), r, info)
}
json.NewEncoder(w).Encode(info)
}

211
nip86.go Normal file
View File

@@ -0,0 +1,211 @@
package khatru
import (
"encoding/json"
"fmt"
"net"
"net/http"
"reflect"
"strings"
"github.com/nbd-wtf/go-nostr/nip86"
)
type RelayManagementAPI struct {
BanPubKey func(pubkey string, reason string) error
ListBannedPubKeys func() ([]nip86.PubKeyReason, error)
AllowPubKey func(pubkey string, reason string) error
ListAllowedPubKeys func() ([]nip86.PubKeyReason, error)
ListEventsNeedingModeration func() ([]nip86.IDReason, error)
AllowEvent func(id string, reason string) error
BanEvent func(id string, reason string) error
ListBannedEvents func() ([]nip86.IDReason, error)
ChangeRelayName func(name string) error
ChangeRelayDescription func(desc string) error
ChangeRelayIcon func(icon string) error
AllowKind func(kind int) error
DisallowKind func(kind int) error
ListAllowedKinds func() ([]int, error)
BlockIP func(ip net.IP, reason string) error
UnblockIP func(ip net.IP, reason string) error
ListBlockedIPs func() ([]nip86.IPReason, error)
}
func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/nostr+json+rpc")
var req nip86.Request
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid json body", 400)
return
}
mp, err := nip86.DecodeRequest(req)
if err != nil {
http.Error(w, "invalid params: "+err.Error(), 400)
return
}
var resp nip86.Response
if _, ok := mp.(nip86.SupportedMethods); ok {
mat := reflect.TypeOf(rl.ManagementAPI)
mav := reflect.ValueOf(rl.ManagementAPI)
methods := make([]string, 0, mat.NumField())
for i := 0; i < mat.NumField(); i++ {
field := mat.Field(i)
// danger: this assumes the struct fields are appropriately named
methodName := strings.ToLower(field.Name)
// assign this only if the function was defined
if mav.Field(i).Interface() != nil {
methods[i] = methodName
}
}
resp.Result = methods
} else {
switch thing := mp.(type) {
case nip86.BanPubKey:
if rl.ManagementAPI.BanPubKey == nil {
resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName())
} else if err := rl.ManagementAPI.BanPubKey(thing.PubKey, thing.Reason); err != nil {
resp.Error = err.Error()
} else {
resp.Result = true
}
case nip86.ListBannedPubKeys:
if rl.ManagementAPI.ListBannedPubKeys == nil {
resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName())
} else if result, err := rl.ManagementAPI.ListBannedPubKeys(); err != nil {
resp.Error = err.Error()
} else {
resp.Result = result
}
case nip86.AllowPubKey:
if rl.ManagementAPI.AllowPubKey == nil {
resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName())
} else if err := rl.ManagementAPI.AllowPubKey(thing.PubKey, thing.Reason); err != nil {
resp.Error = err.Error()
} else {
resp.Result = true
}
case nip86.ListAllowedPubKeys:
if rl.ManagementAPI.ListAllowedPubKeys == nil {
resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName())
} else if result, err := rl.ManagementAPI.ListAllowedPubKeys(); err != nil {
resp.Error = err.Error()
} else {
resp.Result = result
}
case nip86.BanEvent:
if rl.ManagementAPI.BanEvent == nil {
resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName())
} else if err := rl.ManagementAPI.BanEvent(thing.ID, thing.Reason); err != nil {
resp.Error = err.Error()
} else {
resp.Result = true
}
case nip86.AllowEvent:
if rl.ManagementAPI.AllowEvent == nil {
resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName())
} else if err := rl.ManagementAPI.AllowEvent(thing.ID, thing.Reason); err != nil {
resp.Error = err.Error()
} else {
resp.Result = true
}
case nip86.ListEventsNeedingModeration:
if rl.ManagementAPI.ListEventsNeedingModeration == nil {
resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName())
} else if result, err := rl.ManagementAPI.ListEventsNeedingModeration(); err != nil {
resp.Error = err.Error()
} else {
resp.Result = result
}
case nip86.ListBannedEvents:
if rl.ManagementAPI.ListBannedEvents == nil {
resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName())
} else if result, err := rl.ManagementAPI.ListEventsNeedingModeration(); err != nil {
resp.Error = err.Error()
} else {
resp.Result = result
}
case nip86.ChangeRelayName:
if rl.ManagementAPI.ChangeRelayName == nil {
resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName())
} else if err := rl.ManagementAPI.ChangeRelayName(thing.Name); err != nil {
resp.Error = err.Error()
} else {
resp.Result = true
}
case nip86.ChangeRelayDescription:
if rl.ManagementAPI.ChangeRelayDescription == nil {
resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName())
} else if err := rl.ManagementAPI.ChangeRelayDescription(thing.Description); err != nil {
resp.Error = err.Error()
} else {
resp.Result = true
}
case nip86.ChangeRelayIcon:
if rl.ManagementAPI.ChangeRelayIcon == nil {
resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName())
} else if err := rl.ManagementAPI.ChangeRelayIcon(thing.IconURL); err != nil {
resp.Error = err.Error()
} else {
resp.Result = true
}
case nip86.AllowKind:
if rl.ManagementAPI.AllowKind == nil {
resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName())
} else if err := rl.ManagementAPI.AllowKind(thing.Kind); err != nil {
resp.Error = err.Error()
} else {
resp.Result = true
}
case nip86.DisallowKind:
if rl.ManagementAPI.DisallowKind == nil {
resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName())
} else if err := rl.ManagementAPI.DisallowKind(thing.Kind); err != nil {
resp.Error = err.Error()
} else {
resp.Result = true
}
case nip86.ListAllowedKinds:
if rl.ManagementAPI.ListAllowedKinds == nil {
resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName())
} else if result, err := rl.ManagementAPI.ListAllowedKinds(); err != nil {
resp.Error = err.Error()
} else {
resp.Result = result
}
case nip86.BlockIP:
if rl.ManagementAPI.BlockIP == nil {
resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName())
} else if err := rl.ManagementAPI.BlockIP(thing.IP, thing.Reason); err != nil {
resp.Error = err.Error()
} else {
resp.Result = true
}
case nip86.UnblockIP:
if rl.ManagementAPI.UnblockIP == nil {
resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName())
} else if err := rl.ManagementAPI.UnblockIP(thing.IP, thing.Reason); err != nil {
resp.Error = err.Error()
} else {
resp.Result = true
}
case nip86.ListBlockedIPs:
if rl.ManagementAPI.ListBlockedIPs == nil {
resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName())
} else if result, err := rl.ManagementAPI.ListBlockedIPs(); err != nil {
resp.Error = err.Error()
} else {
resp.Result = result
}
default:
resp.Error = fmt.Sprintf("method '%s' not known", mp.MethodName())
}
}
json.NewEncoder(w).Encode(resp)
}

View File

@@ -20,7 +20,7 @@ func NewRelay() *Relay {
Info: &nip11.RelayInformationDocument{
Software: "https://github.com/fiatjaf/khatru",
Version: "n/a",
SupportedNIPs: []int{1, 11, 42, 70},
SupportedNIPs: []int{1, 11, 42, 70, 86},
},
upgrader: websocket.Upgrader{
@@ -60,7 +60,10 @@ type Relay struct {
OnEventSaved []func(ctx context.Context, event *nostr.Event)
OnEphemeralEvent []func(ctx context.Context, event *nostr.Event)
// editing info will affect
// setting up handlers here will enable these methods
ManagementAPI RelayManagementAPI
// editing info will affect the NIP-11 responses
Info *nip11.RelayInformationDocument
// Default logger, as set by NewServer, is a stdlib logger prefixed with "[khatru-relay] ",