diff --git a/app/static/examples/config.example.yml b/app/static/examples/config.example.yml index 0b51cc1..0ee0d7f 100644 --- a/app/static/examples/config.example.yml +++ b/app/static/examples/config.example.yml @@ -14,6 +14,11 @@ server: max_connections: 100 max_subscriptions_per_client: 10 +event_time_constraints: + min_created_at: 1577836800 # January 1, 2020, as Unix timestamp + # max_created_at: 0 # Set to 0 to use the default behavior of 'now' + max_created_at_string: now+5m # Use a string to set a date for max created at in the future or past from current time + resource_limits: cpu_cores: 2 # Limit the number of CPU cores the application can use memory_mb: 1024 # Cap the maximum amount of RAM in MB the application can use diff --git a/config/loadConfig.go b/config/loadConfig.go index 8752c6f..0d1a976 100644 --- a/config/loadConfig.go +++ b/config/loadConfig.go @@ -6,6 +6,8 @@ import ( configTypes "grain/config/types" + "grain/server/utils" + "gopkg.in/yaml.v2" ) @@ -31,6 +33,9 @@ func LoadConfig(filename string) (*configTypes.ServerConfig, error) { return nil, err } + // Adjust event time constraints after loading + utils.AdjustEventTimeConstraints(&config) + once.Do(func() { cfg = &config }) diff --git a/config/tmp/main.exe b/config/tmp/main.exe deleted file mode 100644 index 82bf806..0000000 Binary files a/config/tmp/main.exe and /dev/null differ diff --git a/config/types/serverConfig.go b/config/types/serverConfig.go index 13d41b5..6878a73 100644 --- a/config/types/serverConfig.go +++ b/config/types/serverConfig.go @@ -1,5 +1,11 @@ package config +type EventTimeConstraints struct { + MinCreatedAt int64 `yaml:"min_created_at"` // Minimum allowed timestamp + MaxCreatedAt int64 `yaml:"max_created_at"` // Maximum allowed timestamp + MaxCreatedAtString string `yaml:"max_created_at_string"` // Original string value for parsing (e.g., "now+5m") +} + type ServerConfig struct { MongoDB struct { URI string `yaml:"uri"` @@ -7,15 +13,16 @@ type ServerConfig struct { } `yaml:"mongodb"` Server struct { Port string `yaml:"port"` - ReadTimeout int `yaml:"read_timeout"` // Timeout in seconds - WriteTimeout int `yaml:"write_timeout"` // Timeout in seconds - IdleTimeout int `yaml:"idle_timeout"` // Timeout in seconds - MaxConnections int `yaml:"max_connections"` // Maximum number of concurrent connections - MaxSubscriptionsPerClient int `yaml:"max_subscriptions_per_client"` // Maximum number of subscriptions per client + ReadTimeout int `yaml:"read_timeout"` + WriteTimeout int `yaml:"write_timeout"` + IdleTimeout int `yaml:"idle_timeout"` + MaxConnections int `yaml:"max_connections"` + MaxSubscriptionsPerClient int `yaml:"max_subscriptions_per_client"` } `yaml:"server"` - RateLimit RateLimitConfig `yaml:"rate_limit"` - Blacklist BlacklistConfig `yaml:"blacklist"` - ResourceLimits ResourceLimits `yaml:"resource_limits"` - Auth AuthConfig `yaml:"auth"` - EventPurge EventPurgeConfig `yaml:"event_purge"` + RateLimit RateLimitConfig `yaml:"rate_limit"` + Blacklist BlacklistConfig `yaml:"blacklist"` + ResourceLimits ResourceLimits `yaml:"resource_limits"` + Auth AuthConfig `yaml:"auth"` + EventPurge EventPurgeConfig `yaml:"event_purge"` + EventTimeConstraints EventTimeConstraints `yaml:"event_time_constraints"` // Added this field } diff --git a/main.go b/main.go index 22613b6..4caaae7 100644 --- a/main.go +++ b/main.go @@ -28,6 +28,9 @@ func main() { restartChan := make(chan struct{}) go config.WatchConfigFile("config.yml", restartChan) + go config.WatchConfigFile("whitelist.yml", restartChan) + go config.WatchConfigFile("blacklist.yml", restartChan) + go config.WatchConfigFile("relay_metadata.json", restartChan) signalChan := make(chan os.Signal, 1) signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) diff --git a/server/handlers/event.go b/server/handlers/event.go index cbbe1a0..e848fbf 100644 --- a/server/handlers/event.go +++ b/server/handlers/event.go @@ -6,6 +6,7 @@ import ( "fmt" "grain/config" "grain/server/db/mongo" + "time" "grain/server/handlers/response" "grain/server/utils" @@ -16,7 +17,6 @@ import ( ) func HandleEvent(ws *websocket.Conn, message []interface{}) { - if len(message) != 2 { fmt.Println("Invalid EVENT message format") response.SendNotice(ws, "", "Invalid EVENT message format") @@ -44,13 +44,19 @@ func HandleEvent(ws *websocket.Conn, message []interface{}) { return } + // Validate event timestamps + if !validateEventTimestamp(evt) { + response.SendOK(ws, evt.ID, false, "invalid: event created_at timestamp is out of allowed range") + return + } + // Signature check moved here if !utils.CheckSignature(evt) { response.SendOK(ws, evt.ID, false, "invalid: signature verification failed") return } - eventSize := len(eventBytes) // Calculate event size + eventSize := len(eventBytes) if !handleBlacklistAndWhitelist(ws, evt) { return @@ -60,11 +66,40 @@ func HandleEvent(ws *websocket.Conn, message []interface{}) { return } - // This is where I'll handle storage for multiple database types in the future + // Store the event in MongoDB or other storage mongo.StoreMongoEvent(context.TODO(), evt, ws) - fmt.Println("Event processed:", evt.ID) +} + +// Validate event timestamps against the configured min and max values +func validateEventTimestamp(evt nostr.Event) bool { + cfg := config.GetConfig() + if cfg == nil { + fmt.Println("Server configuration is not loaded") + return false + } + // Use current time for max and a fixed date for min if not specified + now := time.Now().Unix() + minCreatedAt := cfg.EventTimeConstraints.MinCreatedAt + if minCreatedAt == 0 { + // Use January 1, 2020, as the default minimum timestamp + minCreatedAt = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).Unix() + } + + maxCreatedAt := cfg.EventTimeConstraints.MaxCreatedAt + if maxCreatedAt == 0 { + // Default to the current time if not set + maxCreatedAt = now + } + + // Check if the event's created_at timestamp falls within the allowed range + if evt.CreatedAt < minCreatedAt || evt.CreatedAt > maxCreatedAt { + fmt.Printf("Event %s created_at timestamp %d is out of range [%d, %d]\n", evt.ID, evt.CreatedAt, minCreatedAt, maxCreatedAt) + return false + } + + return true } func handleBlacklistAndWhitelist(ws *websocket.Conn, evt nostr.Event) bool { diff --git a/server/utils/adjustTimeContraints.go b/server/utils/adjustTimeContraints.go new file mode 100644 index 0000000..936fba0 --- /dev/null +++ b/server/utils/adjustTimeContraints.go @@ -0,0 +1,34 @@ +package utils + +import ( + "fmt" + config "grain/config/types" + "strings" + "time" +) + +// Adjusts the event time constraints based on the configuration +func AdjustEventTimeConstraints(cfg *config.ServerConfig) { + now := time.Now() + + // Adjust min_created_at (no changes needed if it's already set in the config) + if cfg.EventTimeConstraints.MinCreatedAt == 0 { + cfg.EventTimeConstraints.MinCreatedAt = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).Unix() + } + + // Adjust max_created_at + if strings.HasPrefix(cfg.EventTimeConstraints.MaxCreatedAtString, "now") { + // Extract the offset (e.g., "+5m") + offset := strings.TrimPrefix(cfg.EventTimeConstraints.MaxCreatedAtString, "now") + duration, err := time.ParseDuration(offset) + if err != nil { + fmt.Printf("Invalid time offset for max_created_at: %s\n", offset) + cfg.EventTimeConstraints.MaxCreatedAt = now.Unix() // Default to now if parsing fails + } else { + cfg.EventTimeConstraints.MaxCreatedAt = now.Add(duration).Unix() + } + } else if cfg.EventTimeConstraints.MaxCreatedAt == 0 { + // Default to the current time if it's set to zero and no "now" keyword is used + cfg.EventTimeConstraints.MaxCreatedAt = now.Unix() + } +} \ No newline at end of file