From 94760792af3b1b6e7c716f13955ecf4b3988b96e Mon Sep 17 00:00:00 2001 From: Nicolas Dorier Date: Thu, 25 Jul 2024 22:46:02 +0900 Subject: [PATCH 01/64] Add parts of the UI to translate (#6119) --- BTCPayServer.Tests/SeleniumTester.cs | 1 + BTCPayServer.Tests/SeleniumTests.cs | 20 ++--- BTCPayServer.Tests/UtilitiesTests.cs | 87 +++++++++++++----- .../Components/MainNav/Default.cshtml | 88 +++++++++---------- .../Components/ThemeSwitch/Default.cshtml | 2 +- .../UIServerController.Translations.cs | 13 +++ .../Controllers/UIServerController.cs | 5 +- .../EditDictionaryViewModel.cs | 1 + BTCPayServer/Services/Translations.Default.cs | 80 +++++++++++++++++ BTCPayServer/Services/Translations.cs | 29 +++++- BTCPayServer/TagHelpers/TranslateTagHelper.cs | 32 +++++-- .../Views/Shared/CreateOrEditRole.cshtml | 2 +- .../Shared/Crowdfund/NavExtension.cshtml | 4 +- .../Shared/Crowdfund/UpdateCrowdfund.cshtml | 2 +- BTCPayServer/Views/Shared/ListRoles.cshtml | 2 +- .../Views/Shared/PayButton/Enable.cshtml | 2 +- .../Shared/PayButton/NavExtension.cshtml | 2 +- .../Views/Shared/PayButton/PayButton.cshtml | 2 +- .../Shared/PointOfSale/NavExtension.cshtml | 4 +- .../PointOfSale/UpdatePointOfSale.cshtml | 2 +- .../Views/Shared/Shopify/NavExtension.cshtml | 2 +- BTCPayServer/Views/Shared/_FormWrap.cshtml | 2 +- BTCPayServer/Views/UIAccount/SignedOut.cshtml | 2 +- BTCPayServer/Views/UIApps/CreateApp.cshtml | 2 +- BTCPayServer/Views/UIFido2/Create.cshtml | 2 +- .../Views/UIInvoice/CreateInvoice.cshtml | 2 +- BTCPayServer/Views/UIInvoice/Invoice.cshtml | 2 +- .../Views/UILNURL/EditLightningAddress.cshtml | 2 +- BTCPayServer/Views/UILNURLAuth/Create.cshtml | 2 +- .../Configure.cshtml | 2 +- .../ConfirmLightningPayout.cshtml | 2 +- .../LightningPayoutResult.cshtml | 2 +- BTCPayServer/Views/UIManage/APIKeys.cshtml | 2 +- BTCPayServer/Views/UIManage/AddApiKey.cshtml | 2 +- .../Views/UIManage/ChangePassword.cshtml | 2 +- .../Views/UIManage/EnableAuthenticator.cshtml | 2 +- .../UIManage/GenerateRecoveryCodes.cshtml | 4 +- BTCPayServer/Views/UIManage/Index.cshtml | 2 +- BTCPayServer/Views/UIManage/LoginCodes.cshtml | 2 +- .../UIManage/NotificationSettings.cshtml | 2 +- .../Views/UIManage/SetPassword.cshtml | 2 +- .../UIManage/TwoFactorAuthentication.cshtml | 2 +- .../Views/UINotifications/Index.cshtml | 2 +- .../Configure.cshtml | 2 +- .../EditPaymentRequest.cshtml | 2 +- .../ConfigureStorePayoutProcessors.cshtml | 2 +- .../UIPullPayment/EditPullPayment.cshtml | 2 +- BTCPayServer/Views/UIServer/Branding.cshtml | 6 +- .../UIServer/CLightningRestServices.cshtml | 2 +- .../Views/UIServer/ConfiguratorService.cshtml | 2 +- .../Views/UIServer/CreateDictionary.cshtml | 2 +- .../UIServer/CreateTemporaryFileUrl.cshtml | 2 +- BTCPayServer/Views/UIServer/CreateUser.cshtml | 2 +- .../EditAmazonS3StorageProvider.cshtml | 2 +- ...EditAzureBlobStorageStorageProvider.cshtml | 2 +- .../Views/UIServer/EditDictionary.cshtml | 7 +- .../EditFileSystemStorageProvider.cshtml | 2 +- ...itGoogleCloudStorageStorageProvider.cshtml | 2 +- BTCPayServer/Views/UIServer/Emails.cshtml | 2 +- BTCPayServer/Views/UIServer/Files.cshtml | 2 +- .../UIServer/LightningChargeServices.cshtml | 2 +- .../UIServer/LightningWalletServices.cshtml | 2 +- .../Views/UIServer/ListDictionaries.cshtml | 2 +- BTCPayServer/Views/UIServer/ListStores.cshtml | 4 +- BTCPayServer/Views/UIServer/ListUsers.cshtml | 2 +- .../Views/UIServer/LndSeedBackup.cshtml | 2 +- .../Views/UIServer/LndServices.cshtml | 2 +- BTCPayServer/Views/UIServer/Logs.cshtml | 4 +- .../Views/UIServer/Maintenance.cshtml | 4 +- BTCPayServer/Views/UIServer/P2PService.cshtml | 2 +- BTCPayServer/Views/UIServer/Policies.cshtml | 2 +- BTCPayServer/Views/UIServer/RPCService.cshtml | 2 +- BTCPayServer/Views/UIServer/SSHService.cshtml | 2 +- BTCPayServer/Views/UIServer/Services.cshtml | 4 +- BTCPayServer/Views/UIServer/Storage.cshtml | 2 +- BTCPayServer/Views/UIServer/User.cshtml | 2 +- .../UIStorePullPayments/NewPullPayment.cshtml | 2 +- .../UIStorePullPayments/PullPayments.cshtml | 2 +- .../Views/UIStores/CheckoutAppearance.cshtml | 2 +- .../Views/UIStores/CreateToken.cshtml | 4 +- BTCPayServer/Views/UIStores/Dashboard.cshtml | 2 +- .../Views/UIStores/GeneralSettings.cshtml | 16 ++-- BTCPayServer/Views/UIStores/Lightning.cshtml | 2 +- .../Views/UIStores/LightningSettings.cshtml | 2 +- .../Views/UIStores/ModifyWebhook.cshtml | 2 +- BTCPayServer/Views/UIStores/Rates.cshtml | 2 +- .../Views/UIStores/RequestPairing.cshtml | 4 +- .../Views/UIStores/StoreEmails.cshtml | 2 +- BTCPayServer/Views/UIStores/StoreUsers.cshtml | 2 +- .../Views/UIStores/TestWebhook.cshtml | 2 +- .../Views/UIStores/WalletSettings.cshtml | 2 +- BTCPayServer/Views/UIStores/Webhooks.cshtml | 2 +- .../Views/UIUserStores/CreateStore.cshtml | 2 +- .../Views/UIUserStores/ListStores.cshtml | 4 +- .../UIUserStores/_CreateStoreForm.cshtml | 2 +- .../Views/UIWallets/WalletLabels.cshtml | 4 +- 96 files changed, 381 insertions(+), 192 deletions(-) diff --git a/BTCPayServer.Tests/SeleniumTester.cs b/BTCPayServer.Tests/SeleniumTester.cs index e2f51b29d..7e50b3bd8 100644 --- a/BTCPayServer.Tests/SeleniumTester.cs +++ b/BTCPayServer.Tests/SeleniumTester.cs @@ -18,6 +18,7 @@ using NBitcoin; using NBitcoin.RPC; using OpenQA.Selenium; using OpenQA.Selenium.Chrome; +using OpenQA.Selenium.Support.Extensions; using OpenQA.Selenium.Support.UI; using Xunit; diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs index 65419ce2f..5aa2c6dbd 100644 --- a/BTCPayServer.Tests/SeleniumTests.cs +++ b/BTCPayServer.Tests/SeleniumTests.cs @@ -2063,7 +2063,7 @@ namespace BTCPayServer.Tests s.GoToStore(s.StoreId, StoreNavPages.PullPayments); - s.Driver.FindElement(By.Id("NewPullPayment")).Click(); + s.ClickPagePrimary(); s.Driver.FindElement(By.Id("Name")).SendKeys("PP1"); s.Driver.FindElement(By.Id("Amount")).Clear(); s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0"); @@ -2108,7 +2108,7 @@ namespace BTCPayServer.Tests await s.Server.ExplorerNode.GenerateAsync(1); await s.FundStoreWallet(denomination: 50.0m); s.GoToStore(s.StoreId, StoreNavPages.PullPayments); - s.Driver.FindElement(By.Id("NewPullPayment")).Click(); + s.ClickPagePrimary(); s.Driver.FindElement(By.Id("Name")).SendKeys("PP1"); s.Driver.FindElement(By.Id("Amount")).Clear(); s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0"); @@ -2122,7 +2122,7 @@ namespace BTCPayServer.Tests s.GoToStore(s.StoreId, StoreNavPages.PullPayments); - s.Driver.FindElement(By.Id("NewPullPayment")).Click(); + s.ClickPagePrimary(); s.Driver.FindElement(By.Id("Name")).SendKeys("PP2"); s.Driver.FindElement(By.Id("Amount")).Clear(); s.Driver.FindElement(By.Id("Amount")).SendKeys("100.0"); @@ -2225,7 +2225,7 @@ namespace BTCPayServer.Tests s.GenerateWallet("BTC", "", true, true); s.GoToStore(s.StoreId, StoreNavPages.PullPayments); - s.Driver.FindElement(By.Id("NewPullPayment")).Click(); + s.ClickPagePrimary(); s.Driver.FindElement(By.Id("Name")).SendKeys("External Test"); s.Driver.FindElement(By.Id("Amount")).Clear(); s.Driver.FindElement(By.Id("Amount")).SendKeys("0.001"); @@ -2276,7 +2276,7 @@ namespace BTCPayServer.Tests //Currently an onchain wallet is required to use the Lightning payouts feature.. s.GenerateWallet("BTC", "", true, true); s.GoToStore(newStore.storeId, StoreNavPages.PullPayments); - s.Driver.FindElement(By.Id("NewPullPayment")).Click(); + s.ClickPagePrimary(); var paymentMethodOptions = s.Driver.FindElements(By.CssSelector("input[name='PayoutMethods']")); Assert.Equal(2, paymentMethodOptions.Count); @@ -2344,7 +2344,7 @@ namespace BTCPayServer.Tests //auto-approve pull payments s.GoToStore(StoreNavPages.PullPayments); - s.Driver.FindElement(By.Id("NewPullPayment")).Click(); + s.ClickPagePrimary(); s.Driver.FindElement(By.Id("Name")).SendKeys("PP1"); s.Driver.SetCheckbox(By.Id("AutoApproveClaims"), true); s.Driver.FindElement(By.Id("Amount")).Clear(); @@ -2367,7 +2367,7 @@ namespace BTCPayServer.Tests // LNURL Withdraw support check with BTC denomination s.GoToStore(s.StoreId, StoreNavPages.PullPayments); - s.Driver.FindElement(By.Id("NewPullPayment")).Click(); + s.ClickPagePrimary(); s.Driver.FindElement(By.Id("Name")).SendKeys("PP1"); s.Driver.SetCheckbox(By.Id("AutoApproveClaims"), true); s.Driver.FindElement(By.Id("Amount")).Clear(); @@ -2459,7 +2459,7 @@ namespace BTCPayServer.Tests } s.GoToStore(s.StoreId, StoreNavPages.PullPayments); - s.Driver.FindElement(By.Id("NewPullPayment")).Click(); + s.ClickPagePrimary(); s.Driver.FindElement(By.Id("Name")).SendKeys("PP1"); s.Driver.SetCheckbox(By.Id("AutoApproveClaims"), false); s.Driver.FindElement(By.Id("Amount")).Clear(); @@ -2499,7 +2499,7 @@ namespace BTCPayServer.Tests // LNURL Withdraw support check with SATS denomination s.GoToStore(s.StoreId, StoreNavPages.PullPayments); - s.Driver.FindElement(By.Id("NewPullPayment")).Click(); + s.ClickPagePrimary(); s.Driver.FindElement(By.Id("Name")).SendKeys("PP SATS"); s.Driver.SetCheckbox(By.Id("AutoApproveClaims"), true); s.Driver.FindElement(By.Id("Amount")).Clear(); @@ -3122,7 +3122,7 @@ namespace BTCPayServer.Tests // Check that pull payment has lightning option s.GoToStore(s.StoreId, StoreNavPages.PullPayments); - s.Driver.FindElement(By.Id("NewPullPayment")).Click(); + s.ClickPagePrimary(); Assert.Equal(PaymentTypes.LN.GetPaymentMethodId(cryptoCode), PaymentMethodId.Parse(Assert.Single(s.Driver.FindElements(By.CssSelector("input[name='PayoutMethods']"))).GetAttribute("value"))); s.Driver.FindElement(By.Id("Name")).SendKeys("PP1"); s.Driver.FindElement(By.Id("Amount")).Clear(); diff --git a/BTCPayServer.Tests/UtilitiesTests.cs b/BTCPayServer.Tests/UtilitiesTests.cs index 9591870e5..543809a02 100644 --- a/BTCPayServer.Tests/UtilitiesTests.cs +++ b/BTCPayServer.Tests/UtilitiesTests.cs @@ -15,8 +15,10 @@ using BTCPayServer.Client; using BTCPayServer.Client.Models; using BTCPayServer.Controllers; using ExchangeSharp; +using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Intermediate; +using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using NBitcoin; @@ -27,6 +29,7 @@ using OpenQA.Selenium.Chrome; using OpenQA.Selenium.Support.UI; using Xunit; using Xunit.Abstractions; +using static System.Net.Mime.MediaTypeNames; namespace BTCPayServer.Tests { @@ -267,6 +270,63 @@ retry: Thread.Sleep(200); } + class TranslatedKeyNodeWalker : IntermediateNodeWalker + { + private List _defaultTranslatedKeys; + private string _txt; + + public TranslatedKeyNodeWalker(List defaultTranslatedKeys) + { + _defaultTranslatedKeys = defaultTranslatedKeys; + } + + public TranslatedKeyNodeWalker(List defaultTranslatedKeys, string txt) : this(defaultTranslatedKeys) + { + _txt = txt; + } + + public override void VisitTagHelper(TagHelperIntermediateNode node) + { + if (node.TagName == "input") + { + foreach (var tagHelper in node.TagHelpers) + { + if (tagHelper.Name.EndsWith("TranslateTagHelper")) + { + var inner = ToString(node); + if (inner.Contains("type=\"submit\"")) + { + var m = Regex.Match(inner, "value=\"(.*?)\""); + if (m.Success) + { + _defaultTranslatedKeys.Add(m.Groups[1].Value); + } + } + } + } + return; + } + foreach (var tagHelper in node.TagHelpers) + { + if (tagHelper.Name.EndsWith("TranslateTagHelper")) + { + var htmlContent = node.FindDescendantNodes().FirstOrDefault(); + if (htmlContent is not null) + { + var inner = ToString(htmlContent); + _defaultTranslatedKeys.Add(inner); + } + } + } + base.VisitTagHelper(node); + } + + private string ToString(IntermediateNode? node) + { + return _txt.Substring(node.Source.Value.AbsoluteIndex, node.Source.Value.Length); + } + } + /// /// This utilities crawl through the cs files in search for /// Display attributes, then update Translations.Default to list them @@ -308,30 +368,17 @@ retry: defaultTranslatedKeys.Add(match.Groups[1].Value); } } - else if (txt.Contains("text-translate")) - { - filePath = filePath.Replace(Path.Combine(soldir.FullName, "BTCPayServer"), "/"); - var item = engine.FileSystem.GetItem(filePath); + + filePath = filePath.Replace(Path.Combine(soldir.FullName, "BTCPayServer"), "/"); + var item = engine.FileSystem.GetItem(filePath); - var node = (DocumentIntermediateNode)engine.Process(item).Items[typeof(DocumentIntermediateNode)]; - foreach (var n in node.FindDescendantNodes()) - { - foreach (var tagHelper in n.TagHelpers) - { - if (tagHelper.Name.EndsWith("TranslateTagHelper")) - { - var htmlContent = n.FindDescendantNodes().First(); - var inner = txt.Substring(htmlContent.Source.Value.AbsoluteIndex, htmlContent.Source.Value.Length); - defaultTranslatedKeys.Add(inner); - } - } - } - - } + var node = (DocumentIntermediateNode)engine.Process(item).Items[typeof(DocumentIntermediateNode)]; + var w = new TranslatedKeyNodeWalker(defaultTranslatedKeys, txt); + w.Visit(node); } } - defaultTranslatedKeys = defaultTranslatedKeys.Distinct().OrderBy(o => o).ToList(); + defaultTranslatedKeys = defaultTranslatedKeys.Select(d => d.Trim()).Distinct().OrderBy(o => o).ToList(); var path = Path.Combine(soldir.FullName, "BTCPayServer/Services/Translations.Default.cs"); var defaultTranslation = File.ReadAllText(path); var startIdx = defaultTranslation.IndexOf("\"\"\""); diff --git a/BTCPayServer/Components/MainNav/Default.cshtml b/BTCPayServer/Components/MainNav/Default.cshtml index c22cd6f0d..838fdea49 100644 --- a/BTCPayServer/Components/MainNav/Default.cshtml +++ b/BTCPayServer/Components/MainNav/Default.cshtml @@ -34,43 +34,43 @@ @if (ViewData.IsActivePage([StoreNavPages.General, StoreNavPages.Rates, StoreNavPages.CheckoutAppearance, StoreNavPages.Tokens, StoreNavPages.Users, StoreNavPages.Roles, StoreNavPages.Webhooks, StoreNavPages.PayoutProcessors, StoreNavPages.Emails, StoreNavPages.Forms])) { } @@ -79,9 +79,7 @@
From ed4dea214df9d5bb56117ec23eee4c3f612cd9a1 Mon Sep 17 00:00:00 2001 From: Kukks Date: Mon, 3 Jul 2023 08:40:56 +0200 Subject: [PATCH 04/64] seed not xpriv --- BTCPayServer/Controllers/BtcPayAppController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BTCPayServer/Controllers/BtcPayAppController.cs b/BTCPayServer/Controllers/BtcPayAppController.cs index 98cd60d13..1fc83675a 100644 --- a/BTCPayServer/Controllers/BtcPayAppController.cs +++ b/BTCPayServer/Controllers/BtcPayAppController.cs @@ -115,7 +115,7 @@ public class BtcPayAppController : Controller private async Task GetSeed(ExplorerClient client, DerivationSchemeSettings derivation) { return derivation.IsHotWallet && - await client.GetMetadataAsync(derivation.AccountDerivation, WellknownMetadataKeys.MasterHDKey) is { } seed && + await client.GetMetadataAsync(derivation.AccountDerivation, WellknownMetadataKeys.Mnemonic) is { } seed && !string.IsNullOrEmpty(seed) ? seed : null; } From d8c519d008d5ba812b5af0f20bc2abf251f8eac0 Mon Sep 17 00:00:00 2001 From: Kukks Date: Mon, 3 Jul 2023 09:56:00 +0200 Subject: [PATCH 05/64] add common proj --- .../BTCPayApp.CommonServer.csproj | 9 +++ .../IBTCPayAppServerClient.cs | 7 ++ BTCPayApp.CommonServer/PairSuccessResult.cs | 13 ++++ BTCPayServer/App/BTCPayAppExtensions.cs | 21 ++++++ BTCPayServer/App/BTCPayAppHub.cs | 8 +++ BTCPayServer/App/BTCPayAppPlugin.cs | 20 ++++++ .../BtcPayAppController.cs | 66 ++++--------------- BTCPayServer/App/BtcPayAppService.cs | 39 +++++++++++ BTCPayServer/BTCPayServer.csproj | 1 + BTCPayServer/Hosting/BTCPayServerServices.cs | 1 - 10 files changed, 131 insertions(+), 54 deletions(-) create mode 100644 BTCPayApp.CommonServer/BTCPayApp.CommonServer.csproj create mode 100644 BTCPayApp.CommonServer/IBTCPayAppServerClient.cs create mode 100644 BTCPayApp.CommonServer/PairSuccessResult.cs create mode 100644 BTCPayServer/App/BTCPayAppExtensions.cs create mode 100644 BTCPayServer/App/BTCPayAppHub.cs create mode 100644 BTCPayServer/App/BTCPayAppPlugin.cs rename BTCPayServer/{Controllers => App}/BtcPayAppController.cs (62%) create mode 100644 BTCPayServer/App/BtcPayAppService.cs diff --git a/BTCPayApp.CommonServer/BTCPayApp.CommonServer.csproj b/BTCPayApp.CommonServer/BTCPayApp.CommonServer.csproj new file mode 100644 index 000000000..81975abc7 --- /dev/null +++ b/BTCPayApp.CommonServer/BTCPayApp.CommonServer.csproj @@ -0,0 +1,9 @@ + + + + netstandard2.0 + 8 + enable + + + diff --git a/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs b/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs new file mode 100644 index 000000000..84e6839a9 --- /dev/null +++ b/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs @@ -0,0 +1,7 @@ +namespace BTCPayApp.CommonServer +{ + public interface IBTCPayAppServerClient + { + + } +} diff --git a/BTCPayApp.CommonServer/PairSuccessResult.cs b/BTCPayApp.CommonServer/PairSuccessResult.cs new file mode 100644 index 000000000..6073c404f --- /dev/null +++ b/BTCPayApp.CommonServer/PairSuccessResult.cs @@ -0,0 +1,13 @@ +namespace BTCPayApp.CommonServer +{ + public class PairSuccessResult + { + public string Key { get; set; } + public string StoreId { get; set; } + public string UserId { get; set; } + + public string? ExistingWallet { get; set; } + public string? ExistingWalletSeed { get; set; } + public string Network { get; set; } + } +} diff --git a/BTCPayServer/App/BTCPayAppExtensions.cs b/BTCPayServer/App/BTCPayAppExtensions.cs new file mode 100644 index 000000000..4f8ea4380 --- /dev/null +++ b/BTCPayServer/App/BTCPayAppExtensions.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +namespace BTCPayServer.Controllers; + +public static class BTCPayAppExtensions +{ + public static IServiceCollection AddBTCPayApp(this IServiceCollection serviceCollection) + { + serviceCollection.AddSingleton(); + return serviceCollection; + } + + public static void UseBTCPayApp(this IApplicationBuilder builder) + { + builder.UseEndpoints(routeBuilder => + { + routeBuilder.MapHub("hub/btcpayapp"); + }); + } +} \ No newline at end of file diff --git a/BTCPayServer/App/BTCPayAppHub.cs b/BTCPayServer/App/BTCPayAppHub.cs new file mode 100644 index 000000000..f3ffb6b8d --- /dev/null +++ b/BTCPayServer/App/BTCPayAppHub.cs @@ -0,0 +1,8 @@ +using BTCPayApp.CommonServer; +using Microsoft.AspNetCore.SignalR; + +namespace BTCPayServer.Controllers; + +public class BTCPayAppHub : Hub +{ +} \ No newline at end of file diff --git a/BTCPayServer/App/BTCPayAppPlugin.cs b/BTCPayServer/App/BTCPayAppPlugin.cs new file mode 100644 index 000000000..0bde9745d --- /dev/null +++ b/BTCPayServer/App/BTCPayAppPlugin.cs @@ -0,0 +1,20 @@ +using System; +using BTCPayServer.Abstractions.Models; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +namespace BTCPayServer.Controllers; + +public class BTCPayAppPlugin : BaseBTCPayServerPlugin +{ + public override void Execute(IApplicationBuilder applicationBuilder, + IServiceProvider applicationBuilderApplicationServices) + { + applicationBuilder.UseBTCPayApp(); + } + + public override void Execute(IServiceCollection applicationBuilder) + { + applicationBuilder.AddBTCPayApp(); + } +} \ No newline at end of file diff --git a/BTCPayServer/Controllers/BtcPayAppController.cs b/BTCPayServer/App/BtcPayAppController.cs similarity index 62% rename from BTCPayServer/Controllers/BtcPayAppController.cs rename to BTCPayServer/App/BtcPayAppController.cs index 1fc83675a..5e40a817b 100644 --- a/BTCPayServer/Controllers/BtcPayAppController.cs +++ b/BTCPayServer/App/BtcPayAppController.cs @@ -1,53 +1,19 @@ #nullable enable -using System; using System.Linq; using System.Threading.Tasks; +using BTCPayApp.CommonServer; using BTCPayServer.Client; using BTCPayServer.Data; +using BTCPayServer.Plugins.Shopify; using BTCPayServer.Security.Greenfield; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Caching.Memory; using NBitcoin; using NBitcoin.DataEncoders; using NBXplorer; namespace BTCPayServer.Controllers; -public class BtcPayAppService -{ - private readonly IMemoryCache _memoryCache; - - public BtcPayAppService(IMemoryCache memoryCache) - { - _memoryCache = memoryCache; - } - - private string CacheKey(string k) => $"BtcPayAppService_{k}"; - - public async Task GeneratePairingCode(string storeId, string userId) - { - var code = Guid.NewGuid().ToString(); - _memoryCache.Set(CacheKey(code), new PairingRequest() {Key = code, StoreId = storeId, UserId = userId}, - TimeSpan.FromMinutes(5)); - return code; - } - - public PairingRequest? ConsumePairingCode(string code) - { - return _memoryCache.TryGetValue(CacheKey(code), out var pairingRequest) - ? (PairingRequest?)pairingRequest - : null; - } - - public class PairingRequest - { - public string Key { get; set; } - public string StoreId { get; set; } - public string UserId { get; set; } - } -} - [Route("btcpayapp")] public class BtcPayAppController : Controller { @@ -58,7 +24,8 @@ public class BtcPayAppController : Controller private readonly ExplorerClientProvider _explorerClientProvider; public BtcPayAppController(BtcPayAppService appService, APIKeyRepository apiKeyRepository, - StoreRepository storeRepository, BTCPayNetworkProvider btcPayNetworkProvider, ExplorerClientProvider explorerClientProvider) + StoreRepository storeRepository, BTCPayNetworkProvider btcPayNetworkProvider, + ExplorerClientProvider explorerClientProvider) { _appService = appService; _apiKeyRepository = apiKeyRepository; @@ -106,28 +73,21 @@ public class BtcPayAppController : Controller Key = key.Id, StoreId = store.Id, UserId = res.UserId, - ExistingWallet = onchain?.AccountDerivation?.GetExtPubKeys()?.FirstOrDefault()?.ToString(onchain.Network.NBitcoinNetwork), + ExistingWallet = + onchain?.AccountDerivation?.GetExtPubKeys()?.FirstOrDefault() + ?.ToString(onchain.Network.NBitcoinNetwork), ExistingWalletSeed = onchainSeed, Network = _btcPayNetworkProvider.GetNetwork("BTC").NBitcoinNetwork.Name }); } - + private async Task GetSeed(ExplorerClient client, DerivationSchemeSettings derivation) { return derivation.IsHotWallet && - await client.GetMetadataAsync(derivation.AccountDerivation, WellknownMetadataKeys.Mnemonic) is { } seed && - !string.IsNullOrEmpty(seed) ? seed : null; - } - - - public class PairSuccessResult - { - public string Key { get; set; } - public string StoreId { get; set; } - public string UserId { get; set; } - - public string? ExistingWallet { get; set; } - public string? ExistingWalletSeed { get; set; } - public string Network { get; set; } + await client.GetMetadataAsync(derivation.AccountDerivation, WellknownMetadataKeys.Mnemonic) is + { } seed && + !string.IsNullOrEmpty(seed) + ? seed + : null; } } diff --git a/BTCPayServer/App/BtcPayAppService.cs b/BTCPayServer/App/BtcPayAppService.cs new file mode 100644 index 000000000..aaa4d63e0 --- /dev/null +++ b/BTCPayServer/App/BtcPayAppService.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Memory; + +namespace BTCPayServer.Controllers; + +public class BtcPayAppService +{ + private readonly IMemoryCache _memoryCache; + + public BtcPayAppService(IMemoryCache memoryCache) + { + _memoryCache = memoryCache; + } + + private string CacheKey(string k) => $"BtcPayAppService_{k}"; + + public async Task GeneratePairingCode(string storeId, string userId) + { + var code = Guid.NewGuid().ToString(); + _memoryCache.Set(CacheKey(code), new PairingRequest() {Key = code, StoreId = storeId, UserId = userId}, + TimeSpan.FromMinutes(5)); + return code; + } + + public PairingRequest? ConsumePairingCode(string code) + { + return _memoryCache.TryGetValue(CacheKey(code), out var pairingRequest) + ? (PairingRequest?)pairingRequest + : null; + } + + public class PairingRequest + { + public string Key { get; set; } + public string StoreId { get; set; } + public string UserId { get; set; } + } +} \ No newline at end of file diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 6fd1ad5ed..5a8b9d68a 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -99,6 +99,7 @@ + diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index 3ed003aa7..16abd4305 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -517,7 +517,6 @@ o.GetRequiredService>().ToDictionary(o => o.P }); } - services.AddSingleton(); return services; } From f4cf4a7e4a05eac36eee321ce624995ff77c2082 Mon Sep 17 00:00:00 2001 From: Kukks Date: Thu, 6 Jul 2023 15:07:28 +0200 Subject: [PATCH 06/64] wip --- .../IBTCPayAppServerClient.cs | 16 ++++++++++++++-- BTCPayServer/App/BTCPayAppHub.cs | 19 ++++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs b/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs index 84e6839a9..99a1e255c 100644 --- a/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs +++ b/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs @@ -1,7 +1,19 @@ -namespace BTCPayApp.CommonServer +using System.Threading; +using System.Threading.Tasks; + +namespace BTCPayApp.CommonServer { public interface IBTCPayAppServerClient { - + Task ClientMethod1(string user, string message); + Task ClientMethod2(); + Task ClientMethod3(string user, string message, CancellationToken cancellationToken); // ca } + + public interface IBTCPayAppServerHub + { + Task SendMessage(string user, string message); + Task SomeHubMethod(); + } + } diff --git a/BTCPayServer/App/BTCPayAppHub.cs b/BTCPayServer/App/BTCPayAppHub.cs index f3ffb6b8d..7462f34d8 100644 --- a/BTCPayServer/App/BTCPayAppHub.cs +++ b/BTCPayServer/App/BTCPayAppHub.cs @@ -1,8 +1,21 @@ -using BTCPayApp.CommonServer; +using System; +using System.Threading.Tasks; +using BTCPayApp.CommonServer; using Microsoft.AspNetCore.SignalR; namespace BTCPayServer.Controllers; -public class BTCPayAppHub : Hub +public class BTCPayAppHub : Hub, IBTCPayAppServerHub { -} \ No newline at end of file + public async Task SendMessage(string user, string message) + { + await Clients.All.ClientMethod1(user, message); + return $"[Success] Call SendMessage : {user}, {message}"; + } + + public async Task SomeHubMethod() + { + await Clients.Caller.ClientMethod2(); + } +} + From cdc0a7fb0954a3abd446fdecce9e120def289744 Mon Sep 17 00:00:00 2001 From: Kukks Date: Mon, 17 Jul 2023 12:30:00 +0200 Subject: [PATCH 07/64] fix btcpay app plugin loader --- BTCPayServer/App/BTCPayAppPlugin.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/BTCPayServer/App/BTCPayAppPlugin.cs b/BTCPayServer/App/BTCPayAppPlugin.cs index 0bde9745d..b8918f103 100644 --- a/BTCPayServer/App/BTCPayAppPlugin.cs +++ b/BTCPayServer/App/BTCPayAppPlugin.cs @@ -1,12 +1,17 @@ using System; using BTCPayServer.Abstractions.Models; +using BTCPayServer.Controllers; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; -namespace BTCPayServer.Controllers; +namespace BTCPayServer.App; public class BTCPayAppPlugin : BaseBTCPayServerPlugin { + public override string Identifier => "BTCPay.App"; + public override string Name => "BTCPay App"; + + public override void Execute(IApplicationBuilder applicationBuilder, IServiceProvider applicationBuilderApplicationServices) { @@ -17,4 +22,4 @@ public class BTCPayAppPlugin : BaseBTCPayServerPlugin { applicationBuilder.AddBTCPayApp(); } -} \ No newline at end of file +} From 71f9cb27b6586d350d19a1589e93b81733cc365d Mon Sep 17 00:00:00 2001 From: Kukks Date: Fri, 21 Jul 2023 15:35:13 +0200 Subject: [PATCH 08/64] wip --- BTCPayApp.CommonServer/PairSuccessResult.cs | 2 +- BTCPayServer/App/BtcPayAppController.cs | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/BTCPayApp.CommonServer/PairSuccessResult.cs b/BTCPayApp.CommonServer/PairSuccessResult.cs index 6073c404f..8318e20c3 100644 --- a/BTCPayApp.CommonServer/PairSuccessResult.cs +++ b/BTCPayApp.CommonServer/PairSuccessResult.cs @@ -3,7 +3,7 @@ public class PairSuccessResult { public string Key { get; set; } - public string StoreId { get; set; } + public string? StoreId { get; set; } public string UserId { get; set; } public string? ExistingWallet { get; set; } diff --git a/BTCPayServer/App/BtcPayAppController.cs b/BTCPayServer/App/BtcPayAppController.cs index 5e40a817b..5cbad784d 100644 --- a/BTCPayServer/App/BtcPayAppController.cs +++ b/BTCPayServer/App/BtcPayAppController.cs @@ -43,11 +43,16 @@ public class BtcPayAppController : Controller return Unauthorized(); } - var store = await _storeRepository.FindStore(res.StoreId, res.UserId); - if (store is null) + StoreData? store = null; + if (res.StoreId is not null) { - return NotFound(); + store = await _storeRepository.FindStore(res.StoreId, res.UserId); + if (store is null) + { + return NotFound(); + } } + var key = new APIKeyData() { @@ -60,7 +65,7 @@ public class BtcPayAppController : Controller await _apiKeyRepository.CreateKey(key); - var onchain = store.GetDerivationSchemeSettings(_btcPayNetworkProvider, "BTC"); + var onchain = store?.GetDerivationSchemeSettings(_btcPayNetworkProvider, "BTC"); string? onchainSeed = null; if (onchain is not null) { @@ -71,7 +76,7 @@ public class BtcPayAppController : Controller return Ok(new PairSuccessResult() { Key = key.Id, - StoreId = store.Id, + StoreId = store?.Id, UserId = res.UserId, ExistingWallet = onchain?.AccountDerivation?.GetExtPubKeys()?.FirstOrDefault() From dabf1d4159927d08489e942dd0531f240709c0cc Mon Sep 17 00:00:00 2001 From: Kukks Date: Thu, 17 Aug 2023 15:03:37 +0200 Subject: [PATCH 09/64] start adding notifications in hub for onchain related acitvity --- .../IBTCPayAppServerClient.cs | 18 ++- BTCPayServer/App/BTCPayAppExtensions.cs | 4 +- BTCPayServer/App/BTCPayAppHub.cs | 126 +++++++++++++++++- BTCPayServer/Events/NewBlockEvent.cs | 4 +- 4 files changed, 138 insertions(+), 14 deletions(-) diff --git a/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs b/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs index 99a1e255c..943bbc2a7 100644 --- a/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs +++ b/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs @@ -5,15 +5,23 @@ namespace BTCPayApp.CommonServer { public interface IBTCPayAppServerClient { - Task ClientMethod1(string user, string message); - Task ClientMethod2(); - Task ClientMethod3(string user, string message, CancellationToken cancellationToken); // ca + Task OnTransactionDetected(string txid); + void NewBlock(string block); } public interface IBTCPayAppServerHub { - Task SendMessage(string user, string message); - Task SomeHubMethod(); + Task Handshake(AppHandshake handshake); + } + public class AppHandshake + { + public string DerivationScheme { get; set; } + } + + + + + } diff --git a/BTCPayServer/App/BTCPayAppExtensions.cs b/BTCPayServer/App/BTCPayAppExtensions.cs index 4f8ea4380..fe5566911 100644 --- a/BTCPayServer/App/BTCPayAppExtensions.cs +++ b/BTCPayServer/App/BTCPayAppExtensions.cs @@ -8,6 +8,8 @@ public static class BTCPayAppExtensions public static IServiceCollection AddBTCPayApp(this IServiceCollection serviceCollection) { serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddHostedService(serviceProvider => serviceProvider.GetRequiredService()); return serviceCollection; } @@ -18,4 +20,4 @@ public static class BTCPayAppExtensions routeBuilder.MapHub("hub/btcpayapp"); }); } -} \ No newline at end of file +} diff --git a/BTCPayServer/App/BTCPayAppHub.cs b/BTCPayServer/App/BTCPayAppHub.cs index 7462f34d8..688300059 100644 --- a/BTCPayServer/App/BTCPayAppHub.cs +++ b/BTCPayServer/App/BTCPayAppHub.cs @@ -1,21 +1,135 @@ using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading; using System.Threading.Tasks; using BTCPayApp.CommonServer; +using BTCPayServer.Abstractions.Constants; +using BTCPayServer.Data; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using NBitcoin; +using NBXplorer; +using NBXplorer.Models; +using NewBlockEvent = BTCPayServer.Events.NewBlockEvent; namespace BTCPayServer.Controllers; -public class BTCPayAppHub : Hub, IBTCPayAppServerHub +public class BTCPayAppState : IHostedService { - public async Task SendMessage(string user, string message) + private readonly IHubContext _hubContext; + private readonly ILogger _logger; + private readonly ExplorerClientProvider _explorerClientProvider; + private readonly BTCPayNetworkProvider _networkProvider; + private readonly EventAggregator _eventAggregator; + private CompositeDisposable? _compositeDisposable; + private ExplorerClient _explorerClient; + private DerivationSchemeParser _derivationSchemeParser; + private readonly ConcurrentDictionary _connectionScheme = new(); + + public BTCPayAppState( + IHubContext hubContext, + ILogger logger, + ExplorerClientProvider explorerClientProvider, + BTCPayNetworkProvider networkProvider, + EventAggregator eventAggregator) { - await Clients.All.ClientMethod1(user, message); - return $"[Success] Call SendMessage : {user}, {message}"; + _hubContext = hubContext; + _logger = logger; + _explorerClientProvider = explorerClientProvider; + _networkProvider = networkProvider; + _eventAggregator = eventAggregator; } - public async Task SomeHubMethod() + public Task StartAsync(CancellationToken cancellationToken) { - await Clients.Caller.ClientMethod2(); + _explorerClient = _explorerClientProvider.GetExplorerClient("BTC"); + _derivationSchemeParser = new DerivationSchemeParser(_networkProvider.BTC); + _compositeDisposable = new(); + _compositeDisposable.Add( + _eventAggregator.Subscribe(OnNewBlock)); + _compositeDisposable.Add( + _eventAggregator.Subscribe(OnNewTransaction)); + return Task.CompletedTask; + } + + private void OnNewTransaction(NewTransactionEvent obj) + { + if (obj.CryptoCode != "BTC") + return; + + _connectionScheme.Where(pair => pair.Value == obj.TrackedSource) + .Select(pair => pair.Key) + .ToList() + .ForEach(connectionId => + { + _hubContext.Clients.Client(connectionId) + .OnTransactionDetected(obj.TransactionData.TransactionHash.ToString()); + }); + } + + private void OnNewBlock(NewBlockEvent obj) + { + if (obj.CryptoCode != "BTC") + return; + _hubContext.Clients.All.NewBlock(obj.Hash.ToString()); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _compositeDisposable?.Dispose(); + return Task.CompletedTask; + } + + public async Task Handshake(string contextConnectionId, AppHandshake handshake) + { + try + { + var ts = + TrackedSource.Create(_derivationSchemeParser.Parse(handshake.DerivationScheme)); + await _explorerClient.TrackAsync(ts); + _connectionScheme.AddOrReplace(contextConnectionId, ts); + } + catch (Exception e) + { + _logger.LogError(e, "Error during handshake"); + throw; + } + } + + public void RemoveConnection(string contextConnectionId) + { + _connectionScheme.TryRemove(contextConnectionId, out _); } } +[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)] +public class BTCPayAppHub : Hub, IBTCPayAppServerHub +{ + private readonly BTCPayAppState _appState; + + + public BTCPayAppHub(BTCPayAppState appState) + { + _appState = appState; + } + + + public override async Task OnConnectedAsync() + { + } + + public override Task OnDisconnectedAsync(Exception exception) + { + _appState.RemoveConnection(Context.ConnectionId); + return base.OnDisconnectedAsync(exception); + } + + public Task Handshake(AppHandshake handshake) + { + return _appState.Handshake(Context.ConnectionId, handshake); + } +} diff --git a/BTCPayServer/Events/NewBlockEvent.cs b/BTCPayServer/Events/NewBlockEvent.cs index 5faef5d6c..32d07d8a6 100644 --- a/BTCPayServer/Events/NewBlockEvent.cs +++ b/BTCPayServer/Events/NewBlockEvent.cs @@ -1,11 +1,11 @@ namespace BTCPayServer.Events { - public class NewBlockEvent + public class NewBlockEvent:NBXplorer.Models.NewBlockEvent { - public string CryptoCode { get; set; } public override string ToString() { return $"{CryptoCode}: New block"; } } } + From 029176658839f7c3d42cd03dc87616982ccc893d Mon Sep 17 00:00:00 2001 From: Kukks Date: Fri, 18 Aug 2023 13:40:07 +0200 Subject: [PATCH 10/64] fix hub contract --- BTCPayApp.CommonServer/IBTCPayAppServerClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs b/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs index 943bbc2a7..ffcff3e3b 100644 --- a/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs +++ b/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs @@ -6,7 +6,7 @@ namespace BTCPayApp.CommonServer public interface IBTCPayAppServerClient { Task OnTransactionDetected(string txid); - void NewBlock(string block); + Task NewBlock(string block); } public interface IBTCPayAppServerHub From bdb213dca6f733afce379865acb4dbd0507e5494 Mon Sep 17 00:00:00 2001 From: Kukks Date: Mon, 21 Aug 2023 14:48:16 +0200 Subject: [PATCH 11/64] fix event data from block --- .../IBTCPayAppServerClient.cs | 4 +- BTCPayServer/App/BTCPayAppHub.cs | 39 +++++++++++++++---- .../Payments/Bitcoin/NBXplorerListener.cs | 11 +++++- 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs b/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs index ffcff3e3b..da9722597 100644 --- a/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs +++ b/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs @@ -5,19 +5,21 @@ namespace BTCPayApp.CommonServer { public interface IBTCPayAppServerClient { - Task OnTransactionDetected(string txid); + Task TransactionDetected(string txid); Task NewBlock(string block); } public interface IBTCPayAppServerHub { Task Handshake(AppHandshake handshake); + Task GetTransactions(); } public class AppHandshake { public string DerivationScheme { get; set; } + } diff --git a/BTCPayServer/App/BTCPayAppHub.cs b/BTCPayServer/App/BTCPayAppHub.cs index 688300059..1f430b787 100644 --- a/BTCPayServer/App/BTCPayAppHub.cs +++ b/BTCPayServer/App/BTCPayAppHub.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Collections.Concurrent; using System.Linq; using System.Threading; @@ -6,6 +7,7 @@ using System.Threading.Tasks; using BTCPayApp.CommonServer; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Data; +using BTCPayServer.Services.Wallets; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.SignalR; @@ -26,7 +28,7 @@ public class BTCPayAppState : IHostedService private readonly BTCPayNetworkProvider _networkProvider; private readonly EventAggregator _eventAggregator; private CompositeDisposable? _compositeDisposable; - private ExplorerClient _explorerClient; + public ExplorerClient ExplorerClient { get; private set; } private DerivationSchemeParser _derivationSchemeParser; private readonly ConcurrentDictionary _connectionScheme = new(); @@ -46,7 +48,7 @@ public class BTCPayAppState : IHostedService public Task StartAsync(CancellationToken cancellationToken) { - _explorerClient = _explorerClientProvider.GetExplorerClient("BTC"); + ExplorerClient = _explorerClientProvider.GetExplorerClient("BTC"); _derivationSchemeParser = new DerivationSchemeParser(_networkProvider.BTC); _compositeDisposable = new(); _compositeDisposable.Add( @@ -67,7 +69,7 @@ public class BTCPayAppState : IHostedService .ForEach(connectionId => { _hubContext.Clients.Client(connectionId) - .OnTransactionDetected(obj.TransactionData.TransactionHash.ToString()); + .TransactionDetected(obj.TransactionData.TransactionHash.ToString()); }); } @@ -84,13 +86,19 @@ public class BTCPayAppState : IHostedService return Task.CompletedTask; } + public TrackedSource? GetConnectionState(string connectionId) + { + _connectionScheme.TryGetValue(connectionId, out var res); + return res; + } + public async Task Handshake(string contextConnectionId, AppHandshake handshake) { try { var ts = TrackedSource.Create(_derivationSchemeParser.Parse(handshake.DerivationScheme)); - await _explorerClient.TrackAsync(ts); + await ExplorerClient.TrackAsync(ts); _connectionScheme.AddOrReplace(contextConnectionId, ts); } catch (Exception e) @@ -110,11 +118,13 @@ public class BTCPayAppState : IHostedService public class BTCPayAppHub : Hub, IBTCPayAppServerHub { private readonly BTCPayAppState _appState; + private readonly BTCPayWallet _wallet; - public BTCPayAppHub(BTCPayAppState appState) + public BTCPayAppHub(BTCPayAppState appState, BTCPayWalletProvider walletProvider) { _appState = appState; + _wallet = walletProvider.GetWallet("BTC"); } @@ -122,7 +132,7 @@ public class BTCPayAppHub : Hub, IBTCPayAppServerHub { } - public override Task OnDisconnectedAsync(Exception exception) + public override Task OnDisconnectedAsync(Exception? exception) { _appState.RemoveConnection(Context.ConnectionId); return base.OnDisconnectedAsync(exception); @@ -132,4 +142,19 @@ public class BTCPayAppHub : Hub, IBTCPayAppServerHub { return _appState.Handshake(Context.ConnectionId, handshake); } + + public async Task GetTransactions() + { + var deriv = _appState.GetConnectionState(Context.ConnectionId); + if(deriv is null) + throw new InvalidOperationException("Handshake not done"); + var txs = await _appState.ExplorerClient.GetTransactionsAsync(deriv); + if (txs is null) + { + throw new InvalidOperationException("NBXplorer failed to get transactions"); + } + + + + } } diff --git a/BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs b/BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs index d2d58bfef..8b2160799 100644 --- a/BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs +++ b/BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs @@ -151,7 +151,16 @@ namespace BTCPayServer.Payments.Bitcoin { case NBXplorer.Models.NewBlockEvent evt: await UpdatePaymentStates(wallet); - _Aggregator.Publish(new Events.NewBlockEvent() { CryptoCode = evt.CryptoCode }); + _Aggregator.Publish(new Events.NewBlockEvent() + { + CryptoCode = wallet.Network.CryptoCode, + Confirmations = evt.Confirmations, + Hash = evt.Hash, + Height = evt.Height, + EventId = evt.EventId, + PreviousBlockHash = evt.PreviousBlockHash + } + ); break; case NBXplorer.Models.NewTransactionEvent evt: if (evt.DerivationStrategy != null) From c881c3b7f20a1e3ed74d43c13dabadc1734b39d8 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Tue, 20 Feb 2024 11:47:03 +0100 Subject: [PATCH 12/64] Add Bearer authentication --- .../Constants/AuthenticationSchemes.cs | 1 + BTCPayServer/App/AppUserInfoResponse.cs | 20 ++ BTCPayServer/App/BtcPayAppController.cs | 200 ++++++++++++++++-- BTCPayServer/Hosting/BTCPayServerServices.cs | 1 + .../Security/AuthenticationExtensions.cs | 24 +++ 5 files changed, 223 insertions(+), 23 deletions(-) create mode 100644 BTCPayServer/App/AppUserInfoResponse.cs diff --git a/BTCPayServer.Abstractions/Constants/AuthenticationSchemes.cs b/BTCPayServer.Abstractions/Constants/AuthenticationSchemes.cs index 007acc6a8..b07f95d09 100644 --- a/BTCPayServer.Abstractions/Constants/AuthenticationSchemes.cs +++ b/BTCPayServer.Abstractions/Constants/AuthenticationSchemes.cs @@ -3,6 +3,7 @@ namespace BTCPayServer.Abstractions.Constants public class AuthenticationSchemes { public const string Cookie = "Identity.Application"; + public const string Bearer = "Identity.Bearer"; public const string Bitpay = "Bitpay"; public const string Greenfield = "Greenfield.APIKeys,Greenfield.Basic"; public const string GreenfieldAPIKeys = "Greenfield.APIKeys"; diff --git a/BTCPayServer/App/AppUserInfoResponse.cs b/BTCPayServer/App/AppUserInfoResponse.cs new file mode 100644 index 000000000..8bd4f6551 --- /dev/null +++ b/BTCPayServer/App/AppUserInfoResponse.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace BTCPayServer.App; + +public class AppUserInfoResponse +{ + public string UserId { get; set; } + public string Email { get; set; } + public IEnumerable Roles { get; set; } + public IEnumerable Stores { get; set; } +} + +public class AppUserStoreInfo +{ + public string Id { get; set; } + public string Name { get; set; } + public string RoleId { get; set; } + public bool Archived { get; set; } + public IEnumerable Permissions { get; set; } +} diff --git a/BTCPayServer/App/BtcPayAppController.cs b/BTCPayServer/App/BtcPayAppController.cs index 5cbad784d..a6a68004b 100644 --- a/BTCPayServer/App/BtcPayAppController.cs +++ b/BTCPayServer/App/BtcPayAppController.cs @@ -1,43 +1,197 @@ #nullable enable +using System; +using System.Collections.Generic; using System.Linq; +using System.Text; using System.Threading.Tasks; using BTCPayApp.CommonServer; +using BTCPayServer.Abstractions.Constants; +using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Client; +using BTCPayServer.Common; +using BTCPayServer.Controllers; using BTCPayServer.Data; -using BTCPayServer.Plugins.Shopify; +using BTCPayServer.Events; +using BTCPayServer.Models.AccountViewModels; using BTCPayServer.Security.Greenfield; +using BTCPayServer.Services; using BTCPayServer.Services.Stores; +using Microsoft.AspNetCore.Authentication.BearerToken; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.Data; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Options; using NBitcoin; using NBitcoin.DataEncoders; using NBXplorer; +using NicolasDorier.RateLimits; -namespace BTCPayServer.Controllers; +namespace BTCPayServer.App; +[ApiController] +[Authorize(AuthenticationSchemes = AuthenticationSchemes.Bearer)] [Route("btcpayapp")] -public class BtcPayAppController : Controller +public class BtcPayAppController( + BtcPayAppService appService, + APIKeyRepository apiKeyRepository, + StoreRepository storeRepository, + BTCPayNetworkProvider btcPayNetworkProvider, + IExplorerClientProvider explorerClientProvider, + EventAggregator eventAggregator, + SignInManager signInManager, + UserManager userManager, + TimeProvider timeProvider, + IOptionsMonitor bearerTokenOptions) + : Controller { - private readonly BtcPayAppService _appService; - private readonly APIKeyRepository _apiKeyRepository; - private readonly StoreRepository _storeRepository; - private readonly BTCPayNetworkProvider _btcPayNetworkProvider; - private readonly ExplorerClientProvider _explorerClientProvider; - - public BtcPayAppController(BtcPayAppService appService, APIKeyRepository apiKeyRepository, - StoreRepository storeRepository, BTCPayNetworkProvider btcPayNetworkProvider, - ExplorerClientProvider explorerClientProvider) + [AllowAnonymous] + [HttpPost("login")] + [RateLimitsFilter(ZoneLimits.Login, Scope = RateLimitsScope.RemoteAddress)] + public async Task, EmptyHttpResult, ProblemHttpResult>> Login(LoginRequest login) { - _appService = appService; - _apiKeyRepository = apiKeyRepository; - _storeRepository = storeRepository; - _btcPayNetworkProvider = btcPayNetworkProvider; - _explorerClientProvider = explorerClientProvider; + const string errorMessage = "Invalid login attempt."; + if (ModelState.IsValid) + { + // Require the user to pass basic checks (approval, confirmed email, not disabled) before they can log on + var user = await userManager.FindByEmailAsync(login.Email); + if (!UserService.TryCanLogin(user, out var message)) + { + return TypedResults.Problem(message, statusCode: 401); + } + + signInManager.AuthenticationScheme = AuthenticationSchemes.Bearer; + var signInResult = await signInManager.PasswordSignInAsync(login.Email, login.Password, true, true); + if (signInResult.RequiresTwoFactor) + { + if (!string.IsNullOrEmpty(login.TwoFactorCode)) + signInResult = await signInManager.TwoFactorAuthenticatorSignInAsync(login.TwoFactorCode, true, true); + else if (!string.IsNullOrEmpty(login.TwoFactorRecoveryCode)) + signInResult = await signInManager.TwoFactorRecoveryCodeSignInAsync(login.TwoFactorRecoveryCode); + } + + // TODO: Add FIDO and LNURL Auth + + return signInResult.Succeeded + ? TypedResults.Empty + : TypedResults.Problem(signInResult.ToString(), statusCode: 401); + } + return TypedResults.Problem(errorMessage, statusCode: 401); + } + + [AllowAnonymous] + [HttpPost("refresh")] + [RateLimitsFilter(ZoneLimits.Login, Scope = RateLimitsScope.RemoteAddress)] + public async Task, UnauthorizedHttpResult, SignInHttpResult, ChallengeHttpResult>> Refresh(RefreshRequest refresh) + { + const string scheme = AuthenticationSchemes.Bearer; + var authenticationTicket = bearerTokenOptions.Get(scheme).RefreshTokenProtector.Unprotect(refresh.RefreshToken); + var expiresUtc = authenticationTicket?.Properties.ExpiresUtc; + + ApplicationUser? user = null; + int num; + if (expiresUtc.HasValue) + { + DateTimeOffset valueOrDefault = expiresUtc.GetValueOrDefault(); + num = timeProvider.GetUtcNow() >= valueOrDefault ? 1 : 0; + } + else + num = 1; + bool flag = num != 0; + if (!flag) + { + signInManager.AuthenticationScheme = scheme; + user = await signInManager.ValidateSecurityStampAsync(authenticationTicket?.Principal); + } + + return user != null + ? TypedResults.SignIn(await signInManager.CreateUserPrincipalAsync(user), authenticationScheme: scheme) + : TypedResults.Challenge(authenticationSchemes: new[] { scheme }); + } + + [HttpPost("logout")] + public async Task Logout() + { + var user = await userManager.GetUserAsync(User); + if (user != null) + { + await signInManager.SignOutAsync(); + return Results.Ok(); + } + return Results.Unauthorized(); + } + + [HttpGet("info")] + public async Task, ValidationProblem, NotFound>> Info() + { + var user = await userManager.GetUserAsync(User); + if (user == null) return TypedResults.NotFound(); + + var userStores = await storeRepository.GetStoresByUserId(user.Id); + return TypedResults.Ok(new AppUserInfoResponse + { + UserId = user.Id, + Email = await userManager.GetEmailAsync(user), + Roles = await userManager.GetRolesAsync(user), + Stores = (from store in userStores + let userStore = store.UserStores.Find(us => us.ApplicationUserId == user.Id && us.StoreDataId == store.Id)! + select new AppUserStoreInfo + { + Id = store.Id, + Name = store.StoreName, + Archived = store.Archived, + RoleId = userStore.StoreRole.Id, + Permissions = userStore.StoreRole.Permissions + }).ToList() + }); + } + + [AllowAnonymous] + [HttpPost("forgot-password")] + [RateLimitsFilter(ZoneLimits.ForgotPassword, Scope = RateLimitsScope.RemoteAddress)] + public async Task ForgotPassword(ResetPasswordRequest resetRequest) + { + var user = await userManager.FindByEmailAsync(resetRequest.Email); + if (UserService.TryCanLogin(user, out _)) + { + eventAggregator.Publish(new UserPasswordResetRequestedEvent + { + User = user, + RequestUri = Request.GetAbsoluteRootUri() + }); + } + return TypedResults.Ok(); + } + + [AllowAnonymous] + [HttpPost("reset-password")] + public async Task SetPassword(ResetPasswordRequest resetRequest) + { + var user = await userManager.FindByEmailAsync(resetRequest.Email); + if (!UserService.TryCanLogin(user, out _)) + { + return TypedResults.Problem("Invalid account", statusCode: 401); + } + + IdentityResult result; + try + { + result = await userManager.ResetPasswordAsync(user, resetRequest.ResetCode, resetRequest.NewPassword); + } + catch (FormatException) + { + result = IdentityResult.Failed(userManager.ErrorDescriber.InvalidToken()); + } + return result.Succeeded ? TypedResults.Ok() : TypedResults.Problem(result.ToString().Split(": ").Last(), statusCode: 401); } [HttpGet("pair/{code}")] public async Task StartPair(string code) { - var res = _appService.ConsumePairingCode(code); + var res = appService.ConsumePairingCode(code); if (res is null) { return Unauthorized(); @@ -46,7 +200,7 @@ public class BtcPayAppController : Controller StoreData? store = null; if (res.StoreId is not null) { - store = await _storeRepository.FindStore(res.StoreId, res.UserId); + store = await storeRepository.FindStore(res.StoreId, res.UserId); if (store is null) { return NotFound(); @@ -62,14 +216,14 @@ public class BtcPayAppController : Controller Label = "BTCPay App Pairing" }; key.SetBlob(new APIKeyBlob() {Permissions = new[] {Policies.Unrestricted}}); - await _apiKeyRepository.CreateKey(key); + await apiKeyRepository.CreateKey(key); - var onchain = store?.GetDerivationSchemeSettings(_btcPayNetworkProvider, "BTC"); + var onchain = store?.GetDerivationSchemeSettings(btcPayNetworkProvider, "BTC"); string? onchainSeed = null; if (onchain is not null) { - var explorerClient = _explorerClientProvider.GetExplorerClient("BTC"); + var explorerClient = explorerClientProvider.GetExplorerClient("BTC"); onchainSeed = await GetSeed(explorerClient, onchain); } @@ -82,7 +236,7 @@ public class BtcPayAppController : Controller onchain?.AccountDerivation?.GetExtPubKeys()?.FirstOrDefault() ?.ToString(onchain.Network.NBitcoinNetwork), ExistingWalletSeed = onchainSeed, - Network = _btcPayNetworkProvider.GetNetwork("BTC").NBitcoinNetwork.Name + Network = btcPayNetworkProvider.GetNetwork("BTC").NBitcoinNetwork.Name }); } diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index 16abd4305..4475e7be4 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -703,6 +703,7 @@ o.GetRequiredService>().ToDictionary(o => o.P private static void AddBtcPayServerAuthenticationSchemes(this IServiceCollection services) { services.AddAuthentication() + .AddBearerAuthentication() .AddBitpayAuthentication() .AddAPIKeyAuthentication(); } diff --git a/BTCPayServer/Security/AuthenticationExtensions.cs b/BTCPayServer/Security/AuthenticationExtensions.cs index 0fcfc6de7..881f3e4b7 100644 --- a/BTCPayServer/Security/AuthenticationExtensions.cs +++ b/BTCPayServer/Security/AuthenticationExtensions.cs @@ -1,6 +1,8 @@ +using System; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Security.Bitpay; using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.DependencyInjection; namespace BTCPayServer.Security { @@ -11,5 +13,27 @@ namespace BTCPayServer.Security builder.AddScheme(AuthenticationSchemes.Bitpay, o => { }); return builder; } + + public static AuthenticationBuilder AddBearerAuthentication(this AuthenticationBuilder builder) + { + builder.AddBearerToken(AuthenticationSchemes.Bearer, options => + { + options.BearerTokenExpiration = TimeSpan.FromMinutes(30.0); + options.RefreshTokenExpiration = TimeSpan.FromDays(3.0); + // Customize token handling, https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authentication/BearerToken/src/BearerTokenHandler.cs + /*options.Events.OnMessageReceived = context => + { + /*context.Fail("Failure Message");#1# + const string start = "Bearer "; + var auth = context.Request.Headers.Authorization.ToString(); + if (auth.StartsWith(start, StringComparison.InvariantCultureIgnoreCase)) + { + context.Token = auth[start.Length..]; + } + return Task.CompletedTask; + };*/ + }); + return builder; + } } } From e9f785e2b0b78326a3b9986b34d101c920d5baf7 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Thu, 4 Apr 2024 11:24:56 +0200 Subject: [PATCH 13/64] Update after update --- BTCPayServer/App/BtcPayAppController.cs | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/BTCPayServer/App/BtcPayAppController.cs b/BTCPayServer/App/BtcPayAppController.cs index a6a68004b..898e852b3 100644 --- a/BTCPayServer/App/BtcPayAppController.cs +++ b/BTCPayServer/App/BtcPayAppController.cs @@ -1,8 +1,6 @@ #nullable enable using System; -using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; using BTCPayApp.CommonServer; using BTCPayServer.Abstractions.Constants; @@ -12,9 +10,9 @@ using BTCPayServer.Common; using BTCPayServer.Controllers; using BTCPayServer.Data; using BTCPayServer.Events; -using BTCPayServer.Models.AccountViewModels; using BTCPayServer.Security.Greenfield; using BTCPayServer.Services; +using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Authentication.BearerToken; using Microsoft.AspNetCore.Authorization; @@ -23,7 +21,6 @@ using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.Data; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Options; using NBitcoin; using NBitcoin.DataEncoders; @@ -45,6 +42,7 @@ public class BtcPayAppController( SignInManager signInManager, UserManager userManager, TimeProvider timeProvider, + PaymentMethodHandlerDictionary handlers, IOptionsMonitor bearerTokenOptions) : Controller { @@ -207,19 +205,17 @@ public class BtcPayAppController( } } - - var key = new APIKeyData() + var key = new APIKeyData { Id = Encoders.Hex.EncodeData(RandomUtils.GetBytes(20)), Type = APIKeyType.Permanent, UserId = res.UserId, Label = "BTCPay App Pairing" }; - key.SetBlob(new APIKeyBlob() {Permissions = new[] {Policies.Unrestricted}}); + key.SetBlob(new APIKeyBlob {Permissions = [Policies.Unrestricted] }); await apiKeyRepository.CreateKey(key); - - - var onchain = store?.GetDerivationSchemeSettings(btcPayNetworkProvider, "BTC"); + + var onchain = store?.GetDerivationSchemeSettings(handlers, "BTC"); string? onchainSeed = null; if (onchain is not null) { @@ -227,16 +223,15 @@ public class BtcPayAppController( onchainSeed = await GetSeed(explorerClient, onchain); } - return Ok(new PairSuccessResult() + var nBitcoinNetwork = btcPayNetworkProvider.GetNetwork("BTC").NBitcoinNetwork; + return Ok(new PairSuccessResult { Key = key.Id, StoreId = store?.Id, UserId = res.UserId, - ExistingWallet = - onchain?.AccountDerivation?.GetExtPubKeys()?.FirstOrDefault() - ?.ToString(onchain.Network.NBitcoinNetwork), + ExistingWallet = onchain?.AccountDerivation?.GetExtPubKeys()?.FirstOrDefault()?.ToString(nBitcoinNetwork), ExistingWalletSeed = onchainSeed, - Network = btcPayNetworkProvider.GetNetwork("BTC").NBitcoinNetwork.Name + Network = nBitcoinNetwork.Name }); } From d5faa2c8b7497f5cdb836487bbb9c3247f4f9efd Mon Sep 17 00:00:00 2001 From: Kukks Date: Mon, 8 Apr 2024 15:49:34 +0200 Subject: [PATCH 14/64] add common classes --- BTCPayApp.CommonServer/AccessTokenResult.cs | 10 ++++++++++ .../AppUserInfoResponse.cs | 1 - BTCPayApp.CommonServer/BTCPayApp.CommonServer.csproj | 4 ++-- 3 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 BTCPayApp.CommonServer/AccessTokenResult.cs rename {BTCPayServer/App => BTCPayApp.CommonServer}/AppUserInfoResponse.cs (94%) diff --git a/BTCPayApp.CommonServer/AccessTokenResult.cs b/BTCPayApp.CommonServer/AccessTokenResult.cs new file mode 100644 index 000000000..d54a170aa --- /dev/null +++ b/BTCPayApp.CommonServer/AccessTokenResult.cs @@ -0,0 +1,10 @@ +using System; + +namespace BTCPayApp.CommonServer; + +public class AccessTokenResult(string accessToken, string refreshToken, DateTimeOffset expiry) +{ + public string AccessToken { get; init; } = accessToken; + public string RefreshToken { get; init; } = refreshToken; + public DateTimeOffset Expiry { get; init; } = expiry; +} diff --git a/BTCPayServer/App/AppUserInfoResponse.cs b/BTCPayApp.CommonServer/AppUserInfoResponse.cs similarity index 94% rename from BTCPayServer/App/AppUserInfoResponse.cs rename to BTCPayApp.CommonServer/AppUserInfoResponse.cs index 8bd4f6551..dd239a613 100644 --- a/BTCPayServer/App/AppUserInfoResponse.cs +++ b/BTCPayApp.CommonServer/AppUserInfoResponse.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; -namespace BTCPayServer.App; public class AppUserInfoResponse { diff --git a/BTCPayApp.CommonServer/BTCPayApp.CommonServer.csproj b/BTCPayApp.CommonServer/BTCPayApp.CommonServer.csproj index 81975abc7..f897edefd 100644 --- a/BTCPayApp.CommonServer/BTCPayApp.CommonServer.csproj +++ b/BTCPayApp.CommonServer/BTCPayApp.CommonServer.csproj @@ -1,8 +1,8 @@ - netstandard2.0 - 8 + net8.0 + latest enable From cf47e2a1b38d49e01b38fd8a45415673b5046d28 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Tue, 9 Apr 2024 20:45:21 +0200 Subject: [PATCH 15/64] Refactor AppUserInfo --- .../{AppUserInfoResponse.cs => AppUserInfo.cs} | 3 ++- BTCPayServer/App/BtcPayAppController.cs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) rename BTCPayApp.CommonServer/{AppUserInfoResponse.cs => AppUserInfo.cs} (88%) diff --git a/BTCPayApp.CommonServer/AppUserInfoResponse.cs b/BTCPayApp.CommonServer/AppUserInfo.cs similarity index 88% rename from BTCPayApp.CommonServer/AppUserInfoResponse.cs rename to BTCPayApp.CommonServer/AppUserInfo.cs index dd239a613..625aab437 100644 --- a/BTCPayApp.CommonServer/AppUserInfoResponse.cs +++ b/BTCPayApp.CommonServer/AppUserInfo.cs @@ -1,7 +1,8 @@ using System.Collections.Generic; +namespace BTCPayApp.CommonServer; -public class AppUserInfoResponse +public class AppUserInfo { public string UserId { get; set; } public string Email { get; set; } diff --git a/BTCPayServer/App/BtcPayAppController.cs b/BTCPayServer/App/BtcPayAppController.cs index 898e852b3..1a70e7f92 100644 --- a/BTCPayServer/App/BtcPayAppController.cs +++ b/BTCPayServer/App/BtcPayAppController.cs @@ -123,13 +123,13 @@ public class BtcPayAppController( } [HttpGet("info")] - public async Task, ValidationProblem, NotFound>> Info() + public async Task, ValidationProblem, NotFound>> Info() { var user = await userManager.GetUserAsync(User); if (user == null) return TypedResults.NotFound(); var userStores = await storeRepository.GetStoresByUserId(user.Id); - return TypedResults.Ok(new AppUserInfoResponse + return TypedResults.Ok(new AppUserInfo { UserId = user.Id, Email = await userManager.GetEmailAsync(user), From 696d6a823dbdc9b4ac7b6a574785f6f3a73439d3 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Thu, 11 Apr 2024 11:06:08 +0200 Subject: [PATCH 16/64] Support registration --- BTCPayApp.CommonServer/AppInstanceInfo.cs | 12 ++++ BTCPayApp.CommonServer/SignupRequest.cs | 12 ++++ BTCPayApp.CommonServer/SignupResult.cs | 8 +++ BTCPayServer/App/BtcPayAppController.cs | 73 ++++++++++++++++++++++- BTCPayServer/Services/ServerSettings.cs | 4 +- 5 files changed, 104 insertions(+), 5 deletions(-) create mode 100644 BTCPayApp.CommonServer/AppInstanceInfo.cs create mode 100644 BTCPayApp.CommonServer/SignupRequest.cs create mode 100644 BTCPayApp.CommonServer/SignupResult.cs diff --git a/BTCPayApp.CommonServer/AppInstanceInfo.cs b/BTCPayApp.CommonServer/AppInstanceInfo.cs new file mode 100644 index 000000000..c739c914b --- /dev/null +++ b/BTCPayApp.CommonServer/AppInstanceInfo.cs @@ -0,0 +1,12 @@ +namespace BTCPayApp.CommonServer; + +public class AppInstanceInfo +{ + public string BaseUrl { get; set; } + public string ServerName { get; set; } = "BTCPay Server"; + public string? ContactUrl { get; set; } + public string? LogoUrl { get; set; } + public string? CustomThemeCssUrl { get; set; } + public string? CustomThemeExtension { get; set; } + public bool RegistrationEnabled { get; set; } +} diff --git a/BTCPayApp.CommonServer/SignupRequest.cs b/BTCPayApp.CommonServer/SignupRequest.cs new file mode 100644 index 000000000..fe9e189cb --- /dev/null +++ b/BTCPayApp.CommonServer/SignupRequest.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace BTCPayApp.CommonServer; + +public class SignupRequest +{ + [Required] + public string Email { get; init; } + + [Required] + public string Password { get; init; } +} diff --git a/BTCPayApp.CommonServer/SignupResult.cs b/BTCPayApp.CommonServer/SignupResult.cs new file mode 100644 index 000000000..ab0ad80c5 --- /dev/null +++ b/BTCPayApp.CommonServer/SignupResult.cs @@ -0,0 +1,8 @@ +namespace BTCPayApp.CommonServer; + +public class SignupResult +{ + public string Email { get; set; } + public bool RequiresConfirmedEmail { get; set; } + public bool RequiresUserApproval { get; set; } +} diff --git a/BTCPayServer/App/BtcPayAppController.cs b/BTCPayServer/App/BtcPayAppController.cs index 1a70e7f92..04d12301b 100644 --- a/BTCPayServer/App/BtcPayAppController.cs +++ b/BTCPayServer/App/BtcPayAppController.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using BTCPayApp.CommonServer; using BTCPayServer.Abstractions.Constants; +using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Client; using BTCPayServer.Common; @@ -43,9 +44,77 @@ public class BtcPayAppController( UserManager userManager, TimeProvider timeProvider, PaymentMethodHandlerDictionary handlers, + IFileService fileService, + ISettingsRepository settingsRepository, IOptionsMonitor bearerTokenOptions) : Controller { + [AllowAnonymous] + [HttpGet("instance")] + public async Task, NotFound>> Instance() + { + var serverSettings = await settingsRepository.GetSettingAsync() ?? new ServerSettings(); + var policiesSettings = await settingsRepository.GetSettingAsync() ?? new PoliciesSettings(); + var themeSettings = await settingsRepository.GetSettingAsync() ?? new ThemeSettings(); + + return TypedResults.Ok(new AppInstanceInfo + { + BaseUrl = Request.GetAbsoluteRoot(), + ServerName = serverSettings.ServerName, + ContactUrl = serverSettings.ContactUrl, + RegistrationEnabled = policiesSettings.EnableRegistration, + CustomThemeExtension = themeSettings.CustomTheme ? themeSettings.CustomThemeExtension.ToString() : null, + CustomThemeCssUrl = themeSettings.CustomTheme && !string.IsNullOrEmpty(themeSettings.CustomThemeFileId) + ? await fileService.GetFileUrl(Request.GetAbsoluteRootUri(), themeSettings.CustomThemeFileId) + : null, + LogoUrl = !string.IsNullOrEmpty(themeSettings.LogoFileId) + ? await fileService.GetFileUrl(Request.GetAbsoluteRootUri(), themeSettings.LogoFileId) + : null + }); + } + + [AllowAnonymous] + [HttpPost("register")] + [RateLimitsFilter(ZoneLimits.Login, Scope = RateLimitsScope.RemoteAddress)] + public async Task, ValidationProblem, ProblemHttpResult>> Register(SignupRequest signup) + { + var policiesSettings = await settingsRepository.GetSettingAsync() ?? new PoliciesSettings(); + if (policiesSettings.LockSubscription) + return TypedResults.Problem("This instance does not allow public user registration", statusCode: 406); + + var errorMessage = "Invalid signup attempt."; + if (ModelState.IsValid) + { + var user = new ApplicationUser + { + UserName = signup.Email, + Email = signup.Email, + RequiresEmailConfirmation = policiesSettings.RequiresConfirmedEmail, + RequiresApproval = policiesSettings.RequiresUserApproval, + Created = DateTimeOffset.UtcNow + }; + var result = await userManager.CreateAsync(user, signup.Password); + if (result.Succeeded) + { + eventAggregator.Publish(new UserRegisteredEvent + { + RequestUri = Request.GetAbsoluteRootUri(), + User = user + }); + + var response = new SignupResult + { + Email = user.Email, + RequiresConfirmedEmail = policiesSettings.RequiresConfirmedEmail && !user.EmailConfirmed, + RequiresUserApproval = policiesSettings.RequiresUserApproval && !user.Approved + }; + return TypedResults.Ok(response); + } + errorMessage = result.ToString(); + } + return TypedResults.Problem(errorMessage, statusCode: 400); + } + [AllowAnonymous] [HttpPost("login")] [RateLimitsFilter(ZoneLimits.Login, Scope = RateLimitsScope.RemoteAddress)] @@ -122,8 +191,8 @@ public class BtcPayAppController( return Results.Unauthorized(); } - [HttpGet("info")] - public async Task, ValidationProblem, NotFound>> Info() + [HttpGet("user")] + public async Task, NotFound>> UserInfo() { var user = await userManager.GetUserAsync(User); if (user == null) return TypedResults.NotFound(); diff --git a/BTCPayServer/Services/ServerSettings.cs b/BTCPayServer/Services/ServerSettings.cs index 9ca61a539..e545603f6 100644 --- a/BTCPayServer/Services/ServerSettings.cs +++ b/BTCPayServer/Services/ServerSettings.cs @@ -1,13 +1,11 @@ using System.ComponentModel.DataAnnotations; -using Microsoft.AspNetCore.Http; -using Newtonsoft.Json; namespace BTCPayServer.Services; public class ServerSettings { [Display(Name = "Server Name")] - public string ServerName { get; set; } + public string ServerName { get; set; } = "BTCPay Server"; [Display(Name = "Contact URL")] public string ContactUrl { get; set; } From c6b69babc22c61b88f4cc3f712da09e231d322d7 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Mon, 15 Apr 2024 22:24:07 +0200 Subject: [PATCH 17/64] Add nullable indicators --- BTCPayApp.CommonServer/AppUserInfo.cs | 16 +++---- .../IBTCPayAppServerClient.cs | 43 ++++++++----------- BTCPayApp.CommonServer/PairSuccessResult.cs | 21 +++++---- BTCPayApp.CommonServer/SignupRequest.cs | 4 +- BTCPayApp.CommonServer/SignupResult.cs | 2 +- 5 files changed, 38 insertions(+), 48 deletions(-) diff --git a/BTCPayApp.CommonServer/AppUserInfo.cs b/BTCPayApp.CommonServer/AppUserInfo.cs index 625aab437..0cc0e38e9 100644 --- a/BTCPayApp.CommonServer/AppUserInfo.cs +++ b/BTCPayApp.CommonServer/AppUserInfo.cs @@ -4,17 +4,17 @@ namespace BTCPayApp.CommonServer; public class AppUserInfo { - public string UserId { get; set; } - public string Email { get; set; } - public IEnumerable Roles { get; set; } - public IEnumerable Stores { get; set; } + public string? UserId { get; set; } + public string? Email { get; set; } + public IEnumerable? Roles { get; set; } + public IEnumerable? Stores { get; set; } } public class AppUserStoreInfo { - public string Id { get; set; } - public string Name { get; set; } - public string RoleId { get; set; } + public string? Id { get; set; } + public string? Name { get; set; } + public string? RoleId { get; set; } public bool Archived { get; set; } - public IEnumerable Permissions { get; set; } + public IEnumerable? Permissions { get; set; } } diff --git a/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs b/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs index da9722597..29187b011 100644 --- a/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs +++ b/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs @@ -1,29 +1,20 @@ -using System.Threading; -using System.Threading.Tasks; +using System.Threading.Tasks; -namespace BTCPayApp.CommonServer +namespace BTCPayApp.CommonServer; + +public interface IBTCPayAppServerClient { - public interface IBTCPayAppServerClient - { - Task TransactionDetected(string txid); - Task NewBlock(string block); - } - - public interface IBTCPayAppServerHub - { - Task Handshake(AppHandshake handshake); - Task GetTransactions(); - - } - - public class AppHandshake - { - public string DerivationScheme { get; set; } - - } - - - - - + Task TransactionDetected(string txid); + Task NewBlock(string block); +} + +public interface IBTCPayAppServerHub +{ + Task Handshake(AppHandshake handshake); + Task GetTransactions(); +} + +public class AppHandshake +{ + public string? DerivationScheme { get; set; } } diff --git a/BTCPayApp.CommonServer/PairSuccessResult.cs b/BTCPayApp.CommonServer/PairSuccessResult.cs index 8318e20c3..9d92e663b 100644 --- a/BTCPayApp.CommonServer/PairSuccessResult.cs +++ b/BTCPayApp.CommonServer/PairSuccessResult.cs @@ -1,13 +1,12 @@ -namespace BTCPayApp.CommonServer -{ - public class PairSuccessResult - { - public string Key { get; set; } - public string? StoreId { get; set; } - public string UserId { get; set; } +namespace BTCPayApp.CommonServer; - public string? ExistingWallet { get; set; } - public string? ExistingWalletSeed { get; set; } - public string Network { get; set; } - } +public class PairSuccessResult +{ + public string? Key { get; set; } + public string? StoreId { get; set; } + public string? UserId { get; set; } + + public string? ExistingWallet { get; set; } + public string? ExistingWalletSeed { get; set; } + public string? Network { get; set; } } diff --git a/BTCPayApp.CommonServer/SignupRequest.cs b/BTCPayApp.CommonServer/SignupRequest.cs index fe9e189cb..241d91690 100644 --- a/BTCPayApp.CommonServer/SignupRequest.cs +++ b/BTCPayApp.CommonServer/SignupRequest.cs @@ -5,8 +5,8 @@ namespace BTCPayApp.CommonServer; public class SignupRequest { [Required] - public string Email { get; init; } + public string? Email { get; init; } [Required] - public string Password { get; init; } + public string? Password { get; init; } } diff --git a/BTCPayApp.CommonServer/SignupResult.cs b/BTCPayApp.CommonServer/SignupResult.cs index ab0ad80c5..174dbc067 100644 --- a/BTCPayApp.CommonServer/SignupResult.cs +++ b/BTCPayApp.CommonServer/SignupResult.cs @@ -2,7 +2,7 @@ namespace BTCPayApp.CommonServer; public class SignupResult { - public string Email { get; set; } + public string? Email { get; set; } public bool RequiresConfirmedEmail { get; set; } public bool RequiresUserApproval { get; set; } } From 0c0c2be67b5e61fd6ab96554be2a9102d468f675 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Thu, 18 Apr 2024 10:38:56 +0200 Subject: [PATCH 18/64] Unset default server name --- BTCPayApp.CommonServer/AppInstanceInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BTCPayApp.CommonServer/AppInstanceInfo.cs b/BTCPayApp.CommonServer/AppInstanceInfo.cs index c739c914b..f9db7c444 100644 --- a/BTCPayApp.CommonServer/AppInstanceInfo.cs +++ b/BTCPayApp.CommonServer/AppInstanceInfo.cs @@ -3,7 +3,7 @@ namespace BTCPayApp.CommonServer; public class AppInstanceInfo { public string BaseUrl { get; set; } - public string ServerName { get; set; } = "BTCPay Server"; + public string ServerName { get; set; } public string? ContactUrl { get; set; } public string? LogoUrl { get; set; } public string? CustomThemeCssUrl { get; set; } From cc20a0ee9539350d3f779eb25318784f96ae62d5 Mon Sep 17 00:00:00 2001 From: Kukks Date: Thu, 2 May 2024 15:00:09 +0200 Subject: [PATCH 19/64] wip --- BTCPayApp.CommonServer/AppUserInfo.cs | 10 + BTCPayApp.CommonServer/IBTCPayAppHubClient.cs | 79 +++++ .../IBTCPayAppServerClient.cs | 20 -- BTCPayApp.CommonServer/PairSuccessResult.cs | 12 - BTCPayServer/App/BTCPayAppExtensions.cs | 1 - BTCPayServer/App/BTCPayAppHub.cs | 327 ++++++++++++------ BTCPayServer/App/BTCPayAppState.cs | 132 +++++++ BTCPayServer/App/BtcPayAppController.cs | 61 +--- BTCPayServer/App/BtcPayAppService.cs | 39 --- BTCPayServer/App/Exts.cs | 57 +++ 10 files changed, 495 insertions(+), 243 deletions(-) create mode 100644 BTCPayApp.CommonServer/IBTCPayAppHubClient.cs delete mode 100644 BTCPayApp.CommonServer/IBTCPayAppServerClient.cs delete mode 100644 BTCPayApp.CommonServer/PairSuccessResult.cs create mode 100644 BTCPayServer/App/BTCPayAppState.cs delete mode 100644 BTCPayServer/App/BtcPayAppService.cs create mode 100644 BTCPayServer/App/Exts.cs diff --git a/BTCPayApp.CommonServer/AppUserInfo.cs b/BTCPayApp.CommonServer/AppUserInfo.cs index 0cc0e38e9..aa3aa3915 100644 --- a/BTCPayApp.CommonServer/AppUserInfo.cs +++ b/BTCPayApp.CommonServer/AppUserInfo.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace BTCPayApp.CommonServer; @@ -8,6 +9,15 @@ public class AppUserInfo public string? Email { get; set; } public IEnumerable? Roles { get; set; } public IEnumerable? Stores { get; set; } + + public static bool Equals(AppUserInfo? x, AppUserInfo? y) + { + if (ReferenceEquals(x, y)) return true; + if (ReferenceEquals(x, null)) return false; + if (ReferenceEquals(y, null)) return false; + if (x.GetType() != y.GetType()) return false; + return x.UserId == y.UserId && x.Email == y.Email && Equals(x.Roles, y.Roles) && Equals(x.Stores, y.Stores); + } } public class AppUserStoreInfo diff --git a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs new file mode 100644 index 000000000..597a03794 --- /dev/null +++ b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace BTCPayApp.CommonServer; + +//methods available on the hub in the client +public interface IBTCPayAppHubClient +{ + Task NotifyNetwork(string network); + Task TransactionDetected(string identifier, string txId); + Task NewBlock(string block); +} +//methods available on the hub in the server +public interface IBTCPayAppHubServer +{ + Task> Pair(PairRequest request); + Task Handshake(AppHandshake request); + Task BroadcastTransaction(string tx); + Task GetFeeRate(int blockTarget); + Task GetBestBlock(); + Task GetBlockHeader(string hash); + + Task FetchTxsAndTheirBlockHeads(string[] txIds); + Task DeriveScript(string identifier); + Task TrackScripts(string identifier, string[] scripts); + Task UpdatePsbt(string[] identifiers, string psbt); + Task GetUTXOs(string[] identifiers); +} + +public class CoinResponse +{ + public string Identifier{ get; set; } + public bool Confirmed { get; set; } + public string Script { get; set; } + public string Outpoint { get; set; } + public decimal Value { get; set; } + public string Path { get; set; } +} + +public class TxInfoResponse +{ + public Dictionary Txs { get; set; } + public Dictionary Blocks { get; set; } + public Dictionary BlockHeghts { get; set; } +} + +public class TransactionResponse +{ + public string? BlockHash { get; set; } + public int? BlockHeight { get; set; } + public string Transaction { get; set; } + +} + + +public class BestBlockResponse +{ + public required string BlockHash { get; set; } + public required int BlockHeight { get; set; } + + public string BlockHeader { get; set; } +} + +public class AppHandshake +{ + public string[] Identifiers { get; set; } +} + +public class AppHandshakeResponse +{ + //response about identifiers being tracked successfully + public string[] IdentifiersAcknowledged { get; set; } +} + + +public class PairRequest +{ + public Dictionary Derivations { get; set; } = new(); +} diff --git a/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs b/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs deleted file mode 100644 index 29187b011..000000000 --- a/BTCPayApp.CommonServer/IBTCPayAppServerClient.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Threading.Tasks; - -namespace BTCPayApp.CommonServer; - -public interface IBTCPayAppServerClient -{ - Task TransactionDetected(string txid); - Task NewBlock(string block); -} - -public interface IBTCPayAppServerHub -{ - Task Handshake(AppHandshake handshake); - Task GetTransactions(); -} - -public class AppHandshake -{ - public string? DerivationScheme { get; set; } -} diff --git a/BTCPayApp.CommonServer/PairSuccessResult.cs b/BTCPayApp.CommonServer/PairSuccessResult.cs deleted file mode 100644 index 9d92e663b..000000000 --- a/BTCPayApp.CommonServer/PairSuccessResult.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace BTCPayApp.CommonServer; - -public class PairSuccessResult -{ - public string? Key { get; set; } - public string? StoreId { get; set; } - public string? UserId { get; set; } - - public string? ExistingWallet { get; set; } - public string? ExistingWalletSeed { get; set; } - public string? Network { get; set; } -} diff --git a/BTCPayServer/App/BTCPayAppExtensions.cs b/BTCPayServer/App/BTCPayAppExtensions.cs index fe5566911..d4763dec6 100644 --- a/BTCPayServer/App/BTCPayAppExtensions.cs +++ b/BTCPayServer/App/BTCPayAppExtensions.cs @@ -7,7 +7,6 @@ public static class BTCPayAppExtensions { public static IServiceCollection AddBTCPayApp(this IServiceCollection serviceCollection) { - serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddHostedService(serviceProvider => serviceProvider.GetRequiredService()); return serviceCollection; diff --git a/BTCPayServer/App/BTCPayAppHub.cs b/BTCPayServer/App/BTCPayAppHub.cs index 1f430b787..d2a5b64b2 100644 --- a/BTCPayServer/App/BTCPayAppHub.cs +++ b/BTCPayServer/App/BTCPayAppHub.cs @@ -1,160 +1,265 @@ #nullable enable using System; -using System.Collections.Concurrent; +using System.Collections; +using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; using BTCPayApp.CommonServer; using BTCPayServer.Abstractions.Constants; -using BTCPayServer.Data; +using BTCPayServer.HostedServices; +using BTCPayServer.Services; using BTCPayServer.Services.Wallets; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.SignalR; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using NBitcoin; -using NBXplorer; +using NBXplorer.DerivationStrategy; using NBXplorer.Models; -using NewBlockEvent = BTCPayServer.Events.NewBlockEvent; +using Newtonsoft.Json; namespace BTCPayServer.Controllers; -public class BTCPayAppState : IHostedService + +public class GetBlockchainInfoResponse { - private readonly IHubContext _hubContext; - private readonly ILogger _logger; - private readonly ExplorerClientProvider _explorerClientProvider; - private readonly BTCPayNetworkProvider _networkProvider; - private readonly EventAggregator _eventAggregator; - private CompositeDisposable? _compositeDisposable; - public ExplorerClient ExplorerClient { get; private set; } - private DerivationSchemeParser _derivationSchemeParser; - private readonly ConcurrentDictionary _connectionScheme = new(); - - public BTCPayAppState( - IHubContext hubContext, - ILogger logger, - ExplorerClientProvider explorerClientProvider, - BTCPayNetworkProvider networkProvider, - EventAggregator eventAggregator) + [JsonProperty("headers")] + public int Headers { - _hubContext = hubContext; - _logger = logger; - _explorerClientProvider = explorerClientProvider; - _networkProvider = networkProvider; - _eventAggregator = eventAggregator; + get; set; + } + [JsonProperty("blocks")] + public int Blocks + { + get; set; + } + [JsonProperty("verificationprogress")] + public double VerificationProgress + { + get; set; } - public Task StartAsync(CancellationToken cancellationToken) + [JsonProperty("mediantime")] + public long? MedianTime { - ExplorerClient = _explorerClientProvider.GetExplorerClient("BTC"); - _derivationSchemeParser = new DerivationSchemeParser(_networkProvider.BTC); - _compositeDisposable = new(); - _compositeDisposable.Add( - _eventAggregator.Subscribe(OnNewBlock)); - _compositeDisposable.Add( - _eventAggregator.Subscribe(OnNewTransaction)); - return Task.CompletedTask; + get; set; } - private void OnNewTransaction(NewTransactionEvent obj) + [JsonProperty("initialblockdownload")] + public bool? InitialBlockDownload { - if (obj.CryptoCode != "BTC") - return; - - _connectionScheme.Where(pair => pair.Value == obj.TrackedSource) - .Select(pair => pair.Key) - .ToList() - .ForEach(connectionId => - { - _hubContext.Clients.Client(connectionId) - .TransactionDetected(obj.TransactionData.TransactionHash.ToString()); - }); + get; set; } + [JsonProperty("bestblockhash")] + [JsonConverter(typeof(NBitcoin.JsonConverters.UInt256JsonConverter))] + public uint256 BestBlockHash { get; set; } +} - private void OnNewBlock(NewBlockEvent obj) - { - if (obj.CryptoCode != "BTC") - return; - _hubContext.Clients.All.NewBlock(obj.Hash.ToString()); - } - public Task StopAsync(CancellationToken cancellationToken) +public record RPCBlockHeader(uint256 Hash, uint256? Previous, int Height, DateTimeOffset Time, uint256 MerkleRoot) +{ + public SlimChainedBlock ToSlimChainedBlock() => new(Hash, Previous, Height); +} +public class BlockHeaders : IEnumerable +{ + public readonly Dictionary ByHashes; + public readonly Dictionary ByHeight; + public BlockHeaders(IList headers) { - _compositeDisposable?.Dispose(); - return Task.CompletedTask; - } - - public TrackedSource? GetConnectionState(string connectionId) - { - _connectionScheme.TryGetValue(connectionId, out var res); - return res; - } - - public async Task Handshake(string contextConnectionId, AppHandshake handshake) - { - try + ByHashes = new Dictionary(headers.Count); + ByHeight = new Dictionary(headers.Count); + foreach (var header in headers) { - var ts = - TrackedSource.Create(_derivationSchemeParser.Parse(handshake.DerivationScheme)); - await ExplorerClient.TrackAsync(ts); - _connectionScheme.AddOrReplace(contextConnectionId, ts); - } - catch (Exception e) - { - _logger.LogError(e, "Error during handshake"); - throw; + ByHashes.TryAdd(header.Hash, header); + ByHeight.TryAdd(header.Height, header); } } - - public void RemoveConnection(string contextConnectionId) + public IEnumerator GetEnumerator() { - _connectionScheme.TryRemove(contextConnectionId, out _); + return ByHeight.Values.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); } } -[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)] -public class BTCPayAppHub : Hub, IBTCPayAppServerHub +[Authorize(AuthenticationSchemes = AuthenticationSchemes.Bearer)] +public class BTCPayAppHub : Hub, IBTCPayAppHubServer { + private readonly BTCPayNetworkProvider _btcPayNetworkProvider; + private readonly NBXplorerDashboard _nbXplorerDashboard; private readonly BTCPayAppState _appState; - private readonly BTCPayWallet _wallet; + private readonly ExplorerClientProvider _explorerClientProvider; + private readonly IFeeProviderFactory _feeProviderFactory; - - public BTCPayAppHub(BTCPayAppState appState, BTCPayWalletProvider walletProvider) + public BTCPayAppHub(BTCPayNetworkProvider btcPayNetworkProvider, + NBXplorerDashboard nbXplorerDashboard, + BTCPayAppState appState, + ExplorerClientProvider explorerClientProvider, + IFeeProviderFactory feeProviderFactory) { + _btcPayNetworkProvider = btcPayNetworkProvider; + _nbXplorerDashboard = nbXplorerDashboard; _appState = appState; - _wallet = walletProvider.GetWallet("BTC"); + _explorerClientProvider = explorerClientProvider; + _feeProviderFactory = feeProviderFactory; } public override async Task OnConnectedAsync() { - } - - public override Task OnDisconnectedAsync(Exception? exception) - { - _appState.RemoveConnection(Context.ConnectionId); - return base.OnDisconnectedAsync(exception); - } - - public Task Handshake(AppHandshake handshake) - { - return _appState.Handshake(Context.ConnectionId, handshake); - } - - public async Task GetTransactions() - { - var deriv = _appState.GetConnectionState(Context.ConnectionId); - if(deriv is null) - throw new InvalidOperationException("Handshake not done"); - var txs = await _appState.ExplorerClient.GetTransactionsAsync(deriv); - if (txs is null) + //TODO: this needs to happen BEFORE connection is established + if (!_nbXplorerDashboard.IsFullySynched(_btcPayNetworkProvider.BTC.CryptoCode, out _)) { - throw new InvalidOperationException("NBXplorer failed to get transactions"); + Context.Abort(); + return; } - + await Clients.Client(Context.ConnectionId).NotifyNetwork(_btcPayNetworkProvider.BTC.NBitcoinNetwork.ToString()); } + + public async Task BroadcastTransaction(string tx) + { + var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC); + Transaction txObj = Transaction.Parse(tx, explorerClient.Network.NBitcoinNetwork); + var result = await explorerClient.BroadcastAsync(txObj); + return result.Success; + } + + public async Task GetFeeRate(int blockTarget) + { + + var feeProvider = _feeProviderFactory.CreateFeeProvider( _btcPayNetworkProvider.BTC); + return (await feeProvider.GetFeeRateAsync(blockTarget)).SatoshiPerByte; + } + + public async Task GetBestBlock() + { + + var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC); + var bcInfo = await explorerClient.RPCClient.GetBlockchainInfoAsyncEx(); + var bh = await explorerClient.RPCClient.GetBlockHeaderAsync(bcInfo.BestBlockHash); + + return new BestBlockResponse() + { + BlockHash = bcInfo.BestBlockHash.ToString(), + BlockHeight = bcInfo.Blocks, + BlockHeader = bh.ToString() + }; + } + + public async Task GetBlockHeader(string hash) + { + + var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC); + var bh = await explorerClient.RPCClient.GetBlockHeaderAsync(uint256.Parse(hash)); + return bh.ToString(); + } + + public async Task FetchTxsAndTheirBlockHeads(string[] txIds) + { + + var cancellationToken = Context.ConnectionAborted; + var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC); + var uints = txIds.Select(uint256.Parse).ToArray(); + var txsFetch = await Task.WhenAll(uints.Select( + uint256 => + explorerClient.GetTransactionAsync(uint256, cancellationToken))); + + var batch = explorerClient.RPCClient.PrepareBatch(); + var headersTask = txsFetch.Where(result => result.BlockId is not null && result.BlockId != uint256.Zero) + .Distinct().ToDictionary(result => result.BlockId, result => + batch.GetBlockHeaderAsync(result.BlockId, cancellationToken)); + await batch.SendBatchAsync(cancellationToken); + + + + var headerToHeight = (await Task.WhenAll(headersTask.Values)).ToDictionary(header => header.GetHash(), + header => txsFetch.First(result => result.BlockId == header.GetHash()).Height!); + + return new TxInfoResponse() + { + Txs = txsFetch.ToDictionary(tx => tx.TransactionHash.ToString(), tx => new TransactionResponse() + { + BlockHash = tx.BlockId?.ToString(), + BlockHeight = (int?) tx.Height, + Transaction = tx.Transaction.ToString() + }), + Blocks = txsFetch.Where(tx => tx.BlockId is not null).ToDictionary(tx => tx.BlockId.ToString(), tx => tx.BlockId.ToString()), + BlockHeghts = headerToHeight.ToDictionary(kv => kv.Key.ToString(), kv =>(int) kv.Value!) + }; + } + public async Task DeriveScript(string identifier) + { + var cancellationToken = Context.ConnectionAborted; + var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC); + var ts = TrackedSource.Parse(identifier,explorerClient.Network ) as DerivationSchemeTrackedSource; + var kpi = await explorerClient.GetUnusedAsync(ts.DerivationStrategy, DerivationFeature.Deposit, 0, true, cancellationToken); + return kpi.ScriptPubKey.ToHex(); + } + + public async Task TrackScripts(string identifier, string[] scripts) + { + var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC); + + var ts = TrackedSource.Parse(identifier,explorerClient.Network ) as GroupTrackedSource; + var s = scripts.Select(Script.FromHex).Select(script => script.GetDestinationAddress(explorerClient.Network.NBitcoinNetwork)).Select(address => address.ToString()).ToArray(); + await explorerClient.AddGroupAddressAsync(explorerClient.CryptoCode,ts.GroupId, s); + } + + public async Task UpdatePsbt(string[] identifiers, string psbt) + { + var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC); +var resultPsbt = PSBT.Parse(psbt, explorerClient.Network.NBitcoinNetwork); + foreach (string identifier in identifiers) + { + var ts = TrackedSource.Parse(identifier,explorerClient.Network); + if (ts is not DerivationSchemeTrackedSource derivationSchemeTrackedSource) + continue; + var res = await explorerClient.UpdatePSBTAsync(new UpdatePSBTRequest() + { + PSBT = resultPsbt, DerivationScheme = derivationSchemeTrackedSource.DerivationStrategy, + }); + resultPsbt = resultPsbt.Combine(res.PSBT); + } + return resultPsbt.ToHex(); + } + + public async Task GetUTXOs(string[] identifiers) + { + var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC); + var result = new List(); + foreach (string identifier in identifiers) + { + var ts = TrackedSource.Parse(identifier,explorerClient.Network); + if (ts is not DerivationSchemeTrackedSource derivationSchemeTrackedSource) + continue; + var utxos = await explorerClient.GetUTXOsAsync(derivationSchemeTrackedSource.DerivationStrategy); + result.AddRange(utxos.GetUnspentUTXOs(0).Select(utxo => new CoinResponse() + { + Identifier = identifier, + Confirmed = utxo.Confirmations >0, + Script = utxo.ScriptPubKey.ToHex(), + Outpoint = utxo.Outpoint.ToString(), + Value = utxo.Value.GetValue(_btcPayNetworkProvider.BTC), + Path = utxo.KeyPath.ToString() + })); + } + return result.ToArray(); + } + + + public async Task> Pair(PairRequest request) + { + return await _appState.Pair(Context.ConnectionId, request); + + + } + + public async Task Handshake(AppHandshake request) + { + + return await _appState.Handshake(Context.ConnectionId, request); + } } diff --git a/BTCPayServer/App/BTCPayAppState.cs b/BTCPayServer/App/BTCPayAppState.cs new file mode 100644 index 000000000..19ab8ffa8 --- /dev/null +++ b/BTCPayServer/App/BTCPayAppState.cs @@ -0,0 +1,132 @@ +#nullable enable +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using BTCPayApp.CommonServer; +using BTCPayServer.Events; +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using NBitcoin; +using NBXplorer; +using NBXplorer.Models; +using NewBlockEvent = BTCPayServer.Events.NewBlockEvent; + +namespace BTCPayServer.Controllers; + +public class BTCPayAppState : IHostedService +{ + private readonly IHubContext _hubContext; + private readonly ILogger _logger; + private readonly ExplorerClientProvider _explorerClientProvider; + private readonly BTCPayNetworkProvider _networkProvider; + private readonly EventAggregator _eventAggregator; + private CompositeDisposable? _compositeDisposable; + public ExplorerClient ExplorerClient { get; private set; } + private DerivationSchemeParser _derivationSchemeParser; + // private readonly ConcurrentDictionary _connectionScheme = new(); + + public BTCPayAppState( + IHubContext hubContext, + ILogger logger, + ExplorerClientProvider explorerClientProvider, + BTCPayNetworkProvider networkProvider, + EventAggregator eventAggregator) + { + _hubContext = hubContext; + _logger = logger; + _explorerClientProvider = explorerClientProvider; + _networkProvider = networkProvider; + _eventAggregator = eventAggregator; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + ExplorerClient = _explorerClientProvider.GetExplorerClient("BTC"); + _derivationSchemeParser = new DerivationSchemeParser(_networkProvider.BTC); + _compositeDisposable = new(); + _compositeDisposable.Add( + _eventAggregator.Subscribe(OnNewBlock)); + _compositeDisposable.Add( + _eventAggregator.Subscribe(OnNewTransaction)); + return Task.CompletedTask; + } + + private void OnNewTransaction(NewOnChainTransactionEvent obj) + { + if (obj.CryptoCode != "BTC") + return; + + var identifier = obj.NewTransactionEvent.TrackedSource.ToString()!; + _hubContext.Clients + .Group(identifier) + .TransactionDetected(identifier, obj.NewTransactionEvent.TransactionData.TransactionHash.ToString()); + } + + private void OnNewBlock(NewBlockEvent obj) + { + if (obj.CryptoCode != "BTC") + return; + _hubContext.Clients.All.NewBlock(obj.Hash.ToString()); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _compositeDisposable?.Dispose(); + return Task.CompletedTask; + } + + public async Task Handshake(string contextConnectionId, AppHandshake handshake) + { + + foreach (var ts in handshake.Identifiers) + { + try + { + await _hubContext.Groups.AddToGroupAsync(contextConnectionId, ts); + } + catch (Exception e) + { + _logger.LogError(e, "Error during handshake"); + throw; + } + } + + //TODO: Check if the provided identifiers are already tracked on the server + //TODO: Maybe also introduce a checkpoint to make sure nothing is missed, but this may be somethign to handle alongside VSS + return new AppHandshakeResponse() + { + IdentifiersAcknowledged = handshake.Identifiers + }; + } + + public async Task> Pair(string contextConnectionId, PairRequest request) + { + var result = new Dictionary(); + foreach (var derivation in request.Derivations) + { + + if(derivation.Value is null) + { + var id =await ExplorerClient.CreateGroupAsync(); + + result.Add(derivation.Key, id.TrackedSource); + } + else + { + var strategy = _derivationSchemeParser.ParseOutputDescriptor(derivation.Value); + result.Add(derivation.Key, TrackedSource.Create(strategy.Item1).ToString()); + } + } + await Handshake(contextConnectionId, new AppHandshake() + { + Identifiers = result.Values.ToArray() + }); + return result; + + + } +} diff --git a/BTCPayServer/App/BtcPayAppController.cs b/BTCPayServer/App/BtcPayAppController.cs index 04d12301b..9969fa0ec 100644 --- a/BTCPayServer/App/BtcPayAppController.cs +++ b/BTCPayServer/App/BtcPayAppController.cs @@ -34,7 +34,6 @@ namespace BTCPayServer.App; [Authorize(AuthenticationSchemes = AuthenticationSchemes.Bearer)] [Route("btcpayapp")] public class BtcPayAppController( - BtcPayAppService appService, APIKeyRepository apiKeyRepository, StoreRepository storeRepository, BTCPayNetworkProvider btcPayNetworkProvider, @@ -254,63 +253,5 @@ public class BtcPayAppController( } return result.Succeeded ? TypedResults.Ok() : TypedResults.Problem(result.ToString().Split(": ").Last(), statusCode: 401); } - - [HttpGet("pair/{code}")] - public async Task StartPair(string code) - { - var res = appService.ConsumePairingCode(code); - if (res is null) - { - return Unauthorized(); - } - - StoreData? store = null; - if (res.StoreId is not null) - { - store = await storeRepository.FindStore(res.StoreId, res.UserId); - if (store is null) - { - return NotFound(); - } - } - - var key = new APIKeyData - { - Id = Encoders.Hex.EncodeData(RandomUtils.GetBytes(20)), - Type = APIKeyType.Permanent, - UserId = res.UserId, - Label = "BTCPay App Pairing" - }; - key.SetBlob(new APIKeyBlob {Permissions = [Policies.Unrestricted] }); - await apiKeyRepository.CreateKey(key); - - var onchain = store?.GetDerivationSchemeSettings(handlers, "BTC"); - string? onchainSeed = null; - if (onchain is not null) - { - var explorerClient = explorerClientProvider.GetExplorerClient("BTC"); - onchainSeed = await GetSeed(explorerClient, onchain); - } - - var nBitcoinNetwork = btcPayNetworkProvider.GetNetwork("BTC").NBitcoinNetwork; - return Ok(new PairSuccessResult - { - Key = key.Id, - StoreId = store?.Id, - UserId = res.UserId, - ExistingWallet = onchain?.AccountDerivation?.GetExtPubKeys()?.FirstOrDefault()?.ToString(nBitcoinNetwork), - ExistingWalletSeed = onchainSeed, - Network = nBitcoinNetwork.Name - }); - } - - private async Task GetSeed(ExplorerClient client, DerivationSchemeSettings derivation) - { - return derivation.IsHotWallet && - await client.GetMetadataAsync(derivation.AccountDerivation, WellknownMetadataKeys.Mnemonic) is - { } seed && - !string.IsNullOrEmpty(seed) - ? seed - : null; - } + } diff --git a/BTCPayServer/App/BtcPayAppService.cs b/BTCPayServer/App/BtcPayAppService.cs deleted file mode 100644 index aaa4d63e0..000000000 --- a/BTCPayServer/App/BtcPayAppService.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.Extensions.Caching.Memory; - -namespace BTCPayServer.Controllers; - -public class BtcPayAppService -{ - private readonly IMemoryCache _memoryCache; - - public BtcPayAppService(IMemoryCache memoryCache) - { - _memoryCache = memoryCache; - } - - private string CacheKey(string k) => $"BtcPayAppService_{k}"; - - public async Task GeneratePairingCode(string storeId, string userId) - { - var code = Guid.NewGuid().ToString(); - _memoryCache.Set(CacheKey(code), new PairingRequest() {Key = code, StoreId = storeId, UserId = userId}, - TimeSpan.FromMinutes(5)); - return code; - } - - public PairingRequest? ConsumePairingCode(string code) - { - return _memoryCache.TryGetValue(CacheKey(code), out var pairingRequest) - ? (PairingRequest?)pairingRequest - : null; - } - - public class PairingRequest - { - public string Key { get; set; } - public string StoreId { get; set; } - public string UserId { get; set; } - } -} \ No newline at end of file diff --git a/BTCPayServer/App/Exts.cs b/BTCPayServer/App/Exts.cs new file mode 100644 index 000000000..2411c9547 --- /dev/null +++ b/BTCPayServer/App/Exts.cs @@ -0,0 +1,57 @@ +#nullable enable +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NBitcoin; +using NBitcoin.RPC; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace BTCPayServer.Controllers; + +//TODO: this currently requires NBX to be enabled with RPCPROXY enabled, we need to fix the whitelisted rpc commands to remove this dependency +public static class Exts +{ + public static async Task GetBlockchainInfoAsyncEx(this RPCClient client, CancellationToken cancellationToken = default) + { + var result = await client.SendCommandAsync("getblockchaininfo", cancellationToken).ConfigureAwait(false); + return JsonConvert.DeserializeObject(result.ResultString); + } + + public static async Task GetBlockHeadersAsync(this RPCClient rpc, IList blockHeights, CancellationToken cancellationToken) + { + var batch = rpc.PrepareBatch(); + var hashes = blockHeights.Select(h => batch.GetBlockHashAsync(h)).ToArray(); + await batch.SendBatchAsync(cancellationToken); + + batch = rpc.PrepareBatch(); + var headers = hashes.Select(async h => await batch.GetBlockHeaderAsyncEx(await h, cancellationToken)).ToArray(); + await batch.SendBatchAsync(cancellationToken); + + return new BlockHeaders(headers.Select(h => h.GetAwaiter().GetResult()).Where(h => h is not null).ToList()); + } + + public static async Task GetBlockHeaderAsyncEx(this RPCClient rpc, uint256 blk, CancellationToken cancellationToken) + { + var header = await rpc.SendCommandAsync(new NBitcoin.RPC.RPCRequest("getblockheader", new[] { blk.ToString() }) + { + ThrowIfRPCError = false + }, cancellationToken); + if (header.Result is null || header.Error is not null) + return null; + var response = header.Result; + var confs = response["confirmations"].Value(); + if (confs == -1) + return null; + + var prev = response["previousblockhash"]?.Value(); + return new RPCBlockHeader( + blk, + prev is null ? null : new uint256(prev), + response["height"].Value(), + NBitcoin.Utils.UnixTimeToDateTime(response["time"].Value()), + new uint256(response["merkleroot"]?.Value())); + } + +} From 35645278a2e3b48ec12c587c87099d471f2534fc Mon Sep 17 00:00:00 2001 From: Kukks Date: Fri, 3 May 2024 16:19:10 +0200 Subject: [PATCH 20/64] fix bh --- BTCPayServer/App/BTCPayAppHub.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/BTCPayServer/App/BTCPayAppHub.cs b/BTCPayServer/App/BTCPayAppHub.cs index d2a5b64b2..0fdaa7263 100644 --- a/BTCPayServer/App/BTCPayAppHub.cs +++ b/BTCPayServer/App/BTCPayAppHub.cs @@ -139,13 +139,13 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC); var bcInfo = await explorerClient.RPCClient.GetBlockchainInfoAsyncEx(); - var bh = await explorerClient.RPCClient.GetBlockHeaderAsync(bcInfo.BestBlockHash); + var bh = await GetBlockHeader(bcInfo.BestBlockHash.ToString()); return new BestBlockResponse() { BlockHash = bcInfo.BestBlockHash.ToString(), BlockHeight = bcInfo.Blocks, - BlockHeader = bh.ToString() + BlockHeader = bh }; } @@ -154,7 +154,7 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC); var bh = await explorerClient.RPCClient.GetBlockHeaderAsync(uint256.Parse(hash)); - return bh.ToString(); + return Convert.ToHexString(bh.ToBytes()); } public async Task FetchTxsAndTheirBlockHeads(string[] txIds) @@ -186,7 +186,7 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer BlockHeight = (int?) tx.Height, Transaction = tx.Transaction.ToString() }), - Blocks = txsFetch.Where(tx => tx.BlockId is not null).ToDictionary(tx => tx.BlockId.ToString(), tx => tx.BlockId.ToString()), + Blocks = headersTask.ToDictionary(kv => kv.Key.ToString(), kv => Convert.ToHexString(kv.Value.Result.ToBytes())), BlockHeghts = headerToHeight.ToDictionary(kv => kv.Key.ToString(), kv =>(int) kv.Value!) }; } From ab0b06791645df6f473270af70e80ef0ea641ae9 Mon Sep 17 00:00:00 2001 From: Kukks Date: Mon, 6 May 2024 14:33:44 +0200 Subject: [PATCH 21/64] btcpay ln conn string --- BTCPayApp.CommonServer/IBTCPayAppHubClient.cs | 2 + BTCPayServer/App/BTCPayAppExtensions.cs | 5 +- BTCPayServer/App/BTCPayAppHub.cs | 17 ++ BTCPayServer/App/BTCPayAppState.cs | 61 ++++-- BTCPayServer/App/LN.cs | 174 ++++++++++++++++++ .../Views/UIStores/SetupLightningNode.cshtml | 15 -- 6 files changed, 239 insertions(+), 35 deletions(-) create mode 100644 BTCPayServer/App/LN.cs diff --git a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs index 597a03794..f596a3ab3 100644 --- a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs +++ b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs @@ -13,6 +13,8 @@ public interface IBTCPayAppHubClient //methods available on the hub in the server public interface IBTCPayAppHubServer { + Task MasterNodePong(string group, bool active); + Task> Pair(PairRequest request); Task Handshake(AppHandshake request); Task BroadcastTransaction(string tx); diff --git a/BTCPayServer/App/BTCPayAppExtensions.cs b/BTCPayServer/App/BTCPayAppExtensions.cs index d4763dec6..6dea5c9f2 100644 --- a/BTCPayServer/App/BTCPayAppExtensions.cs +++ b/BTCPayServer/App/BTCPayAppExtensions.cs @@ -1,4 +1,6 @@ -using Microsoft.AspNetCore.Builder; +using BTCPayServer.App; +using BTCPayServer.Lightning; +using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; namespace BTCPayServer.Controllers; @@ -8,6 +10,7 @@ public static class BTCPayAppExtensions public static IServiceCollection AddBTCPayApp(this IServiceCollection serviceCollection) { serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddHostedService(serviceProvider => serviceProvider.GetRequiredService()); return serviceCollection; } diff --git a/BTCPayServer/App/BTCPayAppHub.cs b/BTCPayServer/App/BTCPayAppHub.cs index 0fdaa7263..9a6dee3d7 100644 --- a/BTCPayServer/App/BTCPayAppHub.cs +++ b/BTCPayServer/App/BTCPayAppHub.cs @@ -108,6 +108,9 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer public override async Task OnConnectedAsync() { + + await _appState.Connected(Context.ConnectionId); + //TODO: this needs to happen BEFORE connection is established if (!_nbXplorerDashboard.IsFullySynched(_btcPayNetworkProvider.BTC.CryptoCode, out _)) { @@ -119,6 +122,14 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer } + + public override async Task OnDisconnectedAsync(Exception? exception) + { + await _appState.Disconnected(Context.ConnectionId); + await base.OnDisconnectedAsync(exception); + } + + public async Task BroadcastTransaction(string tx) { var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC); @@ -250,6 +261,12 @@ var resultPsbt = PSBT.Parse(psbt, explorerClient.Network.NBitcoinNetwork); } + public async Task MasterNodePong(string group, bool active) + { + await _appState.MasterNodePong(group, Context.ConnectionId, active); + } + + public async Task> Pair(PairRequest request) { return await _appState.Pair(Context.ConnectionId, request); diff --git a/BTCPayServer/App/BTCPayAppState.cs b/BTCPayServer/App/BTCPayAppState.cs index 19ab8ffa8..bc60b9f98 100644 --- a/BTCPayServer/App/BTCPayAppState.cs +++ b/BTCPayServer/App/BTCPayAppState.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using BTCPayApp.CommonServer; @@ -24,6 +25,7 @@ public class BTCPayAppState : IHostedService private readonly ExplorerClientProvider _explorerClientProvider; private readonly BTCPayNetworkProvider _networkProvider; private readonly EventAggregator _eventAggregator; + private readonly HubLifetimeManager _lifetimeManager; private CompositeDisposable? _compositeDisposable; public ExplorerClient ExplorerClient { get; private set; } private DerivationSchemeParser _derivationSchemeParser; @@ -34,13 +36,15 @@ public class BTCPayAppState : IHostedService ILogger logger, ExplorerClientProvider explorerClientProvider, BTCPayNetworkProvider networkProvider, - EventAggregator eventAggregator) + EventAggregator eventAggregator, + HubLifetimeManager lifetimeManager) { _hubContext = hubContext; _logger = logger; _explorerClientProvider = explorerClientProvider; _networkProvider = networkProvider; _eventAggregator = eventAggregator; + _lifetimeManager = lifetimeManager; } public Task StartAsync(CancellationToken cancellationToken) @@ -81,7 +85,6 @@ public class BTCPayAppState : IHostedService public async Task Handshake(string contextConnectionId, AppHandshake handshake) { - foreach (var ts in handshake.Identifiers) { try @@ -94,13 +97,10 @@ public class BTCPayAppState : IHostedService throw; } } - + //TODO: Check if the provided identifiers are already tracked on the server //TODO: Maybe also introduce a checkpoint to make sure nothing is missed, but this may be somethign to handle alongside VSS - return new AppHandshakeResponse() - { - IdentifiersAcknowledged = handshake.Identifiers - }; + return new AppHandshakeResponse() {IdentifiersAcknowledged = handshake.Identifiers}; } public async Task> Pair(string contextConnectionId, PairRequest request) @@ -108,25 +108,48 @@ public class BTCPayAppState : IHostedService var result = new Dictionary(); foreach (var derivation in request.Derivations) { - - if(derivation.Value is null) + if (derivation.Value is null) { - var id =await ExplorerClient.CreateGroupAsync(); - + var id = await ExplorerClient.CreateGroupAsync(); + result.Add(derivation.Key, id.TrackedSource); } else { - var strategy = _derivationSchemeParser.ParseOutputDescriptor(derivation.Value); - result.Add(derivation.Key, TrackedSource.Create(strategy.Item1).ToString()); + var strategy = _derivationSchemeParser.ParseOutputDescriptor(derivation.Value); + result.Add(derivation.Key, TrackedSource.Create(strategy.Item1).ToString()); } } - await Handshake(contextConnectionId, new AppHandshake() - { - Identifiers = result.Values.ToArray() - }); + + await Handshake(contextConnectionId, new AppHandshake() {Identifiers = result.Values.ToArray()}); return result; - - + } + + public readonly ConcurrentDictionary GroupToConnectionId = new(); + + + public async Task MasterNodePong(string group, string contextConnectionId, bool active) + { + if (active) + { + GroupToConnectionId.AddOrUpdate(group, contextConnectionId, (a, b) => contextConnectionId); + } + else if (GroupToConnectionId.TryGetValue(group, out var connId) && connId == contextConnectionId) + { + GroupToConnectionId.TryRemove(group, out _); + } + } + + public async Task Disconnected(string contextConnectionId) + { + foreach (var group in GroupToConnectionId.Where(a => a.Value == contextConnectionId).Select(a => a.Key) + .ToArray()) + { + GroupToConnectionId.TryRemove(group, out _); + } + } + + public async Task Connected(string contextConnectionId) + { } } diff --git a/BTCPayServer/App/LN.cs b/BTCPayServer/App/LN.cs new file mode 100644 index 000000000..f6076cbb9 --- /dev/null +++ b/BTCPayServer/App/LN.cs @@ -0,0 +1,174 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using BTCPayApp.CommonServer; +using BTCPayServer.Controllers; +using BTCPayServer.Lightning; +using Microsoft.AspNetCore.SignalR; +using NBitcoin; + +namespace BTCPayServer.App; + +public class BTCPayAppLightningConnectionStringHandler:ILightningConnectionStringHandler +{ + private readonly IHubContext _hubContext; + private readonly BTCPayAppState _appState; + private readonly DefaultHubLifetimeManager _lifetimeManager; + + public BTCPayAppLightningConnectionStringHandler(IHubContext hubContext, BTCPayAppState appState) + { + _hubContext = hubContext; + _appState = appState; + } + + public ILightningClient Create(string connectionString, Network network, [UnscopedRef] out string error) + { + var kv = LightningConnectionStringHelper.ExtractValues(connectionString, out var type); + if (type != "app") + { + error = null; + return null; + } + + if (!kv.TryGetValue("group", out var key)) + { + error = $"The key 'group' is mandatory for app connection strings"; + + return null; + } + + if (!_appState.GroupToConnectionId.TryGetValue(key, out var connectionId)) + { + error = $"The group {key} is not connected"; + return null; + } + error = null; + return new BTCPayAppLightningClient(_hubContext, _appState, key); + } + + +} + +public class BTCPayAppLightningClient:ILightningClient +{ + private readonly IHubContext _hubContext; + private readonly BTCPayAppState _appState; + private readonly string _key; + + public BTCPayAppLightningClient(IHubContext hubContext, BTCPayAppState appState, string key) + { + _hubContext = hubContext; + _appState = appState; + _key = key; + } + + public override string ToString() + { + return $"type=app;group={_key}"; + } + + public IBTCPayAppHubClient HubClient => _appState.GroupToConnectionId.TryGetValue(_key, out var connId) ? _hubContext.Clients.Client(connId) : throw new InvalidOperationException("Connection not found"); + + + public async Task GetInvoice(string invoiceId, CancellationToken cancellation = new CancellationToken()) + { + return await GetInvoice(uint256.Parse(invoiceId), cancellation); + } + + public async Task GetInvoice(uint256 paymentHash, CancellationToken cancellation = new CancellationToken()) + { + throw new NotImplementedException(); + } + + public async Task ListInvoices(CancellationToken cancellation = new CancellationToken()) + { + return await ListInvoices(new ListInvoicesParams(), cancellation); + } + + public async Task ListInvoices(ListInvoicesParams request, CancellationToken cancellation = new CancellationToken()) + { + throw new NotImplementedException(); + } + + public async Task GetPayment(string paymentHash, CancellationToken cancellation = new CancellationToken()) + { + throw new NotImplementedException(); + } + + public async Task ListPayments(CancellationToken cancellation = new CancellationToken()) + { + return await ListPayments(new ListPaymentsParams(), cancellation); + } + + public async Task ListPayments(ListPaymentsParams request, CancellationToken cancellation = new CancellationToken()) + { + throw new NotImplementedException(); + } + + public async Task CreateInvoice(LightMoney amount, string description, TimeSpan expiry, + CancellationToken cancellation = new CancellationToken()) + { + return await CreateInvoice(new CreateInvoiceParams(amount, description, expiry), cancellation); + } + + public async Task CreateInvoice(CreateInvoiceParams createInvoiceRequest, CancellationToken cancellation = new CancellationToken()) + { + throw new NotImplementedException(); + } + + public async Task Listen(CancellationToken cancellation = new CancellationToken()) + { + throw new NotImplementedException(); + } + + public async Task GetInfo(CancellationToken cancellation = new CancellationToken()) + { + throw new NotImplementedException(); + } + + public async Task GetBalance(CancellationToken cancellation = new CancellationToken()) + { + throw new NotImplementedException(); + } + + public async Task Pay(PayInvoiceParams payParams, CancellationToken cancellation = new CancellationToken()) + { + throw new NotImplementedException(); + } + + public async Task Pay(string bolt11, PayInvoiceParams payParams, CancellationToken cancellation = new CancellationToken()) + { + throw new NotImplementedException(); + } + + public async Task Pay(string bolt11, CancellationToken cancellation = new CancellationToken()) + { + return await Pay(bolt11, new PayInvoiceParams(), cancellation); + } + + public async Task OpenChannel(OpenChannelRequest openChannelRequest, CancellationToken cancellation = new CancellationToken()) + { + throw new NotImplementedException(); + } + + public async Task GetDepositAddress(CancellationToken cancellation = new CancellationToken()) + { + throw new NotImplementedException(); + } + + public async Task ConnectTo(NodeInfo nodeInfo, CancellationToken cancellation = new CancellationToken()) + { + throw new NotImplementedException(); + } + + public async Task CancelInvoice(string invoiceId, CancellationToken cancellation = new CancellationToken()) + { + throw new NotImplementedException(); + } + + public async Task ListChannels(CancellationToken cancellation = new CancellationToken()) + { + throw new NotImplementedException(); + } +} diff --git a/BTCPayServer/Views/UIStores/SetupLightningNode.cshtml b/BTCPayServer/Views/UIStores/SetupLightningNode.cshtml index 1069f769d..d754d3d02 100644 --- a/BTCPayServer/Views/UIStores/SetupLightningNode.cshtml +++ b/BTCPayServer/Views/UIStores/SetupLightningNode.cshtml @@ -188,21 +188,6 @@
- @inject BtcPayAppService BtcPayAppService - @inject UserManager UserManager -
- @{ - var code = await BtcPayAppService.GeneratePairingCode(Context.GetStoreData().Id, UserManager.GetUserId(User)); - var url = Url.ActionLink("StartPair","BtcPayApp", new { code }); - -
- - -
- } -
From 1ec5917518d4b7c7310374f9e0cbf505d1ab0904 Mon Sep 17 00:00:00 2001 From: Kukks Date: Mon, 6 May 2024 16:13:57 +0200 Subject: [PATCH 22/64] start connecting LN to btcpay --- BTCPayApp.CommonServer/AppUserInfo.cs | 13 +++++++++++++ .../BTCPayApp.CommonServer.csproj | 4 ++++ BTCPayApp.CommonServer/IBTCPayAppHubClient.cs | 8 ++++++++ BTCPayServer/App/BTCPayAppHub.cs | 6 ++++++ BTCPayServer/App/LN.cs | 13 ++++++++++++- 5 files changed, 43 insertions(+), 1 deletion(-) diff --git a/BTCPayApp.CommonServer/AppUserInfo.cs b/BTCPayApp.CommonServer/AppUserInfo.cs index aa3aa3915..897f864b9 100644 --- a/BTCPayApp.CommonServer/AppUserInfo.cs +++ b/BTCPayApp.CommonServer/AppUserInfo.cs @@ -1,8 +1,21 @@ using System; using System.Collections.Generic; +using BTCPayServer.Lightning; namespace BTCPayApp.CommonServer; +public partial class LightningPayment +{ + public string PaymentHash { get; set; } + public string? PaymentId { get; set; } + public string? Preimage { get; set; } + public string? Secret { get; set; } + public bool Inbound { get; set; } + public DateTimeOffset Timestamp { get; set; } + public long Value { get; set; } + public LightningPaymentStatus Status { get; set; } +} + public class AppUserInfo { public string? UserId { get; set; } diff --git a/BTCPayApp.CommonServer/BTCPayApp.CommonServer.csproj b/BTCPayApp.CommonServer/BTCPayApp.CommonServer.csproj index f897edefd..6480f809c 100644 --- a/BTCPayApp.CommonServer/BTCPayApp.CommonServer.csproj +++ b/BTCPayApp.CommonServer/BTCPayApp.CommonServer.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs index f596a3ab3..f2a696424 100644 --- a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs +++ b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs @@ -1,14 +1,19 @@ using System.Collections.Generic; using System.Threading.Tasks; +using BTCPayServer.Client.Models; namespace BTCPayApp.CommonServer; + + //methods available on the hub in the client public interface IBTCPayAppHubClient { Task NotifyNetwork(string network); Task TransactionDetected(string identifier, string txId); Task NewBlock(string block); + + Task CreateInvoice(CreateLightningInvoiceRequest createLightningInvoiceRequest); } //methods available on the hub in the server public interface IBTCPayAppHubServer @@ -27,6 +32,9 @@ public interface IBTCPayAppHubServer Task TrackScripts(string identifier, string[] scripts); Task UpdatePsbt(string[] identifiers, string psbt); Task GetUTXOs(string[] identifiers); + + + Task SendPaymentUpdate(string identifier, LightningPayment lightningPayment); } public class CoinResponse diff --git a/BTCPayServer/App/BTCPayAppHub.cs b/BTCPayServer/App/BTCPayAppHub.cs index 9a6dee3d7..5143048d8 100644 --- a/BTCPayServer/App/BTCPayAppHub.cs +++ b/BTCPayServer/App/BTCPayAppHub.cs @@ -260,6 +260,11 @@ var resultPsbt = PSBT.Parse(psbt, explorerClient.Network.NBitcoinNetwork); return result.ToArray(); } + public async Task SendPaymentUpdate(string identifier, LightningPayment lightningPayment) + { + throw new NotImplementedException(); + } + public async Task MasterNodePong(string group, bool active) { @@ -273,6 +278,7 @@ var resultPsbt = PSBT.Parse(psbt, explorerClient.Network.NBitcoinNetwork); } + public async Task Handshake(AppHandshake request) { diff --git a/BTCPayServer/App/LN.cs b/BTCPayServer/App/LN.cs index f6076cbb9..c88893a7c 100644 --- a/BTCPayServer/App/LN.cs +++ b/BTCPayServer/App/LN.cs @@ -3,10 +3,12 @@ using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using BTCPayApp.CommonServer; +using BTCPayServer.Client.Models; using BTCPayServer.Controllers; using BTCPayServer.Lightning; using Microsoft.AspNetCore.SignalR; using NBitcoin; +using LightningPayment = BTCPayServer.Lightning.LightningPayment; namespace BTCPayServer.App; @@ -114,9 +116,18 @@ public class BTCPayAppLightningClient:ILightningClient public async Task CreateInvoice(CreateInvoiceParams createInvoiceRequest, CancellationToken cancellation = new CancellationToken()) { - throw new NotImplementedException(); + var lp = await HubClient.CreateInvoice(new CreateLightningInvoiceRequest(createInvoiceRequest.Amount, createInvoiceRequest.Description, createInvoiceRequest.Expiry) + { + DescriptionHashOnly = createInvoiceRequest.DescriptionHashOnly, + PrivateRouteHints = createInvoiceRequest.PrivateRouteHints, + + }); + + + return null; } + public async Task Listen(CancellationToken cancellation = new CancellationToken()) { throw new NotImplementedException(); From fc98457add1778cb0666805a119f0b6206764554 Mon Sep 17 00:00:00 2001 From: Kukks Date: Wed, 8 May 2024 12:16:25 +0200 Subject: [PATCH 23/64] update and have working invoice gen --- BTCPayApp.CommonServer/AppUserInfo.cs | 3 + BTCPayApp.CommonServer/IBTCPayAppHubClient.cs | 5 + BTCPayServer/App/BTCPayAppHub.cs | 2 +- BTCPayServer/App/BTCPayAppState.cs | 14 ++- BTCPayServer/App/LN.cs | 92 ++++++++++++++++--- BTCPayServer/BTCPayServer.csproj | 1 + BTCPayServer/Hosting/Startup.cs | 2 +- 7 files changed, 98 insertions(+), 21 deletions(-) diff --git a/BTCPayApp.CommonServer/AppUserInfo.cs b/BTCPayApp.CommonServer/AppUserInfo.cs index 897f864b9..1de28514f 100644 --- a/BTCPayApp.CommonServer/AppUserInfo.cs +++ b/BTCPayApp.CommonServer/AppUserInfo.cs @@ -14,6 +14,9 @@ public partial class LightningPayment public DateTimeOffset Timestamp { get; set; } public long Value { get; set; } public LightningPaymentStatus Status { get; set; } + + //you can have multiple requests generated for the same payment hash, but once you reveal the preimage, you should reject any attempt to pay the same payment hash + public List PaymentRequests { get; set; } } public class AppUserInfo diff --git a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs index f2a696424..a25992833 100644 --- a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs +++ b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using BTCPayServer.Client.Models; +using BTCPayServer.Lightning; namespace BTCPayApp.CommonServer; @@ -14,6 +15,10 @@ public interface IBTCPayAppHubClient Task NewBlock(string block); Task CreateInvoice(CreateLightningInvoiceRequest createLightningInvoiceRequest); + Task GetLightningInvoice(string paymentHash); + Task GetLightningPayment(string paymentHash); + Task> GetLightningPayments(ListPaymentsParams request); + Task> GetLightningInvoices(ListInvoicesParams request); } //methods available on the hub in the server public interface IBTCPayAppHubServer diff --git a/BTCPayServer/App/BTCPayAppHub.cs b/BTCPayServer/App/BTCPayAppHub.cs index 5143048d8..2fae6d0a5 100644 --- a/BTCPayServer/App/BTCPayAppHub.cs +++ b/BTCPayServer/App/BTCPayAppHub.cs @@ -262,7 +262,7 @@ var resultPsbt = PSBT.Parse(psbt, explorerClient.Network.NBitcoinNetwork); public async Task SendPaymentUpdate(string identifier, LightningPayment lightningPayment) { - throw new NotImplementedException(); + await _appState.PaymentUpdate(identifier, lightningPayment); } diff --git a/BTCPayServer/App/BTCPayAppState.cs b/BTCPayServer/App/BTCPayAppState.cs index bc60b9f98..ee8bb033a 100644 --- a/BTCPayServer/App/BTCPayAppState.cs +++ b/BTCPayServer/App/BTCPayAppState.cs @@ -25,26 +25,25 @@ public class BTCPayAppState : IHostedService private readonly ExplorerClientProvider _explorerClientProvider; private readonly BTCPayNetworkProvider _networkProvider; private readonly EventAggregator _eventAggregator; - private readonly HubLifetimeManager _lifetimeManager; private CompositeDisposable? _compositeDisposable; public ExplorerClient ExplorerClient { get; private set; } + private DerivationSchemeParser _derivationSchemeParser; // private readonly ConcurrentDictionary _connectionScheme = new(); + public event EventHandler<(string,LightningPayment)>? OnPaymentUpdate; public BTCPayAppState( IHubContext hubContext, ILogger logger, ExplorerClientProvider explorerClientProvider, BTCPayNetworkProvider networkProvider, - EventAggregator eventAggregator, - HubLifetimeManager lifetimeManager) + EventAggregator eventAggregator) { _hubContext = hubContext; _logger = logger; _explorerClientProvider = explorerClientProvider; _networkProvider = networkProvider; _eventAggregator = eventAggregator; - _lifetimeManager = lifetimeManager; } public Task StartAsync(CancellationToken cancellationToken) @@ -152,4 +151,11 @@ public class BTCPayAppState : IHostedService public async Task Connected(string contextConnectionId) { } + + public async Task PaymentUpdate(string identifier, LightningPayment lightningPayment) + { + OnPaymentUpdate?.Invoke(this, (identifier, lightningPayment)); + } + + } diff --git a/BTCPayServer/App/LN.cs b/BTCPayServer/App/LN.cs index c88893a7c..8015389f0 100644 --- a/BTCPayServer/App/LN.cs +++ b/BTCPayServer/App/LN.cs @@ -1,6 +1,8 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Threading; +using System.Threading.Channels; using System.Threading.Tasks; using BTCPayApp.CommonServer; using BTCPayServer.Client.Models; @@ -8,7 +10,7 @@ using BTCPayServer.Controllers; using BTCPayServer.Lightning; using Microsoft.AspNetCore.SignalR; using NBitcoin; -using LightningPayment = BTCPayServer.Lightning.LightningPayment; +using LightningPayment = BTCPayApp.CommonServer.LightningPayment; namespace BTCPayServer.App; @@ -46,7 +48,7 @@ public class BTCPayAppLightningConnectionStringHandler:ILightningConnectionStrin return null; } error = null; - return new BTCPayAppLightningClient(_hubContext, _appState, key); + return new BTCPayAppLightningClient(_hubContext, _appState, key, network ); } @@ -57,12 +59,14 @@ public class BTCPayAppLightningClient:ILightningClient private readonly IHubContext _hubContext; private readonly BTCPayAppState _appState; private readonly string _key; + private readonly Network _network; - public BTCPayAppLightningClient(IHubContext hubContext, BTCPayAppState appState, string key) + public BTCPayAppLightningClient(IHubContext hubContext, BTCPayAppState appState, string key, Network network) { _hubContext = hubContext; _appState = appState; _key = key; + _network = network; } public override string ToString() @@ -80,7 +84,8 @@ public class BTCPayAppLightningClient:ILightningClient public async Task GetInvoice(uint256 paymentHash, CancellationToken cancellation = new CancellationToken()) { - throw new NotImplementedException(); + var lp = await HubClient.GetLightningInvoice(paymentHash.ToString()); + return ToLightningInvoice(lp, _network); } public async Task ListInvoices(CancellationToken cancellation = new CancellationToken()) @@ -90,22 +95,38 @@ public class BTCPayAppLightningClient:ILightningClient public async Task ListInvoices(ListInvoicesParams request, CancellationToken cancellation = new CancellationToken()) { - throw new NotImplementedException(); + var invs = await HubClient.GetLightningInvoices(request); + return invs.Select(i => ToLightningInvoice(i, _network)).ToArray(); } - public async Task GetPayment(string paymentHash, CancellationToken cancellation = new CancellationToken()) + public async Task GetPayment(string paymentHash, CancellationToken cancellation = new CancellationToken()) { - throw new NotImplementedException(); + + return ToLightningPayment(await HubClient.GetLightningPayment(paymentHash)); } - public async Task ListPayments(CancellationToken cancellation = new CancellationToken()) + private static Lightning.LightningPayment ToLightningPayment(LightningPayment lightningPayment) + { + return new Lightning.LightningPayment() + { + Id = lightningPayment.PaymentHash, + Amount = LightMoney.MilliSatoshis(lightningPayment.Value), + PaymentHash = lightningPayment.PaymentHash, + Preimage = lightningPayment.Preimage, + BOLT11 = lightningPayment.PaymentRequests.FirstOrDefault(), + Status = lightningPayment.Status + }; + } + + public async Task ListPayments(CancellationToken cancellation = new CancellationToken()) { return await ListPayments(new ListPaymentsParams(), cancellation); } - public async Task ListPayments(ListPaymentsParams request, CancellationToken cancellation = new CancellationToken()) + public async Task ListPayments(ListPaymentsParams request, CancellationToken cancellation = new CancellationToken()) { - throw new NotImplementedException(); + var invs = await HubClient.GetLightningPayments(request); + return invs.Select(ToLightningPayment).ToArray(); } public async Task CreateInvoice(LightMoney amount, string description, TimeSpan expiry, @@ -122,20 +143,61 @@ public class BTCPayAppLightningClient:ILightningClient PrivateRouteHints = createInvoiceRequest.PrivateRouteHints, }); - - - return null; + return ToLightningInvoice(lp, _network); } + private static LightningInvoice ToLightningInvoice(LightningPayment lightningPayment, Network _network) + { + var paymenRequest = BOLT11PaymentRequest.Parse(lightningPayment.PaymentRequests.First(), _network); + return new LightningInvoice() + { + Id = lightningPayment.PaymentHash, + Amount = LightMoney.MilliSatoshis(lightningPayment.Value), + PaymentHash = lightningPayment.PaymentHash, + Preimage = lightningPayment.Preimage, + BOLT11 = lightningPayment.PaymentRequests.FirstOrDefault(), + Status = lightningPayment.Status == LightningPaymentStatus.Complete? LightningInvoiceStatus.Paid: paymenRequest.ExpiryDate < DateTimeOffset.UtcNow? LightningInvoiceStatus.Expired: LightningInvoiceStatus.Unpaid + }; + } public async Task Listen(CancellationToken cancellation = new CancellationToken()) { - throw new NotImplementedException(); + return new Listener(_appState, _network); + } + + public class Listener:ILightningInvoiceListener + { + private readonly BTCPayAppState _btcPayAppState; + private readonly Network _network; + private readonly Channel _channel = Channel.CreateUnbounded(); + + public Listener(BTCPayAppState btcPayAppState, Network network) + { + _btcPayAppState = btcPayAppState; + _network = network; + _btcPayAppState.OnPaymentUpdate += BtcPayAppStateOnOnPaymentUpdate; + } + + private void BtcPayAppStateOnOnPaymentUpdate(object sender, (string, LightningPayment) e) + { + _channel.Writer.TryWrite(e.Item2); + } + + public void Dispose() + { + _btcPayAppState.OnPaymentUpdate -= BtcPayAppStateOnOnPaymentUpdate; + _channel.Writer.Complete(); + } + + public async Task WaitInvoice(CancellationToken cancellation) + { + return ToLightningInvoice(await _channel.Reader.ReadAsync(cancellation), _network); + } } public async Task GetInfo(CancellationToken cancellation = new CancellationToken()) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public async Task GetBalance(CancellationToken cancellation = new CancellationToken()) diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 5a8b9d68a..3b11e1390 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -47,6 +47,7 @@ + diff --git a/BTCPayServer/Hosting/Startup.cs b/BTCPayServer/Hosting/Startup.cs index 1ba554e64..2f90ce493 100644 --- a/BTCPayServer/Hosting/Startup.cs +++ b/BTCPayServer/Hosting/Startup.cs @@ -115,7 +115,7 @@ namespace BTCPayServer.Hosting services.AddBTCPayServer(Configuration, Logs); services.AddProviderStorage(); services.AddSession(); - services.AddSignalR(); + services.AddSignalR().AddNewtonsoftJsonProtocol(); services.AddFido2(options => { options.ServerName = "BTCPay Server"; From 106b4442a0adec53b5e1f63edd76c1784dba0f79 Mon Sep 17 00:00:00 2001 From: Kukks Date: Wed, 8 May 2024 14:43:48 +0200 Subject: [PATCH 24/64] expose cln nodes to localhost for ldk peer connections --- BTCPayServer.Tests/docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/BTCPayServer.Tests/docker-compose.yml b/BTCPayServer.Tests/docker-compose.yml index 57ed0229a..360550316 100644 --- a/BTCPayServer.Tests/docker-compose.yml +++ b/BTCPayServer.Tests/docker-compose.yml @@ -168,6 +168,7 @@ services: dev-bitcoind-poll=1 ports: - "30992:9835" # api port + - "30892:9735" # server port expose: - "9735" # server port - "9835" # api port @@ -196,6 +197,7 @@ services: dev-bitcoind-poll=1 ports: - "30993:9835" # api port + - "30893:9735" # api port expose: - "9735" # server port - "9835" # api port From b619688d80bbb3f7117cd20dce07cd3d56ce9f04 Mon Sep 17 00:00:00 2001 From: Kukks Date: Thu, 9 May 2024 17:29:00 +0200 Subject: [PATCH 25/64] expand tx update notif --- BTCPayApp.CommonServer/IBTCPayAppHubClient.cs | 2 +- BTCPayServer/App/BTCPayAppState.cs | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs index a25992833..aceb05dee 100644 --- a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs +++ b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs @@ -11,7 +11,7 @@ namespace BTCPayApp.CommonServer; public interface IBTCPayAppHubClient { Task NotifyNetwork(string network); - Task TransactionDetected(string identifier, string txId); + Task TransactionDetected(string identifier, string txId, string[] relatedScripts, bool confirmed); Task NewBlock(string block); Task CreateInvoice(CreateLightningInvoiceRequest createLightningInvoiceRequest); diff --git a/BTCPayServer/App/BTCPayAppState.cs b/BTCPayServer/App/BTCPayAppState.cs index ee8bb033a..99c975009 100644 --- a/BTCPayServer/App/BTCPayAppState.cs +++ b/BTCPayServer/App/BTCPayAppState.cs @@ -54,19 +54,25 @@ public class BTCPayAppState : IHostedService _compositeDisposable.Add( _eventAggregator.Subscribe(OnNewBlock)); _compositeDisposable.Add( - _eventAggregator.Subscribe(OnNewTransaction)); + _eventAggregator.SubscribeAsync(OnNewTransaction)); return Task.CompletedTask; } - private void OnNewTransaction(NewOnChainTransactionEvent obj) + private async Task OnNewTransaction(NewOnChainTransactionEvent obj) { if (obj.CryptoCode != "BTC") return; var identifier = obj.NewTransactionEvent.TrackedSource.ToString()!; - _hubContext.Clients + var explorer = _explorerClientProvider.GetExplorerClient(obj.CryptoCode); + var expandedTx = await explorer.GetTransactionAsync(obj.NewTransactionEvent.TrackedSource, + obj.NewTransactionEvent.TransactionData.TransactionHash); + var scripts = expandedTx.Inputs.Concat(expandedTx.Outputs).Select(output => output.ScriptPubKey.ToHex()).Distinct().ToArray(); + + await _hubContext.Clients .Group(identifier) - .TransactionDetected(identifier, obj.NewTransactionEvent.TransactionData.TransactionHash.ToString()); + .TransactionDetected(identifier, + obj.NewTransactionEvent.TransactionData.TransactionHash.ToString(), scripts, obj.NewTransactionEvent.BlockId is not null && obj.NewTransactionEvent.BlockId != uint256.Zero); } private void OnNewBlock(NewBlockEvent obj) From 8224c84556b3f94c04a0b43c42c497599c01ca3e Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Mon, 13 May 2024 12:04:23 +0200 Subject: [PATCH 26/64] Fix after update --- BTCPayServer/App/BtcPayAppController.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/BTCPayServer/App/BtcPayAppController.cs b/BTCPayServer/App/BtcPayAppController.cs index 9969fa0ec..805720d12 100644 --- a/BTCPayServer/App/BtcPayAppController.cs +++ b/BTCPayServer/App/BtcPayAppController.cs @@ -63,11 +63,11 @@ public class BtcPayAppController( ContactUrl = serverSettings.ContactUrl, RegistrationEnabled = policiesSettings.EnableRegistration, CustomThemeExtension = themeSettings.CustomTheme ? themeSettings.CustomThemeExtension.ToString() : null, - CustomThemeCssUrl = themeSettings.CustomTheme && !string.IsNullOrEmpty(themeSettings.CustomThemeFileId) - ? await fileService.GetFileUrl(Request.GetAbsoluteRootUri(), themeSettings.CustomThemeFileId) + CustomThemeCssUrl = themeSettings.CustomTheme && !string.IsNullOrEmpty(themeSettings.CustomThemeCssUrl?.ToString()) + ? themeSettings.CustomThemeCssUrl.ToString() : null, - LogoUrl = !string.IsNullOrEmpty(themeSettings.LogoFileId) - ? await fileService.GetFileUrl(Request.GetAbsoluteRootUri(), themeSettings.LogoFileId) + LogoUrl = !string.IsNullOrEmpty(themeSettings.LogoUrl?.ToString()) + ? themeSettings.LogoUrl.ToString() : null }); } From afa3c46ce318dc282e617303a2566edab10698b7 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Fri, 17 May 2024 17:38:58 +0200 Subject: [PATCH 27/64] Resolve logo and CSS URLs --- BTCPayServer/App/BtcPayAppController.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/BTCPayServer/App/BtcPayAppController.cs b/BTCPayServer/App/BtcPayAppController.cs index 805720d12..bcbbeb8f2 100644 --- a/BTCPayServer/App/BtcPayAppController.cs +++ b/BTCPayServer/App/BtcPayAppController.cs @@ -45,6 +45,7 @@ public class BtcPayAppController( PaymentMethodHandlerDictionary handlers, IFileService fileService, ISettingsRepository settingsRepository, + UriResolver uriResolver, IOptionsMonitor bearerTokenOptions) : Controller { @@ -64,10 +65,10 @@ public class BtcPayAppController( RegistrationEnabled = policiesSettings.EnableRegistration, CustomThemeExtension = themeSettings.CustomTheme ? themeSettings.CustomThemeExtension.ToString() : null, CustomThemeCssUrl = themeSettings.CustomTheme && !string.IsNullOrEmpty(themeSettings.CustomThemeCssUrl?.ToString()) - ? themeSettings.CustomThemeCssUrl.ToString() + ? await uriResolver.Resolve(Request.GetAbsoluteRootUri(), themeSettings.CustomThemeCssUrl) : null, LogoUrl = !string.IsNullOrEmpty(themeSettings.LogoUrl?.ToString()) - ? themeSettings.LogoUrl.ToString() + ? await uriResolver.Resolve(Request.GetAbsoluteRootUri(), themeSettings.LogoUrl) : null }); } From 1a28f790fc122ad4221256649d02161a2088e116 Mon Sep 17 00:00:00 2001 From: Kukks Date: Mon, 20 May 2024 11:07:47 +0200 Subject: [PATCH 28/64] use clas --- BTCPayApp.CommonServer/IBTCPayAppHubClient.cs | 11 ++++++++++- BTCPayServer/App/BTCPayAppState.cs | 14 ++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs index aceb05dee..f2065cd6d 100644 --- a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs +++ b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs @@ -6,12 +6,21 @@ using BTCPayServer.Lightning; namespace BTCPayApp.CommonServer; +public class TransactionDetectedRequest +{ + public string Identifier { get; set; } + public string TxId { get; set; } + public string[] SpentScripts { get; set; } + public string[] ReceivedScripts { get; set; } + public bool Confirmed { get; set; } +} + //methods available on the hub in the client public interface IBTCPayAppHubClient { Task NotifyNetwork(string network); - Task TransactionDetected(string identifier, string txId, string[] relatedScripts, bool confirmed); + Task TransactionDetected(TransactionDetectedRequest request); Task NewBlock(string block); Task CreateInvoice(CreateLightningInvoiceRequest createLightningInvoiceRequest); diff --git a/BTCPayServer/App/BTCPayAppState.cs b/BTCPayServer/App/BTCPayAppState.cs index 99c975009..32ab51f39 100644 --- a/BTCPayServer/App/BTCPayAppState.cs +++ b/BTCPayServer/App/BTCPayAppState.cs @@ -67,12 +67,18 @@ public class BTCPayAppState : IHostedService var explorer = _explorerClientProvider.GetExplorerClient(obj.CryptoCode); var expandedTx = await explorer.GetTransactionAsync(obj.NewTransactionEvent.TrackedSource, obj.NewTransactionEvent.TransactionData.TransactionHash); - var scripts = expandedTx.Inputs.Concat(expandedTx.Outputs).Select(output => output.ScriptPubKey.ToHex()).Distinct().ToArray(); - + await _hubContext.Clients .Group(identifier) - .TransactionDetected(identifier, - obj.NewTransactionEvent.TransactionData.TransactionHash.ToString(), scripts, obj.NewTransactionEvent.BlockId is not null && obj.NewTransactionEvent.BlockId != uint256.Zero); + .TransactionDetected(new TransactionDetectedRequest() + { + SpentScripts = expandedTx.Inputs.Select(input => input.ScriptPubKey.ToHex()).ToArray(), + ReceivedScripts = expandedTx.Outputs.Select(output => output.ScriptPubKey.ToHex()).ToArray(), + TxId = obj.NewTransactionEvent.TransactionData.TransactionHash.ToString(), + Confirmed = obj.NewTransactionEvent.BlockId is not null && + obj.NewTransactionEvent.BlockId != uint256.Zero, + Identifier = identifier + }); } private void OnNewBlock(NewBlockEvent obj) From a38553da290a2ec817dc60e34491a8308ddabdd2 Mon Sep 17 00:00:00 2001 From: Kukks Date: Mon, 20 May 2024 13:06:23 +0200 Subject: [PATCH 29/64] expose local ip through nodes --- BTCPayServer.Tests/docker-compose.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/BTCPayServer.Tests/docker-compose.yml b/BTCPayServer.Tests/docker-compose.yml index 360550316..de6924314 100644 --- a/BTCPayServer.Tests/docker-compose.yml +++ b/BTCPayServer.Tests/docker-compose.yml @@ -157,6 +157,7 @@ services: EXPOSE_TCP: "true" LIGHTNINGD_CHAIN: "btc" LIGHTNINGD_NETWORK: "regtest" + LIGHTNINGD_ANNOUNCEADDR: "127.0.0.1:30892" LIGHTNINGD_OPT: | developer bitcoin-datadir=/etc/bitcoin @@ -186,6 +187,7 @@ services: EXPOSE_TCP: "true" LIGHTNINGD_CHAIN: "btc" LIGHTNINGD_NETWORK: "regtest" + LIGHTNINGD_ANNOUNCEADDR: "127.0.0.1:30893" LIGHTNINGD_OPT: | developer bitcoin-datadir=/etc/bitcoin @@ -197,7 +199,7 @@ services: dev-bitcoind-poll=1 ports: - "30993:9835" # api port - - "30893:9735" # api port + - "30893:9735" # server port expose: - "9735" # server port - "9835" # api port @@ -221,6 +223,8 @@ services: restart: unless-stopped environment: LND_CHAIN: "btc" + LND_EXTERNALIP: "127.0.0.1" + LND_PORT: "30894" LND_ENVIRONMENT: "regtest" LND_EXPLORERURL: "http://nbxplorer:32838/" LND_REST_LISTEN_HOST: http://merchant_lnd:8080 @@ -243,6 +247,7 @@ services: ports: - "35531:8080" - "53280:10009" + - "30894:9735" expose: - "8080" - "9735" @@ -258,6 +263,8 @@ services: restart: unless-stopped environment: LND_CHAIN: "btc" + LND_EXTERNALIP: "127.0.0.1" + LND_PORT: "30895" LND_ENVIRONMENT: "regtest" LND_EXPLORERURL: "http://nbxplorer:32838/" LND_REST_LISTEN_HOST: http://customer_lnd:8080 @@ -279,6 +286,7 @@ services: no-rest-tls=1 ports: - "35532:8080" + - "30895:9735" expose: - "8080" - "9735" From 452f2be6c8a039124c9ee94c9d3255609818e65f Mon Sep 17 00:00:00 2001 From: Kukks Date: Mon, 20 May 2024 14:02:44 +0200 Subject: [PATCH 30/64] tell apps the internal node to connect to --- BTCPayApp.CommonServer/IBTCPayAppHubClient.cs | 3 +- BTCPayServer.Tests/docker-compose.yml | 6 ++- BTCPayServer/App/BTCPayAppHub.cs | 4 +- BTCPayServer/App/BTCPayAppState.cs | 49 ++++++++++++++++++- 4 files changed, 55 insertions(+), 7 deletions(-) diff --git a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs index f2065cd6d..beda30c69 100644 --- a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs +++ b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs @@ -20,6 +20,7 @@ public class TransactionDetectedRequest public interface IBTCPayAppHubClient { Task NotifyNetwork(string network); + Task NotifyServerNode(string nodeInfo); Task TransactionDetected(TransactionDetectedRequest request); Task NewBlock(string block); @@ -32,7 +33,7 @@ public interface IBTCPayAppHubClient //methods available on the hub in the server public interface IBTCPayAppHubServer { - Task MasterNodePong(string group, bool active); + Task IdentifierActive(string group, bool active); Task> Pair(PairRequest request); Task Handshake(AppHandshake request); diff --git a/BTCPayServer.Tests/docker-compose.yml b/BTCPayServer.Tests/docker-compose.yml index de6924314..e2b66ccc3 100644 --- a/BTCPayServer.Tests/docker-compose.yml +++ b/BTCPayServer.Tests/docker-compose.yml @@ -157,7 +157,8 @@ services: EXPOSE_TCP: "true" LIGHTNINGD_CHAIN: "btc" LIGHTNINGD_NETWORK: "regtest" - LIGHTNINGD_ANNOUNCEADDR: "127.0.0.1:30892" + LIGHTNINGD_ANNOUNCEADDR: "127.0.0.1" + LIGHTNINGD_PORT: "30892" LIGHTNINGD_OPT: | developer bitcoin-datadir=/etc/bitcoin @@ -187,7 +188,8 @@ services: EXPOSE_TCP: "true" LIGHTNINGD_CHAIN: "btc" LIGHTNINGD_NETWORK: "regtest" - LIGHTNINGD_ANNOUNCEADDR: "127.0.0.1:30893" + LIGHTNINGD_ANNOUNCEADDR: "127.0.0.1" + LIGHTNINGD_PORT: "30893" LIGHTNINGD_OPT: | developer bitcoin-datadir=/etc/bitcoin diff --git a/BTCPayServer/App/BTCPayAppHub.cs b/BTCPayServer/App/BTCPayAppHub.cs index 2fae6d0a5..c67b74a34 100644 --- a/BTCPayServer/App/BTCPayAppHub.cs +++ b/BTCPayServer/App/BTCPayAppHub.cs @@ -266,9 +266,9 @@ var resultPsbt = PSBT.Parse(psbt, explorerClient.Network.NBitcoinNetwork); } - public async Task MasterNodePong(string group, bool active) + public async Task IdentifierActive(string group, bool active) { - await _appState.MasterNodePong(group, Context.ConnectionId, active); + await _appState.IdentifierActive(group, Context.ConnectionId, active); } diff --git a/BTCPayServer/App/BTCPayAppState.cs b/BTCPayServer/App/BTCPayAppState.cs index 32ab51f39..0058920a3 100644 --- a/BTCPayServer/App/BTCPayAppState.cs +++ b/BTCPayServer/App/BTCPayAppState.cs @@ -8,7 +8,10 @@ using System.Threading; using System.Threading.Tasks; using BTCPayApp.CommonServer; using BTCPayServer.Events; +using BTCPayServer.Payments.Lightning; +using BTCPayServer.Services.Invoices; using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using NBitcoin; @@ -25,6 +28,7 @@ public class BTCPayAppState : IHostedService private readonly ExplorerClientProvider _explorerClientProvider; private readonly BTCPayNetworkProvider _networkProvider; private readonly EventAggregator _eventAggregator; + private readonly IServiceProvider _serviceProvider; private CompositeDisposable? _compositeDisposable; public ExplorerClient ExplorerClient { get; private set; } @@ -37,17 +41,19 @@ public class BTCPayAppState : IHostedService ILogger logger, ExplorerClientProvider explorerClientProvider, BTCPayNetworkProvider networkProvider, - EventAggregator eventAggregator) + EventAggregator eventAggregator, IServiceProvider serviceProvider) { _hubContext = hubContext; _logger = logger; _explorerClientProvider = explorerClientProvider; _networkProvider = networkProvider; _eventAggregator = eventAggregator; + _serviceProvider = serviceProvider; } public Task StartAsync(CancellationToken cancellationToken) { + _cts ??= new CancellationTokenSource(); ExplorerClient = _explorerClientProvider.GetExplorerClient("BTC"); _derivationSchemeParser = new DerivationSchemeParser(_networkProvider.BTC); _compositeDisposable = new(); @@ -55,8 +61,44 @@ public class BTCPayAppState : IHostedService _eventAggregator.Subscribe(OnNewBlock)); _compositeDisposable.Add( _eventAggregator.SubscribeAsync(OnNewTransaction)); + _ = UpdateNodeInfo(); return Task.CompletedTask; } + + private string _nodeInfo = string.Empty; + private async Task UpdateNodeInfo() + { + while (!_cts.Token.IsCancellationRequested) + { + + + try + { + var res = await _serviceProvider.GetRequiredService().GetLightningHandler(ExplorerClient.CryptoCode).GetNodeInfo( + new LightningPaymentMethodConfig() {InternalNodeRef = LightningPaymentMethodConfig.InternalNode}, + null, + false, false); + if (res.Any()) + { + var newInf = res.First().ToString(); + if (newInf != _nodeInfo) + { + _nodeInfo = newInf; + await _hubContext.Clients.All.NotifyServerNode(_nodeInfo); + } + + } + + } + catch (Exception e) + { + _logger.LogError(e, "Error during node info update"); + + } + await Task.Delay(TimeSpan.FromMinutes(string.IsNullOrEmpty(_nodeInfo)? 1:5), _cts.Token); + } + + } private async Task OnNewTransaction(NewOnChainTransactionEvent obj) { @@ -137,9 +179,10 @@ public class BTCPayAppState : IHostedService } public readonly ConcurrentDictionary GroupToConnectionId = new(); + private CancellationTokenSource _cts; - public async Task MasterNodePong(string group, string contextConnectionId, bool active) + public async Task IdentifierActive(string group, string contextConnectionId, bool active) { if (active) { @@ -162,6 +205,8 @@ public class BTCPayAppState : IHostedService public async Task Connected(string contextConnectionId) { + if(_nodeInfo.Length > 0) + await _hubContext.Clients.Client(contextConnectionId).NotifyServerNode(_nodeInfo); } public async Task PaymentUpdate(string identifier, LightningPayment lightningPayment) From e42b59bec6c8fddff63f2f569410a4f79d3f8bbd Mon Sep 17 00:00:00 2001 From: Kukks Date: Mon, 20 May 2024 15:57:49 +0200 Subject: [PATCH 31/64] fix compose --- BTCPayServer.Tests/docker-compose.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/BTCPayServer.Tests/docker-compose.yml b/BTCPayServer.Tests/docker-compose.yml index e2b66ccc3..84616558e 100644 --- a/BTCPayServer.Tests/docker-compose.yml +++ b/BTCPayServer.Tests/docker-compose.yml @@ -163,7 +163,6 @@ services: developer bitcoin-datadir=/etc/bitcoin bitcoin-rpcconnect=bitcoind - announce-addr=customer_lightningd:9735 log-level=debug funding-confirms=1 dev-fast-gossip @@ -194,7 +193,6 @@ services: developer bitcoin-datadir=/etc/bitcoin bitcoin-rpcconnect=bitcoind - announce-addr=merchant_lightningd:9735 funding-confirms=1 log-level=debug dev-fast-gossip From 66e8b1211bce3329df38a55c13408da966da444f Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Tue, 21 May 2024 13:24:38 +0200 Subject: [PATCH 32/64] Move Bearer to Greenfield --- .../Constants/AuthenticationSchemes.cs | 4 +- BTCPayServer/App/BTCPayAppHub.cs | 2 +- BTCPayServer/App/BtcPayAppController.cs | 19 +---- .../Security/AuthenticationExtensions.cs | 2 +- .../GreenField/BearerAuthorizationHandler.cs | 70 +++++++++++++++++++ 5 files changed, 77 insertions(+), 20 deletions(-) create mode 100644 BTCPayServer/Security/GreenField/BearerAuthorizationHandler.cs diff --git a/BTCPayServer.Abstractions/Constants/AuthenticationSchemes.cs b/BTCPayServer.Abstractions/Constants/AuthenticationSchemes.cs index b07f95d09..884dd82c1 100644 --- a/BTCPayServer.Abstractions/Constants/AuthenticationSchemes.cs +++ b/BTCPayServer.Abstractions/Constants/AuthenticationSchemes.cs @@ -3,10 +3,10 @@ namespace BTCPayServer.Abstractions.Constants public class AuthenticationSchemes { public const string Cookie = "Identity.Application"; - public const string Bearer = "Identity.Bearer"; public const string Bitpay = "Bitpay"; - public const string Greenfield = "Greenfield.APIKeys,Greenfield.Basic"; + public const string Greenfield = "Greenfield.APIKeys,Greenfield.Basic,Greenfield.Bearer"; public const string GreenfieldAPIKeys = "Greenfield.APIKeys"; public const string GreenfieldBasic = "Greenfield.Basic"; + public const string GreenfieldBearer = "Greenfield.Bearer"; } } diff --git a/BTCPayServer/App/BTCPayAppHub.cs b/BTCPayServer/App/BTCPayAppHub.cs index c67b74a34..5d03713b1 100644 --- a/BTCPayServer/App/BTCPayAppHub.cs +++ b/BTCPayServer/App/BTCPayAppHub.cs @@ -83,7 +83,7 @@ public class BlockHeaders : IEnumerable } } -[Authorize(AuthenticationSchemes = AuthenticationSchemes.Bearer)] +[Authorize(AuthenticationSchemes = AuthenticationSchemes.GreenfieldBearer)] public class BTCPayAppHub : Hub, IBTCPayAppHubServer { private readonly BTCPayNetworkProvider _btcPayNetworkProvider; diff --git a/BTCPayServer/App/BtcPayAppController.cs b/BTCPayServer/App/BtcPayAppController.cs index bcbbeb8f2..c8177f2af 100644 --- a/BTCPayServer/App/BtcPayAppController.cs +++ b/BTCPayServer/App/BtcPayAppController.cs @@ -6,14 +6,9 @@ using BTCPayApp.CommonServer; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Extensions; -using BTCPayServer.Client; -using BTCPayServer.Common; -using BTCPayServer.Controllers; using BTCPayServer.Data; using BTCPayServer.Events; -using BTCPayServer.Security.Greenfield; using BTCPayServer.Services; -using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Authentication.BearerToken; using Microsoft.AspNetCore.Authorization; @@ -23,27 +18,19 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.Data; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; -using NBitcoin; -using NBitcoin.DataEncoders; -using NBXplorer; using NicolasDorier.RateLimits; namespace BTCPayServer.App; [ApiController] -[Authorize(AuthenticationSchemes = AuthenticationSchemes.Bearer)] +[Authorize(AuthenticationSchemes = AuthenticationSchemes.GreenfieldBearer)] [Route("btcpayapp")] public class BtcPayAppController( - APIKeyRepository apiKeyRepository, StoreRepository storeRepository, - BTCPayNetworkProvider btcPayNetworkProvider, - IExplorerClientProvider explorerClientProvider, EventAggregator eventAggregator, SignInManager signInManager, UserManager userManager, TimeProvider timeProvider, - PaymentMethodHandlerDictionary handlers, - IFileService fileService, ISettingsRepository settingsRepository, UriResolver uriResolver, IOptionsMonitor bearerTokenOptions) @@ -130,7 +117,7 @@ public class BtcPayAppController( return TypedResults.Problem(message, statusCode: 401); } - signInManager.AuthenticationScheme = AuthenticationSchemes.Bearer; + signInManager.AuthenticationScheme = AuthenticationSchemes.GreenfieldBearer; var signInResult = await signInManager.PasswordSignInAsync(login.Email, login.Password, true, true); if (signInResult.RequiresTwoFactor) { @@ -154,7 +141,7 @@ public class BtcPayAppController( [RateLimitsFilter(ZoneLimits.Login, Scope = RateLimitsScope.RemoteAddress)] public async Task, UnauthorizedHttpResult, SignInHttpResult, ChallengeHttpResult>> Refresh(RefreshRequest refresh) { - const string scheme = AuthenticationSchemes.Bearer; + const string scheme = AuthenticationSchemes.GreenfieldBearer; var authenticationTicket = bearerTokenOptions.Get(scheme).RefreshTokenProtector.Unprotect(refresh.RefreshToken); var expiresUtc = authenticationTicket?.Properties.ExpiresUtc; diff --git a/BTCPayServer/Security/AuthenticationExtensions.cs b/BTCPayServer/Security/AuthenticationExtensions.cs index 881f3e4b7..b536d8c8f 100644 --- a/BTCPayServer/Security/AuthenticationExtensions.cs +++ b/BTCPayServer/Security/AuthenticationExtensions.cs @@ -16,7 +16,7 @@ namespace BTCPayServer.Security public static AuthenticationBuilder AddBearerAuthentication(this AuthenticationBuilder builder) { - builder.AddBearerToken(AuthenticationSchemes.Bearer, options => + builder.AddBearerToken(AuthenticationSchemes.GreenfieldBearer, options => { options.BearerTokenExpiration = TimeSpan.FromMinutes(30.0); options.RefreshTokenExpiration = TimeSpan.FromDays(3.0); diff --git a/BTCPayServer/Security/GreenField/BearerAuthorizationHandler.cs b/BTCPayServer/Security/GreenField/BearerAuthorizationHandler.cs new file mode 100644 index 000000000..59eccf536 --- /dev/null +++ b/BTCPayServer/Security/GreenField/BearerAuthorizationHandler.cs @@ -0,0 +1,70 @@ +#nullable enable +using System.Linq; +using System.Threading.Tasks; +using BTCPayServer.Client; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using AuthenticationSchemes = BTCPayServer.Abstractions.Constants.AuthenticationSchemes; +using StoreData = BTCPayServer.Client.Models.StoreData; + +namespace BTCPayServer.Security.GreenField; + +public class BearerAuthorizationHandler(IOptionsMonitor identityOptions) + : AuthorizationHandler +{ + //TODO: In the future, we will add these store permissions to actual aspnet roles, and remove this class. + private static readonly PermissionSet _serverAdminRolePermissions = new([Permission.Create(Policies.CanViewStoreSettings)]); + + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PolicyRequirement requirement) + { + if (context.User.Identity?.AuthenticationType != AuthenticationSchemes.GreenfieldBearer) + return; + + var userId = context.User.Claims.FirstOrDefault(c => c.Type == identityOptions.CurrentValue.ClaimsIdentity.UserIdClaimType)?.Value; + if (string.IsNullOrEmpty(userId)) + return; + + StoreData? store = null; + var success = false; + var isAdmin = context.User.IsInRole(Roles.ServerAdmin); + var storeId = context.Resource as string; + var policy = requirement.Policy; + var requiredUnscoped = false; + if (policy.EndsWith(':')) + { + policy = policy[..^1]; + requiredUnscoped = true; + } + + if (Policies.IsServerPolicy(policy) && isAdmin) + { + success = true; + } + else if (Policies.IsUserPolicy(policy) && userId is not null) + { + success = true; + } + else if (Policies.IsStorePolicy(policy)) + { + if (isAdmin && storeId is not null) + { + success = _serverAdminRolePermissions.HasPermission(policy, storeId); + } + + /*if (!success && store?.HasPermission(userId, policy) is true) + { + success = true; + }*/ + + if (!success && store is null && requiredUnscoped) + { + success = true; + } + } + if (success) + { + context.Succeed(requirement); + } + } +} From 4e8975a539ef5dfece53a9658c53325c4f3b9575 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Tue, 21 May 2024 14:51:06 +0200 Subject: [PATCH 33/64] Use original IP and port mappings --- BTCPayServer.Tests/docker-compose.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/BTCPayServer.Tests/docker-compose.yml b/BTCPayServer.Tests/docker-compose.yml index 84616558e..c68be588d 100644 --- a/BTCPayServer.Tests/docker-compose.yml +++ b/BTCPayServer.Tests/docker-compose.yml @@ -157,12 +157,11 @@ services: EXPOSE_TCP: "true" LIGHTNINGD_CHAIN: "btc" LIGHTNINGD_NETWORK: "regtest" - LIGHTNINGD_ANNOUNCEADDR: "127.0.0.1" - LIGHTNINGD_PORT: "30892" LIGHTNINGD_OPT: | developer bitcoin-datadir=/etc/bitcoin bitcoin-rpcconnect=bitcoind + announce-addr=customer_lightningd:9735 log-level=debug funding-confirms=1 dev-fast-gossip @@ -187,12 +186,11 @@ services: EXPOSE_TCP: "true" LIGHTNINGD_CHAIN: "btc" LIGHTNINGD_NETWORK: "regtest" - LIGHTNINGD_ANNOUNCEADDR: "127.0.0.1" - LIGHTNINGD_PORT: "30893" LIGHTNINGD_OPT: | developer bitcoin-datadir=/etc/bitcoin bitcoin-rpcconnect=bitcoind + announce-addr=merchant_lightningd:9735 funding-confirms=1 log-level=debug dev-fast-gossip @@ -223,8 +221,6 @@ services: restart: unless-stopped environment: LND_CHAIN: "btc" - LND_EXTERNALIP: "127.0.0.1" - LND_PORT: "30894" LND_ENVIRONMENT: "regtest" LND_EXPLORERURL: "http://nbxplorer:32838/" LND_REST_LISTEN_HOST: http://merchant_lnd:8080 @@ -263,8 +259,6 @@ services: restart: unless-stopped environment: LND_CHAIN: "btc" - LND_EXTERNALIP: "127.0.0.1" - LND_PORT: "30895" LND_ENVIRONMENT: "regtest" LND_EXPLORERURL: "http://nbxplorer:32838/" LND_REST_LISTEN_HOST: http://customer_lnd:8080 From 6516ed3586354f36c51413ba91a00846d628e0a7 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Tue, 21 May 2024 18:54:17 +0200 Subject: [PATCH 34/64] Parse permissions from claims --- .../GreenField/BearerAuthorizationHandler.cs | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/BTCPayServer/Security/GreenField/BearerAuthorizationHandler.cs b/BTCPayServer/Security/GreenField/BearerAuthorizationHandler.cs index 59eccf536..c20b8e6dd 100644 --- a/BTCPayServer/Security/GreenField/BearerAuthorizationHandler.cs +++ b/BTCPayServer/Security/GreenField/BearerAuthorizationHandler.cs @@ -25,7 +25,7 @@ public class BearerAuthorizationHandler(IOptionsMonitor identit if (string.IsNullOrEmpty(userId)) return; - StoreData? store = null; + var permissionSet = new PermissionSet(); var success = false; var isAdmin = context.User.IsInRole(Roles.ServerAdmin); var storeId = context.Resource as string; @@ -35,29 +35,41 @@ public class BearerAuthorizationHandler(IOptionsMonitor identit { policy = policy[..^1]; requiredUnscoped = true; + storeId = null; + } + + if (!string.IsNullOrEmpty(storeId)) + { + var permissions = context.User.Claims.FirstOrDefault(c => c.Type == storeId)?.Value; + if (!string.IsNullOrEmpty(permissions)) + { + permissionSet = new PermissionSet(permissions.Split(',') + .Select(s => Permission.TryCreatePermission(s, storeId, out var permission) ? permission : null) + .Where(s => s != null).ToArray()); + } } if (Policies.IsServerPolicy(policy) && isAdmin) { success = true; } - else if (Policies.IsUserPolicy(policy) && userId is not null) + else if (Policies.IsUserPolicy(policy) && !string.IsNullOrEmpty(userId)) { success = true; } - else if (Policies.IsStorePolicy(policy)) + else if (Policies.IsStorePolicy(policy) && !string.IsNullOrEmpty(storeId)) { - if (isAdmin && storeId is not null) + if (isAdmin && !string.IsNullOrEmpty(storeId)) { success = _serverAdminRolePermissions.HasPermission(policy, storeId); } - /*if (!success && store?.HasPermission(userId, policy) is true) + if (!success && permissionSet.HasPermission(policy, storeId)) { success = true; - }*/ + } - if (!success && store is null && requiredUnscoped) + if (!success && requiredUnscoped && string.IsNullOrEmpty(storeId)) { success = true; } From 8c0d1fde4589f3246a4b316c8f266129e968a71c Mon Sep 17 00:00:00 2001 From: Kukks Date: Thu, 23 May 2024 14:52:07 +0200 Subject: [PATCH 35/64] fix tx resp format --- BTCPayServer/App/BTCPayAppHub.cs | 2 +- BTCPayServer/App/BTCPayAppState.cs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/BTCPayServer/App/BTCPayAppHub.cs b/BTCPayServer/App/BTCPayAppHub.cs index 5d03713b1..4acde1a11 100644 --- a/BTCPayServer/App/BTCPayAppHub.cs +++ b/BTCPayServer/App/BTCPayAppHub.cs @@ -195,7 +195,7 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer { BlockHash = tx.BlockId?.ToString(), BlockHeight = (int?) tx.Height, - Transaction = tx.Transaction.ToString() + Transaction = tx.Transaction.ToHex() }), Blocks = headersTask.ToDictionary(kv => kv.Key.ToString(), kv => Convert.ToHexString(kv.Value.Result.ToBytes())), BlockHeghts = headerToHeight.ToDictionary(kv => kv.Key.ToString(), kv =>(int) kv.Value!) diff --git a/BTCPayServer/App/BTCPayAppState.cs b/BTCPayServer/App/BTCPayAppState.cs index 0058920a3..6c369845d 100644 --- a/BTCPayServer/App/BTCPayAppState.cs +++ b/BTCPayServer/App/BTCPayAppState.cs @@ -142,6 +142,10 @@ public class BTCPayAppState : IHostedService { try { + if (TrackedSource.TryParse(ts, out var trackedSource, ExplorerClient.Network)) + { + ExplorerClient.Track(trackedSource); + } await _hubContext.Groups.AddToGroupAsync(contextConnectionId, ts); } catch (Exception e) From e1b2bc1a05b82f7aff8589757f0d786d5a1287b5 Mon Sep 17 00:00:00 2001 From: Kukks Date: Fri, 24 May 2024 10:56:08 +0200 Subject: [PATCH 36/64] get onchain txs --- BTCPayApp.CommonServer/IBTCPayAppHubClient.cs | 14 +++++++- BTCPayServer/App/BTCPayAppHub.cs | 34 +++++++++++++++++-- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs index beda30c69..3cec0c4a4 100644 --- a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs +++ b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs @@ -1,7 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Threading.Tasks; using BTCPayServer.Client.Models; using BTCPayServer.Lightning; +using NBitcoin; namespace BTCPayApp.CommonServer; @@ -30,6 +32,15 @@ public interface IBTCPayAppHubClient Task> GetLightningPayments(ListPaymentsParams request); Task> GetLightningInvoices(ListInvoicesParams request); } + +public record TxResp(long Confirmations, long? Height, decimal BalanceChange, DateTimeOffset Timestamp, string TransactionId) +{ + public override string ToString() + { + return $"{{ Confirmations = {Confirmations}, Height = {Height}, BalanceChange = {BalanceChange}, Timestamp = {Timestamp}, TransactionId = {TransactionId} }}"; + } +} + //methods available on the hub in the server public interface IBTCPayAppHubServer { @@ -47,6 +58,7 @@ public interface IBTCPayAppHubServer Task TrackScripts(string identifier, string[] scripts); Task UpdatePsbt(string[] identifiers, string psbt); Task GetUTXOs(string[] identifiers); + Task> GetTransactions(string[] identifiers); Task SendPaymentUpdate(string identifier, LightningPayment lightningPayment); diff --git a/BTCPayServer/App/BTCPayAppHub.cs b/BTCPayServer/App/BTCPayAppHub.cs index 4acde1a11..692679d0c 100644 --- a/BTCPayServer/App/BTCPayAppHub.cs +++ b/BTCPayServer/App/BTCPayAppHub.cs @@ -244,9 +244,11 @@ var resultPsbt = PSBT.Parse(psbt, explorerClient.Network.NBitcoinNetwork); foreach (string identifier in identifiers) { var ts = TrackedSource.Parse(identifier,explorerClient.Network); - if (ts is not DerivationSchemeTrackedSource derivationSchemeTrackedSource) + if (ts is null) + { continue; - var utxos = await explorerClient.GetUTXOsAsync(derivationSchemeTrackedSource.DerivationStrategy); + } + var utxos = await explorerClient.GetUTXOsAsync(ts); result.AddRange(utxos.GetUnspentUTXOs(0).Select(utxo => new CoinResponse() { Identifier = identifier, @@ -254,12 +256,36 @@ var resultPsbt = PSBT.Parse(psbt, explorerClient.Network.NBitcoinNetwork); Script = utxo.ScriptPubKey.ToHex(), Outpoint = utxo.Outpoint.ToString(), Value = utxo.Value.GetValue(_btcPayNetworkProvider.BTC), - Path = utxo.KeyPath.ToString() + Path = utxo.KeyPath?.ToString() })); } return result.ToArray(); } + public async Task> GetTransactions(string[] identifiers) + { + var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC); + var result = new Dictionary(); + foreach (string identifier in identifiers) + { + var ts = TrackedSource.Parse(identifier,explorerClient.Network); + if (ts is null) + { + continue; + } + var txs = await explorerClient.GetTransactionsAsync(ts); + + var items = txs.ConfirmedTransactions.Transactions + .Concat(txs.UnconfirmedTransactions.Transactions) + .Concat(txs.ImmatureTransactions.Transactions) + .Concat(txs.ReplacedTransactions.Transactions) + .Select(tx => new TxResp(tx.Confirmations, tx.Height, tx.BalanceChange.GetValue(_btcPayNetworkProvider.BTC), tx.Timestamp, tx.TransactionId.ToString())).OrderByDescending(arg => arg.Timestamp); + result.Add(identifier,items.ToArray()); + } + + return result; + } + public async Task SendPaymentUpdate(string identifier, LightningPayment lightningPayment) { await _appState.PaymentUpdate(identifier, lightningPayment); @@ -286,3 +312,5 @@ var resultPsbt = PSBT.Parse(psbt, explorerClient.Network.NBitcoinNetwork); return await _appState.Handshake(Context.ConnectionId, request); } } + + From b0e3abe61f8b1462cc1ae8461836b21136612788 Mon Sep 17 00:00:00 2001 From: Kukks Date: Fri, 24 May 2024 12:11:56 +0200 Subject: [PATCH 37/64] impl pay invoice --- BTCPayApp.CommonServer/IBTCPayAppHubClient.cs | 1 + BTCPayServer/App/BTCPayAppState.cs | 9 +++++- BTCPayServer/App/LN.cs | 28 +++++++++++++++---- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs index 3cec0c4a4..b0001d830 100644 --- a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs +++ b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs @@ -31,6 +31,7 @@ public interface IBTCPayAppHubClient Task GetLightningPayment(string paymentHash); Task> GetLightningPayments(ListPaymentsParams request); Task> GetLightningInvoices(ListInvoicesParams request); + Task PayInvoice(string bolt11, long? amountMilliSatoshi); } public record TxResp(long Confirmations, long? Height, decimal BalanceChange, DateTimeOffset Timestamp, string TransactionId) diff --git a/BTCPayServer/App/BTCPayAppState.cs b/BTCPayServer/App/BTCPayAppState.cs index 6c369845d..e7a874641 100644 --- a/BTCPayServer/App/BTCPayAppState.cs +++ b/BTCPayServer/App/BTCPayAppState.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; +using AngleSharp.Dom.Events; using BTCPayApp.CommonServer; using BTCPayServer.Events; using BTCPayServer.Payments.Lightning; @@ -203,10 +204,16 @@ public class BTCPayAppState : IHostedService foreach (var group in GroupToConnectionId.Where(a => a.Value == contextConnectionId).Select(a => a.Key) .ToArray()) { - GroupToConnectionId.TryRemove(group, out _); + if (GroupToConnectionId.TryRemove(group, out _)) + { + GroupRemoved?.Invoke(this, group); + } + } } + public event EventHandler? GroupRemoved; + public async Task Connected(string contextConnectionId) { if(_nodeInfo.Length > 0) diff --git a/BTCPayServer/App/LN.cs b/BTCPayServer/App/LN.cs index 8015389f0..19ee263a9 100644 --- a/BTCPayServer/App/LN.cs +++ b/BTCPayServer/App/LN.cs @@ -162,36 +162,52 @@ public class BTCPayAppLightningClient:ILightningClient public async Task Listen(CancellationToken cancellation = new CancellationToken()) { - return new Listener(_appState, _network); + + return new Listener(_appState, _network, _key); } public class Listener:ILightningInvoiceListener { private readonly BTCPayAppState _btcPayAppState; private readonly Network _network; + private readonly string _key; private readonly Channel _channel = Channel.CreateUnbounded(); + private readonly CancellationTokenSource _cts; - public Listener(BTCPayAppState btcPayAppState, Network network) + public Listener(BTCPayAppState btcPayAppState, Network network, string key) { _btcPayAppState = btcPayAppState; + btcPayAppState.GroupRemoved += BtcPayAppStateOnGroupRemoved; _network = network; + _key = key; + _cts = new CancellationTokenSource(); _btcPayAppState.OnPaymentUpdate += BtcPayAppStateOnOnPaymentUpdate; } + private void BtcPayAppStateOnGroupRemoved(object sender, string e) + { + if(e == _key) + _channel.Writer.Complete(); + } + private void BtcPayAppStateOnOnPaymentUpdate(object sender, (string, LightningPayment) e) { + if(e.Item1 != _key) + return; _channel.Writer.TryWrite(e.Item2); } public void Dispose() { + _cts?.Cancel(); _btcPayAppState.OnPaymentUpdate -= BtcPayAppStateOnOnPaymentUpdate; - _channel.Writer.Complete(); + _btcPayAppState.GroupRemoved -= BtcPayAppStateOnGroupRemoved; + _channel.Writer.TryComplete(); } public async Task WaitInvoice(CancellationToken cancellation) { - return ToLightningInvoice(await _channel.Reader.ReadAsync(cancellation), _network); + return ToLightningInvoice(await _channel.Reader.ReadAsync( CancellationTokenSource.CreateLinkedTokenSource(cancellation, _cts.Token).Token), _network); } } @@ -207,12 +223,12 @@ public class BTCPayAppLightningClient:ILightningClient public async Task Pay(PayInvoiceParams payParams, CancellationToken cancellation = new CancellationToken()) { - throw new NotImplementedException(); + return await Pay(null, payParams, cancellation); } public async Task Pay(string bolt11, PayInvoiceParams payParams, CancellationToken cancellation = new CancellationToken()) { - throw new NotImplementedException(); + return await HubClient.PayInvoice(bolt11, payParams.Amount?.MilliSatoshi); } public async Task Pay(string bolt11, CancellationToken cancellation = new CancellationToken()) From e6fc7f130752d135bff6d2ce47d68aaca885fb2c Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Mon, 27 May 2024 10:49:49 +0200 Subject: [PATCH 38/64] Split API controller --- .../AppApiController.Account.cs} | 40 +------------ BTCPayServer/App/API/AppApiController.cs | 58 +++++++++++++++++++ 2 files changed, 60 insertions(+), 38 deletions(-) rename BTCPayServer/App/{BtcPayAppController.cs => API/AppApiController.Account.cs} (81%) create mode 100644 BTCPayServer/App/API/AppApiController.cs diff --git a/BTCPayServer/App/BtcPayAppController.cs b/BTCPayServer/App/API/AppApiController.Account.cs similarity index 81% rename from BTCPayServer/App/BtcPayAppController.cs rename to BTCPayServer/App/API/AppApiController.Account.cs index c8177f2af..69cbde1bf 100644 --- a/BTCPayServer/App/BtcPayAppController.cs +++ b/BTCPayServer/App/API/AppApiController.Account.cs @@ -20,46 +20,10 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using NicolasDorier.RateLimits; -namespace BTCPayServer.App; +namespace BTCPayServer.App.API; -[ApiController] -[Authorize(AuthenticationSchemes = AuthenticationSchemes.GreenfieldBearer)] -[Route("btcpayapp")] -public class BtcPayAppController( - StoreRepository storeRepository, - EventAggregator eventAggregator, - SignInManager signInManager, - UserManager userManager, - TimeProvider timeProvider, - ISettingsRepository settingsRepository, - UriResolver uriResolver, - IOptionsMonitor bearerTokenOptions) - : Controller +public partial class AppApiController { - [AllowAnonymous] - [HttpGet("instance")] - public async Task, NotFound>> Instance() - { - var serverSettings = await settingsRepository.GetSettingAsync() ?? new ServerSettings(); - var policiesSettings = await settingsRepository.GetSettingAsync() ?? new PoliciesSettings(); - var themeSettings = await settingsRepository.GetSettingAsync() ?? new ThemeSettings(); - - return TypedResults.Ok(new AppInstanceInfo - { - BaseUrl = Request.GetAbsoluteRoot(), - ServerName = serverSettings.ServerName, - ContactUrl = serverSettings.ContactUrl, - RegistrationEnabled = policiesSettings.EnableRegistration, - CustomThemeExtension = themeSettings.CustomTheme ? themeSettings.CustomThemeExtension.ToString() : null, - CustomThemeCssUrl = themeSettings.CustomTheme && !string.IsNullOrEmpty(themeSettings.CustomThemeCssUrl?.ToString()) - ? await uriResolver.Resolve(Request.GetAbsoluteRootUri(), themeSettings.CustomThemeCssUrl) - : null, - LogoUrl = !string.IsNullOrEmpty(themeSettings.LogoUrl?.ToString()) - ? await uriResolver.Resolve(Request.GetAbsoluteRootUri(), themeSettings.LogoUrl) - : null - }); - } - [AllowAnonymous] [HttpPost("register")] [RateLimitsFilter(ZoneLimits.Login, Scope = RateLimitsScope.RemoteAddress)] diff --git a/BTCPayServer/App/API/AppApiController.cs b/BTCPayServer/App/API/AppApiController.cs new file mode 100644 index 000000000..25791d3d9 --- /dev/null +++ b/BTCPayServer/App/API/AppApiController.cs @@ -0,0 +1,58 @@ +#nullable enable +using System; +using System.Threading.Tasks; +using BTCPayApp.CommonServer; +using BTCPayServer.Abstractions.Constants; +using BTCPayServer.Abstractions.Contracts; +using BTCPayServer.Abstractions.Extensions; +using BTCPayServer.Data; +using BTCPayServer.Services; +using BTCPayServer.Services.Stores; +using Microsoft.AspNetCore.Authentication.BearerToken; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; + +namespace BTCPayServer.App.API; + +[ApiController] +[Authorize(AuthenticationSchemes = AuthenticationSchemes.GreenfieldBearer)] +[Route("btcpayapp")] +public partial class AppApiController( + StoreRepository storeRepository, + EventAggregator eventAggregator, + SignInManager signInManager, + UserManager userManager, + TimeProvider timeProvider, + ISettingsRepository settingsRepository, + UriResolver uriResolver, + IOptionsMonitor bearerTokenOptions) + : Controller +{ + [AllowAnonymous] + [HttpGet("instance")] + public async Task, NotFound>> Instance() + { + var serverSettings = await settingsRepository.GetSettingAsync() ?? new ServerSettings(); + var policiesSettings = await settingsRepository.GetSettingAsync() ?? new PoliciesSettings(); + var themeSettings = await settingsRepository.GetSettingAsync() ?? new ThemeSettings(); + + return TypedResults.Ok(new AppInstanceInfo + { + BaseUrl = Request.GetAbsoluteRoot(), + ServerName = serverSettings.ServerName, + ContactUrl = serverSettings.ContactUrl, + RegistrationEnabled = policiesSettings.EnableRegistration, + CustomThemeExtension = themeSettings.CustomTheme ? themeSettings.CustomThemeExtension.ToString() : null, + CustomThemeCssUrl = themeSettings.CustomTheme && !string.IsNullOrEmpty(themeSettings.CustomThemeCssUrl?.ToString()) + ? await uriResolver.Resolve(Request.GetAbsoluteRootUri(), themeSettings.CustomThemeCssUrl) + : null, + LogoUrl = !string.IsNullOrEmpty(themeSettings.LogoUrl?.ToString()) + ? await uriResolver.Resolve(Request.GetAbsoluteRootUri(), themeSettings.LogoUrl) + : null + }); + } +} From 19f33f830c73b47eee94a73c3b831da7851d7f9c Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Mon, 27 May 2024 11:34:40 +0200 Subject: [PATCH 39/64] Move app models --- BTCPayApp.CommonServer/IBTCPayAppHubClient.cs | 1 + BTCPayApp.CommonServer/{ => Models}/AccessTokenResult.cs | 2 +- BTCPayApp.CommonServer/{ => Models}/AppInstanceInfo.cs | 2 +- BTCPayApp.CommonServer/{ => Models}/AppUserInfo.cs | 2 +- BTCPayApp.CommonServer/{ => Models}/SignupRequest.cs | 2 +- BTCPayApp.CommonServer/{ => Models}/SignupResult.cs | 2 +- BTCPayServer/App/API/AppApiController.Account.cs | 1 + BTCPayServer/App/API/AppApiController.cs | 3 ++- BTCPayServer/App/BTCPayAppHub.cs | 1 + BTCPayServer/App/BTCPayAppState.cs | 1 + BTCPayServer/App/LN.cs | 2 +- 11 files changed, 12 insertions(+), 7 deletions(-) rename BTCPayApp.CommonServer/{ => Models}/AccessTokenResult.cs (87%) rename BTCPayApp.CommonServer/{ => Models}/AppInstanceInfo.cs (89%) rename BTCPayApp.CommonServer/{ => Models}/AppUserInfo.cs (97%) rename BTCPayApp.CommonServer/{ => Models}/SignupRequest.cs (82%) rename BTCPayApp.CommonServer/{ => Models}/SignupResult.cs (80%) diff --git a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs index b0001d830..12293740f 100644 --- a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs +++ b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using BTCPayServer.Client.Models; using BTCPayServer.Lightning; using NBitcoin; +using LightningPayment = BTCPayApp.CommonServer.Models.LightningPayment; namespace BTCPayApp.CommonServer; diff --git a/BTCPayApp.CommonServer/AccessTokenResult.cs b/BTCPayApp.CommonServer/Models/AccessTokenResult.cs similarity index 87% rename from BTCPayApp.CommonServer/AccessTokenResult.cs rename to BTCPayApp.CommonServer/Models/AccessTokenResult.cs index d54a170aa..926f19bfa 100644 --- a/BTCPayApp.CommonServer/AccessTokenResult.cs +++ b/BTCPayApp.CommonServer/Models/AccessTokenResult.cs @@ -1,6 +1,6 @@ using System; -namespace BTCPayApp.CommonServer; +namespace BTCPayApp.CommonServer.Models; public class AccessTokenResult(string accessToken, string refreshToken, DateTimeOffset expiry) { diff --git a/BTCPayApp.CommonServer/AppInstanceInfo.cs b/BTCPayApp.CommonServer/Models/AppInstanceInfo.cs similarity index 89% rename from BTCPayApp.CommonServer/AppInstanceInfo.cs rename to BTCPayApp.CommonServer/Models/AppInstanceInfo.cs index f9db7c444..e6663f56b 100644 --- a/BTCPayApp.CommonServer/AppInstanceInfo.cs +++ b/BTCPayApp.CommonServer/Models/AppInstanceInfo.cs @@ -1,4 +1,4 @@ -namespace BTCPayApp.CommonServer; +namespace BTCPayApp.CommonServer.Models; public class AppInstanceInfo { diff --git a/BTCPayApp.CommonServer/AppUserInfo.cs b/BTCPayApp.CommonServer/Models/AppUserInfo.cs similarity index 97% rename from BTCPayApp.CommonServer/AppUserInfo.cs rename to BTCPayApp.CommonServer/Models/AppUserInfo.cs index 1de28514f..cae16d7b5 100644 --- a/BTCPayApp.CommonServer/AppUserInfo.cs +++ b/BTCPayApp.CommonServer/Models/AppUserInfo.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using BTCPayServer.Lightning; -namespace BTCPayApp.CommonServer; +namespace BTCPayApp.CommonServer.Models; public partial class LightningPayment { diff --git a/BTCPayApp.CommonServer/SignupRequest.cs b/BTCPayApp.CommonServer/Models/SignupRequest.cs similarity index 82% rename from BTCPayApp.CommonServer/SignupRequest.cs rename to BTCPayApp.CommonServer/Models/SignupRequest.cs index 241d91690..ddb83e15f 100644 --- a/BTCPayApp.CommonServer/SignupRequest.cs +++ b/BTCPayApp.CommonServer/Models/SignupRequest.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace BTCPayApp.CommonServer; +namespace BTCPayApp.CommonServer.Models; public class SignupRequest { diff --git a/BTCPayApp.CommonServer/SignupResult.cs b/BTCPayApp.CommonServer/Models/SignupResult.cs similarity index 80% rename from BTCPayApp.CommonServer/SignupResult.cs rename to BTCPayApp.CommonServer/Models/SignupResult.cs index 174dbc067..5f37a7c48 100644 --- a/BTCPayApp.CommonServer/SignupResult.cs +++ b/BTCPayApp.CommonServer/Models/SignupResult.cs @@ -1,4 +1,4 @@ -namespace BTCPayApp.CommonServer; +namespace BTCPayApp.CommonServer.Models; public class SignupResult { diff --git a/BTCPayServer/App/API/AppApiController.Account.cs b/BTCPayServer/App/API/AppApiController.Account.cs index 69cbde1bf..8a3763549 100644 --- a/BTCPayServer/App/API/AppApiController.Account.cs +++ b/BTCPayServer/App/API/AppApiController.Account.cs @@ -3,6 +3,7 @@ using System; using System.Linq; using System.Threading.Tasks; using BTCPayApp.CommonServer; +using BTCPayApp.CommonServer.Models; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Extensions; diff --git a/BTCPayServer/App/API/AppApiController.cs b/BTCPayServer/App/API/AppApiController.cs index 25791d3d9..ae820798b 100644 --- a/BTCPayServer/App/API/AppApiController.cs +++ b/BTCPayServer/App/API/AppApiController.cs @@ -1,7 +1,8 @@ -#nullable enable +#nullable enable using System; using System.Threading.Tasks; using BTCPayApp.CommonServer; +using BTCPayApp.CommonServer.Models; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Extensions; diff --git a/BTCPayServer/App/BTCPayAppHub.cs b/BTCPayServer/App/BTCPayAppHub.cs index 692679d0c..b6a6f4f3c 100644 --- a/BTCPayServer/App/BTCPayAppHub.cs +++ b/BTCPayServer/App/BTCPayAppHub.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using BTCPayApp.CommonServer; +using BTCPayApp.CommonServer.Models; using BTCPayServer.Abstractions.Constants; using BTCPayServer.HostedServices; using BTCPayServer.Services; diff --git a/BTCPayServer/App/BTCPayAppState.cs b/BTCPayServer/App/BTCPayAppState.cs index e7a874641..705563332 100644 --- a/BTCPayServer/App/BTCPayAppState.cs +++ b/BTCPayServer/App/BTCPayAppState.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using AngleSharp.Dom.Events; using BTCPayApp.CommonServer; +using BTCPayApp.CommonServer.Models; using BTCPayServer.Events; using BTCPayServer.Payments.Lightning; using BTCPayServer.Services.Invoices; diff --git a/BTCPayServer/App/LN.cs b/BTCPayServer/App/LN.cs index 19ee263a9..2387a5def 100644 --- a/BTCPayServer/App/LN.cs +++ b/BTCPayServer/App/LN.cs @@ -10,7 +10,7 @@ using BTCPayServer.Controllers; using BTCPayServer.Lightning; using Microsoft.AspNetCore.SignalR; using NBitcoin; -using LightningPayment = BTCPayApp.CommonServer.LightningPayment; +using LightningPayment = BTCPayApp.CommonServer.Models.LightningPayment; namespace BTCPayServer.App; From 8450d882f981e1040292974c52b7959d2b4ebcbe Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Mon, 27 May 2024 12:37:53 +0200 Subject: [PATCH 40/64] Add create store data --- .../Models/CreateStoreData.cs | 10 ++++++ .../App/API/AppApiController.Account.cs | 22 ++++++------- .../App/API/AppApiController.Store.cs | 32 +++++++++++++++++++ BTCPayServer/App/API/AppApiController.cs | 5 ++- 4 files changed, 56 insertions(+), 13 deletions(-) create mode 100644 BTCPayApp.CommonServer/Models/CreateStoreData.cs create mode 100644 BTCPayServer/App/API/AppApiController.Store.cs diff --git a/BTCPayApp.CommonServer/Models/CreateStoreData.cs b/BTCPayApp.CommonServer/Models/CreateStoreData.cs new file mode 100644 index 000000000..43ae45d59 --- /dev/null +++ b/BTCPayApp.CommonServer/Models/CreateStoreData.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace BTCPayApp.CommonServer.Models; + +public class CreateStoreData +{ + public string? DefaultCurrency { get; set; } + public string? RecommendedExchangeId { get; set; } + public Dictionary? Exchanges { get; set; } +} diff --git a/BTCPayServer/App/API/AppApiController.Account.cs b/BTCPayServer/App/API/AppApiController.Account.cs index 8a3763549..5bbeee6eb 100644 --- a/BTCPayServer/App/API/AppApiController.Account.cs +++ b/BTCPayServer/App/API/AppApiController.Account.cs @@ -2,15 +2,13 @@ using System; using System.Linq; using System.Threading.Tasks; -using BTCPayApp.CommonServer; using BTCPayApp.CommonServer.Models; using BTCPayServer.Abstractions.Constants; -using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Extensions; +using BTCPayServer.Client.Models; using BTCPayServer.Data; using BTCPayServer.Events; using BTCPayServer.Services; -using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Authentication.BearerToken; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -18,7 +16,6 @@ using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.Data; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; using NicolasDorier.RateLimits; namespace BTCPayServer.App.API; @@ -28,11 +25,11 @@ public partial class AppApiController [AllowAnonymous] [HttpPost("register")] [RateLimitsFilter(ZoneLimits.Login, Scope = RateLimitsScope.RemoteAddress)] - public async Task, ValidationProblem, ProblemHttpResult>> Register(SignupRequest signup) + public async Task Register(SignupRequest signup) { var policiesSettings = await settingsRepository.GetSettingAsync() ?? new PoliciesSettings(); if (policiesSettings.LockSubscription) - return TypedResults.Problem("This instance does not allow public user registration", statusCode: 406); + return this.CreateAPIError("unauthorized", "This instance does not allow public user registration"); var errorMessage = "Invalid signup attempt."; if (ModelState.IsValid) @@ -60,11 +57,12 @@ public partial class AppApiController RequiresConfirmedEmail = policiesSettings.RequiresConfirmedEmail && !user.EmailConfirmed, RequiresUserApproval = policiesSettings.RequiresUserApproval && !user.Approved }; - return TypedResults.Ok(response); + return Ok(response); } errorMessage = result.ToString(); } - return TypedResults.Problem(errorMessage, statusCode: 400); + + return this.CreateAPIError(null, errorMessage); } [AllowAnonymous] @@ -98,6 +96,7 @@ public partial class AppApiController ? TypedResults.Empty : TypedResults.Problem(signInResult.ToString(), statusCode: 401); } + return TypedResults.Problem(errorMessage, statusCode: 401); } @@ -187,12 +186,12 @@ public partial class AppApiController [AllowAnonymous] [HttpPost("reset-password")] - public async Task SetPassword(ResetPasswordRequest resetRequest) + public async Task SetPassword(ResetPasswordRequest resetRequest) { var user = await userManager.FindByEmailAsync(resetRequest.Email); if (!UserService.TryCanLogin(user, out _)) { - return TypedResults.Problem("Invalid account", statusCode: 401); + return Unauthorized(new GreenfieldAPIError(null, "Invalid account")); } IdentityResult result; @@ -204,7 +203,6 @@ public partial class AppApiController { result = IdentityResult.Failed(userManager.ErrorDescriber.InvalidToken()); } - return result.Succeeded ? TypedResults.Ok() : TypedResults.Problem(result.ToString().Split(": ").Last(), statusCode: 401); + return result.Succeeded ? Ok() : this.CreateAPIError(401, "unauthorized", result.ToString().Split(": ").Last()); } - } diff --git a/BTCPayServer/App/API/AppApiController.Store.cs b/BTCPayServer/App/API/AppApiController.Store.cs new file mode 100644 index 000000000..f84e66f0a --- /dev/null +++ b/BTCPayServer/App/API/AppApiController.Store.cs @@ -0,0 +1,32 @@ +#nullable enable +using System; +using System.Linq; +using System.Threading.Tasks; +using BTCPayApp.CommonServer.Models; +using BTCPayServer.Data; +using BTCPayServer.Services; +using Microsoft.AspNetCore.Mvc; + +namespace BTCPayServer.App.API; + +public partial class AppApiController +{ + [HttpGet("create-store")] + public async Task CreateStore() + { + var defaultCurrency = (await settingsRepository.GetSettingAsync())?.DefaultCurrency ?? StoreBlob.StandardDefaultCurrency; + var defaultExchange = defaultRules.GetRecommendedExchange(defaultCurrency); + var exchanges = rateFactory.RateProviderFactory + .AvailableRateProviders + .OrderBy(s => s.Id, StringComparer.OrdinalIgnoreCase) + .ToDictionary(k => k.Id, k => k.DisplayName); + var recommendation = exchanges.First(e => e.Key == defaultExchange); + + return Ok(new CreateStoreData + { + DefaultCurrency = defaultCurrency, + Exchanges = exchanges, + RecommendedExchangeId = recommendation.Key + }); + } +} diff --git a/BTCPayServer/App/API/AppApiController.cs b/BTCPayServer/App/API/AppApiController.cs index ae820798b..97b36c23b 100644 --- a/BTCPayServer/App/API/AppApiController.cs +++ b/BTCPayServer/App/API/AppApiController.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System; using System.Threading.Tasks; using BTCPayApp.CommonServer; @@ -8,6 +8,7 @@ using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Data; using BTCPayServer.Services; +using BTCPayServer.Services.Rates; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Authentication.BearerToken; using Microsoft.AspNetCore.Authorization; @@ -30,6 +31,8 @@ public partial class AppApiController( TimeProvider timeProvider, ISettingsRepository settingsRepository, UriResolver uriResolver, + DefaultRulesCollection defaultRules, + RateFetcher rateFactory, IOptionsMonitor bearerTokenOptions) : Controller { From 78a31b74c790a7273c27aba979b299c6e68147c9 Mon Sep 17 00:00:00 2001 From: Kukks Date: Tue, 28 May 2024 12:29:39 +0200 Subject: [PATCH 41/64] mak e regtest send a hacked but correct node info to app --- BTCPayServer/App/BTCPayAppState.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/BTCPayServer/App/BTCPayAppState.cs b/BTCPayServer/App/BTCPayAppState.cs index 705563332..26bd666af 100644 --- a/BTCPayServer/App/BTCPayAppState.cs +++ b/BTCPayServer/App/BTCPayAppState.cs @@ -8,8 +8,8 @@ using System.Threading; using System.Threading.Tasks; using AngleSharp.Dom.Events; using BTCPayApp.CommonServer; -using BTCPayApp.CommonServer.Models; using BTCPayServer.Events; +using BTCPayServer.Lightning; using BTCPayServer.Payments.Lightning; using BTCPayServer.Services.Invoices; using Microsoft.AspNetCore.SignalR; @@ -19,6 +19,7 @@ using Microsoft.Extensions.Logging; using NBitcoin; using NBXplorer; using NBXplorer.Models; +using LightningPayment = BTCPayApp.CommonServer.Models.LightningPayment; using NewBlockEvent = BTCPayServer.Events.NewBlockEvent; namespace BTCPayServer.Controllers; @@ -82,10 +83,14 @@ public class BTCPayAppState : IHostedService false, false); if (res.Any()) { - var newInf = res.First().ToString(); - if (newInf != _nodeInfo) + var newInf = res.First(); + if (_networkProvider.NetworkType == ChainName.Regtest) { - _nodeInfo = newInf; + newInf = new NodeInfo(newInf.NodeId, "127.0.0.1", 30893); + } + if (newInf.ToString() != _nodeInfo) + { + _nodeInfo = newInf.ToString(); await _hubContext.Clients.All.NotifyServerNode(_nodeInfo); } From c95a2552d0cd2519b95866247f464b67c60e9ff1 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Wed, 29 May 2024 13:40:04 +0200 Subject: [PATCH 42/64] Associate keypad POS with store --- BTCPayApp.CommonServer/Models/AppUserInfo.cs | 1 + .../App/API/AppApiController.Account.cs | 40 ++++++++++++------- BTCPayServer/App/API/AppApiController.cs | 2 + 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/BTCPayApp.CommonServer/Models/AppUserInfo.cs b/BTCPayApp.CommonServer/Models/AppUserInfo.cs index cae16d7b5..ca56a5e2f 100644 --- a/BTCPayApp.CommonServer/Models/AppUserInfo.cs +++ b/BTCPayApp.CommonServer/Models/AppUserInfo.cs @@ -41,6 +41,7 @@ public class AppUserStoreInfo public string? Id { get; set; } public string? Name { get; set; } public string? RoleId { get; set; } + public string? PosAppId { get; set; } public bool Archived { get; set; } public IEnumerable? Permissions { get; set; } } diff --git a/BTCPayServer/App/API/AppApiController.Account.cs b/BTCPayServer/App/API/AppApiController.Account.cs index 5bbeee6eb..6bad758d5 100644 --- a/BTCPayServer/App/API/AppApiController.Account.cs +++ b/BTCPayServer/App/API/AppApiController.Account.cs @@ -1,5 +1,6 @@ #nullable enable using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using BTCPayApp.CommonServer.Models; @@ -8,7 +9,9 @@ using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Client.Models; using BTCPayServer.Data; using BTCPayServer.Events; +using BTCPayServer.Plugins.PointOfSale; using BTCPayServer.Services; +using BTCPayServer.Services.Apps; using Microsoft.AspNetCore.Authentication.BearerToken; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -17,6 +20,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.Data; using Microsoft.AspNetCore.Mvc; using NicolasDorier.RateLimits; +using PosViewType = BTCPayServer.Plugins.PointOfSale.PosViewType; namespace BTCPayServer.App.API; @@ -143,28 +147,36 @@ public partial class AppApiController } [HttpGet("user")] - public async Task, NotFound>> UserInfo() + public async Task UserInfo() { var user = await userManager.GetUserAsync(User); - if (user == null) return TypedResults.NotFound(); + if (user == null) return NotFound(); var userStores = await storeRepository.GetStoresByUserId(user.Id); - return TypedResults.Ok(new AppUserInfo + var stores = new List(); + foreach (var store in userStores) + { + var userStore = store.UserStores.Find(us => us.ApplicationUserId == user.Id && us.StoreDataId == store.Id)!; + var apps = await appService.GetAllApps(user.Id, false, store.Id); + var posApp = apps.FirstOrDefault(app => app.AppType == PointOfSaleAppType.AppType && app.App.GetSettings().DefaultView == PosViewType.Light); + stores.Add(new AppUserStoreInfo + { + Id = store.Id, + Name = store.StoreName, + Archived = store.Archived, + RoleId = userStore.StoreRole.Id, + PosAppId = posApp?.Id, + Permissions = userStore.StoreRole.Permissions + }); + } + var info = new AppUserInfo { UserId = user.Id, Email = await userManager.GetEmailAsync(user), Roles = await userManager.GetRolesAsync(user), - Stores = (from store in userStores - let userStore = store.UserStores.Find(us => us.ApplicationUserId == user.Id && us.StoreDataId == store.Id)! - select new AppUserStoreInfo - { - Id = store.Id, - Name = store.StoreName, - Archived = store.Archived, - RoleId = userStore.StoreRole.Id, - Permissions = userStore.StoreRole.Permissions - }).ToList() - }); + Stores = stores + }; + return Ok(info); } [AllowAnonymous] diff --git a/BTCPayServer/App/API/AppApiController.cs b/BTCPayServer/App/API/AppApiController.cs index 97b36c23b..4dd760069 100644 --- a/BTCPayServer/App/API/AppApiController.cs +++ b/BTCPayServer/App/API/AppApiController.cs @@ -8,6 +8,7 @@ using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Data; using BTCPayServer.Services; +using BTCPayServer.Services.Apps; using BTCPayServer.Services.Rates; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Authentication.BearerToken; @@ -25,6 +26,7 @@ namespace BTCPayServer.App.API; [Route("btcpayapp")] public partial class AppApiController( StoreRepository storeRepository, + AppService appService, EventAggregator eventAggregator, SignInManager signInManager, UserManager userManager, From 13a7e9be0a972b9ad4dd8f837344f949dc0cce5b Mon Sep 17 00:00:00 2001 From: Kukks Date: Wed, 29 May 2024 15:01:58 +0200 Subject: [PATCH 43/64] add logs --- BTCPayServer/App/BTCPayAppHub.cs | 9 ++++++++- BTCPayServer/App/BTCPayAppState.cs | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/BTCPayServer/App/BTCPayAppHub.cs b/BTCPayServer/App/BTCPayAppHub.cs index b6a6f4f3c..e185f3946 100644 --- a/BTCPayServer/App/BTCPayAppHub.cs +++ b/BTCPayServer/App/BTCPayAppHub.cs @@ -12,6 +12,7 @@ using BTCPayServer.Services; using BTCPayServer.Services.Wallets; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.Logging; using NBitcoin; using NBXplorer.DerivationStrategy; using NBXplorer.Models; @@ -92,18 +93,21 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer private readonly BTCPayAppState _appState; private readonly ExplorerClientProvider _explorerClientProvider; private readonly IFeeProviderFactory _feeProviderFactory; + private readonly ILogger _logger; public BTCPayAppHub(BTCPayNetworkProvider btcPayNetworkProvider, NBXplorerDashboard nbXplorerDashboard, BTCPayAppState appState, ExplorerClientProvider explorerClientProvider, - IFeeProviderFactory feeProviderFactory) + IFeeProviderFactory feeProviderFactory, + ILogger logger) { _btcPayNetworkProvider = btcPayNetworkProvider; _nbXplorerDashboard = nbXplorerDashboard; _appState = appState; _explorerClientProvider = explorerClientProvider; _feeProviderFactory = feeProviderFactory; + _logger = logger; } @@ -213,11 +217,14 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer public async Task TrackScripts(string identifier, string[] scripts) { + _logger.LogInformation($"Tracking {scripts.Length} scripts for {identifier}"); var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC); var ts = TrackedSource.Parse(identifier,explorerClient.Network ) as GroupTrackedSource; var s = scripts.Select(Script.FromHex).Select(script => script.GetDestinationAddress(explorerClient.Network.NBitcoinNetwork)).Select(address => address.ToString()).ToArray(); await explorerClient.AddGroupAddressAsync(explorerClient.CryptoCode,ts.GroupId, s); + + _logger.LogInformation($"Tracking {scripts.Length} scripts for {identifier} done "); } public async Task UpdatePsbt(string[] identifiers, string psbt) diff --git a/BTCPayServer/App/BTCPayAppState.cs b/BTCPayServer/App/BTCPayAppState.cs index 26bd666af..049d27b25 100644 --- a/BTCPayServer/App/BTCPayAppState.cs +++ b/BTCPayServer/App/BTCPayAppState.cs @@ -228,6 +228,7 @@ public class BTCPayAppState : IHostedService public async Task PaymentUpdate(string identifier, LightningPayment lightningPayment) { + _logger.LogInformation("Payment update for {identifier} {}", identifier); OnPaymentUpdate?.Invoke(this, (identifier, lightningPayment)); } From 5ca19879b6018b1b986b2922ce0975bcba601794 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Fri, 31 May 2024 11:04:29 +0200 Subject: [PATCH 44/64] Add user profile info --- BTCPayApp.CommonServer/Models/AppUserInfo.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/BTCPayApp.CommonServer/Models/AppUserInfo.cs b/BTCPayApp.CommonServer/Models/AppUserInfo.cs index ca56a5e2f..e50896413 100644 --- a/BTCPayApp.CommonServer/Models/AppUserInfo.cs +++ b/BTCPayApp.CommonServer/Models/AppUserInfo.cs @@ -23,6 +23,8 @@ public class AppUserInfo { public string? UserId { get; set; } public string? Email { get; set; } + public string? Name { get; set; } + public string? ImageUrl { get; set; } public IEnumerable? Roles { get; set; } public IEnumerable? Stores { get; set; } @@ -32,7 +34,9 @@ public class AppUserInfo if (ReferenceEquals(x, null)) return false; if (ReferenceEquals(y, null)) return false; if (x.GetType() != y.GetType()) return false; - return x.UserId == y.UserId && x.Email == y.Email && Equals(x.Roles, y.Roles) && Equals(x.Stores, y.Stores); + return x.UserId == y.UserId && x.Email == y.Email && + x.Name == y.Name && x.ImageUrl == y.ImageUrl && + Equals(x.Roles, y.Roles) && Equals(x.Stores, y.Stores); } } From 3218db5815e3b811ff946c113ab32d61c3ecf06f Mon Sep 17 00:00:00 2001 From: Kukks Date: Wed, 5 Jun 2024 09:14:31 +0200 Subject: [PATCH 45/64] return lock --- BTCPayApp.CommonServer/IBTCPayAppHubClient.cs | 2 +- BTCPayServer/App/BTCPayAppHub.cs | 5 +++-- BTCPayServer/App/BTCPayAppState.cs | 12 +++++++++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs index 12293740f..a1eb01295 100644 --- a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs +++ b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs @@ -46,7 +46,7 @@ public record TxResp(long Confirmations, long? Height, decimal BalanceChange, Da //methods available on the hub in the server public interface IBTCPayAppHubServer { - Task IdentifierActive(string group, bool active); + Task IdentifierActive(string group, bool active); Task> Pair(PairRequest request); Task Handshake(AppHandshake request); diff --git a/BTCPayServer/App/BTCPayAppHub.cs b/BTCPayServer/App/BTCPayAppHub.cs index e185f3946..61f89dce8 100644 --- a/BTCPayServer/App/BTCPayAppHub.cs +++ b/BTCPayServer/App/BTCPayAppHub.cs @@ -300,9 +300,10 @@ var resultPsbt = PSBT.Parse(psbt, explorerClient.Network.NBitcoinNetwork); } - public async Task IdentifierActive(string group, bool active) + public async Task IdentifierActive(string group, bool active) { - await _appState.IdentifierActive(group, Context.ConnectionId, active); + + return await _appState.IdentifierActive(group, Context.ConnectionId, active); } diff --git a/BTCPayServer/App/BTCPayAppState.cs b/BTCPayServer/App/BTCPayAppState.cs index 049d27b25..372b215a8 100644 --- a/BTCPayServer/App/BTCPayAppState.cs +++ b/BTCPayServer/App/BTCPayAppState.cs @@ -151,6 +151,11 @@ public class BTCPayAppState : IHostedService { if (TrackedSource.TryParse(ts, out var trackedSource, ExplorerClient.Network)) { + if (trackedSource is GroupTrackedSource groupTrackedSource) + { + + } + ExplorerClient.Track(trackedSource); } await _hubContext.Groups.AddToGroupAsync(contextConnectionId, ts); @@ -193,16 +198,17 @@ public class BTCPayAppState : IHostedService private CancellationTokenSource _cts; - public async Task IdentifierActive(string group, string contextConnectionId, bool active) + public async Task IdentifierActive(string group, string contextConnectionId, bool active) { if (active) { - GroupToConnectionId.AddOrUpdate(group, contextConnectionId, (a, b) => contextConnectionId); + return GroupToConnectionId.TryAdd(group, contextConnectionId); } else if (GroupToConnectionId.TryGetValue(group, out var connId) && connId == contextConnectionId) { - GroupToConnectionId.TryRemove(group, out _); + return GroupToConnectionId.TryRemove(group, out _); } + return false; } public async Task Disconnected(string contextConnectionId) From 0a50f7dba763924f9672b23aed01e6dd278fa585 Mon Sep 17 00:00:00 2001 From: Kukks Date: Wed, 5 Jun 2024 12:34:44 +0200 Subject: [PATCH 46/64] start ln listener immediately to app nodes --- BTCPayServer/App/BTCPayAppHub.cs | 4 ++-- BTCPayServer/App/BTCPayAppState.cs | 12 +++++++++--- BTCPayServer/App/LN.cs | 12 ++++++------ BTCPayServer/Hosting/BTCPayServerServices.cs | 3 ++- .../Payments/Lightning/LightningListener.cs | 17 +++++++++++++---- 5 files changed, 32 insertions(+), 16 deletions(-) diff --git a/BTCPayServer/App/BTCPayAppHub.cs b/BTCPayServer/App/BTCPayAppHub.cs index 61f89dce8..341cc62eb 100644 --- a/BTCPayServer/App/BTCPayAppHub.cs +++ b/BTCPayServer/App/BTCPayAppHub.cs @@ -170,7 +170,7 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC); var bh = await explorerClient.RPCClient.GetBlockHeaderAsync(uint256.Parse(hash)); - return Convert.ToHexString(bh.ToBytes()); + return Convert.ToHexString(bh.ToBytes()).ToLower(); } public async Task FetchTxsAndTheirBlockHeads(string[] txIds) @@ -202,7 +202,7 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer BlockHeight = (int?) tx.Height, Transaction = tx.Transaction.ToHex() }), - Blocks = headersTask.ToDictionary(kv => kv.Key.ToString(), kv => Convert.ToHexString(kv.Value.Result.ToBytes())), + Blocks = headersTask.ToDictionary(kv => kv.Key.ToString(), kv => Convert.ToHexString(kv.Value.Result.ToBytes()).ToLower()), BlockHeghts = headerToHeight.ToDictionary(kv => kv.Key.ToString(), kv =>(int) kv.Value!) }; } diff --git a/BTCPayServer/App/BTCPayAppState.cs b/BTCPayServer/App/BTCPayAppState.cs index 372b215a8..b5e1b66ea 100644 --- a/BTCPayServer/App/BTCPayAppState.cs +++ b/BTCPayServer/App/BTCPayAppState.cs @@ -194,7 +194,7 @@ public class BTCPayAppState : IHostedService return result; } - public readonly ConcurrentDictionary GroupToConnectionId = new(); + public readonly ConcurrentDictionary GroupToConnectionId = new(StringComparer.InvariantCultureIgnoreCase); private CancellationTokenSource _cts; @@ -202,7 +202,13 @@ public class BTCPayAppState : IHostedService { if (active) { - return GroupToConnectionId.TryAdd(group, contextConnectionId); + if (GroupToConnectionId.TryAdd(group, contextConnectionId)) + { + var connString ="type=app;group=" + group; + _serviceProvider.GetService()?.CheckConnection(ExplorerClient.CryptoCode, connString); + return true; + } + return false; } else if (GroupToConnectionId.TryGetValue(group, out var connId) && connId == contextConnectionId) { @@ -234,7 +240,7 @@ public class BTCPayAppState : IHostedService public async Task PaymentUpdate(string identifier, LightningPayment lightningPayment) { - _logger.LogInformation("Payment update for {identifier} {}", identifier); + _logger.LogInformation($"Payment update for {identifier} {lightningPayment.Value} {lightningPayment.PaymentHash}"); OnPaymentUpdate?.Invoke(this, (identifier, lightningPayment)); } diff --git a/BTCPayServer/App/LN.cs b/BTCPayServer/App/LN.cs index 2387a5def..7602affe6 100644 --- a/BTCPayServer/App/LN.cs +++ b/BTCPayServer/App/LN.cs @@ -71,7 +71,7 @@ public class BTCPayAppLightningClient:ILightningClient public override string ToString() { - return $"type=app;group={_key}"; + return $"type=app;group={_key}".ToLower(); } public IBTCPayAppHubClient HubClient => _appState.GroupToConnectionId.TryGetValue(_key, out var connId) ? _hubContext.Clients.Client(connId) : throw new InvalidOperationException("Connection not found"); @@ -85,7 +85,7 @@ public class BTCPayAppLightningClient:ILightningClient public async Task GetInvoice(uint256 paymentHash, CancellationToken cancellation = new CancellationToken()) { var lp = await HubClient.GetLightningInvoice(paymentHash.ToString()); - return ToLightningInvoice(lp, _network); + return lp is null ? null : ToLightningInvoice(lp, _network); } public async Task ListInvoices(CancellationToken cancellation = new CancellationToken()) @@ -155,6 +155,7 @@ public class BTCPayAppLightningClient:ILightningClient Amount = LightMoney.MilliSatoshis(lightningPayment.Value), PaymentHash = lightningPayment.PaymentHash, Preimage = lightningPayment.Preimage, + PaidAt = lightningPayment.Status == LightningPaymentStatus.Complete? DateTimeOffset.UtcNow: null, //TODO: store these in ln payment BOLT11 = lightningPayment.PaymentRequests.FirstOrDefault(), Status = lightningPayment.Status == LightningPaymentStatus.Complete? LightningInvoiceStatus.Paid: paymenRequest.ExpiryDate < DateTimeOffset.UtcNow? LightningInvoiceStatus.Expired: LightningInvoiceStatus.Unpaid }; @@ -162,7 +163,6 @@ public class BTCPayAppLightningClient:ILightningClient public async Task Listen(CancellationToken cancellation = new CancellationToken()) { - return new Listener(_appState, _network, _key); } @@ -192,9 +192,9 @@ public class BTCPayAppLightningClient:ILightningClient private void BtcPayAppStateOnOnPaymentUpdate(object sender, (string, LightningPayment) e) { - if(e.Item1 != _key) - return; - _channel.Writer.TryWrite(e.Item2); + if (e.Item1.Equals(_key, StringComparison.InvariantCultureIgnoreCase)) + _channel.Writer.TryWrite(e.Item2); + } public void Dispose() diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index 4475e7be4..bde3b4e94 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -406,7 +406,8 @@ o.GetRequiredService>().ToDictionary(o => o.P services.AddSingleton(new UIExtension("LNURL/LightningAddressNav", "store-integrations-nav")); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(o => o.GetRequiredService()); services.AddSingleton(); services.AddSingleton(); diff --git a/BTCPayServer/Payments/Lightning/LightningListener.cs b/BTCPayServer/Payments/Lightning/LightningListener.cs index 9cdeed766..3a0e91085 100644 --- a/BTCPayServer/Payments/Lightning/LightningListener.cs +++ b/BTCPayServer/Payments/Lightning/LightningListener.cs @@ -257,15 +257,24 @@ retry: { lock (_InstanceListeners) { - foreach ((_, var instance) in _InstanceListeners.ToArray()) + foreach (var key in _InstanceListeners.Keys) { - instance.RemoveExpiredInvoices(); - if (!instance.Empty) - instance.EnsureListening(_Cts.Token); + CheckConnection(key.Item1, key.Item2); } } } + public void CheckConnection(string cryptoCode, string connStr) + { + if (_InstanceListeners.TryGetValue((cryptoCode, connStr), out var instance)) + { + + instance.RemoveExpiredInvoices(); + if (!instance.Empty) + instance.EnsureListening(_Cts.Token); + } + } + private async Task CreateNewLNInvoiceForBTCPayInvoice(InvoiceEntity invoice) { var paymentMethods = GetLightningPrompts(invoice).ToArray(); From 048d0c445f6b743880220d3e40af1ac26ecd8350 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Wed, 5 Jun 2024 20:03:35 +0200 Subject: [PATCH 47/64] Add default currency to store data --- BTCPayApp.CommonServer/Models/AppUserInfo.cs | 1 + BTCPayServer/App/API/AppApiController.Account.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/BTCPayApp.CommonServer/Models/AppUserInfo.cs b/BTCPayApp.CommonServer/Models/AppUserInfo.cs index e50896413..646b03be2 100644 --- a/BTCPayApp.CommonServer/Models/AppUserInfo.cs +++ b/BTCPayApp.CommonServer/Models/AppUserInfo.cs @@ -46,6 +46,7 @@ public class AppUserStoreInfo public string? Name { get; set; } public string? RoleId { get; set; } public string? PosAppId { get; set; } + public string? DefaultCurrency { get; set; } public bool Archived { get; set; } public IEnumerable? Permissions { get; set; } } diff --git a/BTCPayServer/App/API/AppApiController.Account.cs b/BTCPayServer/App/API/AppApiController.Account.cs index 6bad758d5..5ba466feb 100644 --- a/BTCPayServer/App/API/AppApiController.Account.cs +++ b/BTCPayServer/App/API/AppApiController.Account.cs @@ -159,6 +159,7 @@ public partial class AppApiController var userStore = store.UserStores.Find(us => us.ApplicationUserId == user.Id && us.StoreDataId == store.Id)!; var apps = await appService.GetAllApps(user.Id, false, store.Id); var posApp = apps.FirstOrDefault(app => app.AppType == PointOfSaleAppType.AppType && app.App.GetSettings().DefaultView == PosViewType.Light); + var storeBlob = userStore.StoreData.GetStoreBlob(); stores.Add(new AppUserStoreInfo { Id = store.Id, @@ -166,6 +167,7 @@ public partial class AppApiController Archived = store.Archived, RoleId = userStore.StoreRole.Id, PosAppId = posApp?.Id, + DefaultCurrency = storeBlob.DefaultCurrency, Permissions = userStore.StoreRole.Permissions }); } From 6bc5c12051233ea9b6488da8cb8ae6e33c2ab158 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Mon, 10 Jun 2024 17:04:51 +0200 Subject: [PATCH 48/64] Add login code support for the app --- .../App/API/AppApiController.Account.cs | 44 +++++++++++++++++-- BTCPayServer/App/API/AppApiController.cs | 10 ++++- .../Controllers/UIAccountController.cs | 3 +- .../UIManageController.LoginCodes.cs | 6 ++- .../Extensions/UrlHelperExtensions.cs | 9 ++++ BTCPayServer/Views/UIManage/LoginCodes.cshtml | 34 +++++++------- 6 files changed, 83 insertions(+), 23 deletions(-) diff --git a/BTCPayServer/App/API/AppApiController.Account.cs b/BTCPayServer/App/API/AppApiController.Account.cs index 5ba466feb..17809cc2b 100644 --- a/BTCPayServer/App/API/AppApiController.Account.cs +++ b/BTCPayServer/App/API/AppApiController.Account.cs @@ -19,6 +19,7 @@ using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.Data; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using NicolasDorier.RateLimits; using PosViewType = BTCPayServer.Plugins.PointOfSale.PosViewType; @@ -74,7 +75,7 @@ public partial class AppApiController [RateLimitsFilter(ZoneLimits.Login, Scope = RateLimitsScope.RemoteAddress)] public async Task, EmptyHttpResult, ProblemHttpResult>> Login(LoginRequest login) { - const string errorMessage = "Invalid login attempt."; + var errorMessage = "Invalid login attempt."; if (ModelState.IsValid) { // Require the user to pass basic checks (approval, confirmed email, not disabled) before they can log on @@ -95,10 +96,44 @@ public partial class AppApiController } // TODO: Add FIDO and LNURL Auth + + if (signInResult.IsLockedOut) + { + _logger.LogWarning("User {Email} tried to log in, but is locked out", user.Email); + } + else if (signInResult.Succeeded) + { + _logger.LogInformation("User {Email} logged in", user.Email); + return TypedResults.Empty; + } - return signInResult.Succeeded - ? TypedResults.Empty - : TypedResults.Problem(signInResult.ToString(), statusCode: 401); + errorMessage = signInResult.ToString(); + } + + return TypedResults.Problem(errorMessage, statusCode: 401); + } + + [AllowAnonymous] + [HttpPost("login/code")] + [RateLimitsFilter(ZoneLimits.Login, Scope = RateLimitsScope.RemoteAddress)] + public async Task, EmptyHttpResult, ProblemHttpResult>> LoginWithCode([FromBody] string loginCode) + { + const string errorMessage = "Invalid login attempt."; + if (!string.IsNullOrEmpty(loginCode)) + { + var code = loginCode.Split(';').First(); + var userId = userLoginCodeService.Verify(code); + var user = userId is null ? null : await userManager.FindByIdAsync(userId); + if (!UserService.TryCanLogin(user, out var message)) + { + return TypedResults.Problem(message, statusCode: 401); + } + + signInManager.AuthenticationScheme = AuthenticationSchemes.GreenfieldBearer; + await signInManager.SignInAsync(user, false, "LoginCode"); + + _logger.LogInformation("User {Email} logged in with a login code", user.Email); + return TypedResults.Empty; } return TypedResults.Problem(errorMessage, statusCode: 401); @@ -141,6 +176,7 @@ public partial class AppApiController if (user != null) { await signInManager.SignOutAsync(); + _logger.LogInformation("User {Email} logged out", user.Email); return Results.Ok(); } return Results.Unauthorized(); diff --git a/BTCPayServer/App/API/AppApiController.cs b/BTCPayServer/App/API/AppApiController.cs index 4dd760069..d6f1d00f8 100644 --- a/BTCPayServer/App/API/AppApiController.cs +++ b/BTCPayServer/App/API/AppApiController.cs @@ -1,12 +1,13 @@ #nullable enable using System; using System.Threading.Tasks; -using BTCPayApp.CommonServer; using BTCPayApp.CommonServer.Models; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Data; +using BTCPayServer.Fido2; +using BTCPayServer.Logging; using BTCPayServer.Services; using BTCPayServer.Services.Apps; using BTCPayServer.Services.Rates; @@ -17,6 +18,8 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace BTCPayServer.App.API; @@ -35,9 +38,14 @@ public partial class AppApiController( UriResolver uriResolver, DefaultRulesCollection defaultRules, RateFetcher rateFactory, + LinkGenerator linkGenerator, + UserLoginCodeService userLoginCodeService, + Logs logs, IOptionsMonitor bearerTokenOptions) : Controller { + private readonly ILogger _logger = logs.PayServer; + [AllowAnonymous] [HttpGet("instance")] public async Task, NotFound>> Instance() diff --git a/BTCPayServer/Controllers/UIAccountController.cs b/BTCPayServer/Controllers/UIAccountController.cs index 3688770ab..5bb8537fd 100644 --- a/BTCPayServer/Controllers/UIAccountController.cs +++ b/BTCPayServer/Controllers/UIAccountController.cs @@ -130,7 +130,8 @@ namespace BTCPayServer.Controllers { if (!string.IsNullOrEmpty(loginCode)) { - var userId = _userLoginCodeService.Verify(loginCode); + var code = loginCode.Split(';').First(); + var userId = _userLoginCodeService.Verify(code); if (userId is null) { TempData[WellKnownTempData.ErrorMessage] = "Login code was invalid"; diff --git a/BTCPayServer/Controllers/UIManageController.LoginCodes.cs b/BTCPayServer/Controllers/UIManageController.LoginCodes.cs index 655085a81..9fdf4eb10 100644 --- a/BTCPayServer/Controllers/UIManageController.LoginCodes.cs +++ b/BTCPayServer/Controllers/UIManageController.LoginCodes.cs @@ -14,8 +14,10 @@ namespace BTCPayServer.Controllers { throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); } - - return View(nameof(LoginCodes), _userLoginCodeService.GetOrGenerate(user.Id)); + + var indexUrl = _linkGenerator.IndexLink(Request.Scheme, Request.Host, Request.PathBase); + var loginCode = _userLoginCodeService.GetOrGenerate(user.Id); + return View(nameof(LoginCodes), $"{loginCode};{indexUrl};{user.Email}"); } } } diff --git a/BTCPayServer/Extensions/UrlHelperExtensions.cs b/BTCPayServer/Extensions/UrlHelperExtensions.cs index 19d0eb713..398bfe008 100644 --- a/BTCPayServer/Extensions/UrlHelperExtensions.cs +++ b/BTCPayServer/Extensions/UrlHelperExtensions.cs @@ -109,5 +109,14 @@ namespace Microsoft.AspNetCore.Mvc values: new { storeId = wallet?.StoreId ?? walletIdOrStoreId, pullPaymentId, payoutState }, scheme, host, pathbase); } + + public static string IndexLink(this LinkGenerator urlHelper, string scheme, HostString host, string pathbase) + { + return urlHelper.GetUriByAction( + action: nameof(UIHomeController.Index), + controller: "UIHome", + values: null, + scheme, host, pathbase); + } } } diff --git a/BTCPayServer/Views/UIManage/LoginCodes.cshtml b/BTCPayServer/Views/UIManage/LoginCodes.cshtml index 478dc12de..f21294d13 100644 --- a/BTCPayServer/Views/UIManage/LoginCodes.cshtml +++ b/BTCPayServer/Views/UIManage/LoginCodes.cshtml @@ -22,22 +22,26 @@ @section PageFootContent { - + } From dd99a7b32dcec3e033bf5069ff899b60f4ae93d3 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Mon, 10 Jun 2024 19:10:48 +0200 Subject: [PATCH 49/64] POS fix --- BTCPayServer/wwwroot/pos/common.js | 1 + 1 file changed, 1 insertion(+) diff --git a/BTCPayServer/wwwroot/pos/common.js b/BTCPayServer/wwwroot/pos/common.js index 736f2cf5f..f77a92bb9 100644 --- a/BTCPayServer/wwwroot/pos/common.js +++ b/BTCPayServer/wwwroot/pos/common.js @@ -266,6 +266,7 @@ const posCommon = { return inSearch && inCategories }, updateDisplay() { + if (!this.$refs.posItems) return; this.forEachItem(item => { item.classList[this.displayItem(item) ? 'add' : 'remove']('posItem--displayed') item.classList.remove('posItem--first') From f56dc8c6ae8a079f987312a5261b42acf07b9cdb Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Wed, 12 Jun 2024 13:29:47 +0200 Subject: [PATCH 50/64] Support invite link in app --- .../Models/AcceptInviteRequest.cs | 12 ++++ .../Models/AcceptInviteResult.cs | 9 +++ .../App/API/AppApiController.Account.cs | 60 ++++++++++++++++++- BTCPayServer/BTCPayServer.csproj | 2 +- .../Controllers/UIAccountController.cs | 6 +- .../Extensions/EmailSenderExtensions.cs | 13 +++- 6 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 BTCPayApp.CommonServer/Models/AcceptInviteRequest.cs create mode 100644 BTCPayApp.CommonServer/Models/AcceptInviteResult.cs diff --git a/BTCPayApp.CommonServer/Models/AcceptInviteRequest.cs b/BTCPayApp.CommonServer/Models/AcceptInviteRequest.cs new file mode 100644 index 000000000..6775f753d --- /dev/null +++ b/BTCPayApp.CommonServer/Models/AcceptInviteRequest.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace BTCPayApp.CommonServer.Models; + +public class AcceptInviteRequest +{ + [Required] + public string? UserId { get; init; } + + [Required] + public string? Code { get; init; } +} diff --git a/BTCPayApp.CommonServer/Models/AcceptInviteResult.cs b/BTCPayApp.CommonServer/Models/AcceptInviteResult.cs new file mode 100644 index 000000000..3fe4e2476 --- /dev/null +++ b/BTCPayApp.CommonServer/Models/AcceptInviteResult.cs @@ -0,0 +1,9 @@ +namespace BTCPayApp.CommonServer.Models; + +public class AcceptInviteResult(string email) +{ + public string Email { get; init; } = email; + public bool? RequiresUserApproval { get; set; } + public bool? EmailHasBeenConfirmed { get; set; } + public string? PasswordSetCode { get; set; } +} diff --git a/BTCPayServer/App/API/AppApiController.Account.cs b/BTCPayServer/App/API/AppApiController.Account.cs index 17809cc2b..64cd14c8f 100644 --- a/BTCPayServer/App/API/AppApiController.Account.cs +++ b/BTCPayServer/App/API/AppApiController.Account.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using BTCPayApp.CommonServer.Models; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Extensions; +using BTCPayServer.Abstractions.Models; using BTCPayServer.Client.Models; using BTCPayServer.Data; using BTCPayServer.Events; @@ -168,6 +169,58 @@ public partial class AppApiController ? TypedResults.SignIn(await signInManager.CreateUserPrincipalAsync(user), authenticationScheme: scheme) : TypedResults.Challenge(authenticationSchemes: new[] { scheme }); } + + [AllowAnonymous] + [HttpPost("accept-invite")] + [RateLimitsFilter(ZoneLimits.Login, Scope = RateLimitsScope.RemoteAddress)] + public async Task AcceptInvite(AcceptInviteRequest invite) + { + if (string.IsNullOrEmpty(invite.UserId) || string.IsNullOrEmpty(invite.Code)) + { + return NotFound(); + } + + var user = await userManager.FindByInvitationTokenAsync(invite.UserId, Uri.UnescapeDataString(invite.Code)); + if (user == null) + { + return NotFound(); + } + + var requiresEmailConfirmation = user is { RequiresEmailConfirmation: true, EmailConfirmed: false }; + var requiresUserApproval = user is { RequiresApproval: true, Approved: false }; + bool? emailHasBeenConfirmed = requiresEmailConfirmation ? false : null; + var requiresSetPassword = !await userManager.HasPasswordAsync(user); + string? passwordSetCode = requiresSetPassword ? await userManager.GeneratePasswordResetTokenAsync(user) : null; + + eventAggregator.Publish(new UserInviteAcceptedEvent + { + User = user, + RequestUri = Request.GetAbsoluteRootUri() + }); + + if (requiresEmailConfirmation) + { + var emailConfirmCode = await userManager.GenerateEmailConfirmationTokenAsync(user); + var result = await userManager.ConfirmEmailAsync(user, emailConfirmCode); + if (result.Succeeded) + { + emailHasBeenConfirmed = true; + eventAggregator.Publish(new UserConfirmedEmailEvent + { + User = user, + RequestUri = Request.GetAbsoluteRootUri() + }); + } + } + + var response = new AcceptInviteResult(user.Email!) + { + EmailHasBeenConfirmed = emailHasBeenConfirmed, + RequiresUserApproval = requiresUserApproval, + PasswordSetCode = passwordSetCode + }; + return Ok(response); + } [HttpPost("logout")] public async Task Logout() @@ -239,9 +292,12 @@ public partial class AppApiController public async Task SetPassword(ResetPasswordRequest resetRequest) { var user = await userManager.FindByEmailAsync(resetRequest.Email); - if (!UserService.TryCanLogin(user, out _)) + var needsInitialPassword = user != null && !await userManager.HasPasswordAsync(user); + // Let unapproved users set a password. Otherwise, don't reveal that the user does not exist. + if (!UserService.TryCanLogin(user, out var message) && !needsInitialPassword || user == null) { - return Unauthorized(new GreenfieldAPIError(null, "Invalid account")); + _logger.LogWarning("User {Email} tried to reset password, but failed: {Message}", user?.Email ?? "(NO EMAIL)", message); + return Unauthorized(new GreenfieldAPIError(null, "Invalid request")); } IdentityResult result; diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 3b11e1390..342563a11 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -59,7 +59,7 @@ - + diff --git a/BTCPayServer/Controllers/UIAccountController.cs b/BTCPayServer/Controllers/UIAccountController.cs index 5bb8537fd..f73496c8b 100644 --- a/BTCPayServer/Controllers/UIAccountController.cs +++ b/BTCPayServer/Controllers/UIAccountController.cs @@ -782,9 +782,11 @@ namespace BTCPayServer.Controllers var user = await _userManager.FindByEmailAsync(model.Email); var hasPassword = user != null && await _userManager.HasPasswordAsync(user); - if (!UserService.TryCanLogin(user, out _)) + var needsInitialPassword = user != null && !await _userManager.HasPasswordAsync(user); + // Let unapproved users set a password. Otherwise, don't reveal that the user does not exist. + if (!UserService.TryCanLogin(user, out var message) && !needsInitialPassword || user == null) { - // Don't reveal that the user does not exist + _logger.LogWarning("User {Email} tried to reset password, but failed: {Message}", user?.Email ?? "(NO EMAIL)", message); return RedirectToAction(nameof(Login)); } diff --git a/BTCPayServer/Extensions/EmailSenderExtensions.cs b/BTCPayServer/Extensions/EmailSenderExtensions.cs index f0e2367e0..479aad155 100644 --- a/BTCPayServer/Extensions/EmailSenderExtensions.cs +++ b/BTCPayServer/Extensions/EmailSenderExtensions.cs @@ -1,6 +1,8 @@ using System.Text.Encodings.Web; +using BTCPayServer.Components.QRCode; using BTCPayServer.Services.Mails; using MimeKit; +using QRCoder; namespace BTCPayServer.Services { @@ -42,7 +44,7 @@ namespace BTCPayServer.Services public static void SendInvitation(this IEmailSender emailSender, MailboxAddress address, string link) { emailSender.SendEmail(address, "Invitation", CreateEmailBody( - $"Please complete your account setup by clicking this link.")); + $"

Please complete your account setup by clicking this link.

You can also use the BTCPay Server app and scan this QR code when connecting:

{GetQrCodeImg(link)}")); } public static void SendNewUserInfo(this IEmailSender emailSender, MailboxAddress address, string newUserInfo, string link) @@ -56,5 +58,14 @@ namespace BTCPayServer.Services emailSender.SendEmail(address, userInfo, CreateEmailBody( $"{userInfo}. You can view the store users here: Store users")); } + + private static string GetQrCodeImg(string data) + { + using var qrGenerator = new QRCodeGenerator(); + using var qrCodeData = qrGenerator.CreateQrCode(data, QRCodeGenerator.ECCLevel.Q); + using var qrCode = new Base64QRCode(qrCodeData); + var base64 = qrCode.GetGraphic(20); + return $"{data}"; + } } } From 5b2e63358910d983de8567dc07724976fd082f60 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Fri, 14 Jun 2024 16:50:17 +0200 Subject: [PATCH 51/64] Extract POSDataParser --- .../Converters/POSDataParser.cs | 49 +++++++++++++++++++ .../Controllers/UIInvoiceController.UI.cs | 45 +---------------- BTCPayServer/Views/Shared/PosData.cshtml | 19 +++++-- 3 files changed, 66 insertions(+), 47 deletions(-) create mode 100644 BTCPayServer.Abstractions/Converters/POSDataParser.cs diff --git a/BTCPayServer.Abstractions/Converters/POSDataParser.cs b/BTCPayServer.Abstractions/Converters/POSDataParser.cs new file mode 100644 index 000000000..f942584d7 --- /dev/null +++ b/BTCPayServer.Abstractions/Converters/POSDataParser.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Linq; + +namespace BTCPayServer.Abstractions.Converters; + +public class PosDataParser +{ + public static Dictionary ParsePosData(JToken? posData) + { + var result = new Dictionary(); + if (posData is JObject jobj) + { + foreach (var item in jobj) + { + ParsePosDataItem(item, ref result); + } + } + return result; + } + + static void ParsePosDataItem(KeyValuePair item, ref Dictionary result) + { + switch (item.Value?.Type) + { + case JTokenType.Array: + var items = item.Value.AsEnumerable().ToList(); + var arrayResult = new List(); + for (var i = 0; i < items.Count; i++) + { + arrayResult.Add(items[i] is JObject + ? ParsePosData(items[i]) + : items[i].ToString()); + } + + result.TryAdd(item.Key, arrayResult); + + break; + case JTokenType.Object: + result.TryAdd(item.Key, ParsePosData(item.Value)); + break; + case null: + break; + default: + result.TryAdd(item.Key, item.Value.ToString()); + break; + } + } +} diff --git a/BTCPayServer/Controllers/UIInvoiceController.UI.cs b/BTCPayServer/Controllers/UIInvoiceController.UI.cs index 7c24554ee..7f0679355 100644 --- a/BTCPayServer/Controllers/UIInvoiceController.UI.cs +++ b/BTCPayServer/Controllers/UIInvoiceController.UI.cs @@ -6,6 +6,7 @@ using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; +using BTCPayServer.Abstractions.Converters; using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Models; using BTCPayServer.Client; @@ -1345,50 +1346,6 @@ namespace BTCPayServer.Controllers private string GetUserId() => _UserManager.GetUserId(User)!; - public class PosDataParser - { - public static Dictionary ParsePosData(JToken? posData) - { - var result = new Dictionary(); - if (posData is JObject jobj) - { - foreach (var item in jobj) - { - ParsePosDataItem(item, ref result); - } - } - return result; - } - - static void ParsePosDataItem(KeyValuePair item, ref Dictionary result) - { - switch (item.Value?.Type) - { - case JTokenType.Array: - var items = item.Value.AsEnumerable().ToList(); - var arrayResult = new List(); - for (var i = 0; i < items.Count; i++) - { - arrayResult.Add(items[i] is JObject - ? ParsePosData(items[i]) - : items[i].ToString()); - } - - result.TryAdd(item.Key, arrayResult); - - break; - case JTokenType.Object: - result.TryAdd(item.Key, ParsePosData(item.Value)); - break; - case null: - break; - default: - result.TryAdd(item.Key, item.Value.ToString()); - break; - } - } - } - private SelectList GetPaymentMethodsSelectList(StoreData store) { var excludeFilter = store.GetStoreBlob().GetExcludedPaymentMethods(); diff --git a/BTCPayServer/Views/Shared/PosData.cshtml b/BTCPayServer/Views/Shared/PosData.cshtml index d7f0296ab..6430c0326 100644 --- a/BTCPayServer/Views/Shared/PosData.cshtml +++ b/BTCPayServer/Views/Shared/PosData.cshtml @@ -38,9 +38,22 @@ @foreach (var value in cartCollection) { - - @value - + if (value is Dictionary { Keys.Count: > 0 } valDict) + { + @foreach (var (key, val) in valDict) + { + + @key + @val + + } + } + else + { + + @value + + } } } From 581307c3341a369e61459467764b9696f64b3397 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Mon, 17 Jun 2024 12:01:40 +0200 Subject: [PATCH 52/64] Remove app tab on LN setup page --- BTCPayServer/Views/UIStores/SetupLightningNode.cshtml | 2 -- 1 file changed, 2 deletions(-) diff --git a/BTCPayServer/Views/UIStores/SetupLightningNode.cshtml b/BTCPayServer/Views/UIStores/SetupLightningNode.cshtml index d754d3d02..59d05edd2 100644 --- a/BTCPayServer/Views/UIStores/SetupLightningNode.cshtml +++ b/BTCPayServer/Views/UIStores/SetupLightningNode.cshtml @@ -37,8 +37,6 @@ - - From a6293d21c2d49b9a300434dc522413f67d576503 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Thu, 20 Jun 2024 20:14:57 +0200 Subject: [PATCH 53/64] Immediate login on signup if possible --- .../App/API/AppApiController.Account.cs | 45 +++++++++++++------ .../Controllers/UIAccountController.cs | 1 + 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/BTCPayServer/App/API/AppApiController.Account.cs b/BTCPayServer/App/API/AppApiController.Account.cs index 64cd14c8f..85dd56cd1 100644 --- a/BTCPayServer/App/API/AppApiController.Account.cs +++ b/BTCPayServer/App/API/AppApiController.Account.cs @@ -13,6 +13,7 @@ using BTCPayServer.Events; using BTCPayServer.Plugins.PointOfSale; using BTCPayServer.Services; using BTCPayServer.Services.Apps; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.BearerToken; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -23,19 +24,22 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using NicolasDorier.RateLimits; using PosViewType = BTCPayServer.Plugins.PointOfSale.PosViewType; +using SignInResult = Microsoft.AspNetCore.Identity.SignInResult; namespace BTCPayServer.App.API; public partial class AppApiController { + private const string Scheme = AuthenticationSchemes.GreenfieldBearer; + [AllowAnonymous] [HttpPost("register")] [RateLimitsFilter(ZoneLimits.Login, Scope = RateLimitsScope.RemoteAddress)] - public async Task Register(SignupRequest signup) + public async Task, Ok, EmptyHttpResult, ProblemHttpResult>> Register(SignupRequest signup) { var policiesSettings = await settingsRepository.GetSettingAsync() ?? new PoliciesSettings(); if (policiesSettings.LockSubscription) - return this.CreateAPIError("unauthorized", "This instance does not allow public user registration"); + return TypedResults.Problem("This instance does not allow public user registration", statusCode: 401); var errorMessage = "Invalid signup attempt."; if (ModelState.IsValid) @@ -57,18 +61,32 @@ public partial class AppApiController User = user }); + SignInResult? signInResult = null; + var requiresConfirmedEmail = policiesSettings.RequiresConfirmedEmail && !user.EmailConfirmed; + var requiresUserApproval = policiesSettings.RequiresUserApproval && !user.Approved; + if (!requiresConfirmedEmail && !requiresUserApproval) + { + signInManager.AuthenticationScheme = Scheme; + signInResult = await signInManager.PasswordSignInAsync(signup.Email, signup.Password, true, true); + } + + if (signInResult?.Succeeded is true) + { + _logger.LogInformation("User {Email} logged in", user.Email); + return TypedResults.Empty; + } var response = new SignupResult { Email = user.Email, - RequiresConfirmedEmail = policiesSettings.RequiresConfirmedEmail && !user.EmailConfirmed, - RequiresUserApproval = policiesSettings.RequiresUserApproval && !user.Approved + RequiresConfirmedEmail = requiresConfirmedEmail, + RequiresUserApproval = requiresUserApproval }; - return Ok(response); + return TypedResults.Ok(response); } errorMessage = result.ToString(); } - return this.CreateAPIError(null, errorMessage); + return TypedResults.Problem(errorMessage, statusCode: 401); } [AllowAnonymous] @@ -86,7 +104,7 @@ public partial class AppApiController return TypedResults.Problem(message, statusCode: 401); } - signInManager.AuthenticationScheme = AuthenticationSchemes.GreenfieldBearer; + signInManager.AuthenticationScheme = Scheme; var signInResult = await signInManager.PasswordSignInAsync(login.Email, login.Password, true, true); if (signInResult.RequiresTwoFactor) { @@ -129,8 +147,8 @@ public partial class AppApiController { return TypedResults.Problem(message, statusCode: 401); } - - signInManager.AuthenticationScheme = AuthenticationSchemes.GreenfieldBearer; + + signInManager.AuthenticationScheme = Scheme; await signInManager.SignInAsync(user, false, "LoginCode"); _logger.LogInformation("User {Email} logged in with a login code", user.Email); @@ -145,8 +163,7 @@ public partial class AppApiController [RateLimitsFilter(ZoneLimits.Login, Scope = RateLimitsScope.RemoteAddress)] public async Task, UnauthorizedHttpResult, SignInHttpResult, ChallengeHttpResult>> Refresh(RefreshRequest refresh) { - const string scheme = AuthenticationSchemes.GreenfieldBearer; - var authenticationTicket = bearerTokenOptions.Get(scheme).RefreshTokenProtector.Unprotect(refresh.RefreshToken); + var authenticationTicket = bearerTokenOptions.Get(Scheme).RefreshTokenProtector.Unprotect(refresh.RefreshToken); var expiresUtc = authenticationTicket?.Properties.ExpiresUtc; ApplicationUser? user = null; @@ -161,13 +178,13 @@ public partial class AppApiController bool flag = num != 0; if (!flag) { - signInManager.AuthenticationScheme = scheme; + signInManager.AuthenticationScheme = Scheme; user = await signInManager.ValidateSecurityStampAsync(authenticationTicket?.Principal); } return user != null - ? TypedResults.SignIn(await signInManager.CreateUserPrincipalAsync(user), authenticationScheme: scheme) - : TypedResults.Challenge(authenticationSchemes: new[] { scheme }); + ? TypedResults.SignIn(await signInManager.CreateUserPrincipalAsync(user), authenticationScheme: Scheme) + : TypedResults.Challenge(authenticationSchemes: new[] { Scheme }); } [AllowAnonymous] diff --git a/BTCPayServer/Controllers/UIAccountController.cs b/BTCPayServer/Controllers/UIAccountController.cs index f73496c8b..7b2dcb83c 100644 --- a/BTCPayServer/Controllers/UIAccountController.cs +++ b/BTCPayServer/Controllers/UIAccountController.cs @@ -632,6 +632,7 @@ namespace BTCPayServer.Controllers if (logon) { await _signInManager.SignInAsync(user, isPersistent: false); + _logger.LogInformation("User {Email} logged in", user.Email); return RedirectToLocal(returnUrl); } } From a05212923baddbc76785532abfa05137fcae0ee5 Mon Sep 17 00:00:00 2001 From: Kukks Date: Fri, 21 Jun 2024 08:26:43 +0200 Subject: [PATCH 54/64] add additional info --- BTCPayApp.CommonServer/Models/AppUserInfo.cs | 6 ++++++ BTCPayServer/App/BTCPayAppHub.cs | 17 ++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/BTCPayApp.CommonServer/Models/AppUserInfo.cs b/BTCPayApp.CommonServer/Models/AppUserInfo.cs index 646b03be2..da56da52f 100644 --- a/BTCPayApp.CommonServer/Models/AppUserInfo.cs +++ b/BTCPayApp.CommonServer/Models/AppUserInfo.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; +using System.Runtime.Serialization; +using System.Text.Json; using BTCPayServer.Lightning; +using Newtonsoft.Json; namespace BTCPayApp.CommonServer.Models; @@ -17,6 +20,9 @@ public partial class LightningPayment //you can have multiple requests generated for the same payment hash, but once you reveal the preimage, you should reject any attempt to pay the same payment hash public List PaymentRequests { get; set; } + [JsonIgnore] + public Dictionary AdditionalData { get; set; } + } public class AppUserInfo diff --git a/BTCPayServer/App/BTCPayAppHub.cs b/BTCPayServer/App/BTCPayAppHub.cs index 341cc62eb..9a6ab6464 100644 --- a/BTCPayServer/App/BTCPayAppHub.cs +++ b/BTCPayServer/App/BTCPayAppHub.cs @@ -145,18 +145,29 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer public async Task GetFeeRate(int blockTarget) { + _logger.LogInformation($"Getting fee rate for {blockTarget}"); var feeProvider = _feeProviderFactory.CreateFeeProvider( _btcPayNetworkProvider.BTC); - return (await feeProvider.GetFeeRateAsync(blockTarget)).SatoshiPerByte; + try + { + + return (await feeProvider.GetFeeRateAsync(blockTarget)).SatoshiPerByte; + } + finally + { + _logger.LogInformation($"Getting fee rate for {blockTarget} done"); + + } + } public async Task GetBestBlock() { - + _logger.LogInformation($"Getting best block"); var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC); var bcInfo = await explorerClient.RPCClient.GetBlockchainInfoAsyncEx(); var bh = await GetBlockHeader(bcInfo.BestBlockHash.ToString()); - + _logger.LogInformation($"Getting best block done"); return new BestBlockResponse() { BlockHash = bcInfo.BestBlockHash.ToString(), From 90feb1810f951c225cc20ff7b7a51f7dff259a02 Mon Sep 17 00:00:00 2001 From: Kukks Date: Fri, 21 Jun 2024 13:35:09 +0200 Subject: [PATCH 55/64] add usings --- BTCPayApp.CommonServer/Models/AppUserInfo.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/BTCPayApp.CommonServer/Models/AppUserInfo.cs b/BTCPayApp.CommonServer/Models/AppUserInfo.cs index da56da52f..16a066402 100644 --- a/BTCPayApp.CommonServer/Models/AppUserInfo.cs +++ b/BTCPayApp.CommonServer/Models/AppUserInfo.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Runtime.Serialization; using System.Text.Json; using BTCPayServer.Lightning; +using NBitcoin; +using NBitcoin.JsonConverters; using Newtonsoft.Json; namespace BTCPayApp.CommonServer.Models; From cb748e0897fa69a1cb6166155f5f4a9e36255458 Mon Sep 17 00:00:00 2001 From: Kukks Date: Fri, 21 Jun 2024 14:14:42 +0200 Subject: [PATCH 56/64] btcpay to user app events --- BTCPayApp.CommonServer/IBTCPayAppHubClient.cs | 2 ++ BTCPayServer/App/BTCPayAppHub.cs | 10 +++++++++- BTCPayServer/App/BTCPayAppState.cs | 10 +++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs index a1eb01295..0263ca378 100644 --- a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs +++ b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs @@ -22,6 +22,8 @@ public class TransactionDetectedRequest //methods available on the hub in the client public interface IBTCPayAppHubClient { + Task NotifyServerEvent(string eventName); + Task NotifyNetwork(string network); Task NotifyServerNode(string nodeInfo); Task TransactionDetected(TransactionDetectedRequest request); diff --git a/BTCPayServer/App/BTCPayAppHub.cs b/BTCPayServer/App/BTCPayAppHub.cs index 9a6ab6464..e05b0ddf7 100644 --- a/BTCPayServer/App/BTCPayAppHub.cs +++ b/BTCPayServer/App/BTCPayAppHub.cs @@ -7,10 +7,12 @@ using System.Threading.Tasks; using BTCPayApp.CommonServer; using BTCPayApp.CommonServer.Models; using BTCPayServer.Abstractions.Constants; +using BTCPayServer.Data; using BTCPayServer.HostedServices; using BTCPayServer.Services; using BTCPayServer.Services.Wallets; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Logging; using NBitcoin; @@ -94,13 +96,15 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer private readonly ExplorerClientProvider _explorerClientProvider; private readonly IFeeProviderFactory _feeProviderFactory; private readonly ILogger _logger; + private readonly UserManager _userManager; public BTCPayAppHub(BTCPayNetworkProvider btcPayNetworkProvider, NBXplorerDashboard nbXplorerDashboard, BTCPayAppState appState, ExplorerClientProvider explorerClientProvider, IFeeProviderFactory feeProviderFactory, - ILogger logger) + ILogger logger, + UserManager userManager) { _btcPayNetworkProvider = btcPayNetworkProvider; _nbXplorerDashboard = nbXplorerDashboard; @@ -108,6 +112,7 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer _explorerClientProvider = explorerClientProvider; _feeProviderFactory = feeProviderFactory; _logger = logger; + _userManager = userManager; } @@ -122,6 +127,9 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer Context.Abort(); return; } + + + await Groups.AddToGroupAsync(Context.ConnectionId,_userManager.GetUserId(Context.User)); await Clients.Client(Context.ConnectionId).NotifyNetwork(_btcPayNetworkProvider.BTC.NBitcoinNetwork.ToString()); diff --git a/BTCPayServer/App/BTCPayAppState.cs b/BTCPayServer/App/BTCPayAppState.cs index b5e1b66ea..19b16ef43 100644 --- a/BTCPayServer/App/BTCPayAppState.cs +++ b/BTCPayServer/App/BTCPayAppState.cs @@ -12,6 +12,7 @@ using BTCPayServer.Events; using BTCPayServer.Lightning; using BTCPayServer.Payments.Lightning; using BTCPayServer.Services.Invoices; +using BTCPayServer.Services.Notifications; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -64,10 +65,17 @@ public class BTCPayAppState : IHostedService _eventAggregator.Subscribe(OnNewBlock)); _compositeDisposable.Add( _eventAggregator.SubscribeAsync(OnNewTransaction)); + _compositeDisposable.Add( + _eventAggregator.SubscribeAsync(UserNotificationsUpdatedEvent)); _ = UpdateNodeInfo(); return Task.CompletedTask; } - + + private async Task UserNotificationsUpdatedEvent(UserNotificationsUpdatedEvent arg) + { + await _hubContext.Clients.Group(arg.UserId).NotifyServerEvent("notifications-updated"); + } + private string _nodeInfo = string.Empty; private async Task UpdateNodeInfo() { From 9480eff096d17ef2f0c23e4045fd569c4144358b Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Wed, 26 Jun 2024 15:55:01 +0200 Subject: [PATCH 57/64] Add user profile info --- BTCPayApp.CommonServer/Models/AppUserInfo.cs | 7 +++++++ BTCPayServer/App/API/AppApiController.Account.cs | 4 ++++ .../Controllers/GreenField/GreenfieldUsersController.cs | 2 ++ BTCPayServer/Services/Stores/StoreRepository.cs | 2 ++ 4 files changed, 15 insertions(+) diff --git a/BTCPayApp.CommonServer/Models/AppUserInfo.cs b/BTCPayApp.CommonServer/Models/AppUserInfo.cs index 16a066402..7bdffa9ac 100644 --- a/BTCPayApp.CommonServer/Models/AppUserInfo.cs +++ b/BTCPayApp.CommonServer/Models/AppUserInfo.cs @@ -36,6 +36,13 @@ public class AppUserInfo public IEnumerable? Roles { get; set; } public IEnumerable? Stores { get; set; } + public void SetInfo(string email, string? name, string? imageUrl) + { + Email = email; + Name = name; + ImageUrl = imageUrl; + } + public static bool Equals(AppUserInfo? x, AppUserInfo? y) { if (ReferenceEquals(x, y)) return true; diff --git a/BTCPayServer/App/API/AppApiController.Account.cs b/BTCPayServer/App/API/AppApiController.Account.cs index 85dd56cd1..759a55ff9 100644 --- a/BTCPayServer/App/API/AppApiController.Account.cs +++ b/BTCPayServer/App/API/AppApiController.Account.cs @@ -280,6 +280,10 @@ public partial class AppApiController var info = new AppUserInfo { UserId = user.Id, + Name = user.Name, + ImageUrl = !string.IsNullOrEmpty(user.ImageUrl) + ? await uriResolver.Resolve(Request.GetAbsoluteRootUri(), UnresolvedUri.Create(user.ImageUrl)) + : null, Email = await userManager.GetEmailAsync(user), Roles = await userManager.GetRolesAsync(user), Stores = stores diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldUsersController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldUsersController.cs index 6f4a4db51..77d28195b 100644 --- a/BTCPayServer/Controllers/GreenField/GreenfieldUsersController.cs +++ b/BTCPayServer/Controllers/GreenField/GreenfieldUsersController.cs @@ -346,6 +346,8 @@ namespace BTCPayServer.Controllers.Greenfield { UserName = request.Email, Email = request.Email, + Name = request.Name, + ImageUrl = request.ImageUrl, RequiresEmailConfirmation = policies.RequiresConfirmedEmail, RequiresApproval = policies.RequiresUserApproval, Created = DateTimeOffset.UtcNow, diff --git a/BTCPayServer/Services/Stores/StoreRepository.cs b/BTCPayServer/Services/Stores/StoreRepository.cs index 020bad515..f033965c2 100644 --- a/BTCPayServer/Services/Stores/StoreRepository.cs +++ b/BTCPayServer/Services/Stores/StoreRepository.cs @@ -61,6 +61,8 @@ namespace BTCPayServer.Services.Stores { public string Id { get; set; } public string Email { get; set; } + public string Name { get; set; } + public string ImageUrl { get; set; } public StoreRole StoreRole { get; set; } public UserBlob UserBlob { get; set; } } From 766d3daf0d9495d7ac9da83b0e55cc3fd2cac00e Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Wed, 26 Jun 2024 17:23:59 +0200 Subject: [PATCH 58/64] Updates after merges --- BTCPayServer/App/API/AppApiController.Account.cs | 8 +++++--- .../Controllers/GreenField/GreenfieldUsersController.cs | 2 -- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/BTCPayServer/App/API/AppApiController.Account.cs b/BTCPayServer/App/API/AppApiController.Account.cs index 759a55ff9..b3985e843 100644 --- a/BTCPayServer/App/API/AppApiController.Account.cs +++ b/BTCPayServer/App/API/AppApiController.Account.cs @@ -277,12 +277,14 @@ public partial class AppApiController Permissions = userStore.StoreRole.Permissions }); } + + var userBlob = user.GetBlob(); var info = new AppUserInfo { UserId = user.Id, - Name = user.Name, - ImageUrl = !string.IsNullOrEmpty(user.ImageUrl) - ? await uriResolver.Resolve(Request.GetAbsoluteRootUri(), UnresolvedUri.Create(user.ImageUrl)) + Name = userBlob?.Name, + ImageUrl = !string.IsNullOrEmpty(userBlob?.ImageUrl) + ? await uriResolver.Resolve(Request.GetAbsoluteRootUri(), UnresolvedUri.Create(userBlob.ImageUrl)) : null, Email = await userManager.GetEmailAsync(user), Roles = await userManager.GetRolesAsync(user), diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldUsersController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldUsersController.cs index 77d28195b..6f4a4db51 100644 --- a/BTCPayServer/Controllers/GreenField/GreenfieldUsersController.cs +++ b/BTCPayServer/Controllers/GreenField/GreenfieldUsersController.cs @@ -346,8 +346,6 @@ namespace BTCPayServer.Controllers.Greenfield { UserName = request.Email, Email = request.Email, - Name = request.Name, - ImageUrl = request.ImageUrl, RequiresEmailConfirmation = policies.RequiresConfirmedEmail, RequiresApproval = policies.RequiresUserApproval, Created = DateTimeOffset.UtcNow, From 04676de7832f84ff47c905c224e7b241c58fabe0 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Thu, 27 Jun 2024 16:02:06 +0200 Subject: [PATCH 59/64] Try to add server events --- BTCPayApp.CommonServer/IBTCPayAppHubClient.cs | 22 +++++- BTCPayServer/App/BTCPayAppHub.cs | 49 +++++++++---- BTCPayServer/App/BTCPayAppState.cs | 73 ++++++++++++++----- BTCPayServer/Events/StoreCreatedEvent.cs | 13 ++++ BTCPayServer/Events/StoreRemovedEvent.cs | 9 +-- BTCPayServer/Events/StoreUpdatedEvent.cs | 13 ++++ BTCPayServer/Events/UserStoreAddedEvent.cs | 9 +++ BTCPayServer/Events/UserStoreEvent.cs | 11 +++ BTCPayServer/Events/UserStoreRemovedEvent.cs | 9 +++ BTCPayServer/Events/UserStoreUpdatedEvent.cs | 9 +++ .../Services/Stores/StoreRepository.cs | 13 +++- 11 files changed, 188 insertions(+), 42 deletions(-) create mode 100644 BTCPayServer/Events/StoreCreatedEvent.cs create mode 100644 BTCPayServer/Events/StoreUpdatedEvent.cs create mode 100644 BTCPayServer/Events/UserStoreAddedEvent.cs create mode 100644 BTCPayServer/Events/UserStoreEvent.cs create mode 100644 BTCPayServer/Events/UserStoreRemovedEvent.cs create mode 100644 BTCPayServer/Events/UserStoreUpdatedEvent.cs diff --git a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs index 0263ca378..aa93c2cfb 100644 --- a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs +++ b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs @@ -22,8 +22,7 @@ public class TransactionDetectedRequest //methods available on the hub in the client public interface IBTCPayAppHubClient { - Task NotifyServerEvent(string eventName); - + Task NotifyServerEvent(IServerEvent ev); Task NotifyNetwork(string network); Task NotifyServerNode(string nodeInfo); Task TransactionDetected(TransactionDetectedRequest request); @@ -37,6 +36,25 @@ public interface IBTCPayAppHubClient Task PayInvoice(string bolt11, long? amountMilliSatoshi); } +public interface IServerEvent +{ + public string Type { get; } +} + +public interface IServerEvent : IServerEvent +{ + public T? Event { get; } +} + +public class ServerEvent(string type) : IServerEvent +{ + public string Type { get; } = type; +} +public class ServerEvent(string type, T? evt = default) : ServerEvent(type), IServerEvent +{ + public T? Event { get; } = evt; +} + public record TxResp(long Confirmations, long? Height, decimal BalanceChange, DateTimeOffset Timestamp, string TransactionId) { public override string ToString() diff --git a/BTCPayServer/App/BTCPayAppHub.cs b/BTCPayServer/App/BTCPayAppHub.cs index e05b0ddf7..e438da287 100644 --- a/BTCPayServer/App/BTCPayAppHub.cs +++ b/BTCPayServer/App/BTCPayAppHub.cs @@ -8,14 +8,17 @@ using BTCPayApp.CommonServer; using BTCPayApp.CommonServer.Models; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Data; +using BTCPayServer.Events; using BTCPayServer.HostedServices; using BTCPayServer.Services; +using BTCPayServer.Services.Stores; using BTCPayServer.Services.Wallets; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Logging; using NBitcoin; +using NBXplorer; using NBXplorer.DerivationStrategy; using NBXplorer.Models; using Newtonsoft.Json; @@ -97,6 +100,9 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer private readonly IFeeProviderFactory _feeProviderFactory; private readonly ILogger _logger; private readonly UserManager _userManager; + private readonly StoreRepository _storeRepository; + private readonly EventAggregator _eventAggregator; + private CompositeDisposable? _compositeDisposable; public BTCPayAppHub(BTCPayNetworkProvider btcPayNetworkProvider, NBXplorerDashboard nbXplorerDashboard, @@ -104,6 +110,8 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer ExplorerClientProvider explorerClientProvider, IFeeProviderFactory feeProviderFactory, ILogger logger, + StoreRepository storeRepository, + EventAggregator eventAggregator, UserManager userManager) { _btcPayNetworkProvider = btcPayNetworkProvider; @@ -113,35 +121,53 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer _feeProviderFactory = feeProviderFactory; _logger = logger; _userManager = userManager; + _storeRepository = storeRepository; + _eventAggregator = eventAggregator; } - public override async Task OnConnectedAsync() { - await _appState.Connected(Context.ConnectionId); - //TODO: this needs to happen BEFORE connection is established + // TODO: this needs to happen BEFORE connection is established if (!_nbXplorerDashboard.IsFullySynched(_btcPayNetworkProvider.BTC.CryptoCode, out _)) { Context.Abort(); return; } - - await Groups.AddToGroupAsync(Context.ConnectionId,_userManager.GetUserId(Context.User)); + var userId = _userManager.GetUserId(Context.User!)!; + var userStores = await _storeRepository.GetStoresByUserId(userId); await Clients.Client(Context.ConnectionId).NotifyNetwork(_btcPayNetworkProvider.BTC.NBitcoinNetwork.ToString()); + await Groups.AddToGroupAsync(Context.ConnectionId, userId); + foreach (var userStore in userStores) + { + await Groups.AddToGroupAsync(Context.ConnectionId, userStore.Id); + } - + _compositeDisposable = new CompositeDisposable(); + _compositeDisposable.Add(_eventAggregator.SubscribeAsync(StoreUserAddedEvent)); + _compositeDisposable.Add(_eventAggregator.SubscribeAsync(StoreUserRemovedEvent)); } - public override async Task OnDisconnectedAsync(Exception? exception) { + _compositeDisposable?.Dispose(); await _appState.Disconnected(Context.ConnectionId); await base.OnDisconnectedAsync(exception); } + + private async Task StoreUserAddedEvent(UserStoreAddedEvent arg) + { + // TODO: Groups and COntext are not accessible here + // await Groups.AddToGroupAsync(Context.ConnectionId, arg.StoreId); + } + private async Task StoreUserRemovedEvent(UserStoreRemovedEvent arg) + { + // TODO: Groups and COntext are not accessible here + // await Groups.RemoveFromGroupAsync(Context.ConnectionId, arg.UserId); + } public async Task BroadcastTransaction(string tx) { @@ -158,15 +184,12 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer var feeProvider = _feeProviderFactory.CreateFeeProvider( _btcPayNetworkProvider.BTC); try { - return (await feeProvider.GetFeeRateAsync(blockTarget)).SatoshiPerByte; } finally { _logger.LogInformation($"Getting fee rate for {blockTarget} done"); - } - } public async Task GetBestBlock() @@ -318,22 +341,16 @@ var resultPsbt = PSBT.Parse(psbt, explorerClient.Network.NBitcoinNetwork); await _appState.PaymentUpdate(identifier, lightningPayment); } - public async Task IdentifierActive(string group, bool active) { - return await _appState.IdentifierActive(group, Context.ConnectionId, active); } - public async Task> Pair(PairRequest request) { return await _appState.Pair(Context.ConnectionId, request); - - } - public async Task Handshake(AppHandshake request) { diff --git a/BTCPayServer/App/BTCPayAppState.cs b/BTCPayServer/App/BTCPayAppState.cs index 19b16ef43..a851e2846 100644 --- a/BTCPayServer/App/BTCPayAppState.cs +++ b/BTCPayServer/App/BTCPayAppState.cs @@ -3,10 +3,8 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Threading; using System.Threading.Tasks; -using AngleSharp.Dom.Events; using BTCPayApp.CommonServer; using BTCPayServer.Events; using BTCPayServer.Lightning; @@ -37,6 +35,8 @@ public class BTCPayAppState : IHostedService public ExplorerClient ExplorerClient { get; private set; } private DerivationSchemeParser _derivationSchemeParser; + public readonly ConcurrentDictionary GroupToConnectionId = new(StringComparer.InvariantCultureIgnoreCase); + private CancellationTokenSource? _cts; // private readonly ConcurrentDictionary _connectionScheme = new(); public event EventHandler<(string,LightningPayment)>? OnPaymentUpdate; @@ -60,20 +60,66 @@ public class BTCPayAppState : IHostedService _cts ??= new CancellationTokenSource(); ExplorerClient = _explorerClientProvider.GetExplorerClient("BTC"); _derivationSchemeParser = new DerivationSchemeParser(_networkProvider.BTC); - _compositeDisposable = new(); - _compositeDisposable.Add( - _eventAggregator.Subscribe(OnNewBlock)); - _compositeDisposable.Add( - _eventAggregator.SubscribeAsync(OnNewTransaction)); - _compositeDisposable.Add( - _eventAggregator.SubscribeAsync(UserNotificationsUpdatedEvent)); + _compositeDisposable = new CompositeDisposable(); + _compositeDisposable.Add(_eventAggregator.Subscribe(OnNewBlock)); + _compositeDisposable.Add(_eventAggregator.SubscribeAsync(OnNewTransaction)); + _compositeDisposable.Add(_eventAggregator.SubscribeAsync(UserNotificationsUpdatedEvent)); + _compositeDisposable.Add(_eventAggregator.SubscribeAsync(InvoiceChangedEvent)); + // Store events + _compositeDisposable.Add(_eventAggregator.SubscribeAsync(StoreCreatedEvent)); + _compositeDisposable.Add(_eventAggregator.SubscribeAsync(StoreUpdatedEvent)); + _compositeDisposable.Add(_eventAggregator.SubscribeAsync(StoreRemovedEvent)); + _compositeDisposable.Add(_eventAggregator.SubscribeAsync(StoreUserAddedEvent)); + _compositeDisposable.Add(_eventAggregator.SubscribeAsync(StoreUserUpdatedEvent)); + _compositeDisposable.Add(_eventAggregator.SubscribeAsync(StoreUserRemovedEvent)); _ = UpdateNodeInfo(); return Task.CompletedTask; } + private async Task InvoiceChangedEvent(InvoiceEvent arg) + { + await _hubContext.Clients.Group(arg.Invoice.StoreId).NotifyServerEvent(new ServerEvent("invoice-updated", arg)); + } + private async Task UserNotificationsUpdatedEvent(UserNotificationsUpdatedEvent arg) { - await _hubContext.Clients.Group(arg.UserId).NotifyServerEvent("notifications-updated"); + await _hubContext.Clients.Group(arg.UserId).NotifyServerEvent(new ServerEvent("notifications-updated", arg)); + } + + private async Task StoreCreatedEvent(StoreCreatedEvent arg) + { + await _hubContext.Clients.Group(arg.Store.Id).NotifyServerEvent(new ServerEvent("store-created", arg)); + } + + private async Task StoreUpdatedEvent(StoreUpdatedEvent arg) + { + await _hubContext.Clients.Group(arg.Store.Id).NotifyServerEvent(new ServerEvent("store-updated", arg)); + } + + private async Task StoreRemovedEvent(StoreRemovedEvent arg) + { + await _hubContext.Clients.Group(arg.StoreId).NotifyServerEvent(new ServerEvent("store-removed", arg)); + } + + private async Task StoreUserAddedEvent(UserStoreAddedEvent arg) + { + var ev = new ServerEvent("user-store-added", arg); + await _hubContext.Clients.Group(arg.StoreId).NotifyServerEvent(ev); + await _hubContext.Clients.Group(arg.UserId).NotifyServerEvent(ev); + } + + private async Task StoreUserUpdatedEvent(UserStoreUpdatedEvent arg) + { + var ev = new ServerEvent("user-store-updated", arg); + await _hubContext.Clients.Group(arg.StoreId).NotifyServerEvent(ev); + await _hubContext.Clients.Group(arg.UserId).NotifyServerEvent(ev); + } + + private async Task StoreUserRemovedEvent(UserStoreRemovedEvent arg) + { + var ev = new ServerEvent("user-store-removed", arg); + await _hubContext.Clients.Group(arg.StoreId).NotifyServerEvent(ev); + await _hubContext.Clients.Group(arg.UserId).NotifyServerEvent(ev); } private string _nodeInfo = string.Empty; @@ -81,8 +127,6 @@ public class BTCPayAppState : IHostedService { while (!_cts.Token.IsCancellationRequested) { - - try { var res = await _serviceProvider.GetRequiredService().GetLightningHandler(ExplorerClient.CryptoCode).GetNodeInfo( @@ -112,7 +156,6 @@ public class BTCPayAppState : IHostedService } await Task.Delay(TimeSpan.FromMinutes(string.IsNullOrEmpty(_nodeInfo)? 1:5), _cts.Token); } - } private async Task OnNewTransaction(NewOnChainTransactionEvent obj) @@ -202,9 +245,6 @@ public class BTCPayAppState : IHostedService return result; } - public readonly ConcurrentDictionary GroupToConnectionId = new(StringComparer.InvariantCultureIgnoreCase); - private CancellationTokenSource _cts; - public async Task IdentifierActive(string group, string contextConnectionId, bool active) { @@ -234,7 +274,6 @@ public class BTCPayAppState : IHostedService { GroupRemoved?.Invoke(this, group); } - } } diff --git a/BTCPayServer/Events/StoreCreatedEvent.cs b/BTCPayServer/Events/StoreCreatedEvent.cs new file mode 100644 index 000000000..362a630f1 --- /dev/null +++ b/BTCPayServer/Events/StoreCreatedEvent.cs @@ -0,0 +1,13 @@ +using BTCPayServer.Data; + +namespace BTCPayServer.Events; + +public class StoreCreatedEvent(StoreData store) +{ + public StoreData Store { get; } = store; + + public override string ToString() + { + return $"Store \"{Store.StoreName}\" has been created"; + } +} diff --git a/BTCPayServer/Events/StoreRemovedEvent.cs b/BTCPayServer/Events/StoreRemovedEvent.cs index 55464d83c..465a628e7 100644 --- a/BTCPayServer/Events/StoreRemovedEvent.cs +++ b/BTCPayServer/Events/StoreRemovedEvent.cs @@ -1,12 +1,9 @@ namespace BTCPayServer.Events; -public class StoreRemovedEvent +public class StoreRemovedEvent(string storeId) { - public StoreRemovedEvent(string storeId) - { - StoreId = storeId; - } - public string StoreId { get; set; } + public string StoreId { get; } = storeId; + public override string ToString() { return $"Store {StoreId} has been removed"; diff --git a/BTCPayServer/Events/StoreUpdatedEvent.cs b/BTCPayServer/Events/StoreUpdatedEvent.cs new file mode 100644 index 000000000..e11b6170d --- /dev/null +++ b/BTCPayServer/Events/StoreUpdatedEvent.cs @@ -0,0 +1,13 @@ +using BTCPayServer.Data; + +namespace BTCPayServer.Events; + +public class StoreUpdatedEvent(StoreData store) +{ + public StoreData Store { get; } = store; + + public override string ToString() + { + return $"Store \"{Store.StoreName}\" has been updated"; + } +} diff --git a/BTCPayServer/Events/UserStoreAddedEvent.cs b/BTCPayServer/Events/UserStoreAddedEvent.cs new file mode 100644 index 000000000..666f778fe --- /dev/null +++ b/BTCPayServer/Events/UserStoreAddedEvent.cs @@ -0,0 +1,9 @@ +namespace BTCPayServer.Events; + +public class UserStoreAddedEvent(string storeId, string userId) : UserStoreEvent(storeId, userId) +{ + public override string ToString() + { + return $"User {UserId} has been added to store {StoreId}"; + } +} diff --git a/BTCPayServer/Events/UserStoreEvent.cs b/BTCPayServer/Events/UserStoreEvent.cs new file mode 100644 index 000000000..b300dfa89 --- /dev/null +++ b/BTCPayServer/Events/UserStoreEvent.cs @@ -0,0 +1,11 @@ +namespace BTCPayServer.Events; + +public abstract class UserStoreEvent(string storeId, string userId) +{ + public string StoreId { get; } = storeId; + public string UserId { get; } = userId; + public new virtual string ToString() + { + return $"StoreUserEvent: User {UserId}, Store {StoreId}"; + } +} diff --git a/BTCPayServer/Events/UserStoreRemovedEvent.cs b/BTCPayServer/Events/UserStoreRemovedEvent.cs new file mode 100644 index 000000000..5e3bc7e47 --- /dev/null +++ b/BTCPayServer/Events/UserStoreRemovedEvent.cs @@ -0,0 +1,9 @@ +namespace BTCPayServer.Events; + +public class UserStoreRemovedEvent(string storeId, string userId) : UserStoreEvent(storeId, userId) +{ + public override string ToString() + { + return $"User {UserId} has been removed from store {StoreId}"; + } +} diff --git a/BTCPayServer/Events/UserStoreUpdatedEvent.cs b/BTCPayServer/Events/UserStoreUpdatedEvent.cs new file mode 100644 index 000000000..c71ed1dce --- /dev/null +++ b/BTCPayServer/Events/UserStoreUpdatedEvent.cs @@ -0,0 +1,9 @@ +namespace BTCPayServer.Events; + +public class UserStoreUpdatedEvent(string storeId, string userId) : UserStoreEvent(storeId, userId) +{ + public override string ToString() + { + return $"User {UserId} and store {StoreId} relation has been updated"; + } +} diff --git a/BTCPayServer/Services/Stores/StoreRepository.cs b/BTCPayServer/Services/Stores/StoreRepository.cs index f033965c2..b9d4cde9c 100644 --- a/BTCPayServer/Services/Stores/StoreRepository.cs +++ b/BTCPayServer/Services/Stores/StoreRepository.cs @@ -295,6 +295,7 @@ namespace BTCPayServer.Services.Stores try { await ctx.SaveChangesAsync(); + _eventAggregator.Publish(new UserStoreAddedEvent(storeId, userId)); return true; } catch (DbUpdateException) @@ -310,10 +311,12 @@ namespace BTCPayServer.Services.Stores roleId ??= await GetDefaultRole(); await using var ctx = _ContextFactory.CreateContext(); var userStore = await ctx.UserStore.FindAsync(userId, storeId); + var added = false; if (userStore is null) { userStore = new UserStore { StoreDataId = storeId, ApplicationUserId = userId }; ctx.UserStore.Add(userStore); + added = true; } if (userStore.StoreRoleId == roleId.Id) @@ -323,6 +326,10 @@ namespace BTCPayServer.Services.Stores try { await ctx.SaveChangesAsync(); + UserStoreEvent evt = added + ? new UserStoreAddedEvent(storeId, userId) + : new UserStoreUpdatedEvent(storeId, userId); + _eventAggregator.Publish(evt); return true; } catch (DbUpdateException) @@ -364,8 +371,8 @@ namespace BTCPayServer.Services.Stores ctx.UserStore.Add(userStore); ctx.Entry(userStore).State = EntityState.Deleted; await ctx.SaveChangesAsync(); + _eventAggregator.Publish(new UserStoreRemovedEvent(storeId, userId)); return true; - } private async Task DeleteStoreIfOrphan(string storeId) @@ -404,6 +411,8 @@ namespace BTCPayServer.Services.Stores ctx.Add(storeData); ctx.Add(userStore); await ctx.SaveChangesAsync(); + _eventAggregator.Publish(new StoreCreatedEvent(storeData)); + _eventAggregator.Publish(new UserStoreAddedEvent(storeData.Id, userStore.ApplicationUserId)); } public async Task GetWebhooks(string storeId) @@ -552,6 +561,7 @@ namespace BTCPayServer.Services.Stores { ctx.Entry(existing).CurrentValues.SetValues(store); await ctx.SaveChangesAsync().ConfigureAwait(false); + _eventAggregator.Publish(new StoreUpdatedEvent(store)); } } @@ -573,6 +583,7 @@ retry: try { await ctx.SaveChangesAsync(); + _eventAggregator.Publish(new StoreRemovedEvent(store.Id)); } catch (DbUpdateException ex) when (IsDeadlock(ex) && retry < 5) { From fbdeec84e398315d64871a14c0c202e68d3e447d Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Thu, 27 Jun 2024 16:31:20 +0200 Subject: [PATCH 60/64] Add store logo --- BTCPayApp.CommonServer/Models/AppUserInfo.cs | 1 + BTCPayServer/App/API/AppApiController.Account.cs | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/BTCPayApp.CommonServer/Models/AppUserInfo.cs b/BTCPayApp.CommonServer/Models/AppUserInfo.cs index 7bdffa9ac..7009563ca 100644 --- a/BTCPayApp.CommonServer/Models/AppUserInfo.cs +++ b/BTCPayApp.CommonServer/Models/AppUserInfo.cs @@ -59,6 +59,7 @@ public class AppUserStoreInfo { public string? Id { get; set; } public string? Name { get; set; } + public string? LogoUrl { get; set; } public string? RoleId { get; set; } public string? PosAppId { get; set; } public string? DefaultCurrency { get; set; } diff --git a/BTCPayServer/App/API/AppApiController.Account.cs b/BTCPayServer/App/API/AppApiController.Account.cs index b3985e843..cb12f1b94 100644 --- a/BTCPayServer/App/API/AppApiController.Account.cs +++ b/BTCPayServer/App/API/AppApiController.Account.cs @@ -274,7 +274,10 @@ public partial class AppApiController RoleId = userStore.StoreRole.Id, PosAppId = posApp?.Id, DefaultCurrency = storeBlob.DefaultCurrency, - Permissions = userStore.StoreRole.Permissions + Permissions = userStore.StoreRole.Permissions, + LogoUrl = storeBlob.LogoUrl != null + ? await uriResolver.Resolve(Request.GetAbsoluteRootUri(), storeBlob.LogoUrl) + : null, }); } From 055276ead9bf1184849f807eddfab7f38063fe61 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Fri, 28 Jun 2024 10:48:23 +0200 Subject: [PATCH 61/64] Fix build and test --- .../{POSDataParser.cs => PosDataParser.cs} | 3 ++- BTCPayServer.Tests/FastTests.cs | 15 ++--------- btcpayserver.sln | 26 +++++++++++++++++++ 3 files changed, 30 insertions(+), 14 deletions(-) rename BTCPayServer.Abstractions/Converters/{POSDataParser.cs => PosDataParser.cs} (96%) diff --git a/BTCPayServer.Abstractions/Converters/POSDataParser.cs b/BTCPayServer.Abstractions/Converters/PosDataParser.cs similarity index 96% rename from BTCPayServer.Abstractions/Converters/POSDataParser.cs rename to BTCPayServer.Abstractions/Converters/PosDataParser.cs index f942584d7..f3a7dffe1 100644 --- a/BTCPayServer.Abstractions/Converters/POSDataParser.cs +++ b/BTCPayServer.Abstractions/Converters/PosDataParser.cs @@ -1,10 +1,11 @@ +#nullable enable using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; namespace BTCPayServer.Abstractions.Converters; -public class PosDataParser +public static class PosDataParser { public static Dictionary ParsePosData(JToken? posData) { diff --git a/BTCPayServer.Tests/FastTests.cs b/BTCPayServer.Tests/FastTests.cs index 76f21e51a..c6b93a86d 100644 --- a/BTCPayServer.Tests/FastTests.cs +++ b/BTCPayServer.Tests/FastTests.cs @@ -3,15 +3,14 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Reflection.Metadata; using System.Runtime.CompilerServices; using System.Security; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using BTCPayServer.Abstractions.Converters; using BTCPayServer.Abstractions.Extensions; -using BTCPayServer.Abstractions.Models; using BTCPayServer.Client; using BTCPayServer.Client.Models; using BTCPayServer.Configuration; @@ -20,30 +19,21 @@ using BTCPayServer.Data; using BTCPayServer.HostedServices; using BTCPayServer.Hosting; using BTCPayServer.JsonConverters; -using BTCPayServer.Logging; using BTCPayServer.Payments; -using BTCPayServer.Payments.Bitcoin; -using BTCPayServer.Payments.Lightning; -using BTCPayServer.Plugins; -using BTCPayServer.Plugins.Bitcoin; using BTCPayServer.Rating; using BTCPayServer.Services; using BTCPayServer.Services.Apps; using BTCPayServer.Services.Fees; using BTCPayServer.Services.Invoices; -using BTCPayServer.Services.Labels; using BTCPayServer.Services.Rates; using BTCPayServer.Services.Stores; using BTCPayServer.Validation; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration.Memory; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using NBitcoin; -using NBitcoin.DataEncoders; using NBitcoin.Scripting.Parser; -using NBXplorer; using NBXplorer.DerivationStrategy; using NBXplorer.Models; using Newtonsoft.Json; @@ -51,7 +41,6 @@ using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; using Xunit; using Xunit.Abstractions; -using StoreData = BTCPayServer.Data.StoreData; namespace BTCPayServer.Tests { @@ -1628,7 +1617,7 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku testCases.ForEach(tuple => { - Assert.Equal(tuple.expectedOutput, UIInvoiceController.PosDataParser.ParsePosData(string.IsNullOrEmpty(tuple.input) ? null : JToken.Parse(tuple.input))); + Assert.Equal(tuple.expectedOutput, PosDataParser.ParsePosData(string.IsNullOrEmpty(tuple.input) ? null : JToken.Parse(tuple.input))); }); } [Fact] diff --git a/btcpayserver.sln b/btcpayserver.sln index b4c3f6030..91d233933 100644 --- a/btcpayserver.sln +++ b/btcpayserver.sln @@ -32,6 +32,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BTCPayServer.Abstractions", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BTCPayServer.PluginPacker", "BTCPayServer.PluginPacker\BTCPayServer.PluginPacker.csproj", "{7DC94B25-1CFC-4170-AA41-7BA983E4C0B8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayApp.CommonServer", "BTCPayApp.CommonServer\BTCPayApp.CommonServer.csproj", "{F204A9E1-52D8-4B67-BE53-5C6B589B64A0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Altcoins-Debug|Any CPU = Altcoins-Debug|Any CPU @@ -240,6 +242,30 @@ Global {7DC94B25-1CFC-4170-AA41-7BA983E4C0B8}.Release|x64.Build.0 = Release|Any CPU {7DC94B25-1CFC-4170-AA41-7BA983E4C0B8}.Release|x86.ActiveCfg = Release|Any CPU {7DC94B25-1CFC-4170-AA41-7BA983E4C0B8}.Release|x86.Build.0 = Release|Any CPU + {F204A9E1-52D8-4B67-BE53-5C6B589B64A0}.Altcoins-Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F204A9E1-52D8-4B67-BE53-5C6B589B64A0}.Altcoins-Debug|Any CPU.Build.0 = Debug|Any CPU + {F204A9E1-52D8-4B67-BE53-5C6B589B64A0}.Altcoins-Debug|x64.ActiveCfg = Debug|Any CPU + {F204A9E1-52D8-4B67-BE53-5C6B589B64A0}.Altcoins-Debug|x64.Build.0 = Debug|Any CPU + {F204A9E1-52D8-4B67-BE53-5C6B589B64A0}.Altcoins-Debug|x86.ActiveCfg = Debug|Any CPU + {F204A9E1-52D8-4B67-BE53-5C6B589B64A0}.Altcoins-Debug|x86.Build.0 = Debug|Any CPU + {F204A9E1-52D8-4B67-BE53-5C6B589B64A0}.Altcoins-Release|Any CPU.ActiveCfg = Debug|Any CPU + {F204A9E1-52D8-4B67-BE53-5C6B589B64A0}.Altcoins-Release|Any CPU.Build.0 = Debug|Any CPU + {F204A9E1-52D8-4B67-BE53-5C6B589B64A0}.Altcoins-Release|x64.ActiveCfg = Debug|Any CPU + {F204A9E1-52D8-4B67-BE53-5C6B589B64A0}.Altcoins-Release|x64.Build.0 = Debug|Any CPU + {F204A9E1-52D8-4B67-BE53-5C6B589B64A0}.Altcoins-Release|x86.ActiveCfg = Debug|Any CPU + {F204A9E1-52D8-4B67-BE53-5C6B589B64A0}.Altcoins-Release|x86.Build.0 = Debug|Any CPU + {F204A9E1-52D8-4B67-BE53-5C6B589B64A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F204A9E1-52D8-4B67-BE53-5C6B589B64A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F204A9E1-52D8-4B67-BE53-5C6B589B64A0}.Debug|x64.ActiveCfg = Debug|Any CPU + {F204A9E1-52D8-4B67-BE53-5C6B589B64A0}.Debug|x64.Build.0 = Debug|Any CPU + {F204A9E1-52D8-4B67-BE53-5C6B589B64A0}.Debug|x86.ActiveCfg = Debug|Any CPU + {F204A9E1-52D8-4B67-BE53-5C6B589B64A0}.Debug|x86.Build.0 = Debug|Any CPU + {F204A9E1-52D8-4B67-BE53-5C6B589B64A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F204A9E1-52D8-4B67-BE53-5C6B589B64A0}.Release|Any CPU.Build.0 = Release|Any CPU + {F204A9E1-52D8-4B67-BE53-5C6B589B64A0}.Release|x64.ActiveCfg = Release|Any CPU + {F204A9E1-52D8-4B67-BE53-5C6B589B64A0}.Release|x64.Build.0 = Release|Any CPU + {F204A9E1-52D8-4B67-BE53-5C6B589B64A0}.Release|x86.ActiveCfg = Release|Any CPU + {F204A9E1-52D8-4B67-BE53-5C6B589B64A0}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From e3afd3f933d6d637f6ac3e94dc522fb8ab2299f1 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Fri, 28 Jun 2024 18:48:40 +0200 Subject: [PATCH 62/64] Cleanups --- BTCPayServer.Common/BTCPayNetwork.cs | 2 -- BTCPayServer.Common/BTCPayNetworkProvider.cs | 4 ---- BTCPayServer.Common/BTCPayServer.Common.csproj | 2 -- BTCPayServer.Common/MultiProcessingQueue.cs | 2 -- BTCPayServer.Common/SelectedChains.cs | 1 - 5 files changed, 11 deletions(-) diff --git a/BTCPayServer.Common/BTCPayNetwork.cs b/BTCPayServer.Common/BTCPayNetwork.cs index dde08a9ae..4e4b2ef25 100644 --- a/BTCPayServer.Common/BTCPayNetwork.cs +++ b/BTCPayServer.Common/BTCPayNetwork.cs @@ -4,8 +4,6 @@ using System.Globalization; using System.IO; using System.Linq; using BTCPayServer.Common; -using Microsoft.AspNetCore.HttpOverrides; -using Microsoft.Extensions.DependencyInjection; using NBitcoin; using NBXplorer; using NBXplorer.Models; diff --git a/BTCPayServer.Common/BTCPayNetworkProvider.cs b/BTCPayServer.Common/BTCPayNetworkProvider.cs index c62317a9b..7a586d2ce 100644 --- a/BTCPayServer.Common/BTCPayNetworkProvider.cs +++ b/BTCPayServer.Common/BTCPayNetworkProvider.cs @@ -3,13 +3,9 @@ using System.Collections.Generic; using System.Linq; using BTCPayServer.Configuration; using BTCPayServer.Logging; -using Microsoft.AspNetCore.DataProtection.KeyManagement; -using Microsoft.AspNetCore.HttpOverrides; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using NBitcoin; using NBXplorer; -using StandardConfiguration; namespace BTCPayServer { diff --git a/BTCPayServer.Common/BTCPayServer.Common.csproj b/BTCPayServer.Common/BTCPayServer.Common.csproj index 3cdf1ac7a..9c85b2c72 100644 --- a/BTCPayServer.Common/BTCPayServer.Common.csproj +++ b/BTCPayServer.Common/BTCPayServer.Common.csproj @@ -1,9 +1,7 @@ - - diff --git a/BTCPayServer.Common/MultiProcessingQueue.cs b/BTCPayServer.Common/MultiProcessingQueue.cs index 93674eab8..481e252a8 100644 --- a/BTCPayServer.Common/MultiProcessingQueue.cs +++ b/BTCPayServer.Common/MultiProcessingQueue.cs @@ -1,7 +1,5 @@ -using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; diff --git a/BTCPayServer.Common/SelectedChains.cs b/BTCPayServer.Common/SelectedChains.cs index d8d05a77f..c490e2d27 100644 --- a/BTCPayServer.Common/SelectedChains.cs +++ b/BTCPayServer.Common/SelectedChains.cs @@ -4,7 +4,6 @@ using System.Linq; using BTCPayServer.Logging; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using Newtonsoft.Json.Bson; namespace BTCPayServer { From e1888a038c778a2545f1dcc0fea2615b04e3ff7d Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Tue, 9 Jul 2024 14:35:18 +0200 Subject: [PATCH 63/64] Fix server event communication --- BTCPayApp.CommonServer/IBTCPayAppHubClient.cs | 65 +++++++++---------- BTCPayServer/App/BTCPayAppHub.cs | 44 +++---------- BTCPayServer/App/BTCPayAppState.cs | 20 +++--- BTCPayServer/Events/StoreCreatedEvent.cs | 8 +-- BTCPayServer/Events/StoreEvent.cs | 13 ++++ BTCPayServer/Events/StoreRemovedEvent.cs | 10 +-- BTCPayServer/Events/StoreUpdatedEvent.cs | 8 +-- BTCPayServer/Events/UserStoreAddedEvent.cs | 4 +- BTCPayServer/Events/UserStoreEvent.cs | 3 +- BTCPayServer/Events/UserStoreRemovedEvent.cs | 4 +- BTCPayServer/Events/UserStoreUpdatedEvent.cs | 4 +- .../Services/Stores/StoreRepository.cs | 8 +-- 12 files changed, 82 insertions(+), 109 deletions(-) create mode 100644 BTCPayServer/Events/StoreEvent.cs diff --git a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs index aa93c2cfb..794c51f14 100644 --- a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs +++ b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs @@ -3,26 +3,14 @@ using System.Collections.Generic; using System.Threading.Tasks; using BTCPayServer.Client.Models; using BTCPayServer.Lightning; -using NBitcoin; using LightningPayment = BTCPayApp.CommonServer.Models.LightningPayment; namespace BTCPayApp.CommonServer; - -public class TransactionDetectedRequest -{ - public string Identifier { get; set; } - public string TxId { get; set; } - public string[] SpentScripts { get; set; } - public string[] ReceivedScripts { get; set; } - public bool Confirmed { get; set; } -} - - //methods available on the hub in the client public interface IBTCPayAppHubClient { - Task NotifyServerEvent(IServerEvent ev); + Task NotifyServerEvent(ServerEvent ev); Task NotifyNetwork(string network); Task NotifyServerNode(string nodeInfo); Task TransactionDetected(TransactionDetectedRequest request); @@ -36,6 +24,28 @@ public interface IBTCPayAppHubClient Task PayInvoice(string bolt11, long? amountMilliSatoshi); } +//methods available on the hub in the server +public interface IBTCPayAppHubServer +{ + Task IdentifierActive(string group, bool active); + + Task> Pair(PairRequest request); + Task Handshake(AppHandshake request); + Task BroadcastTransaction(string tx); + Task GetFeeRate(int blockTarget); + Task GetBestBlock(); + Task GetBlockHeader(string hash); + + Task FetchTxsAndTheirBlockHeads(string[] txIds); + Task DeriveScript(string identifier); + Task TrackScripts(string identifier, string[] scripts); + Task UpdatePsbt(string[] identifiers, string psbt); + Task GetUTXOs(string[] identifiers); + Task> GetTransactions(string[] identifiers); + + Task SendPaymentUpdate(string identifier, LightningPayment lightningPayment); +} + public interface IServerEvent { public string Type { get; } @@ -63,27 +73,13 @@ public record TxResp(long Confirmations, long? Height, decimal BalanceChange, Da } } -//methods available on the hub in the server -public interface IBTCPayAppHubServer +public class TransactionDetectedRequest { - Task IdentifierActive(string group, bool active); - - Task> Pair(PairRequest request); - Task Handshake(AppHandshake request); - Task BroadcastTransaction(string tx); - Task GetFeeRate(int blockTarget); - Task GetBestBlock(); - Task GetBlockHeader(string hash); - - Task FetchTxsAndTheirBlockHeads(string[] txIds); - Task DeriveScript(string identifier); - Task TrackScripts(string identifier, string[] scripts); - Task UpdatePsbt(string[] identifiers, string psbt); - Task GetUTXOs(string[] identifiers); - Task> GetTransactions(string[] identifiers); - - - Task SendPaymentUpdate(string identifier, LightningPayment lightningPayment); + public string Identifier { get; set; } + public string TxId { get; set; } + public string[] SpentScripts { get; set; } + public string[] ReceivedScripts { get; set; } + public bool Confirmed { get; set; } } public class CoinResponse @@ -108,15 +104,12 @@ public class TransactionResponse public string? BlockHash { get; set; } public int? BlockHeight { get; set; } public string Transaction { get; set; } - } - public class BestBlockResponse { public required string BlockHash { get; set; } public required int BlockHeight { get; set; } - public string BlockHeader { get; set; } } diff --git a/BTCPayServer/App/BTCPayAppHub.cs b/BTCPayServer/App/BTCPayAppHub.cs index e438da287..76bb5065c 100644 --- a/BTCPayServer/App/BTCPayAppHub.cs +++ b/BTCPayServer/App/BTCPayAppHub.cs @@ -101,8 +101,6 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer private readonly ILogger _logger; private readonly UserManager _userManager; private readonly StoreRepository _storeRepository; - private readonly EventAggregator _eventAggregator; - private CompositeDisposable? _compositeDisposable; public BTCPayAppHub(BTCPayNetworkProvider btcPayNetworkProvider, NBXplorerDashboard nbXplorerDashboard, @@ -111,7 +109,6 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer IFeeProviderFactory feeProviderFactory, ILogger logger, StoreRepository storeRepository, - EventAggregator eventAggregator, UserManager userManager) { _btcPayNetworkProvider = btcPayNetworkProvider; @@ -122,7 +119,6 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer _logger = logger; _userManager = userManager; _storeRepository = storeRepository; - _eventAggregator = eventAggregator; } public override async Task OnConnectedAsync() @@ -144,30 +140,13 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer { await Groups.AddToGroupAsync(Context.ConnectionId, userStore.Id); } - - _compositeDisposable = new CompositeDisposable(); - _compositeDisposable.Add(_eventAggregator.SubscribeAsync(StoreUserAddedEvent)); - _compositeDisposable.Add(_eventAggregator.SubscribeAsync(StoreUserRemovedEvent)); } public override async Task OnDisconnectedAsync(Exception? exception) { - _compositeDisposable?.Dispose(); await _appState.Disconnected(Context.ConnectionId); await base.OnDisconnectedAsync(exception); } - - private async Task StoreUserAddedEvent(UserStoreAddedEvent arg) - { - // TODO: Groups and COntext are not accessible here - // await Groups.AddToGroupAsync(Context.ConnectionId, arg.StoreId); - } - - private async Task StoreUserRemovedEvent(UserStoreRemovedEvent arg) - { - // TODO: Groups and COntext are not accessible here - // await Groups.RemoveFromGroupAsync(Context.ConnectionId, arg.UserId); - } public async Task BroadcastTransaction(string tx) { @@ -198,8 +177,8 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC); var bcInfo = await explorerClient.RPCClient.GetBlockchainInfoAsyncEx(); var bh = await GetBlockHeader(bcInfo.BestBlockHash.ToString()); - _logger.LogInformation($"Getting best block done"); - return new BestBlockResponse() + _logger.LogInformation("Getting best block done"); + return new BestBlockResponse { BlockHash = bcInfo.BestBlockHash.ToString(), BlockHeight = bcInfo.Blocks, @@ -209,7 +188,6 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer public async Task GetBlockHeader(string hash) { - var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC); var bh = await explorerClient.RPCClient.GetBlockHeaderAsync(uint256.Parse(hash)); return Convert.ToHexString(bh.ToBytes()).ToLower(); @@ -217,28 +195,24 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer public async Task FetchTxsAndTheirBlockHeads(string[] txIds) { - var cancellationToken = Context.ConnectionAborted; var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC); var uints = txIds.Select(uint256.Parse).ToArray(); - var txsFetch = await Task.WhenAll(uints.Select( - uint256 => - explorerClient.GetTransactionAsync(uint256, cancellationToken))); + var txsFetch = await Task.WhenAll(uints.Select(uint256 => + explorerClient.GetTransactionAsync(uint256, cancellationToken))); var batch = explorerClient.RPCClient.PrepareBatch(); var headersTask = txsFetch.Where(result => result.BlockId is not null && result.BlockId != uint256.Zero) .Distinct().ToDictionary(result => result.BlockId, result => batch.GetBlockHeaderAsync(result.BlockId, cancellationToken)); await batch.SendBatchAsync(cancellationToken); - - var headerToHeight = (await Task.WhenAll(headersTask.Values)).ToDictionary(header => header.GetHash(), header => txsFetch.First(result => result.BlockId == header.GetHash()).Height!); - return new TxInfoResponse() + return new TxInfoResponse { - Txs = txsFetch.ToDictionary(tx => tx.TransactionHash.ToString(), tx => new TransactionResponse() + Txs = txsFetch.ToDictionary(tx => tx.TransactionHash.ToString(), tx => new TransactionResponse { BlockHash = tx.BlockId?.ToString(), BlockHeight = (int?) tx.Height, @@ -248,6 +222,7 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer BlockHeghts = headerToHeight.ToDictionary(kv => kv.Key.ToString(), kv =>(int) kv.Value!) }; } + public async Task DeriveScript(string identifier) { var cancellationToken = Context.ConnectionAborted; @@ -271,8 +246,8 @@ public class BTCPayAppHub : Hub, IBTCPayAppHubServer public async Task UpdatePsbt(string[] identifiers, string psbt) { - var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC); -var resultPsbt = PSBT.Parse(psbt, explorerClient.Network.NBitcoinNetwork); + var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC); + var resultPsbt = PSBT.Parse(psbt, explorerClient.Network.NBitcoinNetwork); foreach (string identifier in identifiers) { var ts = TrackedSource.Parse(identifier,explorerClient.Network); @@ -353,7 +328,6 @@ var resultPsbt = PSBT.Parse(psbt, explorerClient.Network.NBitcoinNetwork); public async Task Handshake(AppHandshake request) { - return await _appState.Handshake(Context.ConnectionId, request); } } diff --git a/BTCPayServer/App/BTCPayAppState.cs b/BTCPayServer/App/BTCPayAppState.cs index a851e2846..f0b4f15ea 100644 --- a/BTCPayServer/App/BTCPayAppState.cs +++ b/BTCPayServer/App/BTCPayAppState.cs @@ -88,12 +88,12 @@ public class BTCPayAppState : IHostedService private async Task StoreCreatedEvent(StoreCreatedEvent arg) { - await _hubContext.Clients.Group(arg.Store.Id).NotifyServerEvent(new ServerEvent("store-created", arg)); + await _hubContext.Clients.Group(arg.StoreId).NotifyServerEvent(new ServerEvent("store-created", arg)); } - private async Task StoreUpdatedEvent(StoreUpdatedEvent arg) + private async Task StoreUpdatedEvent(StoreUpdatedEvent arg) { - await _hubContext.Clients.Group(arg.Store.Id).NotifyServerEvent(new ServerEvent("store-updated", arg)); + await _hubContext.Clients.Group(arg.StoreId).NotifyServerEvent(new ServerEvent("store-updated", arg)); } private async Task StoreRemovedEvent(StoreRemovedEvent arg) @@ -106,6 +106,7 @@ public class BTCPayAppState : IHostedService var ev = new ServerEvent("user-store-added", arg); await _hubContext.Clients.Group(arg.StoreId).NotifyServerEvent(ev); await _hubContext.Clients.Group(arg.UserId).NotifyServerEvent(ev); + //await _hubContext.Clients.User(arg.UserId) .AddToGroupAsync(Context.ConnectionId, arg.StoreId); } private async Task StoreUserUpdatedEvent(UserStoreUpdatedEvent arg) @@ -120,6 +121,7 @@ public class BTCPayAppState : IHostedService var ev = new ServerEvent("user-store-removed", arg); await _hubContext.Clients.Group(arg.StoreId).NotifyServerEvent(ev); await _hubContext.Clients.Group(arg.UserId).NotifyServerEvent(ev); + // await Groups.RemoveFromGroupAsync(Context.ConnectionId, arg.UserId); } private string _nodeInfo = string.Empty; @@ -145,14 +147,11 @@ public class BTCPayAppState : IHostedService _nodeInfo = newInf.ToString(); await _hubContext.Clients.All.NotifyServerNode(_nodeInfo); } - } - } catch (Exception e) { _logger.LogError(e, "Error during node info update"); - } await Task.Delay(TimeSpan.FromMinutes(string.IsNullOrEmpty(_nodeInfo)? 1:5), _cts.Token); } @@ -167,10 +166,9 @@ public class BTCPayAppState : IHostedService var explorer = _explorerClientProvider.GetExplorerClient(obj.CryptoCode); var expandedTx = await explorer.GetTransactionAsync(obj.NewTransactionEvent.TrackedSource, obj.NewTransactionEvent.TransactionData.TransactionHash); - await _hubContext.Clients .Group(identifier) - .TransactionDetected(new TransactionDetectedRequest() + .TransactionDetected(new TransactionDetectedRequest { SpentScripts = expandedTx.Inputs.Select(input => input.ScriptPubKey.ToHex()).ToArray(), ReceivedScripts = expandedTx.Outputs.Select(output => output.ScriptPubKey.ToHex()).ToArray(), @@ -258,7 +256,7 @@ public class BTCPayAppState : IHostedService } return false; } - else if (GroupToConnectionId.TryGetValue(group, out var connId) && connId == contextConnectionId) + if (GroupToConnectionId.TryGetValue(group, out var connId) && connId == contextConnectionId) { return GroupToConnectionId.TryRemove(group, out _); } @@ -281,7 +279,7 @@ public class BTCPayAppState : IHostedService public async Task Connected(string contextConnectionId) { - if(_nodeInfo.Length > 0) + if (_nodeInfo.Length > 0) await _hubContext.Clients.Client(contextConnectionId).NotifyServerNode(_nodeInfo); } @@ -290,6 +288,4 @@ public class BTCPayAppState : IHostedService _logger.LogInformation($"Payment update for {identifier} {lightningPayment.Value} {lightningPayment.PaymentHash}"); OnPaymentUpdate?.Invoke(this, (identifier, lightningPayment)); } - - } diff --git a/BTCPayServer/Events/StoreCreatedEvent.cs b/BTCPayServer/Events/StoreCreatedEvent.cs index 362a630f1..1e29a6e65 100644 --- a/BTCPayServer/Events/StoreCreatedEvent.cs +++ b/BTCPayServer/Events/StoreCreatedEvent.cs @@ -2,12 +2,10 @@ using BTCPayServer.Data; namespace BTCPayServer.Events; -public class StoreCreatedEvent(StoreData store) +public class StoreCreatedEvent(StoreData store) : StoreEvent(store) { - public StoreData Store { get; } = store; - - public override string ToString() + protected override string ToString() { - return $"Store \"{Store.StoreName}\" has been created"; + return $"{base.ToString()} has been created"; } } diff --git a/BTCPayServer/Events/StoreEvent.cs b/BTCPayServer/Events/StoreEvent.cs new file mode 100644 index 000000000..a954240da --- /dev/null +++ b/BTCPayServer/Events/StoreEvent.cs @@ -0,0 +1,13 @@ +using BTCPayServer.Data; + +namespace BTCPayServer.Events; + +public class StoreEvent(StoreData store) +{ + public string StoreId { get; } = store.Id; + + protected new virtual string ToString() + { + return $"StoreEvent: Store \"{store.StoreName}\" ({store.Id})"; + } +} diff --git a/BTCPayServer/Events/StoreRemovedEvent.cs b/BTCPayServer/Events/StoreRemovedEvent.cs index 465a628e7..5b4d040eb 100644 --- a/BTCPayServer/Events/StoreRemovedEvent.cs +++ b/BTCPayServer/Events/StoreRemovedEvent.cs @@ -1,11 +1,11 @@ +using BTCPayServer.Data; + namespace BTCPayServer.Events; -public class StoreRemovedEvent(string storeId) +public class StoreRemovedEvent(StoreData store) : StoreEvent(store) { - public string StoreId { get; } = storeId; - - public override string ToString() + protected override string ToString() { - return $"Store {StoreId} has been removed"; + return $"{base.ToString()} has been removed"; } } diff --git a/BTCPayServer/Events/StoreUpdatedEvent.cs b/BTCPayServer/Events/StoreUpdatedEvent.cs index e11b6170d..4acd056d9 100644 --- a/BTCPayServer/Events/StoreUpdatedEvent.cs +++ b/BTCPayServer/Events/StoreUpdatedEvent.cs @@ -2,12 +2,10 @@ using BTCPayServer.Data; namespace BTCPayServer.Events; -public class StoreUpdatedEvent(StoreData store) +public class StoreUpdatedEvent(StoreData store) : StoreEvent(store) { - public StoreData Store { get; } = store; - - public override string ToString() + protected override string ToString() { - return $"Store \"{Store.StoreName}\" has been updated"; + return $"{base.ToString()} has been updated"; } } diff --git a/BTCPayServer/Events/UserStoreAddedEvent.cs b/BTCPayServer/Events/UserStoreAddedEvent.cs index 666f778fe..0061a41dc 100644 --- a/BTCPayServer/Events/UserStoreAddedEvent.cs +++ b/BTCPayServer/Events/UserStoreAddedEvent.cs @@ -2,8 +2,8 @@ namespace BTCPayServer.Events; public class UserStoreAddedEvent(string storeId, string userId) : UserStoreEvent(storeId, userId) { - public override string ToString() + protected override string ToString() { - return $"User {UserId} has been added to store {StoreId}"; + return $"{base.ToString()} has been added"; } } diff --git a/BTCPayServer/Events/UserStoreEvent.cs b/BTCPayServer/Events/UserStoreEvent.cs index b300dfa89..7ec6fe0f6 100644 --- a/BTCPayServer/Events/UserStoreEvent.cs +++ b/BTCPayServer/Events/UserStoreEvent.cs @@ -4,7 +4,8 @@ public abstract class UserStoreEvent(string storeId, string userId) { public string StoreId { get; } = storeId; public string UserId { get; } = userId; - public new virtual string ToString() + + protected new virtual string ToString() { return $"StoreUserEvent: User {UserId}, Store {StoreId}"; } diff --git a/BTCPayServer/Events/UserStoreRemovedEvent.cs b/BTCPayServer/Events/UserStoreRemovedEvent.cs index 5e3bc7e47..239741de3 100644 --- a/BTCPayServer/Events/UserStoreRemovedEvent.cs +++ b/BTCPayServer/Events/UserStoreRemovedEvent.cs @@ -2,8 +2,8 @@ namespace BTCPayServer.Events; public class UserStoreRemovedEvent(string storeId, string userId) : UserStoreEvent(storeId, userId) { - public override string ToString() + protected override string ToString() { - return $"User {UserId} has been removed from store {StoreId}"; + return $"{base.ToString()} has been removed"; } } diff --git a/BTCPayServer/Events/UserStoreUpdatedEvent.cs b/BTCPayServer/Events/UserStoreUpdatedEvent.cs index c71ed1dce..e82d8ca2f 100644 --- a/BTCPayServer/Events/UserStoreUpdatedEvent.cs +++ b/BTCPayServer/Events/UserStoreUpdatedEvent.cs @@ -2,8 +2,8 @@ namespace BTCPayServer.Events; public class UserStoreUpdatedEvent(string storeId, string userId) : UserStoreEvent(storeId, userId) { - public override string ToString() + protected override string ToString() { - return $"User {UserId} and store {StoreId} relation has been updated"; + return $"{base.ToString()} has been updated"; } } diff --git a/BTCPayServer/Services/Stores/StoreRepository.cs b/BTCPayServer/Services/Stores/StoreRepository.cs index b9d4cde9c..37b65ffc0 100644 --- a/BTCPayServer/Services/Stores/StoreRepository.cs +++ b/BTCPayServer/Services/Stores/StoreRepository.cs @@ -347,14 +347,14 @@ namespace BTCPayServer.Services.Stores public async Task CleanUnreachableStores() { await using var ctx = _ContextFactory.CreateContext(); - var events = new List(); + var events = new List(); foreach (var store in await ctx.Stores.Include(data => data.UserStores) .ThenInclude(store => store.StoreRole).Where(s => s.UserStores.All(u => !u.StoreRole.Permissions.Contains(Policies.CanModifyStoreSettings))) .ToArrayAsync()) { ctx.Stores.Remove(store); - events.Add(new Events.StoreRemovedEvent(store.Id)); + events.Add(new StoreRemovedEvent(store)); } await ctx.SaveChangesAsync(); events.ForEach(e => _eventAggregator.Publish(e)); @@ -385,7 +385,7 @@ namespace BTCPayServer.Services.Stores { ctx.Stores.Remove(store); await ctx.SaveChangesAsync(); - _eventAggregator.Publish(new StoreRemovedEvent(store.Id)); + _eventAggregator.Publish(new StoreRemovedEvent(store)); } } } @@ -583,7 +583,7 @@ retry: try { await ctx.SaveChangesAsync(); - _eventAggregator.Publish(new StoreRemovedEvent(store.Id)); + _eventAggregator.Publish(new StoreRemovedEvent(store)); } catch (DbUpdateException ex) when (IsDeadlock(ex) && retry < 5) { From 5b7dafe1426acc03d7c6e13a98b1c5a760f893d2 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Fri, 12 Jul 2024 07:56:50 +0200 Subject: [PATCH 64/64] Simplify server events --- BTCPayApp.CommonServer/IBTCPayAppHubClient.cs | 19 ++++----------- BTCPayServer/App/BTCPayAppState.cs | 24 +++++++++++-------- .../Services/Stores/StoreRepository.cs | 2 +- 3 files changed, 19 insertions(+), 26 deletions(-) diff --git a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs index 794c51f14..1d6212238 100644 --- a/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs +++ b/BTCPayApp.CommonServer/IBTCPayAppHubClient.cs @@ -46,23 +46,12 @@ public interface IBTCPayAppHubServer Task SendPaymentUpdate(string identifier, LightningPayment lightningPayment); } -public interface IServerEvent -{ - public string Type { get; } -} - -public interface IServerEvent : IServerEvent -{ - public T? Event { get; } -} - -public class ServerEvent(string type) : IServerEvent +public class ServerEvent(string type) { public string Type { get; } = type; -} -public class ServerEvent(string type, T? evt = default) : ServerEvent(type), IServerEvent -{ - public T? Event { get; } = evt; + public string? StoreId { get; init; } + public string? UserId { get; init; } + public string? InvoiceId { get; init; } } public record TxResp(long Confirmations, long? Height, decimal BalanceChange, DateTimeOffset Timestamp, string TransactionId) diff --git a/BTCPayServer/App/BTCPayAppState.cs b/BTCPayServer/App/BTCPayAppState.cs index f0b4f15ea..f795be2cd 100644 --- a/BTCPayServer/App/BTCPayAppState.cs +++ b/BTCPayServer/App/BTCPayAppState.cs @@ -78,50 +78,54 @@ public class BTCPayAppState : IHostedService private async Task InvoiceChangedEvent(InvoiceEvent arg) { - await _hubContext.Clients.Group(arg.Invoice.StoreId).NotifyServerEvent(new ServerEvent("invoice-updated", arg)); + await _hubContext.Clients.Group(arg.Invoice.StoreId).NotifyServerEvent(new ServerEvent("invoice-updated") { StoreId = arg.Invoice.StoreId, InvoiceId = arg.InvoiceId }); } private async Task UserNotificationsUpdatedEvent(UserNotificationsUpdatedEvent arg) { - await _hubContext.Clients.Group(arg.UserId).NotifyServerEvent(new ServerEvent("notifications-updated", arg)); + await _hubContext.Clients.Group(arg.UserId).NotifyServerEvent(new ServerEvent("notifications-updated") { UserId = arg.UserId }); } private async Task StoreCreatedEvent(StoreCreatedEvent arg) { - await _hubContext.Clients.Group(arg.StoreId).NotifyServerEvent(new ServerEvent("store-created", arg)); + await _hubContext.Clients.Group(arg.StoreId).NotifyServerEvent(new ServerEvent("store-created") { StoreId = arg.StoreId }); } private async Task StoreUpdatedEvent(StoreUpdatedEvent arg) { - await _hubContext.Clients.Group(arg.StoreId).NotifyServerEvent(new ServerEvent("store-updated", arg)); + await _hubContext.Clients.Group(arg.StoreId).NotifyServerEvent(new ServerEvent("store-updated") { StoreId = arg.StoreId }); } private async Task StoreRemovedEvent(StoreRemovedEvent arg) { - await _hubContext.Clients.Group(arg.StoreId).NotifyServerEvent(new ServerEvent("store-removed", arg)); + await _hubContext.Clients.Group(arg.StoreId).NotifyServerEvent(new ServerEvent("store-removed") { StoreId = arg.StoreId }); } private async Task StoreUserAddedEvent(UserStoreAddedEvent arg) { - var ev = new ServerEvent("user-store-added", arg); + var ev = new ServerEvent("user-store-added") { StoreId = arg.StoreId, UserId = arg.UserId }; await _hubContext.Clients.Group(arg.StoreId).NotifyServerEvent(ev); await _hubContext.Clients.Group(arg.UserId).NotifyServerEvent(ev); - //await _hubContext.Clients.User(arg.UserId) .AddToGroupAsync(Context.ConnectionId, arg.StoreId); + + // TODO: Add user to store group - how to get the connectionId? + //await _hubContext.Groups.AddToGroupAsync(connectionId, arg.StoreId); } private async Task StoreUserUpdatedEvent(UserStoreUpdatedEvent arg) { - var ev = new ServerEvent("user-store-updated", arg); + var ev = new ServerEvent("user-store-updated") { StoreId = arg.StoreId, UserId = arg.UserId }; await _hubContext.Clients.Group(arg.StoreId).NotifyServerEvent(ev); await _hubContext.Clients.Group(arg.UserId).NotifyServerEvent(ev); } private async Task StoreUserRemovedEvent(UserStoreRemovedEvent arg) { - var ev = new ServerEvent("user-store-removed", arg); + var ev = new ServerEvent("user-store-removed") { StoreId = arg.StoreId, UserId = arg.UserId }; await _hubContext.Clients.Group(arg.StoreId).NotifyServerEvent(ev); await _hubContext.Clients.Group(arg.UserId).NotifyServerEvent(ev); - // await Groups.RemoveFromGroupAsync(Context.ConnectionId, arg.UserId); + + // TODO: Remove user from store group - how to get the connectionId? + //await _hubContext.Groups.RemoveFromGroupAsync(connectionId, arg.UserId); } private string _nodeInfo = string.Empty; diff --git a/BTCPayServer/Services/Stores/StoreRepository.cs b/BTCPayServer/Services/Stores/StoreRepository.cs index 37b65ffc0..c89f1a11b 100644 --- a/BTCPayServer/Services/Stores/StoreRepository.cs +++ b/BTCPayServer/Services/Stores/StoreRepository.cs @@ -411,8 +411,8 @@ namespace BTCPayServer.Services.Stores ctx.Add(storeData); ctx.Add(userStore); await ctx.SaveChangesAsync(); - _eventAggregator.Publish(new StoreCreatedEvent(storeData)); _eventAggregator.Publish(new UserStoreAddedEvent(storeData.Id, userStore.ApplicationUserId)); + _eventAggregator.Publish(new StoreCreatedEvent(storeData)); } public async Task GetWebhooks(string storeId)