Add replace confirmation; distinguish wallet types

This commit is contained in:
Dennis Reimann 2021-03-01 12:43:25 +01:00
parent 28d7924077
commit 5bd16f990c
No known key found for this signature in database
GPG key ID: 5009E1797F03F8D0
5 changed files with 84 additions and 13 deletions

View file

@ -11,11 +11,11 @@ using BTCPayServer.Models;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Services;
using BTCPayServer.Services.Wallets;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using NBitcoin;
using NBXplorer;
using NBXplorer.DerivationStrategy;
using NBXplorer.Models;
@ -411,6 +411,8 @@ namespace BTCPayServer.Controllers
}
var (hotWallet, rpcImport) = await CanUseHotWallet();
var isHotWallet = await IsHotWallet(vm.CryptoCode, derivation);
vm.CanUseHotWallet = hotWallet;
vm.CanUseRPCImport = rpcImport;
vm.RootKeyPath = network.GetRootKeyPath();
@ -421,12 +423,13 @@ namespace BTCPayServer.Controllers
vm.KeyPath = derivation.GetSigningAccountKeySettings().AccountKeyPath?.ToString();
vm.Config = derivation.ToJson();
vm.Enabled = !store.GetStoreBlob().IsExcluded(new PaymentMethodId(vm.CryptoCode, PaymentTypes.BTCLike));
vm.IsHotWallet = isHotWallet;
return View(vm);
}
[HttpGet("{storeId}/onchain/{cryptoCode}/delete")]
public IActionResult DeleteWallet(string storeId, string cryptoCode)
[HttpGet("{storeId}/onchain/{cryptoCode}/replace")]
public async Task<IActionResult> ReplaceWallet(string storeId, string cryptoCode)
{
var checkResult = IsAvailable(cryptoCode, out var store, out var network);
if (checkResult != null)
@ -435,9 +438,62 @@ namespace BTCPayServer.Controllers
}
var derivation = GetExistingDerivationStrategy(cryptoCode, store);
var isHotWallet = await IsHotWallet(cryptoCode, derivation);
var walletType = isHotWallet ? "hot" : "watch-only";
var additionalText = isHotWallet
? ""
: " or imported into an external wallet. If you no longer have access to your private key (recovery seed), immediately replace the wallet";
var description =
(derivation.IsHotWallet ? "<p class=\"text-danger font-weight-bold\">Please note that this is a hot wallet!</p> " : "") +
"<p class=\"text-danger font-weight-bold\">Do not remove the wallet if you have not backed it up!</p>" +
$"<p class=\"text-danger font-weight-bold\">Please note that this is a {walletType} wallet!</p>" +
$"<p class=\"text-danger font-weight-bold\">Do not replace the wallet if you have not backed it up{additionalText}.</p>" +
"<p class=\"text-left mb-0\">Replacing the wallet will erase the current wallet data from the server. " +
"The current wallet will be replaced once you finish the setup of the new wallet. If you cancel the setup, the current wallet will stay active .</p>";
return View("Confirm", new ConfirmModel
{
Title = $"Replace {network.CryptoCode} wallet",
Description = description,
DescriptionHtml = true,
Action = "Setup new wallet"
});
}
[HttpPost("{storeId}/onchain/{cryptoCode}/replace")]
public IActionResult ConfirmReplaceWallet(string storeId, string cryptoCode)
{
var checkResult = IsAvailable(cryptoCode, out var store, out _);
if (checkResult != null)
{
return checkResult;
}
var derivation = GetExistingDerivationStrategy(cryptoCode, store);
if (derivation == null)
{
return NotFound();
}
return RedirectToAction(nameof(SetupWallet), new {storeId, cryptoCode});
}
[HttpGet("{storeId}/onchain/{cryptoCode}/delete")]
public async Task<IActionResult> DeleteWallet(string storeId, string cryptoCode)
{
var checkResult = IsAvailable(cryptoCode, out var store, out var network);
if (checkResult != null)
{
return checkResult;
}
var derivation = GetExistingDerivationStrategy(cryptoCode, store);
var isHotWallet = await IsHotWallet(cryptoCode, derivation);
var walletType = isHotWallet ? "hot" : "watch-only";
var additionalText = isHotWallet
? ""
: " or imported into an external wallet. If you no longer have access to your private key (recovery seed), immediately replace the wallet";
var description =
$"<p class=\"text-danger font-weight-bold\">Please note that this is a {walletType} wallet!</p>" +
$"<p class=\"text-danger font-weight-bold\">Do not remove the wallet if you have not backed it up{additionalText}.</p>" +
"<p class=\"text-left mb-0\">Removing the wallet will erase the wallet data from the server. " +
$"The store won't be able to receive {network.CryptoCode} onchain payments until a new wallet is set up.</p>";
@ -480,7 +536,7 @@ namespace BTCPayServer.Controllers
private IActionResult ConfirmAddresses(WalletSetupViewModel vm, DerivationSchemeSettings strategy)
{
vm.DerivationScheme = strategy.AccountDerivation.ToString();
var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
var deposit = new KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
if (!string.IsNullOrEmpty(vm.DerivationScheme))
{
@ -529,7 +585,7 @@ namespace BTCPayServer.Controllers
return (true, true);
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>();
var hotWallet = policies?.AllowHotWalletForAll is true;
return (hotWallet, hotWallet && policies?.AllowHotWalletRPCImportForAll is true);
return (hotWallet, hotWallet && policies.AllowHotWalletRPCImportForAll is true);
}
private async Task<string> ReadAllText(IFormFile file)
@ -539,5 +595,11 @@ namespace BTCPayServer.Controllers
return await stream.ReadToEndAsync();
}
}
private async Task<bool> IsHotWallet(string cryptoCode, DerivationSchemeSettings derivation)
{
return derivation.IsHotWallet && await _ExplorerProvider.GetExplorerClient(cryptoCode)
.GetMetadataAsync<string>(derivation.AccountDerivation, WellknownMetadataKeys.MasterHDKey) != null;
}
}
}

