Compare commits

...

22 Commits

Author SHA1 Message Date
Your Name
1b4d81dde4 corrects malformed json in blossom list 2024-11-22 10:03:50 -03:00
Anthony Accioly
7bfdbb557c fix(blossom): use io.ReadSeeker in Blossom example
This should fix the failing pipeline test
2024-11-22 07:54:57 -03:00
girino
3f26a1f727 Change API of LoadBlob to return io.ReadSeeker instead of io.Reader and use ServeContent to serve blob content (#19)
* first test with http.ServeContent

* added debug to help me here

* removed debug messages

* changed LoadBlob to requisre io.ReadSeeker

---------

Co-authored-by: Your Name <you@example.com>
2024-11-21 11:58:12 -03:00
fiatjaf_
76ecf4f791 Merge pull request #17 from aaccioly-open-source/feature/bud01-cors-support
Optimised CORS Headers + Small fixes
2024-11-05 22:59:18 -03:00
Anthony Accioly
1498da09c8 refactor(blossom): remove setCors function 2024-11-05 19:04:41 +00:00
Anthony Accioly
3d4dd71510 refactor(blossom): undo enhanced routing 2024-11-05 18:55:47 +00:00
Anthony Accioly
582a74c000 refactor: apply CORS rules on relay 2024-11-05 18:37:12 +00:00
Anthony Accioly
bbcf948dd6 fix(blossom): forward requests to base relay 2024-11-05 17:56:56 +00:00
Anthony Accioly
553d848362 fix(policies): update check for ephemeral kinds
The Event.IsEphemeral method has been removed from go-nostr
2024-11-05 17:32:56 +00:00
Anthony Accioly
2a80d4099d perf(blossom): set Access-Control-Max-Age to 24 h 2024-11-05 16:56:59 +00:00
Anthony Accioly
ad6635d86c refactor(blossom): use Go 1.22 enhanced routing 2024-11-05 16:48:21 +00:00
Anthony Accioly
c93441cd63 feat(blossom): use rs/cors to handle BUD-01 CORS 2024-11-04 17:18:37 +00:00
Anthony Accioly
dc34dd7e90 build(deps): bump rs/cors version to v1.11.1 2024-11-04 14:02:18 +00:00
Anthony Accioly
a004f59187 fix(blossom): Example returns io.Reader 2024-11-04 13:56:13 +00:00
fiatjaf
a931a83370 fix negentropy by making special provisions for bypassing query limits. 2024-11-03 16:57:18 -03:00
Anthony Accioly
1c15db2ca1 fix(blossom): CORS headers required by noStrudel 2024-10-31 07:50:34 -03:00
fiatjaf
b617fea679 blossom: again a bunch of fixes. require Authorization on /upload again always. 2024-10-29 16:32:53 -03:00
fiatjaf
1d7bdccb3a blossom: fixes and updates from trying to use it. 2024-10-29 09:01:19 -03:00
fiatjaf
92d1a5b671 blossom: implement bud06 (upload requirements). 2024-10-29 09:01:16 -03:00
fiatjaf
7f878121fc blossom: return code from Reject* functions because HTTP is stupid. 2024-10-29 09:01:13 -03:00
fiatjaf
a893dc2d2c blossom: store as a standalone interface (and an eventstore wrapper). 2024-10-29 09:01:04 -03:00
fiatjaf
91e7737ec1 basic modular blossom support. 2024-10-27 17:20:10 -03:00
13 changed files with 700 additions and 11 deletions

46
blossom/authorization.go Normal file
View File

@@ -0,0 +1,46 @@
package blossom
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"github.com/nbd-wtf/go-nostr"
)
func readAuthorization(r *http.Request) (*nostr.Event, error) {
token := r.Header.Get("Authorization")
if !strings.HasPrefix(token, "Nostr ") {
return nil, nil
}
var reader io.Reader
reader = bytes.NewReader([]byte(token)[6:])
reader = base64.NewDecoder(base64.StdEncoding, reader)
var evt nostr.Event
err := json.NewDecoder(reader).Decode(&evt)
if err != nil || evt.Kind != 24242 || len(evt.ID) != 64 || !evt.CheckID() {
return nil, fmt.Errorf("invalid event")
}
if ok, _ := evt.CheckSignature(); !ok {
return nil, fmt.Errorf("invalid signature")
}
expirationTag := evt.Tags.GetFirst([]string{"expiration", ""})
if expirationTag == nil {
return nil, fmt.Errorf("missing \"expiration\" tag")
}
expiration, _ := strconv.ParseInt((*expirationTag)[1], 10, 64)
if nostr.Timestamp(expiration) < nostr.Now() {
return nil, fmt.Errorf("event expired")
}
return &evt, nil
}

