Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -3,16 +3,20 @@ package relay
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"orly.dev/pkg/encoders/event"
|
||||
"orly.dev/pkg/interfaces/relay"
|
||||
"orly.dev/pkg/interfaces/store"
|
||||
"orly.dev/pkg/protocol/socketapi"
|
||||
"orly.dev/pkg/utils/context"
|
||||
"orly.dev/pkg/utils/normalize"
|
||||
)
|
||||
|
||||
var (
|
||||
NIP20prefixmatcher = regexp.MustCompile(`^\w+: `)
|
||||
)
|
||||
|
||||
// AddEvent processes an incoming event, saves it if valid, and delivers it to
|
||||
// subscribers.
|
||||
//
|
||||
@@ -50,9 +54,7 @@ import (
|
||||
// - Returns a boolean indicating whether the event was accepted and any
|
||||
// relevant message.
|
||||
func (s *Server) AddEvent(
|
||||
c context.T, rl relay.I, ev *event.E,
|
||||
hr *http.Request, origin string,
|
||||
authedPubkey []byte,
|
||||
c context.T, rl relay.I, ev *event.E, hr *http.Request, origin string,
|
||||
) (accepted bool, message []byte) {
|
||||
|
||||
if ev == nil {
|
||||
@@ -65,9 +67,12 @@ func (s *Server) AddEvent(
|
||||
return false, []byte(saveErr.Error())
|
||||
}
|
||||
errmsg := saveErr.Error()
|
||||
if socketapi.NIP20prefixmatcher.MatchString(errmsg) {
|
||||
if NIP20prefixmatcher.MatchString(errmsg) {
|
||||
if strings.Contains(errmsg, "tombstone") {
|
||||
return false, normalize.Error.F("event was deleted, not storing it again")
|
||||
return false, normalize.Error.F(
|
||||
"%s event was deleted, not storing it again",
|
||||
origin,
|
||||
)
|
||||
}
|
||||
if strings.HasPrefix(errmsg, string(normalize.Blocked)) {
|
||||
return false, []byte(errmsg)
|
||||
|
||||
@@ -35,7 +35,20 @@ func NewChallengeWith[V string | []byte](challenge V) *Challenge {
|
||||
// Label returns the label of a authenvelope.Challenge.
|
||||
func (en *Challenge) Label() string { return L }
|
||||
|
||||
// Write the authenvelope.Challenge to a provided io.Writer.
|
||||
// Write encodes and writes the Challenge instance to the provided writer.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - w (io.Writer): The destination where the encoded data will be written.
|
||||
//
|
||||
// # Return Values
|
||||
//
|
||||
// - err (error): An error if writing to the writer fails.
|
||||
//
|
||||
// # Expected behaviour
|
||||
//
|
||||
// Encodes the Challenge instance into a byte slice using Marshal, logs the
|
||||
// encoded challenge, and writes it to the provided io.Writer.
|
||||
func (en *Challenge) Write(w io.Writer) (err error) {
|
||||
var b []byte
|
||||
b = en.Marshal(b)
|
||||
@@ -44,8 +57,26 @@ func (en *Challenge) Write(w io.Writer) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Marshal a authenvelope.Challenge to minified JSON, appending to a provided destination
|
||||
// slice. Note that this ensures correct string escaping on the challenge field.
|
||||
// Marshal encodes the Challenge instance into a byte slice, formatting it as
|
||||
// a JSON-like structure with a specific label and escaping rules applied to
|
||||
// its content.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - dst ([]byte): The destination buffer where the encoded data will be written.
|
||||
//
|
||||
// # Return Values
|
||||
//
|
||||
// - b ([]byte): The byte slice containing the encoded Challenge data.
|
||||
//
|
||||
// # Expected behaviour
|
||||
//
|
||||
// - Prepares the destination buffer and applies a label to it.
|
||||
//
|
||||
// - Escapes the challenge content according to Nostr-specific rules before
|
||||
// appending it to the output.
|
||||
//
|
||||
// - Returns the resulting byte slice with the complete encoded structure.
|
||||
func (en *Challenge) Marshal(dst []byte) (b []byte) {
|
||||
b = dst
|
||||
var err error
|
||||
@@ -63,9 +94,24 @@ func (en *Challenge) Marshal(dst []byte) (b []byte) {
|
||||
return
|
||||
}
|
||||
|
||||
// Unmarshal a authenvelope.Challenge from minified JSON, returning the remainder after the
|
||||
// end of the envelope. Note that this ensures the challenge string was
|
||||
// correctly escaped by NIP-01 escaping rules.
|
||||
// Unmarshal parses the provided byte slice and extracts the challenge value,
|
||||
// leaving any remaining bytes after parsing.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - b ([]byte): The byte slice containing the encoded challenge data.
|
||||
//
|
||||
// # Return Values
|
||||
//
|
||||
// - r ([]byte): Any remaining bytes after parsing the challenge.
|
||||
//
|
||||
// - err (error): An error if parsing fails.
|
||||
//
|
||||
// # Expected behaviour
|
||||
//
|
||||
// - Extracts the quoted challenge string from the input byte slice.
|
||||
//
|
||||
// - Trims any trailing characters following the closing quote.
|
||||
func (en *Challenge) Unmarshal(b []byte) (r []byte, err error) {
|
||||
r = b
|
||||
if en.Challenge, r, err = text2.UnmarshalQuoted(r); chk.E(err) {
|
||||
@@ -80,8 +126,26 @@ func (en *Challenge) Unmarshal(b []byte) (r []byte, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// ParseChallenge reads a authenvelope.Challenge encoded in minified JSON and unpacks it to
|
||||
// the runtime format.
|
||||
// ParseChallenge parses the provided byte slice into a new Challenge instance,
|
||||
// extracting the challenge value and returning any remaining bytes after parsing.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - b ([]byte): The byte slice containing the encoded challenge data.
|
||||
//
|
||||
// # Return Values
|
||||
//
|
||||
// - t (*Challenge): A pointer to the newly created and populated Challenge
|
||||
// instance.
|
||||
//
|
||||
// - rem ([]byte): Any remaining bytes in the input slice after parsing.
|
||||
//
|
||||
// - err (error): An error if parsing fails.
|
||||
//
|
||||
// # Expected behaviour
|
||||
//
|
||||
// Parses the byte slice into a new Challenge instance using Unmarshal,
|
||||
// returning any remaining bytes and an error if parsing fails.
|
||||
func ParseChallenge(b []byte) (t *Challenge, rem []byte, err error) {
|
||||
t = NewChallenge()
|
||||
if rem, err = t.Unmarshal(b); chk.E(err) {
|
||||
|
||||
@@ -20,12 +20,8 @@ type I interface {
|
||||
authedPubkey []byte, remote string,
|
||||
) (allowed *filters.T, accept bool, modified bool)
|
||||
AddEvent(
|
||||
c context.T, rl relay.I, ev *event.E, hr *http.Request,
|
||||
origin string, authedPubkey []byte,
|
||||
) (
|
||||
accepted bool,
|
||||
message []byte,
|
||||
)
|
||||
c context.T, rl relay.I, ev *event.E, hr *http.Request, origin string,
|
||||
) (accepted bool, message []byte)
|
||||
Context() context.T
|
||||
Publisher() *publish.S
|
||||
Publish(c context.T, evt *event.E) (err error)
|
||||
|
||||
62
pkg/protocol/openapi/huma.go
Normal file
62
pkg/protocol/openapi/huma.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/danielgtaylor/huma/v2"
|
||||
"github.com/danielgtaylor/huma/v2/adapters/humago"
|
||||
|
||||
"orly.dev/pkg/protocol/servemux"
|
||||
"orly.dev/pkg/utils/lol"
|
||||
)
|
||||
|
||||
// ExposeMiddleware adds the http.Request and http.ResponseWriter to the context
|
||||
// for the Operations handler.
|
||||
func ExposeMiddleware(ctx huma.Context, next func(huma.Context)) {
|
||||
lol.Tracer("ExposeMiddleware")
|
||||
defer func() { lol.Tracer("end ExposeMiddleware") }()
|
||||
// Unwrap the request and response objects.
|
||||
r, w := humago.Unwrap(ctx)
|
||||
ctx = huma.WithValue(ctx, "http-request", r)
|
||||
ctx = huma.WithValue(ctx, "http-response", w)
|
||||
next(ctx)
|
||||
}
|
||||
|
||||
// NewHuma creates a new huma.API with a Scalar docs UI, and a middleware that allows methods to
|
||||
// access the http.Request and http.ResponseWriter.
|
||||
func NewHuma(
|
||||
router *servemux.S, name, version, description string,
|
||||
) (api huma.API) {
|
||||
lol.Tracer("NewHuma", name, version, description)
|
||||
defer func() { lol.Tracer("end NewHuma") }()
|
||||
config := huma.DefaultConfig(name, version)
|
||||
config.Info.Description = description
|
||||
config.DocsPath = ""
|
||||
config.OpenAPIPath = "/api/openapi"
|
||||
router.HandleFunc(
|
||||
"/api", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
w.Write(
|
||||
[]byte(`<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>realy HTTP API UI</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1" />
|
||||
</head>
|
||||
<body>
|
||||
<script
|
||||
id="api-reference"
|
||||
data-url="/api/openapi.json"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
||||
</body>
|
||||
</html>`),
|
||||
)
|
||||
},
|
||||
)
|
||||
api = humago.New(router, config)
|
||||
api.UseMiddleware(ExposeMiddleware)
|
||||
return
|
||||
}
|
||||
27
pkg/protocol/openapi/openapi.go
Normal file
27
pkg/protocol/openapi/openapi.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"github.com/danielgtaylor/huma/v2"
|
||||
|
||||
"orly.dev/pkg/interfaces/server"
|
||||
"orly.dev/pkg/protocol/servemux"
|
||||
"orly.dev/pkg/utils/lol"
|
||||
)
|
||||
|
||||
type Operations struct {
|
||||
server.I
|
||||
path string
|
||||
*servemux.S
|
||||
}
|
||||
|
||||
// New creates a new openapi.Operations and registers its methods.
|
||||
func New(
|
||||
s server.I, name, version, description string, path string,
|
||||
sm *servemux.S,
|
||||
) {
|
||||
lol.Tracer("New", name, version, description, path)
|
||||
defer func() { lol.Tracer("end New") }()
|
||||
a := NewHuma(sm, name, version, description)
|
||||
huma.AutoRegister(a, &Operations{I: s, path: path})
|
||||
return
|
||||
}
|
||||
@@ -161,16 +161,6 @@ func (a *A) HandleEvent(
|
||||
if len(split) != 3 {
|
||||
continue
|
||||
}
|
||||
// Check if the deletion event is trying to delete itself
|
||||
if bytes.Equal(split[2], env.E.Id) {
|
||||
if err = Ok.Blocked(
|
||||
a, env,
|
||||
"deletion event cannot reference its own ID",
|
||||
); chk.E(err) {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
var pk []byte
|
||||
if pk, err = hex.DecAppend(nil, split[1]); chk.E(err) {
|
||||
if err = Ok.Invalid(
|
||||
@@ -185,7 +175,8 @@ func (a *A) HandleEvent(
|
||||
kin := ints.New(uint16(0))
|
||||
if _, err = kin.Unmarshal(split[0]); chk.E(err) {
|
||||
if err = Ok.Invalid(
|
||||
a, env, "delete event a tag kind value invalid: %s",
|
||||
a, env, "delete event a tag kind value "+
|
||||
"invalid: %s",
|
||||
t.Value(),
|
||||
); chk.E(err) {
|
||||
return
|
||||
@@ -195,7 +186,8 @@ func (a *A) HandleEvent(
|
||||
kk := kind.New(kin.Uint16())
|
||||
if kk.Equal(kind.Deletion) {
|
||||
if err = Ok.Blocked(
|
||||
a, env, "delete event kind may not be deleted",
|
||||
a, env, "delete event kind may not be "+
|
||||
"deleted",
|
||||
); chk.E(err) {
|
||||
return
|
||||
}
|
||||
@@ -204,7 +196,8 @@ func (a *A) HandleEvent(
|
||||
if !kk.IsParameterizedReplaceable() {
|
||||
if err = Ok.Error(
|
||||
a, env,
|
||||
"delete tags with a tags containing non-parameterized-replaceable events can't be processed",
|
||||
"delete tags with a tags containing "+
|
||||
"non-parameterized-replaceable events can't be processed",
|
||||
); chk.E(err) {
|
||||
return
|
||||
}
|
||||
@@ -325,9 +318,7 @@ func (a *A) HandleEvent(
|
||||
}
|
||||
}
|
||||
var reason []byte
|
||||
ok, reason = srv.AddEvent(
|
||||
c, rl, env.E, a.Req(), a.RealRemote(), a.Listener.AuthedPubkey(),
|
||||
)
|
||||
ok, reason = srv.AddEvent(c, rl, env.E, a.Req(), a.RealRemote())
|
||||
log.I.F("event %0x added %v, %s", env.E.Id, ok, reason)
|
||||
if err = okenvelope.NewFrom(env.E.Id, ok).Write(a.Listener); chk.E(err) {
|
||||
return
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
// corresponding handler method, generates a notice for errors or unknown types,
|
||||
// logs the notice, and writes it back to the listener if required.
|
||||
func (a *A) HandleMessage(msg []byte) {
|
||||
log.T.F("received message:\n%s", string(msg))
|
||||
log.T.F("%s received message:\n%s", a.Listener.RealRemote(), string(msg))
|
||||
var notice []byte
|
||||
var err error
|
||||
var t string
|
||||
|
||||
@@ -10,16 +10,11 @@ import (
|
||||
"orly.dev/pkg/protocol/ws"
|
||||
"orly.dev/pkg/utils/chk"
|
||||
"orly.dev/pkg/utils/log"
|
||||
"regexp"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const Type = "socketapi"
|
||||
|
||||
var (
|
||||
NIP20prefixmatcher = regexp.MustCompile(`^\w+: `)
|
||||
)
|
||||
|
||||
// Map is a map of filters associated with a collection of ws.Listener
|
||||
// connections.
|
||||
type Map map[*ws.Listener]map[string]*filters.T
|
||||
|
||||
Reference in New Issue
Block a user