flatten relay interface

This commit is contained in:
2025-04-22 17:54:09 -01:06
parent fda2f638fb
commit 399e166927
13 changed files with 114 additions and 256 deletions

View File

@@ -18,7 +18,6 @@ import (
"realy.mleku.dev/kind"
"realy.mleku.dev/log"
"realy.mleku.dev/realy/helpers"
"realy.mleku.dev/relay"
"realy.mleku.dev/sha256"
"realy.mleku.dev/tag"
)
@@ -32,7 +31,7 @@ type EventInput struct {
// EventOutput is the return parameters for the HTTP API Event method.
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) {
name := "Event"
description := "Submit an event"
@@ -61,7 +60,6 @@ func (x *Operations) RegisterEvent(api huma.API) {
if sto == nil {
panic("no event store has been set to store event")
}
advancedDeleter, _ := sto.(relay.AdvancedDeleter)
var valid bool
var pubkey []byte
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")
return
}
if advancedDeleter != nil {
advancedDeleter.BeforeDelete(ctx, t.Value(), ev.Pubkey)
}
if err = sto.DeleteEvent(ctx, target.EventId()); chk.T(err) {
err = huma.Error500InternalServerError(err.Error())
return
}
if advancedDeleter != nil {
advancedDeleter.AfterDelete(t.Value(), ev.Pubkey)
}
}
res = nil
}

View File

@@ -20,7 +20,6 @@ import (
"realy.mleku.dev/kinds"
"realy.mleku.dev/log"
"realy.mleku.dev/realy/helpers"
"realy.mleku.dev/relay"
"realy.mleku.dev/store"
"realy.mleku.dev/tag"
"realy.mleku.dev/tags"
@@ -128,9 +127,8 @@ func (x *Operations) RegisterFilter(api huma.API) {
return
}
allowed := filters.New(f)
if accepter, ok := x.Relay().(relay.ReqAcceptor); ok {
var accepted, modified bool
allowed, accepted, modified = accepter.AcceptReq(x.Context(), r, nil,
allowed, accepted, modified = x.Relay().AcceptReq(x.Context(), r, nil,
filters.New(f), pubkey)
if !accepted {
err = huma.Error401Unauthorized("auth to get access for this filter")
@@ -138,13 +136,12 @@ func (x *Operations) RegisterFilter(api huma.API) {
} else if modified {
log.D.F("filter modified %s", allowed.F[0])
}
}
if len(allowed.F) == 0 {
err = huma.Error401Unauthorized("all kinds in event restricted; auth to get access for this filter")
return
}
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())
senders := f.Authors
receivers := f.Tags.GetAll(tag.New("#p"))

View File

@@ -13,7 +13,6 @@ import (
"realy.mleku.dev/httpauth"
"realy.mleku.dev/log"
"realy.mleku.dev/realy/helpers"
"realy.mleku.dev/relay"
)
// 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")
return
}
var authRequired bool
var ar relay.Authenticator
if ar, ok = x.Relay().(relay.Authenticator); ok {
authRequired = ar.AuthRequired()
}
x.Publisher().Deliver(authRequired, x.PublicReadable(), ev)
x.Publisher().Deliver(x.Relay().AuthRequired(), x.PublicReadable(), ev)
return
})
}

View File