26
blossom/blob.go Normal file
View File

@@ -0,0 +1,26 @@
package blossom
import (
"context"
"github.com/nbd-wtf/go-nostr"
)
type BlobDescriptor struct {
URL string `json:"url"`
SHA256 string `json:"sha256"`
Size int `json:"size"`
Type string `json:"type"`
Uploaded nostr.Timestamp `json:"uploaded"`
Owner string `json:"-"`
}
type BlobIndex interface {
Keep(ctx context.Context, blob BlobDescriptor, pubkey string) error
List(ctx context.Context, pubkey string) (chan BlobDescriptor, error)
Get(ctx context.Context, sha256 string) (*BlobDescriptor, error)
Delete(ctx context.Context, sha256 string, pubkey string) error
}
var _ BlobIndex = (*EventStoreBlobIndexWrapper)(nil)

View File

@@ -0,0 +1,104 @@
package blossom
import (
"context"
"strconv"
"github.com/fiatjaf/eventstore"
"github.com/nbd-wtf/go-nostr"
)
// EventStoreBlobIndexWrapper uses fake events to keep track of what blobs we have stored and who owns them
type EventStoreBlobIndexWrapper struct {
eventstore.Store
ServiceURL string
}
func (es EventStoreBlobIndexWrapper) Keep(ctx context.Context, blob BlobDescriptor, pubkey string) error {
ch, err := es.Store.QueryEvents(ctx, nostr.Filter{Authors: []string{pubkey}, Kinds: []int{24242}, Tags: nostr.TagMap{"x": []string{blob.SHA256}}})
if err != nil {
return err
}
if <-ch == nil {
// doesn't exist, save
evt := &nostr.Event{
PubKey: pubkey,
Kind: 24242,
Tags: nostr.Tags{
{"x", blob.SHA256},
{"type", blob.Type},
{"size", strconv.Itoa(blob.Size)},
},
CreatedAt: blob.Uploaded,
}
evt.ID = evt.GetID()
es.Store.SaveEvent(ctx, evt)
}
return nil
}
func (es EventStoreBlobIndexWrapper) List(ctx context.Context, pubkey string) (chan BlobDescriptor, error) {
ech, err := es.Store.QueryEvents(ctx, nostr.Filter{Authors: []string{pubkey}, Kinds: []int{24242}})
if err != nil {
return nil, err
}
ch := make(chan BlobDescriptor)
go func() {
for evt := range ech {
ch <- es.parseEvent(evt)
}
close(ch)
}()
return ch, nil
}
func (es EventStoreBlobIndexWrapper) Get(ctx context.Context, sha256 string) (*BlobDescriptor, error) {
ech, err := es.Store.QueryEvents(ctx, nostr.Filter{Tags: nostr.TagMap{"x": []string{sha256}}, Kinds: []int{24242}, Limit: 1})
if err != nil {
return nil, err
}
evt := <-ech
if evt != nil {
bd := es.parseEvent(evt)
return &bd, nil
}
return nil, nil
}
func (es EventStoreBlobIndexWrapper) Delete(ctx context.Context, sha256 string, pubkey string) error {
ech, err := es.Store.QueryEvents(ctx, nostr.Filter{Authors: []string{pubkey}, Tags: nostr.TagMap{"x": []string{sha256}}, Kinds: []int{24242}, Limit: 1})
if err != nil {
return err
}
evt := <-ech
if evt != nil {
return es.Store.DeleteEvent(ctx, evt)
}
return nil
}
func (es EventStoreBlobIndexWrapper) parseEvent(evt *nostr.Event) BlobDescriptor {
hhash := evt.Tags[0][1]
mimetype := evt.Tags[1][1]
ext := getExtension(mimetype)
size, _ := strconv.Atoi(evt.Tags[2][1])
return BlobDescriptor{
Owner: evt.PubKey,
Uploaded: evt.CreatedAt,
URL: es.ServiceURL + "/" + hhash + ext,
SHA256: hhash,
Type: mimetype,
Size: size,
}
}

