From 5d21632ea70e1c7de7becb7ab6227b06b1535a83 Mon Sep 17 00:00:00 2001 From: bndw Date: Sat, 14 Feb 2026 10:02:52 -0800 Subject: feat: add separate read/write allowlists for granular access control - Split allowed_npubs into allowed_npubs_read and allowed_npubs_write - Write operations: Publish, Delete, Create, Update, Insert, Remove, Set, Put - Read operations: everything else (Query, Subscribe, Get, List, etc.) - Auth interceptor checks appropriate list based on method type - Enables common patterns: - Public relay: only some can write, everyone can read - Private relay: restricted read and write - Open relay: everyone can read and write - Updated config, docs, and comprehensive tests Use cases: "only some can write, everyone can read" --- internal/config/config.go | 52 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 15 deletions(-) (limited to 'internal/config/config.go') diff --git a/internal/config/config.go b/internal/config/config.go index 0566537..3e52272 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -39,11 +39,12 @@ type DatabaseConfig struct { // AuthConfig holds authentication configuration. type AuthConfig struct { - Enabled bool `yaml:"enabled"` - Required bool `yaml:"required"` - TimestampWindow int64 `yaml:"timestamp_window"` - AllowedNpubs []string `yaml:"allowed_npubs"` // npub format only (bech32) - normalized to hex internally - SkipMethods []string `yaml:"skip_methods"` + Enabled bool `yaml:"enabled"` + Required bool `yaml:"required"` + TimestampWindow int64 `yaml:"timestamp_window"` + AllowedNpubsRead []string `yaml:"allowed_npubs_read"` // npub format only (bech32) - normalized to hex internally + AllowedNpubsWrite []string `yaml:"allowed_npubs_write"` // npub format only (bech32) - normalized to hex internally + SkipMethods []string `yaml:"skip_methods"` } // RateLimitConfig holds rate limiting configuration. @@ -180,12 +181,31 @@ func Load(filename string) (*Config, error) { // Config only accepts npub format (human-readable), which is converted // to hex format (computer-readable) for internal use. func normalizeNpubs(cfg *Config) error { - if len(cfg.Auth.AllowedNpubs) == 0 { - return nil + var err error + + // Normalize read allowlist + cfg.Auth.AllowedNpubsRead, err = normalizeNpubList(cfg.Auth.AllowedNpubsRead) + if err != nil { + return fmt.Errorf("allowed_npubs_read: %w", err) + } + + // Normalize write allowlist + cfg.Auth.AllowedNpubsWrite, err = normalizeNpubList(cfg.Auth.AllowedNpubsWrite) + if err != nil { + return fmt.Errorf("allowed_npubs_write: %w", err) + } + + return nil +} + +// normalizeNpubList converts a list of npubs to hex pubkeys. +func normalizeNpubList(npubs []string) ([]string, error) { + if len(npubs) == 0 { + return nil, nil } - normalized := make([]string, 0, len(cfg.Auth.AllowedNpubs)) - for _, npub := range cfg.Auth.AllowedNpubs { + normalized := make([]string, 0, len(npubs)) + for _, npub := range npubs { // Skip empty strings npub = strings.TrimSpace(npub) if npub == "" { @@ -194,21 +214,20 @@ func normalizeNpubs(cfg *Config) error { // Validate npub format if !strings.HasPrefix(npub, "npub1") { - return fmt.Errorf("invalid npub format %q: must start with 'npub1'", npub) + return nil, fmt.Errorf("invalid npub format %q: must start with 'npub1'", npub) } // Parse npub to get hex pubkey key, err := nostr.ParsePublicKey(npub) if err != nil { - return fmt.Errorf("invalid npub %q: %w", npub, err) + return nil, fmt.Errorf("invalid npub %q: %w", npub, err) } // Get the hex representation for internal use normalized = append(normalized, key.Public()) } - cfg.Auth.AllowedNpubs = normalized - return nil + return normalized, nil } // Validate validates the configuration. @@ -292,8 +311,11 @@ func applyEnvOverrides(cfg *Config) { cfg.Auth.TimestampWindow = n } } - if val := os.Getenv("MUXSTR_AUTH_ALLOWED_NPUBS"); val != "" { - cfg.Auth.AllowedNpubs = strings.Split(val, ",") + if val := os.Getenv("MUXSTR_AUTH_ALLOWED_NPUBS_READ"); val != "" { + cfg.Auth.AllowedNpubsRead = strings.Split(val, ",") + } + if val := os.Getenv("MUXSTR_AUTH_ALLOWED_NPUBS_WRITE"); val != "" { + cfg.Auth.AllowedNpubsWrite = strings.Split(val, ",") } // Rate limit -- cgit v1.2.3