Skip to content

Commit

Permalink
feat(nip42,nip70): add auth and protected events.
Browse files Browse the repository at this point in the history
  • Loading branch information
kehiy committed Nov 10, 2024
1 parent 5156bae commit 6e3c3ff
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 31 deletions.
7 changes: 6 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}

Expand Down
16 changes: 15 additions & 1 deletion metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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.",
Expand All @@ -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,
}
}
22 changes: 21 additions & 1 deletion server/websocket/auth_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
}
5 changes: 3 additions & 2 deletions server/websocket/event_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"log"

"github.com/dezh-tech/immortal/types/message"
"github.com/dezh-tech/immortal/utils"
"github.com/gorilla/websocket"
)

Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion server/websocket/req_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"

"github.com/dezh-tech/immortal/types/message"
"github.com/dezh-tech/immortal/utils"
"github.com/gorilla/websocket"
)

Expand Down Expand Up @@ -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(
Expand Down
24 changes: 0 additions & 24 deletions server/websocket/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"time"

"github.com/prometheus/client_golang/prometheus"
"golang.org/x/exp/rand"
)

const (
Expand All @@ -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<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
)

func measureLatency(ht prometheus.Histogram) func() {
Expand All @@ -29,21 +23,3 @@ func measureLatency(ht prometheus.Histogram) func() {
ht.Observe(time.Since(start).Seconds())
}
}

func generateChallenge(n int) string {
src := rand.NewSource(uint64(time.Now().UnixNano()))
b := make([]byte, n)
for i, cache, remain := n-1, src.Uint64(), letterIdxMax; i >= 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)
}
4 changes: 3 additions & 1 deletion types/nip11/nip11.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package nip11

import "net/url"

type RelayInformationDocument struct {
Name string `json:"name"`
Description string `json:"description"`
Expand All @@ -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 {
Expand Down
32 changes: 32 additions & 0 deletions utils/random.go
Original file line number Diff line number Diff line change
@@ -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<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
)

func GenerateChallenge(n int) string {
src := rand.NewSource(uint64(time.Now().UnixNano()))
b := make([]byte, n)
for i, cache, remain := n-1, src.Uint64(), letterIdxMax; i >= 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)
}
15 changes: 15 additions & 0 deletions utils/url.go
Original file line number Diff line number Diff line change
@@ -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, "/"),
),
)
}

0 comments on commit 6e3c3ff

Please sign in to comment.