@@ -19,7 +19,6 @@ import (
"realy.mleku.dev/kinds"
"realy.mleku.dev/log"
"realy.mleku.dev/realy/helpers"
"realy.mleku.dev/relay"
"realy.mleku.dev/tag"
"realy.mleku.dev/tags"
)
@@ -99,9 +98,8 @@ func (x *Operations) RegisterSubscribe(api huma.API) {
return
}
allowed := filters.New(f)
if accepter, ok := x.Relay().(relay.ReqAcceptor); ok {
var accepted, modified bool
allowed, accepted, modified = accepter.AcceptReq(x.Context(), r, nil,
allowed, accepted, modified = x.Relay().AcceptReq(x.Context(), r, nil,
filters.New(f),
pubkey)
if !accepted {
@@ -110,13 +108,12 @@ func (x *Operations) RegisterSubscribe(api huma.API) {
} else if modified {
log.D.F("filter modified %s", allowed.F[0])
}
}
if len(allowed.F) == 0 {
err = huma.Error401Unauthorized("all kinds in event restricted; auth to get access for this filter")
return
}
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())
senders := f.Authors
receivers := f.Tags.GetAll(tag.New("#p"))

View File

@@ -23,8 +23,6 @@ func (s *Server) addEvent(c context.T, rl relay.I, ev *event.T,
if ev == nil {
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.
if (s.authRequired || !s.publicReadable) && ev.Tags.ContainsProtectedMarker() {
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() {
} else {
if advancedSaver != nil {
advancedSaver.BeforeSave(c, ev)
}
if saveErr := s.Publish(c, ev); saveErr != nil {
if errors.Is(saveErr, store.ErrDupEvent) {
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)
}
}
if advancedSaver != nil {
advancedSaver.AfterSave(ev)
}
}
var authRequired bool
if ar, ok := rl.(relay.Authenticator); ok {
authRequired = ar.AuthRequired()
}
rl.AuthRequired()
// notify subscribers
s.listeners.Deliver(authRequired, s.publicReadable, ev)
s.listeners.Deliver(rl.AuthRequired(), s.publicReadable, ev)
accepted = true
log.I.F("event id %0x stored", ev.Id)
return

View File

@@ -8,18 +8,13 @@ import (
"realy.mleku.dev"
"realy.mleku.dev/chk"
"realy.mleku.dev/log"
"realy.mleku.dev/relay"
"realy.mleku.dev/relayinfo"
"realy.mleku.dev/store"
)
func (s *Server) handleRelayInfo(w http.ResponseWriter, r *http.Request) {
r.Header.Set("Content-Type", "application/json")
log.I.Ln("handling relay information document")
var info *relayinfo.T
if informationer, ok := s.relay.(relay.Informationer); ok {
info = informationer.GetNIP11InformationDocument()
} else {
supportedNIPs := relayinfo.GetList(
relayinfo.BasicProtocol,
relayinfo.EncryptedDirectMessage,
@@ -34,16 +29,9 @@ func (s *Server) handleRelayInfo(w http.ResponseWriter, r *http.Request) {
relayinfo.ProtectedEvents,
relayinfo.RelayListMetadata,
)
var auther relay.Authenticator
if auther, ok = s.relay.(relay.Authenticator); ok && auther.ServiceUrl(r) != "" {
if s.relay.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(),
@@ -55,7 +43,6 @@ func (s *Server) handleRelayInfo(w http.ResponseWriter, r *http.Request) {
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) {
}
}

View File

@@ -66,10 +66,6 @@ func NewServer(sp *ServerParams, opts ...options.O) (s *Server, err error) {
for _, opt := range opts {
opt(op)
}
var authRequired bool
if ar, ok := sp.Rl.(relay.Authenticator); ok {
authRequired = ar.AuthRequired()
}
if storage := sp.Rl.Storage(); storage != nil {
if err := storage.Init(sp.DbPath); chk.T(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{}),
mux: serveMux,
options: op,
authRequired: authRequired,
authRequired: sp.Rl.AuthRequired(),
publicReadable: sp.PublicReadable,
maxLimit: sp.MaxLimit,
admins: sp.Admins,
@@ -108,13 +104,6 @@ func NewServer(sp *ServerParams, opts ...options.O) (s *Server, err error) {
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
}
@@ -173,9 +162,6 @@ func (s *Server) Shutdown() {
chk.E(s.relay.Storage().Close())
log.W.Ln("shutting down relay listener")
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.

View File

@@ -9,6 +9,7 @@ import (
"realy.mleku.dev/event"
"realy.mleku.dev/eventid"
"realy.mleku.dev/filter"
"realy.mleku.dev/filters"
"realy.mleku.dev/store"
"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
}
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 {
init func() error
close func()
@@ -136,10 +156,3 @@ func (string *testStorage) SaveEvent(c context.T, e *event.T) error {
}
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
}

View File

@@ -9,9 +9,7 @@ import (
"realy.mleku.dev/event"
"realy.mleku.dev/filter"
"realy.mleku.dev/filters"
"realy.mleku.dev/relayinfo"
"realy.mleku.dev/store"
"realy.mleku.dev/ws"
)
// I is the main interface for implementing a nostr relay.
@@ -41,10 +39,6 @@ type I interface {
Storage() store.I
// Owners returns the list of pubkeys designated as owners of the relay.
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
// server. If the returned value is true, the filters is passed on to
// [Storage.QueryEvent].
@@ -58,70 +52,13 @@ type ReqAcceptor interface {
// 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
// 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,
authedPubkey []byte) (allowed *filters.T,
ok bool, modified bool)
}
type FilterAcceptor interface {
// AcceptFilter is basically the same as AcceptReq except it is additional to
// enable the simplified filter query type.
AcceptFilter(c context.T, hr *http.Request, f *filter.S,
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
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)
}

View File

@@ -8,14 +8,13 @@ import (
"realy.mleku.dev/log"
"realy.mleku.dev/normalize"
"realy.mleku.dev/realy/interfaces"
"realy.mleku.dev/relay"
)
func (a *A) HandleAuth(req []byte,
srv interfaces.Server) (msg []byte) {
if auther, ok := srv.Relay().(relay.Authenticator); ok && auther.AuthRequired() {
svcUrl := auther.ServiceUrl(a.Req())
if srv.Relay().AuthRequired() {
svcUrl := srv.Relay().ServiceUrl(a.Req())
if svcUrl == "" {
return
}

View File

@@ -17,7 +17,6 @@ import (
"realy.mleku.dev/log"
"realy.mleku.dev/normalize"
"realy.mleku.dev/realy/interfaces"
"realy.mleku.dev/relay"
"realy.mleku.dev/sha256"
"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 {
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()
advancedDeleter, _ := sto.(relay.AdvancedDeleter)
env := eventenvelope.NewSubmission()
if rem, err = env.Unmarshal(req); chk.E(err) {
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) {
}
} else {
if auther != nil && auther.AuthRequired() {
if rl.AuthRequired() {
if !a.AuthRequested() {
a.RequestAuth()
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
}
if advancedDeleter != nil {
advancedDeleter.BeforeDelete(c, t.Value(), env.Pubkey)
}
if err = sto.DeleteEvent(c, target.EventId()); chk.T(err) {
if err = okenvelope.NewFrom(env.Id, false,
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
}
if advancedDeleter != nil {
advancedDeleter.AfterDelete(t.Value(), env.Pubkey)
}
}
res = nil
}

View File

@@ -11,7 +11,6 @@ import (
"realy.mleku.dev/envelopes/noticeenvelope"
"realy.mleku.dev/envelopes/reqenvelope"
"realy.mleku.dev/log"
"realy.mleku.dev/relay"
)
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) {
notice = []byte(err.Error())
}
rl := a.Relay()
switch t {
case eventenvelope.L:
notice = a.HandleEvent(a.Context(), rem, a.Server)
@@ -33,12 +31,8 @@ func (a *A) HandleMessage(msg []byte) {
case authenvelope.L:
notice = a.HandleAuth(rem, a.Server)
default:
if wsh, ok := rl.(relay.WebSocketHandler); ok {
wsh.HandleUnknownType(a.Listener, t, rem)
} else {
notice = []byte(fmt.Sprintf("unknown envelope type %s\n%s", t, rem))
}
}
if len(notice) > 0 {
log.D.F("notice->%s %s", a.RealRemote(), notice)
if err = noticeenvelope.NewFrom(notice).Write(a.Listener); err != nil {

View File

@@ -23,7 +23,6 @@ import (
"realy.mleku.dev/realy/interfaces"
"realy.mleku.dev/realy/options"
"realy.mleku.dev/realy/pointers"
"realy.mleku.dev/relay"
"realy.mleku.dev/tag"
)
@@ -31,13 +30,6 @@ func (a *A) HandleReq(
c context.T, req []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()
var err error
var rem []byte
@@ -49,13 +41,12 @@ func (a *A) HandleReq(
log.I.F("extra '%s'", rem)
}
allowed := env.Filters
if accepter != nil {
var accepted, modified bool
allowed, accepted, modified = accepter.AcceptReq(c, a.Req(), env.Subscription.T,
allowed, accepted, modified = srv.Relay().AcceptReq(c, a.Req(), env.Subscription.T,
env.Filters,
[]byte(a.Authed()))
if !accepted || allowed == nil || modified {
if auther != nil && auther.AuthRequired() && !a.AuthRequested() {
if srv.Relay().AuthRequired() && !a.AuthRequested() {
a.RequestAuth()
if err = closedenvelope.NewFrom(env.Subscription,
normalize.AuthRequired.F("auth required for request processing")).Write(a.Listener); chk.E(err) {
@@ -70,11 +61,10 @@ func (a *A) HandleReq(
}
}
}
}
// log.I.ToSliceOfBytes("handling %s", env.Marshal(nil))
if allowed != env.Filters {
defer func() {
if auther != nil && auther.AuthRequired() &&
if srv.Relay().AuthRequired() &&
!a.AuthRequested() {
a.RequestAuth()
if err = closedenvelope.NewFrom(env.Subscription,
@@ -101,7 +91,7 @@ func (a *A) HandleReq(
}
i = *f.Limit
}
if auther != nil && auther.AuthRequired() {
if srv.Relay().AuthRequired() {
if f.Kinds.IsPrivileged() {
log.T.F("privileged request\n%s", f.Serialize())
senders := f.Authors
@@ -177,7 +167,6 @@ func (a *A) HandleReq(
// if auth is required, kind is privileged and there is no authed pubkey, skip
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)
if auther != nil {
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) {
}
@@ -190,14 +179,11 @@ func (a *A) HandleReq(
"client implement NIP-42?")
return notice
}
continue
}
// if the authed pubkey is not present in the pubkey or p tags, skip
if ev.Kind.IsPrivileged() && (!bytes.Equal(ev.Pubkey, aut) ||
!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",
// ev.Id, aut)
if auther != nil {
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) {
}
@@ -210,8 +196,6 @@ func (a *A) HandleReq(
"client implement NIP-42?")
return notice
}
continue
}
tmp = append(tmp, ev)
}
events = tmp