diff --git a/zooid/blossom.go b/zooid/blossom.go index 77977e1..501e2d4 100644 --- a/zooid/blossom.go +++ b/zooid/blossom.go @@ -57,7 +57,7 @@ func (bl *BlossomStore) Enable(instance *Instance) { return true, "file too large", 413 } - if auth == nil || !instance.Management.HasAccess(auth.PubKey) { + if auth == nil || !instance.Management.IsMember(auth.PubKey) { return true, "unauthorized", 403 } @@ -65,7 +65,7 @@ func (bl *BlossomStore) Enable(instance *Instance) { } backend.RejectGet = func(ctx context.Context, auth *nostr.Event, sha256 string, ext string) (bool, string, int) { - if auth == nil || !instance.Management.HasAccess(auth.PubKey) { + if auth == nil || !instance.Management.IsMember(auth.PubKey) { return true, "unauthorized", 403 } @@ -73,7 +73,7 @@ func (bl *BlossomStore) Enable(instance *Instance) { } backend.RejectList = func(ctx context.Context, auth *nostr.Event, pubkey nostr.PubKey) (bool, string, int) { - if auth == nil || !instance.Management.HasAccess(auth.PubKey) { + if auth == nil || !instance.Management.IsMember(auth.PubKey) { return true, "unauthorized", 403 } @@ -81,7 +81,7 @@ func (bl *BlossomStore) Enable(instance *Instance) { } backend.RejectDelete = func(ctx context.Context, auth *nostr.Event, sha256 string, ext string) (bool, string, int) { - if auth == nil || !instance.Management.HasAccess(auth.PubKey) { + if auth == nil || !instance.Management.IsMember(auth.PubKey) { return true, "unauthorized", 403 } diff --git a/zooid/groups.go b/zooid/groups.go index 1a40a36..65b1012 100644 --- a/zooid/groups.go +++ b/zooid/groups.go @@ -228,3 +228,9 @@ func (g *GroupStore) HasAccess(h string, pubkey nostr.PubKey) bool { return false } + +// Middleware + +func (g *GroupStore) Enable(instance *Instance) { + instance.Relay.Info.SupportedNIPs = append(instance.Relay.Info.SupportedNIPs, 29) +} diff --git a/zooid/instance.go b/zooid/instance.go index 1db9742..43dcf33 100644 --- a/zooid/instance.go +++ b/zooid/instance.go @@ -66,27 +66,17 @@ func MakeInstance(filename string) (*Instance, error) { // NIP 11 info + owner := config.GetOwner() + instance.Relay.Negentropy = true instance.Relay.Info.Name = config.Info.Name instance.Relay.Info.Icon = config.Info.Icon + instance.Relay.Info.PubKey = &owner instance.Relay.Info.Description = config.Info.Description // instance.Relay.Info.Self = nostr.GetPublicKey(secret) instance.Relay.Info.Software = "https://github.com/coracle-social/zooid" instance.Relay.Info.Version = "v0.1.0" - if config.Info.Pubkey != "" { - pubkey, err := nostr.PubKeyFromHex(config.Info.Pubkey) - if err != nil { - return nil, err - } - - instance.Relay.Info.PubKey = &pubkey - } - - if instance.Config.Groups.Enabled { - instance.Relay.Info.SupportedNIPs = append(instance.Relay.Info.SupportedNIPs, 29) - } - // Handlers instance.Relay.OnConnect = instance.OnConnect @@ -113,12 +103,14 @@ func MakeInstance(filename string) (*Instance, error) { router.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) - // Initialize stuff + // Initialize the database if err := instance.Events.Init(); err != nil { log.Fatal("Failed to initialize event store: ", err) } + // Enable extra functionality + if config.Blossom.Enabled { instance.Blossom.Enable(instance) } @@ -127,6 +119,23 @@ func MakeInstance(filename string) (*Instance, error) { instance.Management.Enable(instance) } + if config.Groups.Enabled { + instance.Groups.Enable(instance) + } + + // Update managed membership/admin lists + + instance.Management.AllowPubkey(config.GetSelf()) + instance.Management.AllowPubkey(config.GetOwner()) + + for _, role := range config.Roles { + for _, hex := range role.Pubkeys { + if pubkey, err := nostr.PubKeyFromHex(hex); err != nil { + instance.Management.AllowPubkey(pubkey) + } + } + } + return instance, nil } @@ -161,7 +170,7 @@ func (instance *Instance) AllowRecipientEvent(event nostr.Event) bool { if recipientTag != nil { pubkey, err := nostr.PubKeyFromHex(recipientTag[1]) - if err == nil && instance.Management.HasAccess(pubkey) { + if err == nil && instance.Management.IsMember(pubkey) { return true } } @@ -258,7 +267,7 @@ func (instance *Instance) OnRequest(ctx context.Context, filter nostr.Filter) (r return true, "auth-required: authentication is required for access" } - if !instance.Management.HasAccess(pubkey) { + if !instance.Management.IsMember(pubkey) { return true, "restricted: you are not a member of this relay" } @@ -335,7 +344,7 @@ func (instance *Instance) QueryStored(ctx context.Context, filter nostr.Filter) continue } - if !instance.Groups.HasAccess(h, pubkey) { + if !instance.Groups.IsMember(h, pubkey) { continue } } @@ -371,7 +380,7 @@ func (instance *Instance) OnEvent(ctx context.Context, event nostr.Event) (rejec return instance.Management.ValidateJoinRequest(event) } - if !instance.Management.HasAccess(pubkey) { + if !instance.Management.IsMember(pubkey) { return true, "restricted: you are not a member of this relay" } diff --git a/zooid/kv.go b/zooid/kv.go new file mode 100644 index 0000000..8aa79ad --- /dev/null +++ b/zooid/kv.go @@ -0,0 +1,95 @@ +package zooid + +import ( + "fmt" + "github.com/Masterminds/squirrel" + "log" + "sync" +) + +var ( + kv *KeyValueStore + kvOnce sync.Once +) + +type KeyValueStore struct{} + +func GetKeyValueStore() *KeyValueStore { + dbOnce.Do(func() { + kv = &KeyValueStore{} + kv.Migrate() + }) + + return kv +} + +func (kv *KeyValueStore) Migrate() { + sql := ` + CREATE TABLE IF NOT EXISTS kv ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL + ); + + CREATE INDEX IF NOT EXISTS idx_kv_key ON kv(key); + ` + + if _, err := GetDb().Exec(sql); err != nil { + log.Fatal("failed to migrate database: %w", err) + } +} + +func (kv *KeyValueStore) Get(key string) (string, error) { + rows, err := squirrel.Select("value"). + From("kv"). + Where(squirrel.Eq{"key": key}). + RunWith(GetDb()). + Query() + + if err != nil { + return "", err + } + + defer rows.Close() + + for rows.Next() { + var value string + + err := rows.Scan(&value) + if err != nil { + return "", err + } + + return value, nil + } + + return "", fmt.Errorf("%s not found", key) +} + +func (kv *KeyValueStore) Set(key string, value string) error { + _, err := squirrel.Insert("kv"). + Columns("key", "value"). + Values(key, value). + Suffix("ON CONFLICT(key) DO UPDATE SET value = excluded.value"). + RunWith(GetDb()). + Exec() + + return err +} + +// Namespaced kv + +type KV struct { + Name string +} + +func (kv *KV) Key(key string) string { + return fmt.Sprintf("%s:%s", kv.Name, key) +} + +func (kv *KV) Get(key string) (string, error) { + return GetKeyValueStore().Get(kv.Key(key)) +} + +func (kv *KV) Set(key string, value string) error { + return GetKeyValueStore().Set(kv.Key(key), value) +} diff --git a/zooid/management.go b/zooid/management.go index 193ad40..1b7d73e 100644 --- a/zooid/management.go +++ b/zooid/management.go @@ -284,10 +284,6 @@ func (m *ManagementStore) GetAllowedPubkeyItems() []nip86.PubKeyReason { } func (m *ManagementStore) AllowPubkey(pubkey nostr.PubKey) error { - if m.HasAccess(pubkey) { - return nil - } - if err := m.AddMember(pubkey); err != nil { return err } @@ -299,18 +295,6 @@ func (m *ManagementStore) AllowPubkey(pubkey nostr.PubKey) error { return nil } -func (m *ManagementStore) HasAccess(pubkey nostr.PubKey) bool { - if m.IsAdmin(pubkey) { - return true - } - - for range m.Config.GetAssignedRoles(pubkey) { - return true - } - - return m.IsMember(pubkey) -} - // Joining func (m *ManagementStore) ValidateJoinRequest(event nostr.Event) (reject bool, err string) { @@ -347,10 +331,6 @@ func (m *ManagementStore) Enable(instance *Instance) { return true, "blocked: please authenticate in order to manage this relay" } - if !m.HasAccess(pubkey) { - return true, "blocked: you are not a member of this relay" - } - if !m.Config.CanManage(pubkey) { return true, "blocked: only relay admins can manage this relay." }