implement nip-42 AUTH and restrict DMs to authed users.
This commit is contained in:
4
go.mod
4
go.mod
@@ -13,11 +13,12 @@ require (
|
|||||||
github.com/kelseyhightower/envconfig v1.4.0
|
github.com/kelseyhightower/envconfig v1.4.0
|
||||||
github.com/lib/pq v1.10.3
|
github.com/lib/pq v1.10.3
|
||||||
github.com/mmcdole/gofeed v1.1.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/rif/cache2go v1.0.0
|
||||||
github.com/rs/cors v1.7.0
|
github.com/rs/cors v1.7.0
|
||||||
github.com/stevelacy/daz v0.1.4
|
github.com/stevelacy/daz v0.1.4
|
||||||
github.com/tidwall/gjson v1.14.1
|
github.com/tidwall/gjson v1.14.1
|
||||||
|
golang.org/x/exp v0.0.0-20221106115401-f9659909a136
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -74,7 +75,6 @@ require (
|
|||||||
github.com/tidwall/pretty v1.2.0 // indirect
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
github.com/valyala/fastjson v1.6.3 // indirect
|
github.com/valyala/fastjson v1.6.3 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // 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/net v0.0.0-20211015210444-4f30a5c0130f // indirect
|
||||||
golang.org/x/sys v0.1.0 // indirect
|
golang.org/x/sys v0.1.0 // indirect
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
|
||||||
|
|||||||
4
go.sum
4
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/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/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/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.11.1 h1:kJn2Q3v1WJ4fvQ1tmwrL2Ke+t/rTj9jlwD3tdUPMgRI=
|
||||||
github.com/nbd-wtf/go-nostr v0.10.0/go.mod h1:qFFTIxh15H5GGN0WsBI/P73DteqsevnhSEW/yk8nEf4=
|
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/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/nwaples/rardecode v1.1.2 h1:Cj0yZY6T1Zx1R7AhTbyGSALm44/Mmq+BAPc4B/p/d3M=
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
|
|||||||
54
handlers.go
54
handlers.go
@@ -1,6 +1,7 @@
|
|||||||
package relayer
|
package relayer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -11,6 +12,8 @@ import (
|
|||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
"github.com/nbd-wtf/go-nostr/nip11"
|
"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
|
// 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{}{}
|
s.clients[conn] = struct{}{}
|
||||||
ticker := time.NewTicker(pingPeriod)
|
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
|
// reader
|
||||||
go func() {
|
go func() {
|
||||||
@@ -72,6 +82,11 @@ func (s *Server) handleWebsocket(w http.ResponseWriter, r *http.Request) {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// nip42 auth challenge
|
||||||
|
if _, ok := s.relay.(Auther); ok {
|
||||||
|
ws.WriteJSON([]interface{}{"AUTH", ws.challenge})
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
typ, message, err := conn.ReadMessage()
|
typ, message, err := conn.ReadMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -183,6 +198,25 @@ func (s *Server) handleWebsocket(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
filter := &filters[i]
|
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 {
|
if advancedQuerier != nil {
|
||||||
advancedQuerier.BeforeQuery(filter)
|
advancedQuerier.BeforeQuery(filter)
|
||||||
}
|
}
|
||||||
@@ -217,6 +251,17 @@ func (s *Server) handleWebsocket(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
removeListenerId(ws, id)
|
removeListenerId(ws, id)
|
||||||
break
|
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:
|
default:
|
||||||
if cwh, ok := s.relay.(CustomWebSocketHandler); ok {
|
if cwh, ok := s.relay.(CustomWebSocketHandler); ok {
|
||||||
cwh.HandleUnknownType(ws, typ, request)
|
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) {
|
func (s *Server) handleNIP11(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
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{
|
info := nip11.RelayInformationDocument{
|
||||||
Name: s.relay.Name(),
|
Name: s.relay.Name(),
|
||||||
Description: "relay powered by the relayer framework",
|
Description: "relay powered by the relayer framework",
|
||||||
PubKey: "~",
|
PubKey: "~",
|
||||||
Contact: "~",
|
Contact: "~",
|
||||||
SupportedNIPs: []int{9, 15, 16},
|
SupportedNIPs: supportedNIPs,
|
||||||
Software: "https://github.com/fiatjaf/relayer",
|
Software: "https://github.com/fiatjaf/relayer",
|
||||||
Version: "~",
|
Version: "~",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ type Relay interface {
|
|||||||
Storage() Storage
|
Storage() Storage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Auther interface {
|
||||||
|
ServiceURL() string
|
||||||
|
}
|
||||||
|
|
||||||
type Injector interface {
|
type Injector interface {
|
||||||
InjectEvents() chan nostr.Event
|
InjectEvents() chan nostr.Event
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ import (
|
|||||||
type WebSocket struct {
|
type WebSocket struct {
|
||||||
conn *websocket.Conn
|
conn *websocket.Conn
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
|
|
||||||
|
// nip42
|
||||||
|
challenge string
|
||||||
|
authed string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *WebSocket) WriteJSON(any interface{}) error {
|
func (ws *WebSocket) WriteJSON(any interface{}) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user