Skip to content

Commit

Permalink
separate owners follows and follows follows
Browse files Browse the repository at this point in the history
setting owners implicitly enables auth also
  • Loading branch information
mleku committed Dec 4, 2024
1 parent 0048ac8 commit d0a79e4
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 91 deletions.
122 changes: 80 additions & 42 deletions cmd/realy/app/implementation.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,35 @@ import (
"realy.lol/tag"
)

// Relay is the state for a realy.
type Relay struct {
sync.Mutex
*config.C
Store store.I
// Owners' pubkeys
Owners []by
Followed, OwnersFollowed, Muted map[st]struct{}
// OwnersFollowLists are the event IDs of owners follow lists, which must not be deleted,
// only replaced.
// Owners' pubkeys. If any are set, C.AuthRequired is implicitly on.
Owners []by
// Followed are the relay owners' follows, who are granted access if there is
// Owners set.
Followed,
// OwnersFollowed are the whitelisted npubs that have general read/write access.
// This enables the use of standard nostr follow events as a means to govern
// access to a relay.
OwnersFollowed,
// FollowsFollows is populated if the relay config.C.FollowsFollows is true.
// These are the follows of the Followed.
FollowsFollows,
// Muted are a list of npubs that the Owners have designated no events from them
// can ever be stored no matter who submits them.
Muted map[st]struct{}
// OwnersFollowLists are the event IDs of owners follow lists, which must not be
// deleted, only replaced. This list includes follow lists of follows, if
// FollowsFollows is enabled, to extend this same protection to preventing
// delete of events. kind.FollowList events are protected from deleting in
// general, and are not deleted when replaced, in order that they remain
// recoverable.
OwnersFollowLists []by
// OwnersMuteLists are the event IDs of owners mute lists, which must not be deleted, only
// replaced.
// OwnersMuteLists are the event IDs of owners mute lists, which must not be
// deleted, only replaced.
OwnersMuteLists []by
}

