diff --git a/go.mod b/go.mod index 8b3d066..0bc0240 100644 --- a/go.mod +++ b/go.mod @@ -13,11 +13,12 @@ require ( github.com/kelseyhightower/envconfig v1.4.0 github.com/lib/pq v1.10.3 github.com/mmcdole/gofeed v1.1.3 - github.com/nbd-wtf/go-nostr v0.10.0 + github.com/nbd-wtf/go-nostr v0.11.1 github.com/rif/cache2go v1.0.0 github.com/rs/cors v1.7.0 github.com/stevelacy/daz v0.1.4 github.com/tidwall/gjson v1.14.1 + golang.org/x/exp v0.0.0-20221106115401-f9659909a136 ) require ( @@ -74,7 +75,6 @@ require ( github.com/tidwall/pretty v1.2.0 // indirect github.com/valyala/fastjson v1.6.3 // indirect golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect - golang.org/x/exp v0.0.0-20221106115401-f9659909a136 // indirect golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect golang.org/x/sys v0.1.0 // indirect golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect diff --git a/go.sum b/go.sum index 3b193d0..a26c9df 100644 --- a/go.sum +++ b/go.sum @@ -320,8 +320,8 @@ github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOA github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM= github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/nbd-wtf/go-nostr v0.10.0 h1:95y0VjyplDCInPn7VD2eNvRHwTGib1LxfVKp0Fv2ZQY= -github.com/nbd-wtf/go-nostr v0.10.0/go.mod h1:qFFTIxh15H5GGN0WsBI/P73DteqsevnhSEW/yk8nEf4= +github.com/nbd-wtf/go-nostr v0.11.1 h1:kJn2Q3v1WJ4fvQ1tmwrL2Ke+t/rTj9jlwD3tdUPMgRI= +github.com/nbd-wtf/go-nostr v0.11.1/go.mod h1:qFFTIxh15H5GGN0WsBI/P73DteqsevnhSEW/yk8nEf4= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nwaples/rardecode v1.1.2 h1:Cj0yZY6T1Zx1R7AhTbyGSALm44/Mmq+BAPc4B/p/d3M= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= diff --git a/handlers.go b/handlers.go index 7875f20..122e3f5 100644 --- a/handlers.go +++ b/handlers.go @@ -1,6 +1,7 @@ package relayer import ( + "crypto/rand" "crypto/sha256" "encoding/hex" "encoding/json" @@ -11,6 +12,8 @@ import ( "github.com/gorilla/websocket" "github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr/nip11" + "github.com/nbd-wtf/go-nostr/nip42" + "golang.org/x/exp/slices" ) // TODO: consdier moving these to Server as config params @@ -50,7 +53,14 @@ func (s *Server) handleWebsocket(w http.ResponseWriter, r *http.Request) { s.clients[conn] = struct{}{} ticker := time.NewTicker(pingPeriod) - ws := &WebSocket{conn: conn} + // nip-42 challenge + challenge := make([]byte, 8) + rand.Read(challenge) + + ws := &WebSocket{ + conn: conn, + challenge: hex.EncodeToString(challenge), + } // reader go func() { @@ -72,6 +82,11 @@ func (s *Server) handleWebsocket(w http.ResponseWriter, r *http.Request) { return nil }) + // nip42 auth challenge + if _, ok := s.relay.(Auther); ok { + ws.WriteJSON([]interface{}{"AUTH", ws.challenge}) + } + for { typ, message, err := conn.ReadMessage() if err != nil { @@ -183,6 +198,25 @@ func (s *Server) handleWebsocket(w http.ResponseWriter, r *http.Request) { filter := &filters[i] + // prevent kind-4 events from being returned to unauthed users, + // only when authentication is a thing + if _, ok := s.relay.(Auther); ok { + if slices.Contains(filter.Kinds, 4) { + // when fetching kind-4 one must be either on the sending or on the receiving end + senders := filter.Authors + receivers, _ := filter.Tags["e"] + if len(senders) > 1 || len(receivers) > 1 { + notice = "restricted: can't serve kind-4 messages to or from more than one key" + return + } + if (len(senders) == 1 && senders[0] != ws.authed) || + (len(receivers) == 1 && receivers[0] != ws.authed) { + notice = "restricted: can't serve kind-4 to their participants" + return + } + } + } + if advancedQuerier != nil { advancedQuerier.BeforeQuery(filter) } @@ -217,6 +251,17 @@ func (s *Server) handleWebsocket(w http.ResponseWriter, r *http.Request) { removeListenerId(ws, id) break + case "AUTH": + if auther, ok := s.relay.(Auther); ok { + var evt nostr.Event + if err := json.Unmarshal(request[1], &evt); err != nil { + notice = "failed to decode auth event: " + err.Error() + return + } + if pubkey, ok := nip42.ValidateAuthEvent(&evt, ws.challenge, auther.ServiceURL()); ok { + ws.authed = pubkey + } + } default: if cwh, ok := s.relay.(CustomWebSocketHandler); ok { cwh.HandleUnknownType(ws, typ, request) @@ -252,12 +297,17 @@ func (s *Server) handleWebsocket(w http.ResponseWriter, r *http.Request) { func (s *Server) handleNIP11(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") + supportedNIPs := []int{9, 12, 15, 16, 20} + if _, ok := s.relay.(Auther); ok { + supportedNIPs = append(supportedNIPs, 42) + } + info := nip11.RelayInformationDocument{ Name: s.relay.Name(), Description: "relay powered by the relayer framework", PubKey: "~", Contact: "~", - SupportedNIPs: []int{9, 15, 16}, + SupportedNIPs: supportedNIPs, Software: "https://github.com/fiatjaf/relayer", Version: "~", } diff --git a/interface.go b/interface.go index 14742b2..bceefed 100644 --- a/interface.go +++ b/interface.go @@ -29,6 +29,10 @@ type Relay interface { Storage() Storage } +type Auther interface { + ServiceURL() string +} + type Injector interface { InjectEvents() chan nostr.Event } diff --git a/websocket.go b/websocket.go index c2f8165..d8e3603 100644 --- a/websocket.go +++ b/websocket.go @@ -9,6 +9,10 @@ import ( type WebSocket struct { conn *websocket.Conn mutex sync.Mutex + + // nip42 + challenge string + authed string } func (ws *WebSocket) WriteJSON(any interface{}) error {