package acl import ( "bytes" "time" "smesh.lol/pkg/grapevine" "smesh.lol/pkg/store" ) // Social is a WoT-depth-based ACL. Uses the grapevine package to // compute follow-graph depth from admin seeds. Pubkeys at different // depths get different treatment (the server can query depth for // throttle decisions). type Social struct { wot *grapevine.WoT admins [][]byte maxDepth int refreshSec int lastRefresh int64 depthMap map[string]int } func NewSocial(s *store.Engine, adminHexPubkeys []string, maxDepth, refreshSec int) *Social { admins := [][]byte{:0:len(adminHexPubkeys)} for _, h := range adminHexPubkeys { if pk := hexDec(h); len(pk) == 32 { admins = append(admins, pk) } } sc := &Social{ wot: grapevine.New(s), admins: admins, maxDepth: maxDepth, refreshSec: refreshSec, depthMap: map[string]int{}, } sc.refresh() return sc } func (s *Social) AllowWrite(pubkey []byte, _ uint16) bool { s.maybeRefresh() for _, a := range s.admins { if bytes.Equal(a, pubkey) { return true } } _, known := s.depthMap[string(pubkey)] return known } func (s *Social) AllowRead([]byte) bool { return true } // Depth returns the WoT depth for a pubkey. 0 = admin, -1 = outsider. func (s *Social) Depth(pubkey []byte) int { s.maybeRefresh() for _, a := range s.admins { if bytes.Equal(a, pubkey) { return 0 } } if d, ok := s.depthMap[string(pubkey)]; ok { return d } return -1 } func (s *Social) maybeRefresh() { now := time.Now().Unix() if now-s.lastRefresh < int64(s.refreshSec) { return } s.refresh() } func (s *Social) refresh() { s.lastRefresh = time.Now().Unix() m := map[string]int{} for _, admin := range s.admins { scores := s.wot.Compute(admin, s.maxDepth) for _, sc := range scores { key := string(sc.Pubkey) if existing, ok := m[key]; ok && existing <= sc.Depth { continue } m[key] = sc.Depth } } s.depthMap = m }