2024-07-23 17:57:21 +00:00
|
|
|
package handlers
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2024-07-30 15:27:38 +00:00
|
|
|
"grain/config"
|
2024-09-17 14:35:31 +00:00
|
|
|
"grain/server/db/mongo"
|
2024-11-09 18:26:59 +00:00
|
|
|
"log"
|
2024-10-18 15:22:01 +00:00
|
|
|
"time"
|
2024-09-02 00:51:02 +00:00
|
|
|
|
2024-07-31 18:12:33 +00:00
|
|
|
"grain/server/handlers/response"
|
|
|
|
"grain/server/utils"
|
2024-07-23 17:57:21 +00:00
|
|
|
|
2024-09-02 00:51:02 +00:00
|
|
|
nostr "grain/server/types"
|
2024-07-23 17:57:21 +00:00
|
|
|
|
|
|
|
"golang.org/x/net/websocket"
|
|
|
|
)
|
|
|
|
|
2024-07-25 13:57:24 +00:00
|
|
|
func HandleEvent(ws *websocket.Conn, message []interface{}) {
|
2024-09-15 22:53:28 +00:00
|
|
|
if len(message) != 2 {
|
|
|
|
fmt.Println("Invalid EVENT message format")
|
|
|
|
response.SendNotice(ws, "", "Invalid EVENT message format")
|
|
|
|
return
|
|
|
|
}
|
2024-07-23 17:57:21 +00:00
|
|
|
|
2024-09-15 22:53:28 +00:00
|
|
|
eventData, ok := message[1].(map[string]interface{})
|
|
|
|
if !ok {
|
|
|
|
fmt.Println("Invalid event data format")
|
|
|
|
response.SendNotice(ws, "", "Invalid event data format")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
eventBytes, err := json.Marshal(eventData)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("Error marshaling event data:", err)
|
|
|
|
response.SendNotice(ws, "", "Error marshaling event data")
|
|
|
|
return
|
|
|
|
}
|
2024-07-23 17:57:21 +00:00
|
|
|
|
2024-09-15 22:53:28 +00:00
|
|
|
var evt nostr.Event
|
|
|
|
err = json.Unmarshal(eventBytes, &evt)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("Error unmarshaling event data:", err)
|
|
|
|
response.SendNotice(ws, "", "Error unmarshaling event data")
|
|
|
|
return
|
|
|
|
}
|
2024-08-30 20:00:41 +00:00
|
|
|
|
2024-10-18 15:22:01 +00:00
|
|
|
// Validate event timestamps
|
|
|
|
if !validateEventTimestamp(evt) {
|
|
|
|
response.SendOK(ws, evt.ID, false, "invalid: event created_at timestamp is out of allowed range")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-09-15 22:53:28 +00:00
|
|
|
// Signature check moved here
|
|
|
|
if !utils.CheckSignature(evt) {
|
|
|
|
response.SendOK(ws, evt.ID, false, "invalid: signature verification failed")
|
|
|
|
return
|
|
|
|
}
|
2024-08-30 19:51:46 +00:00
|
|
|
|
2024-10-18 15:22:01 +00:00
|
|
|
eventSize := len(eventBytes)
|
2024-08-30 19:51:46 +00:00
|
|
|
|
2024-09-15 22:53:28 +00:00
|
|
|
if !handleBlacklistAndWhitelist(ws, evt) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if !handleRateAndSizeLimits(ws, evt, eventSize) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-11-09 16:32:23 +00:00
|
|
|
// Check for duplicate event
|
|
|
|
isDuplicate, err := mongo.CheckDuplicateEvent(context.TODO(), evt)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Error checking for duplicate event: %v\n", err)
|
|
|
|
response.SendOK(ws, evt.ID, false, "error: internal server error during duplicate check")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if isDuplicate {
|
|
|
|
response.SendOK(ws, evt.ID, false, "blocked: the database already contains this event")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-10-18 15:22:01 +00:00
|
|
|
// Store the event in MongoDB or other storage
|
2024-09-17 14:35:31 +00:00
|
|
|
mongo.StoreMongoEvent(context.TODO(), evt, ws)
|
2024-09-15 22:53:28 +00:00
|
|
|
fmt.Println("Event processed:", evt.ID)
|
2024-11-09 18:26:59 +00:00
|
|
|
|
|
|
|
// Load the config and check for errors
|
|
|
|
cfg, err := config.LoadConfig("config.yml")
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Error loading configuration: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send the event to the backup relay if configured
|
|
|
|
if cfg.BackupRelay.Enabled {
|
|
|
|
go func() {
|
|
|
|
err := sendToBackupRelay(cfg.BackupRelay.URL, evt)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Failed to send event %s to backup relay: %v", evt.ID, err)
|
|
|
|
} else {
|
|
|
|
log.Printf("Event %s successfully sent to backup relay", evt.ID)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func sendToBackupRelay(backupURL string, evt nostr.Event) error {
|
|
|
|
conn, err := websocket.Dial(backupURL, "", "http://localhost/")
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error connecting to backup relay %s: %w", backupURL, err)
|
|
|
|
}
|
|
|
|
defer conn.Close()
|
|
|
|
|
|
|
|
// Create the message to send
|
|
|
|
eventMessage := []interface{}{"EVENT", evt}
|
|
|
|
eventMessageBytes, err := json.Marshal(eventMessage)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error marshaling event message: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := conn.Write(eventMessageBytes); err != nil {
|
|
|
|
return fmt.Errorf("error sending event message to backup relay: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Log and return
|
|
|
|
log.Printf("Event %s sent to backup relay %s", evt.ID, backupURL)
|
|
|
|
time.Sleep(500 * time.Millisecond) // Optional: small delay to avoid rapid successive sends
|
|
|
|
|
|
|
|
return nil
|
2024-10-18 15:22:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2024-10-27 21:04:25 +00:00
|
|
|
// Adjust event time constraints in the configuration
|
|
|
|
utils.AdjustEventTimeConstraints(cfg)
|
|
|
|
|
2024-10-18 15:22:01 +00:00
|
|
|
// 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
|
|
|
|
}
|
2024-07-23 17:57:21 +00:00
|
|
|
|
2024-10-18 15:22:01 +00:00
|
|
|
return true
|
2024-07-23 17:57:21 +00:00
|
|
|
}
|
|
|
|
|
2024-09-02 00:51:02 +00:00
|
|
|
func handleBlacklistAndWhitelist(ws *websocket.Conn, evt nostr.Event) bool {
|
2024-11-09 16:32:23 +00:00
|
|
|
// Use the updated CheckBlacklist function
|
|
|
|
if blacklisted, msg := config.CheckBlacklist(evt.PubKey, evt.Content); blacklisted {
|
|
|
|
response.SendOK(ws, evt.ID, false, msg)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the whitelist using CheckWhitelist function
|
|
|
|
isWhitelisted, msg := config.CheckWhitelist(evt)
|
|
|
|
if !isWhitelisted {
|
|
|
|
response.SendOK(ws, evt.ID, false, msg)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
2024-08-30 19:51:46 +00:00
|
|
|
}
|
2024-10-16 18:07:37 +00:00
|
|
|
|
2024-09-02 00:51:02 +00:00
|
|
|
func handleRateAndSizeLimits(ws *websocket.Conn, evt nostr.Event, eventSize int) bool {
|
2024-08-30 19:51:46 +00:00
|
|
|
rateLimiter := config.GetRateLimiter()
|
|
|
|
sizeLimiter := config.GetSizeLimiter()
|
2024-07-27 14:06:34 +00:00
|
|
|
category := determineCategory(evt.Kind)
|
2024-07-25 13:57:24 +00:00
|
|
|
|
2024-07-26 14:02:34 +00:00
|
|
|
if allowed, msg := rateLimiter.AllowEvent(evt.Kind, category); !allowed {
|
|
|
|
response.SendOK(ws, evt.ID, false, msg)
|
2024-08-30 19:51:46 +00:00
|
|
|
return false
|
2024-07-25 13:57:24 +00:00
|
|
|
}
|
|
|
|
|
2024-07-26 20:46:01 +00:00
|
|
|
if allowed, msg := sizeLimiter.AllowSize(evt.Kind, eventSize); !allowed {
|
|
|
|
response.SendOK(ws, evt.ID, false, msg)
|
2024-08-30 19:51:46 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2024-07-27 14:06:34 +00:00
|
|
|
func determineCategory(kind int) string {
|
|
|
|
switch {
|
|
|
|
case kind == 0, kind == 3, kind >= 10000 && kind < 20000:
|
|
|
|
return "replaceable"
|
|
|
|
case kind == 1, kind >= 4 && kind < 45, kind >= 1000 && kind < 10000:
|
|
|
|
return "regular"
|
|
|
|
case kind == 2:
|
|
|
|
return "deprecated"
|
|
|
|
case kind >= 20000 && kind < 30000:
|
|
|
|
return "ephemeral"
|
|
|
|
case kind >= 30000 && kind < 40000:
|
|
|
|
return "parameterized_replaceable"
|
|
|
|
default:
|
|
|
|
return "unknown"
|
|
|
|
}
|
|
|
|
}
|