336
blossom/handlers.go Normal file
View File

@@ -0,0 +1,336 @@
package blossom
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"mime"
"net/http"
"strconv"
"strings"
"time"
"github.com/liamg/magic"
"github.com/nbd-wtf/go-nostr"
)
func (bs BlossomServer) handleUploadCheck(w http.ResponseWriter, r *http.Request) {
auth, err := readAuthorization(r)
if err != nil {
blossomError(w, err.Error(), 400)
return
}
if auth == nil {
blossomError(w, "missing \"Authorization\" header", 400)
return
}
if auth.Tags.GetFirst([]string{"t", "upload"}) == nil {
blossomError(w, "invalid \"Authorization\" event \"t\" tag", 403)
return
}
mimetype := r.Header.Get("X-Content-Type")
exts, _ := mime.ExtensionsByType(mimetype)
var ext string
if len(exts) > 0 {
ext = exts[0]
}
// get the file size from the incoming header
size, _ := strconv.Atoi(r.Header.Get("X-Content-Length"))
for _, rb := range bs.RejectUpload {
reject, reason, code := rb(r.Context(), auth, size, ext)
if reject {
blossomError(w, reason, code)
return
}
}
}
func (bs BlossomServer) handleUpload(w http.ResponseWriter, r *http.Request) {
auth, err := readAuthorization(r)
if err != nil {
blossomError(w, "invalid \"Authorization\": "+err.Error(), 400)
return
}
if auth == nil {
blossomError(w, "missing \"Authorization\" header", 400)
return
}
if auth.Tags.GetFirst([]string{"t", "upload"}) == nil {
blossomError(w, "invalid \"Authorization\" event \"t\" tag", 403)
return
}
// get the file size from the incoming header
size, _ := strconv.Atoi(r.Header.Get("Content-Length"))
if size == 0 {
blossomError(w, "missing \"Content-Length\" header", 400)
return
}
// read first bytes of upload so we can find out the filetype
b := make([]byte, min(50, size), size)
if _, err = r.Body.Read(b); err != nil {
blossomError(w, "failed to read initial bytes of upload body: "+err.Error(), 400)
return
}
var ext string
if ft, _ := magic.Lookup(b); ft != nil {
ext = "." + ft.Extension
} else {
// if we can't find, use the filetype given by the upload header
mimetype := r.Header.Get("Content-Type")
ext = getExtension(mimetype)
}
// run the reject hooks
for _, ru := range bs.RejectUpload {
reject, reason, code := ru(r.Context(), auth, size, ext)
if reject {
blossomError(w, reason, code)
return
}
}
// if it passes then we have to read the entire thing into memory so we can compute the sha256
for {
var n int
n, err = r.Body.Read(b[len(b):cap(b)])
b = b[:len(b)+n]
if err != nil {
if err == io.EOF {
err = nil
}
break
}
if len(b) == cap(b) {
// add more capacity (let append pick how much)
// if Content-Length was correct we shouldn't reach this
b = append(b, 0)[:len(b)]
}
}
if err != nil {
blossomError(w, "failed to read upload body: "+err.Error(), 400)
return
}
hash := sha256.Sum256(b)
hhash := hex.EncodeToString(hash[:])
// keep track of the blob descriptor
bd := BlobDescriptor{
URL: bs.ServiceURL + "/" + hhash + ext,
SHA256: hhash,
Size: len(b),
Type: mime.TypeByExtension(ext),
Uploaded: nostr.Now(),
}
if err := bs.Store.Keep(r.Context(), bd, auth.PubKey); err != nil {
blossomError(w, "failed to save event: "+err.Error(), 400)
return
}
// save actual blob
for _, sb := range bs.StoreBlob {
if err := sb(r.Context(), hhash, b); err != nil {
blossomError(w, "failed to save: "+err.Error(), 500)
return
}
}
// return response
json.NewEncoder(w).Encode(bd)
}
func (bs BlossomServer) handleGetBlob(w http.ResponseWriter, r *http.Request) {
spl := strings.SplitN(r.URL.Path, ".", 2)
hhash := spl[0]
if len(hhash) != 65 {
blossomError(w, "invalid /<sha256>[.ext] path", 400)
return
}
hhash = hhash[1:]
// check for an authorization tag, if any
auth, err := readAuthorization(r)
if err != nil {
blossomError(w, err.Error(), 400)
return
}
// if there is one, we check if it has the extra requirements
if auth != nil {
if auth.Tags.GetFirst([]string{"t", "get"}) == nil {
blossomError(w, "invalid \"Authorization\" event \"t\" tag", 403)
return
}
if auth.Tags.GetFirst([]string{"x", hhash}) == nil &&
auth.Tags.GetFirst([]string{"server", bs.ServiceURL}) == nil {
blossomError(w, "invalid \"Authorization\" event \"x\" or \"server\" tag", 403)
return
}
}
for _, rg := range bs.RejectGet {
reject, reason, code := rg(r.Context(), auth, hhash)
if reject {
blossomError(w, reason, code)
return
}
}
var ext string
if len(spl) == 2 {
ext = "." + spl[1]
}
for _, lb := range bs.LoadBlob {
reader, _ := lb(r.Context(), hhash)
if reader != nil {
// use unix epoch as the time if we can't find the descriptor
// as described in the http.ServeContent documentation
t := time.Unix(0, 0)
descriptor, err := bs.Store.Get(r.Context(), hhash)
if err == nil && descriptor != nil {
t = descriptor.Uploaded.Time()
}
http.ServeContent(w, r, hhash+ext, t, reader)
return
}
}
blossomError(w, "file not found", 404)
return
}
func (bs BlossomServer) handleHasBlob(w http.ResponseWriter, r *http.Request) {
spl := strings.SplitN(r.URL.Path, ".", 2)
hhash := spl[0]
if len(hhash) != 65 {
blossomError(w, "invalid /<sha256>[.ext] path", 400)
return
}
hhash = hhash[1:]
bd, err := bs.Store.Get(r.Context(), hhash)
if err != nil {
blossomError(w, "failed to query: "+err.Error(), 500)
return
}
if bd == nil {
blossomError(w, "file not found", 404)
return
}
return
}
func (bs BlossomServer) handleList(w http.ResponseWriter, r *http.Request) {
// check for an authorization tag, if any
auth, err := readAuthorization(r)
if err != nil {
blossomError(w, err.Error(), 400)
return
}
// if there is one, we check if it has the extra requirements
if auth != nil {
if auth.Tags.GetFirst([]string{"t", "list"}) == nil {
blossomError(w, "invalid \"Authorization\" event \"t\" tag", 403)
return
}
}
pubkey := r.URL.Path[6:]
for _, rl := range bs.RejectList {
reject, reason, code := rl(r.Context(), auth, pubkey)
if reject {
blossomError(w, reason, code)
return
}
}
ch, err := bs.Store.List(r.Context(), pubkey)
if err != nil {
blossomError(w, "failed to query: "+err.Error(), 500)
return
}
w.Write([]byte{'['})
enc := json.NewEncoder(w)
first := true
for bd := range ch {
if !first {
w.Write([]byte{','})
} else {
first = false
}
enc.Encode(bd)
}
w.Write([]byte{']'})
}
func (bs BlossomServer) handleDelete(w http.ResponseWriter, r *http.Request) {
auth, err := readAuthorization(r)
if err != nil {
blossomError(w, err.Error(), 400)
return
}
if auth != nil {
if auth.Tags.GetFirst([]string{"t", "delete"}) == nil {
blossomError(w, "invalid \"Authorization\" event \"t\" tag", 403)
return
}
}
spl := strings.SplitN(r.URL.Path, ".", 2)
hhash := spl[0]
if len(hhash) != 65 {
blossomError(w, "invalid /<sha256>[.ext] path", 400)
return
}
hhash = hhash[1:]
if auth.Tags.GetFirst([]string{"x", hhash}) == nil &&
auth.Tags.GetFirst([]string{"server", bs.ServiceURL}) == nil {
blossomError(w, "invalid \"Authorization\" event \"x\" or \"server\" tag", 403)
return
}
// should we accept this delete?
for _, rd := range bs.RejectDelete {
reject, reason, code := rd(r.Context(), auth, hhash)
if reject {
blossomError(w, reason, code)
return
}
}
// delete the entry that links this blob to this author
if err := bs.Store.Delete(r.Context(), hhash, auth.PubKey); err != nil {
blossomError(w, "delete of blob entry failed: "+err.Error(), 500)
return
}
// we will actually only delete the file if no one else owns it
if bd, err := bs.Store.Get(r.Context(), hhash); err == nil && bd == nil {
for _, del := range bs.DeleteBlob {
if err := del(r.Context(), hhash); err != nil {
blossomError(w, "failed to delete blob: "+err.Error(), 500)
return
}
}
}
}
func (bs BlossomServer) handleMirror(w http.ResponseWriter, r *http.Request) {
}
func (bs BlossomServer) handleNegentropy(w http.ResponseWriter, r *http.Request) {
}

