Compare commits

...

6 Commits

Author SHA1 Message Date
fiatjaf
8d0afc1f12 invalidated filters just return nothing instead of erroring. 2024-01-05 20:48:44 -03:00
fiatjaf
40c3dbdc76 add relay.BroadcastEvent() and rename files. 2024-01-01 17:12:10 -03:00
fiatjaf
e876415677 remove unused .OnAuth() and update README example. 2023-12-28 09:17:06 -03:00
fiatjaf
b00e5b2b3f only reset ws.Authed if it's nil.
i.e. if there has been an auth and for some reason the client tried to auth again
after RequestAuth() has been called again.
2023-12-27 13:05:31 -03:00
fiatjaf
0f7d26f26e missed from last commit: setting ws.Authed to nil. 2023-12-27 12:55:05 -03:00
fiatjaf
21b08cb044 fix closing of closed ws.Authed channel when client AUTHs twice. 2023-12-27 12:30:23 -03:00
10 changed files with 92 additions and 63 deletions

View File

@@ -76,16 +76,17 @@ func main() {
return false, "" // anyone else can
},
)
relay.OnConnect = append(relay.OnConnect,
func(ctx context.Context) {
// request NIP-42 AUTH from everybody
khatru.RequestAuth(ctx)
},
)
relay.OnAuth = append(relay.OnAuth,
func(ctx context.Context, pubkey string) {
// and when they auth we just log that for nothing
log.Println(pubkey + " is authed!")
// you can request auth by rejecting an event or a request with the prefix "auth-required: "
relay.RejectFilter = append(relay.RejectFilter,
func(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
if pubkey := khatru.GetAuthed(ctx); pubkey != "" {
log.Printf("request from %s\n", pubkey)
return false, ""
}
return true, "auth-required: only authenticated users can read from this relay"
// (this will cause an AUTH message to be sent and then a CLOSED message such that clients can
// authenticate and then request again)
},
)
// check the docs for more goodies!

View File

@@ -9,6 +9,7 @@ import (
"github.com/nbd-wtf/go-nostr"
)
// AddEvent sends an event through then normal add pipeline, as if it was received from a websocket.
func (rl *Relay) AddEvent(ctx context.Context, evt *nostr.Event) error {
if evt == nil {
return errors.New("error: event is nil")
@@ -82,46 +83,3 @@ func (rl *Relay) AddEvent(ctx context.Context, evt *nostr.Event) error {
return nil
}
func (rl *Relay) handleDeleteRequest(ctx context.Context, evt *nostr.Event) error {
// event deletion -- nip09
for _, tag := range evt.Tags {
if len(tag) >= 2 && tag[0] == "e" {
// first we fetch the event
for _, query := range rl.QueryEvents {
ch, err := query(ctx, nostr.Filter{IDs: []string{tag[1]}})
if err != nil {
continue
}
target := <-ch
if target == nil {
continue
}
// got the event, now check if the user can delete it
acceptDeletion := target.PubKey == evt.PubKey
var msg string
if acceptDeletion == false {
msg = "you are not the author of this event"
}
// but if we have a function to overwrite this outcome, use that instead
for _, odo := range rl.OverwriteDeletionOutcome {
acceptDeletion, msg = odo(ctx, target, evt)
}
if acceptDeletion {
// delete it
for _, del := range rl.DeleteEvent {
del(ctx, target)
}
} else {
// fail and stop here
return fmt.Errorf("blocked: %s", msg)
}
// don't try to query this same event again
break
}
}
}
return nil
}

11
broadcasting.go Normal file
View File

@@ -0,0 +1,11 @@
package khatru
import (
"github.com/nbd-wtf/go-nostr"
)
// BroadcastEvent emits an event to all listeners whose filters' match, skipping all filters and actions
// it also doesn't attempt to store the event or trigger any reactions or callbacks
func (rl *Relay) BroadcastEvent(evt *nostr.Event) {
notifyListeners(evt)
}

51
deleting.go Normal file
View File

@@ -0,0 +1,51 @@
package khatru
import (
"context"
"fmt"
"github.com/nbd-wtf/go-nostr"
)
func (rl *Relay) handleDeleteRequest(ctx context.Context, evt *nostr.Event) error {
// event deletion -- nip09
for _, tag := range evt.Tags {
if len(tag) >= 2 && tag[0] == "e" {
// first we fetch the event
for _, query := range rl.QueryEvents {
ch, err := query(ctx, nostr.Filter{IDs: []string{tag[1]}})
if err != nil {
continue
}
target := <-ch
if target == nil {
continue
}
// got the event, now check if the user can delete it
acceptDeletion := target.PubKey == evt.PubKey
var msg string
if acceptDeletion == false {
msg = "you are not the author of this event"
}
// but if we have a function to overwrite this outcome, use that instead
for _, odo := range rl.OverwriteDeletionOutcome {
acceptDeletion, msg = odo(ctx, target, evt)
}
if acceptDeletion {
// delete it
for _, del := range rl.DeleteEvent {
del(ctx, target)
}
} else {
// fail and stop here
return fmt.Errorf("blocked: %s", msg)
}
// don't try to query this same event again
break
}
}
}
return nil
}

View File

@@ -69,12 +69,8 @@ func main() {
return false, ""
}
return true, "auth-required: only authenticated users can read from this relay"
},
)
relay.OnAuth = append(relay.OnAuth,
func(ctx context.Context, pubkey string) {
// and when they auth we can just log that for nothing
log.Println(pubkey + " is authed!")
// (this will cause an AUTH message to be sent and then a CLOSED message such that clients can
// authenticate and then request again)
},
)
// check the docs for more goodies!

View File

@@ -50,7 +50,6 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
conn: conn,
Request: r,
Challenge: hex.EncodeToString(challenge),
Authed: make(chan struct{}),
}
ctx, cancel := context.WithCancel(
@@ -204,7 +203,12 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
wsBaseUrl := strings.Replace(rl.ServiceURL, "http", "ws", 1)
if pubkey, ok := nip42.ValidateAuthEvent(&env.Event, ws.Challenge, wsBaseUrl); ok {
ws.AuthedPublicKey = pubkey
close(ws.Authed)
ws.authLock.Lock()
if ws.Authed != nil {
close(ws.Authed)
ws.Authed = nil
}
ws.authLock.Unlock()
ws.WriteJSON(nostr.OKEnvelope{EventID: env.Event.ID, OK: true})
} else {
ws.WriteJSON(nostr.OKEnvelope{EventID: env.Event.ID, OK: false, Reason: "error: failed to authenticate"})

View File

@@ -54,7 +54,6 @@ type Relay struct {
DeleteEvent []func(ctx context.Context, event *nostr.Event) error
QueryEvents []func(ctx context.Context, filter nostr.Filter) (chan *nostr.Event, error)
CountEvents []func(ctx context.Context, filter nostr.Filter) (int64, error)
OnAuth []func(ctx context.Context, pubkey string)
OnConnect []func(ctx context.Context)
OnDisconnect []func(ctx context.Context)
OnEventSaved []func(ctx context.Context, event *nostr.Event)

View File

@@ -18,7 +18,9 @@ func (rl *Relay) handleRequest(ctx context.Context, id string, eose *sync.WaitGr
}
if filter.Limit < 0 {
return errors.New("blocked: filter invalidated")
// this is a special situation through which the implementor signals to us that it doesn't want
// to event perform any queries whatsoever
return nil
}
// then check if we'll reject this filter (we apply this after overwriting

View File

@@ -14,6 +14,11 @@ const (
func RequestAuth(ctx context.Context) {
ws := GetConnection(ctx)
ws.authLock.Lock()
if ws.Authed == nil {
ws.Authed = make(chan struct{})
}
ws.authLock.Unlock()
ws.WriteJSON(nostr.AuthEnvelope{Challenge: &ws.Challenge})
}

View File

@@ -18,6 +18,8 @@ type WebSocket struct {
Challenge string
AuthedPublicKey string
Authed chan struct{}
authLock sync.Mutex
}
func (ws *WebSocket) WriteJSON(any any) error {