From 11f35cbab1614b26840bbf58b8a006ae363106ad Mon Sep 17 00:00:00 2001 From: yse <70684173+hydra-yse@users.noreply.github.com> Date: Thu, 29 Feb 2024 09:59:09 +0100 Subject: [PATCH] feat: remove webhook subcription by url (#194) * feat: added webhook unsubscription * test: adding unsubscribe tests --- itest/lspd_node.go | 32 +++++ itest/lspd_test.go | 4 + itest/notification_test.go | 40 ++++++ notifications/notifications.pb.go | 174 ++++++++++++++++++++++--- notifications/notifications.proto | 13 +- notifications/notifications_grpc.pb.go | 36 +++++ notifications/server.go | 56 ++++++++ notifications/store.go | 1 + postgresql/notifications_store.go | 21 +++ 9 files changed, 356 insertions(+), 21 deletions(-) diff --git a/itest/lspd_node.go b/itest/lspd_node.go index 36492ad1..29fa99fd 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -321,6 +321,38 @@ func SubscribeNotifications(l LspNode, b BreezClient, url string, continueOnErro return err } +func UnsubscribeNotifications(l LspNode, b BreezClient, url string, continueOnError bool) error { + msg := append(lightning.SignedMsgPrefix, []byte(url)...) + first := sha256.Sum256([]byte(msg)) + second := sha256.Sum256(first[:]) + sig, err := ecdsa.SignCompact(b.Node().PrivateKey(), second[:], true) + assert.NoError(b.Harness().T, err) + request := notifications.UnsubscribeNotificationsRequest{ + Url: url, + Signature: zbase32.EncodeToString(sig), + } + serialized, err := proto.Marshal(&request) + lntest.CheckError(l.Harness().T, err) + + encrypted, err := ecies.Encrypt(l.EciesPublicKey(), serialized) + lntest.CheckError(l.Harness().T, err) + + ctx := metadata.AppendToOutgoingContext(l.Harness().Ctx, "authorization", "Bearer hello") + log.Printf("Removing notification subscription") + _, err = l.NotificationsRpc().UnsubscribeNotifications( + ctx, + ¬ifications.EncryptedNotificationRequest{ + Blob: encrypted, + }, + ) + + if !continueOnError { + lntest.CheckError(l.Harness().T, err) + } + + return nil +} + type FeeParamSetting struct { Validity time.Duration MinMsat uint64 diff --git a/itest/lspd_test.go b/itest/lspd_test.go index 34517dc2..b8f4c908 100644 --- a/itest/lspd_test.go +++ b/itest/lspd_test.go @@ -185,6 +185,10 @@ var allTestCases = []*testCase{ name: "testOfflineNotificationZeroConfChannel", test: testOfflineNotificationZeroConfChannel, }, + { + name: "testNotificationsUnsubscribe", + test: testNotificationsUnsubscribe, + }, { name: "testLsps0GetProtocolVersions", test: testLsps0GetProtocolVersions, diff --git a/itest/notification_test.go b/itest/notification_test.go index d116b214..9d5b4ba0 100644 --- a/itest/notification_test.go +++ b/itest/notification_test.go @@ -297,3 +297,43 @@ func testOfflineNotificationZeroConfChannel(p *testParams) { actualheight := p.Miner().GetBlockHeight() assert.Equal(p.t, expectedheight, actualheight) } + +func testNotificationsUnsubscribe(p *testParams) { + url := "http://127.0.0.1/api/v1/notify" + pubkey := hex.EncodeToString(p.BreezClient().Node().PrivateKey().PubKey().SerializeCompressed()) + + log.Printf("Client pubkey: %s", pubkey) + + count_subscriptions := func() (uint64, error) { + row := p.lsp.PostgresBackend().Pool().QueryRow( + p.h.Ctx, + `SELECT COUNT(*) + FROM public.notification_subscriptions + WHERE pubkey = $1 AND url = $2`, + pubkey, + url, + ) + + var count uint64 + if err := row.Scan(&count); err != nil { + p.h.T.Fatalf("Failed to query subscriptions: %v", err) + return 0, err + } + + return count, nil + } + + SubscribeNotifications(p.lsp, p.BreezClient(), url, false) + + // Verify that we have subscribed to the webhook + count, err := count_subscriptions() + assert.Nil(p.h.T, err) + assert.Equal(p.h.T, count, 1) + + UnsubscribeNotifications(p.lsp, p.BreezClient(), url, false) + + // Verify that we have unsubscribed from the webhook + count, err = count_subscriptions() + assert.Nil(p.h.T, err) + assert.Equal(p.h.T, count, 0) +} diff --git a/notifications/notifications.pb.go b/notifications/notifications.pb.go index 80da290f..0f23ee6a 100644 --- a/notifications/notifications.pb.go +++ b/notifications/notifications.pb.go @@ -160,6 +160,99 @@ func (*SubscribeNotificationsReply) Descriptor() ([]byte, []int) { return file_notifications_proto_rawDescGZIP(), []int{2} } +type UnsubscribeNotificationsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + Signature string `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` +} + +func (x *UnsubscribeNotificationsRequest) Reset() { + *x = UnsubscribeNotificationsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_notifications_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UnsubscribeNotificationsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UnsubscribeNotificationsRequest) ProtoMessage() {} + +func (x *UnsubscribeNotificationsRequest) ProtoReflect() protoreflect.Message { + mi := &file_notifications_proto_msgTypes[3] + 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 UnsubscribeNotificationsRequest.ProtoReflect.Descriptor instead. +func (*UnsubscribeNotificationsRequest) Descriptor() ([]byte, []int) { + return file_notifications_proto_rawDescGZIP(), []int{3} +} + +func (x *UnsubscribeNotificationsRequest) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *UnsubscribeNotificationsRequest) GetSignature() string { + if x != nil { + return x.Signature + } + return "" +} + +type UnsubscribeNotificationsReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *UnsubscribeNotificationsReply) Reset() { + *x = UnsubscribeNotificationsReply{} + if protoimpl.UnsafeEnabled { + mi := &file_notifications_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UnsubscribeNotificationsReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UnsubscribeNotificationsReply) ProtoMessage() {} + +func (x *UnsubscribeNotificationsReply) ProtoReflect() protoreflect.Message { + mi := &file_notifications_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 UnsubscribeNotificationsReply.ProtoReflect.Descriptor instead. +func (*UnsubscribeNotificationsReply) Descriptor() ([]byte, []int) { + return file_notifications_proto_rawDescGZIP(), []int{4} +} + var File_notifications_proto protoreflect.FileDescriptor var file_notifications_proto_rawDesc = []byte{ @@ -175,18 +268,33 @@ var file_notifications_proto_rawDesc = []byte{ 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x1d, 0x0a, 0x1b, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x32, 0x84, 0x01, 0x0a, 0x0d, 0x4e, 0x6f, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x73, 0x0a, 0x16, 0x53, 0x75, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x51, 0x0a, 0x1f, 0x55, 0x6e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2b, 0x2e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4e, 0x6f, - 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x2a, 0x2e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, - 0x25, 0x5a, 0x23, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x72, - 0x65, 0x65, 0x7a, 0x2f, 0x6c, 0x73, 0x70, 0x64, 0x2f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, + 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1c, 0x0a, + 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x1f, 0x0a, 0x1d, 0x55, + 0x6e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x32, 0xfd, 0x01, 0x0a, + 0x0d, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x73, + 0x0a, 0x16, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2b, 0x2e, 0x6e, 0x6f, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, + 0x65, 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x4e, + 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x70, 0x6c, + 0x79, 0x22, 0x00, 0x12, 0x77, 0x0a, 0x18, 0x55, 0x6e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, + 0x62, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, + 0x2b, 0x2e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, + 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x6e, + 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x55, 0x6e, 0x73, + 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x25, 0x5a, 0x23, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x72, 0x65, 0x65, 0x7a, + 0x2f, 0x6c, 0x73, 0x70, 0x64, 0x2f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -201,17 +309,21 @@ func file_notifications_proto_rawDescGZIP() []byte { return file_notifications_proto_rawDescData } -var file_notifications_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_notifications_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_notifications_proto_goTypes = []interface{}{ - (*EncryptedNotificationRequest)(nil), // 0: notifications.EncryptedNotificationRequest - (*SubscribeNotificationsRequest)(nil), // 1: notifications.SubscribeNotificationsRequest - (*SubscribeNotificationsReply)(nil), // 2: notifications.SubscribeNotificationsReply + (*EncryptedNotificationRequest)(nil), // 0: notifications.EncryptedNotificationRequest + (*SubscribeNotificationsRequest)(nil), // 1: notifications.SubscribeNotificationsRequest + (*SubscribeNotificationsReply)(nil), // 2: notifications.SubscribeNotificationsReply + (*UnsubscribeNotificationsRequest)(nil), // 3: notifications.UnsubscribeNotificationsRequest + (*UnsubscribeNotificationsReply)(nil), // 4: notifications.UnsubscribeNotificationsReply } var file_notifications_proto_depIdxs = []int32{ 0, // 0: notifications.Notifications.SubscribeNotifications:input_type -> notifications.EncryptedNotificationRequest - 2, // 1: notifications.Notifications.SubscribeNotifications:output_type -> notifications.SubscribeNotificationsReply - 1, // [1:2] is the sub-list for method output_type - 0, // [0:1] is the sub-list for method input_type + 0, // 1: notifications.Notifications.UnsubscribeNotifications:input_type -> notifications.EncryptedNotificationRequest + 2, // 2: notifications.Notifications.SubscribeNotifications:output_type -> notifications.SubscribeNotificationsReply + 4, // 3: notifications.Notifications.UnsubscribeNotifications:output_type -> notifications.UnsubscribeNotificationsReply + 2, // [2:4] is the sub-list for method output_type + 0, // [0:2] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name @@ -259,6 +371,30 @@ func file_notifications_proto_init() { return nil } } + file_notifications_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UnsubscribeNotificationsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_notifications_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UnsubscribeNotificationsReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -266,7 +402,7 @@ func file_notifications_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_notifications_proto_rawDesc, NumEnums: 0, - NumMessages: 3, + NumMessages: 5, NumExtensions: 0, NumServices: 1, }, diff --git a/notifications/notifications.proto b/notifications/notifications.proto index c7544c27..363b19b3 100644 --- a/notifications/notifications.proto +++ b/notifications/notifications.proto @@ -7,6 +7,9 @@ package notifications; service Notifications { rpc SubscribeNotifications(EncryptedNotificationRequest) returns (SubscribeNotificationsReply) {} + + rpc UnsubscribeNotifications(EncryptedNotificationRequest) + returns (UnsubscribeNotificationsReply) {} } message EncryptedNotificationRequest { @@ -18,5 +21,11 @@ message SubscribeNotificationsRequest { string signature = 2; } -message SubscribeNotificationsReply { -} \ No newline at end of file +message SubscribeNotificationsReply {} + +message UnsubscribeNotificationsRequest { + string url = 1; + string signature = 2; +} + +message UnsubscribeNotificationsReply {} diff --git a/notifications/notifications_grpc.pb.go b/notifications/notifications_grpc.pb.go index ad591899..1d11b073 100644 --- a/notifications/notifications_grpc.pb.go +++ b/notifications/notifications_grpc.pb.go @@ -23,6 +23,7 @@ const _ = grpc.SupportPackageIsVersion7 // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type NotificationsClient interface { SubscribeNotifications(ctx context.Context, in *EncryptedNotificationRequest, opts ...grpc.CallOption) (*SubscribeNotificationsReply, error) + UnsubscribeNotifications(ctx context.Context, in *EncryptedNotificationRequest, opts ...grpc.CallOption) (*UnsubscribeNotificationsReply, error) } type notificationsClient struct { @@ -42,11 +43,21 @@ func (c *notificationsClient) SubscribeNotifications(ctx context.Context, in *En return out, nil } +func (c *notificationsClient) UnsubscribeNotifications(ctx context.Context, in *EncryptedNotificationRequest, opts ...grpc.CallOption) (*UnsubscribeNotificationsReply, error) { + out := new(UnsubscribeNotificationsReply) + err := c.cc.Invoke(ctx, "/notifications.Notifications/UnsubscribeNotifications", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // NotificationsServer is the server API for Notifications service. // All implementations must embed UnimplementedNotificationsServer // for forward compatibility type NotificationsServer interface { SubscribeNotifications(context.Context, *EncryptedNotificationRequest) (*SubscribeNotificationsReply, error) + UnsubscribeNotifications(context.Context, *EncryptedNotificationRequest) (*UnsubscribeNotificationsReply, error) mustEmbedUnimplementedNotificationsServer() } @@ -57,6 +68,9 @@ type UnimplementedNotificationsServer struct { func (UnimplementedNotificationsServer) SubscribeNotifications(context.Context, *EncryptedNotificationRequest) (*SubscribeNotificationsReply, error) { return nil, status.Errorf(codes.Unimplemented, "method SubscribeNotifications not implemented") } +func (UnimplementedNotificationsServer) UnsubscribeNotifications(context.Context, *EncryptedNotificationRequest) (*UnsubscribeNotificationsReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method UnsubscribeNotifications not implemented") +} func (UnimplementedNotificationsServer) mustEmbedUnimplementedNotificationsServer() {} // UnsafeNotificationsServer may be embedded to opt out of forward compatibility for this service. @@ -88,6 +102,24 @@ func _Notifications_SubscribeNotifications_Handler(srv interface{}, ctx context. return interceptor(ctx, in, info, handler) } +func _Notifications_UnsubscribeNotifications_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(EncryptedNotificationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NotificationsServer).UnsubscribeNotifications(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/notifications.Notifications/UnsubscribeNotifications", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NotificationsServer).UnsubscribeNotifications(ctx, req.(*EncryptedNotificationRequest)) + } + return interceptor(ctx, in, info, handler) +} + // Notifications_ServiceDesc is the grpc.ServiceDesc for Notifications service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -99,6 +131,10 @@ var Notifications_ServiceDesc = grpc.ServiceDesc{ MethodName: "SubscribeNotifications", Handler: _Notifications_SubscribeNotifications_Handler, }, + { + MethodName: "UnsubscribeNotifications", + Handler: _Notifications_UnsubscribeNotifications_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "notifications.proto", diff --git a/notifications/server.go b/notifications/server.go index 9e129bbc..6a6e8785 100644 --- a/notifications/server.go +++ b/notifications/server.go @@ -82,3 +82,59 @@ func (s *server) SubscribeNotifications( log.Printf("%v was successfully registered for notifications on url %s", pubkey, request.Url) return &SubscribeNotificationsReply{}, nil } + +func (s *server) UnsubscribeNotifications( + ctx context.Context, + in *EncryptedNotificationRequest, +) (*UnsubscribeNotificationsReply, error) { + node, _, err := lspdrpc.GetNode(ctx) + if err != nil { + return nil, err + } + + data, err := ecies.Decrypt(node.EciesPrivateKey, in.Blob) + if err != nil { + log.Printf( + "failed to unsubscribe: ecies.Decrypt error: %v", + err, + ) + return nil, fmt.Errorf("ecies.Decrypt(%x) error: %w", in.Blob, err) + } + + var request UnsubscribeNotificationsRequest + err = proto.Unmarshal(data, &request) + if err != nil { + log.Printf( + "failed to unsubscribe: proto.Unmarshal(%x) error: %v", + data, + err, + ) + return nil, fmt.Errorf("proto.Unmarshal(%x) error: %w", data, err) + } + + pubkey, err := lightning.VerifyMessage([]byte(request.Url), request.Signature) + if err != nil { + log.Printf( + "failed to unsubscribe on url %s: lightning.VerifyMessage error: %v", + request.Url, + err, + ) + + return nil, err + } + + err = s.store.Unsubscribe(ctx, hex.EncodeToString(pubkey.SerializeCompressed()), request.Url) + + if err != nil { + log.Printf( + "failed to unsubscribe %x on url %s: %v", + pubkey.SerializeCompressed(), + request.Url, + err, + ) + + return nil, ErrInternal + } + + return &UnsubscribeNotificationsReply{}, nil +} diff --git a/notifications/store.go b/notifications/store.go index b4a7e941..1bfba87a 100644 --- a/notifications/store.go +++ b/notifications/store.go @@ -8,5 +8,6 @@ import ( type Store interface { Register(ctx context.Context, pubkey string, url string) error GetRegistrations(ctx context.Context, pubkey string) ([]string, error) + Unsubscribe(ctx context.Context, pubkey string, url string) error RemoveExpired(ctx context.Context, before time.Time) error } diff --git a/postgresql/notifications_store.go b/postgresql/notifications_store.go index f020ab52..eb7b0982 100644 --- a/postgresql/notifications_store.go +++ b/postgresql/notifications_store.go @@ -76,6 +76,27 @@ func (s *NotificationsStore) GetRegistrations( return result, nil } +func (s *NotificationsStore) Unsubscribe( + ctx context.Context, + pubkey string, + url string, +) error { + pk, err := hex.DecodeString(pubkey) + if err != nil { + return err + } + + _, err = s.pool.Exec( + ctx, + `DELETE FROM public.notification_subscriptions + WHERE pubkey = $1 AND url = $2`, + pk, + url, + ) + + return nil +} + func (s *NotificationsStore) RemoveExpired( ctx context.Context, before time.Time,