From 45716de712790f6ff1600e9e96dea96195ffde56 Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Tue, 21 Oct 2025 17:13:36 -0700 Subject: [PATCH] Fix some access logic --- zooid/blossom.go | 8 +-- zooid/config.go | 45 ++++++++------- zooid/instance.go | 132 ++++++++++++++++++++++---------------------- zooid/management.go | 36 +++++++----- 4 files changed, 117 insertions(+), 104 deletions(-) diff --git a/zooid/blossom.go b/zooid/blossom.go index 92aad98..77977e1 100644 --- a/zooid/blossom.go +++ b/zooid/blossom.go @@ -57,7 +57,7 @@ func (bl *BlossomStore) Enable(instance *Instance) { return true, "file too large", 413 } - if auth == nil || !instance.Management.IsPubkeyAllowed(auth.PubKey) { + if auth == nil || !instance.Management.HasAccess(auth.PubKey) { 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) { - if auth == nil || !instance.Management.IsPubkeyAllowed(auth.PubKey) { + if auth == nil || !instance.Management.HasAccess(auth.PubKey) { 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) { - if auth == nil || !instance.Management.IsPubkeyAllowed(auth.PubKey) { + if auth == nil || !instance.Management.HasAccess(auth.PubKey) { 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) { - if auth == nil || !instance.Management.IsPubkeyAllowed(auth.PubKey) { + if auth == nil || !instance.Management.HasAccess(auth.PubKey) { return true, "unauthorized", 403 } diff --git a/zooid/config.go b/zooid/config.go index fb4511f..5f74fd2 100644 --- a/zooid/config.go +++ b/zooid/config.go @@ -95,13 +95,13 @@ func (config *Config) IsOwner(pubkey nostr.PubKey) bool { return pubkey.Hex() == config.Info.Pubkey } -func (config *Config) GetRolesForPubkey(pubkey nostr.PubKey) []Role { - roles := make([]Role, 0) - for name, role := range config.Roles { - if name == "member" { - roles = append(roles, role) - } +func (config *Config) IsAdmin(pubkey nostr.PubKey) bool { + return config.IsOwner(pubkey) || config.IsSelf(pubkey) +} +func (config *Config) GetAssignedRoles(pubkey nostr.PubKey) []Role { + roles := make([]Role, 0) + for _, role := range config.Roles { if slices.Contains(role.Pubkeys, pubkey.Hex()) { roles = append(roles, role) } @@ -110,18 +110,25 @@ func (config *Config) GetRolesForPubkey(pubkey nostr.PubKey) []Role { return roles } -func (config *Config) CanManage(pubkey nostr.PubKey) bool { - for _, role := range config.GetRolesForPubkey(pubkey) { - if role.CanManage { - return true +func (config *Config) GetAllRoles(pubkey nostr.PubKey) []Role { + roles := make([]Role, 0) + for name, role := range config.Roles { + if name == "member" { + roles = append(roles, role) + } else if slices.Contains(role.Pubkeys, pubkey.Hex()) { + roles = append(roles, role) } } - return false + return roles } func (config *Config) CanInvite(pubkey nostr.PubKey) bool { - for _, role := range config.GetRolesForPubkey(pubkey) { + if config.IsAdmin(pubkey) { + return true + } + + for _, role := range config.GetAllRoles(pubkey) { if role.CanInvite { return true } @@ -130,17 +137,15 @@ func (config *Config) CanInvite(pubkey nostr.PubKey) bool { return false } -func (config *Config) IsAdmin(pubkey nostr.PubKey) bool { - if config.IsOwner(pubkey) { +func (config *Config) CanManage(pubkey nostr.PubKey) bool { + if config.IsAdmin(pubkey) { return true } - if config.IsSelf(pubkey) { - return true - } - - if config.CanManage(pubkey) { - return true + for _, role := range config.GetAllRoles(pubkey) { + if role.CanManage { + return true + } } return false diff --git a/zooid/instance.go b/zooid/instance.go index 159c498..91c2995 100644 --- a/zooid/instance.go +++ b/zooid/instance.go @@ -137,7 +137,7 @@ func (instance *Instance) Cleanup() { func (instance *Instance) StripSignature(ctx context.Context, event nostr.Event) nostr.Event { pubkey, _ := khatru.GetAuthed(ctx) - if instance.Config.Policy.StripSignatures && !instance.Config.IsAdmin(pubkey) { + if instance.Config.Policy.StripSignatures && !instance.Config.CanManage(pubkey) { var zeroSig [64]byte event.Sig = zeroSig } @@ -159,7 +159,7 @@ func (instance *Instance) AllowRecipientEvent(event nostr.Event) bool { if recipientTag != nil { pubkey, err := nostr.PubKeyFromHex(recipientTag[1]) - if err == nil && instance.Management.IsPubkeyAllowed(pubkey) { + if err == nil && instance.Management.HasAccess(pubkey) { return true } } @@ -251,6 +251,68 @@ func (instance *Instance) DeleteEvent(ctx context.Context, id nostr.ID) error { return instance.Events.DeleteEvent(id) } +// Requests + +func (instance *Instance) OnRequest(ctx context.Context, filter nostr.Filter) (reject bool, msg string) { + pubkey, ok := khatru.GetAuthed(ctx) + + if !ok { + return true, "auth-required: authentication is required for access" + } + + if !instance.Management.HasAccess(pubkey) { + return true, "restricted: you are not a member of this relay" + } + + return false, "" +} + +func (instance *Instance) QueryStored(ctx context.Context, filter nostr.Filter) iter.Seq[nostr.Event] { + return func(yield func(nostr.Event) bool) { + if khatru.IsInternalCall(ctx) { + for event := range instance.Events.QueryEvents(filter, 0) { + if !yield(event) { + return + } + } + } else { + pubkey, _ := khatru.GetAuthed(ctx) + + if slices.Contains(filter.Kinds, RELAY_INVITE) && instance.Config.CanInvite(pubkey) { + if !yield(instance.StripSignature(ctx, instance.GenerateInviteEvent(pubkey))) { + return + } + } + + for event := range instance.Events.QueryEvents(filter, 1000) { + if instance.IsWriteOnlyEvent(event) { + continue + } + + h := GetGroupIDFromEvent(event) + + if h != "" { + if !instance.Config.Groups.Enabled { + continue + } + + if !instance.Groups.HasAccess(h, pubkey) { + continue + } + } + + if !instance.Config.Groups.Enabled && slices.Contains(nip29.MetadataEventKinds, event.Kind) { + continue + } + + if !yield(instance.StripSignature(ctx, event)) { + return + } + } + } + } +} + // Event publishing func (instance *Instance) OnEvent(ctx context.Context, event nostr.Event) (reject bool, msg string) { @@ -270,7 +332,7 @@ func (instance *Instance) OnEvent(ctx context.Context, event nostr.Event) (rejec return instance.Management.ValidateJoinRequest(event) } - if !instance.Management.IsPubkeyAllowed(pubkey) { + if !instance.Management.HasAccess(pubkey) { return true, "restricted: you are not a member of this relay" } @@ -282,7 +344,7 @@ func (instance *Instance) OnEvent(ctx context.Context, event nostr.Event) (rejec return true, "invalid: group metadata cannot be set directly" } - if slices.Contains(nip29.ModerationEventKinds, event.Kind) && !instance.Config.IsAdmin(event.PubKey) { + if slices.Contains(nip29.ModerationEventKinds, event.Kind) && !instance.Config.CanManage(event.PubKey) { return true, "restricted: you are not authorized to manage groups" } @@ -369,65 +431,3 @@ func (instance *Instance) OnEventSaved(ctx context.Context, event nostr.Event) { instance.Groups.DeleteGroup(GetGroupIDFromEvent(event)) } } - -// Requests - -func (instance *Instance) OnRequest(ctx context.Context, filter nostr.Filter) (reject bool, msg string) { - pubkey, ok := khatru.GetAuthed(ctx) - - if !ok { - return true, "auth-required: authentication is required for access" - } - - if !instance.Management.IsPubkeyAllowed(pubkey) { - return true, "restricted: you are not a member of this relay" - } - - return false, "" -} - -func (instance *Instance) QueryStored(ctx context.Context, filter nostr.Filter) iter.Seq[nostr.Event] { - return func(yield func(nostr.Event) bool) { - if khatru.IsInternalCall(ctx) { - for event := range instance.Events.QueryEvents(filter, 0) { - if !yield(event) { - return - } - } - } else { - pubkey, _ := khatru.GetAuthed(ctx) - - if slices.Contains(filter.Kinds, RELAY_INVITE) && instance.Config.CanInvite(pubkey) { - if !yield(instance.StripSignature(ctx, instance.GenerateInviteEvent(pubkey))) { - return - } - } - - for event := range instance.Events.QueryEvents(filter, 1000) { - if instance.IsWriteOnlyEvent(event) { - continue - } - - h := GetGroupIDFromEvent(event) - - if h != "" { - if !instance.Config.Groups.Enabled { - continue - } - - if !instance.Groups.HasAccess(h, pubkey) { - continue - } - } - - if !instance.Config.Groups.Enabled && slices.Contains(nip29.MetadataEventKinds, event.Kind) { - continue - } - - if !yield(instance.StripSignature(ctx, event)) { - return - } - } - } - } -} diff --git a/zooid/management.go b/zooid/management.go index fcd6f74..9f6e795 100644 --- a/zooid/management.go +++ b/zooid/management.go @@ -259,20 +259,8 @@ func (m *ManagementStore) GetAllowedPubkeyItems() []nip86.PubKeyReason { return reasons } -func (m *ManagementStore) IsPubkeyAllowed(pubkey nostr.PubKey) bool { - if m.Config.IsOwner(pubkey) || m.Config.IsSelf(pubkey) { - return true - } - - for range m.Config.GetRolesForPubkey(pubkey) { - return true - } - - return m.IsMember(pubkey) -} - func (m *ManagementStore) AllowPubkey(pubkey nostr.PubKey) error { - if m.IsPubkeyAllowed(pubkey) { + if m.HasAccess(pubkey) { return nil } @@ -287,6 +275,18 @@ func (m *ManagementStore) AllowPubkey(pubkey nostr.PubKey) error { return nil } +func (m *ManagementStore) HasAccess(pubkey nostr.PubKey) bool { + if m.Config.IsAdmin(pubkey) { + return true + } + + for range m.Config.GetAssignedRoles(pubkey) { + return true + } + + return m.IsMember(pubkey) +} + // Joining func (m *ManagementStore) ValidateJoinRequest(event nostr.Event) (reject bool, err string) { @@ -319,7 +319,15 @@ 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 && m.Config.CanManage(pubkey) { + if !ok { + return true, "blocked: please authenticate in order to manage this relay" + } + + if !m.HasAccess(pubkey) { + return true, "blocked: you are not a member of this relay" + } + + if !m.Config.CanManage(pubkey) { return true, "blocked: only relay admins can manage this relay." }