package acl import ( "git.smesh.lol/moxie/pkg/mxutil" "bytes" "time" "git.smesh.lol/morly/pkg/grapevine" "git.smesh.lol/morly/pkg/store" ) // Follows is an ACL that allows writes only from pubkeys followed // by the configured admin set. Follow lists (kind 3) are fetched // from the store at a configurable frequency. type Follows struct { store *store.Engine admins [][]byte followed map[string]bool lastRefresh int64 freqSec int32 } func NewFollows(s *store.Engine, adminHexPubkeys []string, freqSec int32) (f *Follows) { admins := [][]byte{:0:len(adminHexPubkeys)} for _, h := range adminHexPubkeys { if pk := hexDec(h); len(pk) == 32 { admins = mxutil.Ensure(admins, 1) admins = push(admins, pk) } } f := &Follows{ store: s, admins: admins, followed: map[string]bool{}, freqSec: freqSec, } f.refresh() return f } func (f *Follows) AllowWrite(pubkey []byte, _ uint16) (ok bool) { f.maybeRefresh() for _, a := range f.admins { if bytes.Equal(a, pubkey) { return true } } return f.followed[string(pubkey)] } func (f *Follows) AllowRead([]byte) (ok bool) { return true } func (f *Follows) IsFollowed(pubkey []byte) (ok bool) { f.maybeRefresh() return f.followed[string(pubkey)] } func (f *Follows) maybeRefresh() { now := time.Now().Unix() if now-f.lastRefresh < int64(f.freqSec) { return } f.refresh() } func (f *Follows) refresh() { f.lastRefresh = time.Now().Unix() m := map[string]bool{} for _, admin := range f.admins { m[string(admin)] = true for _, pk := range grapevine.GetFollows(f.store, admin) { m[string(pk)] = true } } f.followed = m } func hexDec(s string) (buf []byte) { if len(s)%2 != 0 { return nil } b := []byte{:len(s) / 2} for i := 0; i < len(b); i++ { hi := unhex(s[i*2]) lo := unhex(s[i*2+1]) if hi == 0xff || lo == 0xff { return nil } b[i] = hi<<4 | lo } return b } func unhex(c byte) (b byte) { switch { case c >= '0' && c <= '9': return c - '0' case c >= 'a' && c <= 'f': return c - 'a' + 10 case c >= 'A' && c <= 'F': return c - 'A' + 10 } return 0xff }