Clean up groups stuff
This commit is contained in:
+4
-4
@@ -57,7 +57,7 @@ func (bl *BlossomStore) Enable(instance *Instance) {
|
|||||||
return true, "file too large", 413
|
return true, "file too large", 413
|
||||||
}
|
}
|
||||||
|
|
||||||
if auth == nil || !instance.HasAccess(auth.PubKey) {
|
if auth == nil || !instance.Management.IsPubkeyAllowed(auth.PubKey) {
|
||||||
return true, "unauthorized", 403
|
return true, "unauthorized", 403
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ func (bl *BlossomStore) Enable(instance *Instance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
backend.RejectGet = func(ctx context.Context, auth *nostr.Event, sha256 string, ext string) (bool, string, int) {
|
backend.RejectGet = func(ctx context.Context, auth *nostr.Event, sha256 string, ext string) (bool, string, int) {
|
||||||
if auth == nil || !instance.HasAccess(auth.PubKey) {
|
if auth == nil || !instance.Management.IsPubkeyAllowed(auth.PubKey) {
|
||||||
return true, "unauthorized", 403
|
return true, "unauthorized", 403
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ func (bl *BlossomStore) Enable(instance *Instance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
backend.RejectList = func(ctx context.Context, auth *nostr.Event, pubkey nostr.PubKey) (bool, string, int) {
|
backend.RejectList = func(ctx context.Context, auth *nostr.Event, pubkey nostr.PubKey) (bool, string, int) {
|
||||||
if auth == nil || !instance.HasAccess(auth.PubKey) {
|
if auth == nil || !instance.Management.IsPubkeyAllowed(auth.PubKey) {
|
||||||
return true, "unauthorized", 403
|
return true, "unauthorized", 403
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ func (bl *BlossomStore) Enable(instance *Instance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
backend.RejectDelete = func(ctx context.Context, auth *nostr.Event, sha256 string, ext string) (bool, string, int) {
|
backend.RejectDelete = func(ctx context.Context, auth *nostr.Event, sha256 string, ext string) (bool, string, int) {
|
||||||
if auth == nil || !instance.HasAccess(auth.PubKey) {
|
if auth == nil || !instance.Management.IsPubkeyAllowed(auth.PubKey) {
|
||||||
return true, "unauthorized", 403
|
return true, "unauthorized", 403
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+75
-48
@@ -1,8 +1,6 @@
|
|||||||
package zooid
|
package zooid
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"iter"
|
|
||||||
|
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,17 +14,74 @@ func GetGroupIDFromEvent(event nostr.Event) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeGroupMetadataFilter(h string) nostr.Filter {
|
type GroupStore struct {
|
||||||
return nostr.Filter{
|
Config *Config
|
||||||
|
Events *EventStore
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GroupStore) GetMetadata(h string) nostr.Event {
|
||||||
|
filter := nostr.Filter{
|
||||||
Kinds: []nostr.Kind{nostr.KindSimpleGroupMetadata},
|
Kinds: []nostr.Kind{nostr.KindSimpleGroupMetadata},
|
||||||
Tags: nostr.TagMap{
|
Tags: nostr.TagMap{
|
||||||
"d": []string{h},
|
"d": []string{h},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for event := range g.Events.QueryEvents(filter, 1) {
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
return nostr.Event{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeGroupEventFilters(h string) []nostr.Filter {
|
func (g *GroupStore) AddMember(h string, pubkey nostr.PubKey) error {
|
||||||
return []nostr.Filter{
|
event := nostr.Event{
|
||||||
|
Kind: nostr.KindSimpleGroupPutUser,
|
||||||
|
CreatedAt: nostr.Now(),
|
||||||
|
Tags: nostr.Tags{
|
||||||
|
nostr.Tag{"p", pubkey.Hex()},
|
||||||
|
nostr.Tag{"h", h},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return g.Events.SignAndSaveEvent(event, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GroupStore) RemoveMember(h string, pubkey nostr.PubKey) error {
|
||||||
|
event := nostr.Event{
|
||||||
|
Kind: nostr.KindSimpleGroupRemoveUser,
|
||||||
|
CreatedAt: nostr.Now(),
|
||||||
|
Tags: nostr.Tags{
|
||||||
|
nostr.Tag{"p", pubkey.Hex()},
|
||||||
|
nostr.Tag{"h", h},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return g.Events.SignAndSaveEvent(event, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GroupStore) SetMetadataFromEvent(event nostr.Event) error {
|
||||||
|
tags := nostr.Tags{}
|
||||||
|
|
||||||
|
for _, tag := range event.Tags {
|
||||||
|
if len(tag) >= 2 && tag[0] == "h" {
|
||||||
|
tags = append(tags, nostr.Tag{"d", tag[1]})
|
||||||
|
} else {
|
||||||
|
tags = append(tags, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
metadataEvent := nostr.Event{
|
||||||
|
Kind: nostr.KindSimpleGroupMetadata,
|
||||||
|
CreatedAt: event.CreatedAt,
|
||||||
|
Tags: tags,
|
||||||
|
}
|
||||||
|
|
||||||
|
return g.Events.SignAndSaveEvent(metadataEvent, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GroupStore) DeleteGroup(h string) {
|
||||||
|
filters := []nostr.Filter{
|
||||||
{
|
{
|
||||||
Tags: nostr.TagMap{
|
Tags: nostr.TagMap{
|
||||||
"d": []string{h},
|
"d": []string{h},
|
||||||
@@ -38,20 +93,24 @@ func MakeGroupEventFilters(h string) []nostr.Filter {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, filter := range filters {
|
||||||
|
for event := range g.Events.QueryEvents(filter, 0) {
|
||||||
|
g.Events.DeleteEvent(event.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeGroupMembershipCheckFilter(h string, pubkey nostr.PubKey) nostr.Filter {
|
func (g *GroupStore) IsMember(h string, pubkey nostr.PubKey) bool {
|
||||||
return nostr.Filter{
|
filter := nostr.Filter{
|
||||||
Kinds: []nostr.Kind{nostr.KindSimpleGroupPutUser, nostr.KindSimpleGroupRemoveUser},
|
Kinds: []nostr.Kind{nostr.KindSimpleGroupPutUser, nostr.KindSimpleGroupRemoveUser},
|
||||||
Tags: nostr.TagMap{
|
Tags: nostr.TagMap{
|
||||||
"p": []string{pubkey.Hex()},
|
"p": []string{pubkey.Hex()},
|
||||||
"h": []string{h},
|
"h": []string{h},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func CheckGroupMembership(events iter.Seq[nostr.Event]) bool {
|
for event := range g.Events.QueryEvents(filter, 1) {
|
||||||
for event := range events {
|
|
||||||
if event.Kind == nostr.KindSimpleGroupPutUser {
|
if event.Kind == nostr.KindSimpleGroupPutUser {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -64,42 +123,10 @@ func CheckGroupMembership(events iter.Seq[nostr.Event]) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakePutUserEvent(h string, pubkey nostr.PubKey) nostr.Event {
|
func (g *GroupStore) HasAccess(h string, pubkey nostr.PubKey) bool {
|
||||||
return nostr.Event{
|
if !HasTag(g.GetMetadata(h).Tags, "closed") {
|
||||||
Kind: nostr.KindSimpleGroupPutUser,
|
return true
|
||||||
CreatedAt: nostr.Now(),
|
|
||||||
Tags: nostr.Tags{
|
|
||||||
nostr.Tag{"p", pubkey.Hex()},
|
|
||||||
nostr.Tag{"h", h},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func MakeRemoveUserEvent(h string, pubkey nostr.PubKey) nostr.Event {
|
|
||||||
return nostr.Event{
|
|
||||||
Kind: nostr.KindSimpleGroupRemoveUser,
|
|
||||||
CreatedAt: nostr.Now(),
|
|
||||||
Tags: nostr.Tags{
|
|
||||||
nostr.Tag{"p", pubkey.Hex()},
|
|
||||||
nostr.Tag{"h", h},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func MakeMetadataEvent(event nostr.Event) nostr.Event {
|
|
||||||
tags := nostr.Tags{}
|
|
||||||
|
|
||||||
for _, tag := range event.Tags {
|
|
||||||
if len(tag) >= 2 && tag[0] == "h" {
|
|
||||||
tags = append(tags, nostr.Tag{"d", tag[1]})
|
|
||||||
} else {
|
|
||||||
tags = append(tags, tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nostr.Event{
|
|
||||||
Kind: nostr.KindSimpleGroupMetadata,
|
|
||||||
CreatedAt: event.CreatedAt,
|
|
||||||
Tags: tags,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return g.IsMember(h, pubkey)
|
||||||
}
|
}
|
||||||
|
|||||||
+88
-131
@@ -20,6 +20,7 @@ type Instance struct {
|
|||||||
Events *EventStore
|
Events *EventStore
|
||||||
Blossom *BlossomStore
|
Blossom *BlossomStore
|
||||||
Management *ManagementStore
|
Management *ManagementStore
|
||||||
|
Groups *GroupStore
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeInstance(filename string) (*Instance, error) {
|
func MakeInstance(filename string) (*Instance, error) {
|
||||||
@@ -48,12 +49,18 @@ func MakeInstance(filename string) (*Instance, error) {
|
|||||||
Events: events,
|
Events: events,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
groups := &GroupStore{
|
||||||
|
Config: config,
|
||||||
|
Events: events,
|
||||||
|
}
|
||||||
|
|
||||||
instance := &Instance{
|
instance := &Instance{
|
||||||
Relay: relay,
|
Relay: relay,
|
||||||
Config: config,
|
Config: config,
|
||||||
Events: events,
|
Events: events,
|
||||||
Blossom: blossom,
|
Blossom: blossom,
|
||||||
Management: management,
|
Management: management,
|
||||||
|
Groups: groups,
|
||||||
}
|
}
|
||||||
|
|
||||||
// NIP 11 info
|
// NIP 11 info
|
||||||
@@ -82,16 +89,14 @@ func MakeInstance(filename string) (*Instance, error) {
|
|||||||
// Handlers
|
// Handlers
|
||||||
|
|
||||||
instance.Relay.OnConnect = instance.OnConnect
|
instance.Relay.OnConnect = instance.OnConnect
|
||||||
instance.Relay.OnEvent = instance.OnEvent
|
instance.Relay.PreventBroadcast = instance.PreventBroadcast
|
||||||
instance.Relay.StoreEvent = instance.StoreEvent
|
instance.Relay.StoreEvent = instance.StoreEvent
|
||||||
instance.Relay.ReplaceEvent = instance.ReplaceEvent
|
instance.Relay.ReplaceEvent = instance.ReplaceEvent
|
||||||
instance.Relay.DeleteEvent = instance.DeleteEvent
|
instance.Relay.DeleteEvent = instance.DeleteEvent
|
||||||
|
instance.Relay.OnEvent = instance.OnEvent
|
||||||
instance.Relay.OnEventSaved = instance.OnEventSaved
|
instance.Relay.OnEventSaved = instance.OnEventSaved
|
||||||
instance.Relay.OnEphemeralEvent = instance.OnEphemeralEvent
|
|
||||||
instance.Relay.OnRequest = instance.OnRequest
|
instance.Relay.OnRequest = instance.OnRequest
|
||||||
instance.Relay.QueryStored = instance.QueryStored
|
instance.Relay.QueryStored = instance.QueryStored
|
||||||
instance.Relay.RejectConnection = instance.RejectConnection
|
|
||||||
instance.Relay.PreventBroadcast = instance.PreventBroadcast
|
|
||||||
|
|
||||||
// Todo: when there's a new version of khatru
|
// Todo: when there's a new version of khatru
|
||||||
// instance.Relay.StartExpirationManager()
|
// instance.Relay.StartExpirationManager()
|
||||||
@@ -129,36 +134,15 @@ func (instance *Instance) Cleanup() {
|
|||||||
|
|
||||||
// Utility methods
|
// Utility methods
|
||||||
|
|
||||||
func (instance *Instance) IsGroupMember(id string, pubkey nostr.PubKey) bool {
|
func (instance *Instance) StripSignature(ctx context.Context, event nostr.Event) nostr.Event {
|
||||||
filter := MakeGroupMembershipCheckFilter(id, pubkey)
|
pubkey, _ := khatru.GetAuthed(ctx)
|
||||||
events := instance.Events.QueryEvents(filter, 0)
|
|
||||||
isMember := CheckGroupMembership(events)
|
|
||||||
|
|
||||||
return isMember
|
if instance.Config.Policy.StripSignatures && !instance.Config.IsAdmin(pubkey) {
|
||||||
}
|
var zeroSig [64]byte
|
||||||
|
event.Sig = zeroSig
|
||||||
func (instance *Instance) HasGroupAccess(id string, pubkey nostr.PubKey) bool {
|
|
||||||
filter := MakeGroupMetadataFilter(id)
|
|
||||||
|
|
||||||
for event := range instance.Events.QueryEvents(filter, 1) {
|
|
||||||
if !HasTag(event.Tags, "closed") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return instance.IsGroupMember(id, pubkey)
|
return event
|
||||||
}
|
|
||||||
|
|
||||||
func (instance *Instance) IsInternalEvent(event nostr.Event) bool {
|
|
||||||
if event.Kind == nostr.KindApplicationSpecificData {
|
|
||||||
tag := event.Tags.Find("d")
|
|
||||||
|
|
||||||
if tag != nil && strings.HasPrefix(tag[1], "zooid/") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (instance *Instance) AllowRecipientEvent(event nostr.Event) bool {
|
func (instance *Instance) AllowRecipientEvent(event nostr.Event) bool {
|
||||||
@@ -184,6 +168,41 @@ func (instance *Instance) AllowRecipientEvent(event nostr.Event) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (instance *Instance) IsInternalEvent(event nostr.Event) bool {
|
||||||
|
if event.Kind == nostr.KindApplicationSpecificData {
|
||||||
|
tag := event.Tags.Find("d")
|
||||||
|
|
||||||
|
if tag != nil && strings.HasPrefix(tag[1], "zooid/") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (instance *Instance) IsReadOnlyEvent(event nostr.Event) bool {
|
||||||
|
if instance.IsInternalEvent(event) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
readOnlyEventKinds := []nostr.Kind{
|
||||||
|
RELAY_ADD_MEMBER,
|
||||||
|
RELAY_REMOVE_MEMBER,
|
||||||
|
RELAY_MEMBERS,
|
||||||
|
}
|
||||||
|
|
||||||
|
return slices.Contains(readOnlyEventKinds, event.Kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (instance *Instance) IsWriteOnlyEvent(event nostr.Event) bool {
|
||||||
|
writeOnlyEventKinds := []nostr.Kind{
|
||||||
|
RELAY_JOIN,
|
||||||
|
RELAY_LEAVE,
|
||||||
|
}
|
||||||
|
|
||||||
|
return slices.Contains(writeOnlyEventKinds, event.Kind)
|
||||||
|
}
|
||||||
|
|
||||||
func (instance *Instance) GenerateInviteEvent(pubkey nostr.PubKey) nostr.Event {
|
func (instance *Instance) GenerateInviteEvent(pubkey nostr.PubKey) nostr.Event {
|
||||||
filter := nostr.Filter{
|
filter := nostr.Filter{
|
||||||
Kinds: []nostr.Kind{RELAY_INVITE},
|
Kinds: []nostr.Kind{RELAY_INVITE},
|
||||||
@@ -210,40 +229,30 @@ func (instance *Instance) GenerateInviteEvent(pubkey nostr.PubKey) nostr.Event {
|
|||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
func (instance *Instance) OnJoinEvent(event nostr.Event) (reject bool, msg string) {
|
|
||||||
claimTag := event.Tags.Find("claim")
|
|
||||||
|
|
||||||
if claimTag == nil {
|
|
||||||
return true, "invalid: no claim tag"
|
|
||||||
}
|
|
||||||
|
|
||||||
filter := nostr.Filter{
|
|
||||||
Kinds: []nostr.Kind{RELAY_INVITE},
|
|
||||||
}
|
|
||||||
|
|
||||||
for event := range instance.Events.QueryEvents(filter, 0) {
|
|
||||||
if event.Tags.FindWithValue("claim", claimTag[1]) != nil {
|
|
||||||
return false, ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, "invalid: failed to validate invite code"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (instance *Instance) GetGroupMetadataEvent(h string) nostr.Event {
|
|
||||||
for event := range instance.Events.QueryEvents(MakeGroupMetadataFilter(h), 1) {
|
|
||||||
return event
|
|
||||||
}
|
|
||||||
|
|
||||||
return nostr.Event{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
|
|
||||||
func (instance *Instance) OnConnect(ctx context.Context) {
|
func (instance *Instance) OnConnect(ctx context.Context) {
|
||||||
khatru.RequestAuth(ctx)
|
khatru.RequestAuth(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (instance *Instance) PreventBroadcast(ws *khatru.WebSocket, event nostr.Event) bool {
|
||||||
|
return instance.IsWriteOnlyEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (instance *Instance) StoreEvent(ctx context.Context, event nostr.Event) error {
|
||||||
|
return instance.Events.SaveEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (instance *Instance) ReplaceEvent(ctx context.Context, event nostr.Event) error {
|
||||||
|
return instance.Events.ReplaceEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (instance *Instance) DeleteEvent(ctx context.Context, id nostr.ID) error {
|
||||||
|
return instance.Events.DeleteEvent(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event publishing
|
||||||
|
|
||||||
func (instance *Instance) OnEvent(ctx context.Context, event nostr.Event) (reject bool, msg string) {
|
func (instance *Instance) OnEvent(ctx context.Context, event nostr.Event) (reject bool, msg string) {
|
||||||
if instance.AllowRecipientEvent(event) {
|
if instance.AllowRecipientEvent(event) {
|
||||||
return false, ""
|
return false, ""
|
||||||
@@ -258,15 +267,15 @@ func (instance *Instance) OnEvent(ctx context.Context, event nostr.Event) (rejec
|
|||||||
}
|
}
|
||||||
|
|
||||||
if event.Kind == RELAY_JOIN {
|
if event.Kind == RELAY_JOIN {
|
||||||
return instance.OnJoinEvent(event)
|
return instance.Management.ValidateJoinRequest(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !instance.Management.IsPubkeyAllowed(pubkey) {
|
if !instance.Management.IsPubkeyAllowed(pubkey) {
|
||||||
return true, "restricted: you are not a member of this relay"
|
return true, "restricted: you are not a member of this relay"
|
||||||
}
|
}
|
||||||
|
|
||||||
if instance.IsInternalEvent(event) {
|
if instance.IsReadOnlyEvent(event) {
|
||||||
return true, "invalid: this event is not accepted"
|
return true, "invalid: this event's kind is not accepted"
|
||||||
}
|
}
|
||||||
|
|
||||||
if slices.Contains(nip29.MetadataEventKinds, event.Kind) {
|
if slices.Contains(nip29.MetadataEventKinds, event.Kind) {
|
||||||
@@ -294,7 +303,7 @@ func (instance *Instance) OnEvent(ctx context.Context, event nostr.Event) (rejec
|
|||||||
return true, "invalid: h tag is required"
|
return true, "invalid: h tag is required"
|
||||||
}
|
}
|
||||||
|
|
||||||
meta := instance.GetGroupMetadataEvent(h)
|
meta := instance.Groups.GetMetadata(h)
|
||||||
|
|
||||||
if event.Kind == nostr.KindSimpleGroupCreateGroup {
|
if event.Kind == nostr.KindSimpleGroupCreateGroup {
|
||||||
if !IsEmptyEvent(meta) {
|
if !IsEmptyEvent(meta) {
|
||||||
@@ -304,21 +313,21 @@ func (instance *Instance) OnEvent(ctx context.Context, event nostr.Event) (rejec
|
|||||||
return true, "invalid: no such group exists"
|
return true, "invalid: no such group exists"
|
||||||
}
|
}
|
||||||
|
|
||||||
if event.Kind == nostr.KindSimpleGroupJoinRequest && instance.IsGroupMember(h, event.PubKey) {
|
if event.Kind == nostr.KindSimpleGroupJoinRequest && instance.Groups.IsMember(h, event.PubKey) {
|
||||||
return true, "duplicate: already a member"
|
return true, "duplicate: already a member"
|
||||||
}
|
}
|
||||||
|
|
||||||
if event.Kind == nostr.KindSimpleGroupLeaveRequest && !instance.IsGroupMember(h, event.PubKey) {
|
if event.Kind == nostr.KindSimpleGroupLeaveRequest && !instance.Groups.IsMember(h, event.PubKey) {
|
||||||
return true, "duplicate: not currently a member"
|
return true, "duplicate: not currently a member"
|
||||||
}
|
}
|
||||||
} else if h != "" {
|
} else if h != "" {
|
||||||
meta := instance.GetGroupMetadataEvent(h)
|
meta := instance.Groups.GetMetadata(h)
|
||||||
|
|
||||||
if IsEmptyEvent(meta) {
|
if IsEmptyEvent(meta) {
|
||||||
return true, "invalid: no such group exists"
|
return true, "invalid: no such group exists"
|
||||||
}
|
}
|
||||||
|
|
||||||
if HasTag(meta.Tags, "closed") && !instance.IsGroupMember(h, pubkey) {
|
if HasTag(meta.Tags, "closed") && !instance.Groups.IsMember(h, pubkey) {
|
||||||
return true, "restricted: you are not a member of that group"
|
return true, "restricted: you are not a member of that group"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -330,25 +339,7 @@ func (instance *Instance) OnEvent(ctx context.Context, event nostr.Event) (rejec
|
|||||||
return false, ""
|
return false, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (instance *Instance) StoreEvent(ctx context.Context, event nostr.Event) error {
|
|
||||||
return instance.Events.SaveEvent(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (instance *Instance) ReplaceEvent(ctx context.Context, event nostr.Event) error {
|
|
||||||
return instance.Events.ReplaceEvent(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (instance *Instance) DeleteEvent(ctx context.Context, id nostr.ID) error {
|
|
||||||
return instance.Events.DeleteEvent(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (instance *Instance) OnEventSaved(ctx context.Context, event nostr.Event) {
|
func (instance *Instance) OnEventSaved(ctx context.Context, event nostr.Event) {
|
||||||
addEvent := func(newEvent nostr.Event) {
|
|
||||||
if err := instance.Events.SignAndSaveEvent(newEvent, true); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if event.Kind == RELAY_JOIN {
|
if event.Kind == RELAY_JOIN {
|
||||||
instance.Management.AllowPubkey(event.PubKey)
|
instance.Management.AllowPubkey(event.PubKey)
|
||||||
}
|
}
|
||||||
@@ -359,39 +350,27 @@ func (instance *Instance) OnEventSaved(ctx context.Context, event nostr.Event) {
|
|||||||
|
|
||||||
if event.Kind == nostr.KindSimpleGroupJoinRequest && instance.Config.Groups.AutoJoin {
|
if event.Kind == nostr.KindSimpleGroupJoinRequest && instance.Config.Groups.AutoJoin {
|
||||||
h := GetGroupIDFromEvent(event)
|
h := GetGroupIDFromEvent(event)
|
||||||
meta := instance.GetGroupMetadataEvent(h)
|
meta := instance.Groups.GetMetadata(h)
|
||||||
|
|
||||||
if !HasTag(meta.Tags, "closed") {
|
if !HasTag(meta.Tags, "closed") {
|
||||||
addEvent(MakePutUserEvent(h, event.PubKey))
|
instance.Groups.AddMember(h, event.PubKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if event.Kind == nostr.KindSimpleGroupLeaveRequest && instance.Config.Groups.AutoLeave {
|
if event.Kind == nostr.KindSimpleGroupLeaveRequest && instance.Config.Groups.AutoLeave {
|
||||||
addEvent(MakeRemoveUserEvent(GetGroupIDFromEvent(event), event.PubKey))
|
instance.Groups.RemoveMember(GetGroupIDFromEvent(event), event.PubKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if event.Kind == nostr.KindSimpleGroupCreateGroup {
|
if event.Kind == nostr.KindSimpleGroupCreateGroup || event.Kind == nostr.KindSimpleGroupEditMetadata {
|
||||||
addEvent(MakeMetadataEvent(event))
|
instance.Groups.SetMetadataFromEvent(event)
|
||||||
}
|
|
||||||
|
|
||||||
if event.Kind == nostr.KindSimpleGroupEditMetadata {
|
|
||||||
addEvent(MakeMetadataEvent(event))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if event.Kind == nostr.KindSimpleGroupDeleteGroup {
|
if event.Kind == nostr.KindSimpleGroupDeleteGroup {
|
||||||
for _, filter := range MakeGroupEventFilters(GetGroupIDFromEvent(event)) {
|
instance.Groups.DeleteGroup(GetGroupIDFromEvent(event))
|
||||||
for event := range instance.Events.QueryEvents(filter, 0) {
|
|
||||||
instance.Events.DeleteEvent(event.ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (instance *Instance) OnEphemeralEvent(ctx context.Context, event nostr.Event) {
|
// Requests
|
||||||
if slices.Contains([]nostr.Kind{RELAY_INVITE, RELAY_JOIN}, event.Kind) {
|
|
||||||
instance.Events.SaveEvent(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (instance *Instance) OnRequest(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
|
func (instance *Instance) OnRequest(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
|
||||||
pubkey, ok := khatru.GetAuthed(ctx)
|
pubkey, ok := khatru.GetAuthed(ctx)
|
||||||
@@ -416,30 +395,16 @@ func (instance *Instance) QueryStored(ctx context.Context, filter nostr.Filter)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pubkey, isAuthed := khatru.GetAuthed(ctx)
|
pubkey, _ := khatru.GetAuthed(ctx)
|
||||||
|
|
||||||
if !isAuthed {
|
|
||||||
log.Panic("Unauthorized user was allowed to query events")
|
|
||||||
}
|
|
||||||
|
|
||||||
stripSignature := func(event nostr.Event) nostr.Event {
|
|
||||||
if instance.Config.Policy.StripSignatures && !instance.Config.IsAdmin(pubkey) {
|
|
||||||
var zeroSig [64]byte
|
|
||||||
event.Sig = zeroSig
|
|
||||||
}
|
|
||||||
|
|
||||||
return event
|
|
||||||
}
|
|
||||||
|
|
||||||
if slices.Contains(filter.Kinds, RELAY_INVITE) && instance.Config.CanInvite(pubkey) {
|
if slices.Contains(filter.Kinds, RELAY_INVITE) && instance.Config.CanInvite(pubkey) {
|
||||||
if !yield(stripSignature(instance.GenerateInviteEvent(pubkey))) {
|
if !yield(instance.StripSignature(ctx, instance.GenerateInviteEvent(pubkey))) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for event := range instance.Events.QueryEvents(filter, 1000) {
|
for event := range instance.Events.QueryEvents(filter, 1000) {
|
||||||
// We save some ephemeral events for bookkeeping, don't return them
|
if instance.IsWriteOnlyEvent(event) {
|
||||||
if event.Kind.IsEphemeral() {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,7 +415,7 @@ func (instance *Instance) QueryStored(ctx context.Context, filter nostr.Filter)
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !instance.HasGroupAccess(h, pubkey) {
|
if !instance.Groups.HasAccess(h, pubkey) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -459,18 +424,10 @@ func (instance *Instance) QueryStored(ctx context.Context, filter nostr.Filter)
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !yield(stripSignature(event)) {
|
if !yield(instance.StripSignature(ctx, event)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (instance *Instance) RejectConnection(r *http.Request) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (instance *Instance) PreventBroadcast(ws *khatru.WebSocket, event nostr.Event) bool {
|
|
||||||
return event.Kind == RELAY_JOIN
|
|
||||||
}
|
|
||||||
|
|||||||
+64
-32
@@ -22,11 +22,51 @@ import (
|
|||||||
// All actions are idempotent, and won't do anything if conditions are already correct.
|
// All actions are idempotent, and won't do anything if conditions are already correct.
|
||||||
|
|
||||||
type ManagementStore struct {
|
type ManagementStore struct {
|
||||||
Relay *khatru.Relay
|
|
||||||
Config *Config
|
Config *Config
|
||||||
Events *EventStore
|
Events *EventStore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Banned events
|
||||||
|
|
||||||
|
func (m *ManagementStore) GetBannedEventItems() []nip86.IDReason {
|
||||||
|
items := make([]nip86.IDReason, 0)
|
||||||
|
for tag := range m.Events.GetOrCreateApplicationSpecificData(BANNED_EVENTS).Tags.FindAll("event") {
|
||||||
|
items = append(items, nip86.IDReason{
|
||||||
|
ID: tag[1],
|
||||||
|
Reason: tag[2],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ManagementStore) BanEvent(id nostr.ID, reason string) error {
|
||||||
|
if err := m.Events.DeleteEvent(id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
event := m.Events.GetOrCreateApplicationSpecificData(BANNED_EVENTS)
|
||||||
|
event.Tags = append(event.Tags, nostr.Tag{"event", id.Hex(), reason})
|
||||||
|
|
||||||
|
return m.Events.SignAndSaveEvent(event, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ManagementStore) AllowEvent(id nostr.ID, reason string) error {
|
||||||
|
event := m.Events.GetOrCreateApplicationSpecificData(BANNED_EVENTS)
|
||||||
|
event.Tags = Filter(event.Tags, func(t nostr.Tag) bool {
|
||||||
|
return t[1] == id.Hex()
|
||||||
|
})
|
||||||
|
|
||||||
|
return m.Events.SignAndSaveEvent(event, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ManagementStore) EventIsBanned(id nostr.ID) bool {
|
||||||
|
event := m.Events.GetOrCreateApplicationSpecificData(BANNED_EVENTS)
|
||||||
|
tag := event.Tags.FindWithValue("event", id.Hex())
|
||||||
|
|
||||||
|
return tag != nil
|
||||||
|
}
|
||||||
|
|
||||||
// Internal banned pubkeys list
|
// Internal banned pubkeys list
|
||||||
|
|
||||||
func (m *ManagementStore) GetBannedPubkeyItems() []nip86.PubKeyReason {
|
func (m *ManagementStore) GetBannedPubkeyItems() []nip86.PubKeyReason {
|
||||||
@@ -73,6 +113,13 @@ func (m *ManagementStore) RemoveBannedPubkey(pubkey nostr.PubKey) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *ManagementStore) PubkeyIsBanned(pubkey nostr.PubKey) bool {
|
||||||
|
event := m.Events.GetOrCreateApplicationSpecificData(BANNED_PUBKEYS)
|
||||||
|
tag := event.Tags.FindWithValue("pubkey", pubkey.Hex())
|
||||||
|
|
||||||
|
return tag != nil
|
||||||
|
}
|
||||||
|
|
||||||
// Membership
|
// Membership
|
||||||
|
|
||||||
func (m *ManagementStore) GetMembers() []nostr.PubKey {
|
func (m *ManagementStore) GetMembers() []nostr.PubKey {
|
||||||
@@ -240,45 +287,30 @@ func (m *ManagementStore) AllowPubkey(pubkey nostr.PubKey) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Banned events
|
// Joining
|
||||||
|
|
||||||
func (m *ManagementStore) GetBannedEventItems() []nip86.IDReason {
|
func (m *ManagementStore) ValidateJoinRequest(event nostr.Event) (reject bool, err string) {
|
||||||
items := make([]nip86.IDReason, 0)
|
if m.PubkeyIsBanned(event.PubKey) {
|
||||||
for tag := range m.Events.GetOrCreateApplicationSpecificData(BANNED_EVENTS).Tags.FindAll("event") {
|
return true, "invalid: you have been banned from this relay"
|
||||||
items = append(items, nip86.IDReason{
|
|
||||||
ID: tag[1],
|
|
||||||
Reason: tag[2],
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return items
|
claimTag := event.Tags.Find("claim")
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ManagementStore) BanEvent(id nostr.ID, reason string) error {
|
if claimTag == nil {
|
||||||
if err := m.Events.DeleteEvent(id); err != nil {
|
return true, "invalid: no claim tag"
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
event := m.Events.GetOrCreateApplicationSpecificData(BANNED_EVENTS)
|
filter := nostr.Filter{
|
||||||
event.Tags = append(event.Tags, nostr.Tag{"event", id.Hex(), reason})
|
Kinds: []nostr.Kind{RELAY_INVITE},
|
||||||
|
}
|
||||||
|
|
||||||
return m.Events.SignAndSaveEvent(event, false)
|
for event := range m.Events.QueryEvents(filter, 0) {
|
||||||
}
|
if event.Tags.FindWithValue("claim", claimTag[1]) != nil {
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *ManagementStore) AllowEvent(id nostr.ID, reason string) error {
|
return true, "invalid: failed to validate invite code"
|
||||||
event := m.Events.GetOrCreateApplicationSpecificData(BANNED_EVENTS)
|
|
||||||
event.Tags = Filter(event.Tags, func(t nostr.Tag) bool {
|
|
||||||
return t[1] == id.Hex()
|
|
||||||
})
|
|
||||||
|
|
||||||
return m.Events.SignAndSaveEvent(event, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ManagementStore) EventIsBanned(id nostr.ID) bool {
|
|
||||||
event := m.Events.GetOrCreateApplicationSpecificData(BANNED_EVENTS)
|
|
||||||
tag := event.Tags.FindWithValue("event", id.Hex())
|
|
||||||
|
|
||||||
return tag != nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Middleware
|
// Middleware
|
||||||
|
|||||||
Reference in New Issue
Block a user