View file

@ -20,6 +20,7 @@ namespace BTCPayServer.Models.StoreViewModels
public WalletSetupMethod? Method { get; set; }
public GenerateWalletRequest SetupRequest { get; set; }
public string StoreId { get; set; }
public bool IsHotWallet { get; set; }
public string ViewName =>
Method switch

View file

@ -31,8 +31,8 @@
@if (!String.IsNullOrEmpty(Model.Action))
{
<form method="post" class="modal-footer justify-content-center" action="@Model.ActionUrl">
<button type="submit" class="btn @Model.ButtonClass w-25 mx-2" id="continue">@Model.Action</button>
<button type="submit" class="btn btn-secondary w-25 mx-2" onclick="history.back(); return false;">Go back</button>
<button type="submit" class="btn @Model.ButtonClass xmx-2" id="continue" style="min-width:25%;">@Model.Action</button>
<button type="submit" class="btn btn-secondary mx-2" onclick="history.back(); return false;" style="min-width:25%;">Go back</button>
</form>
}
</div>

View file

@ -23,8 +23,9 @@
<div class="content">
<h4>Hot wallet</h4>
<p class="mb-0 text-secondary">
Allows spending directly from your BTCPay Server.
Each private key associated with an address generated will be stored as metadata and would be accessible to anyone with admin access to your server. Use at your own risk!
Wallet's private key is stored on the server.
Spending the funds you received is convenient.
To minimize the risk of theft, regularly withdraw funds to a different wallet.
</p>
</div>
<vc:icon symbol="caret-right"/>
@ -51,7 +52,10 @@
</div>
<div class="content">
<h4>Watch-only wallet</h4>
<p class="mb-0 text-secondary">Needs to be imported into an external wallet to spend or provide the seed while spending.</p>
<p class="mb-0 text-secondary">
Wallet's private key is erased from the server. Higher security.
To spend, you have to manually input the private key or import it into an external wallet.
</p>
</div>
<vc:icon symbol="caret-right" />
</a>

View file

@ -44,6 +44,10 @@
<th>Source</th>
<td>@Model.Source</td>
</tr>
<tr>
<th>Type</th>
<td>@(Model.IsHotWallet ? "Hot wallet" : "Watch-only wallet")</td>
</tr>
<tr>
<th>
Enabled
@ -57,7 +61,7 @@
</form>
<br>
<form method="get" asp-controller="Stores" asp-action="DeleteWallet" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode" class="mt-5">
<a asp-controller="Stores" asp-action="SetupWallet" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode" id="ChangeWalletLink" class="btn btn-secondary mr-2">
<a asp-controller="Stores" asp-action="ReplaceWallet" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode" id="ChangeWalletLink" class="btn btn-secondary mr-2">
Replace wallet
</a>
<button type="submit" class="btn btn-danger" id="Delete">Remove wallet</button>