diff options
| author | bndw <ben@bdw.to> | 2026-02-14 10:02:52 -0800 |
|---|---|---|
| committer | bndw <ben@bdw.to> | 2026-02-14 10:02:52 -0800 |
| commit | 5d21632ea70e1c7de7becb7ab6227b06b1535a83 (patch) | |
| tree | e63bcbe8cdf7dc888ca0e3476ad529690a0a44a8 /internal/config/config.go | |
| parent | d30459513ec44ab298fafd1bfe0edc08d6ab62e4 (diff) | |
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"
Diffstat (limited to 'internal/config/config.go')
| -rw-r--r-- | internal/config/config.go | 52 |
1 files changed, 37 insertions, 15 deletions
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 { | |||
| 39 | 39 | ||
| 40 | // AuthConfig holds authentication configuration. | 40 | // AuthConfig holds authentication configuration. |
| 41 | type AuthConfig struct { | 41 | type AuthConfig struct { |
| 42 | Enabled bool `yaml:"enabled"` | 42 | Enabled bool `yaml:"enabled"` |
| 43 | Required bool `yaml:"required"` | 43 | Required bool `yaml:"required"` |
| 44 | TimestampWindow int64 `yaml:"timestamp_window"` | 44 | TimestampWindow int64 `yaml:"timestamp_window"` |
| 45 | AllowedNpubs []string `yaml:"allowed_npubs"` // npub format only (bech32) - normalized to hex internally | 45 | AllowedNpubsRead []string `yaml:"allowed_npubs_read"` // npub format only (bech32) - normalized to hex internally |
| 46 | SkipMethods []string `yaml:"skip_methods"` | 46 | AllowedNpubsWrite []string `yaml:"allowed_npubs_write"` // npub format only (bech32) - normalized to hex internally |
| 47 | SkipMethods []string `yaml:"skip_methods"` | ||
| 47 | } | 48 | } |
| 48 | 49 | ||
| 49 | // RateLimitConfig holds rate limiting configuration. | 50 | // RateLimitConfig holds rate limiting configuration. |
| @@ -180,12 +181,31 @@ func Load(filename string) (*Config, error) { | |||
| 180 | // Config only accepts npub format (human-readable), which is converted | 181 | // Config only accepts npub format (human-readable), which is converted |
| 181 | // to hex format (computer-readable) for internal use. | 182 | // to hex format (computer-readable) for internal use. |
| 182 | func normalizeNpubs(cfg *Config) error { | 183 | func normalizeNpubs(cfg *Config) error { |
| 183 | if len(cfg.Auth.AllowedNpubs) == 0 { | 184 | var err error |
| 184 | return nil | 185 | |
| 186 | // Normalize read allowlist | ||
| 187 | cfg.Auth.AllowedNpubsRead, err = normalizeNpubList(cfg.Auth.AllowedNpubsRead) | ||
| 188 | if err != nil { | ||
| 189 | return fmt.Errorf("allowed_npubs_read: %w", err) | ||
| 190 | } | ||
| 191 | |||
| 192 | // Normalize write allowlist | ||
| 193 | cfg.Auth.AllowedNpubsWrite, err = normalizeNpubList(cfg.Auth.AllowedNpubsWrite) | ||
| 194 | if err != nil { | ||
| 195 | return fmt.Errorf("allowed_npubs_write: %w", err) | ||
| 196 | } | ||
| 197 | |||
| 198 | return nil | ||
| 199 | } | ||
| 200 | |||
| 201 | // normalizeNpubList converts a list of npubs to hex pubkeys. | ||
| 202 | func normalizeNpubList(npubs []string) ([]string, error) { | ||
| 203 | if len(npubs) == 0 { | ||
| 204 | return nil, nil | ||
| 185 | } | 205 | } |
| 186 | 206 | ||
| 187 | normalized := make([]string, 0, len(cfg.Auth.AllowedNpubs)) | 207 | normalized := make([]string, 0, len(npubs)) |
| 188 | for _, npub := range cfg.Auth.AllowedNpubs { | 208 | for _, npub := range npubs { |
| 189 | // Skip empty strings | 209 | // Skip empty strings |
| 190 | npub = strings.TrimSpace(npub) | 210 | npub = strings.TrimSpace(npub) |
| 191 | if npub == "" { | 211 | if npub == "" { |
| @@ -194,21 +214,20 @@ func normalizeNpubs(cfg *Config) error { | |||
| 194 | 214 | ||
| 195 | // Validate npub format | 215 | // Validate npub format |
| 196 | if !strings.HasPrefix(npub, "npub1") { | 216 | if !strings.HasPrefix(npub, "npub1") { |
| 197 | return fmt.Errorf("invalid npub format %q: must start with 'npub1'", npub) | 217 | return nil, fmt.Errorf("invalid npub format %q: must start with 'npub1'", npub) |
| 198 | } | 218 | } |
| 199 | 219 | ||
| 200 | // Parse npub to get hex pubkey | 220 | // Parse npub to get hex pubkey |
| 201 | key, err := nostr.ParsePublicKey(npub) | 221 | key, err := nostr.ParsePublicKey(npub) |
| 202 | if err != nil { | 222 | if err != nil { |
| 203 | return fmt.Errorf("invalid npub %q: %w", npub, err) | 223 | return nil, fmt.Errorf("invalid npub %q: %w", npub, err) |
| 204 | } | 224 | } |
| 205 | 225 | ||
| 206 | // Get the hex representation for internal use | 226 | // Get the hex representation for internal use |
| 207 | normalized = append(normalized, key.Public()) | 227 | normalized = append(normalized, key.Public()) |
| 208 | } | 228 | } |
| 209 | 229 | ||
| 210 | cfg.Auth.AllowedNpubs = normalized | 230 | return normalized, nil |
| 211 | return nil | ||
| 212 | } | 231 | } |
| 213 | 232 | ||
| 214 | // Validate validates the configuration. | 233 | // Validate validates the configuration. |
| @@ -292,8 +311,11 @@ func applyEnvOverrides(cfg *Config) { | |||
| 292 | cfg.Auth.TimestampWindow = n | 311 | cfg.Auth.TimestampWindow = n |
| 293 | } | 312 | } |
| 294 | } | 313 | } |
| 295 | if val := os.Getenv("MUXSTR_AUTH_ALLOWED_NPUBS"); val != "" { | 314 | if val := os.Getenv("MUXSTR_AUTH_ALLOWED_NPUBS_READ"); val != "" { |
| 296 | cfg.Auth.AllowedNpubs = strings.Split(val, ",") | 315 | cfg.Auth.AllowedNpubsRead = strings.Split(val, ",") |
| 316 | } | ||
| 317 | if val := os.Getenv("MUXSTR_AUTH_ALLOWED_NPUBS_WRITE"); val != "" { | ||
| 318 | cfg.Auth.AllowedNpubsWrite = strings.Split(val, ",") | ||
| 297 | } | 319 | } |
| 298 | 320 | ||
| 299 | // Rate limit | 321 | // Rate limit |
