test suite and many fixes from that.
This commit is contained in:
parent
68f6f52c82
commit
54dab1951a
@ -48,7 +48,12 @@ func (s *State) restrictWritesBasedOnGroupRules(ctx context.Context, event *nost
|
||||
|
||||
if event.Kind == nostr.KindSimpleGroupCreateGroup {
|
||||
// anyone can create new groups (if this is not desired a policy must be added to filter out this stuff)
|
||||
return false, ""
|
||||
if group == nil {
|
||||
// well, as long as the group doesn't exist, of course
|
||||
return false, ""
|
||||
} else {
|
||||
return true, "group already exists"
|
||||
}
|
||||
}
|
||||
|
||||
// only members can write
|
||||
@ -68,19 +73,23 @@ func (s *State) preventWritingOfEventsJustDeleted(ctx context.Context, event *no
|
||||
return false, ""
|
||||
}
|
||||
|
||||
func (s *State) requireModerationEventsToBeRecent(ctx context.Context, event *nostr.Event) (reject bool, msg string) {
|
||||
// moderation action events must be new and not reused
|
||||
if nip29.ModerationEventKinds.Includes(event.Kind) && event.CreatedAt < nostr.Now()-tooOld {
|
||||
return true, "moderation action is too old (older than 1 minute ago)"
|
||||
}
|
||||
return false, ""
|
||||
}
|
||||
|
||||
func (s *State) restrictInvalidModerationActions(ctx context.Context, event *nostr.Event) (reject bool, msg string) {
|
||||
if !nip29.MetadataEventKinds.Includes(event.Kind) {
|
||||
if !nip29.ModerationEventKinds.Includes(event.Kind) {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// moderation action events must be new and not reused
|
||||
if event.CreatedAt < nostr.Now()-tooOld {
|
||||
return true, "moderation action is too old (older than 1 minute ago)"
|
||||
}
|
||||
|
||||
group := s.GetGroupFromEvent(event)
|
||||
if event.Kind == nostr.KindSimpleGroupCreateGroup && group != nil {
|
||||
return true, "group already exists"
|
||||
if event.Kind == nostr.KindSimpleGroupCreateGroup {
|
||||
// see restrictWritesBasedOnGroupRules for a check that a group cannot be created if it already exists
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// will check if the moderation event author has sufficient permissions to perform this action
|
||||
@ -124,7 +133,9 @@ func (s *State) applyModerationAction(ctx context.Context, event *nostr.Event) {
|
||||
group = s.GetGroupFromEvent(event)
|
||||
}
|
||||
// apply the moderation action
|
||||
group.mu.Lock()
|
||||
action.Apply(&group.Group)
|
||||
group.mu.Unlock()
|
||||
|
||||
// if it's a delete event we have to actually delete stuff from the database here
|
||||
if event.Kind == nostr.KindSimpleGroupDeleteEvent {
|
||||
@ -156,17 +167,33 @@ func (s *State) applyModerationAction(ctx context.Context, event *nostr.Event) {
|
||||
}
|
||||
|
||||
// propagate new replaceable events to listeners depending on what changed happened
|
||||
switch event.Kind {
|
||||
case nostr.KindSimpleGroupCreateGroup, nostr.KindSimpleGroupEditMetadata, nostr.KindSimpleGroupEditGroupStatus:
|
||||
evt := group.ToMetadataEvent()
|
||||
evt.Sign(s.privateKey)
|
||||
s.Relay.BroadcastEvent(evt)
|
||||
case nostr.KindSimpleGroupAddPermission, nostr.KindSimpleGroupRemovePermission:
|
||||
evt := group.ToMetadataEvent()
|
||||
evt.Sign(s.privateKey)
|
||||
s.Relay.BroadcastEvent(evt)
|
||||
case nostr.KindSimpleGroupAddUser, nostr.KindSimpleGroupRemoveUser:
|
||||
evt := group.ToMembersEvent()
|
||||
for _, toBroadcast := range map[int][]func() *nostr.Event{
|
||||
nostr.KindSimpleGroupCreateGroup: {
|
||||
group.ToMetadataEvent,
|
||||
group.ToAdminsEvent,
|
||||
group.ToMembersEvent,
|
||||
},
|
||||
nostr.KindSimpleGroupEditMetadata: {
|
||||
group.ToMetadataEvent,
|
||||
},
|
||||
nostr.KindSimpleGroupEditGroupStatus: {
|
||||
group.ToMetadataEvent,
|
||||
},
|
||||
nostr.KindSimpleGroupAddPermission: {
|
||||
group.ToMembersEvent,
|
||||
group.ToAdminsEvent,
|
||||
},
|
||||
nostr.KindSimpleGroupRemovePermission: {
|
||||
group.ToAdminsEvent,
|
||||
},
|
||||
nostr.KindSimpleGroupAddUser: {
|
||||
group.ToMembersEvent,
|
||||
},
|
||||
nostr.KindSimpleGroupRemoveUser: {
|
||||
group.ToMembersEvent,
|
||||
},
|
||||
}[event.Kind] {
|
||||
evt := toBroadcast()
|
||||
evt.Sign(s.privateKey)
|
||||
s.Relay.BroadcastEvent(evt)
|
||||
}
|
||||
|
@ -15,15 +15,17 @@ import (
|
||||
func main() {
|
||||
relayPrivateKey := nostr.GeneratePrivateKey()
|
||||
|
||||
db := &slicestore.SliceStore{}
|
||||
db.Init()
|
||||
|
||||
state := relay29.Init(relay29.Options{
|
||||
Domain: "localhost:2929",
|
||||
DB: &slicestore.SliceStore{},
|
||||
DB: db,
|
||||
SecretKey: relayPrivateKey,
|
||||
})
|
||||
|
||||
// init relay
|
||||
state.Relay.Info.Name = "very ephemeral chat relay"
|
||||
state.Relay.Info.PubKey, _ = nostr.GetPublicKey(relayPrivateKey)
|
||||
state.Relay.Info.Description = "everything will be deleted as soon as I turn off my computer"
|
||||
|
||||
// extra policies
|
||||
|
@ -58,7 +58,6 @@ func main() {
|
||||
|
||||
// init relay
|
||||
state.Relay.Info.Name = s.RelayName
|
||||
state.Relay.Info.PubKey = s.RelayPubkey
|
||||
state.Relay.Info.Description = s.RelayDescription
|
||||
state.Relay.Info.Contact = s.RelayContact
|
||||
state.Relay.Info.Icon = s.RelayIcon
|
||||
|
@ -56,10 +56,10 @@ func (s *State) requireKindAndSingleGroupIDOrSpecificEventReference(ctx context.
|
||||
// check membership
|
||||
group.mu.RLock()
|
||||
if _, isMember := group.Members[authed]; isMember {
|
||||
group.mu.Unlock()
|
||||
group.mu.RUnlock()
|
||||
continue // fine, this user is a member
|
||||
}
|
||||
group.mu.Unlock()
|
||||
group.mu.RUnlock()
|
||||
return true, "restricted: not a member"
|
||||
}
|
||||
}
|
||||
|
7
go.mod
7
go.mod
@ -7,9 +7,10 @@ require (
|
||||
github.com/fiatjaf/khatru v0.5.0
|
||||
github.com/fiatjaf/set v0.0.3
|
||||
github.com/kelseyhightower/envconfig v1.4.0
|
||||
github.com/nbd-wtf/go-nostr v0.34.0
|
||||
github.com/nbd-wtf/go-nostr v0.34.1
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0
|
||||
github.com/rs/zerolog v1.31.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/theplant/htmlgo v1.0.3
|
||||
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8
|
||||
golang.org/x/time v0.4.0
|
||||
@ -20,6 +21,7 @@ require (
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.3 // indirect
|
||||
github.com/btcsuite/btcd/btcutil v1.1.5 // indirect
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
|
||||
github.com/fasthttp/websocket v1.5.9 // indirect
|
||||
@ -28,9 +30,11 @@ require (
|
||||
github.com/gobwas/ws v1.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.17.8 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rs/cors v1.11.0 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // indirect
|
||||
github.com/tidwall/gjson v1.17.1 // indirect
|
||||
@ -41,4 +45,5 @@ require (
|
||||
go.etcd.io/bbolt v1.3.9 // indirect
|
||||
golang.org/x/net v0.26.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
14
go.sum
14
go.sum
@ -26,6 +26,7 @@ github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@ -77,6 +78,10 @@ github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
||||
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
@ -84,8 +89,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/nbd-wtf/go-nostr v0.34.0 h1:E7tDHFx42gvWwFv1Eysn+NxJqGLmo21x/VEwj2+F21E=
|
||||
github.com/nbd-wtf/go-nostr v0.34.0/go.mod h1:NZQkxl96ggbO8rvDpVjcsojJqKTPwqhP4i82O7K5DJs=
|
||||
github.com/nbd-wtf/go-nostr v0.34.1 h1:wagHkPL0xHu8Tfnaybw3zERItu5ScoR3fKLQIPdCpi8=
|
||||
github.com/nbd-wtf/go-nostr v0.34.1/go.mod h1:NZQkxl96ggbO8rvDpVjcsojJqKTPwqhP4i82O7K5DJs=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
@ -95,11 +100,14 @@ github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0 h1:EewKT7/LNac5SLiEblJeUu8z5eERHrmRLnMQL2d7qX4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po=
|
||||
github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
@ -173,6 +181,8 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
397
relay_test.go
Normal file
397
relay_test.go
Normal file
@ -0,0 +1,397 @@
|
||||
package relay29
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fiatjaf/eventstore/slicestore"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var relayPrivateKey = nostr.GeneratePrivateKey()
|
||||
|
||||
func startTestRelay() func() {
|
||||
db := &slicestore.SliceStore{}
|
||||
db.Init()
|
||||
|
||||
state := Init(Options{
|
||||
Domain: "localhost:29292",
|
||||
DB: db,
|
||||
SecretKey: relayPrivateKey,
|
||||
})
|
||||
|
||||
state.Relay.Info.Name = "very testy relay"
|
||||
state.Relay.Info.Description = "this is just for testing"
|
||||
|
||||
// don't do this at home -- we're going to remove one requirement to make tests simpler
|
||||
state.Relay.RejectEvent = slices.DeleteFunc(state.Relay.RejectEvent, func(f func(ctx context.Context, event *nostr.Event) (reject bool, msg string)) bool {
|
||||
return fmt.Sprintf("%v", []any{f}) == fmt.Sprintf("%v", []any{state.requireModerationEventsToBeRecent})
|
||||
})
|
||||
|
||||
server := &http.Server{Addr: ":29292", Handler: state.Relay}
|
||||
|
||||
go func() {
|
||||
server.ListenAndServe()
|
||||
}()
|
||||
|
||||
return func() {
|
||||
server.Shutdown(context.Background())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupStuffABunch(t *testing.T) {
|
||||
defer startTestRelay()()
|
||||
ctx := context.Background()
|
||||
|
||||
user1 := "0000000000000000000000000000000000000000000000000000000000000001"
|
||||
user1pk, _ := nostr.GetPublicKey(user1)
|
||||
|
||||
user2 := "0000000000000000000000000000000000000000000000000000000000000002"
|
||||
user2pk, _ := nostr.GetPublicKey(user2)
|
||||
|
||||
user3 := "0000000000000000000000000000000000000000000000000000000000000003"
|
||||
user3pk, _ := nostr.GetPublicKey(user3)
|
||||
|
||||
// simple open group
|
||||
{
|
||||
r, err := nostr.RelayConnect(ctx, "ws://localhost:29292")
|
||||
require.NoError(t, err, "failed to connect to relay")
|
||||
|
||||
metaSub, err := r.Subscribe(ctx, nostr.Filters{{Kinds: []int{39000}, Tags: nostr.TagMap{"d": []string{"a"}}}})
|
||||
require.NoError(t, err, "failed to subscribe to group metadata")
|
||||
|
||||
membersSub, err := r.Subscribe(ctx, nostr.Filters{{Kinds: []int{39002}, Tags: nostr.TagMap{"d": []string{"a"}}}})
|
||||
require.NoError(t, err, "failed to subscribe to group members")
|
||||
|
||||
createGroup := nostr.Event{
|
||||
CreatedAt: 1,
|
||||
Kind: 9007,
|
||||
Tags: nostr.Tags{{"h", "a"}},
|
||||
}
|
||||
createGroup.Sign(user1)
|
||||
require.NoError(t, r.Publish(ctx, createGroup), "failed to publish kind 9007")
|
||||
|
||||
select {
|
||||
case evt := <-metaSub.Events:
|
||||
require.Equal(t, "a", evt.Tags.GetD())
|
||||
require.Nil(t, evt.Tags.GetFirst([]string{"private"}))
|
||||
require.NotNil(t, evt.Tags.GetFirst([]string{"public"}))
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("select took too long")
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case evt := <-membersSub.Events:
|
||||
require.Equal(t, "a", evt.Tags.GetD())
|
||||
require.NotNil(t,
|
||||
evt.Tags.GetFirst([]string{"p", user1pk}),
|
||||
)
|
||||
require.Len(t, evt.Tags, 2)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("select took too long")
|
||||
return
|
||||
}
|
||||
|
||||
inviteMember := nostr.Event{
|
||||
CreatedAt: 2,
|
||||
Kind: 9000,
|
||||
Tags: nostr.Tags{{"h", "a"}, {"p", user2pk}},
|
||||
}
|
||||
inviteMember.Sign(user1)
|
||||
require.NoError(t, r.Publish(ctx, inviteMember), "failed to publish kind 9000")
|
||||
|
||||
select {
|
||||
case evt := <-membersSub.Events:
|
||||
require.Equal(t, "a", evt.Tags.GetD())
|
||||
require.NotNil(t,
|
||||
evt.Tags.GetFirst([]string{"p", user1pk}),
|
||||
evt.Tags.GetFirst([]string{"p", user2pk}),
|
||||
)
|
||||
require.Len(t, evt.Tags, 3)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("select took too long")
|
||||
return
|
||||
}
|
||||
|
||||
updateMetadata := nostr.Event{
|
||||
CreatedAt: 3,
|
||||
Kind: 9002,
|
||||
Tags: nostr.Tags{{"h", "a"}, {"name", "alface"}},
|
||||
}
|
||||
updateMetadata.Sign(user1)
|
||||
require.NoError(t, r.Publish(ctx, updateMetadata), "failed to publish kind 9002")
|
||||
|
||||
select {
|
||||
case evt := <-metaSub.Events:
|
||||
require.Equal(t, "a", evt.Tags.GetD())
|
||||
require.Equal(t, &nostr.Tag{"name", "alface"}, evt.Tags.GetFirst([]string{"name"}))
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("select took too long")
|
||||
return
|
||||
}
|
||||
|
||||
msgSub, err := r.Subscribe(ctx, nostr.Filters{{Kinds: []int{9, 10}, Tags: nostr.TagMap{"h": []string{"a"}}}})
|
||||
require.NoError(t, err, "failed to subscribe to group messages")
|
||||
|
||||
for i := 4; i < 10; i++ {
|
||||
message := nostr.Event{
|
||||
CreatedAt: nostr.Timestamp(i),
|
||||
Content: fmt.Sprintf("hello %d", i),
|
||||
Kind: 9,
|
||||
Tags: nostr.Tags{{"h", "a"}},
|
||||
}
|
||||
signer := user1
|
||||
if i%2 == 1 {
|
||||
signer = user2
|
||||
}
|
||||
message.Sign(signer)
|
||||
require.NoError(t, r.Publish(ctx, message), "failed to publish kind 9")
|
||||
}
|
||||
|
||||
for i := 4; i < 10; i++ {
|
||||
publisher := user1pk
|
||||
if i%2 == 1 {
|
||||
publisher = user2pk
|
||||
}
|
||||
message := <-msgSub.Events
|
||||
require.Equal(t, fmt.Sprintf("hello %d", i), message.Content)
|
||||
require.Equal(t, publisher, message.PubKey)
|
||||
}
|
||||
|
||||
failedNoHTag := nostr.Event{
|
||||
CreatedAt: 11,
|
||||
Content: "failed",
|
||||
Kind: 9,
|
||||
}
|
||||
failedNoHTag.Sign(user1)
|
||||
require.Error(t, r.Publish(ctx, failedNoHTag), "should fail to publish kind 9 with no h tag")
|
||||
|
||||
failedWrongHTag := nostr.Event{
|
||||
CreatedAt: 11,
|
||||
Content: "failed",
|
||||
Kind: 9,
|
||||
Tags: nostr.Tags{{"h", "b"}},
|
||||
}
|
||||
failedWrongHTag.Sign(user1)
|
||||
require.Error(t, r.Publish(ctx, failedWrongHTag), "should fail to publish kind 9 with wrong h tag")
|
||||
|
||||
failedFromNonMember := nostr.Event{
|
||||
CreatedAt: 11,
|
||||
Content: "failed",
|
||||
Kind: 9,
|
||||
Tags: nostr.Tags{{"h", "a"}},
|
||||
}
|
||||
failedWrongHTag.Sign(user3)
|
||||
require.Error(t, r.Publish(ctx, failedFromNonMember), "should fail to publish kind 9 from non-member")
|
||||
|
||||
ext, err := r.Subscribe(ctx, nostr.Filters{{Kinds: []int{9, 10, 11, 12}, Tags: nostr.TagMap{"h": []string{"a"}}}})
|
||||
require.NoError(t, err, "failed to subscribe to messages again")
|
||||
count := 0
|
||||
for {
|
||||
select {
|
||||
case message := <-ext.Events:
|
||||
require.Equal(t, 9, message.Kind)
|
||||
require.Equal(t, fmt.Sprintf("hello %d", message.CreatedAt), message.Content)
|
||||
count++
|
||||
case <-ext.EndOfStoredEvents:
|
||||
require.Equal(t, 6, count, "must have 6 messages")
|
||||
goto end1
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("select took too long")
|
||||
return
|
||||
}
|
||||
}
|
||||
end1:
|
||||
}
|
||||
|
||||
// adding now a private group
|
||||
{
|
||||
r, err := nostr.RelayConnect(ctx, "ws://localhost:29292")
|
||||
require.NoError(t, err, "failed to connect to relay")
|
||||
|
||||
createGroupFail := nostr.Event{
|
||||
CreatedAt: 1,
|
||||
Kind: 9007,
|
||||
Tags: nostr.Tags{{"h", "a"}},
|
||||
}
|
||||
createGroupFail.Sign(user3)
|
||||
require.Error(t, r.Publish(ctx, createGroupFail), "should fail to publish kind 9007 for existing group")
|
||||
|
||||
metaSub, err := r.Subscribe(ctx, nostr.Filters{{Kinds: []int{39000}, Tags: nostr.TagMap{"d": []string{"b"}}}})
|
||||
require.NoError(t, err, "failed to subscribe to group metadata")
|
||||
|
||||
membersSub, err := r.Subscribe(ctx, nostr.Filters{{Kinds: []int{39002}, Tags: nostr.TagMap{"d": []string{"b"}}}})
|
||||
require.NoError(t, err, "failed to subscribe to group members")
|
||||
|
||||
createGroup := nostr.Event{
|
||||
CreatedAt: 1,
|
||||
Kind: 9007,
|
||||
Tags: nostr.Tags{{"h", "b"}},
|
||||
}
|
||||
createGroup.Sign(user3)
|
||||
require.NoError(t, r.Publish(ctx, createGroup), "failed to publish kind 9007")
|
||||
|
||||
select {
|
||||
case evt := <-metaSub.Events:
|
||||
require.Equal(t, "b", evt.Tags.GetD())
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("select took too long")
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case evt := <-membersSub.Events:
|
||||
require.Equal(t, "b", evt.Tags.GetD())
|
||||
require.NotNil(t,
|
||||
evt.Tags.GetFirst([]string{"p", user3pk}),
|
||||
)
|
||||
require.Len(t, evt.Tags, 2)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("select took too long")
|
||||
return
|
||||
}
|
||||
|
||||
inviteMember := nostr.Event{
|
||||
CreatedAt: 2,
|
||||
Kind: 9000,
|
||||
Tags: nostr.Tags{{"h", "b"}, {"p", user2pk}},
|
||||
}
|
||||
inviteMember.Sign(user3)
|
||||
require.NoError(t, r.Publish(ctx, inviteMember), "failed to publish kind 9000")
|
||||
|
||||
select {
|
||||
case evt := <-membersSub.Events:
|
||||
require.Equal(t, "b", evt.Tags.GetD())
|
||||
require.NotNil(t,
|
||||
evt.Tags.GetFirst([]string{"p", user3pk}),
|
||||
evt.Tags.GetFirst([]string{"p", user2pk}),
|
||||
)
|
||||
require.Len(t, evt.Tags, 3)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("select took too long")
|
||||
return
|
||||
}
|
||||
|
||||
setGroupPrivate := nostr.Event{
|
||||
CreatedAt: 3,
|
||||
Kind: 9006,
|
||||
Tags: nostr.Tags{
|
||||
{"h", "b"},
|
||||
{"private"},
|
||||
},
|
||||
}
|
||||
setGroupPrivate.Sign(user2)
|
||||
require.Error(t, r.Publish(ctx, setGroupPrivate), "should fail to accept moderation from non-mod")
|
||||
|
||||
setGroupPrivate.Sign(user3)
|
||||
require.NoError(t, r.Publish(ctx, setGroupPrivate), "failed to publish kind 9006")
|
||||
|
||||
select {
|
||||
case evt := <-metaSub.Events:
|
||||
require.Equal(t, "b", evt.Tags.GetD())
|
||||
require.Nil(t, evt.Tags.GetFirst([]string{"public"}))
|
||||
require.NotNil(t, evt.Tags.GetFirst([]string{"private"}))
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("select took too long")
|
||||
return
|
||||
}
|
||||
|
||||
for i := 4; i < 10; i++ {
|
||||
message := nostr.Event{
|
||||
CreatedAt: nostr.Timestamp(i),
|
||||
Content: fmt.Sprintf("hello %d", i),
|
||||
Kind: 9,
|
||||
Tags: nostr.Tags{{"h", "b"}},
|
||||
}
|
||||
signer := user3
|
||||
if i%2 == 1 {
|
||||
signer = user2
|
||||
}
|
||||
message.Sign(signer)
|
||||
require.NoError(t, r.Publish(ctx, message), "failed to publish kind 9")
|
||||
}
|
||||
|
||||
failedSub, err := r.Subscribe(ctx, nostr.Filters{{Kinds: []int{9}, Tags: nostr.TagMap{"h": []string{"b"}}}})
|
||||
require.NoError(t, err, "failed to subscribe to private messages")
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-failedSub.Events:
|
||||
t.Fatal("should not have received events")
|
||||
return
|
||||
case <-failedSub.EndOfStoredEvents:
|
||||
t.Fatal("should not have received EOSE")
|
||||
return
|
||||
case closed := <-failedSub.ClosedReason:
|
||||
require.Contains(t, closed, "auth-required:")
|
||||
goto end2
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("select took too long")
|
||||
return
|
||||
}
|
||||
}
|
||||
end2:
|
||||
r2, err := nostr.RelayConnect(ctx, "ws://localhost:29292")
|
||||
require.NoError(t, err, "failed to connect to relay")
|
||||
|
||||
time.Sleep(time.Millisecond * 20) // wait until auth is received
|
||||
|
||||
err = r2.Auth(ctx, func(authEvent *nostr.Event) error {
|
||||
authEvent.Sign(user2)
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err, "auth should have worked")
|
||||
|
||||
goodSub, err := r2.Subscribe(ctx, nostr.Filters{{Kinds: []int{9}, Tags: nostr.TagMap{"h": []string{"b"}}}})
|
||||
require.NoError(t, err, "failed to subscribe to private messages")
|
||||
|
||||
count := 0
|
||||
for {
|
||||
select {
|
||||
case message := <-goodSub.Events:
|
||||
require.Equal(t, 9, message.Kind)
|
||||
require.Equal(t, fmt.Sprintf("hello %d", message.CreatedAt), message.Content)
|
||||
count++
|
||||
case <-goodSub.EndOfStoredEvents:
|
||||
require.Equal(t, 6, count, "must have 6 messages")
|
||||
goto end3
|
||||
case <-failedSub.ClosedReason:
|
||||
t.Fatal("should not have received CLOSED")
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("select took too long")
|
||||
return
|
||||
}
|
||||
}
|
||||
end3:
|
||||
|
||||
anotherMessage := nostr.Event{
|
||||
CreatedAt: 11,
|
||||
Content: "last",
|
||||
Kind: 9,
|
||||
Tags: nostr.Tags{{"h", "b"}},
|
||||
}
|
||||
anotherMessage.Sign(user3)
|
||||
require.NoError(t, r.Publish(ctx, anotherMessage), "failed to publish last kind 9")
|
||||
|
||||
select {
|
||||
case message := <-goodSub.Events:
|
||||
// good sub should receive it
|
||||
require.Equal(t, "last", message.Content)
|
||||
case <-time.After(time.Millisecond):
|
||||
t.Fatal("select took too long")
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-failedSub.Events:
|
||||
t.Fatal("unauthed sub should not receive it")
|
||||
case <-time.After(time.Millisecond * 200):
|
||||
}
|
||||
}
|
||||
}
|
2
state.go
2
state.go
@ -42,6 +42,7 @@ func Init(opts Options) *State {
|
||||
|
||||
// we create a new khatru relay
|
||||
relay := khatru.NewRelay()
|
||||
relay.Info.PubKey = pubkey
|
||||
relay.Info.SupportedNIPs = append(relay.Info.SupportedNIPs, 29)
|
||||
|
||||
state := &State{
|
||||
@ -70,6 +71,7 @@ func Init(opts Options) *State {
|
||||
state.requireKindAndSingleGroupIDOrSpecificEventReference,
|
||||
)
|
||||
relay.RejectEvent = append(relay.RejectEvent,
|
||||
state.requireModerationEventsToBeRecent,
|
||||
state.requireHTagForExistingGroup,
|
||||
state.restrictWritesBasedOnGroupRules,
|
||||
state.restrictInvalidModerationActions,
|
||||
|
Loading…
x
Reference in New Issue
Block a user