forked from coracle/zooid
382 lines
9.9 KiB
Go
382 lines
9.9 KiB
Go
package zooid
|
|
|
|
import (
|
|
"context"
|
|
"fiatjaf.com/nostr"
|
|
"fiatjaf.com/nostr/khatru"
|
|
"fiatjaf.com/nostr/nip86"
|
|
)
|
|
|
|
// 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 {
|
|
Config *Config
|
|
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") {
|
|
if id, err := nostr.IDFromHex(tag[1]); err == nil {
|
|
items = append(items, nip86.IDReason{
|
|
ID: id,
|
|
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.CreatedAt = nostr.Now()
|
|
event.Tags = append(event.Tags, nostr.Tag{"event", id.Hex(), reason})
|
|
|
|
return m.Events.SignAndStoreEvent(&event, false)
|
|
}
|
|
|
|
func (m *ManagementStore) AllowEvent(id nostr.ID, reason string) error {
|
|
event := m.Events.GetOrCreateApplicationSpecificData(BANNED_EVENTS)
|
|
event.CreatedAt = nostr.Now()
|
|
event.Tags = Filter(event.Tags, func(t nostr.Tag) bool {
|
|
return t[1] != id.Hex()
|
|
})
|
|
|
|
return m.Events.SignAndStoreEvent(&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
|
|
|
|
func (m *ManagementStore) GetBannedPubkeyItems() []nip86.PubKeyReason {
|
|
event := m.Events.GetOrCreateApplicationSpecificData(BANNED_PUBKEYS)
|
|
|
|
items := make([]nip86.PubKeyReason, 0)
|
|
for tag := range event.Tags.FindAll("banned") {
|
|
items = append(items, nip86.PubKeyReason{
|
|
PubKey: nostr.MustPubKeyFromHex(tag[1]),
|
|
Reason: tag[2],
|
|
})
|
|
}
|
|
|
|
return items
|
|
}
|
|
|
|
func (m *ManagementStore) AddBannedPubkey(pubkey nostr.PubKey, reason string) error {
|
|
event := m.Events.GetOrCreateApplicationSpecificData(BANNED_PUBKEYS)
|
|
|
|
if event.Tags.FindWithValue("banned", pubkey.Hex()) == nil {
|
|
event.CreatedAt = nostr.Now()
|
|
event.Tags = append(event.Tags, nostr.Tag{"banned", pubkey.Hex(), reason})
|
|
|
|
if err := m.Events.SignAndStoreEvent(&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("banned", pubkey.Hex()) != nil {
|
|
event.CreatedAt = nostr.Now()
|
|
event.Tags = Filter(event.Tags, func(t nostr.Tag) bool {
|
|
return len(t) >= 2 && t[1] != pubkey.Hex()
|
|
})
|
|
|
|
if err := m.Events.SignAndStoreEvent(&event, false); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *ManagementStore) PubkeyIsBanned(pubkey nostr.PubKey) bool {
|
|
event := m.Events.GetOrCreateApplicationSpecificData(BANNED_PUBKEYS)
|
|
tag := event.Tags.FindWithValue("banned", pubkey.Hex())
|
|
|
|
return tag != nil
|
|
}
|
|
|
|
// Admins
|
|
|
|
func (m *ManagementStore) IsAdmin(pubkey nostr.PubKey) bool {
|
|
return m.Config.IsOwner(pubkey) || m.Config.IsSelf(pubkey)
|
|
}
|
|
|
|
func (m *ManagementStore) GetAdmins() []nostr.PubKey {
|
|
members := make([]nostr.PubKey, 0)
|
|
|
|
members = append(members, m.Config.GetOwner())
|
|
|
|
members = append(members, m.Config.GetSelf())
|
|
|
|
for _, role := range m.Config.Roles {
|
|
if role.CanManage {
|
|
for _, pubkey := range role.Pubkeys {
|
|
members = append(members, nostr.MustPubKeyFromHex(pubkey))
|
|
}
|
|
}
|
|
}
|
|
|
|
return members
|
|
}
|
|
|
|
// Membership
|
|
|
|
func (m *ManagementStore) GetMembers() []nostr.PubKey {
|
|
pubkeys := make([]nostr.PubKey, 0)
|
|
for tag := range m.Events.GetOrCreateRelayMembersList().Tags.FindAll("member") {
|
|
pubkey, err := nostr.PubKeyFromHex(tag[1])
|
|
|
|
if err == nil {
|
|
pubkeys = append(pubkeys, pubkey)
|
|
}
|
|
}
|
|
|
|
return pubkeys
|
|
}
|
|
|
|
func (m *ManagementStore) IsMember(pubkey nostr.PubKey) bool {
|
|
return m.Events.GetOrCreateRelayMembersList().Tags.FindWithValue("member", pubkey.Hex()) != nil
|
|
}
|
|
|
|
func (m *ManagementStore) AddMember(pubkey nostr.PubKey) error {
|
|
membersEvent := m.Events.GetOrCreateRelayMembersList()
|
|
|
|
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.SignAndStoreEvent(&addMemberEvent, true); err != nil {
|
|
return err
|
|
}
|
|
|
|
membersEvent.CreatedAt = nostr.Now()
|
|
membersEvent.Tags = append(membersEvent.Tags, nostr.Tag{"member", pubkey.Hex()})
|
|
|
|
if err := m.Events.SignAndStoreEvent(&membersEvent, true); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *ManagementStore) RemoveMember(pubkey nostr.PubKey) error {
|
|
membersEvent := m.Events.GetOrCreateRelayMembersList()
|
|
|
|
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.SignAndStoreEvent(&removeMemberEvent, true); err != nil {
|
|
return err
|
|
}
|
|
|
|
membersEvent.CreatedAt = nostr.Now()
|
|
membersEvent.Tags = Filter(membersEvent.Tags, func(t nostr.Tag) bool {
|
|
return len(t) >= 2 && t[1] != pubkey.Hex()
|
|
})
|
|
|
|
if err := m.Events.SignAndStoreEvent(&membersEvent, true); err != nil {
|
|
return err
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Banning
|
|
|
|
func (m *ManagementStore) BanPubkey(pubkey nostr.PubKey, reason string) error {
|
|
if err := m.RemoveMember(pubkey); err != nil {
|
|
return err
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// Allowing
|
|
|
|
func (m *ManagementStore) GetAllowedPubkeyItems() []nip86.PubKeyReason {
|
|
reasons := make([]nip86.PubKeyReason, 0)
|
|
for _, pubkey := range m.GetMembers() {
|
|
reasons = append(
|
|
reasons,
|
|
nip86.PubKeyReason{
|
|
PubKey: pubkey,
|
|
Reason: "relay member",
|
|
},
|
|
)
|
|
}
|
|
|
|
return reasons
|
|
}
|
|
|
|
func (m *ManagementStore) AllowPubkey(pubkey nostr.PubKey) error {
|
|
if err := m.AddMember(pubkey); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := m.RemoveBannedPubkey(pubkey); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Joining
|
|
|
|
func (m *ManagementStore) ValidateJoinRequest(event nostr.Event) (reject bool, err string) {
|
|
if m.IsMember(event.PubKey) {
|
|
return false, ""
|
|
}
|
|
|
|
if m.PubkeyIsBanned(event.PubKey) {
|
|
return true, "invalid: you have been banned from this relay"
|
|
}
|
|
|
|
if m.Config.Policy.PublicJoin {
|
|
return false, ""
|
|
}
|
|
|
|
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 m.Events.QueryEvents(filter, 0) {
|
|
if event.Tags.FindWithValue("claim", claimTag[1]) != nil {
|
|
return false, ""
|
|
}
|
|
}
|
|
|
|
return true, "invalid: failed to validate invite code"
|
|
}
|
|
|
|
// Middleware
|
|
|
|
func (m *ManagementStore) Enable(instance *Instance) {
|
|
instance.Relay.ManagementAPI.OnAPICall = func(ctx context.Context, mp nip86.MethodParams) (reject bool, msg string) {
|
|
pubkey, ok := khatru.GetAuthed(ctx)
|
|
|
|
if !ok {
|
|
return true, "blocked: please authenticate in order to manage this relay"
|
|
}
|
|
|
|
if !m.Config.CanManage(pubkey) {
|
|
return true, "blocked: only relay admins can manage this relay."
|
|
}
|
|
|
|
return false, ""
|
|
}
|
|
|
|
instance.Relay.ManagementAPI.ChangeRelayName = func(ctx context.Context, name string) error {
|
|
return m.Config.SetName(name)
|
|
}
|
|
instance.Relay.ManagementAPI.ChangeRelayDescription = func(ctx context.Context, desc string) error {
|
|
return m.Config.SetDescription(desc)
|
|
}
|
|
instance.Relay.ManagementAPI.ChangeRelayIcon = func(ctx context.Context, icon string) error {
|
|
return m.Config.SetIcon(icon)
|
|
}
|
|
|
|
instance.Relay.ManagementAPI.BanPubKey = func(ctx context.Context, pubkey nostr.PubKey, reason string) error {
|
|
return m.BanPubkey(pubkey, reason)
|
|
}
|
|
|
|
instance.Relay.ManagementAPI.BanPubKey = func(ctx context.Context, pubkey nostr.PubKey, reason string) error {
|
|
return m.BanPubkey(pubkey, reason)
|
|
}
|
|
|
|
// instance.Relay.ManagementAPI.UnbanPubKey = func(ctx context.Context, pubkey nostr.PubKey, reason string) error {
|
|
// return m.RemoveBannedPubkey(pubkey)
|
|
// }
|
|
|
|
instance.Relay.ManagementAPI.AllowPubKey = func(ctx context.Context, pubkey nostr.PubKey, reason string) error {
|
|
return m.AllowPubkey(pubkey)
|
|
}
|
|
|
|
// instance.Relay.ManagementAPI.UnallowPubKey = func(ctx context.Context, pubkey nostr.PubKey, reason string) error {
|
|
// return m.RemoveMember(pubkey)
|
|
// }
|
|
|
|
instance.Relay.ManagementAPI.ListBannedPubKeys = func(ctx context.Context) ([]nip86.PubKeyReason, error) {
|
|
return m.GetBannedPubkeyItems(), nil
|
|
}
|
|
|
|
instance.Relay.ManagementAPI.ListAllowedPubKeys = func(ctx context.Context) ([]nip86.PubKeyReason, error) {
|
|
return m.GetAllowedPubkeyItems(), nil
|
|
}
|
|
|
|
instance.Relay.ManagementAPI.BanEvent = func(ctx context.Context, id nostr.ID, reason string) error {
|
|
return m.BanEvent(id, reason)
|
|
}
|
|
|
|
instance.Relay.ManagementAPI.AllowEvent = func(ctx context.Context, id nostr.ID, reason string) error {
|
|
return m.AllowEvent(id, reason)
|
|
}
|
|
|
|
instance.Relay.ManagementAPI.ListBannedEvents = func(ctx context.Context) ([]nip86.IDReason, error) {
|
|
return m.GetBannedEventItems(), nil
|
|
}
|
|
}
|