From ce947e23f8c3fee918df006c7b52b0c752f6336e Mon Sep 17 00:00:00 2001 From: mleku Date: Wed, 23 Apr 2025 21:16:38 -0106 Subject: [PATCH] massive code cleanup --- filter/filter.go | 14 -- normalize/normalize.go | 40 ---- openapi/http-configuration.go | 5 +- ratel/queryevents.go | 14 -- realy/addEvent.go | 14 +- realy/interfaces/interfaces.go | 2 +- realy/server-impl.go | 8 +- realy/server-publish.go | 6 +- reason/reason.go | 45 ++++ socketapi/handleAuth.go | 10 +- socketapi/handleEvent.go | 385 +++++++++++++++++---------------- socketapi/handleReq.go | 98 ++++----- socketapi/main.go | 2 +- socketapi/ok.go | 48 ++++ ws/client_test.go | 3 +- 15 files changed, 362 insertions(+), 332 deletions(-) create mode 100644 reason/reason.go create mode 100644 socketapi/ok.go diff --git a/filter/filter.go b/filter/filter.go index 4c1365f..b698dde 100644 --- a/filter/filter.go +++ b/filter/filter.go @@ -410,14 +410,10 @@ func (f *T) Unmarshal(b []byte) (r []byte, err error) { } if r[0] == '}' { state = afterClose - // log.I.Ln("afterClose") - // rem = rem[1:] } else if r[0] == ',' { state = openParen - // log.I.Ln("openParen") } else if r[0] == '"' { state = inKey - // log.I.Ln("inKey") } } if len(r) == 0 { @@ -436,30 +432,24 @@ invalid: // Matches checks a filter against an event and determines if the event matches the filter. func (f *T) Matches(ev *event.T) bool { if ev == nil { - // log.T.ToSliceOfBytes("nil event") return false } if f.IDs.Len() > 0 && !f.IDs.Contains(ev.Id) { - // log.T.ToSliceOfBytes("no ids in filter match event\nEVENT %s\nFILTER %s", ev.ToObject().String(), f.ToObject().String()) return false } if f.Kinds.Len() > 0 && !f.Kinds.Contains(ev.Kind) { - // log.T.ToSliceOfBytes("no matching kinds in filter\nEVENT %s\nFILTER %s", ev.ToObject().String(), f.ToObject().String()) return false } if f.Authors.Len() > 0 && !f.Authors.Contains(ev.Pubkey) { - // log.T.ToSliceOfBytes("no matching authors in filter\nEVENT %s\nFILTER %s", ev.ToObject().String(), f.ToObject().String()) return false } if f.Tags.Len() > 0 && !ev.Tags.Intersects(f.Tags) { return false } if f.Since.Int() != 0 && ev.CreatedAt.I64() < f.Since.I64() { - // log.T.ToSliceOfBytes("event is older than since\nEVENT %s\nFILTER %s", ev.ToObject().String(), f.ToObject().String()) return false } if f.Until.Int() != 0 && ev.CreatedAt.I64() > f.Until.I64() { - // log.T.ToSliceOfBytes("event is newer than until\nEVENT %s\nFILTER %s", ev.ToObject().String(), f.ToObject().String()) return false } return true @@ -535,7 +525,6 @@ func GenFilter() (f *T, err error) { id := make([]byte, sha256.Size) frand.Read(id) f.IDs = f.IDs.Append(id) - // f.IDs.Field = append(f.IDs.Field, id) } n = frand.Intn(16) for _ = range n { @@ -549,7 +538,6 @@ func GenFilter() (f *T, err error) { } pk := sk.PubKey() f.Authors = f.Authors.Append(schnorr.SerializePubKey(pk)) - // f.Authors.Field = append(f.Authors.Field, schnorr.SerializePubKey(pk)) } a := frand.Intn(16) if a < n { @@ -570,7 +558,6 @@ func GenFilter() (f *T, err error) { } idb = append([][]byte{{'#', byte(b)}}, idb...) f.Tags = f.Tags.AppendTags(tag.FromBytesSlice(idb...)) - // f.Tags.T = append(f.Tags.T, tag.FromBytesSlice(idb...)) } else { var idb [][]byte for range l { @@ -582,7 +569,6 @@ func GenFilter() (f *T, err error) { } idb = append([][]byte{{'#', byte(b)}}, idb...) f.Tags = f.Tags.AppendTags(tag.FromBytesSlice(idb...)) - // f.Tags.T = append(f.Tags.T, tag.FromBytesSlice(idb...)) } } tn := int(timestamp.Now().I64()) diff --git a/normalize/normalize.go b/normalize/normalize.go index 6be16a2..d29a8bc 100644 --- a/normalize/normalize.go +++ b/normalize/normalize.go @@ -4,7 +4,6 @@ package normalize import ( "bytes" - "fmt" "net/url" "realy.mleku.dev/chk" @@ -91,42 +90,3 @@ func URL[V string | []byte](v V) (b []byte) { p.Path = string(bytes.TrimRight([]byte(p.Path), "/")) return []byte(p.String()) } - -// Msg constructs a properly formatted message with a machine-readable prefix for OK and CLOSED -// envelopes. -func Msg(prefix Reason, format string, params ...any) []byte { - if len(prefix) < 1 { - prefix = Error - } - return []byte(fmt.Sprintf(prefix.S()+": "+format, params...)) -} - -// Reason is the machine-readable prefix before the colon in an OK or CLOSED envelope message. -// Below are the most common kinds that are mentioned in NIP-01. -type Reason []byte - -var ( - AuthRequired = Reason("auth-required") - PoW = Reason("pow") - Duplicate = Reason("duplicate") - Blocked = Reason("blocked") - RateLimited = Reason("rate-limited") - Invalid = Reason("invalid") - Error = Reason("error") - Unsupported = Reason("unsupported") - Restricted = Reason("restricted") -) - -// S returns the Reason as a string -func (r Reason) S() string { return string(r) } - -// B returns the Reason as a byte slice. -func (r Reason) B() []byte { return r } - -// IsPrefix returns whether a text contains the same Reason prefix. -func (r Reason) IsPrefix(reason []byte) bool { return bytes.HasPrefix(reason, r.B()) } - -// F allows creation of a full Reason text with a printf style format. -func (r Reason) F(format string, params ...any) []byte { - return Msg(r, format, params...) -} diff --git a/openapi/http-configuration.go b/openapi/http-configuration.go index 040031f..bec428b 100644 --- a/openapi/http-configuration.go +++ b/openapi/http-configuration.go @@ -83,7 +83,10 @@ func (x *Operations) RegisterConfigurationSet(api huma.API) { } } log.I.F("setting configuration") - x.SetConfiguration(input.Body) + if err = x.SetConfiguration(input.Body); chk.E(err) { + err = huma.Error400BadRequest(err.Error()) + return + } return }) } diff --git a/ratel/queryevents.go b/ratel/queryevents.go index f92dc15..151fc37 100644 --- a/ratel/queryevents.go +++ b/ratel/queryevents.go @@ -194,7 +194,6 @@ func (r *T) QueryEvents(c context.T, f *filter.T) (evs event.Ts, err error) { // intent or the client is erroneous, if any limit greater is // requested this will be used instead as the previous clause. if len(evMap) >= r.MaxLimit { - // log.T.ToSliceOfBytes("found MaxLimit events: %d", len(evMap)) return } } @@ -227,17 +226,6 @@ func (r *T) QueryEvents(c context.T, f *filter.T) (evs event.Ts, err error) { if len(evs) > limit { evs = evs[:limit] } - // log.T.C(func() string { - // evIds := make([]string, len(evs)) - // for i, ev := range evs { - // evIds[i] = hex.Enc(ev.Id) - // } - // heading := fmt.Sprintf("query complete,%d events found,%s", len(evs), - // f.Serialize()) - // return fmt.Sprintf("%s\nevents,%v", heading, evIds) - // }) - // bump the access times on all retrieved events. do this in a goroutine so the - // user's events are delivered immediately go func() { for ser := range accessed { seri := serial.New([]byte(ser)) @@ -252,7 +240,6 @@ func (r *T) QueryEvents(c context.T, f *filter.T) (evs event.Ts, err error) { return } } - // log.T.Ln("last access for", seri.Uint64(), now.U64()) return nil }) } @@ -260,6 +247,5 @@ func (r *T) QueryEvents(c context.T, f *filter.T) (evs event.Ts, err error) { } else { log.T.F("no events found,%s", f.Serialize()) } - // } return } diff --git a/realy/addEvent.go b/realy/addEvent.go index 25e5a8f..89e6e8b 100644 --- a/realy/addEvent.go +++ b/realy/addEvent.go @@ -10,8 +10,8 @@ import ( "realy.mleku.dev/context" "realy.mleku.dev/event" "realy.mleku.dev/log" - "realy.mleku.dev/normalize" "realy.mleku.dev/publish" + "realy.mleku.dev/reason" "realy.mleku.dev/store" ) @@ -24,7 +24,7 @@ func (s *Server) addEvent(c context.T, ev *event.T, if ev == nil { log.I.F("empty event") - return false, normalize.Invalid.F("empty event") + return false, reason.Invalid.F("empty event") } // don't allow storing event with protected marker as per nip-70 with auth enabled. if (s.AuthRequired() || !s.PublicReadable()) && ev.Tags.ContainsProtectedMarker() { @@ -39,19 +39,19 @@ func (s *Server) addEvent(c context.T, ev *event.T, } else { if saveErr := s.Publish(c, ev); saveErr != nil { if errors.Is(saveErr, store.ErrDupEvent) { - return false, normalize.Error.F(saveErr.Error()) + return false, reason.Error.F(saveErr.Error()) } errmsg := saveErr.Error() if NIP20prefixmatcher.MatchString(errmsg) { if strings.Contains(errmsg, "tombstone") { - return false, normalize.Blocked.F("event was deleted, not storing it again") + return false, reason.Blocked.F("event was deleted, not storing it again") } - if strings.HasPrefix(errmsg, string(normalize.Blocked)) { + if strings.HasPrefix(errmsg, string(reason.Blocked)) { return false, []byte(errmsg) } - return false, normalize.Error.F(errmsg) + return false, reason.Error.F(errmsg) } else { - return false, normalize.Error.F("failed to save (%s)", errmsg) + return false, reason.Error.F("failed to save (%s)", errmsg) } } } diff --git a/realy/interfaces/interfaces.go b/realy/interfaces/interfaces.go index 7b43a49..6ba8219 100644 --- a/realy/interfaces/interfaces.go +++ b/realy/interfaces/interfaces.go @@ -27,7 +27,7 @@ type Server interface { OwnersFollowed(pubkey string) (ok bool) PublicReadable() bool ServiceURL(req *http.Request) (s string) - SetConfiguration(*config.C) + SetConfiguration(cfg *config.C) (err error) Shutdown() Storage() store.I Unlock() diff --git a/realy/server-impl.go b/realy/server-impl.go index 6afd0b9..11a0f33 100644 --- a/realy/server-impl.go +++ b/realy/server-impl.go @@ -6,6 +6,7 @@ import ( "realy.mleku.dev/chk" "realy.mleku.dev/context" + "realy.mleku.dev/errorf" "realy.mleku.dev/event" "realy.mleku.dev/log" "realy.mleku.dev/realy/config" @@ -32,7 +33,11 @@ func (s *Server) Configuration() config.C { return *s.configuration } -func (s *Server) SetConfiguration(cfg *config.C) { +func (s *Server) SetConfiguration(cfg *config.C) (err error) { + if len(cfg.Admins) == 0 { + err = errorf.E("cannot set configuration without at least one admin") + return + } s.configurationMx.Lock() s.configuration = cfg s.configured = true @@ -41,6 +46,7 @@ func (s *Server) SetConfiguration(cfg *config.C) { chk.E(c.SetConfiguration(cfg)) chk.E(s.UpdateConfiguration()) } + return err } func (s *Server) AddEvent( diff --git a/realy/server-publish.go b/realy/server-publish.go index 70445aa..51246fa 100644 --- a/realy/server-publish.go +++ b/realy/server-publish.go @@ -12,7 +12,7 @@ import ( "realy.mleku.dev/filter" "realy.mleku.dev/kinds" "realy.mleku.dev/log" - "realy.mleku.dev/normalize" + "realy.mleku.dev/reason" "realy.mleku.dev/store" "realy.mleku.dev/tag" ) @@ -42,7 +42,7 @@ func (s *Server) Publish(c context.T, evt *event.T) (err error) { } if ev.CreatedAt.Int() > evt.CreatedAt.Int() { log.I.F("not replacing newer replaceable event") - return errorf.W(string(normalize.Invalid.F("not replacing newer replaceable event"))) + return errorf.W(string(reason.Invalid.F("not replacing newer replaceable event"))) } // not deleting these events because some clients are retarded and the query // will pull the new one but a backup can recover the data of old ones @@ -88,7 +88,7 @@ func (s *Server) Publish(c context.T, evt *event.T) (err error) { err = nil log.I.F("maybe replace %s", ev.Serialize()) if ev.CreatedAt.Int() > evt.CreatedAt.Int() { - return errorf.D(string(normalize.Blocked.F("not replacing newer parameterized replaceable event"))) + return errorf.D(string(reason.Blocked.F("not replacing newer parameterized replaceable event"))) } // not deleting these events because some clients are retarded and the query // will pull the new one but a backup can recover the data of old ones diff --git a/reason/reason.go b/reason/reason.go new file mode 100644 index 0000000..59cf230 --- /dev/null +++ b/reason/reason.go @@ -0,0 +1,45 @@ +package reason + +import ( + "bytes" + "fmt" +) + +// R is the machine-readable prefix before the colon in an OK or CLOSED envelope message. +// Below are the most common kinds that are mentioned in NIP-01. +type R []byte + +var ( + AuthRequired = R("auth-required") + PoW = R("pow") + Duplicate = R("duplicate") + Blocked = R("blocked") + RateLimited = R("rate-limited") + Invalid = R("invalid") + Error = R("error") + Unsupported = R("unsupported") + Restricted = R("restricted") +) + +// S returns the R as a string +func (r R) S() string { return string(r) } + +// B returns the R as a byte slice. +func (r R) B() []byte { return r } + +// IsPrefix returns whether a text contains the same R prefix. +func (r R) IsPrefix(reason []byte) bool { return bytes.HasPrefix(reason, r.B()) } + +// F allows creation of a full R text with a printf style format. +func (r R) F(format string, params ...any) []byte { + return Msg(r, format, params...) +} + +// Msg constructs a properly formatted message with a machine-readable prefix for OK and CLOSED +// envelopes. +func Msg(prefix R, format string, params ...any) []byte { + if len(prefix) < 1 { + prefix = Error + } + return []byte(fmt.Sprintf(prefix.S()+": "+format, params...)) +} diff --git a/socketapi/handleAuth.go b/socketapi/handleAuth.go index 3ebffd4..0c82208 100644 --- a/socketapi/handleAuth.go +++ b/socketapi/handleAuth.go @@ -6,8 +6,8 @@ import ( "realy.mleku.dev/envelopes/authenvelope" "realy.mleku.dev/envelopes/okenvelope" "realy.mleku.dev/log" - "realy.mleku.dev/normalize" "realy.mleku.dev/realy/interfaces" + "realy.mleku.dev/reason" ) func (a *A) HandleAuth(req []byte, @@ -33,16 +33,16 @@ func (a *A) HandleAuth(req []byte, svcUrl); chk.E(err) { e := err.Error() if err = okenvelope.NewFrom(env.Event.Id, false, - normalize.Error.F(err.Error())).Write(a.Listener); chk.E(err) { + reason.Error.F(err.Error())).Write(a.Listener); chk.E(err) { return []byte(err.Error()) } - return normalize.Error.F(e) + return reason.Error.F(e) } else if !valid { if err = okenvelope.NewFrom(env.Event.Id, false, - normalize.Error.F("failed to authenticate")).Write(a.Listener); chk.E(err) { + reason.Error.F("failed to authenticate")).Write(a.Listener); chk.E(err) { return []byte(err.Error()) } - return normalize.Restricted.F("auth response does not validate") + return reason.Restricted.F("auth response does not validate") } else { if err = okenvelope.NewFrom(env.Event.Id, true, []byte{}).Write(a.Listener); chk.E(err) { diff --git a/socketapi/handleEvent.go b/socketapi/handleEvent.go index 635fd75..2e3d239 100644 --- a/socketapi/handleEvent.go +++ b/socketapi/handleEvent.go @@ -15,9 +15,10 @@ import ( "realy.mleku.dev/ints" "realy.mleku.dev/kind" "realy.mleku.dev/log" - "realy.mleku.dev/normalize" "realy.mleku.dev/realy/interfaces" + "realy.mleku.dev/reason" "realy.mleku.dev/sha256" + "realy.mleku.dev/store" "realy.mleku.dev/tag" ) @@ -43,199 +44,14 @@ func (a *A) HandleEvent(c context.T, req []byte, srv interfaces.Server, a.Listener.AuthedBytes(), remote) log.T.F("%s accepted %v", remote, accept) if !accept { - if strings.Contains(notice, "mute") { - if err = okenvelope.NewFrom(env.Id, false, - normalize.Blocked.F(notice)).Write(a.Listener); chk.T(err) { - } - } else { - if !a.Listener.AuthRequested() { - a.Listener.RequestAuth() - log.I.F("requesting auth from client %s", a.Listener.RealRemote()) - if err = authenvelope.NewChallengeWith(a.Listener.Challenge()).Write(a.Listener); chk.T(err) { - return - } - if err = okenvelope.NewFrom(env.Id, false, - normalize.AuthRequired.F("auth required for storing events")).Write(a.Listener); chk.T(err) { - } - return - } else { - log.I.F("requesting auth again from client %s", a.Listener.RealRemote()) - if err = authenvelope.NewChallengeWith(a.Listener.Challenge()).Write(a.Listener); chk.T(err) { - return - } - if err = okenvelope.NewFrom(env.Id, false, - normalize.AuthRequired.F("auth required for storing events")).Write(a.Listener); chk.T(err) { - } - return - } - } - if err = okenvelope.NewFrom(env.Id, false, - normalize.Invalid.F(notice)).Write(a.Listener); chk.T(err) { - } - return - } - if !bytes.Equal(env.GetIDBytes(), env.Id) { - if err = okenvelope.NewFrom(env.Id, false, - normalize.Invalid.F("event id is computed incorrectly")).Write(a.Listener); chk.E(err) { + if err = a.HandleRejectEvent(env, notice); chk.E(err) { return } return } - if ok, err = env.Verify(); chk.T(err) { - if err = okenvelope.NewFrom(env.Id, false, - normalize.Error.F("failed to verify signature")).Write(a.Listener); chk.E(err) { - return - } - } else if !ok { - if err = okenvelope.NewFrom(env.Id, false, - normalize.Error.F("signature is invalid")).Write(a.Listener); chk.E(err) { - return - } + if err = a.VerifyEvent(env); chk.E(err) { return } - if env.T.Kind.K == kind.Deletion.K { - log.I.F("delete event\n%s", env.T.Serialize()) - for _, t := range env.Tags.ToSliceOfTags() { - var res []*event.T - if t.Len() >= 2 { - switch { - case bytes.Equal(t.Key(), []byte("e")): - evId := make([]byte, sha256.Size) - if _, err = hex.DecBytes(evId, t.Value()); chk.E(err) { - continue - } - res, err = sto.QueryEvents(c, &filter.T{IDs: tag.New(evId)}) - if err != nil { - if err = okenvelope.NewFrom(env.Id, false, - normalize.Error.F("failed to query for target event")).Write(a.Listener); chk.E(err) { - return - } - return - } - for i := range res { - if res[i].Kind.Equal(kind.Deletion) { - if err = okenvelope.NewFrom(env.Id, false, - normalize.Blocked.F("not processing or storing delete event containing delete event references")).Write(a.Listener); chk.E(err) { - return - } - return - } - if !bytes.Equal(res[i].Pubkey, env.T.Pubkey) { - if err = okenvelope.NewFrom(env.Id, false, - normalize.Blocked.F("cannot delete other users' events (delete by e tag)")).Write(a.Listener); chk.E(err) { - return - } - return - } - } - case bytes.Equal(t.Key(), []byte("a")): - split := bytes.Split(t.Value(), []byte{':'}) - if len(split) != 3 { - continue - } - var pk []byte - if pk, err = hex.DecAppend(nil, split[1]); chk.E(err) { - if err = okenvelope.NewFrom(env.Id, false, - normalize.Invalid.F("delete event a tag pubkey value invalid: %s", - t.Value())).Write(a.Listener); chk.E(err) { - return - } - return - } - kin := ints.New(uint16(0)) - if _, err = kin.Unmarshal(split[0]); chk.E(err) { - if err = okenvelope.NewFrom(env.Id, false, - normalize.Invalid.F("delete event a tag kind value invalid: %s", - t.Value())).Write(a.Listener); chk.E(err) { - return - } - return - } - kk := kind.New(kin.Uint16()) - if kk.Equal(kind.Deletion) { - if err = okenvelope.NewFrom(env.Id, false, - normalize.Blocked.F("delete event kind may not be deleted")).Write(a.Listener); chk.E(err) { - return - } - return - } - if !kk.IsParameterizedReplaceable() { - if err = okenvelope.NewFrom(env.Id, false, - normalize.Error.F("delete tags with a tags containing non-parameterized-replaceable events cannot be processed")).Write(a.Listener); chk.E(err) { - return - } - return - } - if !bytes.Equal(pk, env.T.Pubkey) { - log.I.S(pk, env.T.Pubkey, env.T) - if err = okenvelope.NewFrom(env.Id, false, - normalize.Blocked.F("cannot delete other users' events (delete by a tag)")).Write(a.Listener); chk.E(err) { - return - } - return - } - f := filter.New() - f.Kinds.K = []*kind.T{kk} - // aut := make(by, 0, len(pk)/2) - // if aut, err = hex.DecAppend(aut, pk); chk.E(err) { - // return - // } - f.Authors.Append(pk) - f.Tags.AppendTags(tag.New([]byte{'#', 'd'}, split[2])) - res, err = sto.QueryEvents(c, f) - if err != nil { - if err = okenvelope.NewFrom(env.Id, false, - normalize.Error.F("failed to query for target event")).Write(a.Listener); chk.E(err) { - return - } - return - } - } - } - if len(res) < 1 { - continue - } - var resTmp []*event.T - for _, v := range res { - if env.T.CreatedAt.U64() >= v.CreatedAt.U64() { - resTmp = append(resTmp, v) - } - } - res = resTmp - for _, target := range res { - if target.Kind.K == kind.Deletion.K { - if err = okenvelope.NewFrom(env.Id, false, - normalize.Error.F("cannot delete delete event %s", - env.Id)).Write(a.Listener); chk.E(err) { - return - } - } - if target.CreatedAt.Int() > env.T.CreatedAt.Int() { - log.I.F("not deleting\n%d%\nbecause delete event is older\n%d", - target.CreatedAt.Int(), env.T.CreatedAt.Int()) - continue - } - if !bytes.Equal(target.Pubkey, env.Pubkey) { - if err = okenvelope.NewFrom(env.Id, false, - normalize.Error.F("only author can delete event")).Write(a.Listener); chk.E(err) { - return - } - return - } - 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) { - return - } - return - } - } - res = nil - } - if err = okenvelope.NewFrom(env.Id, true).Write(a.Listener); chk.E(err) { - return - } - } var reason []byte ok, reason = srv.AddEvent(c, env.T, a.Listener.Req(), a.Listener.AuthedBytes(), remote) log.T.F("event added %v", ok) @@ -247,3 +63,196 @@ func (a *A) HandleEvent(c context.T, req []byte, srv interfaces.Server, } return } + +func (a *A) VerifyEvent(env *eventenvelope.Submission) (err error) { + if !bytes.Equal(env.GetIDBytes(), env.Id) { + if err = a.Invalid(env, "event id is computed incorrectly"); chk.E(err) { + return + } + return + } + var ok bool + if ok, err = env.Verify(); chk.T(err) { + if err = a.Error(env, "failed to verify signature", err); chk.T(err) { + return + } + return + } else if !ok { + if err = a.Error(env, "signature is invalid", err); chk.T(err) { + return + } + return + } + return +} + +func (a *A) HandleRejectEvent(env *eventenvelope.Submission, notice string) (err error) { + if strings.Contains(notice, "mute") { + if err = a.Blocked(env, notice); chk.E(err) { + return + } + } else { + if !a.Listener.AuthRequested() { + a.Listener.RequestAuth() + log.I.F("requesting auth from client %s", a.Listener.RealRemote()) + } else { + log.I.F("requesting auth again from client %s", a.Listener.RealRemote()) + } + if err = authenvelope.NewChallengeWith(a.Listener.Challenge()).Write(a.Listener); chk.T(err) { + return + } + if err = a.AuthRequired(env, "auth required for storing events"); chk.E(err) { + return + } + return + } + if err = a.Invalid(env, notice); chk.E(err) { + return + } + return +} + +func (a *A) CheckDelete(c context.T, env *eventenvelope.Submission, sto store.I) (err error) { + log.I.F("delete event\n%s", env.T.Serialize()) + for _, t := range env.Tags.ToSliceOfTags() { + var res []*event.T + if t.Len() >= 2 { + switch { + case bytes.Equal(t.Key(), []byte("e")): + evId := make([]byte, sha256.Size) + if _, err = hex.DecBytes(evId, t.Value()); chk.E(err) { + continue + } + res, err = sto.QueryEvents(c, &filter.T{IDs: tag.New(evId)}) + if err != nil { + if err = a.Ok("failed to query for target event", reason.Error, env); chk.T(err) { + return + } + return + } + for i := range res { + if res[i].Kind.Equal(kind.Deletion) { + if err = a.Blocked(env, + "not processing or storing delete event containing delete event references", + ); chk.E(err) { + return + } + return + } + if !bytes.Equal(res[i].Pubkey, env.T.Pubkey) { + if err = a.Blocked(env, + "cannot delete other users' events (delete by e tag)", + ); chk.E(err) { + return + } + return + } + } + case bytes.Equal(t.Key(), []byte("a")): + split := bytes.Split(t.Value(), []byte{':'}) + if len(split) != 3 { + continue + } + var pk []byte + if pk, err = hex.DecAppend(nil, split[1]); chk.E(err) { + if err = a.Invalid(env, + "delete event a tag pubkey value invalid: %s", t.Value()); chk.T(err) { + } + return + } + kin := ints.New(uint16(0)) + if _, err = kin.Unmarshal(split[0]); chk.E(err) { + if err = a.Invalid(env, + "delete event a tag kind value invalid: %s", t.Value()); chk.T(err) { + return + } + return + } + kk := kind.New(kin.Uint16()) + if kk.Equal(kind.Deletion) { + if err = a.Blocked(env, "delete event kind may not be deleted"); chk.E(err) { + return + } + return + } + if !kk.IsParameterizedReplaceable() { + if err = a.Error(env, + "delete tags with a tags containing non-parameterized-replaceable events cannot be processed"); chk.E(err) { + return + } + return + } + if !bytes.Equal(pk, env.T.Pubkey) { + log.I.S(pk, env.T.Pubkey, env.T) + if err = a.Blocked(env, + "cannot delete other users' events (delete by a tag)"); chk.E(err) { + return + } + return + } + f := filter.New() + f.Kinds.K = []*kind.T{kk} + f.Authors.Append(pk) + f.Tags.AppendTags(tag.New([]byte{'#', 'd'}, split[2])) + if res, err = sto.QueryEvents(c, f); err != nil { + if err = a.Error(env, "failed to query for target event", err); chk.T(err) { + return + } + return + } + } + } + if len(res) < 1 { + continue + } + var resTmp []*event.T + for _, v := range res { + if env.T.CreatedAt.U64() >= v.CreatedAt.U64() { + resTmp = append(resTmp, v) + } + } + res = resTmp + for _, target := range res { + var skip bool + if skip, err = a.ProcessDelete(c, target, env, sto); skip { + continue + } else if err != nil { + return + } + } + res = nil + } + if err = okenvelope.NewFrom(env.Id, true).Write(a.Listener); chk.E(err) { + return + } + return +} + +func (a *A) ProcessDelete(c context.T, target *event.T, env *eventenvelope.Submission, sto store.I) (skip bool, err error) { + if target.Kind.K == kind.Deletion.K { + if err = a.Error(env, "cannot delete delete event %s", env.Id); chk.E(err) { + return + } + } + if target.CreatedAt.Int() > env.T.CreatedAt.Int() { + if err = a.Error(env, + "not deleting\n%d%\nbecause delete event is older\n%d", + target.CreatedAt.Int(), env.T.CreatedAt.Int()); chk.E(err) { + return + } + skip = true + } + if !bytes.Equal(target.Pubkey, env.Pubkey) { + if err = a.Error(env, "only author can delete event"); chk.E(err) { + return + } + return + } + if err = sto.DeleteEvent(c, target.EventId()); chk.T(err) { + if err = a.Error(env, err.Error()); chk.T(err) { + return + } + return + } + return +} diff --git a/socketapi/handleReq.go b/socketapi/handleReq.go index 331306b..f8432cc 100644 --- a/socketapi/handleReq.go +++ b/socketapi/handleReq.go @@ -19,10 +19,10 @@ import ( "realy.mleku.dev/kind" "realy.mleku.dev/kinds" "realy.mleku.dev/log" - "realy.mleku.dev/normalize" "realy.mleku.dev/publish" "realy.mleku.dev/realy/interfaces" "realy.mleku.dev/realy/pointers" + "realy.mleku.dev/reason" "realy.mleku.dev/tag" ) @@ -35,7 +35,7 @@ func (a *A) HandleReq( var rem []byte env := reqenvelope.New() if rem, err = env.Unmarshal(req); chk.E(err) { - return normalize.Error.F(err.Error()) + return reason.Error.F(err.Error()) } if len(rem) > 0 { log.I.F("extra '%s'", rem) @@ -48,7 +48,7 @@ func (a *A) HandleReq( if srv.AuthRequired() && !a.Listener.AuthRequested() { a.Listener.RequestAuth() if err = closedenvelope.NewFrom(env.Subscription, - normalize.AuthRequired.F("auth required for request processing")).Write(a.Listener); chk.E(err) { + reason.AuthRequired.F("auth required for request processing")).Write(a.Listener); chk.E(err) { } log.T.F("requesting auth from client from %s, challenge '%s'", a.Listener.RealRemote(), a.Listener.Challenge()) @@ -60,18 +60,13 @@ func (a *A) HandleReq( } } } - // log.I.ToSliceOfBytes("handling %s", env.Marshal(nil)) + var notice []byte if allowed != env.Filters { defer func() { if srv.AuthRequired() && !a.Listener.AuthRequested() { a.Listener.RequestAuth() - if err = closedenvelope.NewFrom(env.Subscription, - 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'", - remote, a.Listener.Challenge()) - if err = authenvelope.NewChallengeWith(a.Listener.Challenge()).Write(a.Listener); chk.E(err) { + if notice, err = a.AuthRequiredResponse(env, remote); chk.E(err) { return } return @@ -89,33 +84,23 @@ func (a *A) HandleReq( } i = *f.Limit } - if srv.AuthRequired() { - if f.Kinds.IsPrivileged() { - log.T.F("privileged request\n%s", f.Serialize()) - senders := f.Authors - receivers := f.Tags.GetAll(tag.New("#p")) - switch { - case len(a.Listener.Authed()) == 0: - // a.RequestAuth() - 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) { - } - log.I.F("requesting auth from client from %s", remote) - if err = authenvelope.NewChallengeWith(a.Listener.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 - case senders.Contains(a.Listener.AuthedBytes()) || - receivers.ContainsAny([]byte("#p"), tag.New(a.Listener.AuthedBytes())): - log.T.F("user %0x from %s allowed to query for privileged event", - a.Listener.AuthedBytes(), remote) - default: - return normalize.Restricted.F("authenticated user %0x does not have authorization for "+ - "requested filters", a.Listener.AuthedBytes()) + if srv.AuthRequired() && f.Kinds.IsPrivileged() { + log.T.F("privileged request\n%s", f.Serialize()) + senders := f.Authors + receivers := f.Tags.GetAll(tag.New("#p")) + switch { + case len(a.Listener.Authed()) == 0: + if notice, err = a.AuthRequiredResponse(env, remote); chk.E(err) { + return } + return notice + case senders.Contains(a.Listener.AuthedBytes()) || + receivers.ContainsAny([]byte("#p"), tag.New(a.Listener.AuthedBytes())): + log.T.F("user %0x from %s allowed to query for privileged event", + a.Listener.AuthedBytes(), remote) + default: + return reason.Restricted.F("authenticated user %0x does not have authorization for "+ + "requested filters", a.Listener.AuthedBytes()) } } var events event.Ts @@ -161,37 +146,20 @@ func (a *A) HandleReq( // remove privileged events as they come through in scrape queries var tmp event.Ts for _, ev := range events { - receivers := f.Tags.GetAll(tag.New("#p")) // 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 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) { - } - log.I.F("requesting auth from client from %s", remote) - if err = authenvelope.NewChallengeWith(a.Listener.Challenge()).Write(a.Listener); chk.E(err) { + if notice, err = a.AuthRequiredResponse(env, remote); 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 + receivers := f.Tags.GetAll(tag.New("#p")) if ev.Kind.IsPrivileged() && (!bytes.Equal(ev.Pubkey, aut) || !receivers.ContainsAny([]byte("#p"), tag.New(a.Listener.AuthedBytes()))) { - // log.I.ToSliceOfBytes("skipping event %0x because authed key %0x is in neither pubkey or p tag", - // ev.Id, aut) - 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) { - } - log.I.F("requesting auth from client from %s", remote) - if err = authenvelope.NewChallengeWith(a.Listener.Challenge()).Write(a.Listener); chk.E(err) { + if notice, err = a.AuthRequiredResponse(env, remote); 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) @@ -228,3 +196,21 @@ func (a *A) HandleReq( }) return } + +func (a *A) AuthRequiredResponse(env *reqenvelope.T, remote string) (notice []byte, err error) { + if err = closedenvelope.NewFrom(env.Subscription, + reason.AuthRequired.F(privilegedClosedNotice)).Write(a.Listener); chk.E(err) { + } + log.I.F("requesting auth from client from %s", remote) + if err = authenvelope.NewChallengeWith(a.Listener.Challenge()).Write(a.Listener); chk.E(err) { + return + } + notice = reason.Restricted.F(privilegedNotice) + return +} + +var privilegedNotice = "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?" + +var privilegedClosedNotice = "auth required for processing request due to presence of privileged kinds (DMs, app specific data)" diff --git a/socketapi/main.go b/socketapi/main.go index d6a6156..0ea0217 100644 --- a/socketapi/main.go +++ b/socketapi/main.go @@ -128,6 +128,6 @@ func (a *A) ServeHTTP(w http.ResponseWriter, r *http.Request) { } continue } - go a.HandleMessage(message, remote) + a.HandleMessage(message, remote) } } diff --git a/socketapi/ok.go b/socketapi/ok.go new file mode 100644 index 0000000..7237893 --- /dev/null +++ b/socketapi/ok.go @@ -0,0 +1,48 @@ +package socketapi + +import ( + "realy.mleku.dev/envelopes/eventenvelope" + "realy.mleku.dev/envelopes/okenvelope" + "realy.mleku.dev/reason" +) + +func (a *A) Ok(format string, prefix reason.R, env *eventenvelope.Submission, params ...any) (err error) { + err = okenvelope.NewFrom(env.Id, false, prefix.F(format, params...)).Write(a.Listener) + return +} + +func (a *A) AuthRequired(env *eventenvelope.Submission, format string, params ...any) (err error) { + return okenvelope.NewFrom(env.Id, false, reason.AuthRequired.F(format, params...)).Write(a.Listener) +} + +func (a *A) PoW(env *eventenvelope.Submission, format string, params ...any) (err error) { + return okenvelope.NewFrom(env.Id, false, reason.PoW.F(format, params...)).Write(a.Listener) +} + +func (a *A) Duplicate(env *eventenvelope.Submission, format string, params ...any) (err error) { + return okenvelope.NewFrom(env.Id, false, reason.Duplicate.F(format, params...)).Write(a.Listener) +} + +func (a *A) Blocked(env *eventenvelope.Submission, format string, params ...any) (err error) { + return okenvelope.NewFrom(env.Id, false, reason.Blocked.F(format, params...)).Write(a.Listener) +} + +func (a *A) RateLimited(env *eventenvelope.Submission, format string, params ...any) (err error) { + return okenvelope.NewFrom(env.Id, false, reason.RateLimited.F(format, params...)).Write(a.Listener) +} + +func (a *A) Invalid(env *eventenvelope.Submission, format string, params ...any) (err error) { + return okenvelope.NewFrom(env.Id, false, reason.Invalid.F(format, params...)).Write(a.Listener) +} + +func (a *A) Error(env *eventenvelope.Submission, format string, params ...any) (err error) { + return okenvelope.NewFrom(env.Id, false, reason.Error.F(format, params...)).Write(a.Listener) +} + +func (a *A) Unsupported(env *eventenvelope.Submission, format string, params ...any) (err error) { + return okenvelope.NewFrom(env.Id, false, reason.Unsupported.F(format, params...)).Write(a.Listener) +} + +func (a *A) Restricted(env *eventenvelope.Submission, format string, params ...any) (err error) { + return okenvelope.NewFrom(env.Id, false, reason.Restricted.F(format, params...)).Write(a.Listener) +} diff --git a/ws/client_test.go b/ws/client_test.go index 4114cdb..e414a9d 100644 --- a/ws/client_test.go +++ b/ws/client_test.go @@ -22,6 +22,7 @@ import ( "realy.mleku.dev/kind" "realy.mleku.dev/normalize" "realy.mleku.dev/p256k" + "realy.mleku.dev/reason" "realy.mleku.dev/tag" "realy.mleku.dev/tags" "realy.mleku.dev/timestamp" @@ -115,7 +116,7 @@ func TestPublishBlocked(t *testing.T) { // send back a not ok nip-20 command result var res []byte if res = okenvelope.NewFrom(textNote.Id, false, - normalize.Msg(normalize.Blocked, "no reason")).Marshal(res); chk.E(err) { + reason.Msg(reason.Blocked, "no reason")).Marshal(res); chk.E(err) { t.Fatal(err) } if err := websocket.Message.Send(conn, res); chk.T(err) {