diff --git a/config/config.go b/config/config.go index 06984fe..e86f115 100644 --- a/config/config.go +++ b/config/config.go @@ -8,6 +8,7 @@ import ( "github.com/dezh-tech/immortal/server/http" "github.com/dezh-tech/immortal/server/websocket" "github.com/dezh-tech/immortal/types/nip11" + "github.com/dezh-tech/immortal/utils" "github.com/joho/godotenv" "gopkg.in/yaml.v3" ) @@ -91,7 +92,6 @@ func (c *Config) GetNIP11Documents() *nip11.RelayInformationDocument { PaymentsURL: c.Parameters.PaymentsURL, Icon: c.Parameters.Icon, Fees: new(nip11.RelayFeesDocument), - URL: c.Parameters.URL, } addmissions := make([]nip11.Admission, 0) @@ -124,6 +124,11 @@ func (c *Config) GetNIP11Documents() *nip11.RelayInformationDocument { n11d.Fees.Subscription = subscription n11d.Fees.Publication = publication + url, err := utils.ParseURL(c.Parameters.URL) + if err == nil { + n11d.URL = url + } + return n11d } diff --git a/metrics/metrics.go b/metrics/metrics.go index cbd9e6b..e8ad4f1 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -8,11 +8,13 @@ import ( type Metrics struct { EventsTotal *prometheus.CounterVec RequestsTotal *prometheus.CounterVec + AuthsTotal *prometheus.CounterVec MessagesTotal prometheus.Counter Subscriptions prometheus.Gauge Connections prometheus.Gauge EventLatency prometheus.Histogram RequestLatency prometheus.Histogram + AuthLatency prometheus.Histogram } func New() *Metrics { @@ -26,6 +28,11 @@ func New() *Metrics { Help: "number of REQ messages sent to relay.", }, []string{"status"}) + authsT := promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "auths_total", + Help: "number of AUTH messages sent to relay.", + }, []string{"status"}) + msgT := promauto.NewCounter(prometheus.CounterOpts{ Name: "messages_total", Help: "number of messages received.", @@ -47,17 +54,24 @@ func New() *Metrics { }) reqL := promauto.NewHistogram(prometheus.HistogramOpts{ - Name: "requset_latency", + Name: "request_latency", Help: "time needed to request to a REQ message.", }) + authL := promauto.NewHistogram(prometheus.HistogramOpts{ + Name: "auth_latency", + Help: "time needed to request to a AUTH message.", + }) + return &Metrics{ EventsTotal: eventsT, + AuthsTotal: authsT, Connections: conns, MessagesTotal: msgT, Subscriptions: subs, RequestsTotal: reqsT, EventLatency: eventL, RequestLatency: reqL, + AuthLatency: authL, } } diff --git a/server/websocket/auth_handler.go b/server/websocket/auth_handler.go index 0e49a97..09328cf 100644 --- a/server/websocket/auth_handler.go +++ b/server/websocket/auth_handler.go @@ -5,16 +5,24 @@ import ( "github.com/dezh-tech/immortal/types" "github.com/dezh-tech/immortal/types/message" + "github.com/dezh-tech/immortal/utils" "github.com/gorilla/websocket" ) func (s *Server) handleAuth(conn *websocket.Conn, m message.Message) { s.mu.Lock() defer s.mu.Unlock() + defer measureLatency(s.metrics.AuthLatency)() + + status := success + defer func() { + s.metrics.AuthsTotal.WithLabelValues(status).Inc() + }() msg, ok := m.(*message.Auth) if !ok { _ = conn.WriteMessage(1, message.MakeNotice("error: can't parse AUTH message.")) + status = parseFail return } @@ -23,6 +31,7 @@ func (s *Server) handleAuth(conn *websocket.Conn, m message.Message) { if !ok { _ = conn.WriteMessage(1, message.MakeNotice(fmt.Sprintf("error: can't find connection %s.", conn.RemoteAddr()))) + status = serverFail return } @@ -44,9 +53,19 @@ func (s *Server) handleAuth(conn *websocket.Conn, m message.Message) { } } + relayURL, err := utils.ParseURL(relay) + if err != nil { + _ = conn.WriteMessage(1, message.MakeNotice("error: invalid auth event.")) + status = parseFail + + return + } + if !msg.Event.IsValid(msg.Event.GetRawID()) && msg.Event.Kind != types.KindClientAuthentication && - client.challenge != challenge && s.nip11Doc.URL != relay { + client.challenge != challenge && s.nip11Doc.URL.Scheme != relayURL.Scheme || s.nip11Doc.URL.Host != relayURL.Host || + s.nip11Doc.URL.Path != relayURL.Path { _ = conn.WriteMessage(1, message.MakeNotice("error: invalid auth event.")) + status = invalidFail return } @@ -55,4 +74,5 @@ func (s *Server) handleAuth(conn *websocket.Conn, m message.Message) { *client.pubkey = msg.Event.PublicKey _ = conn.WriteMessage(1, message.MakeOK(true, msg.Event.ID, "")) + status = success } diff --git a/server/websocket/event_handler.go b/server/websocket/event_handler.go index 8c3248f..cde2689 100644 --- a/server/websocket/event_handler.go +++ b/server/websocket/event_handler.go @@ -6,6 +6,7 @@ import ( "log" "github.com/dezh-tech/immortal/types/message" + "github.com/dezh-tech/immortal/utils" "github.com/gorilla/websocket" ) @@ -46,7 +47,7 @@ func (s *Server) handleEvent(conn *websocket.Conn, m message.Message) { } if s.config.Limitation.AuthRequired && !*client.isKnown { - client.challenge = generateChallenge(10) + client.challenge = utils.GenerateChallenge(10) authm := message.MakeAuth(client.challenge) okm := message.MakeOK(false, @@ -63,7 +64,7 @@ func (s *Server) handleEvent(conn *websocket.Conn, m message.Message) { } if msg.Event.IsProtected() && msg.Event.PublicKey != *client.pubkey { - client.challenge = generateChallenge(10) + client.challenge = utils.GenerateChallenge(10) authm := message.MakeAuth(client.challenge) okm := message.MakeOK(false, diff --git a/server/websocket/req_handler.go b/server/websocket/req_handler.go index 2585c49..5b92191 100644 --- a/server/websocket/req_handler.go +++ b/server/websocket/req_handler.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/dezh-tech/immortal/types/message" + "github.com/dezh-tech/immortal/utils" "github.com/gorilla/websocket" ) @@ -38,7 +39,7 @@ func (s *Server) handleReq(conn *websocket.Conn, m message.Message) { } if s.config.Limitation.AuthRequired && !*client.isKnown { - client.challenge = generateChallenge(10) + client.challenge = utils.GenerateChallenge(10) authm := message.MakeAuth(client.challenge) closem := message.MakeClosed( diff --git a/server/websocket/utils.go b/server/websocket/utils.go index a9ffbd7..560adc8 100644 --- a/server/websocket/utils.go +++ b/server/websocket/utils.go @@ -4,7 +4,6 @@ import ( "time" "github.com/prometheus/client_golang/prometheus" - "golang.org/x/exp/rand" ) const ( @@ -15,11 +14,6 @@ const ( limitsFail = "limits_fail" serverFail = "server_fail" invalidFail = "invalid_fail" - - chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - letterIdxBits = 6 // 6 bits to represent a letter index - letterIdxMask = 1<= 0; { - if remain == 0 { - cache, remain = src.Uint64(), letterIdxMax - } - if idx := int(cache & letterIdxMask); idx < len(chars) { - b[i] = chars[idx] - i-- - } - cache >>= letterIdxBits - remain-- - } - - return string(b) -} diff --git a/types/nip11/nip11.go b/types/nip11/nip11.go index e51ac89..746c223 100644 --- a/types/nip11/nip11.go +++ b/types/nip11/nip11.go @@ -1,5 +1,7 @@ package nip11 +import "net/url" + type RelayInformationDocument struct { Name string `json:"name"` Description string `json:"description"` @@ -17,7 +19,7 @@ type RelayInformationDocument struct { PaymentsURL string `json:"payments_url,omitempty"` Fees *RelayFeesDocument `json:"fees,omitempty"` Icon string `json:"icon"` - URL string `json:"-"` + URL *url.URL `json:"-"` } type RelayLimitationDocument struct { diff --git a/utils/random.go b/utils/random.go new file mode 100644 index 0000000..7b052f4 --- /dev/null +++ b/utils/random.go @@ -0,0 +1,32 @@ +package utils + +import ( + "time" + + "golang.org/x/exp/rand" +) + +const ( + chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + letterIdxBits = 6 // 6 bits to represent a letter index + letterIdxMask = 1<= 0; { + if remain == 0 { + cache, remain = src.Uint64(), letterIdxMax + } + if idx := cache & letterIdxMask; idx < uint64(len(chars)) { + b[i] = chars[idx] + i-- + } + cache >>= letterIdxBits + remain-- + } + + return string(b) +} diff --git a/utils/url.go b/utils/url.go new file mode 100644 index 0000000..6246288 --- /dev/null +++ b/utils/url.go @@ -0,0 +1,15 @@ +package utils + +import ( + "net/url" + "strings" +) + +// helper function for ValidateAuthEvent. +func ParseURL(input string) (*url.URL, error) { + return url.Parse( + strings.ToLower( + strings.TrimSuffix(input, "/"), + ), + ) +}