Expand Down Expand Up @@ -61,6 +78,7 @@ func (r *Relay) Init() (err er) {
})
r.Followed = make(map[st]struct{})
r.OwnersFollowed = make(map[st]struct{})
r.FollowsFollows = make(map[st]struct{})
r.Muted = make(map[st]struct{})
r.CheckOwnerLists(context.Bg())
return nil
Expand Down Expand Up @@ -88,6 +106,22 @@ func (r *Relay) AcceptEvent(c cx, evt *event.T, hr *http.Request, origin st,
return true, "", func() {
r.Followed = make(map[st]struct{})
r.OwnersFollowed = make(map[st]struct{})
r.FollowsFollows = make(map[st]struct{})
r.OwnersFollowLists = r.OwnersFollowLists[:0]
r.Muted = make(map[st]struct{})
r.OwnersMuteLists = r.OwnersMuteLists[:0]
r.CheckOwnerLists(context.Bg())
}
}
}
// the FollowsFollows list will only be populated if this feature is enabled in
// config.
for o := range r.FollowsFollows {
if equals(by(o), evt.PubKey) {
return true, "", func() {
r.Followed = make(map[st]struct{})
r.OwnersFollowed = make(map[st]struct{})
r.FollowsFollows = make(map[st]struct{})
r.OwnersFollowLists = r.OwnersFollowLists[:0]
r.Muted = make(map[st]struct{})
r.OwnersMuteLists = r.OwnersMuteLists[:0]
Expand All @@ -103,6 +137,7 @@ func (r *Relay) AcceptEvent(c cx, evt *event.T, hr *http.Request, origin st,
return true, "", func() {
r.Followed = make(map[st]struct{})
r.OwnersFollowed = make(map[st]struct{})
r.FollowsFollows = make(map[st]struct{})
r.OwnersFollowLists = r.OwnersFollowLists[:0]
r.Muted = make(map[st]struct{})
r.OwnersMuteLists = r.OwnersMuteLists[:0]
Expand Down Expand Up @@ -171,7 +206,7 @@ func (r *Relay) AcceptEvent(c cx, evt *event.T, hr *http.Request, origin st,
" because on owner mute list", nil
}
}
// for all else, check the authed pubkey is in the follow list
// check the authed pubkey is in the follow list
for pk := range r.Followed {
// allow all events from follows of owners
if equals(authedPubkey, by(pk)) {
Expand All @@ -180,6 +215,14 @@ func (r *Relay) AcceptEvent(c cx, evt *event.T, hr *http.Request, origin st,
return true, "", nil
}
}
for pk := range r.FollowsFollows {
// allow all events from follows of owners follows
if equals(authedPubkey, by(pk)) {
log.I.F("accepting event %0x because %0x on owners follow's follow list",
evt.ID, by(pk))
return true, "", nil
}
}
}
}
// if auth is enabled and there is no moderators we just check that the pubkey
Expand Down Expand Up @@ -289,31 +332,35 @@ func (r *Relay) CheckOwnerLists(c cx) {
}
}
evs = evs[:0]
// next, search for the follow lists of all on the follow list
log.D.Ln("searching for owners follows follow lists")
var followed []st
for f := range r.Followed {
followed = append(followed, f)
}
if evs, err = r.Store.QueryEvents(c,
&filter.T{Authors: tag.New(followed...),
Kinds: kinds.New(kind.FollowList)}); chk.E(err) {
}
for _, ev := range evs {
// we want to protect the follow lists of users as well so they also cannot be
// deleted, only replaced.
r.OwnersFollowLists = append(r.OwnersFollowLists, ev.ID)
for _, t := range ev.Tags.F() {
if equals(t.Key(), by("p")) {
var p by
if p, err = hex.Dec(st(t.Value())); err != nil {
continue
if r.C.FollowsFollows {
// next, search for the follow lists of all on the follow list. this feature
// enables whitelisted users follows to use the relay as inbox for DMs and
// replies.
log.D.Ln("searching for owners follows follow lists")
var followed []st
for f := range r.Followed {
followed = append(followed, f)
}
if evs, err = r.Store.QueryEvents(c,
&filter.T{Authors: tag.New(followed...),
Kinds: kinds.New(kind.FollowList)}); chk.E(err) {
}
for _, ev := range evs {
// we want to protect the follow lists of users as well so they also cannot be
// deleted, only replaced.
r.OwnersFollowLists = append(r.OwnersFollowLists, ev.ID)
for _, t := range ev.Tags.F() {
if equals(t.Key(), by("p")) {
var p by
if p, err = hex.Dec(st(t.Value())); err != nil {
continue
}
r.FollowsFollows[st(p)] = struct{}{}
}
r.Followed[st(p)] = struct{}{}
}
}
evs = evs[:0]
}
evs = evs[:0]
}
if len(r.Muted) < 1 {
log.D.Ln("regenerating owners mute lists")
Expand All @@ -336,26 +383,17 @@ func (r *Relay) CheckOwnerLists(c cx) {
}
evs = evs[:0]
}
log.I.F("%d allowed npubs, %d blocked", len(r.Followed), len(r.Muted))
// // log this info
// o := "followed:\n"
// for pk := range r.Followed {
// o += fmt.Sprintf("%0x,", pk)
// }
// o += "\nmuted:\n"
// for pk := range r.Muted {
// o += fmt.Sprintf("%0x,", pk)
// }
// log.T.F("%s\n", o)
log.I.F("%d allowed npubs, %d allowed npubs follows, %d blocked",
len(r.Followed), len(r.FollowsFollows), len(r.Muted))
}
}

func (r *Relay) AuthEnabled() bo { return r.C.AuthRequired }
func (r *Relay) AuthEnabled() bo { return r.C.AuthRequired || len(r.C.Owners) > 0 }

// ServiceUrl returns the address of the relay to send back in auth responses.
// If auth is disabled this returns an empty string.
func (r *Relay) ServiceUrl(req *http.Request) (s st) {
if !r.C.AuthRequired {
if !r.C.AuthRequired || len(r.C.Owners) > 0 {
return
}
host := req.Header.Get("X-Forwarded-Host")
Expand Down
31 changes: 0 additions & 31 deletions ratel/keys/index/prefixes.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@ package index

import (
"realy.lol/ratel/keys"
"realy.lol/ratel/keys/createdat"
"realy.lol/ratel/keys/id"
"realy.lol/ratel/keys/kinder"
"realy.lol/ratel/keys/pubkey"
"realy.lol/ratel/keys/serial"
)

type P byte
Expand Down Expand Up @@ -122,29 +117,3 @@ var FilterPrefixes = []by{
{Tag32.B()},
{TagAddr.B()},
}

// KeySizes are the byte size of keys of each type of key prefix. int(P) or call the P.I() method
// corresponds to the index 1:1. For future index additions be sure to add the
// relevant KeySizes sum as it describes the data for a programmer.
var KeySizes = []no{
// Event
1 + serial.Len,
// CreatedAt
1 + createdat.Len + serial.Len,
// Id
1 + id.Len + serial.Len,
// Kind
1 + kinder.Len + createdat.Len + serial.Len,
// Pubkey
1 + pubkey.Len + createdat.Len + serial.Len,
// PubkeyKind
1 + pubkey.Len + kinder.Len + createdat.Len + serial.Len,
// Tag (worst case scenario)
1 + 100 + createdat.Len + serial.Len,
// Tag32
1 + pubkey.Len + createdat.Len + serial.Len,
// TagAddr
1 + kinder.Len + pubkey.Len + 100 + createdat.Len + serial.Len,
// Counter
1 + serial.Len,
}
35 changes: 18 additions & 17 deletions realy/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,24 @@ import (
)

type C struct {
AppName st `env:"APP_NAME" default:"realy"`
Profile st `env:"PROFILE" usage:"root path for all other path configurations (based on APP_NAME and OS specific location)"`
Listen st `env:"LISTEN" default:"0.0.0.0" usage:"network listen address"`
Port no `env:"PORT" default:"3334" usage:"port to listen on"`
AdminUser st `env:"ADMIN_USER" default:"admin" usage:"admin user"`
AdminPass st `env:"ADMIN_PASS" usage:"admin password"`
LogLevel st `env:"LOG_LEVEL" default:"info" usage:"debug level: fatal error warn info debug trace"`
DbLogLevel st `env:"DB_LOG_LEVEL" default:"info" usage:"debug level: fatal error warn info debug trace"`
AuthRequired bo `env:"AUTH_REQUIRED" default:"false" usage:"requires auth for all access"`
Owners []st `env:"OWNERS" usage:"list of npubs of users in hex format whose follow and mute list dictate accepting requests and events with AUTH_REQUIRED enabled - follows and follows follows are allowed to read/write, owners mutes events are rejected"`
DBSizeLimit no `env:"DB_SIZE_LIMIT" default:"0" usage:"the number of gigabytes (1,000,000,000 bytes) we want to keep the data store from exceeding, 0 means disabled"`
DBLowWater no `env:"DB_LOW_WATER" default:"60" usage:"the percentage of DBSizeLimit a GC run will reduce the used storage down to"`
DBHighWater no `env:"DB_HIGH_WATER" default:"80" usage:"the trigger point at which a GC run should start if exceeded"`
GCFrequency no `env:"GC_FREQUENCY" default:"3600" usage:"the frequency of checks of the current utilisation in minutes"`
Pprof bo `env:"PPROF" default:"false" usage:"enable pprof on 127.0.0.1:6060"`
MemLimit no `env:"MEMLIMIT" default:"250000000" usage:"set memory limit, default is 250Mb"`
NWC st `env:"NWC" usage:"NWC connection string for relay to interact with an NWC enabled wallet"`
AppName st `env:"APP_NAME" default:"realy"`
Profile st `env:"PROFILE" usage:"root path for all other path configurations (based on APP_NAME and OS specific location)"`
Listen st `env:"LISTEN" default:"0.0.0.0" usage:"network listen address"`
Port no `env:"PORT" default:"3334" usage:"port to listen on"`
AdminUser st `env:"ADMIN_USER" default:"admin" usage:"admin user"`
AdminPass st `env:"ADMIN_PASS" usage:"admin password"`
LogLevel st `env:"LOG_LEVEL" default:"info" usage:"debug level: fatal error warn info debug trace"`
DbLogLevel st `env:"DB_LOG_LEVEL" default:"info" usage:"debug level: fatal error warn info debug trace"`
AuthRequired bo `env:"AUTH_REQUIRED" default:"false" usage:"requires auth for all access, implicitly enabled if any relay owners are set, otherwise makes the relay require auth."`
Owners []st `env:"OWNERS" usage:"list of npubs of users in hex format; if any are present, AUTH_REQUIRED is implicitly enabled, the npubs of the owners' follow lists are whitelisted for general read/write access, and events published by users on owners' mute lists will not be saved on the relay."`
FollowsFollows bo `env:"FOLLOWS_FOLLOWS" usage:"enable allowing access to the follows of owners follows (facilitates nip-65 inboxes to work for follows)"`
DBSizeLimit no `env:"DB_SIZE_LIMIT" default:"0" usage:"the number of gigabytes (1,000,000,000 bytes) we want to keep the data store from exceeding, 0 means disabled"`
DBLowWater no `env:"DB_LOW_WATER" default:"60" usage:"the percentage of DBSizeLimit a GC run will reduce the used storage down to"`
DBHighWater no `env:"DB_HIGH_WATER" default:"80" usage:"the trigger point at which a GC run should start if exceeded"`
GCFrequency no `env:"GC_FREQUENCY" default:"3600" usage:"the frequency of checks of the current utilisation in minutes"`
Pprof bo `env:"PPROF" default:"false" usage:"enable pprof on 127.0.0.1:6060"`
MemLimit no `env:"MEMLIMIT" default:"250000000" usage:"set memory limit, default is 250Mb"`
NWC st `env:"NWC" usage:"NWC connection string for relay to interact with an NWC enabled wallet"`
}

func New() (cfg *C, err er) {
Expand Down
2 changes: 1 addition & 1 deletion realy/version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v1.2.28
v1.2.29

0 comments on commit d0a79e4

Please sign in to comment.