package acl import ( "bytes" "time" "smesh.lol/pkg/grapevine" "smesh.lol/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 int } func NewFollows(s *store.Engine, adminHexPubkeys []string, freqSec int) *Follows { admins := [][]byte{:0:len(adminHexPubkeys)} for _, h := range adminHexPubkeys { if pk := hexDec(h); len(pk) == 32 { admins = append(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) 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) bool { return true } func (f *Follows) IsFollowed(pubkey []byte) 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) []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) 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 }