mirror of
https://github.com/fiatjaf/khatru.git
synced 2026-04-07 22:16:46 +02:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
375236cfe2 | ||
|
|
35e801379a | ||
|
|
22da06b629 | ||
|
|
7bfde76ab1 |
@@ -26,7 +26,7 @@ func main() {
|
||||
relay.CountEvents = append(relay.CountEvents, db.CountEvents)
|
||||
relay.DeleteEvent = append(relay.DeleteEvent, db.DeleteEvent)
|
||||
|
||||
relay.RejectEvent = append(relay.RejectEvent, policies.PreventTooManyIndexableTags(10))
|
||||
relay.RejectEvent = append(relay.RejectEvent, policies.PreventTooManyIndexableTags(10, nil, nil))
|
||||
relay.RejectFilter = append(relay.RejectFilter, policies.NoComplexFilters)
|
||||
|
||||
relay.OnEventSaved = append(relay.OnEventSaved, func(ctx context.Context, event *nostr.Event) {
|
||||
|
||||
@@ -60,15 +60,20 @@ func main() {
|
||||
return false, "" // anyone else can
|
||||
},
|
||||
)
|
||||
relay.OnConnect = append(relay.OnConnect,
|
||||
func(ctx context.Context) {
|
||||
// request NIP-42 AUTH from everybody
|
||||
relay.RequestAuth(ctx)
|
||||
|
||||
// you can request auth by rejecting an event or a request with the prefix "auth-required: "
|
||||
relay.RejectFilter = append(relay.RejectFilter,
|
||||
func(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
|
||||
if pubkey := khatru.GetAuthed(ctx); pubkey != "" {
|
||||
log.Printf("request from %s\n", pubkey)
|
||||
return false, ""
|
||||
}
|
||||
return true, "auth-required: only authenticated users can read from this relay"
|
||||
},
|
||||
)
|
||||
relay.OnAuth = append(relay.OnAuth,
|
||||
func(ctx context.Context, pubkey string) {
|
||||
// and when they auth we just log that for nothing
|
||||
// and when they auth we can just log that for nothing
|
||||
log.Println(pubkey + " is authed!")
|
||||
},
|
||||
)
|
||||
|
||||
43
handlers.go
43
handlers.go
@@ -19,6 +19,10 @@ import (
|
||||
|
||||
// ServeHTTP implements http.Handler interface.
|
||||
func (rl *Relay) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if rl.ServiceURL == "" {
|
||||
rl.ServiceURL = getServiceBaseURL(r)
|
||||
}
|
||||
|
||||
if r.Header.Get("Upgrade") == "websocket" {
|
||||
rl.HandleWebsocket(w, r)
|
||||
} else if r.Header.Get("Accept") == "application/nostr+json" {
|
||||
@@ -29,7 +33,7 @@ func (rl *Relay) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
connectionContext := r.Context()
|
||||
|
||||
conn, err := rl.upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
@@ -50,7 +54,7 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
||||
Authed: make(chan struct{}),
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, WS_KEY, ws)
|
||||
connectionContext = context.WithValue(connectionContext, WS_KEY, ws)
|
||||
|
||||
// reader
|
||||
go func() {
|
||||
@@ -71,7 +75,7 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
|
||||
for _, onconnect := range rl.OnConnect {
|
||||
onconnect(ctx)
|
||||
onconnect(connectionContext)
|
||||
}
|
||||
|
||||
for {
|
||||
@@ -95,7 +99,13 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
go func(message []byte) {
|
||||
ctx := context.WithValue(context.Background(), WS_KEY, ws)
|
||||
ctx := context.WithValue(
|
||||
context.WithValue(
|
||||
context.Background(),
|
||||
AUTH_CONTEXT_KEY, connectionContext.Value(AUTH_CONTEXT_KEY),
|
||||
),
|
||||
WS_KEY, ws,
|
||||
)
|
||||
|
||||
envelope := nostr.ParseMessage(message)
|
||||
if envelope == nil {
|
||||
@@ -134,6 +144,9 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
||||
ok = true
|
||||
} else {
|
||||
reason = nostr.NormalizeOKMessage(err.Error(), "blocked")
|
||||
if isAuthRequired(reason) {
|
||||
ws.WriteJSON(nostr.AuthEnvelope{Challenge: &ws.Challenge})
|
||||
}
|
||||
}
|
||||
ws.WriteJSON(nostr.OKEnvelope{EventID: env.Event.ID, OK: ok, Reason: reason})
|
||||
case *nostr.CountEnvelope:
|
||||
@@ -152,8 +165,11 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
for _, filter := range env.Filters {
|
||||
err := rl.handleRequest(ctx, env.SubscriptionID, &eose, ws, filter)
|
||||
if err == nil {
|
||||
if err != nil {
|
||||
reason := nostr.NormalizeOKMessage(err.Error(), "blocked")
|
||||
if isAuthRequired(reason) {
|
||||
ws.WriteJSON(nostr.AuthEnvelope{Challenge: &ws.Challenge})
|
||||
}
|
||||
ws.WriteJSON(nostr.ClosedEnvelope{SubscriptionID: env.SubscriptionID, Reason: reason})
|
||||
return
|
||||
}
|
||||
@@ -168,15 +184,14 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
||||
case *nostr.CloseEnvelope:
|
||||
removeListenerId(ws, string(*env))
|
||||
case *nostr.AuthEnvelope:
|
||||
if rl.ServiceURL != "" {
|
||||
if pubkey, ok := nip42.ValidateAuthEvent(&env.Event, ws.Challenge, rl.ServiceURL); ok {
|
||||
ws.AuthedPublicKey = pubkey
|
||||
close(ws.Authed)
|
||||
ctx = context.WithValue(ctx, AUTH_CONTEXT_KEY, pubkey)
|
||||
ws.WriteJSON(nostr.OKEnvelope{EventID: env.Event.ID, OK: true})
|
||||
} else {
|
||||
ws.WriteJSON(nostr.OKEnvelope{EventID: env.Event.ID, OK: false, Reason: "error: failed to authenticate"})
|
||||
}
|
||||
wsBaseUrl := strings.Replace(rl.ServiceURL, "http", "ws", 1)
|
||||
if pubkey, ok := nip42.ValidateAuthEvent(&env.Event, ws.Challenge, wsBaseUrl); ok {
|
||||
ws.AuthedPublicKey = pubkey
|
||||
close(ws.Authed)
|
||||
connectionContext = context.WithValue(ctx, AUTH_CONTEXT_KEY, pubkey)
|
||||
ws.WriteJSON(nostr.OKEnvelope{EventID: env.Event.ID, OK: true})
|
||||
} else {
|
||||
ws.WriteJSON(nostr.OKEnvelope{EventID: env.Event.ID, OK: false, Reason: "error: failed to authenticate"})
|
||||
}
|
||||
}
|
||||
}(message)
|
||||
|
||||
55
helpers.go
Normal file
55
helpers.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package khatru
|
||||
|
||||
import (
|
||||
"hash/maphash"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
const (
|
||||
AUTH_CONTEXT_KEY = iota
|
||||
WS_KEY
|
||||
)
|
||||
|
||||
var nip20prefixmatcher = regexp.MustCompile(`^\w+: `)
|
||||
|
||||
func pointerHasher[V any](_ maphash.Seed, k *V) uint64 {
|
||||
return uint64(uintptr(unsafe.Pointer(k)))
|
||||
}
|
||||
|
||||
func isOlder(previous, next *nostr.Event) bool {
|
||||
return previous.CreatedAt < next.CreatedAt ||
|
||||
(previous.CreatedAt == next.CreatedAt && previous.ID > next.ID)
|
||||
}
|
||||
|
||||
func isAuthRequired(msg string) bool {
|
||||
idx := strings.IndexByte(msg, ':')
|
||||
return msg[0:idx] == "auth-required"
|
||||
}
|
||||
|
||||
func getServiceBaseURL(r *http.Request) string {
|
||||
host := r.Header.Get("X-Forwarded-Host")
|
||||
if host == "" {
|
||||
host = r.Host
|
||||
}
|
||||
proto := r.Header.Get("X-Forwarded-Proto")
|
||||
if proto == "" {
|
||||
if host == "localhost" {
|
||||
proto = "http"
|
||||
} else if strings.Index(host, ":") != -1 {
|
||||
// has a port number
|
||||
proto = "http"
|
||||
} else if _, err := strconv.Atoi(strings.ReplaceAll(host, ".", "")); err == nil {
|
||||
// it's a naked IP
|
||||
proto = "http"
|
||||
} else {
|
||||
proto = "https"
|
||||
}
|
||||
}
|
||||
return proto + "://" + host
|
||||
}
|
||||
7
relay.go
7
relay.go
@@ -40,7 +40,7 @@ func NewRelay() *Relay {
|
||||
}
|
||||
|
||||
type Relay struct {
|
||||
ServiceURL string // required for nip-42
|
||||
ServiceURL string
|
||||
|
||||
RejectEvent []func(ctx context.Context, event *nostr.Event) (reject bool, msg string)
|
||||
RejectFilter []func(ctx context.Context, filter nostr.Filter) (reject bool, msg string)
|
||||
@@ -82,8 +82,3 @@ type Relay struct {
|
||||
PingPeriod time.Duration // Send pings to peer with this period. Must be less than pongWait.
|
||||
MaxMessageSize int64 // Maximum message size allowed from peer.
|
||||
}
|
||||
|
||||
func (rl *Relay) RequestAuth(ctx context.Context) {
|
||||
ws := GetConnection(ctx)
|
||||
ws.WriteJSON(nostr.AuthEnvelope{Challenge: &ws.Challenge})
|
||||
}
|
||||
|
||||
21
utils.go
21
utils.go
@@ -2,20 +2,8 @@ package khatru
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hash/maphash"
|
||||
"regexp"
|
||||
"unsafe"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
const (
|
||||
AUTH_CONTEXT_KEY = iota
|
||||
WS_KEY = iota
|
||||
)
|
||||
|
||||
var nip20prefixmatcher = regexp.MustCompile(`^\w+: `)
|
||||
|
||||
func GetConnection(ctx context.Context) *WebSocket {
|
||||
return ctx.Value(WS_KEY).(*WebSocket)
|
||||
}
|
||||
@@ -27,12 +15,3 @@ func GetAuthed(ctx context.Context) string {
|
||||
}
|
||||
return authedPubkey.(string)
|
||||
}
|
||||
|
||||
func pointerHasher[V any](_ maphash.Seed, k *V) uint64 {
|
||||
return uint64(uintptr(unsafe.Pointer(k)))
|
||||
}
|
||||
|
||||
func isOlder(previous, next *nostr.Event) bool {
|
||||
return previous.CreatedAt < next.CreatedAt ||
|
||||
(previous.CreatedAt == next.CreatedAt && previous.ID > next.ID)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user