diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index b58b603..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index fc86bbd..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/pkg/sync/cluster.go b/pkg/sync/cluster.go index 576deba..068ab54 100644 --- a/pkg/sync/cluster.go +++ b/pkg/sync/cluster.go @@ -13,6 +13,7 @@ import ( "lol.mleku.dev/log" "next.orly.dev/pkg/database" "next.orly.dev/pkg/database/indexes/types" + "git.mleku.dev/mleku/nostr/crypto/keys" "git.mleku.dev/mleku/nostr/encoders/event" "git.mleku.dev/mleku/nostr/encoders/hex" "git.mleku.dev/mleku/nostr/encoders/kind" @@ -23,6 +24,7 @@ type ClusterManager struct { cancel context.CancelFunc db *database.D adminNpubs []string + relayIdentityPubkey string // Our relay's identity pubkey (hex) members map[string]*ClusterMember // keyed by relay URL membersMux sync.RWMutex pollTicker *time.Ticker @@ -30,6 +32,7 @@ type ClusterManager struct { httpClient *http.Client propagatePrivilegedEvents bool publisher interface{ Deliver(*event.E) } + nip11Cache *NIP11Cache } type ClusterMember struct { @@ -61,11 +64,20 @@ type EventInfo struct { func NewClusterManager(ctx context.Context, db *database.D, adminNpubs []string, propagatePrivilegedEvents bool, publisher interface{ Deliver(*event.E) }) *ClusterManager { ctx, cancel := context.WithCancel(ctx) + // Get our relay identity pubkey + var relayPubkey string + if skb, err := db.GetRelayIdentitySecret(); err == nil && len(skb) == 32 { + if pk, err := keys.SecretBytesToPubKeyHex(skb); err == nil { + relayPubkey = pk + } + } + cm := &ClusterManager{ ctx: ctx, cancel: cancel, db: db, adminNpubs: adminNpubs, + relayIdentityPubkey: relayPubkey, members: make(map[string]*ClusterMember), pollDone: make(chan struct{}), propagatePrivilegedEvents: propagatePrivilegedEvents, @@ -73,6 +85,7 @@ func NewClusterManager(ctx context.Context, db *database.D, adminNpubs []string, httpClient: &http.Client{ Timeout: 30 * time.Second, }, + nip11Cache: NewNIP11Cache(30 * time.Minute), } return cm @@ -254,6 +267,12 @@ func (cm *ClusterManager) UpdateMembership(relayURLs []string) { // Add new members for _, url := range relayURLs { + // Skip if this is our own relay (check via NIP-11 pubkey) + if cm.isSelfRelay(url) { + log.D.F("skipping cluster member (self): %s (pubkey matches our relay identity)", url) + continue + } + if _, exists := cm.members[url]; !exists { // For simplicity, assume HTTP and WebSocket URLs are the same // In practice, you'd need to parse these properly @@ -269,6 +288,25 @@ func (cm *ClusterManager) UpdateMembership(relayURLs []string) { } } +// isSelfRelay checks if a relay URL is actually ourselves by comparing NIP-11 pubkeys +func (cm *ClusterManager) isSelfRelay(relayURL string) bool { + // If we don't have a relay identity pubkey, can't compare + if cm.relayIdentityPubkey == "" { + return false + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + peerPubkey, err := cm.nip11Cache.GetPubkey(ctx, relayURL) + if err != nil { + log.D.F("couldn't fetch NIP-11 for %s to check if self: %v", relayURL, err) + return false + } + + return peerPubkey == cm.relayIdentityPubkey +} + // HandleMembershipEvent processes a cluster membership event (Kind 39108) func (cm *ClusterManager) HandleMembershipEvent(event *event.E) error { // Verify the event is signed by a cluster admin @@ -313,6 +351,21 @@ func (cm *ClusterManager) HandleLatestSerial(w http.ResponseWriter, r *http.Requ return } + // Check if request is from ourselves by examining the Referer or Origin header + origin := r.Header.Get("Origin") + referer := r.Header.Get("Referer") + + if origin != "" && cm.isSelfRelay(origin) { + log.D.F("rejecting cluster latest request from self (origin: %s)", origin) + http.Error(w, "Cannot sync with self", http.StatusBadRequest) + return + } + if referer != "" && cm.isSelfRelay(referer) { + log.D.F("rejecting cluster latest request from self (referer: %s)", referer) + http.Error(w, "Cannot sync with self", http.StatusBadRequest) + return + } + // Get the latest serial from database by querying for the highest serial latestSerial, err := cm.getLatestSerialFromDB() if err != nil { @@ -336,6 +389,21 @@ func (cm *ClusterManager) HandleEventsRange(w http.ResponseWriter, r *http.Reque return } + // Check if request is from ourselves by examining the Referer or Origin header + origin := r.Header.Get("Origin") + referer := r.Header.Get("Referer") + + if origin != "" && cm.isSelfRelay(origin) { + log.D.F("rejecting cluster events request from self (origin: %s)", origin) + http.Error(w, "Cannot sync with self", http.StatusBadRequest) + return + } + if referer != "" && cm.isSelfRelay(referer) { + log.D.F("rejecting cluster events request from self (referer: %s)", referer) + http.Error(w, "Cannot sync with self", http.StatusBadRequest) + return + } + // Parse query parameters fromStr := r.URL.Query().Get("from") toStr := r.URL.Query().Get("to") diff --git a/pkg/sync/manager.go b/pkg/sync/manager.go index 0cf43fc..9bf8370 100644 --- a/pkg/sync/manager.go +++ b/pkg/sync/manager.go @@ -173,12 +173,36 @@ func (m *Manager) syncRoutine() { // syncWithPeersSequentially syncs with all configured peers one at a time func (m *Manager) syncWithPeersSequentially() { for _, peerURL := range m.peers { + // Check if this peer is ourselves via NIP-11 pubkey + if m.isSelfPeer(peerURL) { + log.D.F("skipping sync with self: %s (pubkey matches our relay identity)", peerURL) + continue + } m.syncWithPeer(peerURL) // Small delay between peers to avoid overwhelming time.Sleep(100 * time.Millisecond) } } +// isSelfPeer checks if a peer URL is actually ourselves by comparing NIP-11 pubkeys +func (m *Manager) isSelfPeer(peerURL string) bool { + // If we don't have a nodeID, can't compare + if m.nodeID == "" { + return false + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + peerPubkey, err := m.nip11Cache.GetPubkey(ctx, peerURL) + if err != nil { + log.D.F("couldn't fetch NIP-11 for %s to check if self: %v", peerURL, err) + return false + } + + return peerPubkey == m.nodeID +} + // syncWithPeer syncs with a specific peer func (m *Manager) syncWithPeer(peerURL string) { // Get the peer's current serial @@ -390,6 +414,13 @@ func (m *Manager) HandleCurrentRequest(w http.ResponseWriter, r *http.Request) { return } + // Reject requests from ourselves (same nodeID) + if req.NodeID != "" && req.NodeID == m.nodeID { + log.D.F("rejecting sync current request from self (nodeID: %s)", req.NodeID) + http.Error(w, "Cannot sync with self", http.StatusBadRequest) + return + } + resp := CurrentResponse{ NodeID: m.nodeID, RelayURL: m.relayURL, @@ -413,6 +444,13 @@ func (m *Manager) HandleEventIDsRequest(w http.ResponseWriter, r *http.Request) return } + // Reject requests from ourselves (same nodeID) + if req.NodeID != "" && req.NodeID == m.nodeID { + log.D.F("rejecting sync event-ids request from self (nodeID: %s)", req.NodeID) + http.Error(w, "Cannot sync with self", http.StatusBadRequest) + return + } + // Get events with IDs in the requested range eventMap, err := m.getEventsWithIDs(req.From, req.To) if err != nil { diff --git a/pkg/version/version b/pkg/version/version index e0954ee..f96e4b5 100644 --- a/pkg/version/version +++ b/pkg/version/version @@ -1 +1 @@ -v0.29.17 \ No newline at end of file +v0.29.18 \ No newline at end of file