mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 06:24:24 +01:00
Disable cold wallet creation by default
This commit is contained in:
@@ -43,13 +43,13 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
}
|
||||
|
||||
var canUseHotWallet = await CanUseHotWallet();
|
||||
if (request.SavePrivateKeys && !canUseHotWallet.HotWallet)
|
||||
if (request.SavePrivateKeys && !canUseHotWallet.CanCreateHotWallet)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.SavePrivateKeys),
|
||||
"This instance forbids non-admins from having a hot wallet for your store.");
|
||||
}
|
||||
|
||||
if (request.ImportKeysToRPC && !canUseHotWallet.RPCImport)
|
||||
if (request.ImportKeysToRPC && !canUseHotWallet.CanRPCImport)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.ImportKeysToRPC),
|
||||
"This instance forbids non-admins from having importing the wallet addresses/keys to the underlying node.");
|
||||
@@ -120,7 +120,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
private async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet()
|
||||
private async Task<WalletCreationPermissions> CanUseHotWallet()
|
||||
{
|
||||
return await _authorizationService.CanUseHotWallet(PoliciesSettings, User);
|
||||
}
|
||||
|
||||
@@ -383,7 +383,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
}
|
||||
|
||||
//This API is only meant for hot wallet usage for now. We can expand later when we allow PSBT manipulation.
|
||||
if (!(await CanUseHotWallet()).HotWallet)
|
||||
if (!(await CanUseHotWallet()).CanCreateHotWallet)
|
||||
{
|
||||
return this.CreateAPIError(503, "not-available",
|
||||
$"You need to allow non-admins to use hotwallets for their stores (in /server/policies)");
|
||||
@@ -802,7 +802,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
}
|
||||
|
||||
|
||||
private async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet()
|
||||
private async Task<WalletCreationPermissions> CanUseHotWallet()
|
||||
{
|
||||
return await _authorizationService.CanUseHotWallet(PoliciesSettings, User);
|
||||
}
|
||||
|
||||
@@ -54,10 +54,9 @@ public partial class UIStoresController
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
var (hotWallet, rpcImport) = await CanUseHotWallet();
|
||||
var perm = await CanUseHotWallet();
|
||||
vm.Network = network;
|
||||
vm.CanUseHotWallet = hotWallet;
|
||||
vm.CanUseRPCImport = rpcImport;
|
||||
vm.SetPermission(perm);
|
||||
vm.SupportTaproot = network.NBitcoinNetwork.Consensus.SupportTaproot;
|
||||
vm.SupportSegwit = network.NBitcoinNetwork.Consensus.SupportSegwit;
|
||||
|
||||
@@ -218,14 +217,13 @@ public partial class UIStoresController
|
||||
}
|
||||
|
||||
var isHotWallet = vm.Method == WalletSetupMethod.HotWallet;
|
||||
var (hotWallet, rpcImport) = await CanUseHotWallet();
|
||||
if (isHotWallet && !hotWallet)
|
||||
{
|
||||
var isColdWallet = vm.Method == WalletSetupMethod.WatchOnly;
|
||||
var perm = await CanUseHotWallet();
|
||||
if (isHotWallet && !perm.CanCreateHotWallet)
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
vm.CanUseHotWallet = hotWallet;
|
||||
vm.CanUseRPCImport = rpcImport;
|
||||
if (isColdWallet && !perm.CanCreateColdWallet)
|
||||
return NotFound();
|
||||
vm.SetPermission(perm);
|
||||
vm.SupportTaproot = network.NBitcoinNetwork.Consensus.SupportTaproot;
|
||||
vm.SupportSegwit = network.NBitcoinNetwork.Consensus.SupportSegwit;
|
||||
vm.Network = network;
|
||||
@@ -236,7 +234,7 @@ public partial class UIStoresController
|
||||
}
|
||||
else
|
||||
{
|
||||
var canUsePayJoin = hotWallet && isHotWallet && network.SupportPayJoin;
|
||||
var canUsePayJoin = perm.CanCreateHotWallet && isHotWallet && network.SupportPayJoin;
|
||||
vm.SetupRequest = new WalletSetupRequest
|
||||
{
|
||||
SavePrivateKeys = isHotWallet,
|
||||
@@ -260,8 +258,10 @@ public partial class UIStoresController
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
var (hotWallet, rpcImport) = await CanUseHotWallet();
|
||||
if (!hotWallet && request.SavePrivateKeys || !rpcImport && request.ImportKeysToRPC)
|
||||
var perm = await CanUseHotWallet();
|
||||
if ((!perm.CanCreateHotWallet && request.SavePrivateKeys) ||
|
||||
(!perm.CanRPCImport && request.ImportKeysToRPC) ||
|
||||
(!perm.CanCreateColdWallet && !request.SavePrivateKeys))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
@@ -279,12 +279,10 @@ public partial class UIStoresController
|
||||
Source = isImport ? "SeedImported" : "NBXplorerGenerated",
|
||||
IsHotWallet = isImport ? request.SavePrivateKeys : method == WalletSetupMethod.HotWallet,
|
||||
DerivationSchemeFormat = "BTCPay",
|
||||
CanUseHotWallet = hotWallet,
|
||||
CanUseRPCImport = rpcImport,
|
||||
SupportTaproot = network.NBitcoinNetwork.Consensus.SupportTaproot,
|
||||
SupportSegwit = network.NBitcoinNetwork.Consensus.SupportSegwit
|
||||
};
|
||||
|
||||
vm.SetPermission(perm);
|
||||
if (isImport && string.IsNullOrEmpty(request.ExistingMnemonic))
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.ExistingMnemonic), StringLocalizer["Please provide your existing seed"]);
|
||||
@@ -404,7 +402,7 @@ public partial class UIStoresController
|
||||
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var excludeFilters = storeBlob.GetExcludedPaymentMethods();
|
||||
(bool canUseHotWallet, bool rpcImport) = await CanUseHotWallet();
|
||||
var perm = await CanUseHotWallet();
|
||||
var client = _explorerProvider.GetExplorerClient(network);
|
||||
|
||||
var handler = _handlers.GetBitcoinHandler(cryptoCode);
|
||||
@@ -426,7 +424,7 @@ public partial class UIStoresController
|
||||
Label = derivation.Label,
|
||||
SelectedSigningKey = derivation.SigningKey?.ToString(),
|
||||
NBXSeedAvailable = derivation.IsHotWallet &&
|
||||
canUseHotWallet &&
|
||||
perm.CanCreateHotWallet &&
|
||||
!string.IsNullOrEmpty(await client.GetMetadataAsync<string>(derivation.AccountDerivation,
|
||||
WellknownMetadataKeys.MasterHDKey)),
|
||||
AccountKeys = (derivation.AccountKeySettings ?? [])
|
||||
@@ -438,9 +436,9 @@ public partial class UIStoresController
|
||||
}).ToList(),
|
||||
Config = ProtectString(JToken.FromObject(derivation, handler.Serializer).ToString()),
|
||||
PayJoinEnabled = storeBlob.PayJoinEnabled,
|
||||
CanUsePayJoin = canUseHotWallet && network.SupportPayJoin && derivation.IsHotWallet,
|
||||
CanUseHotWallet = canUseHotWallet,
|
||||
CanUseRPCImport = rpcImport,
|
||||
CanUsePayJoin = perm.CanCreateHotWallet && network.SupportPayJoin && derivation.IsHotWallet,
|
||||
CanUseHotWallet = perm.CanCreateHotWallet,
|
||||
CanUseRPCImport = perm.CanRPCImport,
|
||||
StoreName = store.StoreName,
|
||||
CanSetupMultiSig = (derivation.AccountKeySettings ?? []).Length > 1,
|
||||
IsMultiSigOnServer = derivation.IsMultiSigOnServer,
|
||||
@@ -589,11 +587,8 @@ public partial class UIStoresController
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
(bool canUseHotWallet, bool _) = await CanUseHotWallet();
|
||||
if (!canUseHotWallet)
|
||||
{
|
||||
if (!(await CanUseHotWallet()).CanCreateHotWallet)
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var client = _explorerProvider.GetExplorerClient(network);
|
||||
if (await GetSeed(client, derivation) != null)
|
||||
@@ -753,7 +748,7 @@ public partial class UIStoresController
|
||||
!string.IsNullOrEmpty(seed) ? seed : null;
|
||||
}
|
||||
|
||||
private async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet()
|
||||
private async Task<WalletCreationPermissions> CanUseHotWallet()
|
||||
{
|
||||
return await _authorizationService.CanUseHotWallet(_policiesSettings, User);
|
||||
}
|
||||
|
||||
@@ -795,7 +795,7 @@ namespace BTCPayServer.Controllers
|
||||
private async Task<bool> CanUseHotWallet()
|
||||
{
|
||||
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>();
|
||||
return (await _authorizationService.CanUseHotWallet(policies, User)).HotWallet;
|
||||
return (await _authorizationService.CanUseHotWallet(policies, User)).CanCreateHotWallet;
|
||||
}
|
||||
|
||||
[HttpGet("{walletId}/send")]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#nullable enable
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
@@ -9,6 +10,7 @@ using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public record WalletCreationPermissions(bool CanCreateHotWallet, bool CanCreateColdWallet, bool CanRPCImport);
|
||||
public static class AuthorizationExtensions
|
||||
{
|
||||
public static async Task<bool> CanModifyStore(this IAuthorizationService authorizationService, ClaimsPrincipal user)
|
||||
@@ -16,24 +18,26 @@ namespace BTCPayServer
|
||||
return (await authorizationService.AuthorizeAsync(user, null,
|
||||
new PolicyRequirement(Policies.CanModifyStoreSettings))).Succeeded;
|
||||
}
|
||||
public static async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet(
|
||||
public static async Task<WalletCreationPermissions> CanUseHotWallet(
|
||||
this IAuthorizationService authorizationService,
|
||||
PoliciesSettings policiesSettings,
|
||||
PoliciesSettings? policiesSettings,
|
||||
ClaimsPrincipal user)
|
||||
{
|
||||
if (!user.Identity.IsAuthenticated)
|
||||
return (false, false);
|
||||
if (user.Identity?.IsAuthenticated is not true)
|
||||
return new(false, false, false);
|
||||
var claimUser = user.Identity as ClaimsIdentity;
|
||||
if (claimUser is null)
|
||||
return (false, false);
|
||||
return new(false, false, false);
|
||||
|
||||
bool isAdmin = false;
|
||||
if (claimUser.AuthenticationType == AuthenticationSchemes.Cookie)
|
||||
isAdmin = user.IsInRole(Roles.ServerAdmin);
|
||||
else if (claimUser.AuthenticationType == GreenfieldConstants.AuthenticationType)
|
||||
isAdmin = (await authorizationService.AuthorizeAsync(user, Policies.CanModifyServerSettings)).Succeeded;
|
||||
return isAdmin ? (true, true) :
|
||||
(policiesSettings?.AllowHotWalletForAll is true, policiesSettings?.AllowHotWalletRPCImportForAll is true);
|
||||
return isAdmin ? new(true, true, true) :
|
||||
new(policiesSettings?.AllowHotWalletForAll is true,
|
||||
policiesSettings?.AllowCreateColdWalletForAll is true,
|
||||
policiesSettings?.AllowHotWalletRPCImportForAll is true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
public BTCPayNetwork Network { get; set; }
|
||||
[Display(Name = "Can use hot wallet")]
|
||||
public bool CanUseHotWallet { get; set; }
|
||||
[Display(Name = "Can create a new cold wallet")]
|
||||
public bool CanCreateNewColdWallet { get; set; }
|
||||
[Display(Name = "Can use RPC import")]
|
||||
public bool CanUseRPCImport { get; set; }
|
||||
public bool SupportSegwit { get; set; }
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
|
||||
namespace BTCPayServer.Models.StoreViewModels
|
||||
{
|
||||
public enum WalletSetupMethod
|
||||
@@ -34,5 +37,21 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
WalletSetupMethod.WatchOnly => "GenerateWallet",
|
||||
_ => "SetupWallet"
|
||||
};
|
||||
|
||||
internal void SetPermission(WalletCreationPermissions perm)
|
||||
{
|
||||
this.CanCreateNewColdWallet = perm.CanCreateColdWallet;
|
||||
this.CanUseHotWallet = perm.CanCreateHotWallet;
|
||||
this.CanUseRPCImport = perm.CanRPCImport;
|
||||
}
|
||||
public void SetViewData(ViewDataDictionary ViewData)
|
||||
{
|
||||
ViewData.Add(nameof(CanUseHotWallet), CanUseHotWallet);
|
||||
ViewData.Add(nameof(CanCreateNewColdWallet), CanCreateNewColdWallet);
|
||||
ViewData.Add(nameof(CanUseRPCImport), CanUseRPCImport);
|
||||
ViewData.Add(nameof(SupportSegwit), SupportSegwit);
|
||||
ViewData.Add(nameof(SupportTaproot), SupportTaproot);
|
||||
ViewData.Add(nameof(Method), Method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ namespace BTCPayServer.Services
|
||||
|
||||
[Display(Name = "Non-admins can create Hot Wallets for their Store")]
|
||||
public bool AllowHotWalletForAll { get; set; }
|
||||
[Display(Name = "Non-admins can create Cold Wallets for their Store")]
|
||||
public bool AllowCreateColdWalletForAll { get; set; }
|
||||
|
||||
[Display(Name = "Non-admins can import Hot Wallets for their Store")]
|
||||
public bool AllowHotWalletRPCImportForAll { get; set; }
|
||||
|
||||
@@ -120,6 +120,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex my-3">
|
||||
<input asp-for="AllowCreateColdWalletForAll" type="checkbox" class="btcpay-toggle me-3" />
|
||||
<div>
|
||||
<label asp-for="AllowCreateColdWalletForAll" class="form-check-label"></label>
|
||||
<span asp-validation-for="AllowCreateColdWalletForAll" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex my-3">
|
||||
<input asp-for="AllowHotWalletRPCImportForAll" type="checkbox" class="btcpay-toggle me-3"/>
|
||||
<div>
|
||||
|
||||
@@ -4,11 +4,7 @@
|
||||
var title = isHotWallet ? StringLocalizer["Create {0} Hot Wallet", Model.CryptoCode] : StringLocalizer["Create {0} Watch-Only Wallet", Model.CryptoCode];
|
||||
Layout = "_LayoutWalletSetup";
|
||||
ViewData.SetActivePage(StoreNavPages.OnchainSettings, title, $"{Context.GetStoreData().Id}-{Model.CryptoCode}");
|
||||
ViewData.Add(nameof(Model.CanUseHotWallet), Model.CanUseHotWallet);
|
||||
ViewData.Add(nameof(Model.CanUseRPCImport), Model.CanUseRPCImport);
|
||||
ViewData.Add(nameof(Model.SupportSegwit), Model.SupportSegwit);
|
||||
ViewData.Add(nameof(Model.SupportTaproot), Model.SupportTaproot);
|
||||
ViewData.Add(nameof(Model.Method), Model.Method);
|
||||
Model.SetViewData(ViewData);
|
||||
}
|
||||
|
||||
@section Navbar {
|
||||
|
||||
@@ -46,6 +46,8 @@
|
||||
</div>
|
||||
|
||||
<div class="list-group mt-4">
|
||||
@if (Model.CanCreateNewColdWallet)
|
||||
{
|
||||
<a asp-controller="UIStores" asp-action="GenerateWallet" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode" asp-route-method="@WalletSetupMethod.WatchOnly.ToString()" id="GenerateWatchonlyLink" class="list-group-item list-group-item-action">
|
||||
<div class="image">
|
||||
<vc:icon symbol="wallet-watchonly"/>
|
||||
@@ -58,6 +60,19 @@
|
||||
</div>
|
||||
<vc:icon symbol="caret-right" />
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="list-group-item text-muted">
|
||||
<div class="image">
|
||||
<vc:icon symbol="wallet-watchonly" />
|
||||
</div>
|
||||
<div class="content">
|
||||
<h4 text-translate="true">Watch-only wallet</h4>
|
||||
<p class="mb-0" text-translate="true">Please note that this instance does not support creating a new cold wallet for non-administrators. However, you can import one from other wallet software.</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -18,12 +18,7 @@
|
||||
<div class="my-5">
|
||||
@if (Model.CanUseHotWallet)
|
||||
{
|
||||
ViewData.Add(nameof(Model.CanUseHotWallet), Model.CanUseHotWallet);
|
||||
ViewData.Add(nameof(Model.CanUseRPCImport), Model.CanUseRPCImport);
|
||||
ViewData.Add(nameof(Model.SupportSegwit), Model.SupportSegwit);
|
||||
ViewData.Add(nameof(Model.SupportTaproot), Model.SupportTaproot);
|
||||
ViewData.Add(nameof(Model.Method), Model.Method);
|
||||
|
||||
Model.SetViewData(ViewData);
|
||||
<partial name="_GenerateWalletForm" model="Model.SetupRequest" />
|
||||
}
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user