diff --git a/app/handle-delete.go b/app/handle-delete.go index 4109b0c..7c9d932 100644 --- a/app/handle-delete.go +++ b/app/handle-delete.go @@ -24,23 +24,36 @@ func (l *Listener) GetSerialsFromFilter(f *filter.F) ( } func (l *Listener) HandleDelete(env *eventenvelope.Submission) (err error) { - // log.I.C( - // func() string { - // return fmt.Sprintf( - // "delete event\n%s", env.E.Serialize(), - // ) - // }, - // ) + log.I.F("HandleDelete: processing delete event %0x from pubkey %0x", env.E.ID, env.E.Pubkey) + log.I.F("HandleDelete: delete event tags: %d tags", len(*env.E.Tags)) + for i, t := range *env.E.Tags { + log.I.F("HandleDelete: tag %d: %s = %s", i, string(t.Key()), string(t.Value())) + } + var ownerDelete bool for _, pk := range l.Admins { if utils.FastEqual(pk, env.E.Pubkey) { ownerDelete = true + log.I.F("HandleDelete: delete event from admin/owner %0x", env.E.Pubkey) break } } + if !ownerDelete { + for _, pk := range l.Owners { + if utils.FastEqual(pk, env.E.Pubkey) { + ownerDelete = true + log.I.F("HandleDelete: delete event from owner %0x", env.E.Pubkey) + break + } + } + } + if !ownerDelete { + log.I.F("HandleDelete: delete event from regular user %0x", env.E.Pubkey) + } // process the tags in the delete event var deleteErr error var validDeletionFound bool + var deletionCount int for _, t := range *env.E.Tags { // first search for a tags, as these are the simplest to process if utils.FastEqual(t.Key(), []byte("a")) { @@ -109,8 +122,10 @@ func (l *Listener) HandleDelete(env *eventenvelope.Submission) (err error) { if err = l.DeleteEventBySerial( l.Ctx(), s, ev, ); chk.E(err) { + log.E.F("HandleDelete: failed to delete event %s: %v", hex.Enc(ev.ID), err) continue } + deletionCount++ } } } @@ -121,21 +136,27 @@ func (l *Listener) HandleDelete(env *eventenvelope.Submission) (err error) { if utils.FastEqual(t.Key(), []byte("e")) { val := t.Value() if len(val) == 0 { + log.W.F("HandleDelete: empty e-tag value") continue } + log.I.F("HandleDelete: processing e-tag with value: %s", string(val)) var dst []byte if b, e := hex.Dec(string(val)); chk.E(e) { + log.E.F("HandleDelete: failed to decode hex event ID %s: %v", string(val), e) continue } else { dst = b + log.I.F("HandleDelete: decoded event ID: %0x", dst) } f := &filter.F{ Ids: tag.NewFromBytesSlice(dst), } var sers types.Uint40s if sers, err = l.GetSerialsFromFilter(f); chk.E(err) { + log.E.F("HandleDelete: failed to get serials from filter: %v", err) continue } + log.I.F("HandleDelete: found %d serials for event ID %s", len(sers), string(val)) // if found, delete them if len(sers) > 0 { // there should be only one event per serial, so we can just @@ -164,8 +185,10 @@ func (l *Listener) HandleDelete(env *eventenvelope.Submission) (err error) { hex.Enc(ev.ID), hex.Enc(env.E.Pubkey), ) if err = l.DeleteEventBySerial(l.Ctx(), s, ev); chk.E(err) { + log.E.F("HandleDelete: failed to delete event %s: %v", hex.Enc(ev.ID), err) continue } + deletionCount++ } continue } @@ -204,17 +227,27 @@ func (l *Listener) HandleDelete(env *eventenvelope.Submission) (err error) { if !utils.FastEqual(env.E.Pubkey, ev.Pubkey) { continue } + validDeletionFound = true + log.I.F( + "HandleDelete: deleting event %s via k-tag by authorized user %s", + hex.Enc(ev.ID), hex.Enc(env.E.Pubkey), + ) + if err = l.DeleteEventBySerial(l.Ctx(), s, ev); chk.E(err) { + log.E.F("HandleDelete: failed to delete event %s: %v", hex.Enc(ev.ID), err) + continue + } + deletionCount++ } - continue } } - continue } // If no valid deletions were found, return an error if !validDeletionFound { + log.W.F("HandleDelete: no valid deletions found for event %0x", env.E.ID) return fmt.Errorf("blocked: cannot delete events that belong to other users") } + log.I.F("HandleDelete: successfully processed %d deletions for event %0x", deletionCount, env.E.ID) return } diff --git a/app/handle-event.go b/app/handle-event.go index 6576e2e..3799135 100644 --- a/app/handle-event.go +++ b/app/handle-event.go @@ -167,6 +167,21 @@ func (l *Listener) HandleEvent(msg []byte) (err error) { // user has write access or better, continue // log.D.F("user has %s access", accessLevel) } + + // check if event is ephemeral - if so, deliver and return early + if kind.IsEphemeral(env.E.Kind) { + log.D.F("handling ephemeral event %0x (kind %d)", env.E.ID, env.E.Kind) + // Send OK response for ephemeral events + if err = Ok.Ok(l, env, ""); chk.E(err) { + return + } + // Deliver the event to subscribers immediately + clonedEvent := env.E.Clone() + go l.publishers.Deliver(clonedEvent) + log.D.F("delivered ephemeral event %0x", env.E.ID) + return + } + // check for protected tag (NIP-70) protectedTag := env.E.Tags.GetFirst([]byte("-")) if protectedTag != nil && acl.Registry.Active.Load() != "none" { @@ -183,7 +198,9 @@ func (l *Listener) HandleEvent(msg []byte) (err error) { } // if the event is a delete, process the delete if env.E.Kind == kind.EventDeletion.K { + log.I.F("processing delete event %0x", env.E.ID) if err = l.HandleDelete(env); err != nil { + log.E.F("HandleDelete failed for event %0x: %v", env.E.ID, err) if strings.HasPrefix(err.Error(), "blocked:") { errStr := err.Error()[len("blocked: "):len(err.Error())] if err = Ok.Error( @@ -193,10 +210,41 @@ func (l *Listener) HandleEvent(msg []byte) (err error) { } return } + // For non-blocked errors, still send OK but log the error + log.W.F("Delete processing failed but continuing: %v", err) + } else { + log.I.F("HandleDelete completed successfully for event %0x", env.E.ID) } + // Send OK response for delete events + if err = Ok.Ok(l, env, ""); chk.E(err) { + return + } + // Store the delete event itself + saveCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + if _, _, err = l.SaveEvent(saveCtx, env.E); err != nil { + if strings.HasPrefix(err.Error(), "blocked:") { + errStr := err.Error()[len("blocked: "):len(err.Error())] + if err = Ok.Error( + l, env, errStr, + ); chk.E(err) { + return + } + return + } + chk.E(err) + return + } + // Deliver the delete event to subscribers + clonedEvent := env.E.Clone() + go l.publishers.Deliver(clonedEvent) + log.D.F("processed delete event %0x", env.E.ID) + return } else { // check if the event was deleted - if err = l.CheckForDeleted(env.E, l.Admins); err != nil { + // Combine admins and owners for deletion checking + adminOwners := append(l.Admins, l.Owners...) + if err = l.CheckForDeleted(env.E, adminOwners); err != nil { if strings.HasPrefix(err.Error(), "blocked:") { errStr := err.Error()[len("blocked: "):len(err.Error())] if err = Ok.Error( @@ -234,12 +282,21 @@ func (l *Listener) HandleEvent(msg []byte) (err error) { go l.publishers.Deliver(clonedEvent) log.D.F("saved event %0x", env.E.ID) var isNewFromAdmin bool + // Check if event is from admin or owner for _, admin := range l.Admins { if utils.FastEqual(admin, env.E.Pubkey) { isNewFromAdmin = true break } } + if !isNewFromAdmin { + for _, owner := range l.Owners { + if utils.FastEqual(owner, env.E.Pubkey) { + isNewFromAdmin = true + break + } + } + } if isNewFromAdmin { log.I.F("new event from admin %0x", env.E.Pubkey) // if a follow list was saved, reconfigure ACLs now that it is persisted diff --git a/app/main.go b/app/main.go index 89f4ae1..d02aac4 100644 --- a/app/main.go +++ b/app/main.go @@ -36,6 +36,18 @@ func Run( } adminKeys = append(adminKeys, pk) } + // get the owners + var ownerKeys [][]byte + for _, owner := range cfg.Owners { + if len(owner) == 0 { + continue + } + var pk []byte + if pk, err = bech32encoding.NpubOrHexToPublicKeyBinary(owner); chk.E(err) { + continue + } + ownerKeys = append(ownerKeys, pk) + } // start listener l := &Server{ Ctx: ctx, @@ -43,6 +55,7 @@ func Run( D: db, publishers: publish.New(NewPublisher(ctx)), Admins: adminKeys, + Owners: ownerKeys, } // Initialize sprocket manager @@ -68,6 +81,18 @@ func Run( cfg.Admins = append(cfg.Admins, pk) log.I.F("added relay identity to admins for follow-list whitelisting") } + // also ensure relay identity pubkey is considered an owner for full control + found = false + for _, o := range cfg.Owners { + if o == pk { + found = true + break + } + } + if !found { + cfg.Owners = append(cfg.Owners, pk) + log.I.F("added relay identity to owners for full control") + } } } diff --git a/app/server.go b/app/server.go index 6276bcd..e73eee7 100644 --- a/app/server.go +++ b/app/server.go @@ -34,6 +34,7 @@ type Server struct { remote string publishers *publish.S Admins [][]byte + Owners [][]byte *database.D // optional reverse proxy for dev web server @@ -179,7 +180,7 @@ func (s *Server) UserInterface() { s.challengeMutex.Unlock() } - // Serve favicon.ico by serving orly.png + // Serve favicon.ico by serving orly-favicon.png s.mux.HandleFunc("/favicon.ico", s.handleFavicon) // Serve the main login interface (and static assets) or proxy in dev mode @@ -206,7 +207,7 @@ func (s *Server) UserInterface() { s.mux.HandleFunc("/api/sprocket/config", s.handleSprocketConfig) } -// handleFavicon serves orly.png as favicon.ico +// handleFavicon serves orly-favicon.png as favicon.ico func (s *Server) handleFavicon(w http.ResponseWriter, r *http.Request) { // In dev mode with proxy configured, forward to dev server if s.devProxy != nil { @@ -214,14 +215,14 @@ func (s *Server) handleFavicon(w http.ResponseWriter, r *http.Request) { return } - // Serve orly.png as favicon.ico from embedded web app + // Serve orly-favicon.png as favicon.ico from embedded web app w.Header().Set("Content-Type", "image/png") w.Header().Set("Cache-Control", "public, max-age=86400") // Cache for 1 day - // Create a request for orly.png and serve it + // Create a request for orly-favicon.png and serve it faviconReq := &http.Request{ Method: "GET", - URL: &url.URL{Path: "/orly.png"}, + URL: &url.URL{Path: "/orly-favicon.png"}, } ServeEmbeddedWeb(w, faviconReq) } diff --git a/app/web/dist/index.html b/app/web/dist/index.html index 8cf2156..c580a73 100644 --- a/app/web/dist/index.html +++ b/app/web/dist/index.html @@ -4,7 +4,7 @@