a bunch of conversions and api tweaks on khatru and eventstore.
This commit is contained in:
@@ -16,25 +16,25 @@ import (
|
||||
//
|
||||
// If ignoreKinds is given this restriction will not apply to these kinds (useful for allowing a bigger).
|
||||
// If onlyKinds is given then all other kinds will be ignored.
|
||||
func PreventTooManyIndexableTags(max int, ignoreKinds []int, onlyKinds []int) func(context.Context, *nostr.Event) (bool, string) {
|
||||
func PreventTooManyIndexableTags(max int, ignoreKinds []uint16, onlyKinds []uint16) func(context.Context, nostr.Event) (bool, string) {
|
||||
slices.Sort(ignoreKinds)
|
||||
slices.Sort(onlyKinds)
|
||||
|
||||
ignore := func(kind int) bool { return false }
|
||||
ignore := func(kind uint16) bool { return false }
|
||||
if len(ignoreKinds) > 0 {
|
||||
ignore = func(kind int) bool {
|
||||
ignore = func(kind uint16) bool {
|
||||
_, isIgnored := slices.BinarySearch(ignoreKinds, kind)
|
||||
return isIgnored
|
||||
}
|
||||
}
|
||||
if len(onlyKinds) > 0 {
|
||||
ignore = func(kind int) bool {
|
||||
ignore = func(kind uint16) bool {
|
||||
_, isApplicable := slices.BinarySearch(onlyKinds, kind)
|
||||
return !isApplicable
|
||||
}
|
||||
}
|
||||
|
||||
return func(ctx context.Context, event *nostr.Event) (reject bool, msg string) {
|
||||
return func(ctx context.Context, event nostr.Event) (reject bool, msg string) {
|
||||
if ignore(event.Kind) {
|
||||
return false, ""
|
||||
}
|
||||
@@ -53,8 +53,8 @@ func PreventTooManyIndexableTags(max int, ignoreKinds []int, onlyKinds []int) fu
|
||||
}
|
||||
|
||||
// PreventLargeTags rejects events that have indexable tag values greater than maxTagValueLen.
|
||||
func PreventLargeTags(maxTagValueLen int) func(context.Context, *nostr.Event) (bool, string) {
|
||||
return func(ctx context.Context, event *nostr.Event) (reject bool, msg string) {
|
||||
func PreventLargeTags(maxTagValueLen int) func(context.Context, nostr.Event) (bool, string) {
|
||||
return func(ctx context.Context, event nostr.Event) (reject bool, msg string) {
|
||||
for _, tag := range event.Tags {
|
||||
if len(tag) > 1 && len(tag[0]) == 1 {
|
||||
if len(tag[1]) > maxTagValueLen {
|
||||
@@ -68,11 +68,11 @@ func PreventLargeTags(maxTagValueLen int) func(context.Context, *nostr.Event) (b
|
||||
|
||||
// RestrictToSpecifiedKinds returns a function that can be used as a RejectFilter that will reject
|
||||
// any events with kinds different than the specified ones.
|
||||
func RestrictToSpecifiedKinds(allowEphemeral bool, kinds ...uint16) func(context.Context, *nostr.Event) (bool, string) {
|
||||
func RestrictToSpecifiedKinds(allowEphemeral bool, kinds ...uint16) func(context.Context, nostr.Event) (bool, string) {
|
||||
// sort the kinds in increasing order
|
||||
slices.Sort(kinds)
|
||||
|
||||
return func(ctx context.Context, event *nostr.Event) (reject bool, msg string) {
|
||||
return func(ctx context.Context, event nostr.Event) (reject bool, msg string) {
|
||||
if allowEphemeral && nostr.IsEphemeralKind(event.Kind) {
|
||||
return false, ""
|
||||
}
|
||||
@@ -85,9 +85,9 @@ func RestrictToSpecifiedKinds(allowEphemeral bool, kinds ...uint16) func(context
|
||||
}
|
||||
}
|
||||
|
||||
func PreventTimestampsInThePast(threshold time.Duration) func(context.Context, *nostr.Event) (bool, string) {
|
||||
func PreventTimestampsInThePast(threshold time.Duration) func(context.Context, nostr.Event) (bool, string) {
|
||||
thresholdSeconds := nostr.Timestamp(threshold.Seconds())
|
||||
return func(ctx context.Context, event *nostr.Event) (reject bool, msg string) {
|
||||
return func(ctx context.Context, event nostr.Event) (reject bool, msg string) {
|
||||
if nostr.Now()-event.CreatedAt > thresholdSeconds {
|
||||
return true, "event too old"
|
||||
}
|
||||
@@ -95,9 +95,9 @@ func PreventTimestampsInThePast(threshold time.Duration) func(context.Context, *
|
||||
}
|
||||
}
|
||||
|
||||
func PreventTimestampsInTheFuture(threshold time.Duration) func(context.Context, *nostr.Event) (bool, string) {
|
||||
func PreventTimestampsInTheFuture(threshold time.Duration) func(context.Context, nostr.Event) (bool, string) {
|
||||
thresholdSeconds := nostr.Timestamp(threshold.Seconds())
|
||||
return func(ctx context.Context, event *nostr.Event) (reject bool, msg string) {
|
||||
return func(ctx context.Context, event nostr.Event) (reject bool, msg string) {
|
||||
if event.CreatedAt-nostr.Now() > thresholdSeconds {
|
||||
return true, "event too much in the future"
|
||||
}
|
||||
@@ -105,12 +105,12 @@ func PreventTimestampsInTheFuture(threshold time.Duration) func(context.Context,
|
||||
}
|
||||
}
|
||||
|
||||
func RejectEventsWithBase64Media(ctx context.Context, evt *nostr.Event) (bool, string) {
|
||||
func RejectEventsWithBase64Media(ctx context.Context, evt nostr.Event) (bool, string) {
|
||||
return strings.Contains(evt.Content, "data:image/") || strings.Contains(evt.Content, "data:video/"), "event with base64 media"
|
||||
}
|
||||
|
||||
func OnlyAllowNIP70ProtectedEvents(ctx context.Context, event *nostr.Event) (reject bool, msg string) {
|
||||
if nip70.IsProtected(*event) {
|
||||
func OnlyAllowNIP70ProtectedEvents(ctx context.Context, event nostr.Event) (reject bool, msg string) {
|
||||
if nip70.IsProtected(event) {
|
||||
return false, ""
|
||||
}
|
||||
return true, "blocked: we only accept events protected with the nip70 \"-\" tag"
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"fiatjaf.com/nostr/khatru"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/khatru"
|
||||
)
|
||||
|
||||
// NoComplexFilters disallows filters with more than 2 tags.
|
||||
@@ -21,7 +21,7 @@ func NoComplexFilters(ctx context.Context, filter nostr.Filter) (reject bool, ms
|
||||
|
||||
// MustAuth requires all subscribers to be authenticated
|
||||
func MustAuth(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
|
||||
if khatru.GetAuthed(ctx) == "" {
|
||||
if _, isAuthed := khatru.GetAuthed(ctx); !isAuthed {
|
||||
return true, "auth-required: all requests must be authenticated"
|
||||
}
|
||||
return false, ""
|
||||
@@ -63,7 +63,7 @@ func RemoveSearchQueries(ctx context.Context, filter *nostr.Filter) {
|
||||
func RemoveAllButKinds(kinds ...uint16) func(context.Context, *nostr.Filter) {
|
||||
return func(ctx context.Context, filter *nostr.Filter) {
|
||||
if n := len(filter.Kinds); n > 0 {
|
||||
newKinds := make([]int, 0, n)
|
||||
newKinds := make([]uint16, 0, n)
|
||||
for i := 0; i < n; i++ {
|
||||
if k := filter.Kinds[i]; slices.Contains(kinds, uint16(k)) {
|
||||
newKinds = append(newKinds, k)
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
func ValidateKind(ctx context.Context, evt *nostr.Event) (bool, string) {
|
||||
func ValidateKind(ctx context.Context, evt nostr.Event) (bool, string) {
|
||||
switch evt.Kind {
|
||||
case 0:
|
||||
var m struct {
|
||||
|
||||
47
khatru/policies/multi.go
Normal file
47
khatru/policies/multi.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package policies
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
func SeqEvent(
|
||||
funcs ...func(ctx context.Context, evt nostr.Event) (bool, string),
|
||||
) func(context.Context, nostr.Event) (reject bool, reason string) {
|
||||
return func(ctx context.Context, evt nostr.Event) (reject bool, reason string) {
|
||||
for _, fn := range funcs {
|
||||
reject, reason := fn(ctx, evt)
|
||||
if reject {
|
||||
return reject, reason
|
||||
}
|
||||
}
|
||||
return false, ""
|
||||
}
|
||||
}
|
||||
|
||||
func SeqStore(funcs ...func(ctx context.Context, evt nostr.Event) error) func(context.Context, nostr.Event) error {
|
||||
return func(ctx context.Context, evt nostr.Event) error {
|
||||
for _, fn := range funcs {
|
||||
err := fn(ctx, evt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func SeqRequest(
|
||||
funcs ...func(ctx context.Context, evt nostr.Filter) (bool, string),
|
||||
) func(context.Context, nostr.Filter) (reject bool, reason string) {
|
||||
return func(ctx context.Context, evt nostr.Filter) (reject bool, reason string) {
|
||||
for _, fn := range funcs {
|
||||
reject, reason := fn(ctx, evt)
|
||||
if reject {
|
||||
return reject, reason
|
||||
}
|
||||
}
|
||||
return false, ""
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package policies
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"fiatjaf.com/nostr/khatru"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
// RejectKind04Snoopers prevents reading NIP-04 messages from people not involved in the conversation.
|
||||
func RejectKind04Snoopers(ctx context.Context, filter nostr.Filter) (bool, string) {
|
||||
// prevent kind-4 events from being returned to unauthed users,
|
||||
// only when authentication is a thing
|
||||
if !slices.Contains(filter.Kinds, 4) {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
ws := khatru.GetConnection(ctx)
|
||||
senders := filter.Authors
|
||||
receivers, _ := filter.Tags["p"]
|
||||
switch {
|
||||
case ws.AuthedPublicKey == "":
|
||||
// not authenticated
|
||||
return true, "restricted: this relay does not serve kind-4 to unauthenticated users, does your client implement NIP-42?"
|
||||
case len(senders) == 1 && len(receivers) < 2 && (senders[0] == ws.AuthedPublicKey):
|
||||
// allowed filter: ws.authed is sole sender (filter specifies one or all receivers)
|
||||
return false, ""
|
||||
case len(receivers) == 1 && len(senders) < 2 && (receivers[0] == ws.AuthedPublicKey):
|
||||
// allowed filter: ws.authed is sole receiver (filter specifies one or all senders)
|
||||
return false, ""
|
||||
default:
|
||||
// restricted filter: do not return any events,
|
||||
// even if other elements in filters array were not restricted).
|
||||
// client should know better.
|
||||
return true, "restricted: authenticated user does not have authorization for requested filters."
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,14 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"fiatjaf.com/nostr/khatru"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/khatru"
|
||||
)
|
||||
|
||||
func EventIPRateLimiter(tokensPerInterval int, interval time.Duration, maxTokens int) func(ctx context.Context, _ *nostr.Event) (reject bool, msg string) {
|
||||
func EventIPRateLimiter(tokensPerInterval int, interval time.Duration, maxTokens int) func(ctx context.Context, _ nostr.Event) (reject bool, msg string) {
|
||||
rl := startRateLimitSystem[string](tokensPerInterval, interval, maxTokens)
|
||||
|
||||
return func(ctx context.Context, _ *nostr.Event) (reject bool, msg string) {
|
||||
return func(ctx context.Context, _ nostr.Event) (reject bool, msg string) {
|
||||
ip := khatru.GetIP(ctx)
|
||||
if ip == "" {
|
||||
return false, ""
|
||||
@@ -21,11 +21,11 @@ func EventIPRateLimiter(tokensPerInterval int, interval time.Duration, maxTokens
|
||||
}
|
||||
}
|
||||
|
||||
func EventPubKeyRateLimiter(tokensPerInterval int, interval time.Duration, maxTokens int) func(ctx context.Context, _ *nostr.Event) (reject bool, msg string) {
|
||||
func EventPubKeyRateLimiter(tokensPerInterval int, interval time.Duration, maxTokens int) func(ctx context.Context, _ nostr.Event) (reject bool, msg string) {
|
||||
rl := startRateLimitSystem[string](tokensPerInterval, interval, maxTokens)
|
||||
|
||||
return func(ctx context.Context, evt *nostr.Event) (reject bool, msg string) {
|
||||
return rl(evt.PubKey), "rate-limited: slow down, please"
|
||||
return func(ctx context.Context, evt nostr.Event) (reject bool, msg string) {
|
||||
return rl(evt.PubKey.Hex()), "rate-limited: slow down, please"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,17 +7,15 @@ import (
|
||||
)
|
||||
|
||||
func ApplySaneDefaults(relay *khatru.Relay) {
|
||||
relay.RejectEvent = append(relay.RejectEvent,
|
||||
relay.OnEvent = SeqEvent(
|
||||
RejectEventsWithBase64Media,
|
||||
EventIPRateLimiter(2, time.Minute*3, 10),
|
||||
)
|
||||
|
||||
relay.RejectFilter = append(relay.RejectFilter,
|
||||
relay.OnRequest = SeqRequest(
|
||||
NoComplexFilters,
|
||||
FilterIPRateLimiter(20, time.Minute, 100),
|
||||
)
|
||||
|
||||
relay.RejectConnection = append(relay.RejectConnection,
|
||||
ConnectionRateLimiter(1, time.Minute*5, 100),
|
||||
)
|
||||
relay.RejectConnection = ConnectionRateLimiter(1, time.Minute*5, 100)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user