Refactor group access, implement hidden/private/closed

This commit is contained in:
Jon Staab
2025-10-31 10:59:24 -07:00
parent f035dadbb8
commit 7497dacf8d
4 changed files with 81 additions and 76 deletions
-2
View File
@@ -44,7 +44,6 @@ Configures NIP 29 support.
- `enabled` - whether NIP 29 is enabled.
- `auto_join` - whether relay members can join `open` groups without approval. Defaults to `true`.
- `auto_leave` - whether relay members can leave groups without approval. Defaults to `true`.
### `[management]`
@@ -90,7 +89,6 @@ strip_signatures = false
[groups]
enabled = true
auto_join = false
auto_leave = true
[management]
enabled = true
-1
View File
@@ -33,7 +33,6 @@ type Config struct {
Groups struct {
Enabled bool `toml:"enabled"`
AutoJoin bool `toml:"auto_join"`
AutoLeave bool `toml:"auto_leave"`
} `toml:"groups"`
Management struct {
+76 -7
View File
@@ -223,25 +223,94 @@ func (g *GroupStore) UpdateMembersList(h string) error {
// Other stuff
func (g *GroupStore) HasAccess(h string, pubkey nostr.PubKey) bool {
return g.IsAdmin(h, pubkey) || g.IsMember(h, pubkey)
}
func (g *GroupStore) IsGroupEvent(event nostr.Event) bool {
if slices.Contains(nip29.MetadataEventKinds, event.Kind) {
return true
}
if slices.Contains(nip29.ModerationEventKinds, event.Kind) {
return true
}
joinKinds := []nostr.Kind{
nostr.KindSimpleGroupJoinRequest,
nostr.KindSimpleGroupLeaveRequest,
}
if slices.Contains(joinKinds, event.Kind) {
return true
}
return GetGroupIDFromEvent(event) != ""
}
func (g *GroupStore) CanRead(pubkey nostr.PubKey, event nostr.Event) bool {
if !g.Config.Groups.Enabled {
return false
}
h := GetGroupIDFromEvent(event)
meta, found := g.GetMetadata(h)
if !found {
return false
}
if !HasTag(meta.Tags, "closed") {
return true
if HasTag(meta.Tags, "hidden") && !g.HasAccess(h, pubkey) {
return false
}
if g.IsAdmin(h, pubkey) {
return true
if HasTag(meta.Tags, "private") && !g.HasAccess(h, pubkey) {
return false
}
if g.IsMember(h, pubkey) {
return true
return true
}
func (g *GroupStore) CheckWrite(event nostr.Event) string {
if !g.Config.Groups.Enabled {
return "invalid: groups are not enabled"
}
return false
if slices.Contains(nip29.MetadataEventKinds, event.Kind) {
return "invalid: group metadata cannot be set directly"
}
h := GetGroupIDFromEvent(event)
meta, found := g.GetMetadata(h)
if event.Kind == nostr.KindSimpleGroupCreateGroup {
if found {
return "invalid: that group already exists"
}
} else if !found {
return "invalid: group not found"
}
if event.Kind == nostr.KindSimpleGroupJoinRequest && g.IsMember(h, event.PubKey) {
return "duplicate: already a member"
}
if event.Kind == nostr.KindSimpleGroupLeaveRequest && !g.IsMember(h, event.PubKey) {
return "duplicate: not currently a member"
}
if slices.Contains(nip29.ModerationEventKinds, event.Kind) && !g.Config.CanManage(event.PubKey) {
return "restricted: you are not authorized to manage groups"
}
if HasTag(meta.Tags, "hidden") && !g.HasAccess(h, event.PubKey) {
return "invalid: group not found"
}
if HasTag(meta.Tags, "closed") && !g.HasAccess(h, event.PubKey) {
return "restricted: you are not a member of that group"
}
return ""
}
// Middleware
+5 -66
View File
@@ -10,7 +10,6 @@ import (
"fiatjaf.com/nostr"
"fiatjaf.com/nostr/khatru"
"fiatjaf.com/nostr/nip29"
"github.com/gosimple/slug"
)
@@ -317,19 +316,7 @@ func (instance *Instance) QueryStored(ctx context.Context, filter nostr.Filter)
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) {
if instance.Groups.IsGroupEvent(event) && !instance.Groups.CanRead(pubkey, event) {
continue
}
@@ -372,57 +359,9 @@ func (instance *Instance) OnEvent(ctx context.Context, event nostr.Event) (rejec
return true, "invalid: this event's kind is not accepted"
}
if slices.Contains(nip29.MetadataEventKinds, event.Kind) {
return true, "invalid: group metadata cannot be set directly"
}
if slices.Contains(nip29.ModerationEventKinds, event.Kind) && !instance.Config.CanManage(event.PubKey) {
return true, "restricted: you are not authorized to manage groups"
}
allGroupKinds := append(
nip29.ModerationEventKinds,
nostr.KindSimpleGroupJoinRequest,
nostr.KindSimpleGroupLeaveRequest,
)
h := GetGroupIDFromEvent(event)
if slices.Contains(allGroupKinds, event.Kind) {
if !instance.Config.Groups.Enabled {
return true, "invalid: group events not accepted on this relay"
}
if h == "" {
return true, "invalid: h tag is required"
}
_, found := instance.Groups.GetMetadata(h)
if event.Kind == nostr.KindSimpleGroupCreateGroup {
if found {
return true, "invalid: that group already exists"
}
} else if !found {
return true, "invalid: no such group exists"
}
if event.Kind == nostr.KindSimpleGroupJoinRequest && instance.Groups.IsMember(h, event.PubKey) {
return true, "duplicate: already a member"
}
if event.Kind == nostr.KindSimpleGroupLeaveRequest && !instance.Groups.IsMember(h, event.PubKey) {
return true, "duplicate: not currently a member"
}
} else if h != "" {
_, found := instance.Groups.GetMetadata(h)
if !found {
return true, "invalid: no such group exists"
}
if !instance.Groups.HasAccess(h, pubkey) {
return true, "restricted: you are not a member of that group"
if instance.Groups.IsGroupEvent(event) {
if err := instance.Groups.CheckWrite(event); err != "" {
return true, err
}
}
@@ -455,7 +394,7 @@ func (instance *Instance) OnEventSaved(ctx context.Context, event nostr.Event) {
}
}
if event.Kind == nostr.KindSimpleGroupLeaveRequest && instance.Config.Groups.AutoLeave {
if event.Kind == nostr.KindSimpleGroupLeaveRequest {
instance.Groups.RemoveMember(h, event.PubKey)
instance.Groups.UpdateMembersList(h)
}