follows.mx raw

   1  package acl
   2  
   3  import (
   4  	"git.smesh.lol/moxie/pkg/mxutil"
   5  	"bytes"
   6  	"time"
   7  
   8  	"git.smesh.lol/morly/pkg/grapevine"
   9  	"git.smesh.lol/morly/pkg/store"
  10  )
  11  
  12  // Follows is an ACL that allows writes only from pubkeys followed
  13  // by the configured admin set. Follow lists (kind 3) are fetched
  14  // from the store at a configurable frequency.
  15  type Follows struct {
  16  	store      *store.Engine
  17  	admins     [][]byte
  18  	followed   map[string]bool
  19  	lastRefresh int64
  20  	freqSec    int32
  21  }
  22  
  23  func NewFollows(s *store.Engine, adminHexPubkeys []string, freqSec int32) (f *Follows) {
  24  	admins := [][]byte{:0:len(adminHexPubkeys)}
  25  	for _, h := range adminHexPubkeys {
  26  		if pk := hexDec(h); len(pk) == 32 {
  27  			admins = mxutil.Ensure(admins, 1)
  28  			admins = push(admins, pk)
  29  		}
  30  	}
  31  	f := &Follows{
  32  		store:    s,
  33  		admins:   admins,
  34  		followed: map[string]bool{},
  35  		freqSec:  freqSec,
  36  	}
  37  	f.refresh()
  38  	return f
  39  }
  40  
  41  func (f *Follows) AllowWrite(pubkey []byte, _ uint16) (ok bool) {
  42  	f.maybeRefresh()
  43  	for _, a := range f.admins {
  44  		if bytes.Equal(a, pubkey) {
  45  			return true
  46  		}
  47  	}
  48  	return f.followed[string(pubkey)]
  49  }
  50  
  51  func (f *Follows) AllowRead([]byte) (ok bool) { return true }
  52  
  53  func (f *Follows) IsFollowed(pubkey []byte) (ok bool) {
  54  	f.maybeRefresh()
  55  	return f.followed[string(pubkey)]
  56  }
  57  
  58  func (f *Follows) maybeRefresh() {
  59  	now := time.Now().Unix()
  60  	if now-f.lastRefresh < int64(f.freqSec) {
  61  		return
  62  	}
  63  	f.refresh()
  64  }
  65  
  66  func (f *Follows) refresh() {
  67  	f.lastRefresh = time.Now().Unix()
  68  	m := map[string]bool{}
  69  	for _, admin := range f.admins {
  70  		m[string(admin)] = true
  71  		for _, pk := range grapevine.GetFollows(f.store, admin) {
  72  			m[string(pk)] = true
  73  		}
  74  	}
  75  	f.followed = m
  76  }
  77  
  78  func hexDec(s string) (buf []byte) {
  79  	if len(s)%2 != 0 {
  80  		return nil
  81  	}
  82  	b := []byte{:len(s) / 2}
  83  	for i := 0; i < len(b); i++ {
  84  		hi := unhex(s[i*2])
  85  		lo := unhex(s[i*2+1])
  86  		if hi == 0xff || lo == 0xff {
  87  			return nil
  88  		}
  89  		b[i] = hi<<4 | lo
  90  	}
  91  	return b
  92  }
  93  
  94  func unhex(c byte) (b byte) {
  95  	switch {
  96  	case c >= '0' && c <= '9':
  97  		return c - '0'
  98  	case c >= 'a' && c <= 'f':
  99  		return c - 'a' + 10
 100  	case c >= 'A' && c <= 'F':
 101  		return c - 'A' + 10
 102  	}
 103  	return 0xff
 104  }
 105