mirror of
https://github.com/fiatjaf/khatru.git
synced 2026-04-30 16:58:02 +02:00
Compare commits
1 Commits
v0.0.13
...
author-nam
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86d2fec964 |
@@ -1,5 +1,7 @@
|
|||||||
# khatru, a relay framework [](https://pkg.go.dev/github.com/fiatjaf/khatru#Relay)
|
# khatru, a relay framework [](https://pkg.go.dev/github.com/fiatjaf/khatru#Relay)
|
||||||
|
|
||||||
|
`author: pablof7z`
|
||||||
|
|
||||||
[](https://github.com/fiatjaf/khatru/actions/workflows/test.yml)
|
[](https://github.com/fiatjaf/khatru/actions/workflows/test.yml)
|
||||||
[](https://pkg.go.dev/github.com/fiatjaf/khatru)
|
[](https://pkg.go.dev/github.com/fiatjaf/khatru)
|
||||||
[](https://goreportcard.com/report/github.com/fiatjaf/khatru)
|
[](https://goreportcard.com/report/github.com/fiatjaf/khatru)
|
||||||
@@ -34,7 +36,7 @@ func main() {
|
|||||||
relay.Info.Name = "my relay"
|
relay.Info.Name = "my relay"
|
||||||
relay.Info.PubKey = "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
|
relay.Info.PubKey = "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
|
||||||
relay.Info.Description = "this is my custom relay"
|
relay.Info.Description = "this is my custom relay"
|
||||||
relay.Info.Icon = "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fliquipedia.net%2Fcommons%2Fimages%2F3%2F35%2FSCProbe.jpg&f=1&nofb=1&ipt=0cbbfef25bce41da63d910e86c3c343e6c3b9d63194ca9755351bb7c2efa3359&ipo=images"
|
relay.Info.IconURL = "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fliquipedia.net%2Fcommons%2Fimages%2F3%2F35%2FSCProbe.jpg&f=1&nofb=1&ipt=0cbbfef25bce41da63d910e86c3c343e6c3b9d63194ca9755351bb7c2efa3359&ipo=images"
|
||||||
|
|
||||||
// you must bring your own storage scheme -- if you want to have any
|
// you must bring your own storage scheme -- if you want to have any
|
||||||
store := make(map[string]*nostr.Event, 120)
|
store := make(map[string]*nostr.Event, 120)
|
||||||
|
|||||||
21
handlers.go
21
handlers.go
@@ -14,7 +14,6 @@ import (
|
|||||||
"github.com/fasthttp/websocket"
|
"github.com/fasthttp/websocket"
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
"github.com/nbd-wtf/go-nostr/nip42"
|
"github.com/nbd-wtf/go-nostr/nip42"
|
||||||
"github.com/rs/cors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ServeHTTP implements http.Handler interface.
|
// ServeHTTP implements http.Handler interface.
|
||||||
@@ -22,7 +21,7 @@ func (rl *Relay) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
if r.Header.Get("Upgrade") == "websocket" {
|
if r.Header.Get("Upgrade") == "websocket" {
|
||||||
rl.HandleWebsocket(w, r)
|
rl.HandleWebsocket(w, r)
|
||||||
} else if r.Header.Get("Accept") == "application/nostr+json" {
|
} else if r.Header.Get("Accept") == "application/nostr+json" {
|
||||||
cors.AllowAll().Handler(http.HandlerFunc(rl.HandleNIP11)).ServeHTTP(w, r)
|
rl.HandleNIP11(w, r)
|
||||||
} else {
|
} else {
|
||||||
rl.serveMux.ServeHTTP(w, r)
|
rl.serveMux.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
@@ -49,8 +48,6 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
|||||||
WaitingForAuth: make(chan struct{}),
|
WaitingForAuth: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, WS_KEY, ws)
|
|
||||||
|
|
||||||
// reader
|
// reader
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -78,7 +75,6 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
if websocket.IsUnexpectedCloseError(
|
if websocket.IsUnexpectedCloseError(
|
||||||
err,
|
err,
|
||||||
websocket.CloseNormalClosure, // 1000
|
|
||||||
websocket.CloseGoingAway, // 1001
|
websocket.CloseGoingAway, // 1001
|
||||||
websocket.CloseNoStatusReceived, // 1005
|
websocket.CloseNoStatusReceived, // 1005
|
||||||
websocket.CloseAbnormalClosure, // 1006
|
websocket.CloseAbnormalClosure, // 1006
|
||||||
@@ -119,15 +115,14 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check id
|
// check serialization
|
||||||
hash := sha256.Sum256(evt.Serialize())
|
serialized := evt.Serialize()
|
||||||
id := hex.EncodeToString(hash[:])
|
|
||||||
if id != evt.ID {
|
|
||||||
ws.WriteJSON(nostr.OKEnvelope{EventID: evt.ID, OK: false, Reason: "invalid: id is computed incorrectly"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check signature
|
// assign ID
|
||||||
|
hash := sha256.Sum256(serialized)
|
||||||
|
evt.ID = hex.EncodeToString(hash[:])
|
||||||
|
|
||||||
|
// check signature (requires the ID to be set)
|
||||||
if ok, err := evt.CheckSignature(); err != nil {
|
if ok, err := evt.CheckSignature(); err != nil {
|
||||||
ws.WriteJSON(nostr.OKEnvelope{EventID: evt.ID, OK: false, Reason: "error: failed to verify signature"})
|
ws.WriteJSON(nostr.OKEnvelope{EventID: evt.ID, OK: false, Reason: "error: failed to verify signature"})
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package policies
|
package plugins
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -64,7 +64,9 @@ func PreventLargeTags(maxTagValueLen int) func(context.Context, *nostr.Event) (b
|
|||||||
func RestrictToSpecifiedKinds(kinds ...uint16) func(context.Context, *nostr.Event) (bool, string) {
|
func RestrictToSpecifiedKinds(kinds ...uint16) func(context.Context, *nostr.Event) (bool, string) {
|
||||||
max := 0
|
max := 0
|
||||||
min := 0
|
min := 0
|
||||||
|
allowed := make(map[uint16]struct{}, len(kinds))
|
||||||
for _, kind := range kinds {
|
for _, kind := range kinds {
|
||||||
|
allowed[kind] = struct{}{}
|
||||||
if int(kind) > max {
|
if int(kind) > max {
|
||||||
max = int(kind)
|
max = int(kind)
|
||||||
}
|
}
|
||||||
@@ -84,7 +86,7 @@ func RestrictToSpecifiedKinds(kinds ...uint16) func(context.Context, *nostr.Even
|
|||||||
}
|
}
|
||||||
|
|
||||||
// hopefully this map of uint16s is very fast
|
// hopefully this map of uint16s is very fast
|
||||||
if _, allowed := slices.BinarySearch(kinds, uint16(event.Kind)); allowed {
|
if _, allowed := allowed[uint16(event.Kind)]; allowed {
|
||||||
return false, ""
|
return false, ""
|
||||||
}
|
}
|
||||||
return true, "event kind not allowed"
|
return true, "event kind not allowed"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package policies
|
package plugins
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NoComplexFilters disallows filters with more than 2 tags.
|
|
||||||
func NoComplexFilters(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
|
func NoComplexFilters(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
|
||||||
items := len(filter.Tags) + len(filter.Kinds)
|
items := len(filter.Tags) + len(filter.Kinds)
|
||||||
|
|
||||||
@@ -18,7 +17,6 @@ func NoComplexFilters(ctx context.Context, filter nostr.Filter) (reject bool, ms
|
|||||||
return false, ""
|
return false, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoEmptyFilters disallows filters that don't have at least a tag, a kind, an author or an id.
|
|
||||||
func NoEmptyFilters(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
|
func NoEmptyFilters(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
|
||||||
c := len(filter.Kinds) + len(filter.IDs) + len(filter.Authors)
|
c := len(filter.Kinds) + len(filter.IDs) + len(filter.Authors)
|
||||||
for _, tagItems := range filter.Tags {
|
for _, tagItems := range filter.Tags {
|
||||||
@@ -30,13 +28,6 @@ func NoEmptyFilters(ctx context.Context, filter nostr.Filter) (reject bool, msg
|
|||||||
return false, ""
|
return false, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// AntiSyncBots tries to prevent people from syncing kind:1s from this relay to else by always
|
|
||||||
// requiring an author parameter at least.
|
|
||||||
func AntiSyncBots(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
|
|
||||||
return (len(filter.Kinds) == 0 || slices.Contains(filter.Kinds, 1)) &&
|
|
||||||
len(filter.Authors) == 0, "an author must be specified to get their kind:1 notes"
|
|
||||||
}
|
|
||||||
|
|
||||||
func NoSearchQueries(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
|
func NoSearchQueries(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
|
||||||
if filter.Search != "" {
|
if filter.Search != "" {
|
||||||
return true, "search is not supported"
|
return true, "search is not supported"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package policies
|
package plugins
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -8,8 +8,7 @@ import (
|
|||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RejectKind04Snoopers prevents reading NIP-04 messages from people not involved in the conversation.
|
func rejectKind04Snoopers(ctx context.Context, filter nostr.Filter) (bool, string) {
|
||||||
func RejectKind04Snoopers(ctx context.Context, filter nostr.Filter) (bool, string) {
|
|
||||||
// prevent kind-4 events from being returned to unauthed users,
|
// prevent kind-4 events from being returned to unauthed users,
|
||||||
// only when authentication is a thing
|
// only when authentication is a thing
|
||||||
if !slices.Contains(filter.Kinds, 4) {
|
if !slices.Contains(filter.Kinds, 4) {
|
||||||
5
relay.go
5
relay.go
@@ -18,9 +18,8 @@ func NewRelay() *Relay {
|
|||||||
Log: log.New(os.Stderr, "[khatru-relay] ", log.LstdFlags),
|
Log: log.New(os.Stderr, "[khatru-relay] ", log.LstdFlags),
|
||||||
|
|
||||||
Info: &nip11.RelayInformationDocument{
|
Info: &nip11.RelayInformationDocument{
|
||||||
Software: "https://github.com/fiatjaf/khatru",
|
Software: "https://github.com/fiatjaf/khatru",
|
||||||
Version: "n/a",
|
Version: "n/a",
|
||||||
SupportedNIPs: make([]int, 0),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
upgrader: websocket.Upgrader{
|
upgrader: websocket.Upgrader{
|
||||||
|
|||||||
Reference in New Issue
Block a user