70
blossom/server.go Normal file
View File

@@ -0,0 +1,70 @@
package blossom
import (
"context"
"io"
"net/http"
"strings"
"github.com/fiatjaf/khatru"
"github.com/nbd-wtf/go-nostr"
)
type BlossomServer struct {
ServiceURL string
Store BlobIndex
StoreBlob []func(ctx context.Context, sha256 string, body []byte) error
LoadBlob []func(ctx context.Context, sha256 string) (io.ReadSeeker, error)
DeleteBlob []func(ctx context.Context, sha256 string) error
RejectUpload []func(ctx context.Context, auth *nostr.Event, size int, ext string) (bool, string, int)
RejectGet []func(ctx context.Context, auth *nostr.Event, sha256 string) (bool, string, int)
RejectList []func(ctx context.Context, auth *nostr.Event, pubkey string) (bool, string, int)
RejectDelete []func(ctx context.Context, auth *nostr.Event, sha256 string) (bool, string, int)
}
func New(rl *khatru.Relay, serviceURL string) *BlossomServer {
bs := &BlossomServer{
ServiceURL: serviceURL,
}
base := rl.Router()
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/upload" {
if r.Method == "PUT" {
bs.handleUpload(w, r)
return
} else if r.Method == "HEAD" {
bs.handleUploadCheck(w, r)
return
}
}
if strings.HasPrefix(r.URL.Path, "/list/") && r.Method == "GET" {
bs.handleList(w, r)
return
}
if len(strings.SplitN(r.URL.Path, ".", 2)[0]) == 65 {
if r.Method == "HEAD" {
bs.handleHasBlob(w, r)
return
} else if r.Method == "GET" {
bs.handleGetBlob(w, r)
return
} else if r.Method == "DELETE" {
bs.handleDelete(w, r)
return
}
}
base.ServeHTTP(w, r)
})
rl.SetRouter(mux)
return bs
}

