implemented nip-86 relay management API and added to relay client
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package acl
|
||||
|
||||
import (
|
||||
"next.orly.dev/pkg/encoders/event"
|
||||
"next.orly.dev/pkg/interfaces/acl"
|
||||
"next.orly.dev/pkg/utils/atomic"
|
||||
)
|
||||
@@ -78,3 +79,21 @@ func (s *S) AddFollow(pub []byte) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CheckPolicy checks if an event is allowed by the active ACL policy
|
||||
func (s *S) CheckPolicy(ev *event.E) (allowed bool, err error) {
|
||||
for _, i := range s.ACL {
|
||||
if i.Type() == s.Active.Load() {
|
||||
// Check if the ACL implementation has a CheckPolicy method
|
||||
if policyChecker, ok := i.(interface {
|
||||
CheckPolicy(ev *event.E) (allowed bool, err error)
|
||||
}); ok {
|
||||
return policyChecker.CheckPolicy(ev)
|
||||
}
|
||||
// If no CheckPolicy method, default to allowing
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
// If no active ACL, default to allowing
|
||||
return true, nil
|
||||
}
|
||||
|
||||
223
pkg/acl/managed.go
Normal file
223
pkg/acl/managed.go
Normal file
@@ -0,0 +1,223 @@
|
||||
package acl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"net"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"lol.mleku.dev/errorf"
|
||||
"lol.mleku.dev/log"
|
||||
"next.orly.dev/app/config"
|
||||
"next.orly.dev/pkg/database"
|
||||
"next.orly.dev/pkg/encoders/bech32encoding"
|
||||
"next.orly.dev/pkg/encoders/event"
|
||||
"next.orly.dev/pkg/utils"
|
||||
)
|
||||
|
||||
type Managed struct {
|
||||
Ctx context.Context
|
||||
cfg *config.C
|
||||
*database.D
|
||||
managedACL *database.ManagedACL
|
||||
owners [][]byte
|
||||
admins [][]byte
|
||||
mx sync.RWMutex
|
||||
}
|
||||
|
||||
func (m *Managed) Configure(cfg ...any) (err error) {
|
||||
log.I.F("configuring managed ACL")
|
||||
for _, ca := range cfg {
|
||||
switch c := ca.(type) {
|
||||
case *config.C:
|
||||
m.cfg = c
|
||||
case *database.D:
|
||||
m.D = c
|
||||
m.managedACL = database.NewManagedACL(c)
|
||||
case context.Context:
|
||||
m.Ctx = c
|
||||
default:
|
||||
err = errorf.E("invalid type: %T", reflect.TypeOf(ca))
|
||||
}
|
||||
}
|
||||
if m.cfg == nil || m.D == nil {
|
||||
err = errorf.E("both config and database must be set")
|
||||
return
|
||||
}
|
||||
|
||||
// Load owners
|
||||
for _, owner := range m.cfg.Owners {
|
||||
if len(owner) == 0 {
|
||||
continue
|
||||
}
|
||||
var pk []byte
|
||||
if pk, err = bech32encoding.NpubOrHexToPublicKeyBinary(owner); err != nil {
|
||||
continue
|
||||
}
|
||||
m.owners = append(m.owners, pk)
|
||||
}
|
||||
|
||||
// Load admins
|
||||
for _, admin := range m.cfg.Admins {
|
||||
if len(admin) == 0 {
|
||||
continue
|
||||
}
|
||||
var pk []byte
|
||||
if pk, err = bech32encoding.NpubOrHexToPublicKeyBinary(admin); err != nil {
|
||||
continue
|
||||
}
|
||||
m.admins = append(m.admins, pk)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Managed) GetAccessLevel(pub []byte, address string) (level string) {
|
||||
m.mx.RLock()
|
||||
defer m.mx.RUnlock()
|
||||
|
||||
// If no pubkey provided and auth is required, return "none"
|
||||
if len(pub) == 0 && m.cfg.AuthRequired {
|
||||
return "none"
|
||||
}
|
||||
|
||||
// Check owners first
|
||||
for _, v := range m.owners {
|
||||
if utils.FastEqual(v, pub) {
|
||||
return "owner"
|
||||
}
|
||||
}
|
||||
|
||||
// Check admins
|
||||
for _, v := range m.admins {
|
||||
if utils.FastEqual(v, pub) {
|
||||
return "admin"
|
||||
}
|
||||
}
|
||||
|
||||
// Check if pubkey is banned
|
||||
pubkeyHex := hex.EncodeToString(pub)
|
||||
if banned, err := m.managedACL.IsPubkeyBanned(pubkeyHex); err == nil && banned {
|
||||
return "banned"
|
||||
}
|
||||
|
||||
// Check if pubkey is explicitly allowed
|
||||
if allowed, err := m.managedACL.IsPubkeyAllowed(pubkeyHex); err == nil && allowed {
|
||||
return "write"
|
||||
}
|
||||
|
||||
// Check if IP is blocked
|
||||
if blocked, err := m.managedACL.IsIPBlocked(address); err == nil && blocked {
|
||||
return "blocked"
|
||||
}
|
||||
|
||||
// Default to read-only for managed mode
|
||||
return "read"
|
||||
}
|
||||
|
||||
func (m *Managed) CheckPolicy(ev *event.E) (allowed bool, err error) {
|
||||
// Check if event is banned
|
||||
eventID := hex.EncodeToString(ev.ID)
|
||||
if banned, err := m.managedACL.IsEventBanned(eventID); err == nil && banned {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Check if event is explicitly allowed
|
||||
if allowed, err := m.managedACL.IsEventAllowed(eventID); err == nil && allowed {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Check if event kind is allowed
|
||||
if allowed, err := m.managedACL.IsKindAllowed(int(ev.Kind)); err == nil && !allowed {
|
||||
// If there are allowed kinds configured and this kind is not in the list, deny
|
||||
allowedKinds, err := m.managedACL.ListAllowedKinds()
|
||||
if err == nil && len(allowedKinds) > 0 {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Check if author is banned
|
||||
authorHex := hex.EncodeToString(ev.Pubkey)
|
||||
if banned, err := m.managedACL.IsPubkeyBanned(authorHex); err == nil && banned {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Check if author is explicitly allowed
|
||||
if allowed, err := m.managedACL.IsPubkeyAllowed(authorHex); err == nil && allowed {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// For managed mode, default to allowing events from owners and admins
|
||||
for _, v := range m.owners {
|
||||
if utils.FastEqual(v, ev.Pubkey) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range m.admins {
|
||||
if utils.FastEqual(v, ev.Pubkey) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we should add this event to moderation queue
|
||||
// This could be extended to add events to moderation based on content analysis
|
||||
// For now, we'll just allow the event
|
||||
|
||||
// Default to allowing events in managed mode (can be restricted by explicit bans/allows)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (m *Managed) GetACLInfo() (name, description, documentation string) {
|
||||
return "managed", "managed ACL with NIP-86 support",
|
||||
`Managed ACL mode provides fine-grained access control through NIP-86 management API.
|
||||
|
||||
Features:
|
||||
- Ban/allow specific pubkeys
|
||||
- Ban/allow specific events
|
||||
- Block IP addresses
|
||||
- Allow/deny specific event kinds
|
||||
- Relay metadata management
|
||||
- Event moderation queue
|
||||
|
||||
This mode requires explicit management through the NIP-86 API endpoints.
|
||||
Only relay owners can access the management interface and API.`
|
||||
}
|
||||
|
||||
func (m *Managed) Type() string {
|
||||
return "managed"
|
||||
}
|
||||
|
||||
func (m *Managed) Syncer() {
|
||||
// Managed ACL doesn't need background syncing
|
||||
// All management is done through the API
|
||||
}
|
||||
|
||||
// Helper methods for the management API
|
||||
|
||||
// IsIPBlocked checks if an IP address is blocked
|
||||
func (m *Managed) IsIPBlocked(ip string) bool {
|
||||
// Parse IP to handle both IPv4 and IPv6
|
||||
parsedIP := net.ParseIP(ip)
|
||||
if parsedIP == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
blocked, err := m.managedACL.IsIPBlocked(ip)
|
||||
if err != nil {
|
||||
log.W.F("error checking if IP is blocked: %v", err)
|
||||
return false
|
||||
}
|
||||
return blocked
|
||||
}
|
||||
|
||||
// GetManagedACL returns the managed ACL database instance
|
||||
func (m *Managed) GetManagedACL() *database.ManagedACL {
|
||||
return m.managedACL
|
||||
}
|
||||
|
||||
func init() {
|
||||
log.T.F("registering managed ACL")
|
||||
Registry.Register(new(Managed))
|
||||
}
|
||||
107
pkg/acl/managed_minimal_test.go
Normal file
107
pkg/acl/managed_minimal_test.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package acl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"next.orly.dev/app/config"
|
||||
"next.orly.dev/pkg/database"
|
||||
"next.orly.dev/pkg/encoders/event"
|
||||
)
|
||||
|
||||
func TestManagedACL_BasicFunctionality(t *testing.T) {
|
||||
// Setup test database
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// Use a temporary directory for the test database
|
||||
tmpDir := t.TempDir()
|
||||
db, err := database.New(ctx, cancel, tmpDir, "test.db")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create test database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Setup managed ACL
|
||||
cfg := &config.C{
|
||||
AuthRequired: false,
|
||||
Owners: []string{"owner1"},
|
||||
Admins: []string{"admin1"},
|
||||
}
|
||||
|
||||
managed := &Managed{
|
||||
Ctx: ctx,
|
||||
cfg: cfg,
|
||||
D: db,
|
||||
managedACL: database.NewManagedACL(db),
|
||||
owners: [][]byte{[]byte("owner1")},
|
||||
admins: [][]byte{[]byte("admin1")},
|
||||
}
|
||||
|
||||
// Test basic functionality
|
||||
t.Run("owner should get owner access", func(t *testing.T) {
|
||||
level := managed.GetAccessLevel([]byte("owner1"), "127.0.0.1")
|
||||
if level != "owner" {
|
||||
t.Errorf("GetAccessLevel() = %v, want owner", level)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("admin should get admin access", func(t *testing.T) {
|
||||
level := managed.GetAccessLevel([]byte("admin1"), "127.0.0.1")
|
||||
if level != "admin" {
|
||||
t.Errorf("GetAccessLevel() = %v, want admin", level)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("default user should get read access", func(t *testing.T) {
|
||||
level := managed.GetAccessLevel([]byte("user1"), "127.0.0.1")
|
||||
if level != "read" {
|
||||
t.Errorf("GetAccessLevel() = %v, want read", level)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("owner event should be allowed", func(t *testing.T) {
|
||||
ev := createMinimalTestEvent("owner1", 1)
|
||||
allowed, err := managed.CheckPolicy(ev)
|
||||
if err != nil {
|
||||
t.Fatalf("CheckPolicy() error = %v", err)
|
||||
}
|
||||
if !allowed {
|
||||
t.Errorf("CheckPolicy() = %v, want true", allowed)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("admin event should be allowed", func(t *testing.T) {
|
||||
ev := createMinimalTestEvent("admin1", 1)
|
||||
allowed, err := managed.CheckPolicy(ev)
|
||||
if err != nil {
|
||||
t.Fatalf("CheckPolicy() error = %v", err)
|
||||
}
|
||||
if !allowed {
|
||||
t.Errorf("CheckPolicy() = %v, want true", allowed)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("default event should be allowed", func(t *testing.T) {
|
||||
ev := createMinimalTestEvent("user1", 1)
|
||||
allowed, err := managed.CheckPolicy(ev)
|
||||
if err != nil {
|
||||
t.Fatalf("CheckPolicy() error = %v", err)
|
||||
}
|
||||
if !allowed {
|
||||
t.Errorf("CheckPolicy() = %v, want true", allowed)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func createMinimalTestEvent(pubkey string, kind uint16) *event.E {
|
||||
ev := event.New()
|
||||
ev.Pubkey = []byte(pubkey)
|
||||
ev.Kind = kind
|
||||
ev.CreatedAt = time.Now().Unix()
|
||||
ev.Content = []byte("test content")
|
||||
ev.Tags = nil
|
||||
ev.ID = ev.GetIDBytes()
|
||||
return ev
|
||||
}
|
||||
645
pkg/database/managed-acl.go
Normal file
645
pkg/database/managed-acl.go
Normal file
@@ -0,0 +1,645 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
)
|
||||
|
||||
// ManagedACLConfig represents the configuration for managed ACL mode
|
||||
type ManagedACLConfig struct {
|
||||
RelayName string `json:"relay_name"`
|
||||
RelayDescription string `json:"relay_description"`
|
||||
RelayIcon string `json:"relay_icon"`
|
||||
}
|
||||
|
||||
// BannedPubkey represents a banned public key entry
|
||||
type BannedPubkey struct {
|
||||
Pubkey string `json:"pubkey"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Added time.Time `json:"added"`
|
||||
}
|
||||
|
||||
// AllowedPubkey represents an allowed public key entry
|
||||
type AllowedPubkey struct {
|
||||
Pubkey string `json:"pubkey"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Added time.Time `json:"added"`
|
||||
}
|
||||
|
||||
// BannedEvent represents a banned event entry
|
||||
type BannedEvent struct {
|
||||
ID string `json:"id"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Added time.Time `json:"added"`
|
||||
}
|
||||
|
||||
// AllowedEvent represents an allowed event entry
|
||||
type AllowedEvent struct {
|
||||
ID string `json:"id"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Added time.Time `json:"added"`
|
||||
}
|
||||
|
||||
// BlockedIP represents a blocked IP address entry
|
||||
type BlockedIP struct {
|
||||
IP string `json:"ip"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Added time.Time `json:"added"`
|
||||
}
|
||||
|
||||
// AllowedKind represents an allowed event kind
|
||||
type AllowedKind struct {
|
||||
Kind int `json:"kind"`
|
||||
Added time.Time `json:"added"`
|
||||
}
|
||||
|
||||
// EventNeedingModeration represents an event that needs moderation
|
||||
type EventNeedingModeration struct {
|
||||
ID string `json:"id"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Added time.Time `json:"added"`
|
||||
}
|
||||
|
||||
// ManagedACL database operations
|
||||
type ManagedACL struct {
|
||||
*D
|
||||
}
|
||||
|
||||
// NewManagedACL creates a new ManagedACL instance
|
||||
func NewManagedACL(db *D) *ManagedACL {
|
||||
return &ManagedACL{D: db}
|
||||
}
|
||||
|
||||
// SaveBannedPubkey saves a banned pubkey to the database
|
||||
func (m *ManagedACL) SaveBannedPubkey(pubkey string, reason string) error {
|
||||
return m.Update(func(txn *badger.Txn) error {
|
||||
key := m.getBannedPubkeyKey(pubkey)
|
||||
banned := BannedPubkey{
|
||||
Pubkey: pubkey,
|
||||
Reason: reason,
|
||||
Added: time.Now(),
|
||||
}
|
||||
data, err := json.Marshal(banned)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return txn.Set(key, data)
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveBannedPubkey removes a banned pubkey from the database
|
||||
func (m *ManagedACL) RemoveBannedPubkey(pubkey string) error {
|
||||
return m.Update(func(txn *badger.Txn) error {
|
||||
key := m.getBannedPubkeyKey(pubkey)
|
||||
return txn.Delete(key)
|
||||
})
|
||||
}
|
||||
|
||||
// ListBannedPubkeys returns all banned pubkeys
|
||||
func (m *ManagedACL) ListBannedPubkeys() ([]BannedPubkey, error) {
|
||||
var banned []BannedPubkey
|
||||
return banned, m.View(func(txn *badger.Txn) error {
|
||||
prefix := m.getBannedPubkeyPrefix()
|
||||
it := txn.NewIterator(badger.IteratorOptions{Prefix: prefix})
|
||||
defer it.Close()
|
||||
|
||||
for it.Rewind(); it.Valid(); it.Next() {
|
||||
item := it.Item()
|
||||
val, err := item.ValueCopy(nil)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var bannedPubkey BannedPubkey
|
||||
if err := json.Unmarshal(val, &bannedPubkey); err != nil {
|
||||
continue
|
||||
}
|
||||
banned = append(banned, bannedPubkey)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// SaveAllowedPubkey saves an allowed pubkey to the database
|
||||
func (m *ManagedACL) SaveAllowedPubkey(pubkey string, reason string) error {
|
||||
return m.Update(func(txn *badger.Txn) error {
|
||||
key := m.getAllowedPubkeyKey(pubkey)
|
||||
allowed := AllowedPubkey{
|
||||
Pubkey: pubkey,
|
||||
Reason: reason,
|
||||
Added: time.Now(),
|
||||
}
|
||||
data, err := json.Marshal(allowed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return txn.Set(key, data)
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveAllowedPubkey removes an allowed pubkey from the database
|
||||
func (m *ManagedACL) RemoveAllowedPubkey(pubkey string) error {
|
||||
return m.Update(func(txn *badger.Txn) error {
|
||||
key := m.getAllowedPubkeyKey(pubkey)
|
||||
return txn.Delete(key)
|
||||
})
|
||||
}
|
||||
|
||||
// ListAllowedPubkeys returns all allowed pubkeys
|
||||
func (m *ManagedACL) ListAllowedPubkeys() ([]AllowedPubkey, error) {
|
||||
var allowed []AllowedPubkey
|
||||
return allowed, m.View(func(txn *badger.Txn) error {
|
||||
prefix := m.getAllowedPubkeyPrefix()
|
||||
it := txn.NewIterator(badger.IteratorOptions{Prefix: prefix})
|
||||
defer it.Close()
|
||||
|
||||
for it.Rewind(); it.Valid(); it.Next() {
|
||||
item := it.Item()
|
||||
val, err := item.ValueCopy(nil)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var allowedPubkey AllowedPubkey
|
||||
if err := json.Unmarshal(val, &allowedPubkey); err != nil {
|
||||
continue
|
||||
}
|
||||
allowed = append(allowed, allowedPubkey)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// SaveBannedEvent saves a banned event to the database
|
||||
func (m *ManagedACL) SaveBannedEvent(eventID string, reason string) error {
|
||||
return m.Update(func(txn *badger.Txn) error {
|
||||
key := m.getBannedEventKey(eventID)
|
||||
banned := BannedEvent{
|
||||
ID: eventID,
|
||||
Reason: reason,
|
||||
Added: time.Now(),
|
||||
}
|
||||
data, err := json.Marshal(banned)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return txn.Set(key, data)
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveBannedEvent removes a banned event from the database
|
||||
func (m *ManagedACL) RemoveBannedEvent(eventID string) error {
|
||||
return m.Update(func(txn *badger.Txn) error {
|
||||
key := m.getBannedEventKey(eventID)
|
||||
return txn.Delete(key)
|
||||
})
|
||||
}
|
||||
|
||||
// ListBannedEvents returns all banned events
|
||||
func (m *ManagedACL) ListBannedEvents() ([]BannedEvent, error) {
|
||||
var banned []BannedEvent
|
||||
return banned, m.View(func(txn *badger.Txn) error {
|
||||
prefix := m.getBannedEventPrefix()
|
||||
it := txn.NewIterator(badger.IteratorOptions{Prefix: prefix})
|
||||
defer it.Close()
|
||||
|
||||
for it.Rewind(); it.Valid(); it.Next() {
|
||||
item := it.Item()
|
||||
val, err := item.ValueCopy(nil)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var bannedEvent BannedEvent
|
||||
if err := json.Unmarshal(val, &bannedEvent); err != nil {
|
||||
continue
|
||||
}
|
||||
banned = append(banned, bannedEvent)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// SaveAllowedEvent saves an allowed event to the database
|
||||
func (m *ManagedACL) SaveAllowedEvent(eventID string, reason string) error {
|
||||
return m.Update(func(txn *badger.Txn) error {
|
||||
key := m.getAllowedEventKey(eventID)
|
||||
allowed := AllowedEvent{
|
||||
ID: eventID,
|
||||
Reason: reason,
|
||||
Added: time.Now(),
|
||||
}
|
||||
data, err := json.Marshal(allowed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return txn.Set(key, data)
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveAllowedEvent removes an allowed event from the database
|
||||
func (m *ManagedACL) RemoveAllowedEvent(eventID string) error {
|
||||
return m.Update(func(txn *badger.Txn) error {
|
||||
key := m.getAllowedEventKey(eventID)
|
||||
return txn.Delete(key)
|
||||
})
|
||||
}
|
||||
|
||||
// ListAllowedEvents returns all allowed events
|
||||
func (m *ManagedACL) ListAllowedEvents() ([]AllowedEvent, error) {
|
||||
var allowed []AllowedEvent
|
||||
return allowed, m.View(func(txn *badger.Txn) error {
|
||||
prefix := m.getAllowedEventPrefix()
|
||||
it := txn.NewIterator(badger.IteratorOptions{Prefix: prefix})
|
||||
defer it.Close()
|
||||
|
||||
for it.Rewind(); it.Valid(); it.Next() {
|
||||
item := it.Item()
|
||||
val, err := item.ValueCopy(nil)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var allowedEvent AllowedEvent
|
||||
if err := json.Unmarshal(val, &allowedEvent); err != nil {
|
||||
continue
|
||||
}
|
||||
allowed = append(allowed, allowedEvent)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// SaveBlockedIP saves a blocked IP to the database
|
||||
func (m *ManagedACL) SaveBlockedIP(ip string, reason string) error {
|
||||
return m.Update(func(txn *badger.Txn) error {
|
||||
key := m.getBlockedIPKey(ip)
|
||||
blocked := BlockedIP{
|
||||
IP: ip,
|
||||
Reason: reason,
|
||||
Added: time.Now(),
|
||||
}
|
||||
data, err := json.Marshal(blocked)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return txn.Set(key, data)
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveBlockedIP removes a blocked IP from the database
|
||||
func (m *ManagedACL) RemoveBlockedIP(ip string) error {
|
||||
return m.Update(func(txn *badger.Txn) error {
|
||||
key := m.getBlockedIPKey(ip)
|
||||
return txn.Delete(key)
|
||||
})
|
||||
}
|
||||
|
||||
// ListBlockedIPs returns all blocked IPs
|
||||
func (m *ManagedACL) ListBlockedIPs() ([]BlockedIP, error) {
|
||||
var blocked []BlockedIP
|
||||
return blocked, m.View(func(txn *badger.Txn) error {
|
||||
prefix := m.getBlockedIPPrefix()
|
||||
it := txn.NewIterator(badger.IteratorOptions{Prefix: prefix})
|
||||
defer it.Close()
|
||||
|
||||
for it.Rewind(); it.Valid(); it.Next() {
|
||||
item := it.Item()
|
||||
val, err := item.ValueCopy(nil)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var blockedIP BlockedIP
|
||||
if err := json.Unmarshal(val, &blockedIP); err != nil {
|
||||
continue
|
||||
}
|
||||
blocked = append(blocked, blockedIP)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// SaveAllowedKind saves an allowed kind to the database
|
||||
func (m *ManagedACL) SaveAllowedKind(kind int) error {
|
||||
return m.Update(func(txn *badger.Txn) error {
|
||||
key := m.getAllowedKindKey(kind)
|
||||
allowed := AllowedKind{
|
||||
Kind: kind,
|
||||
Added: time.Now(),
|
||||
}
|
||||
data, err := json.Marshal(allowed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return txn.Set(key, data)
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveAllowedKind removes an allowed kind from the database
|
||||
func (m *ManagedACL) RemoveAllowedKind(kind int) error {
|
||||
return m.Update(func(txn *badger.Txn) error {
|
||||
key := m.getAllowedKindKey(kind)
|
||||
return txn.Delete(key)
|
||||
})
|
||||
}
|
||||
|
||||
// ListAllowedKinds returns all allowed kinds
|
||||
func (m *ManagedACL) ListAllowedKinds() ([]int, error) {
|
||||
var kinds []int
|
||||
return kinds, m.View(func(txn *badger.Txn) error {
|
||||
prefix := m.getAllowedKindPrefix()
|
||||
it := txn.NewIterator(badger.IteratorOptions{Prefix: prefix})
|
||||
defer it.Close()
|
||||
|
||||
for it.Rewind(); it.Valid(); it.Next() {
|
||||
item := it.Item()
|
||||
val, err := item.ValueCopy(nil)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var allowedKind AllowedKind
|
||||
if err := json.Unmarshal(val, &allowedKind); err != nil {
|
||||
continue
|
||||
}
|
||||
kinds = append(kinds, allowedKind.Kind)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// SaveEventNeedingModeration saves an event that needs moderation
|
||||
func (m *ManagedACL) SaveEventNeedingModeration(eventID string, reason string) error {
|
||||
return m.Update(func(txn *badger.Txn) error {
|
||||
key := m.getEventNeedingModerationKey(eventID)
|
||||
event := EventNeedingModeration{
|
||||
ID: eventID,
|
||||
Reason: reason,
|
||||
Added: time.Now(),
|
||||
}
|
||||
data, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return txn.Set(key, data)
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveEventNeedingModeration removes an event from moderation queue
|
||||
func (m *ManagedACL) RemoveEventNeedingModeration(eventID string) error {
|
||||
return m.Update(func(txn *badger.Txn) error {
|
||||
key := m.getEventNeedingModerationKey(eventID)
|
||||
return txn.Delete(key)
|
||||
})
|
||||
}
|
||||
|
||||
// ListEventsNeedingModeration returns all events needing moderation
|
||||
func (m *ManagedACL) ListEventsNeedingModeration() ([]EventNeedingModeration, error) {
|
||||
var events []EventNeedingModeration
|
||||
return events, m.View(func(txn *badger.Txn) error {
|
||||
prefix := m.getEventNeedingModerationPrefix()
|
||||
it := txn.NewIterator(badger.IteratorOptions{Prefix: prefix})
|
||||
defer it.Close()
|
||||
|
||||
for it.Rewind(); it.Valid(); it.Next() {
|
||||
item := it.Item()
|
||||
val, err := item.ValueCopy(nil)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var event EventNeedingModeration
|
||||
if err := json.Unmarshal(val, &event); err != nil {
|
||||
continue
|
||||
}
|
||||
events = append(events, event)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// SaveRelayConfig saves relay configuration
|
||||
func (m *ManagedACL) SaveRelayConfig(config ManagedACLConfig) error {
|
||||
return m.Update(func(txn *badger.Txn) error {
|
||||
key := m.getRelayConfigKey()
|
||||
data, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return txn.Set(key, data)
|
||||
})
|
||||
}
|
||||
|
||||
// GetRelayConfig returns relay configuration
|
||||
func (m *ManagedACL) GetRelayConfig() (ManagedACLConfig, error) {
|
||||
var config ManagedACLConfig
|
||||
return config, m.View(func(txn *badger.Txn) error {
|
||||
key := m.getRelayConfigKey()
|
||||
item, err := txn.Get(key)
|
||||
if err != nil {
|
||||
if err == badger.ErrKeyNotFound {
|
||||
// Return default config
|
||||
config = ManagedACLConfig{
|
||||
RelayName: "Managed Relay",
|
||||
RelayDescription: "A managed Nostr relay",
|
||||
RelayIcon: "",
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
val, err := item.ValueCopy(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(val, &config)
|
||||
})
|
||||
}
|
||||
|
||||
// Check if a pubkey is banned
|
||||
func (m *ManagedACL) IsPubkeyBanned(pubkey string) (bool, error) {
|
||||
var banned bool
|
||||
return banned, m.View(func(txn *badger.Txn) error {
|
||||
key := m.getBannedPubkeyKey(pubkey)
|
||||
_, err := txn.Get(key)
|
||||
if err == badger.ErrKeyNotFound {
|
||||
banned = false
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
banned = true
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Check if a pubkey is explicitly allowed
|
||||
func (m *ManagedACL) IsPubkeyAllowed(pubkey string) (bool, error) {
|
||||
var allowed bool
|
||||
return allowed, m.View(func(txn *badger.Txn) error {
|
||||
key := m.getAllowedPubkeyKey(pubkey)
|
||||
_, err := txn.Get(key)
|
||||
if err == badger.ErrKeyNotFound {
|
||||
allowed = false
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allowed = true
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Check if an event is banned
|
||||
func (m *ManagedACL) IsEventBanned(eventID string) (bool, error) {
|
||||
var banned bool
|
||||
return banned, m.View(func(txn *badger.Txn) error {
|
||||
key := m.getBannedEventKey(eventID)
|
||||
_, err := txn.Get(key)
|
||||
if err == badger.ErrKeyNotFound {
|
||||
banned = false
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
banned = true
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Check if an event is explicitly allowed
|
||||
func (m *ManagedACL) IsEventAllowed(eventID string) (bool, error) {
|
||||
var allowed bool
|
||||
return allowed, m.View(func(txn *badger.Txn) error {
|
||||
key := m.getAllowedEventKey(eventID)
|
||||
_, err := txn.Get(key)
|
||||
if err == badger.ErrKeyNotFound {
|
||||
allowed = false
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allowed = true
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Check if an IP is blocked
|
||||
func (m *ManagedACL) IsIPBlocked(ip string) (bool, error) {
|
||||
var blocked bool
|
||||
return blocked, m.View(func(txn *badger.Txn) error {
|
||||
key := m.getBlockedIPKey(ip)
|
||||
_, err := txn.Get(key)
|
||||
if err == badger.ErrKeyNotFound {
|
||||
blocked = false
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
blocked = true
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Check if a kind is allowed
|
||||
func (m *ManagedACL) IsKindAllowed(kind int) (bool, error) {
|
||||
var allowed bool
|
||||
return allowed, m.View(func(txn *badger.Txn) error {
|
||||
key := m.getAllowedKindKey(kind)
|
||||
_, err := txn.Get(key)
|
||||
if err == badger.ErrKeyNotFound {
|
||||
allowed = false
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allowed = true
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Key generation methods
|
||||
func (m *ManagedACL) getBannedPubkeyKey(pubkey string) []byte {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteString("MANAGED_ACL_BANNED_PUBKEY_")
|
||||
buf.WriteString(pubkey)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (m *ManagedACL) getBannedPubkeyPrefix() []byte {
|
||||
return []byte("MANAGED_ACL_BANNED_PUBKEY_")
|
||||
}
|
||||
|
||||
func (m *ManagedACL) getAllowedPubkeyKey(pubkey string) []byte {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteString("MANAGED_ACL_ALLOWED_PUBKEY_")
|
||||
buf.WriteString(pubkey)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (m *ManagedACL) getAllowedPubkeyPrefix() []byte {
|
||||
return []byte("MANAGED_ACL_ALLOWED_PUBKEY_")
|
||||
}
|
||||
|
||||
func (m *ManagedACL) getBannedEventKey(eventID string) []byte {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteString("MANAGED_ACL_BANNED_EVENT_")
|
||||
buf.WriteString(eventID)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (m *ManagedACL) getBannedEventPrefix() []byte {
|
||||
return []byte("MANAGED_ACL_BANNED_EVENT_")
|
||||
}
|
||||
|
||||
func (m *ManagedACL) getAllowedEventKey(eventID string) []byte {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteString("MANAGED_ACL_ALLOWED_EVENT_")
|
||||
buf.WriteString(eventID)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (m *ManagedACL) getAllowedEventPrefix() []byte {
|
||||
return []byte("MANAGED_ACL_ALLOWED_EVENT_")
|
||||
}
|
||||
|
||||
func (m *ManagedACL) getBlockedIPKey(ip string) []byte {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteString("MANAGED_ACL_BLOCKED_IP_")
|
||||
buf.WriteString(ip)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (m *ManagedACL) getBlockedIPPrefix() []byte {
|
||||
return []byte("MANAGED_ACL_BLOCKED_IP_")
|
||||
}
|
||||
|
||||
func (m *ManagedACL) getAllowedKindKey(kind int) []byte {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteString("MANAGED_ACL_ALLOWED_KIND_")
|
||||
buf.WriteString(fmt.Sprintf("%d", kind))
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (m *ManagedACL) getAllowedKindPrefix() []byte {
|
||||
return []byte("MANAGED_ACL_ALLOWED_KIND_")
|
||||
}
|
||||
|
||||
func (m *ManagedACL) getEventNeedingModerationKey(eventID string) []byte {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteString("MANAGED_ACL_MODERATION_")
|
||||
buf.WriteString(eventID)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (m *ManagedACL) getEventNeedingModerationPrefix() []byte {
|
||||
return []byte("MANAGED_ACL_MODERATION_")
|
||||
}
|
||||
|
||||
func (m *ManagedACL) getRelayConfigKey() []byte {
|
||||
return []byte("MANAGED_ACL_RELAY_CONFIG")
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
v0.16.2
|
||||
v0.17.0
|
||||
Reference in New Issue
Block a user