mirror of
https://github.com/fiatjaf/khatru.git
synced 2026-04-10 07:26:50 +02:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f99b9827a | ||
|
|
8d5fc324f7 | ||
|
|
668c41b988 | ||
|
|
3c802caff5 | ||
|
|
e81416f41e | ||
|
|
e12d30f247 |
@@ -1,3 +1,11 @@
|
|||||||
|
<div>
|
||||||
|
<p><b>This repository is in maintenance mode and adventurous programmers are encouraged to try <a href="https://pkg.go.dev/fiatjaf.com/nostr/khatru"><code>fiatjaf.com/nostr/khatru@master</code></a> instead.</b></p>
|
||||||
|
|
||||||
|
<p>The core codebase and functionality are the same, but the API breaks a little bit (for good reason) and there are some new features.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
# 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)
|
||||||
|
|
||||||
[](https://github.com/fiatjaf/khatru/actions/workflows/test.yml)
|
[](https://github.com/fiatjaf/khatru/actions/workflows/test.yml)
|
||||||
|
|||||||
@@ -125,13 +125,17 @@ func (bs BlossomServer) handleUpload(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
hash := sha256.Sum256(b)
|
hash := sha256.Sum256(b)
|
||||||
hhash := hex.EncodeToString(hash[:])
|
hhash := hex.EncodeToString(hash[:])
|
||||||
|
mimeType := mime.TypeByExtension(ext)
|
||||||
|
if mimeType == "" {
|
||||||
|
mimeType = "application/octet-stream"
|
||||||
|
}
|
||||||
|
|
||||||
// keep track of the blob descriptor
|
// keep track of the blob descriptor
|
||||||
bd := BlobDescriptor{
|
bd := BlobDescriptor{
|
||||||
URL: bs.ServiceURL + "/" + hhash + ext,
|
URL: bs.ServiceURL + "/" + hhash + ext,
|
||||||
SHA256: hhash,
|
SHA256: hhash,
|
||||||
Size: len(b),
|
Size: len(b),
|
||||||
Type: mime.TypeByExtension(ext),
|
Type: mimeType,
|
||||||
Uploaded: nostr.Now(),
|
Uploaded: nostr.Now(),
|
||||||
}
|
}
|
||||||
if err := bs.Store.Keep(r.Context(), bd, auth.PubKey); err != nil {
|
if err := bs.Store.Keep(r.Context(), bd, auth.PubKey); err != nil {
|
||||||
@@ -141,7 +145,7 @@ func (bs BlossomServer) handleUpload(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// save actual blob
|
// save actual blob
|
||||||
for _, sb := range bs.StoreBlob {
|
for _, sb := range bs.StoreBlob {
|
||||||
if err := sb(r.Context(), hhash, b); err != nil {
|
if err := sb(r.Context(), hhash, ext, b); err != nil {
|
||||||
blossomError(w, "failed to save: "+err.Error(), 500)
|
blossomError(w, "failed to save: "+err.Error(), 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -182,19 +186,25 @@ func (bs BlossomServer) handleGetBlob(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ext string
|
||||||
|
bd, err := bs.Store.Get(r.Context(), hhash)
|
||||||
|
if err != nil {
|
||||||
|
// can't find the BlobDescriptor, try to get the extension from the URL
|
||||||
|
if len(spl) == 2 {
|
||||||
|
ext = spl[1]
|
||||||
|
}
|
||||||
|
} else if bd != nil {
|
||||||
|
ext = getExtension(bd.Type)
|
||||||
|
}
|
||||||
|
|
||||||
for _, rg := range bs.RejectGet {
|
for _, rg := range bs.RejectGet {
|
||||||
reject, reason, code := rg(r.Context(), auth, hhash)
|
reject, reason, code := rg(r.Context(), auth, hhash, ext)
|
||||||
if reject {
|
if reject {
|
||||||
blossomError(w, reason, code)
|
blossomError(w, reason, code)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var ext string
|
|
||||||
if len(spl) == 2 {
|
|
||||||
ext = spl[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(bs.RedirectGet) > 0 {
|
if len(bs.RedirectGet) > 0 {
|
||||||
for _, redirect := range bs.RedirectGet {
|
for _, redirect := range bs.RedirectGet {
|
||||||
redirectURL, code, err := redirect(r.Context(), hhash, ext)
|
redirectURL, code, err := redirect(r.Context(), hhash, ext)
|
||||||
@@ -215,7 +225,7 @@ func (bs BlossomServer) handleGetBlob(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, lb := range bs.LoadBlob {
|
for _, lb := range bs.LoadBlob {
|
||||||
reader, _ := lb(r.Context(), hhash)
|
reader, _ := lb(r.Context(), hhash, ext)
|
||||||
if reader != nil {
|
if reader != nil {
|
||||||
// use unix epoch as the time if we can't find the descriptor
|
// use unix epoch as the time if we can't find the descriptor
|
||||||
// as described in the http.ServeContent documentation
|
// as described in the http.ServeContent documentation
|
||||||
@@ -335,9 +345,20 @@ func (bs BlossomServer) handleDelete(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ext string
|
||||||
|
bd, err := bs.Store.Get(r.Context(), hhash)
|
||||||
|
if err != nil {
|
||||||
|
// can't find the BlobDescriptor, try to get the extension from the URL
|
||||||
|
if len(spl) == 2 {
|
||||||
|
ext = spl[1]
|
||||||
|
}
|
||||||
|
} else if bd != nil {
|
||||||
|
ext = getExtension(bd.Type)
|
||||||
|
}
|
||||||
|
|
||||||
// should we accept this delete?
|
// should we accept this delete?
|
||||||
for _, rd := range bs.RejectDelete {
|
for _, rd := range bs.RejectDelete {
|
||||||
reject, reason, code := rd(r.Context(), auth, hhash)
|
reject, reason, code := rd(r.Context(), auth, hhash, ext)
|
||||||
if reject {
|
if reject {
|
||||||
blossomError(w, reason, code)
|
blossomError(w, reason, code)
|
||||||
return
|
return
|
||||||
@@ -353,7 +374,7 @@ func (bs BlossomServer) handleDelete(w http.ResponseWriter, r *http.Request) {
|
|||||||
// we will actually only delete the file if no one else owns it
|
// 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 {
|
if bd, err := bs.Store.Get(r.Context(), hhash); err == nil && bd == nil {
|
||||||
for _, del := range bs.DeleteBlob {
|
for _, del := range bs.DeleteBlob {
|
||||||
if err := del(r.Context(), hhash); err != nil {
|
if err := del(r.Context(), hhash, ext); err != nil {
|
||||||
blossomError(w, "failed to delete blob: "+err.Error(), 500)
|
blossomError(w, "failed to delete blob: "+err.Error(), 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -442,13 +463,12 @@ func (bs BlossomServer) handleMirror(w http.ResponseWriter, r *http.Request) {
|
|||||||
// get content type and extension
|
// get content type and extension
|
||||||
var ext string
|
var ext string
|
||||||
contentType := resp.Header.Get("Content-Type")
|
contentType := resp.Header.Get("Content-Type")
|
||||||
if contentType != "" {
|
if contentType != "" { // First try to get the extension from the Content-Type header
|
||||||
ext = getExtension(contentType)
|
ext = getExtension(contentType)
|
||||||
} else {
|
} else if ft, _ := magic.Lookup(b); ft != nil { // Else try to infer extension from the file content
|
||||||
// Try to detect from URL extension
|
ext = "." + ft.Extension
|
||||||
if idx := strings.LastIndex(body.URL, "."); idx >= 0 {
|
} else if idx := strings.LastIndex(body.URL, "."); idx >= 0 { // Else, try to get the extension from the URL
|
||||||
ext = body.URL[idx:]
|
ext = body.URL[idx:]
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// run reject hooks
|
// run reject hooks
|
||||||
@@ -477,7 +497,7 @@ func (bs BlossomServer) handleMirror(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// store actual blob
|
// store actual blob
|
||||||
for _, sb := range bs.StoreBlob {
|
for _, sb := range bs.StoreBlob {
|
||||||
if err := sb(r.Context(), hhash, b); err != nil {
|
if err := sb(r.Context(), hhash, ext, b); err != nil {
|
||||||
blossomError(w, "failed to save blob: "+err.Error(), 500)
|
blossomError(w, "failed to save blob: "+err.Error(), 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,16 +14,16 @@ type BlossomServer struct {
|
|||||||
ServiceURL string
|
ServiceURL string
|
||||||
Store BlobIndex
|
Store BlobIndex
|
||||||
|
|
||||||
StoreBlob []func(ctx context.Context, sha256 string, body []byte) error
|
StoreBlob []func(ctx context.Context, sha256 string, ext string, body []byte) error
|
||||||
LoadBlob []func(ctx context.Context, sha256 string) (io.ReadSeeker, error)
|
LoadBlob []func(ctx context.Context, sha256 string, ext string) (io.ReadSeeker, error)
|
||||||
DeleteBlob []func(ctx context.Context, sha256 string) error
|
DeleteBlob []func(ctx context.Context, sha256 string, ext string) error
|
||||||
ReceiveReport []func(ctx context.Context, reportEvt *nostr.Event) error
|
ReceiveReport []func(ctx context.Context, reportEvt *nostr.Event) error
|
||||||
RedirectGet []func(ctx context.Context, sha256 string, fileExtension string) (url string, code int, err error)
|
RedirectGet []func(ctx context.Context, sha256 string, ext string) (url string, code int, err error)
|
||||||
|
|
||||||
RejectUpload []func(ctx context.Context, auth *nostr.Event, size int, ext string) (bool, string, int)
|
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)
|
RejectGet []func(ctx context.Context, auth *nostr.Event, sha256 string, ext string) (bool, string, int)
|
||||||
RejectList []func(ctx context.Context, auth *nostr.Event, pubkey 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)
|
RejectDelete []func(ctx context.Context, auth *nostr.Event, sha256 string, ext string) (bool, string, int)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerOption represents a functional option for configuring a BlossomServer
|
// ServerOption represents a functional option for configuring a BlossomServer
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func (rl *Relay) handleDeleteRequest(ctx context.Context, evt *nostr.Event) erro
|
|||||||
case "e":
|
case "e":
|
||||||
f = nostr.Filter{IDs: []string{tag[1]}}
|
f = nostr.Filter{IDs: []string{tag[1]}}
|
||||||
case "a":
|
case "a":
|
||||||
spl := strings.Split(tag[1], ":")
|
spl := strings.SplitN(tag[1], ":", 3)
|
||||||
if len(spl) != 3 {
|
if len(spl) != 3 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,15 +22,15 @@ func main() {
|
|||||||
bl.Store = blossom.EventStoreBlobIndexWrapper{Store: blobdb, ServiceURL: bl.ServiceURL}
|
bl.Store = blossom.EventStoreBlobIndexWrapper{Store: blobdb, ServiceURL: bl.ServiceURL}
|
||||||
|
|
||||||
// implement the required storage functions
|
// implement the required storage functions
|
||||||
bl.StoreBlob = append(bl.StoreBlob, func(ctx context.Context, sha256 string, body []byte) error {
|
bl.StoreBlob = append(bl.StoreBlob, func(ctx context.Context, sha256 string, ext string, body []byte) error {
|
||||||
// store the blob data somewhere
|
// store the blob data somewhere
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
bl.LoadBlob = append(bl.LoadBlob, func(ctx context.Context, sha256 string) (io.ReadSeeker, error) {
|
bl.LoadBlob = append(bl.LoadBlob, func(ctx context.Context, sha256 string, ext string) (io.ReadSeeker, error) {
|
||||||
// load and return the blob data
|
// load and return the blob data
|
||||||
return nil, nil
|
return nil, nil
|
||||||
})
|
})
|
||||||
bl.DeleteBlob = append(bl.DeleteBlob, func(ctx context.Context, sha256 string) error {
|
bl.DeleteBlob = append(bl.DeleteBlob, func(ctx context.Context, sha256 string, ext string) error {
|
||||||
// delete the blob data
|
// delete the blob data
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -31,11 +31,11 @@ func main() {
|
|||||||
}
|
}
|
||||||
bl := blossom.New(relay, "http://localhost:3334")
|
bl := blossom.New(relay, "http://localhost:3334")
|
||||||
bl.Store = blossom.EventStoreBlobIndexWrapper{Store: bdb, ServiceURL: bl.ServiceURL}
|
bl.Store = blossom.EventStoreBlobIndexWrapper{Store: bdb, ServiceURL: bl.ServiceURL}
|
||||||
bl.StoreBlob = append(bl.StoreBlob, func(ctx context.Context, sha256 string, body []byte) error {
|
bl.StoreBlob = append(bl.StoreBlob, func(ctx context.Context, sha256 string, ext string, body []byte) error {
|
||||||
fmt.Println("storing", sha256, len(body))
|
fmt.Println("storing", sha256, ext, len(body))
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
bl.LoadBlob = append(bl.LoadBlob, func(ctx context.Context, sha256 string) (io.ReadSeeker, error) {
|
bl.LoadBlob = append(bl.LoadBlob, func(ctx context.Context, sha256 string, ext string) (io.ReadSeeker, error) {
|
||||||
fmt.Println("loading", sha256)
|
fmt.Println("loading", sha256)
|
||||||
blob := strings.NewReader("aaaaa")
|
blob := strings.NewReader("aaaaa")
|
||||||
return blob, nil
|
return blob, nil
|
||||||
|
|||||||
2
nip86.go
2
nip86.go
@@ -201,7 +201,7 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) {
|
|||||||
case nip86.ListBannedEvents:
|
case nip86.ListBannedEvents:
|
||||||
if rl.ManagementAPI.ListBannedEvents == nil {
|
if rl.ManagementAPI.ListBannedEvents == nil {
|
||||||
resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName())
|
resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName())
|
||||||
} else if result, err := rl.ManagementAPI.ListEventsNeedingModeration(ctx); err != nil {
|
} else if result, err := rl.ManagementAPI.ListBannedEvents(ctx); err != nil {
|
||||||
resp.Error = err.Error()
|
resp.Error = err.Error()
|
||||||
} else {
|
} else {
|
||||||
resp.Result = result
|
resp.Result = result
|
||||||
|
|||||||
@@ -29,6 +29,18 @@ func EventPubKeyRateLimiter(tokensPerInterval int, interval time.Duration, maxTo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func EventAuthedPubKeyRateLimiter(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) {
|
||||||
|
user := khatru.GetAuthed(ctx)
|
||||||
|
if user == "" {
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
return rl(user), "rate-limited: slow down, please"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ConnectionRateLimiter(tokensPerInterval int, interval time.Duration, maxTokens int) func(r *http.Request) bool {
|
func ConnectionRateLimiter(tokensPerInterval int, interval time.Duration, maxTokens int) func(r *http.Request) bool {
|
||||||
rl := startRateLimitSystem[string](tokensPerInterval, interval, maxTokens)
|
rl := startRateLimitSystem[string](tokensPerInterval, interval, maxTokens)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user