"#p" filtering on group metadata events.
This commit is contained in:
parent
820814f6f5
commit
0c6725cbea
74
queries.go
74
queries.go
@ -6,20 +6,27 @@ import (
|
||||
"github.com/fiatjaf/khatru"
|
||||
"github.com/fiatjaf/set"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip29"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func (s *State) metadataQueryHandler(ctx context.Context, filter nostr.Filter) (chan *nostr.Event, error) {
|
||||
ch := make(chan *nostr.Event, 1)
|
||||
|
||||
authed := khatru.GetAuthed(ctx)
|
||||
go func() {
|
||||
if slices.Contains(filter.Kinds, nostr.KindSimpleGroupMetadata) {
|
||||
if _, ok := filter.Tags["d"]; !ok {
|
||||
// no "d" tag specified, return everything
|
||||
s.Groups.Range(func(_ string, group *Group) bool {
|
||||
if group.Private {
|
||||
// don't reveal metadata about private groups in lists
|
||||
return true
|
||||
// don't reveal metadata about private groups in lists unless we're a member
|
||||
if authed == "" {
|
||||
return true
|
||||
}
|
||||
if _, isMember := group.Members[authed]; !isMember {
|
||||
return true
|
||||
}
|
||||
} else if group.Closed {
|
||||
// closed groups also shouldn't be listed since people can't freely join them
|
||||
}
|
||||
@ -49,16 +56,25 @@ func (s *State) metadataQueryHandler(ctx context.Context, filter nostr.Filter) (
|
||||
func (s *State) adminsQueryHandler(ctx context.Context, filter nostr.Filter) (chan *nostr.Event, error) {
|
||||
ch := make(chan *nostr.Event, 1)
|
||||
|
||||
authed := khatru.GetAuthed(ctx)
|
||||
go func() {
|
||||
if slices.Contains(filter.Kinds, nostr.KindSimpleGroupAdmins) {
|
||||
if _, ok := filter.Tags["d"]; !ok {
|
||||
// no "d" tag specified, return everything
|
||||
s.Groups.Range(func(_ string, group *Group) bool {
|
||||
if group.Private {
|
||||
// don't reveal lists of admins of private groups ever
|
||||
// don't reveal lists of admins of private groups unless we're a member
|
||||
if authed == "" {
|
||||
return true
|
||||
}
|
||||
if _, isMember := group.Members[authed]; !isMember {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if pks, hasPTags := filter.Tags["p"]; hasPTags && !hasOneOfTheseAdmins(group.Group, pks) {
|
||||
// filter queried p tags
|
||||
return true
|
||||
}
|
||||
|
||||
evt := group.ToAdminsEvent()
|
||||
evt.Sign(s.privateKey)
|
||||
ch <- evt
|
||||
@ -68,7 +84,16 @@ func (s *State) adminsQueryHandler(ctx context.Context, filter nostr.Filter) (ch
|
||||
for _, groupId := range filter.Tags["d"] {
|
||||
if group, _ := s.Groups.Load(groupId); group != nil {
|
||||
if group.Private {
|
||||
// don't reveal lists of admins of private groups ever
|
||||
// don't reveal lists of admins of private groups unless we're a member
|
||||
if authed == "" {
|
||||
continue
|
||||
}
|
||||
if _, isMember := group.Members[authed]; !isMember {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if pks, hasPTags := filter.Tags["p"]; hasPTags && !hasOneOfTheseAdmins(group.Group, pks) {
|
||||
// filter queried p tags
|
||||
continue
|
||||
}
|
||||
evt := group.ToAdminsEvent()
|
||||
@ -88,13 +113,23 @@ func (s *State) adminsQueryHandler(ctx context.Context, filter nostr.Filter) (ch
|
||||
func (s *State) membersQueryHandler(ctx context.Context, filter nostr.Filter) (chan *nostr.Event, error) {
|
||||
ch := make(chan *nostr.Event, 1)
|
||||
|
||||
authed := khatru.GetAuthed(ctx)
|
||||
go func() {
|
||||
if slices.Contains(filter.Kinds, nostr.KindSimpleGroupMembers) {
|
||||
if _, ok := filter.Tags["d"]; !ok {
|
||||
// no "d" tag specified, return everything
|
||||
s.Groups.Range(func(_ string, group *Group) bool {
|
||||
if group.Private {
|
||||
// don't reveal lists of members of private groups ever
|
||||
// don't reveal lists of members of private groups unless we're a member
|
||||
if authed == "" {
|
||||
return true
|
||||
}
|
||||
if _, isMember := group.Members[authed]; !isMember {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if pks, hasPTags := filter.Tags["p"]; hasPTags && !hasOneOfTheseMembers(group.Group, pks) {
|
||||
// filter queried p tags
|
||||
return true
|
||||
}
|
||||
evt := group.ToMembersEvent()
|
||||
@ -107,6 +142,15 @@ func (s *State) membersQueryHandler(ctx context.Context, filter nostr.Filter) (c
|
||||
if group, _ := s.Groups.Load(groupId); group != nil {
|
||||
if group.Private {
|
||||
// don't reveal lists of members of private groups ever
|
||||
if authed == "" {
|
||||
continue
|
||||
}
|
||||
if _, isMember := group.Members[authed]; !isMember {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if pks, hasPTags := filter.Tags["p"]; hasPTags && !hasOneOfTheseMembers(group.Group, pks) {
|
||||
// filter queried p tags
|
||||
continue
|
||||
}
|
||||
evt := group.ToMembersEvent()
|
||||
@ -170,3 +214,21 @@ func (s *State) normalEventQuery(ctx context.Context, filter nostr.Filter) (chan
|
||||
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
func hasOneOfTheseMembers(group nip29.Group, pubkeys []string) bool {
|
||||
for _, pk := range pubkeys {
|
||||
if _, ok := group.Members[pk]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasOneOfTheseAdmins(group nip29.Group, pubkeys []string) bool {
|
||||
for _, pk := range pubkeys {
|
||||
if role, ok := group.Members[pk]; ok && role != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -68,6 +68,7 @@ func TestGroupStuffABunch(t *testing.T) {
|
||||
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")
|
||||
|
||||
// create group
|
||||
createGroup := nostr.Event{
|
||||
CreatedAt: 1,
|
||||
Kind: 9007,
|
||||
@ -76,6 +77,7 @@ func TestGroupStuffABunch(t *testing.T) {
|
||||
createGroup.Sign(user1)
|
||||
require.NoError(t, r.Publish(ctx, createGroup), "failed to publish kind 9007")
|
||||
|
||||
// see if we get notified about that
|
||||
select {
|
||||
case evt := <-metaSub.Events:
|
||||
require.Equal(t, "a", evt.Tags.GetD())
|
||||
@ -98,6 +100,7 @@ func TestGroupStuffABunch(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
// invite another member
|
||||
inviteMember := nostr.Event{
|
||||
CreatedAt: 2,
|
||||
Kind: 9000,
|
||||
@ -106,6 +109,7 @@ func TestGroupStuffABunch(t *testing.T) {
|
||||
inviteMember.Sign(user1)
|
||||
require.NoError(t, r.Publish(ctx, inviteMember), "failed to publish kind 9000")
|
||||
|
||||
// see if we get notified about that
|
||||
select {
|
||||
case evt := <-membersSub.Events:
|
||||
require.Equal(t, "a", evt.Tags.GetD())
|
||||
@ -119,6 +123,7 @@ func TestGroupStuffABunch(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
// update metadata
|
||||
updateMetadata := nostr.Event{
|
||||
CreatedAt: 3,
|
||||
Kind: 9002,
|
||||
@ -127,6 +132,7 @@ func TestGroupStuffABunch(t *testing.T) {
|
||||
updateMetadata.Sign(user1)
|
||||
require.NoError(t, r.Publish(ctx, updateMetadata), "failed to publish kind 9002")
|
||||
|
||||
// see if we get notified about that
|
||||
select {
|
||||
case evt := <-metaSub.Events:
|
||||
require.Equal(t, "a", evt.Tags.GetD())
|
||||
@ -139,6 +145,7 @@ func TestGroupStuffABunch(t *testing.T) {
|
||||
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")
|
||||
|
||||
// publish some messages
|
||||
for i := 4; i < 10; i++ {
|
||||
message := nostr.Event{
|
||||
CreatedAt: nostr.Timestamp(i),
|
||||
@ -154,6 +161,7 @@ func TestGroupStuffABunch(t *testing.T) {
|
||||
require.NoError(t, r.Publish(ctx, message), "failed to publish kind 9")
|
||||
}
|
||||
|
||||
// check if we have received messages correctly from the subscription
|
||||
for i := 4; i < 10; i++ {
|
||||
publisher := user1pk
|
||||
if i%2 == 1 {
|
||||
@ -164,6 +172,7 @@ func TestGroupStuffABunch(t *testing.T) {
|
||||
require.Equal(t, publisher, message.PubKey)
|
||||
}
|
||||
|
||||
// events that should be rejected
|
||||
failedNoHTag := nostr.Event{
|
||||
CreatedAt: 11,
|
||||
Content: "failed",
|
||||
@ -190,6 +199,7 @@ func TestGroupStuffABunch(t *testing.T) {
|
||||
failedWrongHTag.Sign(user3)
|
||||
require.Error(t, r.Publish(ctx, failedFromNonMember), "should fail to publish kind 9 from non-member")
|
||||
|
||||
// get stored messages
|
||||
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
|
||||
@ -201,13 +211,13 @@ func TestGroupStuffABunch(t *testing.T) {
|
||||
count++
|
||||
case <-ext.EndOfStoredEvents:
|
||||
require.Equal(t, 6, count, "must have 6 messages")
|
||||
goto end1
|
||||
goto end1_1
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("select took too long")
|
||||
return
|
||||
}
|
||||
}
|
||||
end1:
|
||||
end1_1:
|
||||
}
|
||||
|
||||
// adding now a private group
|
||||
@ -330,13 +340,13 @@ func TestGroupStuffABunch(t *testing.T) {
|
||||
return
|
||||
case closed := <-failedSub.ClosedReason:
|
||||
require.Contains(t, closed, "auth-required:")
|
||||
goto end2
|
||||
goto end2_1
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("select took too long")
|
||||
return
|
||||
}
|
||||
}
|
||||
end2:
|
||||
end2_1:
|
||||
r2, err := nostr.RelayConnect(ctx, "ws://localhost:29292")
|
||||
require.NoError(t, err, "failed to connect to relay")
|
||||
|
||||
@ -360,7 +370,7 @@ func TestGroupStuffABunch(t *testing.T) {
|
||||
count++
|
||||
case <-goodSub.EndOfStoredEvents:
|
||||
require.Equal(t, 6, count, "must have 6 messages")
|
||||
goto end3
|
||||
goto end2_2
|
||||
case <-failedSub.ClosedReason:
|
||||
t.Fatal("should not have received CLOSED")
|
||||
case <-time.After(time.Second):
|
||||
@ -368,7 +378,7 @@ func TestGroupStuffABunch(t *testing.T) {
|
||||
return
|
||||
}
|
||||
}
|
||||
end3:
|
||||
end2_2:
|
||||
|
||||
anotherMessage := nostr.Event{
|
||||
CreatedAt: 11,
|
||||
@ -394,4 +404,64 @@ func TestGroupStuffABunch(t *testing.T) {
|
||||
case <-time.After(time.Millisecond * 200):
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// query members list filtering by "#p"
|
||||
for i, s := range []struct {
|
||||
key string
|
||||
groupcount int
|
||||
groupcountwhenauthedasuser2 int
|
||||
}{
|
||||
{user1pk, 1, 1}, {user2pk, 1, 2}, {user3pk, 0, 1},
|
||||
} {
|
||||
r, err := nostr.RelayConnect(ctx, "ws://localhost:29292")
|
||||
require.NoError(t, err, "failed to connect to relay")
|
||||
|
||||
ms, err := r.Subscribe(ctx, nostr.Filters{{Kinds: []int{39002}, Tags: nostr.TagMap{"p": []string{s.key}}}})
|
||||
require.NoError(t, err, "failed to subscribe to group members")
|
||||
|
||||
count := 0
|
||||
for {
|
||||
select {
|
||||
case message := <-ms.Events:
|
||||
require.Equal(t, 39002, message.Kind)
|
||||
count++
|
||||
case <-ms.EndOfStoredEvents:
|
||||
require.Equal(t, s.groupcount, count,
|
||||
"when unauthed for key%d expected %d groups but got %d",
|
||||
i+1, s.groupcount, count)
|
||||
goto end3_1
|
||||
case <-time.After(time.Second):
|
||||
t.Fatalf("select took too long for key%d", i+1)
|
||||
return
|
||||
}
|
||||
}
|
||||
end3_1:
|
||||
|
||||
// perform auth and try again
|
||||
err = r.Auth(ctx, func(authEvent *nostr.Event) error {
|
||||
authEvent.Sign(user2)
|
||||
return nil
|
||||
})
|
||||
ms, err = r.Subscribe(ctx, nostr.Filters{{Kinds: []int{39002}, Tags: nostr.TagMap{"p": []string{s.key}}}})
|
||||
require.NoError(t, err, "failed to subscribe to group members")
|
||||
|
||||
count = 0
|
||||
for {
|
||||
select {
|
||||
case message := <-ms.Events:
|
||||
require.Equal(t, 39002, message.Kind)
|
||||
count++
|
||||
case <-ms.EndOfStoredEvents:
|
||||
require.Equal(t, s.groupcountwhenauthedasuser2, count,
|
||||
"when authed for key%d expected %d groups but got %d",
|
||||
i+1, s.groupcountwhenauthedasuser2, count)
|
||||
goto end3_2
|
||||
case <-time.After(time.Second):
|
||||
return
|
||||
}
|
||||
}
|
||||
end3_2:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user