test suite and many fixes from that.

This commit is contained in:
fiatjaf 2024-07-05 17:00:16 -03:00
parent 68f6f52c82
commit 54dab1951a
8 changed files with 470 additions and 28 deletions

View File

@ -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)
}

View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -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
View File

@ -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
View 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):
}
}
}

View File

@ -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,