implement nip-42 AUTH and restrict DMs to authed users.

This commit is contained in:
fiatjaf 2023-01-15 22:38:24 -03:00
parent e70a5601c7
commit fe91197d45
No known key found for this signature in database
GPG Key ID: BAD43C4BE5C1A3A1
5 changed files with 64 additions and 6 deletions

4
go.mod
View File

@ -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

4
go.sum
View File

@ -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=

View File

@ -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: "~",
}

View File

@ -29,6 +29,10 @@ type Relay interface {
Storage() Storage
}
type Auther interface {
ServiceURL() string
}
type Injector interface {
InjectEvents() chan nostr.Event
}

View File

@ -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 {