Implement private tag filtering for event visibility

- Added functionality to filter events based on "private" tags, allowing only authorized users to see private events.
- Introduced a new method `canSeePrivateEvent` to check user permissions against private tags.
- Updated event delivery logic to deny access to unauthorized users for private events, enhancing security and user experience.
- Bumped version to v0.17.7.
This commit is contained in:
2025-10-21 19:17:16 +01:00
parent 8609e9dc22
commit da66e26614
4 changed files with 174 additions and 62 deletions

View File

@@ -431,6 +431,44 @@ privCheck:
allEvents = aclFilteredEvents
}
// Apply private tag filtering - only show events with "private" tags to authorized users
var privateFilteredEvents event.S
authedPubkey := l.authedPubkey.Load()
for _, ev := range allEvents {
// Check if event has private tags
hasPrivateTag := false
var privatePubkey []byte
if ev.Tags != nil && ev.Tags.Len() > 0 {
for _, t := range *ev.Tags {
if t.Len() >= 2 {
keyBytes := t.Key()
if len(keyBytes) == 7 && string(keyBytes) == "private" {
hasPrivateTag = true
privatePubkey = t.Value()
break
}
}
}
}
// If no private tag, include the event
if !hasPrivateTag {
privateFilteredEvents = append(privateFilteredEvents, ev)
continue
}
// Event has private tag - check if user is authorized to see it
canSeePrivate := l.canSeePrivateEvent(authedPubkey, privatePubkey)
if canSeePrivate {
privateFilteredEvents = append(privateFilteredEvents, ev)
log.D.F("private tag: allowing event %s for authorized user", hexenc.Enc(ev.ID))
} else {
log.D.F("private tag: filtering out event %s from unauthorized user", hexenc.Enc(ev.ID))
}
}
allEvents = privateFilteredEvents
seen := make(map[string]struct{})
for _, ev := range allEvents {
log.T.C(

View File

@@ -12,6 +12,7 @@ import (
"next.orly.dev/pkg/database"
"next.orly.dev/pkg/encoders/event"
"next.orly.dev/pkg/encoders/filter"
"next.orly.dev/pkg/utils"
"next.orly.dev/pkg/utils/atomic"
)
@@ -133,3 +134,25 @@ func (l *Listener) QueryEvents(ctx context.Context, f *filter.F) (event.S, error
func (l *Listener) QueryAllVersions(ctx context.Context, f *filter.F) (event.S, error) {
return l.D.QueryAllVersions(ctx, f)
}
// canSeePrivateEvent checks if the authenticated user can see an event with a private tag
func (l *Listener) canSeePrivateEvent(authedPubkey, privatePubkey []byte) (canSee bool) {
// If no authenticated user, deny access
if len(authedPubkey) == 0 {
return false
}
// If the authenticated user matches the private tag pubkey, allow access
if len(privatePubkey) > 0 && utils.FastEqual(authedPubkey, privatePubkey) {
return true
}
// Check if user is an admin or owner (they can see all private events)
accessLevel := acl.Registry.GetAccessLevel(authedPubkey, l.remote)
if accessLevel == "admin" || accessLevel == "owner" {
return true
}
// Default deny
return false
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/coder/websocket"
"lol.mleku.dev/chk"
"lol.mleku.dev/log"
"next.orly.dev/pkg/acl"
"next.orly.dev/pkg/encoders/envelopes/eventenvelope"
"next.orly.dev/pkg/encoders/event"
"next.orly.dev/pkg/encoders/filter"
@@ -220,6 +221,34 @@ func (p *P) Deliver(ev *event.E) {
}
}
// Check for private tags - only deliver to authorized users
if ev.Tags != nil && ev.Tags.Len() > 0 {
hasPrivateTag := false
var privatePubkey []byte
for _, t := range *ev.Tags {
if t.Len() >= 2 {
keyBytes := t.Key()
if len(keyBytes) == 7 && string(keyBytes) == "private" {
hasPrivateTag = true
privatePubkey = t.Value()
break
}
}
}
if hasPrivateTag {
canSeePrivate := p.canSeePrivateEvent(d.sub.AuthedPubkey, privatePubkey, d.sub.remote)
if !canSeePrivate {
log.D.F("subscription delivery DENIED for private event %s to %s (unauthorized)",
hex.Enc(ev.ID), d.sub.remote)
continue
}
log.D.F("subscription delivery ALLOWED for private event %s to %s (authorized)",
hex.Enc(ev.ID), d.sub.remote)
}
}
var res *eventenvelope.Result
if res, err = eventenvelope.NewResultWith(d.id, ev); chk.E(err) {
log.E.F("failed to create event envelope for %s to %s: %v",
@@ -299,3 +328,25 @@ func (p *P) removeSubscriber(ws *websocket.Conn) {
clear(p.Map[ws])
delete(p.Map, ws)
}
// canSeePrivateEvent checks if the authenticated user can see an event with a private tag
func (p *P) canSeePrivateEvent(authedPubkey, privatePubkey []byte, remote string) (canSee bool) {
// If no authenticated user, deny access
if len(authedPubkey) == 0 {
return false
}
// If the authenticated user matches the private tag pubkey, allow access
if len(privatePubkey) > 0 && utils.FastEqual(authedPubkey, privatePubkey) {
return true
}
// Check if user is an admin or owner (they can see all private events)
accessLevel := acl.Registry.GetAccessLevel(authedPubkey, remote)
if accessLevel == "admin" || accessLevel == "owner" {
return true
}
// Default deny
return false
}

View File

@@ -1 +1 @@
v0.17.6
v0.17.7