flatten relay interface
This commit is contained in:
@@ -18,7 +18,6 @@ import (
|
|||||||
"realy.mleku.dev/kind"
|
"realy.mleku.dev/kind"
|
||||||
"realy.mleku.dev/log"
|
"realy.mleku.dev/log"
|
||||||
"realy.mleku.dev/realy/helpers"
|
"realy.mleku.dev/realy/helpers"
|
||||||
"realy.mleku.dev/relay"
|
|
||||||
"realy.mleku.dev/sha256"
|
"realy.mleku.dev/sha256"
|
||||||
"realy.mleku.dev/tag"
|
"realy.mleku.dev/tag"
|
||||||
)
|
)
|
||||||
@@ -32,7 +31,7 @@ type EventInput struct {
|
|||||||
// EventOutput is the return parameters for the HTTP API Event method.
|
// EventOutput is the return parameters for the HTTP API Event method.
|
||||||
type EventOutput struct{ Body string }
|
type EventOutput struct{ Body string }
|
||||||
|
|
||||||
// RegisterEvent is the implementatino of the HTTP API Event method.
|
// RegisterEvent is the implementation of the HTTP API Event method.
|
||||||
func (x *Operations) RegisterEvent(api huma.API) {
|
func (x *Operations) RegisterEvent(api huma.API) {
|
||||||
name := "Event"
|
name := "Event"
|
||||||
description := "Submit an event"
|
description := "Submit an event"
|
||||||
@@ -61,7 +60,6 @@ func (x *Operations) RegisterEvent(api huma.API) {
|
|||||||
if sto == nil {
|
if sto == nil {
|
||||||
panic("no event store has been set to store event")
|
panic("no event store has been set to store event")
|
||||||
}
|
}
|
||||||
advancedDeleter, _ := sto.(relay.AdvancedDeleter)
|
|
||||||
var valid bool
|
var valid bool
|
||||||
var pubkey []byte
|
var pubkey []byte
|
||||||
valid, pubkey, err = httpauth.CheckAuth(r)
|
valid, pubkey, err = httpauth.CheckAuth(r)
|
||||||
@@ -189,16 +187,10 @@ func (x *Operations) RegisterEvent(api huma.API) {
|
|||||||
err = huma.Error403Forbidden("only author can delete event")
|
err = huma.Error403Forbidden("only author can delete event")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if advancedDeleter != nil {
|
|
||||||
advancedDeleter.BeforeDelete(ctx, t.Value(), ev.Pubkey)
|
|
||||||
}
|
|
||||||
if err = sto.DeleteEvent(ctx, target.EventId()); chk.T(err) {
|
if err = sto.DeleteEvent(ctx, target.EventId()); chk.T(err) {
|
||||||
err = huma.Error500InternalServerError(err.Error())
|
err = huma.Error500InternalServerError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if advancedDeleter != nil {
|
|
||||||
advancedDeleter.AfterDelete(t.Value(), ev.Pubkey)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
res = nil
|
res = nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import (
|
|||||||
"realy.mleku.dev/kinds"
|
"realy.mleku.dev/kinds"
|
||||||
"realy.mleku.dev/log"
|
"realy.mleku.dev/log"
|
||||||
"realy.mleku.dev/realy/helpers"
|
"realy.mleku.dev/realy/helpers"
|
||||||
"realy.mleku.dev/relay"
|
|
||||||
"realy.mleku.dev/store"
|
"realy.mleku.dev/store"
|
||||||
"realy.mleku.dev/tag"
|
"realy.mleku.dev/tag"
|
||||||
"realy.mleku.dev/tags"
|
"realy.mleku.dev/tags"
|
||||||
@@ -128,23 +127,21 @@ func (x *Operations) RegisterFilter(api huma.API) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
allowed := filters.New(f)
|
allowed := filters.New(f)
|
||||||
if accepter, ok := x.Relay().(relay.ReqAcceptor); ok {
|
var accepted, modified bool
|
||||||
var accepted, modified bool
|
allowed, accepted, modified = x.Relay().AcceptReq(x.Context(), r, nil,
|
||||||
allowed, accepted, modified = accepter.AcceptReq(x.Context(), r, nil,
|
filters.New(f), pubkey)
|
||||||
filters.New(f), pubkey)
|
if !accepted {
|
||||||
if !accepted {
|
err = huma.Error401Unauthorized("auth to get access for this filter")
|
||||||
err = huma.Error401Unauthorized("auth to get access for this filter")
|
return
|
||||||
return
|
} else if modified {
|
||||||
} else if modified {
|
log.D.F("filter modified %s", allowed.F[0])
|
||||||
log.D.F("filter modified %s", allowed.F[0])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if len(allowed.F) == 0 {
|
if len(allowed.F) == 0 {
|
||||||
err = huma.Error401Unauthorized("all kinds in event restricted; auth to get access for this filter")
|
err = huma.Error401Unauthorized("all kinds in event restricted; auth to get access for this filter")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if f.Kinds.IsPrivileged() {
|
if f.Kinds.IsPrivileged() {
|
||||||
if auther, ok := x.Relay().(relay.Authenticator); ok && auther.AuthRequired() {
|
if x.Relay().AuthRequired() {
|
||||||
log.T.F("privileged request\n%s", f.Serialize())
|
log.T.F("privileged request\n%s", f.Serialize())
|
||||||
senders := f.Authors
|
senders := f.Authors
|
||||||
receivers := f.Tags.GetAll(tag.New("#p"))
|
receivers := f.Tags.GetAll(tag.New("#p"))
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
"realy.mleku.dev/httpauth"
|
"realy.mleku.dev/httpauth"
|
||||||
"realy.mleku.dev/log"
|
"realy.mleku.dev/log"
|
||||||
"realy.mleku.dev/realy/helpers"
|
"realy.mleku.dev/realy/helpers"
|
||||||
"realy.mleku.dev/relay"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// RelayInput is the parameters for the Event HTTP API method.
|
// RelayInput is the parameters for the Event HTTP API method.
|
||||||
@@ -83,12 +82,7 @@ func (x *Operations) RegisterRelay(api huma.API) {
|
|||||||
err = huma.Error400BadRequest("signature is invalid")
|
err = huma.Error400BadRequest("signature is invalid")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var authRequired bool
|
x.Publisher().Deliver(x.Relay().AuthRequired(), x.PublicReadable(), ev)
|
||||||
var ar relay.Authenticator
|
|
||||||
if ar, ok = x.Relay().(relay.Authenticator); ok {
|
|
||||||
authRequired = ar.AuthRequired()
|
|
||||||
}
|
|
||||||
x.Publisher().Deliver(authRequired, x.PublicReadable(), ev)
|
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import (
|
|||||||
"realy.mleku.dev/kinds"
|
"realy.mleku.dev/kinds"
|
||||||
"realy.mleku.dev/log"
|
"realy.mleku.dev/log"
|
||||||
"realy.mleku.dev/realy/helpers"
|
"realy.mleku.dev/realy/helpers"
|
||||||
"realy.mleku.dev/relay"
|
|
||||||
"realy.mleku.dev/tag"
|
"realy.mleku.dev/tag"
|
||||||
"realy.mleku.dev/tags"
|
"realy.mleku.dev/tags"
|
||||||
)
|
)
|
||||||
@@ -99,24 +98,22 @@ func (x *Operations) RegisterSubscribe(api huma.API) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
allowed := filters.New(f)
|
allowed := filters.New(f)
|
||||||
if accepter, ok := x.Relay().(relay.ReqAcceptor); ok {
|
var accepted, modified bool
|
||||||
var accepted, modified bool
|
allowed, accepted, modified = x.Relay().AcceptReq(x.Context(), r, nil,
|
||||||
allowed, accepted, modified = accepter.AcceptReq(x.Context(), r, nil,
|
filters.New(f),
|
||||||
filters.New(f),
|
pubkey)
|
||||||
pubkey)
|
if !accepted {
|
||||||
if !accepted {
|
err = huma.Error401Unauthorized("auth to get access for this filter")
|
||||||
err = huma.Error401Unauthorized("auth to get access for this filter")
|
return
|
||||||
return
|
} else if modified {
|
||||||
} else if modified {
|
log.D.F("filter modified %s", allowed.F[0])
|
||||||
log.D.F("filter modified %s", allowed.F[0])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if len(allowed.F) == 0 {
|
if len(allowed.F) == 0 {
|
||||||
err = huma.Error401Unauthorized("all kinds in event restricted; auth to get access for this filter")
|
err = huma.Error401Unauthorized("all kinds in event restricted; auth to get access for this filter")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if f.Kinds.IsPrivileged() {
|
if f.Kinds.IsPrivileged() {
|
||||||
if auther, ok := x.Relay().(relay.Authenticator); ok && auther.AuthRequired() {
|
if x.Relay().AuthRequired() {
|
||||||
log.T.F("privileged request\n%s", f.Serialize())
|
log.T.F("privileged request\n%s", f.Serialize())
|
||||||
senders := f.Authors
|
senders := f.Authors
|
||||||
receivers := f.Tags.GetAll(tag.New("#p"))
|
receivers := f.Tags.GetAll(tag.New("#p"))
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ func (s *Server) addEvent(c context.T, rl relay.I, ev *event.T,
|
|||||||
if ev == nil {
|
if ev == nil {
|
||||||
return false, normalize.Invalid.F("empty event")
|
return false, normalize.Invalid.F("empty event")
|
||||||
}
|
}
|
||||||
sto := rl.Storage()
|
|
||||||
advancedSaver, _ := sto.(relay.AdvancedSaver)
|
|
||||||
// don't allow storing event with protected marker as per nip-70 with auth enabled.
|
// don't allow storing event with protected marker as per nip-70 with auth enabled.
|
||||||
if (s.authRequired || !s.publicReadable) && ev.Tags.ContainsProtectedMarker() {
|
if (s.authRequired || !s.publicReadable) && ev.Tags.ContainsProtectedMarker() {
|
||||||
if len(authedPubkey) == 0 || !bytes.Equal(ev.Pubkey, authedPubkey) {
|
if len(authedPubkey) == 0 || !bytes.Equal(ev.Pubkey, authedPubkey) {
|
||||||
@@ -36,9 +34,6 @@ func (s *Server) addEvent(c context.T, rl relay.I, ev *event.T,
|
|||||||
}
|
}
|
||||||
if ev.Kind.IsEphemeral() {
|
if ev.Kind.IsEphemeral() {
|
||||||
} else {
|
} else {
|
||||||
if advancedSaver != nil {
|
|
||||||
advancedSaver.BeforeSave(c, ev)
|
|
||||||
}
|
|
||||||
if saveErr := s.Publish(c, ev); saveErr != nil {
|
if saveErr := s.Publish(c, ev); saveErr != nil {
|
||||||
if errors.Is(saveErr, store.ErrDupEvent) {
|
if errors.Is(saveErr, store.ErrDupEvent) {
|
||||||
return false, normalize.Error.F(saveErr.Error())
|
return false, normalize.Error.F(saveErr.Error())
|
||||||
@@ -56,16 +51,10 @@ func (s *Server) addEvent(c context.T, rl relay.I, ev *event.T,
|
|||||||
return false, normalize.Error.F("failed to save (%s)", errmsg)
|
return false, normalize.Error.F("failed to save (%s)", errmsg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if advancedSaver != nil {
|
|
||||||
advancedSaver.AfterSave(ev)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var authRequired bool
|
|
||||||
if ar, ok := rl.(relay.Authenticator); ok {
|
|
||||||
authRequired = ar.AuthRequired()
|
|
||||||
}
|
}
|
||||||
|
rl.AuthRequired()
|
||||||
// notify subscribers
|
// notify subscribers
|
||||||
s.listeners.Deliver(authRequired, s.publicReadable, ev)
|
s.listeners.Deliver(rl.AuthRequired(), s.publicReadable, ev)
|
||||||
accepted = true
|
accepted = true
|
||||||
log.I.F("event id %0x stored", ev.Id)
|
log.I.F("event id %0x stored", ev.Id)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -8,54 +8,41 @@ import (
|
|||||||
"realy.mleku.dev"
|
"realy.mleku.dev"
|
||||||
"realy.mleku.dev/chk"
|
"realy.mleku.dev/chk"
|
||||||
"realy.mleku.dev/log"
|
"realy.mleku.dev/log"
|
||||||
"realy.mleku.dev/relay"
|
|
||||||
"realy.mleku.dev/relayinfo"
|
"realy.mleku.dev/relayinfo"
|
||||||
"realy.mleku.dev/store"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) handleRelayInfo(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleRelayInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
r.Header.Set("Content-Type", "application/json")
|
r.Header.Set("Content-Type", "application/json")
|
||||||
log.I.Ln("handling relay information document")
|
log.I.Ln("handling relay information document")
|
||||||
var info *relayinfo.T
|
var info *relayinfo.T
|
||||||
if informationer, ok := s.relay.(relay.Informationer); ok {
|
supportedNIPs := relayinfo.GetList(
|
||||||
info = informationer.GetNIP11InformationDocument()
|
relayinfo.BasicProtocol,
|
||||||
} else {
|
relayinfo.EncryptedDirectMessage,
|
||||||
supportedNIPs := relayinfo.GetList(
|
relayinfo.EventDeletion,
|
||||||
relayinfo.BasicProtocol,
|
relayinfo.RelayInformationDocument,
|
||||||
relayinfo.EncryptedDirectMessage,
|
relayinfo.GenericTagQueries,
|
||||||
relayinfo.EventDeletion,
|
relayinfo.NostrMarketplace,
|
||||||
relayinfo.RelayInformationDocument,
|
relayinfo.EventTreatment,
|
||||||
relayinfo.GenericTagQueries,
|
relayinfo.CommandResults,
|
||||||
relayinfo.NostrMarketplace,
|
relayinfo.ParameterizedReplaceableEvents,
|
||||||
relayinfo.EventTreatment,
|
relayinfo.ExpirationTimestamp,
|
||||||
relayinfo.CommandResults,
|
relayinfo.ProtectedEvents,
|
||||||
relayinfo.ParameterizedReplaceableEvents,
|
relayinfo.RelayListMetadata,
|
||||||
relayinfo.ExpirationTimestamp,
|
)
|
||||||
relayinfo.ProtectedEvents,
|
if s.relay.ServiceUrl(r) != "" {
|
||||||
relayinfo.RelayListMetadata,
|
supportedNIPs = append(supportedNIPs, relayinfo.Authentication.N())
|
||||||
)
|
|
||||||
var auther relay.Authenticator
|
|
||||||
if auther, ok = s.relay.(relay.Authenticator); ok && auther.ServiceUrl(r) != "" {
|
|
||||||
supportedNIPs = append(supportedNIPs, relayinfo.Authentication.N())
|
|
||||||
}
|
|
||||||
var storage store.I
|
|
||||||
if storage = s.relay.Storage(); storage != nil {
|
|
||||||
if _, ok = storage.(relay.EventCounter); ok {
|
|
||||||
supportedNIPs = append(supportedNIPs, relayinfo.CountingResults.N())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Sort(supportedNIPs)
|
|
||||||
log.T.Ln("supported NIPs", supportedNIPs)
|
|
||||||
info = &relayinfo.T{Name: s.relay.Name(),
|
|
||||||
Description: realy_lol.Description,
|
|
||||||
Nips: supportedNIPs, Software: realy_lol.URL, Version: realy_lol.Version,
|
|
||||||
Limitation: relayinfo.Limits{
|
|
||||||
MaxLimit: s.maxLimit,
|
|
||||||
AuthRequired: s.authRequired,
|
|
||||||
RestrictedWrites: !s.publicReadable || s.authRequired || len(s.owners) > 0,
|
|
||||||
},
|
|
||||||
Icon: "https://cdn.satellite.earth/ac9778868fbf23b63c47c769a74e163377e6ea94d3f0f31711931663d035c4f6.png"}
|
|
||||||
}
|
}
|
||||||
|
sort.Sort(supportedNIPs)
|
||||||
|
log.T.Ln("supported NIPs", supportedNIPs)
|
||||||
|
info = &relayinfo.T{Name: s.relay.Name(),
|
||||||
|
Description: realy_lol.Description,
|
||||||
|
Nips: supportedNIPs, Software: realy_lol.URL, Version: realy_lol.Version,
|
||||||
|
Limitation: relayinfo.Limits{
|
||||||
|
MaxLimit: s.maxLimit,
|
||||||
|
AuthRequired: s.authRequired,
|
||||||
|
RestrictedWrites: !s.publicReadable || s.authRequired || len(s.owners) > 0,
|
||||||
|
},
|
||||||
|
Icon: "https://cdn.satellite.earth/ac9778868fbf23b63c47c769a74e163377e6ea94d3f0f31711931663d035c4f6.png"}
|
||||||
if err := json.NewEncoder(w).Encode(info); chk.E(err) {
|
if err := json.NewEncoder(w).Encode(info); chk.E(err) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,10 +66,6 @@ func NewServer(sp *ServerParams, opts ...options.O) (s *Server, err error) {
|
|||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(op)
|
opt(op)
|
||||||
}
|
}
|
||||||
var authRequired bool
|
|
||||||
if ar, ok := sp.Rl.(relay.Authenticator); ok {
|
|
||||||
authRequired = ar.AuthRequired()
|
|
||||||
}
|
|
||||||
if storage := sp.Rl.Storage(); storage != nil {
|
if storage := sp.Rl.Storage(); storage != nil {
|
||||||
if err := storage.Init(sp.DbPath); chk.T(err) {
|
if err := storage.Init(sp.DbPath); chk.T(err) {
|
||||||
return nil, fmt.Errorf("storage init: %w", err)
|
return nil, fmt.Errorf("storage init: %w", err)
|
||||||
@@ -83,7 +79,7 @@ func NewServer(sp *ServerParams, opts ...options.O) (s *Server, err error) {
|
|||||||
clients: make(map[*websocket.Conn]struct{}),
|
clients: make(map[*websocket.Conn]struct{}),
|
||||||
mux: serveMux,
|
mux: serveMux,
|
||||||
options: op,
|
options: op,
|
||||||
authRequired: authRequired,
|
authRequired: sp.Rl.AuthRequired(),
|
||||||
publicReadable: sp.PublicReadable,
|
publicReadable: sp.PublicReadable,
|
||||||
maxLimit: sp.MaxLimit,
|
maxLimit: sp.MaxLimit,
|
||||||
admins: sp.Admins,
|
admins: sp.Admins,
|
||||||
@@ -108,13 +104,6 @@ func NewServer(sp *ServerParams, opts ...options.O) (s *Server, err error) {
|
|||||||
s.Shutdown()
|
s.Shutdown()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
if inj, ok := s.relay.(relay.Injector); ok {
|
|
||||||
go func() {
|
|
||||||
for ev := range inj.InjectEvents() {
|
|
||||||
s.listeners.Deliver(s.authRequired, s.publicReadable, ev)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,9 +162,6 @@ func (s *Server) Shutdown() {
|
|||||||
chk.E(s.relay.Storage().Close())
|
chk.E(s.relay.Storage().Close())
|
||||||
log.W.Ln("shutting down relay listener")
|
log.W.Ln("shutting down relay listener")
|
||||||
chk.E(s.httpServer.Shutdown(s.Ctx))
|
chk.E(s.httpServer.Shutdown(s.Ctx))
|
||||||
if f, ok := s.relay.(relay.ShutdownAware); ok {
|
|
||||||
f.OnShutdown(s.Ctx)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Router returns the servemux that handles paths on the HTTP server of the relay.
|
// Router returns the servemux that handles paths on the HTTP server of the relay.
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"realy.mleku.dev/event"
|
"realy.mleku.dev/event"
|
||||||
"realy.mleku.dev/eventid"
|
"realy.mleku.dev/eventid"
|
||||||
"realy.mleku.dev/filter"
|
"realy.mleku.dev/filter"
|
||||||
|
"realy.mleku.dev/filters"
|
||||||
"realy.mleku.dev/store"
|
"realy.mleku.dev/store"
|
||||||
"realy.mleku.dev/units"
|
"realy.mleku.dev/units"
|
||||||
)
|
)
|
||||||
@@ -68,6 +69,25 @@ func (tr *testRelay) AcceptEvent(c context.T, evt *event.T, hr *http.Request, or
|
|||||||
return true, "", nil
|
return true, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tr *testRelay) AcceptReq(c context.T, hr *http.Request, id []byte,
|
||||||
|
ff *filters.T, authedPubkey []byte) (allowed *filters.T, ok bool, modified bool) {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
func (tr *testRelay) AcceptFilter(c context.T, hr *http.Request, f *filter.S,
|
||||||
|
authedPubkey []byte) (allowed *filter.S, ok bool, modified bool) {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
func (tr *testRelay) AuthRequired() bool {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
func (tr *testRelay) ServiceUrl(req *http.Request) (s string) {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
type testStorage struct {
|
type testStorage struct {
|
||||||
init func() error
|
init func() error
|
||||||
close func()
|
close func()
|
||||||
@@ -136,10 +156,3 @@ func (string *testStorage) SaveEvent(c context.T, e *event.T) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (string *testStorage) CountEvents(c context.T, f *filter.T) (int, bool, error) {
|
|
||||||
if fn := string.countEvents; fn != nil {
|
|
||||||
return fn(c, f)
|
|
||||||
}
|
|
||||||
return 0, false, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ import (
|
|||||||
"realy.mleku.dev/event"
|
"realy.mleku.dev/event"
|
||||||
"realy.mleku.dev/filter"
|
"realy.mleku.dev/filter"
|
||||||
"realy.mleku.dev/filters"
|
"realy.mleku.dev/filters"
|
||||||
"realy.mleku.dev/relayinfo"
|
|
||||||
"realy.mleku.dev/store"
|
"realy.mleku.dev/store"
|
||||||
"realy.mleku.dev/ws"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// I is the main interface for implementing a nostr relay.
|
// I is the main interface for implementing a nostr relay.
|
||||||
@@ -41,10 +39,6 @@ type I interface {
|
|||||||
Storage() store.I
|
Storage() store.I
|
||||||
// Owners returns the list of pubkeys designated as owners of the relay.
|
// Owners returns the list of pubkeys designated as owners of the relay.
|
||||||
Owners() [][]byte
|
Owners() [][]byte
|
||||||
}
|
|
||||||
|
|
||||||
// ReqAcceptor is the main interface for implementing a nostr
|
|
||||||
type ReqAcceptor interface {
|
|
||||||
// AcceptReq is called for every nostr request filters received by the
|
// AcceptReq is called for every nostr request filters received by the
|
||||||
// server. If the returned value is true, the filters is passed on to
|
// server. If the returned value is true, the filters is passed on to
|
||||||
// [Storage.QueryEvent].
|
// [Storage.QueryEvent].
|
||||||
@@ -58,70 +52,13 @@ type ReqAcceptor interface {
|
|||||||
// request is for a message that contains their npub in a `p` tag that are
|
// request is for a message that contains their npub in a `p` tag that are
|
||||||
// direct or group chat messages they also can be accepted, enabling full
|
// direct or group chat messages they also can be accepted, enabling full
|
||||||
// support for in/outbox access.
|
// support for in/outbox access.
|
||||||
//
|
|
||||||
// In order to support the ability to respond to
|
|
||||||
AcceptReq(c context.T, hr *http.Request, id []byte, ff *filters.T,
|
AcceptReq(c context.T, hr *http.Request, id []byte, ff *filters.T,
|
||||||
authedPubkey []byte) (allowed *filters.T,
|
authedPubkey []byte) (allowed *filters.T,
|
||||||
ok bool, modified bool)
|
ok bool, modified bool)
|
||||||
}
|
|
||||||
|
|
||||||
type FilterAcceptor interface {
|
|
||||||
// AcceptFilter is basically the same as AcceptReq except it is additional to
|
// AcceptFilter is basically the same as AcceptReq except it is additional to
|
||||||
// enable the simplified filter query type.
|
// enable the simplified filter query type.
|
||||||
AcceptFilter(c context.T, hr *http.Request, f *filter.S,
|
AcceptFilter(c context.T, hr *http.Request, f *filter.S,
|
||||||
authedPubkey []byte) (allowed *filter.S, ok bool, modified bool)
|
authedPubkey []byte) (allowed *filter.S, ok bool, modified bool)
|
||||||
}
|
|
||||||
|
|
||||||
// Authenticator is the interface for implementing NIP-42.
|
|
||||||
// ServiceURL() returns the URL used to verify the "AUTH" event from clients.
|
|
||||||
type Authenticator interface {
|
|
||||||
AuthRequired() bool
|
AuthRequired() bool
|
||||||
ServiceUrl(r *http.Request) string
|
ServiceUrl(r *http.Request) string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Injector interface {
|
|
||||||
InjectEvents() event.C
|
|
||||||
}
|
|
||||||
|
|
||||||
// Informationer is called to compose NIP-11 response to an HTTP request
|
|
||||||
// with application/nostr+json mime type.
|
|
||||||
// See also [I.Name].
|
|
||||||
type Informationer interface {
|
|
||||||
GetNIP11InformationDocument() *relayinfo.T
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebSocketHandler is passed nostr message types unrecognized by the
|
|
||||||
// server. The server handles "EVENT", "REQ" and "CLOSE" messages, as described in NIP-01.
|
|
||||||
type WebSocketHandler interface {
|
|
||||||
HandleUnknownType(ws *ws.Listener, t string, request []byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShutdownAware is called during the server shutdown.
|
|
||||||
// See [Server.Shutdown] for details.
|
|
||||||
type ShutdownAware interface {
|
|
||||||
OnShutdown(context.T)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logger is what [Server] uses to log messages.
|
|
||||||
type Logger interface {
|
|
||||||
Infof(format string, v ...any)
|
|
||||||
Warningf(format string, v ...any)
|
|
||||||
Errorf(format string, v ...any)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AdvancedDeleter methods are called before and after [Storage.DeleteEvent].
|
|
||||||
type AdvancedDeleter interface {
|
|
||||||
BeforeDelete(ctx context.T, id, pubkey []byte)
|
|
||||||
AfterDelete(id, pubkey []byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AdvancedSaver methods are called before and after [Storage.SaveEvent].
|
|
||||||
type AdvancedSaver interface {
|
|
||||||
BeforeSave(context.T, *event.T)
|
|
||||||
AfterSave(*event.T)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EventCounter implements the NIP-45 count API.
|
|
||||||
type EventCounter interface {
|
|
||||||
CountEvents(c context.T, f *filter.T) (count int, approx bool, err error)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,14 +8,13 @@ import (
|
|||||||
"realy.mleku.dev/log"
|
"realy.mleku.dev/log"
|
||||||
"realy.mleku.dev/normalize"
|
"realy.mleku.dev/normalize"
|
||||||
"realy.mleku.dev/realy/interfaces"
|
"realy.mleku.dev/realy/interfaces"
|
||||||
"realy.mleku.dev/relay"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *A) HandleAuth(req []byte,
|
func (a *A) HandleAuth(req []byte,
|
||||||
srv interfaces.Server) (msg []byte) {
|
srv interfaces.Server) (msg []byte) {
|
||||||
|
|
||||||
if auther, ok := srv.Relay().(relay.Authenticator); ok && auther.AuthRequired() {
|
if srv.Relay().AuthRequired() {
|
||||||
svcUrl := auther.ServiceUrl(a.Req())
|
svcUrl := srv.Relay().ServiceUrl(a.Req())
|
||||||
if svcUrl == "" {
|
if svcUrl == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import (
|
|||||||
"realy.mleku.dev/log"
|
"realy.mleku.dev/log"
|
||||||
"realy.mleku.dev/normalize"
|
"realy.mleku.dev/normalize"
|
||||||
"realy.mleku.dev/realy/interfaces"
|
"realy.mleku.dev/realy/interfaces"
|
||||||
"realy.mleku.dev/relay"
|
|
||||||
"realy.mleku.dev/sha256"
|
"realy.mleku.dev/sha256"
|
||||||
"realy.mleku.dev/tag"
|
"realy.mleku.dev/tag"
|
||||||
)
|
)
|
||||||
@@ -32,11 +31,7 @@ func (a *A) HandleEvent(c context.T, req []byte, srv interfaces.Server) (msg []b
|
|||||||
if sto == nil {
|
if sto == nil {
|
||||||
panic("no event store has been set to store event")
|
panic("no event store has been set to store event")
|
||||||
}
|
}
|
||||||
var auther relay.Authenticator
|
|
||||||
if auther, ok = srv.Relay().(relay.Authenticator); ok {
|
|
||||||
}
|
|
||||||
rl := srv.Relay()
|
rl := srv.Relay()
|
||||||
advancedDeleter, _ := sto.(relay.AdvancedDeleter)
|
|
||||||
env := eventenvelope.NewSubmission()
|
env := eventenvelope.NewSubmission()
|
||||||
if rem, err = env.Unmarshal(req); chk.E(err) {
|
if rem, err = env.Unmarshal(req); chk.E(err) {
|
||||||
return
|
return
|
||||||
@@ -52,7 +47,7 @@ func (a *A) HandleEvent(c context.T, req []byte, srv interfaces.Server) (msg []b
|
|||||||
normalize.Blocked.F(notice)).Write(a.Listener); chk.T(err) {
|
normalize.Blocked.F(notice)).Write(a.Listener); chk.T(err) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if auther != nil && auther.AuthRequired() {
|
if rl.AuthRequired() {
|
||||||
if !a.AuthRequested() {
|
if !a.AuthRequested() {
|
||||||
a.RequestAuth()
|
a.RequestAuth()
|
||||||
log.I.F("requesting auth from client %s", a.RealRemote())
|
log.I.F("requesting auth from client %s", a.RealRemote())
|
||||||
@@ -230,9 +225,6 @@ func (a *A) HandleEvent(c context.T, req []byte, srv interfaces.Server) (msg []b
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if advancedDeleter != nil {
|
|
||||||
advancedDeleter.BeforeDelete(c, t.Value(), env.Pubkey)
|
|
||||||
}
|
|
||||||
if err = sto.DeleteEvent(c, target.EventId()); chk.T(err) {
|
if err = sto.DeleteEvent(c, target.EventId()); chk.T(err) {
|
||||||
if err = okenvelope.NewFrom(env.Id, false,
|
if err = okenvelope.NewFrom(env.Id, false,
|
||||||
normalize.Error.F(err.Error())).Write(a.Listener); chk.E(err) {
|
normalize.Error.F(err.Error())).Write(a.Listener); chk.E(err) {
|
||||||
@@ -240,9 +232,6 @@ func (a *A) HandleEvent(c context.T, req []byte, srv interfaces.Server) (msg []b
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if advancedDeleter != nil {
|
|
||||||
advancedDeleter.AfterDelete(t.Value(), env.Pubkey)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
res = nil
|
res = nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"realy.mleku.dev/envelopes/noticeenvelope"
|
"realy.mleku.dev/envelopes/noticeenvelope"
|
||||||
"realy.mleku.dev/envelopes/reqenvelope"
|
"realy.mleku.dev/envelopes/reqenvelope"
|
||||||
"realy.mleku.dev/log"
|
"realy.mleku.dev/log"
|
||||||
"realy.mleku.dev/relay"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *A) HandleMessage(msg []byte) {
|
func (a *A) HandleMessage(msg []byte) {
|
||||||
@@ -22,7 +21,6 @@ func (a *A) HandleMessage(msg []byte) {
|
|||||||
if t, rem = envelopes.Identify(msg); chk.E(err) {
|
if t, rem = envelopes.Identify(msg); chk.E(err) {
|
||||||
notice = []byte(err.Error())
|
notice = []byte(err.Error())
|
||||||
}
|
}
|
||||||
rl := a.Relay()
|
|
||||||
switch t {
|
switch t {
|
||||||
case eventenvelope.L:
|
case eventenvelope.L:
|
||||||
notice = a.HandleEvent(a.Context(), rem, a.Server)
|
notice = a.HandleEvent(a.Context(), rem, a.Server)
|
||||||
@@ -33,11 +31,7 @@ func (a *A) HandleMessage(msg []byte) {
|
|||||||
case authenvelope.L:
|
case authenvelope.L:
|
||||||
notice = a.HandleAuth(rem, a.Server)
|
notice = a.HandleAuth(rem, a.Server)
|
||||||
default:
|
default:
|
||||||
if wsh, ok := rl.(relay.WebSocketHandler); ok {
|
notice = []byte(fmt.Sprintf("unknown envelope type %s\n%s", t, rem))
|
||||||
wsh.HandleUnknownType(a.Listener, t, rem)
|
|
||||||
} else {
|
|
||||||
notice = []byte(fmt.Sprintf("unknown envelope type %s\n%s", t, rem))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if len(notice) > 0 {
|
if len(notice) > 0 {
|
||||||
log.D.F("notice->%s %s", a.RealRemote(), notice)
|
log.D.F("notice->%s %s", a.RealRemote(), notice)
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import (
|
|||||||
"realy.mleku.dev/realy/interfaces"
|
"realy.mleku.dev/realy/interfaces"
|
||||||
"realy.mleku.dev/realy/options"
|
"realy.mleku.dev/realy/options"
|
||||||
"realy.mleku.dev/realy/pointers"
|
"realy.mleku.dev/realy/pointers"
|
||||||
"realy.mleku.dev/relay"
|
|
||||||
"realy.mleku.dev/tag"
|
"realy.mleku.dev/tag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,13 +30,6 @@ func (a *A) HandleReq(
|
|||||||
c context.T, req []byte,
|
c context.T, req []byte,
|
||||||
skipEventFunc options.SkipEventFunc, srv interfaces.Server) (r []byte) {
|
skipEventFunc options.SkipEventFunc, srv interfaces.Server) (r []byte) {
|
||||||
|
|
||||||
var ok bool
|
|
||||||
var accepter relay.ReqAcceptor
|
|
||||||
if accepter, ok = srv.Relay().(relay.ReqAcceptor); ok {
|
|
||||||
}
|
|
||||||
var auther relay.Authenticator
|
|
||||||
if auther, ok = srv.Relay().(relay.Authenticator); ok {
|
|
||||||
}
|
|
||||||
sto := srv.Storage()
|
sto := srv.Storage()
|
||||||
var err error
|
var err error
|
||||||
var rem []byte
|
var rem []byte
|
||||||
@@ -49,32 +41,30 @@ func (a *A) HandleReq(
|
|||||||
log.I.F("extra '%s'", rem)
|
log.I.F("extra '%s'", rem)
|
||||||
}
|
}
|
||||||
allowed := env.Filters
|
allowed := env.Filters
|
||||||
if accepter != nil {
|
var accepted, modified bool
|
||||||
var accepted, modified bool
|
allowed, accepted, modified = srv.Relay().AcceptReq(c, a.Req(), env.Subscription.T,
|
||||||
allowed, accepted, modified = accepter.AcceptReq(c, a.Req(), env.Subscription.T,
|
env.Filters,
|
||||||
env.Filters,
|
[]byte(a.Authed()))
|
||||||
[]byte(a.Authed()))
|
if !accepted || allowed == nil || modified {
|
||||||
if !accepted || allowed == nil || modified {
|
if srv.Relay().AuthRequired() && !a.AuthRequested() {
|
||||||
if auther != nil && auther.AuthRequired() && !a.AuthRequested() {
|
a.RequestAuth()
|
||||||
a.RequestAuth()
|
if err = closedenvelope.NewFrom(env.Subscription,
|
||||||
if err = closedenvelope.NewFrom(env.Subscription,
|
normalize.AuthRequired.F("auth required for request processing")).Write(a.Listener); chk.E(err) {
|
||||||
normalize.AuthRequired.F("auth required for request processing")).Write(a.Listener); chk.E(err) {
|
}
|
||||||
}
|
log.T.F("requesting auth from client from %s, challenge '%s'",
|
||||||
log.T.F("requesting auth from client from %s, challenge '%s'",
|
a.RealRemote(), a.Challenge())
|
||||||
a.RealRemote(), a.Challenge())
|
if err = authenvelope.NewChallengeWith(a.Challenge()).Write(a.Listener); chk.E(err) {
|
||||||
if err = authenvelope.NewChallengeWith(a.Challenge()).Write(a.Listener); chk.E(err) {
|
return
|
||||||
return
|
}
|
||||||
}
|
if !modified {
|
||||||
if !modified {
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// log.I.ToSliceOfBytes("handling %s", env.Marshal(nil))
|
// log.I.ToSliceOfBytes("handling %s", env.Marshal(nil))
|
||||||
if allowed != env.Filters {
|
if allowed != env.Filters {
|
||||||
defer func() {
|
defer func() {
|
||||||
if auther != nil && auther.AuthRequired() &&
|
if srv.Relay().AuthRequired() &&
|
||||||
!a.AuthRequested() {
|
!a.AuthRequested() {
|
||||||
a.RequestAuth()
|
a.RequestAuth()
|
||||||
if err = closedenvelope.NewFrom(env.Subscription,
|
if err = closedenvelope.NewFrom(env.Subscription,
|
||||||
@@ -101,7 +91,7 @@ func (a *A) HandleReq(
|
|||||||
}
|
}
|
||||||
i = *f.Limit
|
i = *f.Limit
|
||||||
}
|
}
|
||||||
if auther != nil && auther.AuthRequired() {
|
if srv.Relay().AuthRequired() {
|
||||||
if f.Kinds.IsPrivileged() {
|
if f.Kinds.IsPrivileged() {
|
||||||
log.T.F("privileged request\n%s", f.Serialize())
|
log.T.F("privileged request\n%s", f.Serialize())
|
||||||
senders := f.Authors
|
senders := f.Authors
|
||||||
@@ -177,40 +167,34 @@ func (a *A) HandleReq(
|
|||||||
// if auth is required, kind is privileged and there is no authed pubkey, skip
|
// if auth is required, kind is privileged and there is no authed pubkey, skip
|
||||||
if srv.AuthRequired() && ev.Kind.IsPrivileged() && len(aut) == 0 {
|
if srv.AuthRequired() && ev.Kind.IsPrivileged() && len(aut) == 0 {
|
||||||
// log.I.ToSliceOfBytes("skipping event because event kind is %d and no auth", ev.Kind.K)
|
// log.I.ToSliceOfBytes("skipping event because event kind is %d and no auth", ev.Kind.K)
|
||||||
if auther != nil {
|
if err = closedenvelope.NewFrom(env.Subscription,
|
||||||
if err = closedenvelope.NewFrom(env.Subscription,
|
normalize.AuthRequired.F("auth required for processing request due to presence of privileged kinds (DMs, app specific data)")).Write(a.Listener); chk.E(err) {
|
||||||
normalize.AuthRequired.F("auth required for processing request due to presence of privileged kinds (DMs, app specific data)")).Write(a.Listener); chk.E(err) {
|
|
||||||
}
|
|
||||||
log.I.F("requesting auth from client from %s", a.RealRemote())
|
|
||||||
if err = authenvelope.NewChallengeWith(a.Challenge()).Write(a.Listener); chk.E(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
notice := normalize.Restricted.F("this realy does not serve DMs or Application Specific Data " +
|
|
||||||
"to unauthenticated users or to npubs not found in the event tags or author fields, does your " +
|
|
||||||
"client implement NIP-42?")
|
|
||||||
return notice
|
|
||||||
}
|
}
|
||||||
continue
|
log.I.F("requesting auth from client from %s", a.RealRemote())
|
||||||
|
if err = authenvelope.NewChallengeWith(a.Challenge()).Write(a.Listener); chk.E(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
notice := normalize.Restricted.F("this realy does not serve DMs or Application Specific Data " +
|
||||||
|
"to unauthenticated users or to npubs not found in the event tags or author fields, does your " +
|
||||||
|
"client implement NIP-42?")
|
||||||
|
return notice
|
||||||
}
|
}
|
||||||
// if the authed pubkey is not present in the pubkey or p tags, skip
|
// if the authed pubkey is not present in the pubkey or p tags, skip
|
||||||
if ev.Kind.IsPrivileged() && (!bytes.Equal(ev.Pubkey, aut) ||
|
if ev.Kind.IsPrivileged() && (!bytes.Equal(ev.Pubkey, aut) ||
|
||||||
!receivers.ContainsAny([]byte("#p"), tag.New(a.AuthedBytes()))) {
|
!receivers.ContainsAny([]byte("#p"), tag.New(a.AuthedBytes()))) {
|
||||||
// log.I.ToSliceOfBytes("skipping event %0x because authed key %0x is in neither pubkey or p tag",
|
// log.I.ToSliceOfBytes("skipping event %0x because authed key %0x is in neither pubkey or p tag",
|
||||||
// ev.Id, aut)
|
// ev.Id, aut)
|
||||||
if auther != nil {
|
if err = closedenvelope.NewFrom(env.Subscription,
|
||||||
if err = closedenvelope.NewFrom(env.Subscription,
|
normalize.AuthRequired.F("auth required for processing request due to presence of privileged kinds (DMs, app specific data)")).Write(a.Listener); chk.E(err) {
|
||||||
normalize.AuthRequired.F("auth required for processing request due to presence of privileged kinds (DMs, app specific data)")).Write(a.Listener); chk.E(err) {
|
|
||||||
}
|
|
||||||
log.I.F("requesting auth from client from %s", a.RealRemote())
|
|
||||||
if err = authenvelope.NewChallengeWith(a.Challenge()).Write(a.Listener); chk.E(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
notice := normalize.Restricted.F("this realy does not serve DMs or Application Specific Data " +
|
|
||||||
"to unauthenticated users or to npubs not found in the event tags or author fields, does your " +
|
|
||||||
"client implement NIP-42?")
|
|
||||||
return notice
|
|
||||||
}
|
}
|
||||||
continue
|
log.I.F("requesting auth from client from %s", a.RealRemote())
|
||||||
|
if err = authenvelope.NewChallengeWith(a.Challenge()).Write(a.Listener); chk.E(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
notice := normalize.Restricted.F("this realy does not serve DMs or Application Specific Data " +
|
||||||
|
"to unauthenticated users or to npubs not found in the event tags or author fields, does your " +
|
||||||
|
"client implement NIP-42?")
|
||||||
|
return notice
|
||||||
}
|
}
|
||||||
tmp = append(tmp, ev)
|
tmp = append(tmp, ev)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user