37
blossom/utils.go Normal file
View File

@@ -0,0 +1,37 @@
package blossom
import (
"mime"
"net/http"
)
func blossomError(w http.ResponseWriter, msg string, code int) {
w.Header().Add("X-Reason", msg)
w.WriteHeader(code)
}
func getExtension(mimetype string) string {
if mimetype == "" {
return ""
}
switch mimetype {
case "image/jpeg":
return ".jpg"
case "image/gif":
return ".gif"
case "image/png":
return ".png"
case "image/webp":
return ".webp"
case "video/mp4":
return ".mp4"
}
exts, _ := mime.ExtensionsByType(mimetype)
if len(exts) > 0 {
return exts[0]
}
return ""
}

42
examples/blossom/main.go Normal file
View File

@@ -0,0 +1,42 @@
package main
import (
"context"
"fmt"
"io"
"net/http"
"strings"
"github.com/fiatjaf/eventstore/badger"
"github.com/fiatjaf/khatru"
"github.com/fiatjaf/khatru/blossom"
)
func main() {
relay := khatru.NewRelay()
db := &badger.BadgerBackend{Path: "/tmp/khatru-badger-blossom-tmp"}
if err := db.Init(); err != nil {
panic(err)
}
relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent)
relay.QueryEvents = append(relay.QueryEvents, db.QueryEvents)
relay.CountEvents = append(relay.CountEvents, db.CountEvents)
relay.DeleteEvent = append(relay.DeleteEvent, db.DeleteEvent)
bl := blossom.New(relay, "http://localhost:3334")
bl.Store = blossom.EventStoreBlobIndexWrapper{Store: db, ServiceURL: bl.ServiceURL}
bl.StoreBlob = append(bl.StoreBlob, func(ctx context.Context, sha256 string, body []byte) error {
fmt.Println("storing", sha256, len(body))
return nil
})
bl.LoadBlob = append(bl.LoadBlob, func(ctx context.Context, sha256 string) (io.ReadSeeker, error) {
fmt.Println("loading", sha256)
blob := strings.NewReader("aaaaa")
return blob, nil
})
fmt.Println("running on :3334")
http.ListenAndServe(":3334", relay)
}

