forked from coracle/zooid
Refactor management stuff
This commit is contained in:
+33
-6
@@ -5,15 +5,16 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"iter"
|
"iter"
|
||||||
"log"
|
|
||||||
|
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
"fiatjaf.com/nostr/eventstore"
|
"fiatjaf.com/nostr/eventstore"
|
||||||
|
"fiatjaf.com/nostr/khatru"
|
||||||
"github.com/Masterminds/squirrel"
|
"github.com/Masterminds/squirrel"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EventStore struct {
|
type EventStore struct {
|
||||||
|
Relay *khatru.Relay
|
||||||
Config *Config
|
Config *Config
|
||||||
Schema *Schema
|
Schema *Schema
|
||||||
FTSAvailable bool
|
FTSAvailable bool
|
||||||
@@ -337,6 +338,22 @@ func (events *EventStore) CountEvents(filter nostr.Filter) (uint32, error) {
|
|||||||
|
|
||||||
// Non-eventstore methods
|
// Non-eventstore methods
|
||||||
|
|
||||||
|
func (events *EventStore) SignAndSaveEvent(event nostr.Event, broadcast bool) error {
|
||||||
|
if err := events.Config.Sign(&event); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := events.SaveEvent(event); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if broadcast {
|
||||||
|
events.Relay.BroadcastEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (events *EventStore) GetOrCreateApplicationSpecificData(d string) nostr.Event {
|
func (events *EventStore) GetOrCreateApplicationSpecificData(d string) nostr.Event {
|
||||||
filter := nostr.Filter{
|
filter := nostr.Filter{
|
||||||
Kinds: []nostr.Kind{nostr.KindApplicationSpecificData},
|
Kinds: []nostr.Kind{nostr.KindApplicationSpecificData},
|
||||||
@@ -349,19 +366,29 @@ func (events *EventStore) GetOrCreateApplicationSpecificData(d string) nostr.Eve
|
|||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
event := nostr.Event{
|
return nostr.Event{
|
||||||
Kind: nostr.KindApplicationSpecificData,
|
Kind: nostr.KindApplicationSpecificData,
|
||||||
CreatedAt: nostr.Now(),
|
CreatedAt: nostr.Now(),
|
||||||
Tags: nostr.Tags{
|
Tags: nostr.Tags{
|
||||||
[]string{"d", d},
|
[]string{"d", d},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := events.Config.Sign(&event); err != nil {
|
func (events *EventStore) GetOrCreateMemberList() nostr.Event {
|
||||||
log.Println("Failed to sign application specific event: %w", err)
|
filter := nostr.Filter{
|
||||||
|
Kinds: []nostr.Kind{RELAY_MEMBERS},
|
||||||
}
|
}
|
||||||
|
|
||||||
events.SaveEvent(event)
|
for event := range events.QueryEvents(filter, 1) {
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
return event
|
return nostr.Event{
|
||||||
|
Kind: nostr.KindApplicationSpecificData,
|
||||||
|
CreatedAt: nostr.Now(),
|
||||||
|
Tags: nostr.Tags{
|
||||||
|
[]string{"-"},
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-30
@@ -9,18 +9,17 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
"fiatjaf.com/nostr/eventstore"
|
|
||||||
"fiatjaf.com/nostr/khatru"
|
"fiatjaf.com/nostr/khatru"
|
||||||
"fiatjaf.com/nostr/nip29"
|
"fiatjaf.com/nostr/nip29"
|
||||||
"github.com/gosimple/slug"
|
"github.com/gosimple/slug"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Instance struct {
|
type Instance struct {
|
||||||
|
Relay *khatru.Relay
|
||||||
Config *Config
|
Config *Config
|
||||||
Events eventstore.Store
|
Events *EventStore
|
||||||
Blossom *BlossomStore
|
Blossom *BlossomStore
|
||||||
Management *ManagementStore
|
Management *ManagementStore
|
||||||
Relay *khatru.Relay
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeInstance(filename string) (*Instance, error) {
|
func MakeInstance(filename string) (*Instance, error) {
|
||||||
@@ -29,7 +28,10 @@ func MakeInstance(filename string) (*Instance, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
relay := khatru.NewRelay()
|
||||||
|
|
||||||
events := &EventStore{
|
events := &EventStore{
|
||||||
|
Relay: relay,
|
||||||
Config: config,
|
Config: config,
|
||||||
Schema: &Schema{
|
Schema: &Schema{
|
||||||
Name: slug.Make(config.Schema),
|
Name: slug.Make(config.Schema),
|
||||||
@@ -47,11 +49,11 @@ func MakeInstance(filename string) (*Instance, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
instance := &Instance{
|
instance := &Instance{
|
||||||
|
Relay: relay,
|
||||||
Config: config,
|
Config: config,
|
||||||
Events: events,
|
Events: events,
|
||||||
Blossom: blossom,
|
Blossom: blossom,
|
||||||
Management: management,
|
Management: management,
|
||||||
Relay: khatru.NewRelay(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NIP 11 info
|
// NIP 11 info
|
||||||
@@ -127,10 +129,6 @@ func (instance *Instance) Cleanup() {
|
|||||||
|
|
||||||
// Utility methods
|
// Utility methods
|
||||||
|
|
||||||
func (instance *Instance) HasAccess(pubkey nostr.PubKey) bool {
|
|
||||||
return instance.Management.PubkeyIsAllowed(pubkey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (instance *Instance) IsGroupMember(id string, pubkey nostr.PubKey) bool {
|
func (instance *Instance) IsGroupMember(id string, pubkey nostr.PubKey) bool {
|
||||||
filter := MakeGroupMembershipCheckFilter(id, pubkey)
|
filter := MakeGroupMembershipCheckFilter(id, pubkey)
|
||||||
events := instance.Events.QueryEvents(filter, 0)
|
events := instance.Events.QueryEvents(filter, 0)
|
||||||
@@ -177,7 +175,7 @@ func (instance *Instance) AllowRecipientEvent(event nostr.Event) bool {
|
|||||||
if recipientTag != nil {
|
if recipientTag != nil {
|
||||||
pubkey, err := nostr.PubKeyFromHex(recipientTag[1])
|
pubkey, err := nostr.PubKeyFromHex(recipientTag[1])
|
||||||
|
|
||||||
if err == nil && instance.HasAccess(pubkey) {
|
if err == nil && instance.Management.IsPubkeyAllowed(pubkey) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,7 +186,7 @@ func (instance *Instance) AllowRecipientEvent(event nostr.Event) bool {
|
|||||||
|
|
||||||
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{AUTH_INVITE},
|
Kinds: []nostr.Kind{RELAY_INVITE},
|
||||||
Authors: []nostr.PubKey{pubkey},
|
Authors: []nostr.PubKey{pubkey},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +195,7 @@ func (instance *Instance) GenerateInviteEvent(pubkey nostr.PubKey) nostr.Event {
|
|||||||
}
|
}
|
||||||
|
|
||||||
event := nostr.Event{
|
event := nostr.Event{
|
||||||
Kind: AUTH_INVITE,
|
Kind: RELAY_INVITE,
|
||||||
CreatedAt: nostr.Now(),
|
CreatedAt: nostr.Now(),
|
||||||
Tags: nostr.Tags{
|
Tags: nostr.Tags{
|
||||||
[]string{"claim", RandomString(8)},
|
[]string{"claim", RandomString(8)},
|
||||||
@@ -205,14 +203,10 @@ func (instance *Instance) GenerateInviteEvent(pubkey nostr.PubKey) nostr.Event {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := instance.Config.Sign(&event); err != nil {
|
if err := instance.Events.SignAndSaveEvent(event, false); err != nil {
|
||||||
log.Printf("Failed to sign invite event: %v", err)
|
log.Printf("Failed to sign invite event: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := instance.Events.SaveEvent(event); err != nil {
|
|
||||||
log.Printf("Failed to save invite event: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +218,7 @@ func (instance *Instance) OnJoinEvent(event nostr.Event) (reject bool, msg strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
filter := nostr.Filter{
|
filter := nostr.Filter{
|
||||||
Kinds: []nostr.Kind{AUTH_INVITE},
|
Kinds: []nostr.Kind{RELAY_INVITE},
|
||||||
}
|
}
|
||||||
|
|
||||||
for event := range instance.Events.QueryEvents(filter, 0) {
|
for event := range instance.Events.QueryEvents(filter, 0) {
|
||||||
@@ -263,11 +257,11 @@ func (instance *Instance) OnEvent(ctx context.Context, event nostr.Event) (rejec
|
|||||||
return true, "restricted: you cannot publish events on behalf of others"
|
return true, "restricted: you cannot publish events on behalf of others"
|
||||||
}
|
}
|
||||||
|
|
||||||
if event.Kind == AUTH_JOIN {
|
if event.Kind == RELAY_JOIN {
|
||||||
return instance.OnJoinEvent(event)
|
return instance.OnJoinEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !instance.HasAccess(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"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,17 +344,19 @@ func (instance *Instance) DeleteEvent(ctx context.Context, id nostr.ID) error {
|
|||||||
|
|
||||||
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) {
|
addEvent := func(newEvent nostr.Event) {
|
||||||
if err := instance.Config.Sign(&newEvent); err != nil {
|
if err := instance.Events.SignAndSaveEvent(newEvent, true); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
} else {
|
|
||||||
if err := instance.Events.SaveEvent(newEvent); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
} else {
|
|
||||||
instance.Relay.BroadcastEvent(newEvent)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if event.Kind == RELAY_JOIN {
|
||||||
|
instance.Management.AllowPubkey(event.PubKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Kind == RELAY_LEAVE {
|
||||||
|
instance.Management.BanPubkey(event.PubKey, "exited relay")
|
||||||
|
}
|
||||||
|
|
||||||
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.GetGroupMetadataEvent(h)
|
||||||
@@ -392,7 +388,7 @@ func (instance *Instance) OnEventSaved(ctx context.Context, event nostr.Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (instance *Instance) OnEphemeralEvent(ctx context.Context, event nostr.Event) {
|
func (instance *Instance) OnEphemeralEvent(ctx context.Context, event nostr.Event) {
|
||||||
if slices.Contains([]nostr.Kind{AUTH_INVITE, AUTH_JOIN}, event.Kind) {
|
if slices.Contains([]nostr.Kind{RELAY_INVITE, RELAY_JOIN}, event.Kind) {
|
||||||
instance.Events.SaveEvent(event)
|
instance.Events.SaveEvent(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -404,7 +400,7 @@ func (instance *Instance) OnRequest(ctx context.Context, filter nostr.Filter) (r
|
|||||||
return true, "auth-required: authentication is required for access"
|
return true, "auth-required: authentication is required for access"
|
||||||
}
|
}
|
||||||
|
|
||||||
if !instance.HasAccess(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"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,7 +431,7 @@ func (instance *Instance) QueryStored(ctx context.Context, filter nostr.Filter)
|
|||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
if slices.Contains(filter.Kinds, AUTH_INVITE) && instance.Config.CanInvite(pubkey) {
|
if slices.Contains(filter.Kinds, RELAY_INVITE) && instance.Config.CanInvite(pubkey) {
|
||||||
if !yield(stripSignature(instance.GenerateInviteEvent(pubkey))) {
|
if !yield(stripSignature(instance.GenerateInviteEvent(pubkey))) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -476,5 +472,5 @@ func (instance *Instance) RejectConnection(r *http.Request) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (instance *Instance) PreventBroadcast(ws *khatru.WebSocket, event nostr.Event) bool {
|
func (instance *Instance) PreventBroadcast(ws *khatru.WebSocket, event nostr.Event) bool {
|
||||||
return event.Kind == AUTH_JOIN
|
return event.Kind == RELAY_JOIN
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ func TestInstance_HasAccess(t *testing.T) {
|
|||||||
|
|
||||||
// Add a join event for the user (must be signed by the user)
|
// Add a join event for the user (must be signed by the user)
|
||||||
joinEvent := nostr.Event{
|
joinEvent := nostr.Event{
|
||||||
Kind: AUTH_JOIN,
|
Kind: RELAY_JOIN,
|
||||||
CreatedAt: nostr.Now(),
|
CreatedAt: nostr.Now(),
|
||||||
PubKey: userPubkey,
|
PubKey: userPubkey,
|
||||||
Tags: nostr.Tags{{"claim", "test"}},
|
Tags: nostr.Tags{{"claim", "test"}},
|
||||||
@@ -211,7 +211,7 @@ func TestInstance_AllowRecipientEvent(t *testing.T) {
|
|||||||
|
|
||||||
// Add user access
|
// Add user access
|
||||||
joinEvent := nostr.Event{
|
joinEvent := nostr.Event{
|
||||||
Kind: AUTH_JOIN,
|
Kind: RELAY_JOIN,
|
||||||
CreatedAt: nostr.Now(),
|
CreatedAt: nostr.Now(),
|
||||||
PubKey: userPubkey,
|
PubKey: userPubkey,
|
||||||
Tags: nostr.Tags{{"claim", "test"}},
|
Tags: nostr.Tags{{"claim", "test"}},
|
||||||
@@ -285,8 +285,8 @@ func TestInstance_GenerateInviteEvent(t *testing.T) {
|
|||||||
inviteEvent := instance.GenerateInviteEvent(userPubkey)
|
inviteEvent := instance.GenerateInviteEvent(userPubkey)
|
||||||
|
|
||||||
// Test event properties
|
// Test event properties
|
||||||
if inviteEvent.Kind != AUTH_INVITE {
|
if inviteEvent.Kind != RELAY_INVITE {
|
||||||
t.Errorf("GenerateInviteEvent() kind = %v, want %v", inviteEvent.Kind, AUTH_INVITE)
|
t.Errorf("GenerateInviteEvent() kind = %v, want %v", inviteEvent.Kind, RELAY_INVITE)
|
||||||
}
|
}
|
||||||
|
|
||||||
if inviteEvent.PubKey != instance.Config.Secret.Public() {
|
if inviteEvent.PubKey != instance.Config.Secret.Public() {
|
||||||
@@ -332,7 +332,7 @@ func TestInstance_OnJoinEvent(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "valid join event",
|
name: "valid join event",
|
||||||
joinEvent: nostr.Event{
|
joinEvent: nostr.Event{
|
||||||
Kind: AUTH_JOIN,
|
Kind: RELAY_JOIN,
|
||||||
Tags: nostr.Tags{{"claim", claimTag[1]}},
|
Tags: nostr.Tags{{"claim", claimTag[1]}},
|
||||||
},
|
},
|
||||||
wantReject: false,
|
wantReject: false,
|
||||||
@@ -341,7 +341,7 @@ func TestInstance_OnJoinEvent(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "join event without claim",
|
name: "join event without claim",
|
||||||
joinEvent: nostr.Event{
|
joinEvent: nostr.Event{
|
||||||
Kind: AUTH_JOIN,
|
Kind: RELAY_JOIN,
|
||||||
Tags: nostr.Tags{},
|
Tags: nostr.Tags{},
|
||||||
},
|
},
|
||||||
wantReject: true,
|
wantReject: true,
|
||||||
@@ -350,7 +350,7 @@ func TestInstance_OnJoinEvent(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "join event with invalid claim",
|
name: "join event with invalid claim",
|
||||||
joinEvent: nostr.Event{
|
joinEvent: nostr.Event{
|
||||||
Kind: AUTH_JOIN,
|
Kind: RELAY_JOIN,
|
||||||
Tags: nostr.Tags{{"claim", "invalid-claim"}},
|
Tags: nostr.Tags{{"claim", "invalid-claim"}},
|
||||||
},
|
},
|
||||||
wantReject: true,
|
wantReject: true,
|
||||||
@@ -476,7 +476,7 @@ func TestInstance_HasAccess_WithBannedUser(t *testing.T) {
|
|||||||
|
|
||||||
// Test banned user has no access even with join event
|
// Test banned user has no access even with join event
|
||||||
joinEvent := nostr.Event{
|
joinEvent := nostr.Event{
|
||||||
Kind: AUTH_JOIN,
|
Kind: RELAY_JOIN,
|
||||||
CreatedAt: nostr.Now(),
|
CreatedAt: nostr.Now(),
|
||||||
PubKey: userPubkey,
|
PubKey: userPubkey,
|
||||||
Tags: nostr.Tags{{"claim", "test"}},
|
Tags: nostr.Tags{{"claim", "test"}},
|
||||||
|
|||||||
+172
-97
@@ -8,12 +8,26 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Management store takes care of all nip 86 methods, as well as defining actions for internal use.
|
||||||
|
//
|
||||||
|
// The banned pubkeys list is a NIP 78 application-specific event, which keeps track of which pubkeys
|
||||||
|
// have been banned, independently of the members list. Banned events works the same way.
|
||||||
|
//
|
||||||
|
// Membership is implemented as defined here https://github.com/nostr-protocol/nips/pull/1079/files, using
|
||||||
|
// both membership lists and add/remove events.
|
||||||
|
//
|
||||||
|
// Actions like BanPubkey and AllowPubkey synchronize ban and membership lists. These should be called in most
|
||||||
|
// cases, unless you're trying to do something more advanced.
|
||||||
|
//
|
||||||
|
// 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 pubkeys
|
// Internal banned pubkeys list
|
||||||
|
|
||||||
func (m *ManagementStore) GetBannedPubkeyItems() []nip86.PubKeyReason {
|
func (m *ManagementStore) GetBannedPubkeyItems() []nip86.PubKeyReason {
|
||||||
event := m.Events.GetOrCreateApplicationSpecificData(BANNED_PUBKEYS)
|
event := m.Events.GetOrCreateApplicationSpecificData(BANNED_PUBKEYS)
|
||||||
@@ -29,30 +43,135 @@ func (m *ManagementStore) GetBannedPubkeyItems() []nip86.PubKeyReason {
|
|||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ManagementStore) GetBannedPubkeys() []nostr.PubKey {
|
func (m *ManagementStore) AddBannedPubkey(pubkey nostr.PubKey, reason string) error {
|
||||||
|
event := m.Events.GetOrCreateApplicationSpecificData(BANNED_PUBKEYS)
|
||||||
|
|
||||||
|
if event.Tags.FindWithValue("pubkey", pubkey.Hex()) == nil {
|
||||||
|
event.Tags = append(event.Tags, nostr.Tag{"pubkey", pubkey.Hex(), reason})
|
||||||
|
|
||||||
|
if err := m.Events.SignAndSaveEvent(event, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ManagementStore) RemoveBannedPubkey(pubkey nostr.PubKey) error {
|
||||||
|
event := m.Events.GetOrCreateApplicationSpecificData(BANNED_PUBKEYS)
|
||||||
|
|
||||||
|
if event.Tags.FindWithValue("pubkey", pubkey.Hex()) != nil {
|
||||||
|
event.Tags = Filter(event.Tags, func(t nostr.Tag) bool {
|
||||||
|
return t[1] != pubkey.Hex()
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := m.Events.SignAndSaveEvent(event, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Membership
|
||||||
|
|
||||||
|
func (m *ManagementStore) GetMembers() []nostr.PubKey {
|
||||||
pubkeys := make([]nostr.PubKey, 0)
|
pubkeys := make([]nostr.PubKey, 0)
|
||||||
for _, item := range m.GetBannedPubkeyItems() {
|
for tag := range m.Events.GetOrCreateMemberList().Tags.FindAll("member") {
|
||||||
pubkeys = append(pubkeys, item.PubKey)
|
pubkey, err := nostr.PubKeyFromHex(tag[1])
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
pubkeys = append(pubkeys, pubkey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pubkeys
|
return pubkeys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *ManagementStore) IsMember(pubkey nostr.PubKey) bool {
|
||||||
|
return m.Events.GetOrCreateMemberList().Tags.FindWithValue("member", pubkey.Hex()) != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ManagementStore) AddMember(pubkey nostr.PubKey) error {
|
||||||
|
membersEvent := m.Events.GetOrCreateMemberList()
|
||||||
|
|
||||||
|
if membersEvent.Tags.FindWithValue("member", pubkey.Hex()) == nil {
|
||||||
|
addMemberEvent := nostr.Event{
|
||||||
|
Kind: RELAY_ADD_MEMBER,
|
||||||
|
CreatedAt: nostr.Now(),
|
||||||
|
Tags: nostr.Tags{
|
||||||
|
[]string{"-"},
|
||||||
|
[]string{"p", pubkey.Hex()},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.Events.SignAndSaveEvent(addMemberEvent, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
membersEvent.Tags = append(membersEvent.Tags, nostr.Tag{"pubkey", pubkey.Hex()})
|
||||||
|
|
||||||
|
if err := m.Events.SignAndSaveEvent(membersEvent, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ManagementStore) RemoveMember(pubkey nostr.PubKey) error {
|
||||||
|
membersEvent := m.Events.GetOrCreateMemberList()
|
||||||
|
|
||||||
|
if membersEvent.Tags.FindWithValue("member", pubkey.Hex()) != nil {
|
||||||
|
removeMemberEvent := nostr.Event{
|
||||||
|
Kind: RELAY_REMOVE_MEMBER,
|
||||||
|
CreatedAt: nostr.Now(),
|
||||||
|
Tags: nostr.Tags{
|
||||||
|
[]string{"-"},
|
||||||
|
[]string{"p", pubkey.Hex()},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.Events.SignAndSaveEvent(removeMemberEvent, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
membersEvent.Tags = Filter(membersEvent.Tags, func(t nostr.Tag) bool {
|
||||||
|
return t[1] != pubkey.Hex()
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := m.Events.SignAndSaveEvent(membersEvent, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Banning
|
||||||
|
|
||||||
func (m *ManagementStore) BanPubkey(pubkey nostr.PubKey, reason string) error {
|
func (m *ManagementStore) BanPubkey(pubkey nostr.PubKey, reason string) error {
|
||||||
event := m.Events.GetOrCreateApplicationSpecificData(BANNED_PUBKEYS)
|
if err := m.RemoveMember(pubkey); err != nil {
|
||||||
event.Tags = append(event.Tags, nostr.Tag{"pubkey", pubkey.Hex(), reason})
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return m.Events.SaveEvent(event)
|
if err := m.AddBannedPubkey(pubkey, reason); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := nostr.Filter{
|
||||||
|
Authors: []nostr.PubKey{pubkey},
|
||||||
|
}
|
||||||
|
|
||||||
|
for event := range m.Events.QueryEvents(filter, 0) {
|
||||||
|
m.Events.DeleteEvent(event.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ManagementStore) PubkeyIsBanned(pubkey nostr.PubKey) bool {
|
// Allowing
|
||||||
event := m.Events.GetOrCreateApplicationSpecificData(BANNED_PUBKEYS)
|
|
||||||
tag := event.Tags.FindWithValue("pubkey", pubkey.Hex())
|
|
||||||
|
|
||||||
return tag != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allowed pubkeys
|
|
||||||
|
|
||||||
func (m *ManagementStore) GetAllowedPubkeyItems() []nip86.PubKeyReason {
|
func (m *ManagementStore) GetAllowedPubkeyItems() []nip86.PubKeyReason {
|
||||||
reasons := make([]nip86.PubKeyReason, 0)
|
reasons := make([]nip86.PubKeyReason, 0)
|
||||||
@@ -71,47 +190,29 @@ func (m *ManagementStore) GetAllowedPubkeyItems() []nip86.PubKeyReason {
|
|||||||
for _, pubkey := range role.Pubkeys {
|
for _, pubkey := range role.Pubkeys {
|
||||||
reasons = append(reasons, nip86.PubKeyReason{
|
reasons = append(reasons, nip86.PubKeyReason{
|
||||||
PubKey: nostr.MustPubKeyFromHex(pubkey),
|
PubKey: nostr.MustPubKeyFromHex(pubkey),
|
||||||
Reason: fmt.Sprintf("assigned to role: %s", name),
|
Reason: fmt.Sprintf("assigned to %s role", name),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
filter := nostr.Filter{
|
for tag := range m.Events.GetOrCreateMemberList().Tags.FindAll("member") {
|
||||||
Kinds: []nostr.Kind{AUTH_JOIN},
|
pubkey, err := nostr.PubKeyFromHex(tag[1])
|
||||||
}
|
|
||||||
|
|
||||||
for event := range m.Events.QueryEvents(filter, 0) {
|
if err != nil {
|
||||||
reasons = append(
|
reasons = append(
|
||||||
reasons,
|
reasons,
|
||||||
nip86.PubKeyReason{
|
nip86.PubKeyReason{
|
||||||
PubKey: event.PubKey,
|
PubKey: pubkey,
|
||||||
Reason: "joined via invite code",
|
Reason: "relay member",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return reasons
|
return reasons
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ManagementStore) GetAllowedPubkeys() []nostr.PubKey {
|
func (m *ManagementStore) IsPubkeyAllowed(pubkey nostr.PubKey) bool {
|
||||||
pubkeys := make([]nostr.PubKey, 0)
|
|
||||||
for _, item := range m.GetAllowedPubkeyItems() {
|
|
||||||
pubkeys = append(pubkeys, item.PubKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pubkeys
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ManagementStore) AllowPubkey(pubkey nostr.PubKey, reason string) error {
|
|
||||||
event := m.Events.GetOrCreateApplicationSpecificData(BANNED_PUBKEYS)
|
|
||||||
event.Tags = Filter(event.Tags, func(t nostr.Tag) bool {
|
|
||||||
return t[1] != pubkey.Hex()
|
|
||||||
})
|
|
||||||
|
|
||||||
return m.Events.SaveEvent(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ManagementStore) PubkeyIsAllowed(pubkey nostr.PubKey) bool {
|
|
||||||
if m.Config.IsOwner(pubkey) || m.Config.IsSelf(pubkey) {
|
if m.Config.IsOwner(pubkey) || m.Config.IsSelf(pubkey) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -120,32 +221,32 @@ func (m *ManagementStore) PubkeyIsAllowed(pubkey nostr.PubKey) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
filter := nostr.Filter{
|
return m.IsMember(pubkey)
|
||||||
Kinds: []nostr.Kind{AUTH_JOIN},
|
}
|
||||||
Authors: []nostr.PubKey{pubkey},
|
|
||||||
|
func (m *ManagementStore) AllowPubkey(pubkey nostr.PubKey) error {
|
||||||
|
if m.IsPubkeyAllowed(pubkey) {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for range m.Events.QueryEvents(filter, 1) {
|
if err := m.AddMember(pubkey); err != nil {
|
||||||
return true
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
if err := m.RemoveBannedPubkey(pubkey); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Banned events
|
// Banned events
|
||||||
|
|
||||||
type BannedEventItem struct {
|
func (m *ManagementStore) GetBannedEventItems() []nip86.IDReason {
|
||||||
ID nostr.ID
|
items := make([]nip86.IDReason, 0)
|
||||||
Reason string
|
for tag := range m.Events.GetOrCreateApplicationSpecificData(BANNED_EVENTS).Tags.FindAll("event") {
|
||||||
}
|
items = append(items, nip86.IDReason{
|
||||||
|
ID: tag[1],
|
||||||
func (m *ManagementStore) GetBannedEventItems() []BannedEventItem {
|
|
||||||
event := m.Events.GetOrCreateApplicationSpecificData(BANNED_EVENTS)
|
|
||||||
|
|
||||||
items := make([]BannedEventItem, 0)
|
|
||||||
for tag := range event.Tags.FindAll("event") {
|
|
||||||
items = append(items, BannedEventItem{
|
|
||||||
ID: nostr.MustIDFromHex(tag[1]),
|
|
||||||
Reason: tag[2],
|
Reason: tag[2],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -153,20 +254,15 @@ func (m *ManagementStore) GetBannedEventItems() []BannedEventItem {
|
|||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ManagementStore) GetBannedEvents() []nostr.ID {
|
func (m *ManagementStore) BanEvent(id nostr.ID, reason string) error {
|
||||||
ids := make([]nostr.ID, 0)
|
if err := m.Events.DeleteEvent(id); err != nil {
|
||||||
for _, item := range m.GetBannedEventItems() {
|
return err
|
||||||
ids = append(ids, item.ID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ids
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ManagementStore) BanEvent(id nostr.ID, reason string) error {
|
|
||||||
event := m.Events.GetOrCreateApplicationSpecificData(BANNED_EVENTS)
|
event := m.Events.GetOrCreateApplicationSpecificData(BANNED_EVENTS)
|
||||||
event.Tags = append(event.Tags, nostr.Tag{"event", id.Hex(), reason})
|
event.Tags = append(event.Tags, nostr.Tag{"event", id.Hex(), reason})
|
||||||
|
|
||||||
return m.Events.SaveEvent(event)
|
return m.Events.SignAndSaveEvent(event, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ManagementStore) AllowEvent(id nostr.ID, reason string) error {
|
func (m *ManagementStore) AllowEvent(id nostr.ID, reason string) error {
|
||||||
@@ -175,7 +271,7 @@ func (m *ManagementStore) AllowEvent(id nostr.ID, reason string) error {
|
|||||||
return t[1] == id.Hex()
|
return t[1] == id.Hex()
|
||||||
})
|
})
|
||||||
|
|
||||||
return m.Events.SaveEvent(event)
|
return m.Events.SignAndSaveEvent(event, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ManagementStore) EventIsBanned(id nostr.ID) bool {
|
func (m *ManagementStore) EventIsBanned(id nostr.ID) bool {
|
||||||
@@ -199,19 +295,11 @@ func (m *ManagementStore) Enable(instance *Instance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
instance.Relay.ManagementAPI.BanPubKey = func(ctx context.Context, pubkey nostr.PubKey, reason string) error {
|
instance.Relay.ManagementAPI.BanPubKey = func(ctx context.Context, pubkey nostr.PubKey, reason string) error {
|
||||||
filter := nostr.Filter{
|
|
||||||
Authors: []nostr.PubKey{pubkey},
|
|
||||||
}
|
|
||||||
|
|
||||||
for event := range instance.Events.QueryEvents(filter, 0) {
|
|
||||||
instance.Events.DeleteEvent(event.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.BanPubkey(pubkey, reason)
|
return m.BanPubkey(pubkey, reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
instance.Relay.ManagementAPI.AllowPubKey = func(ctx context.Context, pubkey nostr.PubKey, reason string) error {
|
instance.Relay.ManagementAPI.AllowPubKey = func(ctx context.Context, pubkey nostr.PubKey, reason string) error {
|
||||||
return m.AllowPubkey(pubkey, reason)
|
return m.AllowPubkey(pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
instance.Relay.ManagementAPI.ListBannedPubKeys = func(ctx context.Context) ([]nip86.PubKeyReason, error) {
|
instance.Relay.ManagementAPI.ListBannedPubKeys = func(ctx context.Context) ([]nip86.PubKeyReason, error) {
|
||||||
@@ -223,8 +311,6 @@ func (m *ManagementStore) Enable(instance *Instance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
instance.Relay.ManagementAPI.BanEvent = func(ctx context.Context, id nostr.ID, reason string) error {
|
instance.Relay.ManagementAPI.BanEvent = func(ctx context.Context, id nostr.ID, reason string) error {
|
||||||
instance.Events.DeleteEvent(id)
|
|
||||||
|
|
||||||
return m.BanEvent(id, reason)
|
return m.BanEvent(id, reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,17 +319,6 @@ func (m *ManagementStore) Enable(instance *Instance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
instance.Relay.ManagementAPI.ListBannedEvents = func(ctx context.Context) ([]nip86.IDReason, error) {
|
instance.Relay.ManagementAPI.ListBannedEvents = func(ctx context.Context) ([]nip86.IDReason, error) {
|
||||||
reasons := make([]nip86.IDReason, 0)
|
return m.GetBannedEventItems(), nil
|
||||||
for _, item := range m.GetBannedEventItems() {
|
|
||||||
reasons = append(
|
|
||||||
reasons,
|
|
||||||
nip86.IDReason{
|
|
||||||
ID: item.ID.Hex(),
|
|
||||||
Reason: item.Reason,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return reasons, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-4
@@ -7,10 +7,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
AUTH_JOIN = 28934
|
RELAY_ADD_MEMBER = 8000
|
||||||
AUTH_INVITE = 28935
|
RELAY_REMOVE_MEMBER = 8001
|
||||||
BANNED_PUBKEYS = "zooid/banned_pubkeys"
|
RELAY_MEMBERS = 13534
|
||||||
BANNED_EVENTS = "zooid/banned_events"
|
RELAY_JOIN = 28934
|
||||||
|
RELAY_INVITE = 28935
|
||||||
|
RELAY_LEAVE = 28936
|
||||||
|
BANNED_PUBKEYS = "zooid/banned_pubkeys"
|
||||||
|
BANNED_EVENTS = "zooid/banned_events"
|
||||||
)
|
)
|
||||||
|
|
||||||
func First[T any](s []T) T {
|
func First[T any](s []T) T {
|
||||||
|
|||||||
Reference in New Issue
Block a user