managed.go raw

   1  package acl
   2  
   3  import (
   4  	"context"
   5  	"encoding/hex"
   6  	"net"
   7  	"reflect"
   8  	"sync"
   9  
  10  	"next.orly.dev/pkg/lol/errorf"
  11  	"next.orly.dev/pkg/lol/log"
  12  	"next.orly.dev/app/config"
  13  	"next.orly.dev/pkg/database"
  14  	"next.orly.dev/pkg/nostr/encoders/bech32encoding"
  15  	"next.orly.dev/pkg/nostr/encoders/event"
  16  	"next.orly.dev/pkg/utils"
  17  )
  18  
  19  type Managed struct {
  20  	// Ctx holds the context for the ACL.
  21  	// Deprecated: Use Context() method instead of accessing directly.
  22  	Ctx context.Context
  23  	cfg *config.C
  24  	db  database.Database
  25  	managedACL *database.ManagedACL
  26  	owners     [][]byte
  27  	admins     [][]byte
  28  	peerAdmins [][]byte // peer relay identity pubkeys with admin access
  29  	mx         sync.RWMutex
  30  }
  31  
  32  // Context returns the ACL context.
  33  func (m *Managed) Context() context.Context {
  34  	return m.Ctx
  35  }
  36  
  37  func (m *Managed) Configure(cfg ...any) (err error) {
  38  	log.I.F("configuring managed ACL")
  39  	for _, ca := range cfg {
  40  		switch c := ca.(type) {
  41  		case *config.C:
  42  			m.cfg = c
  43  		case database.Database:
  44  			m.db = c
  45  			// ManagedACL requires the concrete Badger database type
  46  			// Type assertion to check if it's a Badger database
  47  			if d, ok := c.(*database.D); ok {
  48  				m.managedACL = database.NewManagedACL(d)
  49  			} else {
  50  				log.W.F("managed ACL: database is not Badger, managed ACL features will be limited")
  51  			}
  52  		case context.Context:
  53  			m.Ctx = c
  54  		default:
  55  			err = errorf.E("invalid type: %T", reflect.TypeOf(ca))
  56  		}
  57  	}
  58  	if m.cfg == nil || m.db == nil {
  59  		err = errorf.E("both config and database must be set")
  60  		return
  61  	}
  62  
  63  	// Load owners
  64  	for _, owner := range m.cfg.Owners {
  65  		if len(owner) == 0 {
  66  			continue
  67  		}
  68  		var pk []byte
  69  		if pk, err = bech32encoding.NpubOrHexToPublicKeyBinary(owner); err != nil {
  70  			continue
  71  		}
  72  		m.owners = append(m.owners, pk)
  73  	}
  74  
  75  	// Load admins
  76  	for _, admin := range m.cfg.Admins {
  77  		if len(admin) == 0 {
  78  			continue
  79  		}
  80  		var pk []byte
  81  		if pk, err = bech32encoding.NpubOrHexToPublicKeyBinary(admin); err != nil {
  82  			continue
  83  		}
  84  		m.admins = append(m.admins, pk)
  85  	}
  86  
  87  	return
  88  }
  89  
  90  // UpdatePeerAdmins updates the list of peer relay identity pubkeys that have admin access
  91  func (m *Managed) UpdatePeerAdmins(peerPubkeys [][]byte) {
  92  	m.mx.Lock()
  93  	defer m.mx.Unlock()
  94  	m.peerAdmins = make([][]byte, len(peerPubkeys))
  95  	copy(m.peerAdmins, peerPubkeys)
  96  	log.I.F("updated peer admin list with %d pubkeys", len(peerPubkeys))
  97  }
  98  
  99  func (m *Managed) GetAccessLevel(pub []byte, address string) (level string) {
 100  	m.mx.RLock()
 101  	defer m.mx.RUnlock()
 102  
 103  	// If no pubkey provided and auth is required, return "none"
 104  	if len(pub) == 0 && m.cfg.AuthRequired {
 105  		return "none"
 106  	}
 107  
 108  	// Check owners first
 109  	for _, v := range m.owners {
 110  		if utils.FastEqual(v, pub) {
 111  			return "owner"
 112  		}
 113  	}
 114  
 115  	// Check admins
 116  	for _, v := range m.admins {
 117  		if utils.FastEqual(v, pub) {
 118  			return "admin"
 119  		}
 120  	}
 121  
 122  	// Check peer relay identity pubkeys (they get admin access)
 123  	for _, v := range m.peerAdmins {
 124  		if utils.FastEqual(v, pub) {
 125  			return "admin"
 126  		}
 127  	}
 128  
 129  	// managedACL may be nil when database is not Badger (e.g., gRPC proxy).
 130  	// Fall through to default read access in that case.
 131  	if m.managedACL == nil {
 132  		if len(pub) == 0 {
 133  			return "none"
 134  		}
 135  		return "read"
 136  	}
 137  
 138  	// Check if pubkey is banned
 139  	pubkeyHex := hex.EncodeToString(pub)
 140  	if banned, err := m.managedACL.IsPubkeyBanned(pubkeyHex); err == nil && banned {
 141  		return "banned"
 142  	}
 143  
 144  	// Check if pubkey is explicitly allowed
 145  	if allowed, err := m.managedACL.IsPubkeyAllowed(pubkeyHex); err == nil && allowed {
 146  		return "write"
 147  	}
 148  
 149  	// Check if IP is blocked
 150  	if blocked, err := m.managedACL.IsIPBlocked(address); err == nil && blocked {
 151  		return "blocked"
 152  	}
 153  
 154  	// Default to read-only for managed mode
 155  	return "read"
 156  }
 157  
 158  func (m *Managed) CheckPolicy(ev *event.E) (allowed bool, err error) {
 159  	// If managedACL is nil (non-Badger DB), allow everything
 160  	if m.managedACL == nil {
 161  		return true, nil
 162  	}
 163  
 164  	// Check if event is banned
 165  	eventID := hex.EncodeToString(ev.ID)
 166  	if banned, err := m.managedACL.IsEventBanned(eventID); err == nil && banned {
 167  		return false, nil
 168  	}
 169  
 170  	// Check if event is explicitly allowed
 171  	if allowed, err := m.managedACL.IsEventAllowed(eventID); err == nil && allowed {
 172  		return true, nil
 173  	}
 174  
 175  	// Check if event kind is allowed
 176  	if allowed, err := m.managedACL.IsKindAllowed(int(ev.Kind)); err == nil && !allowed {
 177  		// If there are allowed kinds configured and this kind is not in the list, deny
 178  		allowedKinds, err := m.managedACL.ListAllowedKinds()
 179  		if err == nil && len(allowedKinds) > 0 {
 180  			return false, nil
 181  		}
 182  	}
 183  
 184  	// Check if author is banned
 185  	authorHex := hex.EncodeToString(ev.Pubkey)
 186  	if banned, err := m.managedACL.IsPubkeyBanned(authorHex); err == nil && banned {
 187  		return false, nil
 188  	}
 189  
 190  	// Check if author is explicitly allowed
 191  	if allowed, err := m.managedACL.IsPubkeyAllowed(authorHex); err == nil && allowed {
 192  		return true, nil
 193  	}
 194  
 195  	// For managed mode, default to allowing events from owners and admins
 196  	for _, v := range m.owners {
 197  		if utils.FastEqual(v, ev.Pubkey) {
 198  			return true, nil
 199  		}
 200  	}
 201  
 202  	for _, v := range m.admins {
 203  		if utils.FastEqual(v, ev.Pubkey) {
 204  			return true, nil
 205  		}
 206  	}
 207  
 208  	// Check if we should add this event to moderation queue
 209  	// This could be extended to add events to moderation based on content analysis
 210  	// For now, we'll just allow the event
 211  
 212  	// Default to allowing events in managed mode (can be restricted by explicit bans/allows)
 213  	return true, nil
 214  }
 215  
 216  func (m *Managed) GetACLInfo() (name, description, documentation string) {
 217  	return "managed", "managed ACL with NIP-86 support",
 218  		`Managed ACL mode provides fine-grained access control through NIP-86 management API.
 219  
 220  Features:
 221  - Ban/allow specific pubkeys
 222  - Ban/allow specific events
 223  - Block IP addresses
 224  - Allow/deny specific event kinds
 225  - Relay metadata management
 226  - Event moderation queue
 227  
 228  This mode requires explicit management through the NIP-86 API endpoints.
 229  Only relay owners can access the management interface and API.`
 230  }
 231  
 232  func (m *Managed) Type() string {
 233  	return "managed"
 234  }
 235  
 236  func (m *Managed) Syncer() {
 237  	// Managed ACL doesn't need background syncing
 238  	// All management is done through the API
 239  }
 240  
 241  // Helper methods for the management API
 242  
 243  // IsIPBlocked checks if an IP address is blocked
 244  func (m *Managed) IsIPBlocked(ip string) bool {
 245  	// Parse IP to handle both IPv4 and IPv6
 246  	parsedIP := net.ParseIP(ip)
 247  	if parsedIP == nil {
 248  		return false
 249  	}
 250  
 251  	blocked, err := m.managedACL.IsIPBlocked(ip)
 252  	if err != nil {
 253  		log.W.F("error checking if IP is blocked: %v", err)
 254  		return false
 255  	}
 256  	return blocked
 257  }
 258  
 259  // GetManagedACL returns the managed ACL database instance
 260  func (m *Managed) GetManagedACL() *database.ManagedACL {
 261  	return m.managedACL
 262  }
 263  
 264  func init() {
 265  	Registry.Register(new(Managed))
 266  }
 267