Compare commits

...

3 Commits

Author SHA1 Message Date
fiatjaf
583f712fe4 admin: normalize urls for nip86 checking. 2025-04-17 08:02:25 -03:00
Jon Staab
28b1061166 Reject deleted events 2025-04-16 18:55:36 -03:00
Jon Staab
25f19ce46e Store and serve delete events 2025-04-16 18:55:28 -03:00
4 changed files with 109 additions and 29 deletions

View File

@@ -33,6 +33,23 @@ func (rl *Relay) handleNormal(ctx context.Context, evt *nostr.Event) (skipBroadc
}
}
// Check to see if the event has been deleted by ID
for _, query := range rl.QueryEvents {
ch, err := query(ctx, nostr.Filter{
Kinds: []int{5},
Tags: nostr.TagMap{"#e": []string{evt.ID}},
})
if err != nil {
continue
}
target := <-ch
if target == nil {
continue
}
return true, errors.New("blocked: this event has been deleted")
}
// will store
// regular kinds are just saved directly
if nostr.IsRegularKind(evt.Kind) {
@@ -47,6 +64,33 @@ func (rl *Relay) handleNormal(ctx context.Context, evt *nostr.Event) (skipBroadc
}
}
} else {
// Check to see if the event has been deleted by address
for _, query := range rl.QueryEvents {
dTagValue := ""
for _, tag := range evt.Tags {
if len(tag) > 0 && tag[0] == "d" {
dTagValue = tag[1]
break
}
}
address := fmt.Sprintf("%d:%s:%s", evt.Kind, evt.PubKey, dTagValue)
ch, err := query(ctx, nostr.Filter{
Kinds: []int{5},
Since: &evt.CreatedAt,
Tags: nostr.TagMap{"#a": []string{address}},
})
if err != nil {
continue
}
target := <-ch
if target == nil {
continue
}
return true, errors.New("blocked: this event has been deleted")
}
// otherwise it's a replaceable -- so we'll use the replacer functions if we have any
if len(rl.ReplaceEvent) > 0 {
for _, repl := range rl.ReplaceEvent {

View File

@@ -217,12 +217,16 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
if env.Event.Kind == 5 {
// this always returns "blocked: " whenever it returns an error
writeErr = srl.handleDeleteRequest(ctx, &env.Event)
} else if nostr.IsEphemeralKind(env.Event.Kind) {
// this will also always return a prefixed reason
writeErr = srl.handleEphemeral(ctx, &env.Event)
} else {
// this will also always return a prefixed reason
skipBroadcast, writeErr = srl.handleNormal(ctx, &env.Event)
}
if writeErr == nil {
if nostr.IsEphemeralKind(env.Event.Kind) {
// this will also always return a prefixed reason
writeErr = srl.handleEphemeral(ctx, &env.Event)
} else {
// this will also always return a prefixed reason
skipBroadcast, writeErr = srl.handleNormal(ctx, &env.Event)
}
}
var reason string

View File

@@ -86,8 +86,9 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) {
goto respond
}
if uTag := evt.Tags.Find("u"); uTag == nil || rl.getBaseURL(r) != uTag[1] {
resp.Error = "invalid 'u' tag"
if uTag := evt.Tags.Find("u"); uTag == nil || nostr.NormalizeURL(rl.getBaseURL(r)) != nostr.NormalizeURL(uTag[1]) {
resp.Error = fmt.Sprintf("invalid 'u' tag, got '%s', expected '%s'",
nostr.NormalizeURL(rl.getBaseURL(r)), nostr.NormalizeURL(uTag[1]))
goto respond
} else if pht := evt.Tags.FindWithValue("payload", hex.EncodeToString(payloadHash[:])); pht == nil {
resp.Error = "invalid auth event payload hash"

View File

@@ -151,33 +151,64 @@ func TestBasicRelayFunctionality(t *testing.T) {
t.Fatalf("failed to publish deletion event: %v", err)
}
// Try to query the deleted event
sub, err := client2.Subscribe(ctx, []nostr.Filter{{
IDs: []string{evt3.ID},
}})
if err != nil {
t.Fatalf("failed to subscribe: %v", err)
}
defer sub.Unsub()
{
// Try to query the deleted event
sub, err := client2.Subscribe(ctx, []nostr.Filter{{
IDs: []string{evt3.ID},
}})
if err != nil {
t.Fatalf("failed to subscribe: %v", err)
}
defer sub.Unsub()
// Should get EOSE without receiving the deleted event
gotEvent := false
for {
select {
case <-sub.Events:
gotEvent = true
case <-sub.EndOfStoredEvents:
if gotEvent {
t.Error("should not have received deleted event")
// Should get EOSE without receiving the deleted event
gotEvent := false
DeletedLoop:
for {
select {
case <-sub.Events:
gotEvent = true
case <-sub.EndOfStoredEvents:
if gotEvent {
t.Error("should not have received deleted event")
}
break DeletedLoop
case <-ctx.Done():
t.Fatal("timeout waiting for EOSE")
}
}
}
{
// Try to query the deletion itself
sub, err := client2.Subscribe(ctx, []nostr.Filter{{
Kinds: []int{5},
}})
if err != nil {
t.Fatalf("failed to subscribe: %v", err)
}
defer sub.Unsub()
// Should get EOSE without receiving the deleted event
gotEvent := false
DeletionLoop:
for {
select {
case <-sub.Events:
gotEvent = true
case <-sub.EndOfStoredEvents:
if !gotEvent {
t.Error("should have received deletion event")
}
break DeletionLoop
case <-ctx.Done():
t.Fatal("timeout waiting for EOSE")
}
return
case <-ctx.Done():
t.Fatal("timeout waiting for EOSE")
}
}
})
// test 4: teplaceable events
// test 4: replaceable events
t.Run("replaceable events", func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()