View File

@@ -15,6 +15,10 @@ func (rl *Relay) Router() *http.ServeMux {
return rl.serveMux
}
func (rl *Relay) SetRouter(mux *http.ServeMux) {
rl.serveMux = mux
}
// Start creates an http server and starts listening on given host and port.
func (rl *Relay) Start(host string, port int, started ...chan bool) error {
addr := net.JoinHostPort(host, strconv.Itoa(port))

7
go.mod
View File

@@ -5,10 +5,11 @@ go 1.23.1
require (
github.com/bep/debounce v1.2.1
github.com/fasthttp/websocket v1.5.7
github.com/fiatjaf/eventstore v0.12.0
github.com/nbd-wtf/go-nostr v0.40.0
github.com/fiatjaf/eventstore v0.13.0
github.com/liamg/magic v0.0.1
github.com/nbd-wtf/go-nostr v0.42.0
github.com/puzpuzpuz/xsync/v3 v3.4.0
github.com/rs/cors v1.7.0
github.com/rs/cors v1.11.1
github.com/stretchr/testify v1.9.0
)

14
go.sum
View File

@@ -53,8 +53,8 @@ github.com/fasthttp/websocket v1.5.7 h1:0a6o2OfeATvtGgoMKleURhLT6JqWPg7fYfWnH4KH
github.com/fasthttp/websocket v1.5.7/go.mod h1:bC4fxSono9czeXHQUVKxsC0sNjbm7lPJR04GDFqClfU=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fiatjaf/eventstore v0.12.0 h1:ZdL+dZkIgBgIp5A3+3XLdPg/uucv5Tiws6DHzNfZG4M=
github.com/fiatjaf/eventstore v0.12.0/go.mod h1:PxeYbZ3MsH0XLobANsp6c0cJjJYkfmBJ3TwrplFy/08=
github.com/fiatjaf/eventstore v0.13.0 h1:60cE/oIUdVHoE6aOayjIyubiQIhMW6jezLjdvcl29Y4=
github.com/fiatjaf/eventstore v0.13.0/go.mod h1:XOl5B6WGBX1a0ww6s3WT94QVOmye/6zDTtyWHVtHQ5U=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
@@ -111,6 +111,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/liamg/magic v0.0.1 h1:Ru22ElY+sCh6RvRTWjQzKKCxsEco8hE0co8n1qe7TBM=
github.com/liamg/magic v0.0.1/go.mod h1:yQkOmZZI52EA+SQ2xyHpVw8fNvTBruF873Y+Vt6S+fk=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
@@ -119,8 +121,8 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/nbd-wtf/go-nostr v0.40.0 h1:ea7FlOsm4kO1071Tm4OT0lXTcyleiZCT9Ll4XERjTZw=
github.com/nbd-wtf/go-nostr v0.40.0/go.mod h1:FBa4FBJO7NuANvkeKSlrf0BIyxGufmrUbuelr6Q4Ick=
github.com/nbd-wtf/go-nostr v0.42.0 h1:EofWfXEhKic9AYVf4RHuXZr+kKUZE2jVyJtJByNe1rE=
github.com/nbd-wtf/go-nostr v0.42.0/go.mod h1:FBa4FBJO7NuANvkeKSlrf0BIyxGufmrUbuelr6Q4Ick=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -132,6 +134,8 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
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.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
@@ -235,5 +239,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -26,14 +26,28 @@ func (rl *Relay) ServeHTTP(w http.ResponseWriter, r *http.Request) {
rl.ServiceURL = getServiceBaseURL(r)
}
corsMiddleware := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{
http.MethodHead,
http.MethodGet,
http.MethodPost,
http.MethodPut,
http.MethodPatch,
http.MethodDelete,
},
AllowedHeaders: []string{"Authorization", "*"},
MaxAge: 86400,
})
if r.Header.Get("Upgrade") == "websocket" {
rl.HandleWebsocket(w, r)
} else if r.Header.Get("Accept") == "application/nostr+json" {
cors.AllowAll().Handler(http.HandlerFunc(rl.HandleNIP11)).ServeHTTP(w, r)
corsMiddleware.Handler(http.HandlerFunc(rl.HandleNIP11)).ServeHTTP(w, r)
} else if r.Header.Get("Content-Type") == "application/nostr+json+rpc" {
cors.AllowAll().Handler(http.HandlerFunc(rl.HandleNIP86)).ServeHTTP(w, r)
corsMiddleware.Handler(http.HandlerFunc(rl.HandleNIP86)).ServeHTTP(w, r)
} else {
rl.serveMux.ServeHTTP(w, r)
corsMiddleware.Handler(rl.serveMux).ServeHTTP(w, r)
}
}

View File

@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"github.com/fiatjaf/eventstore"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip77/negentropy"
"github.com/nbd-wtf/go-nostr/nip77/negentropy/storage/vector"
@@ -16,6 +17,8 @@ type NegentropySession struct {
}
func (rl *Relay) startNegentropySession(ctx context.Context, filter nostr.Filter) (*vector.Vector, error) {
ctx = eventstore.SetNegentropy(ctx)
// do the same overwrite/reject flow we do in normal REQs
for _, ovw := range rl.OverwriteFilter {
ovw(ctx, &filter)

View File

@@ -72,7 +72,7 @@ func RestrictToSpecifiedKinds(allowEphemeral bool, kinds ...uint16) func(context
slices.Sort(kinds)
return func(ctx context.Context, event *nostr.Event) (reject bool, msg string) {
if allowEphemeral && event.IsEphemeral() {
if allowEphemeral && nostr.IsEphemeralKind(event.Kind) {
return false, ""
}