From 1b2e434d6a13dc87d0964607efd37c69865b1bda Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Mon, 25 Nov 2024 12:04:30 -0300 Subject: [PATCH] commands to ban (and unban) events using the relay management api. --- internal.pb.go | 109 +++++++++++++++++++++++++++++++++++++++--------- internal.proto | 7 +++- internaldb.go | 53 +++++++++++++++++++++-- main.go | 3 ++ management.go | 45 ++++++++++++++++++++ render_event.go | 8 ++++ 6 files changed, 202 insertions(+), 23 deletions(-) create mode 100644 management.go diff --git a/internal.pb.go b/internal.pb.go index 3654f87..945f921 100644 --- a/internal.pb.go +++ b/internal.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.34.2 -// protoc v5.28.1 +// protoc v5.28.2 // source: internal.proto package main @@ -185,7 +185,7 @@ func (x *PubKeyArchive) GetPubkey() string { return "" } -type EventInRelay struct { +type ID struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields @@ -193,8 +193,8 @@ type EventInRelay struct { Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` } -func (x *EventInRelay) Reset() { - *x = EventInRelay{} +func (x *ID) Reset() { + *x = ID{} if protoimpl.UnsafeEnabled { mi := &file_internal_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -202,13 +202,13 @@ func (x *EventInRelay) Reset() { } } -func (x *EventInRelay) String() string { +func (x *ID) String() string { return protoimpl.X.MessageStringOf(x) } -func (*EventInRelay) ProtoMessage() {} +func (*ID) ProtoMessage() {} -func (x *EventInRelay) ProtoReflect() protoreflect.Message { +func (x *ID) ProtoReflect() protoreflect.Message { mi := &file_internal_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -220,18 +220,73 @@ func (x *EventInRelay) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use EventInRelay.ProtoReflect.Descriptor instead. -func (*EventInRelay) Descriptor() ([]byte, []int) { +// Deprecated: Use ID.ProtoReflect.Descriptor instead. +func (*ID) Descriptor() ([]byte, []int) { return file_internal_proto_rawDescGZIP(), []int{3} } -func (x *EventInRelay) GetId() string { +func (x *ID) GetId() string { if x != nil { return x.Id } return "" } +type BannedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Reason string `protobuf:"bytes,2,opt,name=reason,proto3" json:"reason,omitempty"` +} + +func (x *BannedEvent) Reset() { + *x = BannedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_internal_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BannedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BannedEvent) ProtoMessage() {} + +func (x *BannedEvent) ProtoReflect() protoreflect.Message { + mi := &file_internal_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BannedEvent.ProtoReflect.Descriptor instead. +func (*BannedEvent) Descriptor() ([]byte, []int) { + return file_internal_proto_rawDescGZIP(), []int{4} +} + +func (x *BannedEvent) GetId() []byte { + if x != nil { + return x.Id + } + return nil +} + +func (x *BannedEvent) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + var File_internal_proto protoreflect.FileDescriptor var file_internal_proto_rawDesc = []byte{ @@ -248,11 +303,14 @@ var file_internal_proto_rawDesc = []byte{ 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x22, 0x27, 0x0a, 0x0d, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, - 0x1e, 0x0a, 0x0c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x42, - 0x1f, 0x5a, 0x1d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x66, 0x69, - 0x61, 0x74, 0x6a, 0x61, 0x66, 0x2f, 0x6e, 0x6a, 0x75, 0x6d, 0x70, 0x3b, 0x6d, 0x61, 0x69, 0x6e, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x14, 0x0a, 0x02, 0x49, 0x44, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x35, 0x0a, 0x0b, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x42, 0x1f, 0x5a, 0x1d, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x66, 0x69, 0x61, 0x74, 0x6a, + 0x61, 0x66, 0x2f, 0x6e, 0x6a, 0x75, 0x6d, 0x70, 0x3b, 0x6d, 0x61, 0x69, 0x6e, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -267,12 +325,13 @@ func file_internal_proto_rawDescGZIP() []byte { return file_internal_proto_rawDescData } -var file_internal_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_internal_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_internal_proto_goTypes = []any{ (*CachedEvent)(nil), // 0: CachedEvent (*FollowListArchive)(nil), // 1: FollowListArchive (*PubKeyArchive)(nil), // 2: PubKeyArchive - (*EventInRelay)(nil), // 3: EventInRelay + (*ID)(nil), // 3: ID + (*BannedEvent)(nil), // 4: BannedEvent } var file_internal_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type @@ -325,7 +384,19 @@ func file_internal_proto_init() { } } file_internal_proto_msgTypes[3].Exporter = func(v any, i int) any { - switch v := v.(*EventInRelay); i { + switch v := v.(*ID); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_internal_proto_msgTypes[4].Exporter = func(v any, i int) any { + switch v := v.(*BannedEvent); i { case 0: return &v.state case 1: @@ -343,7 +414,7 @@ func file_internal_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_internal_proto_rawDesc, NumEnums: 0, - NumMessages: 4, + NumMessages: 5, NumExtensions: 0, NumServices: 0, }, diff --git a/internal.proto b/internal.proto index b39291e..218eda8 100644 --- a/internal.proto +++ b/internal.proto @@ -17,6 +17,11 @@ message PubKeyArchive { string pubkey = 1; } -message EventInRelay { +message ID { string id = 1; } + +message BannedEvent { + bytes id = 1; + string reason = 2; +} diff --git a/internaldb.go b/internaldb.go index 12a2944..1809860 100644 --- a/internaldb.go +++ b/internaldb.go @@ -19,6 +19,7 @@ const ( TypeFollowListArchive leafdb.DataType = 3 TypePubKeyArchive leafdb.DataType = 4 TypeEventInRelay leafdb.DataType = 5 + TypeBannedEvent leafdb.DataType = 6 ) func NewInternalDB(path string) (*InternalDB, error) { @@ -36,7 +37,9 @@ func NewInternalDB(path string) (*InternalDB, error) { case TypePubKeyArchive: v = &PubKeyArchive{} case TypeEventInRelay: - v = &EventInRelay{} + v = &ID{} + case TypeBannedEvent: + v = &BannedEvent{} default: return nil, fmt.Errorf("what is this? %v", t) } @@ -74,6 +77,14 @@ func NewInternalDB(path string) (*InternalDB, error) { emit(pkb) }, }, + "banned-event": { + Version: 1, + Types: []leafdb.DataType{TypeBannedEvent}, + Emit: func(t leafdb.DataType, value proto.Message, emit func([]byte)) { + ban := value.(*BannedEvent) + emit(ban.Id[0:8]) + }, + }, }, Views: map[string]leafdb.ViewDefinition[proto.Message]{ "pubkey-archive": { @@ -92,7 +103,7 @@ func NewInternalDB(path string) (*InternalDB, error) { Emit: func(t leafdb.DataType, value proto.Message, emit func(idxkey []byte, t leafdb.DataType, value proto.Message)) { ee := value.(*CachedEvent) for _, r := range ee.Relays { - emit([]byte(trimProtocolAndEndingSlash(r)), TypeEventInRelay, &EventInRelay{Id: ee.Id}) + emit([]byte(trimProtocolAndEndingSlash(r)), TypeEventInRelay, &ID{Id: ee.Id}) } }, }, @@ -194,10 +205,46 @@ func (internal *InternalDB) getRelaysForEvent(eventId string) []string { func (internal *InternalDB) getEventsInRelay(hostname string) iter.Seq[string] { return func(yield func(string) bool) { for value := range internal.DB.View(leafdb.ExactQuery("events-in-relay", []byte(hostname))) { - evtid := value.(*EventInRelay) + evtid := value.(*ID) if !yield(evtid.Id) { break } } } } + +func (internal *InternalDB) banEvent(id, reason string) error { + idb, err := hex.DecodeString(id) + if err != nil { + return err + } + + _, err = internal.DB.AddOrReplace("banned-event", TypeBannedEvent, &BannedEvent{ + Id: idb, + Reason: reason, + }) + + return err +} + +func (internal *InternalDB) unbanEvent(id string) error { + idb, err := hex.DecodeString(id) + if err != nil { + return err + } + _, err = internal.DB.DeleteQuery(leafdb.ExactQuery("banned-event", idb[0:8])) + return err +} + +func (internal *InternalDB) isBanned(id string) (bool, string) { + idb, err := hex.DecodeString(id) + if err != nil { + return false, "" + } + + for record := range internal.DB.Query(leafdb.ExactQuery("banned-event", idb[0:8])) { + return true, record.(*BannedEvent).Reason + } + + return false, "" +} diff --git a/main.go b/main.go index 711a492..9c1e726 100644 --- a/main.go +++ b/main.go @@ -129,6 +129,9 @@ func main() { }, ) + // admin + setupRelayManagement(relay) + // routes mux := relay.Router() mux.Handle("/njump/static/", http.StripPrefix("/njump/", http.FileServer(http.FS(static)))) diff --git a/management.go b/management.go new file mode 100644 index 0000000..c7a53cd --- /dev/null +++ b/management.go @@ -0,0 +1,45 @@ +package main + +import ( + "context" + "slices" + + "github.com/fiatjaf/khatru" + "github.com/nbd-wtf/go-nostr" + "github.com/nbd-wtf/go-nostr/nip86" +) + +func setupRelayManagement(relay *khatru.Relay) { + relay.ManagementAPI.RejectAPICall = append(relay.ManagementAPI.RejectAPICall, + func(ctx context.Context, mp nip86.MethodParams) (reject bool, msg string) { + if slices.Contains(s.TrustedPubKeys, khatru.GetAuthed(ctx)) { + return false, "" + } + return true, "you are not a trusted pubkey" + }, + ) + relay.ManagementAPI.BanEvent = func(ctx context.Context, id, reason string) error { + log.Info().Str("id", id).Str("reason", reason).Msg("banning event") + ch, err := sys.Store.QueryEvents(ctx, nostr.Filter{IDs: []string{id}}) + if err != nil { + return err + } + + for evt := range ch { + sys.Store.DeleteEvent(ctx, evt) + } + + if err := internal.banEvent(id, reason); err != nil { + return err + } + + return nil + } + relay.ManagementAPI.AllowEvent = func(ctx context.Context, id, reason string) error { + log.Info().Str("id", id).Str("reason", reason).Msg("unbanning event") + if err := internal.unbanEvent(id); err != nil { + return err + } + return nil + } +} diff --git a/render_event.go b/render_event.go index 674b246..7aa58fd 100644 --- a/render_event.go +++ b/render_event.go @@ -72,6 +72,14 @@ func renderEvent(w http.ResponseWriter, r *http.Request) { return } + if banned, reason := internal.isBanned(data.event.ID); banned { + w.Header().Set("Cache-Control", "max-age=60") + log.Warn().Err(err).Str("code", code).Str("reason", reason).Msg("event banned") + w.WriteHeader(http.StatusNotFound) + errorTemplate(ErrorPageParams{Errors: "event banned"}).Render(ctx, w) + return + } + // if we originally got a note code or an nevent with no hints // augment the URL to point to an nevent with hints -- redirect if p, ok := decoded.(nostr.EventPointer); (ok && p.Author == "" && len(p.Relays) == 0) || prefix == "note" {