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