mirror of
https://github.com/fiatjaf/khatru.git
synced 2026-06-09 22:28:56 +02:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8bb58f77c | ||
|
|
79eaedcffe | ||
|
|
aa2ac552ea | ||
|
|
4e2b921af9 | ||
|
|
cf98cfc6d8 | ||
|
|
62a3b9b7d9 | ||
|
|
e8637afa38 | ||
|
|
e38036a5e6 |
36
deleting.go
36
deleting.go
@@ -3,6 +3,8 @@ package khatru
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
)
|
)
|
||||||
@@ -10,10 +12,35 @@ import (
|
|||||||
func (rl *Relay) handleDeleteRequest(ctx context.Context, evt *nostr.Event) error {
|
func (rl *Relay) handleDeleteRequest(ctx context.Context, evt *nostr.Event) error {
|
||||||
// event deletion -- nip09
|
// event deletion -- nip09
|
||||||
for _, tag := range evt.Tags {
|
for _, tag := range evt.Tags {
|
||||||
if len(tag) >= 2 && tag[0] == "e" {
|
if len(tag) >= 2 {
|
||||||
// first we fetch the event
|
var f nostr.Filter
|
||||||
|
|
||||||
|
switch tag[0] {
|
||||||
|
case "e":
|
||||||
|
f = nostr.Filter{IDs: []string{tag[1]}}
|
||||||
|
case "a":
|
||||||
|
spl := strings.Split(tag[1], ":")
|
||||||
|
if len(spl) != 3 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
kind, err := strconv.Atoi(spl[0])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
author := spl[1]
|
||||||
|
identifier := spl[2]
|
||||||
|
f = nostr.Filter{
|
||||||
|
Kinds: []int{kind},
|
||||||
|
Authors: []string{author},
|
||||||
|
Tags: nostr.TagMap{"d": []string{identifier}},
|
||||||
|
Until: &evt.CreatedAt,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
for _, query := range rl.QueryEvents {
|
for _, query := range rl.QueryEvents {
|
||||||
ch, err := query(ctx, nostr.Filter{IDs: []string{tag[1]}})
|
ch, err := query(ctx, f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -24,13 +51,14 @@ func (rl *Relay) handleDeleteRequest(ctx context.Context, evt *nostr.Event) erro
|
|||||||
// got the event, now check if the user can delete it
|
// got the event, now check if the user can delete it
|
||||||
acceptDeletion := target.PubKey == evt.PubKey
|
acceptDeletion := target.PubKey == evt.PubKey
|
||||||
var msg string
|
var msg string
|
||||||
if acceptDeletion == false {
|
if !acceptDeletion {
|
||||||
msg = "you are not the author of this event"
|
msg = "you are not the author of this event"
|
||||||
}
|
}
|
||||||
// but if we have a function to overwrite this outcome, use that instead
|
// but if we have a function to overwrite this outcome, use that instead
|
||||||
for _, odo := range rl.OverwriteDeletionOutcome {
|
for _, odo := range rl.OverwriteDeletionOutcome {
|
||||||
acceptDeletion, msg = odo(ctx, target, evt)
|
acceptDeletion, msg = odo(ctx, target, evt)
|
||||||
}
|
}
|
||||||
|
|
||||||
if acceptDeletion {
|
if acceptDeletion {
|
||||||
// delete it
|
// delete it
|
||||||
for _, del := range rl.DeleteEvent {
|
for _, del := range rl.DeleteEvent {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ If on `RejectFilter` or `RejectEvent` you prefix the message with `auth-required
|
|||||||
relay.RejectFilter = append(relay.RejectFilter, func(ctx context.Context, filter nostr.Filter) (bool, string) {
|
relay.RejectFilter = append(relay.RejectFilter, func(ctx context.Context, filter nostr.Filter) (bool, string) {
|
||||||
return true, "auth-required: this query requires you to be authenticated"
|
return true, "auth-required: this query requires you to be authenticated"
|
||||||
})
|
})
|
||||||
relay.RejectEvent = append(relay.RejectFilter, func(ctx context.Context, event *nostr.Event) (bool, string) {
|
relay.RejectEvent = append(relay.RejectEvent, func(ctx context.Context, event *nostr.Event) (bool, string) {
|
||||||
return true, "auth-required: publishing this event requires authentication"
|
return true, "auth-required: publishing this event requires authentication"
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Other local key-value embedded databases that work the same way are [LMDB](https://pkg.go.dev/github.com/fiatjaf/eventstore/lmdb) and [BoltDB](https://pkg.go.dev/github.com/fiatjaf/eventstore/bolt).
|
[LMDB](https://pkg.go.dev/github.com/fiatjaf/eventstore/lmdb) works the same way.
|
||||||
|
|
||||||
[SQLite](https://pkg.go.dev/github.com/fiatjaf/eventstore/sqlite3) also stores things locally so it only needs a `Path`.
|
[SQLite](https://pkg.go.dev/github.com/fiatjaf/eventstore/sqlite3) also stores things locally so it only needs a `Path`.
|
||||||
|
|
||||||
|
|||||||
@@ -39,3 +39,21 @@ features:
|
|||||||
link: https://pkg.go.dev/github.com/fiatjaf/khatru
|
link: https://pkg.go.dev/github.com/fiatjaf/khatru
|
||||||
details: That means it is fast and lightweight, you can learn the language in 5 minutes and it builds your relay into a single binary that's easy to ship and deploy.
|
details: That means it is fast and lightweight, you can learn the language in 5 minutes and it builds your relay into a single binary that's easy to ship and deploy.
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## A glimpse of `khatru`'s power
|
||||||
|
|
||||||
|
It allows you to create a fully-functional relay in 7 lines of code:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
relay := khatru.NewRelay()
|
||||||
|
db := badger.BadgerBackend{Path: "/tmp/khatru-badgern-tmp"}
|
||||||
|
db.Init()
|
||||||
|
relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent)
|
||||||
|
relay.QueryEvents = append(relay.QueryEvents, db.QueryEvents)
|
||||||
|
relay.DeleteEvent = append(relay.DeleteEvent, db.DeleteEvent)
|
||||||
|
http.ListenAndServe(":3334", relay)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
After that you can customize it in infinite ways. See the links above.
|
||||||
|
|||||||
4
go.mod
4
go.mod
@@ -1,12 +1,12 @@
|
|||||||
module github.com/fiatjaf/khatru
|
module github.com/fiatjaf/khatru
|
||||||
|
|
||||||
go 1.21.4
|
go 1.23
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/fasthttp/websocket v1.5.7
|
github.com/fasthttp/websocket v1.5.7
|
||||||
github.com/fiatjaf/eventstore v0.5.1
|
github.com/fiatjaf/eventstore v0.5.1
|
||||||
github.com/nbd-wtf/go-nostr v0.34.5
|
github.com/nbd-wtf/go-nostr v0.34.5
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.0.2
|
github.com/puzpuzpuz/xsync/v3 v3.4.0
|
||||||
github.com/rs/cors v1.7.0
|
github.com/rs/cors v1.7.0
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
)
|
)
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -124,8 +124,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
|||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.0.2 h1:3yESHrRFYr6xzkz61LLkvNiPFXxJEAABanTQpKbAaew=
|
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.0.2/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||||
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
||||||
|
|||||||
@@ -177,6 +177,7 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
|||||||
var ok bool
|
var ok bool
|
||||||
var writeErr error
|
var writeErr error
|
||||||
var skipBroadcast bool
|
var skipBroadcast bool
|
||||||
|
|
||||||
if env.Event.Kind == 5 {
|
if env.Event.Kind == 5 {
|
||||||
// this always returns "blocked: " whenever it returns an error
|
// this always returns "blocked: " whenever it returns an error
|
||||||
writeErr = srl.handleDeleteRequest(ctx, &env.Event)
|
writeErr = srl.handleDeleteRequest(ctx, &env.Event)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
)
|
)
|
||||||
@@ -79,7 +80,8 @@ func RestrictToSpecifiedKinds(kinds ...uint16) func(context.Context, *nostr.Even
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func PreventTimestampsInThePast(thresholdSeconds nostr.Timestamp) 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 {
|
if nostr.Now()-event.CreatedAt > thresholdSeconds {
|
||||||
return true, "event too old"
|
return true, "event too old"
|
||||||
@@ -88,7 +90,8 @@ func PreventTimestampsInThePast(thresholdSeconds nostr.Timestamp) func(context.C
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func PreventTimestampsInTheFuture(thresholdSeconds nostr.Timestamp) 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 {
|
if event.CreatedAt-nostr.Now() > thresholdSeconds {
|
||||||
return true, "event too much in the future"
|
return true, "event too much in the future"
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ func ApplySaneDefaults(relay *khatru.Relay) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
relay.RejectConnection = append(relay.RejectConnection,
|
relay.RejectConnection = append(relay.RejectConnection,
|
||||||
ConnectionRateLimiter(1, time.Minute*5, 3),
|
ConnectionRateLimiter(1, time.Minute*5, 10),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user