From 702fa6c37b9f74e75404a0ea8e6f9023841143de Mon Sep 17 00:00:00 2001 From: bndw Date: Sat, 14 Feb 2026 10:17:54 -0800 Subject: refactor: remove frivolous comments from auth and config Removed ~100 lines of obvious comments that just repeated what the code does. Kept only comments that add clarity or valuable detail. --- internal/auth/interceptor.go | 78 ++++++-------------------------------------- internal/config/config.go | 53 ++++-------------------------- 2 files changed, 16 insertions(+), 115 deletions(-) diff --git a/internal/auth/interceptor.go b/internal/auth/interceptor.go index d394102..42c2688 100644 --- a/internal/auth/interceptor.go +++ b/internal/auth/interceptor.go @@ -11,52 +11,28 @@ import ( "google.golang.org/grpc/status" ) -// contextKey is a custom type for context keys to avoid collisions. type contextKey string const ( - // pubkeyContextKey is the key for storing the authenticated pubkey in context. pubkeyContextKey contextKey = "nostr-pubkey" ) -// InterceptorOptions configures the authentication interceptor behavior. type InterceptorOptions struct { - // Read configures authentication for read operations (Subscribe, QueryEvents, etc.) - Read OperationAuthConfig - - // Write configures authentication for write operations (PublishEvent, PublishBatch) - Write OperationAuthConfig - - // TimestampWindow is the maximum age of auth events in seconds. - // Events older than this are rejected to prevent replay attacks. - // Default: 60 seconds + Read OperationAuthConfig + Write OperationAuthConfig TimestampWindow int64 - - // ValidatePayload checks the payload hash tag if present. - // Default: false ValidatePayload bool - - // SkipMethods is a list of gRPC methods that bypass authentication. - // Useful for public endpoints like health checks or relay info. - // Example: []string{"/grpc.health.v1.Health/Check"} - // Default: nil (authenticate all methods) - SkipMethods []string + SkipMethods []string } -// OperationAuthConfig configures auth for a specific operation type. +// OperationAuthConfig configures auth for read or write operations. +// Three states: disabled (allow all), enabled with empty list (require auth), +// enabled with npubs (whitelist only). Npubs normalized to hex at load time. type OperationAuthConfig struct { - // Enabled determines if auth is required. - // false = no auth, allow all - // true = auth required - Enabled bool - - // AllowedNpubs is an optional whitelist (hex format, normalized from npub at config load). - // If Enabled=true && AllowedNpubs=[]: any valid signature accepted - // If Enabled=true && AllowedNpubs=[...]: only whitelisted npubs accepted + Enabled bool AllowedNpubs []string } -// DefaultInterceptorOptions returns the default configuration. func DefaultInterceptorOptions() *InterceptorOptions { return &InterceptorOptions{ Read: OperationAuthConfig{ @@ -73,19 +49,16 @@ func DefaultInterceptorOptions() *InterceptorOptions { } } -// NostrUnaryInterceptor creates a gRPC unary interceptor for NIP-98 authentication. func NostrUnaryInterceptor(opts *InterceptorOptions) grpc.UnaryServerInterceptor { if opts == nil { opts = DefaultInterceptorOptions() } return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - // Check if this method should skip auth if shouldSkipAuth(info.FullMethod, opts.SkipMethods) { return handler(ctx, req) } - // Check if auth is required for this operation type var authRequired bool if isWriteMethod(info.FullMethod) { authRequired = opts.Write.Enabled @@ -93,37 +66,31 @@ func NostrUnaryInterceptor(opts *InterceptorOptions) grpc.UnaryServerInterceptor authRequired = opts.Read.Enabled } - // If auth not required, skip validation if !authRequired { return handler(ctx, req) } - // Extract and validate auth pubkey, err := validateAuthFromContext(ctx, info.FullMethod, opts) if err != nil { return nil, status.Error(codes.Unauthenticated, err.Error()) } - // Add pubkey to context for handlers ctx = context.WithValue(ctx, pubkeyContextKey, pubkey) return handler(ctx, req) } } -// NostrStreamInterceptor creates a gRPC stream interceptor for NIP-98 authentication. func NostrStreamInterceptor(opts *InterceptorOptions) grpc.StreamServerInterceptor { if opts == nil { opts = DefaultInterceptorOptions() } return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { - // Check if this method should skip auth if shouldSkipAuth(info.FullMethod, opts.SkipMethods) { return handler(srv, ss) } - // Check if auth is required for this operation type var authRequired bool if isWriteMethod(info.FullMethod) { authRequired = opts.Write.Enabled @@ -131,19 +98,16 @@ func NostrStreamInterceptor(opts *InterceptorOptions) grpc.StreamServerIntercept authRequired = opts.Read.Enabled } - // If auth not required, skip validation if !authRequired { return handler(srv, ss) } - // Extract and validate auth ctx := ss.Context() pubkey, err := validateAuthFromContext(ctx, info.FullMethod, opts) if err != nil { return status.Error(codes.Unauthenticated, err.Error()) } - // Wrap stream with authenticated context wrappedStream := &authenticatedStream{ ServerStream: ss, ctx: context.WithValue(ctx, pubkeyContextKey, pubkey), @@ -153,8 +117,7 @@ func NostrStreamInterceptor(opts *InterceptorOptions) grpc.StreamServerIntercept } } -// authenticatedStream wraps a ServerStream with an authenticated context. -type authenticatedStream struct { +type authenticatedStream struct{ grpc.ServerStream ctx context.Context } @@ -163,45 +126,34 @@ func (s *authenticatedStream) Context() context.Context { return s.ctx } -// validateAuthFromContext extracts and validates the NIP-98 auth event from the context. func validateAuthFromContext(ctx context.Context, method string, opts *InterceptorOptions) (string, error) { - // Extract metadata from context md, ok := metadata.FromIncomingContext(ctx) if !ok { return "", fmt.Errorf("missing metadata") } - // Get authorization header authHeaders := md.Get("authorization") if len(authHeaders) == 0 { return "", fmt.Errorf("missing authorization header") } - authHeader := authHeaders[0] - - // Parse the auth event - event, err := ParseAuthHeader(authHeader) + event, err := ParseAuthHeader(authHeaders[0]) if err != nil { return "", fmt.Errorf("invalid auth header: %w", err) } - // Validate the event validationOpts := ValidationOptions{ TimestampWindow: opts.TimestampWindow, ValidatePayload: opts.ValidatePayload, - ExpectedMethod: "POST", // gRPC always uses POST - // Note: We don't validate URI here because the full URI isn't easily - // available in the interceptor context. The method name is validated instead. + ExpectedMethod: "POST", } if err := ValidateAuthEvent(event, validationOpts); err != nil { return "", fmt.Errorf("invalid auth event: %w", err) } - // Extract pubkey pubkey := ExtractPubkey(event) - // Get the operation config based on method type var opConfig OperationAuthConfig if isWriteMethod(method) { opConfig = opts.Write @@ -209,7 +161,6 @@ func validateAuthFromContext(ctx context.Context, method string, opts *Intercept opConfig = opts.Read } - // Check whitelist if configured if len(opConfig.AllowedNpubs) > 0 { if !contains(opConfig.AllowedNpubs, pubkey) { if isWriteMethod(method) { @@ -219,18 +170,13 @@ func validateAuthFromContext(ctx context.Context, method string, opts *Intercept } } - // No whitelist or pubkey in whitelist - allow return pubkey, nil } -// isWriteMethod determines if a gRPC method is a write operation. -// Write: PublishEvent, PublishBatch -// Read: Subscribe, Unsubscribe, QueryEvents, CountEvents func isWriteMethod(method string) bool { return strings.Contains(method, "/PublishEvent") || strings.Contains(method, "/PublishBatch") } -// shouldSkipAuth checks if a method should bypass authentication. func shouldSkipAuth(method string, skipMethods []string) bool { for _, skip := range skipMethods { if skip == method { @@ -240,7 +186,6 @@ func shouldSkipAuth(method string, skipMethods []string) bool { return false } -// contains checks if a slice contains a string. func contains(slice []string, item string) bool { for _, s := range slice { if s == item { @@ -250,14 +195,11 @@ func contains(slice []string, item string) bool { return false } -// PubkeyFromContext retrieves the authenticated pubkey from the context. -// Returns the pubkey and true if authentication was successful, or empty string and false otherwise. func PubkeyFromContext(ctx context.Context) (string, bool) { pubkey, ok := ctx.Value(pubkeyContextKey).(string) return pubkey, ok } -// RequireAuth is a helper that extracts the pubkey and returns an error if not authenticated. func RequireAuth(ctx context.Context) (string, error) { pubkey, ok := PubkeyFromContext(ctx) if !ok || pubkey == "" { diff --git a/internal/config/config.go b/internal/config/config.go index 294510d..fc392b4 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -10,7 +10,6 @@ import ( "gopkg.in/yaml.v3" ) -// Config holds all configuration for the relay. type Config struct { Server ServerConfig `yaml:"server"` Database DatabaseConfig `yaml:"database"` @@ -21,7 +20,6 @@ type Config struct { Storage StorageConfig `yaml:"storage"` } -// ServerConfig holds server configuration. type ServerConfig struct { GrpcAddr string `yaml:"grpc_addr"` HttpAddr string `yaml:"http_addr"` @@ -30,14 +28,10 @@ type ServerConfig struct { WriteTimeout time.Duration `yaml:"write_timeout"` } -// DatabaseConfig holds database configuration. type DatabaseConfig struct { Path string `yaml:"path"` - // Note: SQLite connection pooling is handled internally in the storage layer. - // SQLite works best with a single connection due to its single-writer architecture. } -// AuthConfig holds authentication configuration. type AuthConfig struct { Read AuthOperationConfig `yaml:"read"` Write AuthOperationConfig `yaml:"write"` @@ -45,16 +39,14 @@ type AuthConfig struct { SkipMethods []string `yaml:"skip_methods"` } -// AuthOperationConfig configures auth for a specific operation type (read or write). +// AuthOperationConfig configures auth for read or write operations. +// Three states: disabled (allow all), enabled with empty list (require auth), +// enabled with npubs (whitelist only). Npubs normalized to hex at load time. type AuthOperationConfig struct { - Enabled bool `yaml:"enabled"` // false = no auth required, true = auth required - AllowedNpubs []string `yaml:"allowed_npubs"` // npub format only - normalized to hex internally - // If enabled=false: no auth, allow all - // If enabled=true && allowed_npubs=[]: auth required, any valid signature accepted - // If enabled=true && allowed_npubs=[...]: auth required, only whitelisted npubs + Enabled bool `yaml:"enabled"` + AllowedNpubs []string `yaml:"allowed_npubs"` } -// RateLimitConfig holds rate limiting configuration. type RateLimitConfig struct { Enabled bool `yaml:"enabled"` DefaultRPS float64 `yaml:"default_rps"` @@ -69,20 +61,17 @@ type RateLimitConfig struct { MaxIdleTime time.Duration `yaml:"max_idle_time"` } -// MethodLimit defines rate limits for a specific method. type MethodLimit struct { RPS float64 `yaml:"rps"` Burst int `yaml:"burst"` } -// UserLimit defines rate limits for a specific user. type UserLimit struct { RPS float64 `yaml:"rps"` Burst int `yaml:"burst"` Methods map[string]MethodLimit `yaml:"methods"` } -// MetricsConfig holds metrics configuration. type MetricsConfig struct { Enabled bool `yaml:"enabled"` Addr string `yaml:"addr"` @@ -91,14 +80,12 @@ type MetricsConfig struct { Subsystem string `yaml:"subsystem"` } -// LoggingConfig holds logging configuration. type LoggingConfig struct { Level string `yaml:"level"` Format string `yaml:"format"` Output string `yaml:"output"` } -// StorageConfig holds storage configuration. type StorageConfig struct { AutoCompact bool `yaml:"auto_compact"` CompactInterval time.Duration `yaml:"compact_interval"` @@ -157,12 +144,9 @@ func Default() *Config { } } -// Load loads configuration from a YAML file and applies environment variable overrides. func Load(filename string) (*Config, error) { - // Start with defaults cfg := Default() - // Read file if provided if filename != "" { data, err := os.ReadFile(filename) if err != nil { @@ -174,15 +158,12 @@ func Load(filename string) (*Config, error) { } } - // Apply environment variable overrides applyEnvOverrides(cfg) - // Normalize npubs to hex pubkeys if err := normalizeNpubs(cfg); err != nil { return nil, fmt.Errorf("failed to normalize npubs: %w", err) } - // Validate if err := cfg.Validate(); err != nil { return nil, fmt.Errorf("invalid configuration: %w", err) } @@ -190,19 +171,15 @@ func Load(filename string) (*Config, error) { return cfg, nil } -// normalizeNpubs converts all npub (bech32) pubkeys to hex format. -// Config only accepts npub format (human-readable), which is converted -// to hex format (computer-readable) for internal use. +// normalizeNpubs converts npub (bech32) to hex format for internal use. func normalizeNpubs(cfg *Config) error { var err error - // Normalize read allowlist cfg.Auth.Read.AllowedNpubs, err = normalizeNpubList(cfg.Auth.Read.AllowedNpubs) if err != nil { return fmt.Errorf("auth.read.allowed_npubs: %w", err) } - // Normalize write allowlist cfg.Auth.Write.AllowedNpubs, err = normalizeNpubList(cfg.Auth.Write.AllowedNpubs) if err != nil { return fmt.Errorf("auth.write.allowed_npubs: %w", err) @@ -211,7 +188,6 @@ func normalizeNpubs(cfg *Config) error { return nil } -// normalizeNpubList converts a list of npubs to hex pubkeys. func normalizeNpubList(npubs []string) ([]string, error) { if len(npubs) == 0 { return nil, nil @@ -219,33 +195,27 @@ func normalizeNpubList(npubs []string) ([]string, error) { normalized := make([]string, 0, len(npubs)) for _, npub := range npubs { - // Skip empty strings npub = strings.TrimSpace(npub) if npub == "" { continue } - // Validate npub format if !strings.HasPrefix(npub, "npub1") { 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 nil, fmt.Errorf("invalid npub %q: %w", npub, err) } - // Get the hex representation for internal use normalized = append(normalized, key.Public()) } return normalized, nil } -// Validate validates the configuration. func (c *Config) Validate() error { - // Validate server addresses if c.Server.GrpcAddr == "" { return fmt.Errorf("server.grpc_addr is required") } @@ -253,12 +223,10 @@ func (c *Config) Validate() error { return fmt.Errorf("server.http_addr is required") } - // Validate database path if c.Database.Path == "" { return fmt.Errorf("database.path is required") } - // Validate metrics config if enabled if c.Metrics.Enabled { if c.Metrics.Addr == "" { return fmt.Errorf("metrics.addr is required when metrics enabled") @@ -268,7 +236,6 @@ func (c *Config) Validate() error { } } - // Validate logging validLevels := map[string]bool{"debug": true, "info": true, "warn": true, "error": true} if !validLevels[c.Logging.Level] { return fmt.Errorf("invalid logging.level: %s (must be debug, info, warn, or error)", c.Logging.Level) @@ -282,10 +249,7 @@ func (c *Config) Validate() error { return nil } -// applyEnvOverrides applies environment variable overrides to the configuration. -// Environment variables follow the pattern: MUXSTR_
_ func applyEnvOverrides(cfg *Config) { - // Server if val := os.Getenv("MUXSTR_SERVER_GRPC_ADDR"); val != "" { cfg.Server.GrpcAddr = val } @@ -306,12 +270,10 @@ func applyEnvOverrides(cfg *Config) { } } - // Database if val := os.Getenv("MUXSTR_DATABASE_PATH"); val != "" { cfg.Database.Path = val } - // Auth if val := os.Getenv("MUXSTR_AUTH_READ_ENABLED"); val != "" { cfg.Auth.Read.Enabled = parseBool(val) } @@ -331,7 +293,6 @@ func applyEnvOverrides(cfg *Config) { } } - // Rate limit if val := os.Getenv("MUXSTR_RATE_LIMIT_ENABLED"); val != "" { cfg.RateLimit.Enabled = parseBool(val) } @@ -348,7 +309,6 @@ func applyEnvOverrides(cfg *Config) { } } - // Metrics if val := os.Getenv("MUXSTR_METRICS_ENABLED"); val != "" { cfg.Metrics.Enabled = parseBool(val) } @@ -359,7 +319,6 @@ func applyEnvOverrides(cfg *Config) { cfg.Metrics.Path = val } - // Logging if val := os.Getenv("MUXSTR_LOGGING_LEVEL"); val != "" { cfg.Logging.Level = val } -- cgit v1.2.3