mirror of
https://github.com/0ceanSlim/grain.git
synced 2024-11-23 09:07:12 +00:00
Compare commits
7 Commits
9df03646db
...
158f284be9
Author | SHA1 | Date | |
---|---|---|---|
158f284be9 | |||
e6188796d2 | |||
108142b801 | |||
3d88938b7e | |||
21c431dd22 | |||
3de1aeb998 | |||
5133c3a005 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,5 +1,7 @@
|
|||||||
/tmp
|
/tmp
|
||||||
config.yml
|
config.yml
|
||||||
|
whitelist.yml
|
||||||
|
blacklist.yml
|
||||||
relay_metadata.json
|
relay_metadata.json
|
||||||
grain.exe
|
grain.exe
|
||||||
/build
|
/build
|
||||||
|
16
app/static/examples/blacklist.example.yml
Normal file
16
app/static/examples/blacklist.example.yml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
enabled: true
|
||||||
|
permanent_ban_words:
|
||||||
|
- nigger
|
||||||
|
temp_ban_words:
|
||||||
|
- crypto
|
||||||
|
- web3
|
||||||
|
- airdrop
|
||||||
|
max_temp_bans: 3
|
||||||
|
temp_ban_duration: 3600
|
||||||
|
permanent_blacklist_pubkeys:
|
||||||
|
- db0c9b8acd6101adb9b281c5321f98f6eebb33c5719d230ed1870997538a9765
|
||||||
|
permanent_blacklist_npubs:
|
||||||
|
- npub1x0r5gflnk2mn6h3c70nvnywpy2j46gzqwg6k7uw6fxswyz0md9qqnhshtn
|
||||||
|
mutelist_authors:
|
||||||
|
- 3fe0ab6cbdb7ee27148202249e3fb3b89423c6f6cda6ef43ea5057c3d93088e4
|
||||||
|
# mutelist Event MUST be stored in this relay for it to be retrieved.
|
@ -14,6 +14,12 @@ server:
|
|||||||
max_connections: 100
|
max_connections: 100
|
||||||
max_subscriptions_per_client: 10
|
max_subscriptions_per_client: 10
|
||||||
|
|
||||||
|
event_time_constraints:
|
||||||
|
min_created_at: 1577836800 # January 1, 2020, as Unix timestamp
|
||||||
|
# min_created_at_string: now-5m # Custom value to indicate 5 minutes in the past
|
||||||
|
# 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:
|
resource_limits:
|
||||||
cpu_cores: 2 # Limit the number of CPU cores the application can use
|
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
|
memory_mb: 1024 # Cap the maximum amount of RAM in MB the application can use
|
||||||
@ -63,33 +69,6 @@ rate_limit:
|
|||||||
limit: 25
|
limit: 25
|
||||||
burst: 50
|
burst: 50
|
||||||
|
|
||||||
pubkey_whitelist:
|
|
||||||
enabled: false
|
|
||||||
pubkeys: [] # List of allowed public keys
|
|
||||||
npubs: [] # List of allowed npubs (Nostr public keys in bech32 format)
|
|
||||||
|
|
||||||
kind_whitelist:
|
|
||||||
enabled: false
|
|
||||||
kinds: [] # List of allowed event kinds
|
|
||||||
|
|
||||||
domain_whitelist:
|
|
||||||
enabled: false
|
|
||||||
domains: [] # List of allowed domains
|
|
||||||
|
|
||||||
blacklist: #Removing a pubkey from the Blacklist requires a hard restart; Blacklist overides the Whitelist
|
|
||||||
enabled: true
|
|
||||||
permanent_ban_words: [] # Words that trigger a permanent ban
|
|
||||||
temp_ban_words: # Words that trigger a temporary ban
|
|
||||||
- crypto
|
|
||||||
- web3
|
|
||||||
- airdrop
|
|
||||||
max_temp_bans: 3 # Number of temporary bans before a permanent ban
|
|
||||||
temp_ban_duration: 3600 # Temporary ban duration in seconds
|
|
||||||
permanent_blacklist_pubkeys: # List of permanently banned public keys
|
|
||||||
- db0c9b8acd6101adb9b281c5321f98f6eebb33c5719d230ed1870997538a9765
|
|
||||||
permanent_blacklist_npubs: # List of permanently banned npubs
|
|
||||||
- npub1x0r5gflnk2mn6h3c70nvnywpy2j46gzqwg6k7uw6fxswyz0md9qqnhshtn
|
|
||||||
|
|
||||||
event_purge:
|
event_purge:
|
||||||
enabled: true # Toggle to enable/disable event purging
|
enabled: true # Toggle to enable/disable event purging
|
||||||
keep_duration_days: 2 # Number of days to keep events
|
keep_duration_days: 2 # Number of days to keep events
|
||||||
|
20
app/static/examples/whitelist.example.yml
Normal file
20
app/static/examples/whitelist.example.yml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
pubkey_whitelist:
|
||||||
|
enabled: false
|
||||||
|
pubkeys:
|
||||||
|
- pubkey1
|
||||||
|
- pubkey2
|
||||||
|
npubs:
|
||||||
|
- npub18ls2km9aklhzw9yzqgjfu0anhz2z83hkeknw7sl22ptu8kfs3rjq54am44
|
||||||
|
- npub2
|
||||||
|
|
||||||
|
kind_whitelist:
|
||||||
|
enabled: false
|
||||||
|
kinds:
|
||||||
|
- "1"
|
||||||
|
- "2"
|
||||||
|
|
||||||
|
domain_whitelist:
|
||||||
|
enabled: false
|
||||||
|
domains:
|
||||||
|
- "example.com"
|
||||||
|
- "anotherdomain.com"
|
@ -1,65 +1,68 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
types "grain/config/types"
|
types "grain/config/types"
|
||||||
"grain/server/utils"
|
"grain/server/utils"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CheckBlacklist checks if a pubkey is in the blacklist based on event content
|
// CheckBlacklist checks if a pubkey is in the blacklist based on event content
|
||||||
func CheckBlacklist(pubkey, eventContent string) (bool, string) {
|
func CheckBlacklist(pubkey, eventContent string) (bool, string) {
|
||||||
blacklistConfig := GetConfig().Blacklist
|
blacklistConfig := GetBlacklistConfig()
|
||||||
|
if blacklistConfig == nil || !blacklistConfig.Enabled {
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
if !blacklistConfig.Enabled {
|
log.Printf("Checking blacklist for pubkey: %s", pubkey)
|
||||||
return false, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Checking blacklist for pubkey: %s", pubkey)
|
// Check for permanent blacklist by pubkey or npub.
|
||||||
|
if isPubKeyPermanentlyBlacklisted(pubkey, blacklistConfig) {
|
||||||
|
log.Printf("Pubkey %s is permanently blacklisted", pubkey)
|
||||||
|
return true, fmt.Sprintf("pubkey %s is permanently blacklisted", pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
// Check for permanent blacklist by pubkey or npub
|
// Check for temporary ban.
|
||||||
if isPubKeyPermanentlyBlacklisted(pubkey, blacklistConfig) {
|
if isPubKeyTemporarilyBlacklisted(pubkey) {
|
||||||
log.Printf("Pubkey %s is permanently blacklisted", pubkey)
|
log.Printf("Pubkey %s is temporarily blacklisted", pubkey)
|
||||||
return true, fmt.Sprintf("pubkey %s is permanently blacklisted", pubkey)
|
return true, fmt.Sprintf("pubkey %s is temporarily blacklisted", pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for temporary ban
|
// Check for permanent ban based on wordlist.
|
||||||
if isPubKeyTemporarilyBlacklisted(pubkey) {
|
for _, word := range blacklistConfig.PermanentBanWords {
|
||||||
log.Printf("Pubkey %s is temporarily blacklisted", pubkey)
|
if strings.Contains(eventContent, word) {
|
||||||
return true, fmt.Sprintf("pubkey %s is temporarily blacklisted", pubkey)
|
err := AddToPermanentBlacklist(pubkey)
|
||||||
}
|
if err != nil {
|
||||||
|
return true, fmt.Sprintf("pubkey %s is permanently banned and failed to save: %v", pubkey, err)
|
||||||
|
}
|
||||||
|
return true, "blocked: pubkey is permanently banned"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check for permanent ban based on wordlist
|
// Check for temporary ban based on wordlist.
|
||||||
for _, word := range blacklistConfig.PermanentBanWords {
|
for _, word := range blacklistConfig.TempBanWords {
|
||||||
if strings.Contains(eventContent, word) {
|
if strings.Contains(eventContent, word) {
|
||||||
err := AddToPermanentBlacklist(pubkey)
|
err := AddToTemporaryBlacklist(pubkey, *blacklistConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return true, fmt.Sprintf("pubkey %s is permanently banned and failed to save: %v", pubkey, err)
|
return true, fmt.Sprintf("pubkey %s is temporarily banned and failed to save: %v", pubkey, err)
|
||||||
}
|
}
|
||||||
return true, "blocked: pubkey is permanently banned"
|
return true, "blocked: pubkey is temporarily banned"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for temporary ban based on wordlist
|
return false, ""
|
||||||
for _, word := range blacklistConfig.TempBanWords {
|
|
||||||
if strings.Contains(eventContent, word) {
|
|
||||||
err := AddToTemporaryBlacklist(pubkey, blacklistConfig)
|
|
||||||
if err != nil {
|
|
||||||
return true, fmt.Sprintf("pubkey %s is temporarily banned and failed to save: %v", pubkey, err)
|
|
||||||
}
|
|
||||||
return true, "blocked: pubkey is temporarily banned"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Checks if a pubkey is temporarily blacklisted
|
// Checks if a pubkey is temporarily blacklisted
|
||||||
func isPubKeyTemporarilyBlacklisted(pubkey string) bool {
|
func isPubKeyTemporarilyBlacklisted(pubkey string) bool {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
@ -142,63 +145,216 @@ func AddToTemporaryBlacklist(pubkey string, blacklistConfig types.BlacklistConfi
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if a pubkey is permanently blacklisted (only using config.yml)
|
func isPubKeyPermanentlyBlacklisted(pubKey string, blacklistConfig *types.BlacklistConfig) bool {
|
||||||
func isPubKeyPermanentlyBlacklisted(pubKey string, blacklistConfig types.BlacklistConfig) bool {
|
if blacklistConfig == nil || !blacklistConfig.Enabled {
|
||||||
if !blacklistConfig.Enabled {
|
return false
|
||||||
return false
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Check pubkeys
|
// Check pubkeys.
|
||||||
for _, blacklistedKey := range blacklistConfig.PermanentBlacklistPubkeys {
|
for _, blacklistedKey := range blacklistConfig.PermanentBlacklistPubkeys {
|
||||||
if pubKey == blacklistedKey {
|
if pubKey == blacklistedKey {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check npubs
|
// Check npubs.
|
||||||
for _, npub := range blacklistConfig.PermanentBlacklistNpubs {
|
for _, npub := range blacklistConfig.PermanentBlacklistNpubs {
|
||||||
decodedPubKey, err := utils.DecodeNpub(npub)
|
decodedPubKey, err := utils.DecodeNpub(npub)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error decoding npub:", err)
|
fmt.Println("Error decoding npub:", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if pubKey == decodedPubKey {
|
if pubKey == decodedPubKey {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddToPermanentBlacklist(pubkey string) error {
|
func AddToPermanentBlacklist(pubkey string) error {
|
||||||
// Remove the mutex lock from here
|
blacklistConfig := GetBlacklistConfig()
|
||||||
blacklistConfig := GetConfig().Blacklist
|
if blacklistConfig == nil {
|
||||||
|
return fmt.Errorf("blacklist configuration is not loaded")
|
||||||
|
}
|
||||||
|
|
||||||
// Check if already blacklisted
|
// Check if already blacklisted.
|
||||||
if isPubKeyPermanentlyBlacklisted(pubkey, blacklistConfig) {
|
if isPubKeyPermanentlyBlacklisted(pubkey, blacklistConfig) {
|
||||||
return fmt.Errorf("pubkey %s is already in the permanent blacklist", pubkey)
|
return fmt.Errorf("pubkey %s is already in the permanent blacklist", pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add pubkey to the blacklist
|
// Add pubkey to the permanent blacklist.
|
||||||
blacklistConfig.PermanentBlacklistPubkeys = append(blacklistConfig.PermanentBlacklistPubkeys, pubkey)
|
blacklistConfig.PermanentBlacklistPubkeys = append(blacklistConfig.PermanentBlacklistPubkeys, pubkey)
|
||||||
|
|
||||||
// Persist changes to config.yml
|
// Persist changes to blacklist.yml.
|
||||||
return saveBlacklistConfig(blacklistConfig)
|
return saveBlacklistConfig(*blacklistConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveBlacklistConfig(blacklistConfig types.BlacklistConfig) error {
|
func saveBlacklistConfig(blacklistConfig types.BlacklistConfig) error {
|
||||||
configData := GetConfig()
|
data, err := yaml.Marshal(blacklistConfig)
|
||||||
configData.Blacklist = blacklistConfig
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to marshal blacklist config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
data, err := yaml.Marshal(configData)
|
err = os.WriteFile("blacklist.yml", data, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal config: %v", err)
|
return fmt.Errorf("failed to write config to file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.WriteFile("config.yml", data, 0644)
|
return nil
|
||||||
if err != nil {
|
}
|
||||||
return fmt.Errorf("failed to write config to file: %v", err)
|
|
||||||
}
|
// FetchPubkeysFromLocalMuteList sends a REQ to the local relay for mute list events.
|
||||||
|
func FetchPubkeysFromLocalMuteList(localRelayURL string, muteListAuthors []string) ([]string, error) {
|
||||||
return nil
|
var wg sync.WaitGroup
|
||||||
|
var mu sync.Mutex
|
||||||
|
var allPubkeys []string
|
||||||
|
results := make(chan []string, 1)
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
conn, _, err := websocket.DefaultDialer.Dial(localRelayURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to connect to local relay %s: %v", localRelayURL, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
subscriptionID := "mutelist-fetch"
|
||||||
|
|
||||||
|
// Create the REQ message to fetch the mute list events by IDs.
|
||||||
|
req := []interface{}{"REQ", subscriptionID, map[string]interface{}{
|
||||||
|
"authors": muteListAuthors,
|
||||||
|
"kinds": []int{10000}, // Mute list events kind.
|
||||||
|
}}
|
||||||
|
|
||||||
|
reqJSON, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to marshal request: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = conn.WriteMessage(websocket.TextMessage, reqJSON)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to send request to local relay %s: %v", localRelayURL, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for messages from the local relay.
|
||||||
|
for {
|
||||||
|
_, message, err := conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error reading message from local relay %s: %v", localRelayURL, err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the raw message for debugging.
|
||||||
|
log.Printf("Received raw message: %s", message)
|
||||||
|
|
||||||
|
var response []interface{}
|
||||||
|
err = json.Unmarshal(message, &response)
|
||||||
|
if err != nil || len(response) < 2 {
|
||||||
|
log.Printf("Invalid message format or failed to unmarshal: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for "EVENT" type messages.
|
||||||
|
eventType, ok := response[0].(string)
|
||||||
|
if !ok {
|
||||||
|
log.Printf("Unexpected event type: %v", response[0])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if eventType == "EOSE" {
|
||||||
|
// End of subscription events; send a "CLOSE" message to the relay.
|
||||||
|
closeReq := []interface{}{"CLOSE", subscriptionID}
|
||||||
|
closeReqJSON, err := json.Marshal(closeReq)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to marshal close request: %v", err)
|
||||||
|
} else {
|
||||||
|
if err = conn.WriteMessage(websocket.TextMessage, closeReqJSON); err != nil {
|
||||||
|
log.Printf("Failed to send close request to relay %s: %v", localRelayURL, err)
|
||||||
|
} else {
|
||||||
|
log.Println("Sent CLOSE request to end subscription.")
|
||||||
|
|
||||||
|
// Wait for a potential response or timeout
|
||||||
|
conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
|
||||||
|
_, _, err = conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
log.Println("Connection closed by the server after CLOSE request (EOF)")
|
||||||
|
} else if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway) {
|
||||||
|
log.Println("WebSocket closed normally after CLOSE request")
|
||||||
|
} else {
|
||||||
|
log.Printf("Unexpected error after CLOSE request: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we break the loop after handling EOSE
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if eventType == "EVENT" {
|
||||||
|
// Safely cast the event data from the third element.
|
||||||
|
if len(response) < 3 {
|
||||||
|
log.Printf("Unexpected event format with insufficient data: %v", response)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
eventData, ok := response[2].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
log.Printf("Expected event data to be a map, got: %T", response[2])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log event data for debugging.
|
||||||
|
log.Printf("Event data received: %+v", eventData)
|
||||||
|
|
||||||
|
pubkeys := extractPubkeysFromMuteListEvent(eventData)
|
||||||
|
results <- pubkeys
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(results)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Collect results from the relay.
|
||||||
|
for pubkeys := range results {
|
||||||
|
mu.Lock()
|
||||||
|
allPubkeys = append(allPubkeys, pubkeys...)
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return allPubkeys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractPubkeysFromMuteListEvent extracts pubkeys from a mute list event.
|
||||||
|
func extractPubkeysFromMuteListEvent(eventData map[string]interface{}) []string {
|
||||||
|
var pubkeys []string
|
||||||
|
|
||||||
|
tags, ok := eventData["tags"].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
log.Println("Tags field is missing or not an array")
|
||||||
|
return pubkeys
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tag := range tags {
|
||||||
|
tagArray, ok := tag.([]interface{})
|
||||||
|
if ok && len(tagArray) > 1 && tagArray[0] == "p" {
|
||||||
|
pubkey, ok := tagArray[1].(string)
|
||||||
|
if ok {
|
||||||
|
pubkeys = append(pubkeys, pubkey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Extracted pubkeys: %v", pubkeys)
|
||||||
|
return pubkeys
|
||||||
}
|
}
|
||||||
|
@ -6,52 +6,50 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Helper function to check if a pubkey or npub is whitelisted
|
// Check if a pubkey or npub is whitelisted
|
||||||
func IsPubKeyWhitelisted(pubKey string) bool {
|
func IsPubKeyWhitelisted(pubKey string) bool {
|
||||||
cfg := GetConfig()
|
cfg := GetWhitelistConfig()
|
||||||
if !cfg.PubkeyWhitelist.Enabled {
|
if !cfg.PubkeyWhitelist.Enabled {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check pubkeys
|
for _, whitelistedKey := range cfg.PubkeyWhitelist.Pubkeys {
|
||||||
for _, whitelistedKey := range cfg.PubkeyWhitelist.Pubkeys {
|
if pubKey == whitelistedKey {
|
||||||
if pubKey == whitelistedKey {
|
return true
|
||||||
return true
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Check npubs
|
for _, npub := range cfg.PubkeyWhitelist.Npubs {
|
||||||
for _, npub := range cfg.PubkeyWhitelist.Npubs {
|
decodedPubKey, err := utils.DecodeNpub(npub)
|
||||||
decodedPubKey, err := utils.DecodeNpub(npub)
|
if err != nil {
|
||||||
if err != nil {
|
fmt.Println("Error decoding npub:", err)
|
||||||
fmt.Println("Error decoding npub:", err)
|
continue
|
||||||
continue
|
}
|
||||||
}
|
if pubKey == decodedPubKey {
|
||||||
if pubKey == decodedPubKey {
|
return true
|
||||||
return true
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if a kind is whitelisted
|
||||||
func IsKindWhitelisted(kind int) bool {
|
func IsKindWhitelisted(kind int) bool {
|
||||||
cfg := GetConfig()
|
cfg := GetWhitelistConfig()
|
||||||
if !cfg.KindWhitelist.Enabled {
|
if !cfg.KindWhitelist.Enabled {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check event kinds
|
for _, whitelistedKindStr := range cfg.KindWhitelist.Kinds {
|
||||||
for _, whitelistedKindStr := range cfg.KindWhitelist.Kinds {
|
whitelistedKind, err := strconv.Atoi(whitelistedKindStr)
|
||||||
whitelistedKind, err := strconv.Atoi(whitelistedKindStr)
|
if err != nil {
|
||||||
if err != nil {
|
fmt.Println("Error converting whitelisted kind to int:", err)
|
||||||
fmt.Println("Error converting whitelisted kind to int:", err)
|
continue
|
||||||
continue
|
}
|
||||||
}
|
if kind == whitelistedKind {
|
||||||
if kind == whitelistedKind {
|
return true
|
||||||
return true
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
@ -6,33 +6,91 @@ import (
|
|||||||
|
|
||||||
configTypes "grain/config/types"
|
configTypes "grain/config/types"
|
||||||
|
|
||||||
|
"grain/server/utils"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cfg *configTypes.ServerConfig
|
cfg *configTypes.ServerConfig
|
||||||
once sync.Once
|
whitelistCfg *configTypes.WhitelistConfig
|
||||||
|
blacklistCfg *configTypes.BlacklistConfig
|
||||||
|
once sync.Once
|
||||||
|
whitelistOnce sync.Once
|
||||||
|
blacklistOnce sync.Once
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// LoadConfig loads the server configuration from config.yml
|
||||||
func LoadConfig(filename string) (*configTypes.ServerConfig, error) {
|
func LoadConfig(filename string) (*configTypes.ServerConfig, error) {
|
||||||
data, err := os.ReadFile(filename)
|
data, err := os.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var config configTypes.ServerConfig
|
var config configTypes.ServerConfig
|
||||||
err = yaml.Unmarshal(data, &config)
|
err = yaml.Unmarshal(data, &config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
once.Do(func() {
|
// Adjust event time constraints after loading
|
||||||
cfg = &config
|
utils.AdjustEventTimeConstraints(&config)
|
||||||
})
|
|
||||||
|
|
||||||
return cfg, nil
|
once.Do(func() {
|
||||||
|
cfg = &config
|
||||||
|
})
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadWhitelistConfig loads the whitelist configuration from whitelist.yml
|
||||||
|
func LoadWhitelistConfig(filename string) (*configTypes.WhitelistConfig, error) {
|
||||||
|
data, err := os.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var config configTypes.WhitelistConfig
|
||||||
|
err = yaml.Unmarshal(data, &config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
whitelistOnce.Do(func() {
|
||||||
|
whitelistCfg = &config
|
||||||
|
})
|
||||||
|
|
||||||
|
return whitelistCfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetConfig() *configTypes.ServerConfig {
|
func GetConfig() *configTypes.ServerConfig {
|
||||||
return cfg
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetWhitelistConfig() *configTypes.WhitelistConfig {
|
||||||
|
return whitelistCfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadBlacklistConfig loads the blacklist configuration from blacklist.yml
|
||||||
|
func LoadBlacklistConfig(filename string) (*configTypes.BlacklistConfig, error) {
|
||||||
|
data, err := os.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var config configTypes.BlacklistConfig
|
||||||
|
err = yaml.Unmarshal(data, &config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
blacklistOnce.Do(func() {
|
||||||
|
blacklistCfg = &config
|
||||||
|
})
|
||||||
|
|
||||||
|
return blacklistCfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBlacklistConfig() *configTypes.BlacklistConfig {
|
||||||
|
return blacklistCfg
|
||||||
}
|
}
|
Binary file not shown.
@ -8,4 +8,5 @@ type BlacklistConfig struct {
|
|||||||
TempBanDuration int `yaml:"temp_ban_duration"`
|
TempBanDuration int `yaml:"temp_ban_duration"`
|
||||||
PermanentBlacklistPubkeys []string `yaml:"permanent_blacklist_pubkeys"`
|
PermanentBlacklistPubkeys []string `yaml:"permanent_blacklist_pubkeys"`
|
||||||
PermanentBlacklistNpubs []string `yaml:"permanent_blacklist_npubs"`
|
PermanentBlacklistNpubs []string `yaml:"permanent_blacklist_npubs"`
|
||||||
|
MuteListAuthors []string `yaml:"mutelist_authors"`
|
||||||
}
|
}
|
@ -1,5 +1,12 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
|
type EventTimeConstraints struct {
|
||||||
|
MinCreatedAt int64 `yaml:"min_created_at"` // Minimum allowed timestamp
|
||||||
|
MinCreatedAtString string `yaml:"min_created_at_string"` // Original string value for parsing (e.g., "now-5m")
|
||||||
|
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 {
|
type ServerConfig struct {
|
||||||
MongoDB struct {
|
MongoDB struct {
|
||||||
URI string `yaml:"uri"`
|
URI string `yaml:"uri"`
|
||||||
@ -7,18 +14,16 @@ type ServerConfig struct {
|
|||||||
} `yaml:"mongodb"`
|
} `yaml:"mongodb"`
|
||||||
Server struct {
|
Server struct {
|
||||||
Port string `yaml:"port"`
|
Port string `yaml:"port"`
|
||||||
ReadTimeout int `yaml:"read_timeout"` // Timeout in seconds
|
ReadTimeout int `yaml:"read_timeout"`
|
||||||
WriteTimeout int `yaml:"write_timeout"` // Timeout in seconds
|
WriteTimeout int `yaml:"write_timeout"`
|
||||||
IdleTimeout int `yaml:"idle_timeout"` // Timeout in seconds
|
IdleTimeout int `yaml:"idle_timeout"`
|
||||||
MaxConnections int `yaml:"max_connections"` // Maximum number of concurrent connections
|
MaxConnections int `yaml:"max_connections"`
|
||||||
MaxSubscriptionsPerClient int `yaml:"max_subscriptions_per_client"` // Maximum number of subscriptions per client
|
MaxSubscriptionsPerClient int `yaml:"max_subscriptions_per_client"`
|
||||||
} `yaml:"server"`
|
} `yaml:"server"`
|
||||||
RateLimit RateLimitConfig `yaml:"rate_limit"`
|
RateLimit RateLimitConfig `yaml:"rate_limit"`
|
||||||
PubkeyWhitelist PubkeyWhitelistConfig `yaml:"pubkey_whitelist"`
|
Blacklist BlacklistConfig `yaml:"blacklist"`
|
||||||
KindWhitelist KindWhitelistConfig `yaml:"kind_whitelist"`
|
ResourceLimits ResourceLimits `yaml:"resource_limits"`
|
||||||
DomainWhitelist DomainWhitelistConfig `yaml:"domain_whitelist"`
|
Auth AuthConfig `yaml:"auth"`
|
||||||
Blacklist BlacklistConfig `yaml:"blacklist"`
|
EventPurge EventPurgeConfig `yaml:"event_purge"`
|
||||||
ResourceLimits ResourceLimits `yaml:"resource_limits"`
|
EventTimeConstraints EventTimeConstraints `yaml:"event_time_constraints"` // Added this field
|
||||||
Auth AuthConfig `yaml:"auth"`
|
|
||||||
EventPurge EventPurgeConfig `yaml:"event_purge"`
|
|
||||||
}
|
}
|
||||||
|
19
config/types/whitelistConfig.go
Normal file
19
config/types/whitelistConfig.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type WhitelistConfig struct {
|
||||||
|
PubkeyWhitelist struct {
|
||||||
|
Enabled bool `yaml:"enabled"`
|
||||||
|
Pubkeys []string `yaml:"pubkeys"`
|
||||||
|
Npubs []string `yaml:"npubs"`
|
||||||
|
} `yaml:"pubkey_whitelist"`
|
||||||
|
|
||||||
|
KindWhitelist struct {
|
||||||
|
Enabled bool `yaml:"enabled"`
|
||||||
|
Kinds []string `yaml:"kinds"`
|
||||||
|
} `yaml:"kind_whitelist"`
|
||||||
|
|
||||||
|
DomainWhitelist struct {
|
||||||
|
Enabled bool `yaml:"enabled"`
|
||||||
|
Domains []string `yaml:"domains"`
|
||||||
|
} `yaml:"domain_whitelist"`
|
||||||
|
}
|
@ -1,6 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
type DomainWhitelistConfig struct {
|
|
||||||
Enabled bool `yaml:"enabled"`
|
|
||||||
Domains []string `yaml:"domains"`
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
type KindWhitelistConfig struct {
|
|
||||||
Enabled bool `yaml:"enabled"`
|
|
||||||
Kinds []string `yaml:"kinds"`
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
type PubkeyWhitelistConfig struct {
|
|
||||||
Enabled bool `yaml:"enabled"`
|
|
||||||
Pubkeys []string `yaml:"pubkeys"`
|
|
||||||
Npubs []string `yaml:"npubs"`
|
|
||||||
}
|
|
1
go.mod
1
go.mod
@ -4,6 +4,7 @@ go 1.22.2
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.3.4
|
github.com/btcsuite/btcd/btcec/v2 v2.3.4
|
||||||
|
github.com/gorilla/websocket v1.5.3
|
||||||
go.mongodb.org/mongo-driver v1.16.0
|
go.mongodb.org/mongo-driver v1.16.0
|
||||||
golang.org/x/net v0.27.0
|
golang.org/x/net v0.27.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
|
2
go.sum
2
go.sum
@ -28,6 +28,8 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
|||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||||
|
103
main.go
103
main.go
@ -21,64 +21,75 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
utils.EnsureFileExists("config.yml", "app/static/examples/config.example.yml")
|
utils.EnsureFileExists("config.yml", "app/static/examples/config.example.yml")
|
||||||
utils.EnsureFileExists("relay_metadata.json", "app/static/examples/relay_metadata.example.json")
|
utils.EnsureFileExists("whitelist.yml", "app/static/examples/whitelist.example.yml")
|
||||||
|
utils.EnsureFileExists("blacklist.yml", "app/static/examples/blacklist.example.yml")
|
||||||
|
utils.EnsureFileExists("relay_metadata.json", "app/static/examples/relay_metadata.example.json")
|
||||||
|
|
||||||
restartChan := make(chan struct{})
|
restartChan := make(chan struct{})
|
||||||
go config.WatchConfigFile("config.yml", restartChan) // Critical goroutine
|
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)
|
signalChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for {
|
for {
|
||||||
wg.Add(1) // Add to WaitGroup for the server goroutine
|
wg.Add(1)
|
||||||
|
|
||||||
cfg, err := config.LoadConfig("config.yml")
|
cfg, err := config.LoadConfig("config.yml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error loading config: ", err)
|
log.Fatal("Error loading config: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start event purging in the background
|
_, err = config.LoadWhitelistConfig("whitelist.yml")
|
||||||
go mongo.ScheduleEventPurging(cfg)
|
if err != nil {
|
||||||
|
log.Fatal("Error loading whitelist config: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
config.SetResourceLimit(&cfg.ResourceLimits) // Apply limits once before starting the server
|
_, err = config.LoadBlacklistConfig("blacklist.yml")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error loading blacklist config: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
client, err := mongo.InitDB(cfg)
|
// Start event purging in the background.
|
||||||
if err != nil {
|
go mongo.ScheduleEventPurging(cfg)
|
||||||
log.Fatal("Error initializing database: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config.SetRateLimit(cfg)
|
config.SetResourceLimit(&cfg.ResourceLimits)
|
||||||
config.SetSizeLimit(cfg)
|
client, err := mongo.InitDB(cfg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error initializing database: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
config.ClearTemporaryBans()
|
config.SetRateLimit(cfg)
|
||||||
|
config.SetSizeLimit(cfg)
|
||||||
|
config.ClearTemporaryBans()
|
||||||
|
|
||||||
err = utils.LoadRelayMetadataJSON()
|
err = utils.LoadRelayMetadataJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Failed to load relay metadata: ", err)
|
log.Fatal("Failed to load relay metadata: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mux := setupRoutes()
|
mux := setupRoutes()
|
||||||
|
server := startServer(cfg, mux, &wg)
|
||||||
|
|
||||||
// Start the server
|
// Monitor for server restart or shutdown signals.
|
||||||
server := startServer(cfg, mux, &wg)
|
select {
|
||||||
|
case <-restartChan:
|
||||||
select {
|
log.Println("Restarting server...")
|
||||||
case <-restartChan:
|
server.Close()
|
||||||
log.Println("Restarting server...")
|
wg.Wait()
|
||||||
server.Close() // Stop the current server instance
|
time.Sleep(3 * time.Second)
|
||||||
wg.Wait() // Wait for the server goroutine to finish
|
case <-signalChan:
|
||||||
time.Sleep(3 * time.Second)
|
log.Println("Shutting down server...")
|
||||||
|
server.Close()
|
||||||
case <-signalChan:
|
mongo.DisconnectDB(client)
|
||||||
log.Println("Shutting down server...")
|
wg.Wait()
|
||||||
server.Close() // Stop the server
|
return
|
||||||
mongo.DisconnectDB(client) // Disconnect from MongoDB
|
}
|
||||||
wg.Wait() // Wait for all goroutines to finish
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupRoutes() *http.ServeMux {
|
func setupRoutes() *http.ServeMux {
|
||||||
|
@ -2,6 +2,7 @@ package mongo
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"grain/config"
|
||||||
types "grain/config/types"
|
types "grain/config/types"
|
||||||
"grain/server/utils"
|
"grain/server/utils"
|
||||||
"log"
|
"log"
|
||||||
@ -10,6 +11,7 @@ import (
|
|||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// PurgeOldEvents removes old events based on the configuration and a list of whitelisted pubkeys.
|
||||||
func PurgeOldEvents(cfg *types.EventPurgeConfig, whitelist []string) {
|
func PurgeOldEvents(cfg *types.EventPurgeConfig, whitelist []string) {
|
||||||
if !cfg.Enabled {
|
if !cfg.Enabled {
|
||||||
return
|
return
|
||||||
@ -21,10 +23,12 @@ func PurgeOldEvents(cfg *types.EventPurgeConfig, whitelist []string) {
|
|||||||
// Calculate the cutoff time
|
// Calculate the cutoff time
|
||||||
cutoff := time.Now().AddDate(0, 0, -cfg.KeepDurationDays).Unix()
|
cutoff := time.Now().AddDate(0, 0, -cfg.KeepDurationDays).Unix()
|
||||||
|
|
||||||
|
// Create the filter for purging old events
|
||||||
filter := bson.M{
|
filter := bson.M{
|
||||||
"created_at": bson.M{"$lt": cutoff}, // Filter older events
|
"created_at": bson.M{"$lt": cutoff}, // Filter older events
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exclude whitelisted pubkeys if specified in the config
|
||||||
if cfg.ExcludeWhitelisted && len(whitelist) > 0 {
|
if cfg.ExcludeWhitelisted && len(whitelist) > 0 {
|
||||||
filter["pubkey"] = bson.M{"$nin": whitelist} // Exclude whitelisted pubkeys
|
filter["pubkey"] = bson.M{"$nin": whitelist} // Exclude whitelisted pubkeys
|
||||||
}
|
}
|
||||||
@ -52,7 +56,6 @@ func PurgeOldEvents(cfg *types.EventPurgeConfig, whitelist []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Example of a periodic purging task
|
|
||||||
// ScheduleEventPurging runs the event purging at a configurable interval.
|
// ScheduleEventPurging runs the event purging at a configurable interval.
|
||||||
func ScheduleEventPurging(cfg *types.ServerConfig) {
|
func ScheduleEventPurging(cfg *types.ServerConfig) {
|
||||||
// Use the purge interval from the configuration
|
// Use the purge interval from the configuration
|
||||||
@ -61,22 +64,33 @@ func ScheduleEventPurging(cfg *types.ServerConfig) {
|
|||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
whitelist := getWhitelistedPubKeys(cfg)
|
// Fetch the whitelisted pubkeys without passing cfg directly
|
||||||
|
whitelist := getWhitelistedPubKeys()
|
||||||
PurgeOldEvents(&cfg.EventPurge, whitelist)
|
PurgeOldEvents(&cfg.EventPurge, whitelist)
|
||||||
|
log.Printf("Purged old events, keeping whitelisted pubkeys: %v", whitelist)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch whitelisted pubkeys from both the config and any additional domains.
|
// Fetch whitelisted pubkeys from both the whitelist config and any additional domains.
|
||||||
func getWhitelistedPubKeys(cfg *types.ServerConfig) []string {
|
func getWhitelistedPubKeys() []string {
|
||||||
whitelistedPubkeys := cfg.PubkeyWhitelist.Pubkeys
|
// Get the whitelist configuration
|
||||||
|
whitelistCfg := config.GetWhitelistConfig()
|
||||||
|
if whitelistCfg == nil {
|
||||||
|
log.Println("whitelistCfg is nil, returning an empty list of whitelisted pubkeys.")
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start with the statically defined pubkeys
|
||||||
|
whitelistedPubkeys := whitelistCfg.PubkeyWhitelist.Pubkeys
|
||||||
|
|
||||||
// Fetch pubkeys from domains if domain whitelist is enabled
|
// Fetch pubkeys from domains if domain whitelist is enabled
|
||||||
if cfg.DomainWhitelist.Enabled {
|
if whitelistCfg.DomainWhitelist.Enabled {
|
||||||
domains := cfg.DomainWhitelist.Domains
|
domains := whitelistCfg.DomainWhitelist.Domains
|
||||||
pubkeys, err := utils.FetchPubkeysFromDomains(domains)
|
pubkeys, err := utils.FetchPubkeysFromDomains(domains)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error fetching pubkeys from domains: %v", err)
|
log.Printf("Error fetching pubkeys from domains: %v", err)
|
||||||
return whitelistedPubkeys // Return existing whitelisted pubkeys in case of error
|
// Return the existing statically whitelisted pubkeys in case of an error
|
||||||
|
return whitelistedPubkeys
|
||||||
}
|
}
|
||||||
// Append fetched pubkeys from domains to the whitelisted pubkeys
|
// Append fetched pubkeys from domains to the whitelisted pubkeys
|
||||||
whitelistedPubkeys = append(whitelistedPubkeys, pubkeys...)
|
whitelistedPubkeys = append(whitelistedPubkeys, pubkeys...)
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"grain/config"
|
"grain/config"
|
||||||
"grain/server/db/mongo"
|
"grain/server/db/mongo"
|
||||||
|
"time"
|
||||||
|
|
||||||
"grain/server/handlers/response"
|
"grain/server/handlers/response"
|
||||||
"grain/server/utils"
|
"grain/server/utils"
|
||||||
@ -16,7 +17,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func HandleEvent(ws *websocket.Conn, message []interface{}) {
|
func HandleEvent(ws *websocket.Conn, message []interface{}) {
|
||||||
|
|
||||||
if len(message) != 2 {
|
if len(message) != 2 {
|
||||||
fmt.Println("Invalid EVENT message format")
|
fmt.Println("Invalid EVENT message format")
|
||||||
response.SendNotice(ws, "", "Invalid EVENT message format")
|
response.SendNotice(ws, "", "Invalid EVENT message format")
|
||||||
@ -44,13 +44,19 @@ func HandleEvent(ws *websocket.Conn, message []interface{}) {
|
|||||||
return
|
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
|
// Signature check moved here
|
||||||
if !utils.CheckSignature(evt) {
|
if !utils.CheckSignature(evt) {
|
||||||
response.SendOK(ws, evt.ID, false, "invalid: signature verification failed")
|
response.SendOK(ws, evt.ID, false, "invalid: signature verification failed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
eventSize := len(eventBytes) // Calculate event size
|
eventSize := len(eventBytes)
|
||||||
|
|
||||||
if !handleBlacklistAndWhitelist(ws, evt) {
|
if !handleBlacklistAndWhitelist(ws, evt) {
|
||||||
return
|
return
|
||||||
@ -60,45 +66,120 @@ func HandleEvent(ws *websocket.Conn, message []interface{}) {
|
|||||||
return
|
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)
|
mongo.StoreMongoEvent(context.TODO(), evt, ws)
|
||||||
|
|
||||||
fmt.Println("Event processed:", evt.ID)
|
fmt.Println("Event processed:", evt.ID)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleBlacklistAndWhitelist(ws *websocket.Conn, evt nostr.Event) bool {
|
// Validate event timestamps against the configured min and max values
|
||||||
if config.GetConfig().DomainWhitelist.Enabled {
|
func validateEventTimestamp(evt nostr.Event) bool {
|
||||||
domains := config.GetConfig().DomainWhitelist.Domains
|
cfg := config.GetConfig()
|
||||||
pubkeys, err := utils.FetchPubkeysFromDomains(domains)
|
if cfg == nil {
|
||||||
if err != nil {
|
fmt.Println("Server configuration is not loaded")
|
||||||
fmt.Println("Error fetching pubkeys from domains:", err)
|
|
||||||
response.SendNotice(ws, "", "Error fetching pubkeys from domains")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, pubkey := range pubkeys {
|
|
||||||
config.GetConfig().PubkeyWhitelist.Pubkeys = append(config.GetConfig().PubkeyWhitelist.Pubkeys, pubkey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if blacklisted, msg := config.CheckBlacklist(evt.PubKey, evt.Content); blacklisted {
|
|
||||||
response.SendOK(ws, evt.ID, false, msg)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.GetConfig().KindWhitelist.Enabled && !config.IsKindWhitelisted(evt.Kind) {
|
// Use current time for max and a fixed date for min if not specified
|
||||||
response.SendOK(ws, evt.ID, false, "not allowed: event kind is not whitelisted")
|
now := time.Now().Unix()
|
||||||
return false
|
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.GetConfig().PubkeyWhitelist.Enabled && !config.IsPubKeyWhitelisted(evt.PubKey) {
|
maxCreatedAt := cfg.EventTimeConstraints.MaxCreatedAt
|
||||||
response.SendOK(ws, evt.ID, false, "not allowed: pubkey or npub is not whitelisted")
|
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 false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleBlacklistAndWhitelist(ws *websocket.Conn, evt nostr.Event) bool {
|
||||||
|
// Get the current whitelist configuration
|
||||||
|
whitelistCfg := config.GetWhitelistConfig()
|
||||||
|
if whitelistCfg == nil {
|
||||||
|
fmt.Println("Whitelist configuration is not loaded.")
|
||||||
|
response.SendNotice(ws, "", "Internal server error: whitelist configuration is missing")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If domain whitelisting is enabled, dynamically fetch pubkeys from domains
|
||||||
|
if whitelistCfg.DomainWhitelist.Enabled {
|
||||||
|
domains := whitelistCfg.DomainWhitelist.Domains
|
||||||
|
pubkeys, err := utils.FetchPubkeysFromDomains(domains)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error fetching pubkeys from domains:", err)
|
||||||
|
response.SendNotice(ws, "", "Error fetching pubkeys from domains")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Update the whitelisted pubkeys dynamically
|
||||||
|
whitelistCfg.PubkeyWhitelist.Pubkeys = append(whitelistCfg.PubkeyWhitelist.Pubkeys, pubkeys...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the event's pubkey or content is blacklisted
|
||||||
|
if blacklisted, msg := config.CheckBlacklist(evt.PubKey, evt.Content); blacklisted {
|
||||||
|
response.SendOK(ws, evt.ID, false, msg)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check mutelist blacklist
|
||||||
|
cfg := config.GetConfig()
|
||||||
|
if cfg == nil {
|
||||||
|
fmt.Println("Server configuration is not loaded")
|
||||||
|
response.SendNotice(ws, "", "Internal server error: server configuration is missing")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
blacklistCfg := config.GetBlacklistConfig()
|
||||||
|
if blacklistCfg == nil {
|
||||||
|
fmt.Println("Blacklist configuration is not loaded")
|
||||||
|
response.SendNotice(ws, "", "Internal server error: blacklist configuration is missing")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only proceed if there are mutelist event IDs specified
|
||||||
|
if len(blacklistCfg.MuteListAuthors) > 0 {
|
||||||
|
localRelayURL := fmt.Sprintf("ws://localhost%s", cfg.Server.Port)
|
||||||
|
mutelistedPubkeys, err := config.FetchPubkeysFromLocalMuteList(localRelayURL, blacklistCfg.MuteListAuthors)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error fetching pubkeys from mutelist:", err)
|
||||||
|
response.SendNotice(ws, "", "Error fetching pubkeys from mutelist")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, mutelistedPubkey := range mutelistedPubkeys {
|
||||||
|
if evt.PubKey == mutelistedPubkey {
|
||||||
|
response.SendOK(ws, evt.ID, false, "not allowed: pubkey is in mutelist")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println("No mutelist event IDs specified in the blacklist configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the event's kind is whitelisted
|
||||||
|
if whitelistCfg.KindWhitelist.Enabled && !config.IsKindWhitelisted(evt.Kind) {
|
||||||
|
response.SendOK(ws, evt.ID, false, "not allowed: event kind is not whitelisted")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the event's pubkey is whitelisted
|
||||||
|
if whitelistCfg.PubkeyWhitelist.Enabled && !config.IsPubKeyWhitelisted(evt.PubKey) {
|
||||||
|
response.SendOK(ws, evt.ID, false, "not allowed: pubkey or npub is not whitelisted")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func handleRateAndSizeLimits(ws *websocket.Conn, evt nostr.Event, eventSize int) bool {
|
func handleRateAndSizeLimits(ws *websocket.Conn, evt nostr.Event, eventSize int) bool {
|
||||||
rateLimiter := config.GetRateLimiter()
|
rateLimiter := config.GetRateLimiter()
|
||||||
sizeLimiter := config.GetSizeLimiter()
|
sizeLimiter := config.GetSizeLimiter()
|
||||||
|
43
server/utils/adjustTimeContraints.go
Normal file
43
server/utils/adjustTimeContraints.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
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 based on string value or default to January 1, 2020
|
||||||
|
if strings.HasPrefix(cfg.EventTimeConstraints.MinCreatedAtString, "now") {
|
||||||
|
offset := strings.TrimPrefix(cfg.EventTimeConstraints.MinCreatedAtString, "now")
|
||||||
|
duration, err := time.ParseDuration(offset)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Invalid time offset for min_created_at: %s\n", offset)
|
||||||
|
cfg.EventTimeConstraints.MinCreatedAt = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).Unix()
|
||||||
|
} else {
|
||||||
|
cfg.EventTimeConstraints.MinCreatedAt = now.Add(duration).Unix()
|
||||||
|
}
|
||||||
|
} else if cfg.EventTimeConstraints.MinCreatedAt == 0 {
|
||||||
|
// Default to January 1, 2020, if not set
|
||||||
|
cfg.EventTimeConstraints.MinCreatedAt = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust max_created_at based on string value or default to current time
|
||||||
|
if strings.HasPrefix(cfg.EventTimeConstraints.MaxCreatedAtString, "now") {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user