Merge pull request #3772 from NicolasDorier/experimental

Add experimental mode
This commit is contained in:
Nicolas Dorier 2022-05-24 19:05:38 +09:00 committed by GitHub
commit 891809a13a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 265 additions and 149 deletions

View file

@ -72,14 +72,14 @@ namespace BTCPayServer.Tests
// Setup Lightning // Setup Lightning
var controller = user.GetController<UIStoresController>(); var controller = user.GetController<UIStoresController>();
var lightningVm = (LightningNodeViewModel)Assert.IsType<ViewResult>(await controller.SetupLightningNode(user.StoreId, cryptoCode)).Model; var lightningVm = (LightningNodeViewModel)Assert.IsType<ViewResult>(controller.SetupLightningNode(user.StoreId, cryptoCode)).Model;
Assert.True(lightningVm.Enabled); Assert.True(lightningVm.Enabled);
var response = await controller.SetLightningNodeEnabled(user.StoreId, cryptoCode, false); var response = await controller.SetLightningNodeEnabled(user.StoreId, cryptoCode, false);
Assert.IsType<RedirectToActionResult>(response); Assert.IsType<RedirectToActionResult>(response);
// Get enabled state from settings // Get enabled state from settings
LightningSettingsViewModel lnSettingsModel; LightningSettingsViewModel lnSettingsModel;
response = controller.LightningSettings(user.StoreId, cryptoCode).GetAwaiter().GetResult(); response = controller.LightningSettings(user.StoreId, cryptoCode);
lnSettingsModel = (LightningSettingsViewModel)Assert.IsType<ViewResult>(response).Model; lnSettingsModel = (LightningSettingsViewModel)Assert.IsType<ViewResult>(response).Model;
Assert.NotNull(lnSettingsModel?.ConnectionString); Assert.NotNull(lnSettingsModel?.ConnectionString);
Assert.False(lnSettingsModel.Enabled); Assert.False(lnSettingsModel.Enabled);

View file

@ -339,5 +339,13 @@ namespace BTCPayServer.Tests
var index = coinAverageMock.ExchangeRates.FindIndex(o => o.CurrencyPair == p); var index = coinAverageMock.ExchangeRates.FindIndex(o => o.CurrencyPair == p);
coinAverageMock.ExchangeRates[index] = new PairRate(p, bidAsk); coinAverageMock.ExchangeRates[index] = new PairRate(p, bidAsk);
} }
public async Task EnableExperimental()
{
var r = GetService<SettingsRepository>();
var p = await r.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
p.Experimental = true;
await r.UpdateSetting(p);
}
} }
} }

View file

@ -2520,6 +2520,7 @@ namespace BTCPayServer.Tests
{ {
using var tester = CreateServerTester(); using var tester = CreateServerTester();
await tester.StartAsync(); await tester.StartAsync();
await tester.PayTester.EnableExperimental();
var unauthClient = new BTCPayServerClient(tester.PayTester.ServerUri); var unauthClient = new BTCPayServerClient(tester.PayTester.ServerUri);
await AssertHttpError(401, async () => await unauthClient.GetCustodians()); await AssertHttpError(401, async () => await unauthClient.GetCustodians());
@ -2539,6 +2540,7 @@ namespace BTCPayServer.Tests
using var tester = CreateServerTester(); using var tester = CreateServerTester();
await tester.StartAsync(); await tester.StartAsync();
await tester.PayTester.EnableExperimental();
var admin = tester.NewAccount(); var admin = tester.NewAccount();
await admin.GrantAccessAsync(true); await admin.GrantAccessAsync(true);
@ -2710,7 +2712,8 @@ namespace BTCPayServer.Tests
{ {
using var tester = CreateServerTester(); using var tester = CreateServerTester();
await tester.StartAsync(); await tester.StartAsync();
await tester.PayTester.EnableExperimental();
var admin = tester.NewAccount(); var admin = tester.NewAccount();
await admin.GrantAccessAsync(true); await admin.GrantAccessAsync(true);

View file

@ -408,7 +408,7 @@ namespace BTCPayServer.Tests
var storeController = user.GetController<UIStoresController>(); var storeController = user.GetController<UIStoresController>();
var storeResponse = storeController.GeneralSettings(); var storeResponse = storeController.GeneralSettings();
Assert.IsType<ViewResult>(storeResponse); Assert.IsType<ViewResult>(storeResponse);
Assert.IsType<ViewResult>(await storeController.SetupLightningNode(user.StoreId, "BTC")); Assert.IsType<ViewResult>(storeController.SetupLightningNode(user.StoreId, "BTC"));
storeController.SetupLightningNode(user.StoreId, new LightningNodeViewModel storeController.SetupLightningNode(user.StoreId, new LightningNodeViewModel
{ {
@ -430,7 +430,7 @@ namespace BTCPayServer.Tests
new LightningNodeViewModel { ConnectionString = tester.MerchantCharge.Client.Uri.AbsoluteUri }, new LightningNodeViewModel { ConnectionString = tester.MerchantCharge.Client.Uri.AbsoluteUri },
"save", "BTC").GetAwaiter().GetResult()); "save", "BTC").GetAwaiter().GetResult());
storeResponse = storeController.LightningSettings(user.StoreId, "BTC").GetAwaiter().GetResult(); storeResponse = storeController.LightningSettings(user.StoreId, "BTC");
var storeVm = var storeVm =
Assert.IsType<LightningSettingsViewModel>(Assert Assert.IsType<LightningSettingsViewModel>(Assert
.IsType<ViewResult>(storeResponse).Model); .IsType<ViewResult>(storeResponse).Model);
@ -1571,7 +1571,7 @@ namespace BTCPayServer.Tests
// enable unified QR code in settings // enable unified QR code in settings
var vm = Assert.IsType<LightningSettingsViewModel>(Assert var vm = Assert.IsType<LightningSettingsViewModel>(Assert
.IsType<ViewResult>(await user.GetController<UIStoresController>().LightningSettings(user.StoreId, cryptoCode)).Model .IsType<ViewResult>(user.GetController<UIStoresController>().LightningSettings(user.StoreId, cryptoCode)).Model
); );
vm.OnChainWithLnInvoiceFallback = true; vm.OnChainWithLnInvoiceFallback = true;
Assert.IsType<RedirectToActionResult>( Assert.IsType<RedirectToActionResult>(
@ -1629,7 +1629,7 @@ namespace BTCPayServer.Tests
// Activating LNUrl, we should still have only 1 payment criteria that can be set. // Activating LNUrl, we should still have only 1 payment criteria that can be set.
user.RegisterLightningNode(cryptoCode, LightningConnectionType.Charge); user.RegisterLightningNode(cryptoCode, LightningConnectionType.Charge);
var lnSettingsVm = await user.GetController<UIStoresController>().LightningSettings(user.StoreId, cryptoCode).AssertViewModelAsync<LightningSettingsViewModel>(); var lnSettingsVm = user.GetController<UIStoresController>().LightningSettings(user.StoreId, cryptoCode).AssertViewModel<LightningSettingsViewModel>();
lnSettingsVm.LNURLEnabled = true; lnSettingsVm.LNURLEnabled = true;
lnSettingsVm.LNURLStandardInvoiceEnabled = true; lnSettingsVm.LNURLStandardInvoiceEnabled = true;
Assert.IsType<RedirectToActionResult>(user.GetController<UIStoresController>().LightningSettings(lnSettingsVm).Result); Assert.IsType<RedirectToActionResult>(user.GetController<UIStoresController>().LightningSettings(lnSettingsVm).Result);

View file

@ -8,16 +8,15 @@
@using BTCPayServer.Abstractions.Extensions @using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Abstractions.Contracts @using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Client @using BTCPayServer.Client
@using BTCPayServer.Services
@inject BTCPayServer.Services.BTCPayServerEnvironment Env @inject BTCPayServer.Services.BTCPayServerEnvironment Env
@inject SignInManager<ApplicationUser> SignInManager @inject SignInManager<ApplicationUser> SignInManager
@inject ISettingsRepository SettingsRepository @inject PoliciesSettings PoliciesSettings
@inject ThemeSettings Theme
@model BTCPayServer.Components.MainNav.MainNavViewModel @model BTCPayServer.Components.MainNav.MainNavViewModel
@addTagHelper *, BundlerMinifier.TagHelpers @addTagHelper *, BundlerMinifier.TagHelpers
@{
var theme = await SettingsRepository.GetTheme();
}
<nav id="mainNav" class="d-flex flex-column justify-content-between"> <nav id="mainNav" class="d-flex flex-column justify-content-between">
<div class="accordion px-3 px-lg-4"> <div class="accordion px-3 px-lg-4">
@ -222,7 +221,7 @@
else if (Env.IsSecure) else if (Env.IsSecure)
{ {
<ul class="navbar-nav"> <ul class="navbar-nav">
@if (!(await SettingsRepository.GetPolicies()).LockSubscription) @if (!PoliciesSettings.LockSubscription)
{ {
<li class="nav-item"> <li class="nav-item">
<a asp-area="" asp-controller="UIAccount" asp-action="Register" class="nav-link js-scroll-trigger" id="Nav-Register">Register</a> <a asp-area="" asp-controller="UIAccount" asp-action="Register" class="nav-link js-scroll-trigger" id="Nav-Register">Register</a>
@ -256,7 +255,7 @@
<div class="text-secondary">Administrator</div> <div class="text-secondary">Administrator</div>
} }
</li> </li>
@if (!theme.CustomTheme) @if (!Theme.CustomTheme)
{ {
<li class="border-top py-1 px-3"> <li class="border-top py-1 px-3">
<vc:theme-switch css-class="nav-link"/> <vc:theme-switch css-class="nav-link"/>

View file

@ -49,7 +49,7 @@ namespace BTCPayServer.Configuration
app.Option("--plugin-remote", "Which github repository to fetch the available plugins list (default:btcpayserver/btcpayserver-plugins)", CommandOptionType.SingleValue); app.Option("--plugin-remote", "Which github repository to fetch the available plugins list (default:btcpayserver/btcpayserver-plugins)", CommandOptionType.SingleValue);
app.Option("--recommended-plugins", "Plugins which would be marked as recommended to be installed. Separated by newline or space", CommandOptionType.MultipleValue); app.Option("--recommended-plugins", "Plugins which would be marked as recommended to be installed. Separated by newline or space", CommandOptionType.MultipleValue);
app.Option("--xforwardedproto", "If specified, set X-Forwarded-Proto to the specified value, this may be useful if your reverse proxy handle https but is not configured to add X-Forwarded-Proto (example: --xforwardedproto https)", CommandOptionType.SingleValue); app.Option("--xforwardedproto", "If specified, set X-Forwarded-Proto to the specified value, this may be useful if your reverse proxy handle https but is not configured to add X-Forwarded-Proto (example: --xforwardedproto https)", CommandOptionType.SingleValue);
app.Option("--cheatmode", "Add elements in the UI to facilitate dev-time testing (Default false)", CommandOptionType.BoolValue); app.Option("--cheatmode", "Add some helper UI to facilitate dev-time testing (Default false)", CommandOptionType.BoolValue);
app.Option("--explorerpostgres", $"Connection string to the postgres database of NBXplorer. (optional, used for dashboard and reporting features)", CommandOptionType.SingleValue); app.Option("--explorerpostgres", $"Connection string to the postgres database of NBXplorer. (optional, used for dashboard and reporting features)", CommandOptionType.SingleValue);
foreach (var network in provider.GetAll().OfType<BTCPayNetwork>()) foreach (var network in provider.GetAll().OfType<BTCPayNetwork>())

View file

@ -10,6 +10,7 @@ using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Filters;
using BTCPayServer.Security; using BTCPayServer.Security;
using BTCPayServer.Services.Custodian; using BTCPayServer.Services.Custodian;
using BTCPayServer.Services.Custodian.Client; using BTCPayServer.Services.Custodian.Client;
@ -38,6 +39,7 @@ namespace BTCPayServer.Controllers.Greenfield
[Authorize(AuthenticationSchemes = AuthenticationSchemes.GreenfieldAPIKeys)] [Authorize(AuthenticationSchemes = AuthenticationSchemes.GreenfieldAPIKeys)]
[EnableCors(CorsPolicies.All)] [EnableCors(CorsPolicies.All)]
[CustodianExceptionFilter] [CustodianExceptionFilter]
[ExperimentalRouteAttribute] // if you remove this, also remove "x_experimental": true in swagger.template.custodians.json
public class GreenfieldCustodianAccountController : ControllerBase public class GreenfieldCustodianAccountController : ControllerBase
{ {
private readonly CustodianAccountRepository _custodianAccountRepository; private readonly CustodianAccountRepository _custodianAccountRepository;

View file

@ -3,6 +3,7 @@ using System.Linq;
using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Custodians; using BTCPayServer.Abstractions.Custodians;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
using BTCPayServer.Filters;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -12,6 +13,7 @@ namespace BTCPayServer.Controllers.Greenfield
[ApiController] [ApiController]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.GreenfieldAPIKeys)] [Authorize(AuthenticationSchemes = AuthenticationSchemes.GreenfieldAPIKeys)]
[EnableCors(CorsPolicies.All)] [EnableCors(CorsPolicies.All)]
[ExperimentalRouteAttribute] // if you remove this, also remove "x_experimental": true in swagger.template.custodians.json
public class GreenfieldCustodianController : ControllerBase public class GreenfieldCustodianController : ControllerBase
{ {
private readonly IEnumerable<ICustodian> _custodianRegistry; private readonly IEnumerable<ICustodian> _custodianRegistry;

View file

@ -28,10 +28,10 @@ namespace BTCPayServer.Controllers.Greenfield
public GreenfieldInternalLightningNodeApiController( public GreenfieldInternalLightningNodeApiController(
BTCPayNetworkProvider btcPayNetworkProvider, ISettingsRepository settingsRepository, LightningClientFactoryService lightningClientFactory, BTCPayNetworkProvider btcPayNetworkProvider, PoliciesSettings policiesSettings, LightningClientFactoryService lightningClientFactory,
IOptions<LightningNetworkOptions> lightningNetworkOptions, IOptions<LightningNetworkOptions> lightningNetworkOptions,
IAuthorizationService authorizationService) : base( IAuthorizationService authorizationService) : base(
btcPayNetworkProvider, settingsRepository, authorizationService) btcPayNetworkProvider, policiesSettings, authorizationService)
{ {
_btcPayNetworkProvider = btcPayNetworkProvider; _btcPayNetworkProvider = btcPayNetworkProvider;
_lightningClientFactory = lightningClientFactory; _lightningClientFactory = lightningClientFactory;

View file

@ -32,9 +32,9 @@ namespace BTCPayServer.Controllers.Greenfield
public GreenfieldStoreLightningNodeApiController( public GreenfieldStoreLightningNodeApiController(
IOptions<LightningNetworkOptions> lightningNetworkOptions, IOptions<LightningNetworkOptions> lightningNetworkOptions,
LightningClientFactoryService lightningClientFactory, BTCPayNetworkProvider btcPayNetworkProvider, LightningClientFactoryService lightningClientFactory, BTCPayNetworkProvider btcPayNetworkProvider,
ISettingsRepository settingsRepository, PoliciesSettings policiesSettings,
IAuthorizationService authorizationService) : base( IAuthorizationService authorizationService) : base(
btcPayNetworkProvider, settingsRepository, authorizationService) btcPayNetworkProvider, policiesSettings, authorizationService)
{ {
_lightningNetworkOptions = lightningNetworkOptions; _lightningNetworkOptions = lightningNetworkOptions;
_lightningClientFactory = lightningClientFactory; _lightningClientFactory = lightningClientFactory;

View file

@ -8,6 +8,7 @@ using BTCPayServer.Client;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
using BTCPayServer.Lightning; using BTCPayServer.Lightning;
using BTCPayServer.Security; using BTCPayServer.Security;
using BTCPayServer.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
@ -26,14 +27,14 @@ namespace BTCPayServer.Controllers.Greenfield
public abstract class GreenfieldLightningNodeApiController : Controller public abstract class GreenfieldLightningNodeApiController : Controller
{ {
private readonly BTCPayNetworkProvider _btcPayNetworkProvider; private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly ISettingsRepository _settingsRepository; private readonly PoliciesSettings _policiesSettings;
private readonly IAuthorizationService _authorizationService; private readonly IAuthorizationService _authorizationService;
protected GreenfieldLightningNodeApiController(BTCPayNetworkProvider btcPayNetworkProvider, protected GreenfieldLightningNodeApiController(BTCPayNetworkProvider btcPayNetworkProvider,
ISettingsRepository settingsRepository, PoliciesSettings policiesSettings,
IAuthorizationService authorizationService) IAuthorizationService authorizationService)
{ {
_btcPayNetworkProvider = btcPayNetworkProvider; _btcPayNetworkProvider = btcPayNetworkProvider;
_settingsRepository = settingsRepository; _policiesSettings = policiesSettings;
_authorizationService = authorizationService; _authorizationService = authorizationService;
} }
@ -296,8 +297,7 @@ namespace BTCPayServer.Controllers.Greenfield
protected async Task<bool> CanUseInternalLightning(bool doingAdminThings) protected async Task<bool> CanUseInternalLightning(bool doingAdminThings)
{ {
return (!doingAdminThings && this._policiesSettings.AllowLightningInternalNodeForAll) ||
return (!doingAdminThings && (await _settingsRepository.GetPolicies()).AllowLightningInternalNodeForAll) ||
(await _authorizationService.AuthorizeAsync(User, null, (await _authorizationService.AuthorizeAsync(User, null,
new PolicyRequirement(Policies.CanUseInternalLightningNode))).Succeeded; new PolicyRequirement(Policies.CanUseInternalLightningNode))).Succeeded;
} }

View file

@ -15,6 +15,7 @@ using BTCPayServer.Lightning;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning; using BTCPayServer.Payments.Lightning;
using BTCPayServer.Security; using BTCPayServer.Security;
using BTCPayServer.Services;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -28,6 +29,9 @@ namespace BTCPayServer.Controllers.Greenfield
public class GreenfieldStoreLightningNetworkPaymentMethodsController : ControllerBase public class GreenfieldStoreLightningNetworkPaymentMethodsController : ControllerBase
{ {
private StoreData Store => HttpContext.GetStoreData(); private StoreData Store => HttpContext.GetStoreData();
public PoliciesSettings PoliciesSettings { get; }
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider; private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly IAuthorizationService _authorizationService; private readonly IAuthorizationService _authorizationService;
@ -37,12 +41,14 @@ namespace BTCPayServer.Controllers.Greenfield
StoreRepository storeRepository, StoreRepository storeRepository,
BTCPayNetworkProvider btcPayNetworkProvider, BTCPayNetworkProvider btcPayNetworkProvider,
IAuthorizationService authorizationService, IAuthorizationService authorizationService,
ISettingsRepository settingsRepository) ISettingsRepository settingsRepository,
PoliciesSettings policiesSettings)
{ {
_storeRepository = storeRepository; _storeRepository = storeRepository;
_btcPayNetworkProvider = btcPayNetworkProvider; _btcPayNetworkProvider = btcPayNetworkProvider;
_authorizationService = authorizationService; _authorizationService = authorizationService;
_settingsRepository = settingsRepository; _settingsRepository = settingsRepository;
PoliciesSettings = policiesSettings;
} }
public static IEnumerable<LightningNetworkPaymentMethodData> GetLightningPaymentMethods(StoreData store, public static IEnumerable<LightningNetworkPaymentMethodData> GetLightningPaymentMethods(StoreData store,
@ -216,7 +222,7 @@ namespace BTCPayServer.Controllers.Greenfield
private async Task<bool> CanUseInternalLightning() private async Task<bool> CanUseInternalLightning()
{ {
return (await _settingsRepository.GetPolicies()).AllowLightningInternalNodeForAll || return PoliciesSettings.AllowLightningInternalNodeForAll ||
(await _authorizationService.AuthorizeAsync(User, null, (await _authorizationService.AuthorizeAsync(User, null,
new PolicyRequirement(Policies.CanUseInternalLightningNode))).Succeeded; new PolicyRequirement(Policies.CanUseInternalLightningNode))).Succeeded;
} }

View file

@ -95,7 +95,7 @@ namespace BTCPayServer.Controllers.Greenfield
private async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet() private async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet()
{ {
return await _authorizationService.CanUseHotWallet(await _settingsRepository.GetPolicies(), User); return await _authorizationService.CanUseHotWallet(PoliciesSettings, User);
} }
} }
} }

View file

@ -9,6 +9,7 @@ using BTCPayServer.Client.Models;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.HostedServices; using BTCPayServer.HostedServices;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Services;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Wallets; using BTCPayServer.Services.Wallets;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -25,11 +26,13 @@ namespace BTCPayServer.Controllers.Greenfield
public partial class GreenfieldStoreOnChainPaymentMethodsController : ControllerBase public partial class GreenfieldStoreOnChainPaymentMethodsController : ControllerBase
{ {
private StoreData Store => HttpContext.GetStoreData(); private StoreData Store => HttpContext.GetStoreData();
public PoliciesSettings PoliciesSettings { get; }
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider; private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly BTCPayWalletProvider _walletProvider; private readonly BTCPayWalletProvider _walletProvider;
private readonly IAuthorizationService _authorizationService; private readonly IAuthorizationService _authorizationService;
private readonly ISettingsRepository _settingsRepository;
private readonly ExplorerClientProvider _explorerClientProvider; private readonly ExplorerClientProvider _explorerClientProvider;
public GreenfieldStoreOnChainPaymentMethodsController( public GreenfieldStoreOnChainPaymentMethodsController(
@ -37,14 +40,15 @@ namespace BTCPayServer.Controllers.Greenfield
BTCPayNetworkProvider btcPayNetworkProvider, BTCPayNetworkProvider btcPayNetworkProvider,
BTCPayWalletProvider walletProvider, BTCPayWalletProvider walletProvider,
IAuthorizationService authorizationService, IAuthorizationService authorizationService,
ExplorerClientProvider explorerClientProvider, ISettingsRepository settingsRepository) ExplorerClientProvider explorerClientProvider,
PoliciesSettings policiesSettings)
{ {
_storeRepository = storeRepository; _storeRepository = storeRepository;
_btcPayNetworkProvider = btcPayNetworkProvider; _btcPayNetworkProvider = btcPayNetworkProvider;
_walletProvider = walletProvider; _walletProvider = walletProvider;
_authorizationService = authorizationService; _authorizationService = authorizationService;
_explorerClientProvider = explorerClientProvider; _explorerClientProvider = explorerClientProvider;
_settingsRepository = settingsRepository; PoliciesSettings = policiesSettings;
} }
public static IEnumerable<OnChainPaymentMethodData> GetOnChainPaymentMethods(StoreData store, public static IEnumerable<OnChainPaymentMethodData> GetOnChainPaymentMethods(StoreData store,

View file

@ -38,12 +38,14 @@ namespace BTCPayServer.Controllers.Greenfield
public class GreenfieldStoreOnChainWalletsController : Controller public class GreenfieldStoreOnChainWalletsController : Controller
{ {
private StoreData Store => HttpContext.GetStoreData(); private StoreData Store => HttpContext.GetStoreData();
public PoliciesSettings PoliciesSettings { get; }
private readonly IAuthorizationService _authorizationService; private readonly IAuthorizationService _authorizationService;
private readonly BTCPayWalletProvider _btcPayWalletProvider; private readonly BTCPayWalletProvider _btcPayWalletProvider;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider; private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly WalletRepository _walletRepository; private readonly WalletRepository _walletRepository;
private readonly ExplorerClientProvider _explorerClientProvider; private readonly ExplorerClientProvider _explorerClientProvider;
private readonly ISettingsRepository _settingsRepository;
private readonly NBXplorerDashboard _nbXplorerDashboard; private readonly NBXplorerDashboard _nbXplorerDashboard;
private readonly UIWalletsController _walletsController; private readonly UIWalletsController _walletsController;
private readonly PayjoinClient _payjoinClient; private readonly PayjoinClient _payjoinClient;
@ -59,8 +61,8 @@ namespace BTCPayServer.Controllers.Greenfield
BTCPayNetworkProvider btcPayNetworkProvider, BTCPayNetworkProvider btcPayNetworkProvider,
WalletRepository walletRepository, WalletRepository walletRepository,
ExplorerClientProvider explorerClientProvider, ExplorerClientProvider explorerClientProvider,
ISettingsRepository settingsRepository,
NBXplorerDashboard nbXplorerDashboard, NBXplorerDashboard nbXplorerDashboard,
PoliciesSettings policiesSettings,
UIWalletsController walletsController, UIWalletsController walletsController,
PayjoinClient payjoinClient, PayjoinClient payjoinClient,
DelayedTransactionBroadcaster delayedTransactionBroadcaster, DelayedTransactionBroadcaster delayedTransactionBroadcaster,
@ -75,7 +77,7 @@ namespace BTCPayServer.Controllers.Greenfield
_btcPayNetworkProvider = btcPayNetworkProvider; _btcPayNetworkProvider = btcPayNetworkProvider;
_walletRepository = walletRepository; _walletRepository = walletRepository;
_explorerClientProvider = explorerClientProvider; _explorerClientProvider = explorerClientProvider;
_settingsRepository = settingsRepository; PoliciesSettings = policiesSettings;
_nbXplorerDashboard = nbXplorerDashboard; _nbXplorerDashboard = nbXplorerDashboard;
_walletsController = walletsController; _walletsController = walletsController;
_payjoinClient = payjoinClient; _payjoinClient = payjoinClient;
@ -615,7 +617,7 @@ namespace BTCPayServer.Controllers.Greenfield
private async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet() private async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet()
{ {
return await _authorizationService.CanUseHotWallet(await _settingsRepository.GetPolicies(), User); return await _authorizationService.CanUseHotWallet(PoliciesSettings, User);
} }
private bool IsInvalidWalletRequest(string cryptoCode, [MaybeNullWhen(true)] out BTCPayNetwork network, private bool IsInvalidWalletRequest(string cryptoCode, [MaybeNullWhen(true)] out BTCPayNetwork network,

View file

@ -27,6 +27,7 @@ namespace BTCPayServer.Controllers.Greenfield
[EnableCors(CorsPolicies.All)] [EnableCors(CorsPolicies.All)]
public class GreenfieldUsersController : ControllerBase public class GreenfieldUsersController : ControllerBase
{ {
public PoliciesSettings PoliciesSettings { get; }
public Logs Logs { get; } public Logs Logs { get; }
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
@ -42,6 +43,7 @@ namespace BTCPayServer.Controllers.Greenfield
public GreenfieldUsersController(UserManager<ApplicationUser> userManager, public GreenfieldUsersController(UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager, RoleManager<IdentityRole> roleManager,
SettingsRepository settingsRepository, SettingsRepository settingsRepository,
PoliciesSettings policiesSettings,
EventAggregator eventAggregator, EventAggregator eventAggregator,
IPasswordValidator<ApplicationUser> passwordValidator, IPasswordValidator<ApplicationUser> passwordValidator,
RateLimitService throttleService, RateLimitService throttleService,
@ -54,6 +56,7 @@ namespace BTCPayServer.Controllers.Greenfield
_userManager = userManager; _userManager = userManager;
_roleManager = roleManager; _roleManager = roleManager;
_settingsRepository = settingsRepository; _settingsRepository = settingsRepository;
PoliciesSettings = policiesSettings;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_passwordValidator = passwordValidator; _passwordValidator = passwordValidator;
_throttleService = throttleService; _throttleService = throttleService;
@ -147,7 +150,7 @@ namespace BTCPayServer.Controllers.Greenfield
if (request.IsAdministrator is true && !isAdmin) if (request.IsAdministrator is true && !isAdmin)
return this.CreateAPIPermissionError(Policies.Unrestricted, $"Insufficient API Permissions. Please use an API key with permission: {Policies.Unrestricted} and be an admin."); return this.CreateAPIPermissionError(Policies.Unrestricted, $"Insufficient API Permissions. Please use an API key with permission: {Policies.Unrestricted} and be an admin.");
if (!isAdmin && (policies.LockSubscription || (await _settingsRepository.GetPolicies()).DisableNonAdminCreateUserApi)) if (!isAdmin && (policies.LockSubscription || PoliciesSettings.DisableNonAdminCreateUserApi))
{ {
// If we are not admin and subscriptions are locked, we need to check the Policies.CanCreateUser.Key permission // If we are not admin and subscriptions are locked, we need to check the Policies.CanCreateUser.Key permission
var canCreateUser = (await _authorizationService.AuthorizeAsync(User, null, new PolicyRequirement(Policies.CanCreateUser))).Succeeded; var canCreateUser = (await _authorizationService.AuthorizeAsync(User, null, new PolicyRequirement(Policies.CanCreateUser))).Succeeded;

View file

@ -33,9 +33,9 @@ namespace BTCPayServer.Controllers
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager; private readonly SignInManager<ApplicationUser> _signInManager;
readonly RoleManager<IdentityRole> _RoleManager; readonly RoleManager<IdentityRole> _RoleManager;
readonly SettingsRepository _SettingsRepository;
readonly Configuration.BTCPayServerOptions _Options; readonly Configuration.BTCPayServerOptions _Options;
private readonly BTCPayServerEnvironment _btcPayServerEnvironment; private readonly BTCPayServerEnvironment _btcPayServerEnvironment;
readonly SettingsRepository _SettingsRepository;
private readonly Fido2Service _fido2Service; private readonly Fido2Service _fido2Service;
private readonly LnurlAuthService _lnurlAuthService; private readonly LnurlAuthService _lnurlAuthService;
private readonly LinkGenerator _linkGenerator; private readonly LinkGenerator _linkGenerator;
@ -43,12 +43,14 @@ namespace BTCPayServer.Controllers
private readonly EventAggregator _eventAggregator; private readonly EventAggregator _eventAggregator;
readonly ILogger _logger; readonly ILogger _logger;
public PoliciesSettings PoliciesSettings { get; }
public Logs Logs { get; } public Logs Logs { get; }
public UIAccountController( public UIAccountController(
UserManager<ApplicationUser> userManager, UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager, RoleManager<IdentityRole> roleManager,
SignInManager<ApplicationUser> signInManager, SignInManager<ApplicationUser> signInManager,
PoliciesSettings policiesSettings,
SettingsRepository settingsRepository, SettingsRepository settingsRepository,
Configuration.BTCPayServerOptions options, Configuration.BTCPayServerOptions options,
BTCPayServerEnvironment btcPayServerEnvironment, BTCPayServerEnvironment btcPayServerEnvironment,
@ -61,8 +63,9 @@ namespace BTCPayServer.Controllers
{ {
_userManager = userManager; _userManager = userManager;
_signInManager = signInManager; _signInManager = signInManager;
_RoleManager = roleManager; PoliciesSettings = policiesSettings;
_SettingsRepository = settingsRepository; _SettingsRepository = settingsRepository;
_RoleManager = roleManager;
_Options = options; _Options = options;
_btcPayServerEnvironment = btcPayServerEnvironment; _btcPayServerEnvironment = btcPayServerEnvironment;
_fido2Service = fido2Service; _fido2Service = fido2Service;
@ -85,7 +88,7 @@ namespace BTCPayServer.Controllers
public async Task<IActionResult> Login(string returnUrl = null, string email = null) public async Task<IActionResult> Login(string returnUrl = null, string email = null)
{ {
if (User.Identity.IsAuthenticated && string.IsNullOrEmpty(returnUrl)) if (User.Identity.IsAuthenticated && string.IsNullOrEmpty(returnUrl))
return await RedirectToLocal(); return RedirectToLocal();
// Clear the existing external cookie to ensure a clean login process // Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
@ -119,7 +122,7 @@ namespace BTCPayServer.Controllers
_logger.LogInformation("User with ID {UserId} logged in with a login code.", user.Id); _logger.LogInformation("User with ID {UserId} logged in with a login code.", user.Id);
await _signInManager.SignInAsync(user, false, "LoginCode"); await _signInManager.SignInAsync(user, false, "LoginCode");
return await RedirectToLocal(returnUrl); return RedirectToLocal(returnUrl);
} }
return await Login(returnUrl, null); return await Login(returnUrl, null);
} }
@ -194,7 +197,7 @@ namespace BTCPayServer.Controllers
if (result.Succeeded) if (result.Succeeded)
{ {
_logger.LogInformation($"User '{user.Id}' logged in."); _logger.LogInformation($"User '{user.Id}' logged in.");
return await RedirectToLocal(returnUrl); return RedirectToLocal(returnUrl);
} }
if (result.RequiresTwoFactor) if (result.RequiresTwoFactor)
{ {
@ -293,7 +296,7 @@ namespace BTCPayServer.Controllers
_lnurlAuthService.FinalLoginStore.TryRemove(viewModel.UserId, out _); _lnurlAuthService.FinalLoginStore.TryRemove(viewModel.UserId, out _);
await _signInManager.SignInAsync(user, viewModel.RememberMe, "FIDO2"); await _signInManager.SignInAsync(user, viewModel.RememberMe, "FIDO2");
_logger.LogInformation("User logged in."); _logger.LogInformation("User logged in.");
return await RedirectToLocal(returnUrl); return RedirectToLocal(returnUrl);
} }
errorMessage = "Invalid login attempt."; errorMessage = "Invalid login attempt.";
@ -344,7 +347,7 @@ namespace BTCPayServer.Controllers
{ {
await _signInManager.SignInAsync(user, viewModel.RememberMe, "FIDO2"); await _signInManager.SignInAsync(user, viewModel.RememberMe, "FIDO2");
_logger.LogInformation("User logged in."); _logger.LogInformation("User logged in.");
return await RedirectToLocal(returnUrl); return RedirectToLocal(returnUrl);
} }
errorMessage = "Invalid login attempt."; errorMessage = "Invalid login attempt.";
@ -423,7 +426,7 @@ namespace BTCPayServer.Controllers
if (result.Succeeded) if (result.Succeeded)
{ {
_logger.LogInformation("User with ID {UserId} logged in with 2fa.", user.Id); _logger.LogInformation("User with ID {UserId} logged in with 2fa.", user.Id);
return await RedirectToLocal(returnUrl); return RedirectToLocal(returnUrl);
} }
else if (result.IsLockedOut) else if (result.IsLockedOut)
{ {
@ -492,7 +495,7 @@ namespace BTCPayServer.Controllers
if (result.Succeeded) if (result.Succeeded)
{ {
_logger.LogInformation("User with ID {UserId} logged in with a recovery code.", user.Id); _logger.LogInformation("User with ID {UserId} logged in with a recovery code.", user.Id);
return await RedirectToLocal(returnUrl); return RedirectToLocal(returnUrl);
} }
if (result.IsLockedOut) if (result.IsLockedOut)
{ {
@ -518,14 +521,13 @@ namespace BTCPayServer.Controllers
[HttpGet("/register")] [HttpGet("/register")]
[AllowAnonymous] [AllowAnonymous]
[RateLimitsFilter(ZoneLimits.Register, Scope = RateLimitsScope.RemoteAddress)] [RateLimitsFilter(ZoneLimits.Register, Scope = RateLimitsScope.RemoteAddress)]
public async Task<IActionResult> Register(string returnUrl = null, bool logon = true) public IActionResult Register(string returnUrl = null, bool logon = true)
{ {
if (!CanLoginOrRegister()) if (!CanLoginOrRegister())
{ {
SetInsecureFlags(); SetInsecureFlags();
} }
var policies = await _SettingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings(); if (PoliciesSettings.LockSubscription && !User.IsInRole(Roles.ServerAdmin))
if (policies.LockSubscription && !User.IsInRole(Roles.ServerAdmin))
return RedirectToAction(nameof(UIHomeController.Index), "UIHome"); return RedirectToAction(nameof(UIHomeController.Index), "UIHome");
ViewData["ReturnUrl"] = returnUrl; ViewData["ReturnUrl"] = returnUrl;
return View(); return View();
@ -583,7 +585,7 @@ namespace BTCPayServer.Controllers
{ {
if (logon) if (logon)
await _signInManager.SignInAsync(user, isPersistent: false); await _signInManager.SignInAsync(user, isPersistent: false);
return await RedirectToLocal(returnUrl); return RedirectToLocal(returnUrl);
} }
else else
{ {
@ -750,7 +752,7 @@ namespace BTCPayServer.Controllers
} }
} }
private async Task<IActionResult> RedirectToLocal(string returnUrl = null) private IActionResult RedirectToLocal(string returnUrl = null)
{ {
if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl)) if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))
{ {
@ -759,10 +761,9 @@ namespace BTCPayServer.Controllers
else else
{ {
// After login, if there is an app on "/", we should redirect to BTCPay explicit home route, and not to the app. // After login, if there is an app on "/", we should redirect to BTCPay explicit home route, and not to the app.
var policies = await _SettingsRepository.GetPolicies(); if (PoliciesSettings.RootAppId is not null && PoliciesSettings.RootAppType is not null)
if (policies?.RootAppId is not null && policies?.RootAppType is not null)
return RedirectToAction(nameof(UIHomeController.Home), "UIHome"); return RedirectToAction(nameof(UIHomeController.Home), "UIHome");
if (policies?.DomainToAppMapping is { } mapping) if (PoliciesSettings.DomainToAppMapping is { } mapping)
{ {
var matchedDomainMapping = mapping.FirstOrDefault(item => var matchedDomainMapping = mapping.FirstOrDefault(item =>
item.Domain.Equals(this.HttpContext.Request.Host.Host, StringComparison.InvariantCultureIgnoreCase)); item.Domain.Equals(this.HttpContext.Request.Host.Host, StringComparison.InvariantCultureIgnoreCase));

View file

@ -39,7 +39,7 @@ namespace BTCPayServer.Controllers
{ {
public class UIHomeController : Controller public class UIHomeController : Controller
{ {
private readonly ISettingsRepository _settingsRepository; private readonly ThemeSettings _theme;
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
private readonly BTCPayNetworkProvider _networkProvider; private readonly BTCPayNetworkProvider _networkProvider;
private IHttpClientFactory HttpClientFactory { get; } private IHttpClientFactory HttpClientFactory { get; }
@ -47,13 +47,13 @@ namespace BTCPayServer.Controllers
public LanguageService LanguageService { get; } public LanguageService LanguageService { get; }
public UIHomeController(IHttpClientFactory httpClientFactory, public UIHomeController(IHttpClientFactory httpClientFactory,
ISettingsRepository settingsRepository, ThemeSettings theme,
LanguageService languageService, LanguageService languageService,
StoreRepository storeRepository, StoreRepository storeRepository,
BTCPayNetworkProvider networkProvider, BTCPayNetworkProvider networkProvider,
SignInManager<ApplicationUser> signInManager) SignInManager<ApplicationUser> signInManager)
{ {
_settingsRepository = settingsRepository; _theme = theme;
HttpClientFactory = httpClientFactory; HttpClientFactory = httpClientFactory;
LanguageService = languageService; LanguageService = languageService;
_networkProvider = networkProvider; _networkProvider = networkProvider;
@ -71,7 +71,7 @@ namespace BTCPayServer.Controllers
[DomainMappingConstraint] [DomainMappingConstraint]
public async Task<IActionResult> Index() public async Task<IActionResult> Index()
{ {
if ((await _settingsRepository.GetTheme()).FirstRun) if (_theme.FirstRun)
{ {
return RedirectToAction(nameof(UIAccountController.Register), "UIAccount"); return RedirectToAction(nameof(UIAccountController.Register), "UIAccount");
} }

View file

@ -121,10 +121,9 @@ namespace BTCPayServer.Controllers
[Route("server/users/new")] [Route("server/users/new")]
[HttpGet] [HttpGet]
public async Task<IActionResult> CreateUser() public IActionResult CreateUser()
{ {
ViewData["AllowRequestEmailConfirmation"] = (await _SettingsRepository.GetPolicies()).RequiresConfirmedEmail; ViewData["AllowRequestEmailConfirmation"] = _policiesSettings.RequiresConfirmedEmail;
return View(); return View();
} }
@ -132,7 +131,7 @@ namespace BTCPayServer.Controllers
[HttpPost] [HttpPost]
public async Task<IActionResult> CreateUser(RegisterFromAdminViewModel model) public async Task<IActionResult> CreateUser(RegisterFromAdminViewModel model)
{ {
var requiresConfirmedEmail = (await _SettingsRepository.GetPolicies()).RequiresConfirmedEmail; var requiresConfirmedEmail = _policiesSettings.RequiresConfirmedEmail;
ViewData["AllowRequestEmailConfirmation"] = requiresConfirmedEmail; ViewData["AllowRequestEmailConfirmation"] = requiresConfirmedEmail;
if (!_Options.CheatMode) if (!_Options.CheatMode)
model.IsAdmin = false; model.IsAdmin = false;

View file

@ -48,6 +48,7 @@ namespace BTCPayServer.Controllers
private readonly UserManager<ApplicationUser> _UserManager; private readonly UserManager<ApplicationUser> _UserManager;
private readonly UserService _userService; private readonly UserService _userService;
readonly SettingsRepository _SettingsRepository; readonly SettingsRepository _SettingsRepository;
readonly PoliciesSettings _policiesSettings;
private readonly NBXplorerDashboard _dashBoard; private readonly NBXplorerDashboard _dashBoard;
private readonly StoreRepository _StoreRepository; private readonly StoreRepository _StoreRepository;
readonly LightningConfigurationProvider _LnConfigProvider; readonly LightningConfigurationProvider _LnConfigProvider;
@ -70,6 +71,7 @@ namespace BTCPayServer.Controllers
IEnumerable<IStorageProviderService> storageProviderServices, IEnumerable<IStorageProviderService> storageProviderServices,
BTCPayServerOptions options, BTCPayServerOptions options,
SettingsRepository settingsRepository, SettingsRepository settingsRepository,
PoliciesSettings policiesSettings,
NBXplorerDashboard dashBoard, NBXplorerDashboard dashBoard,
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
LightningConfigurationProvider lnConfigProvider, LightningConfigurationProvider lnConfigProvider,
@ -81,6 +83,7 @@ namespace BTCPayServer.Controllers
IOptions<ExternalServicesOptions> externalServiceOptions, IOptions<ExternalServicesOptions> externalServiceOptions,
Logs logs) Logs logs)
{ {
_policiesSettings = policiesSettings;
_Options = options; _Options = options;
_StoredFileRepository = storedFileRepository; _StoredFileRepository = storedFileRepository;
_FileService = fileService; _FileService = fileService;
@ -278,10 +281,9 @@ namespace BTCPayServer.Controllers
[Route("server/policies")] [Route("server/policies")]
public async Task<IActionResult> Policies() public async Task<IActionResult> Policies()
{ {
var data = (await _SettingsRepository.GetSettingAsync<PoliciesSettings>()) ?? new PoliciesSettings();
ViewBag.AppsList = await GetAppSelectList(); ViewBag.AppsList = await GetAppSelectList();
ViewBag.UpdateUrlPresent = _Options.UpdateUrl != null; ViewBag.UpdateUrlPresent = _Options.UpdateUrl != null;
return View(data); return View(_policiesSettings);
} }
[Route("server/policies")] [Route("server/policies")]
@ -358,7 +360,7 @@ namespace BTCPayServer.Controllers
Link = this.Request.GetAbsoluteUriNoPathBase(externalService.Value).AbsoluteUri Link = this.Request.GetAbsoluteUriNoPathBase(externalService.Value).AbsoluteUri
}); });
} }
if (await CanShowSSHService()) if (CanShowSSHService())
{ {
result.OtherExternalServices.Add(new ServicesViewModel.OtherExternalService() result.OtherExternalServices.Add(new ServicesViewModel.OtherExternalService()
{ {
@ -845,7 +847,7 @@ namespace BTCPayServer.Controllers
[HttpGet("server/services/ssh")] [HttpGet("server/services/ssh")]
public async Task<IActionResult> SSHService() public async Task<IActionResult> SSHService()
{ {
if (!await CanShowSSHService()) if (!CanShowSSHService())
return NotFound(); return NotFound();
var settings = _Options.SSHSettings; var settings = _Options.SSHSettings;
@ -881,10 +883,9 @@ namespace BTCPayServer.Controllers
return View(vm); return View(vm);
} }
async Task<bool> CanShowSSHService() bool CanShowSSHService()
{ {
var policies = await _SettingsRepository.GetSettingAsync<PoliciesSettings>(); return !_policiesSettings.DisableSSHService &&
return !(policies?.DisableSSHService is true) &&
_Options.SSHSettings != null && (_sshState.CanUseSSH || CanAccessAuthorizedKeyFile()); _Options.SSHSettings != null && (_sshState.CanUseSSH || CanAccessAuthorizedKeyFile());
} }
@ -896,7 +897,7 @@ namespace BTCPayServer.Controllers
[HttpPost("server/services/ssh")] [HttpPost("server/services/ssh")]
public async Task<IActionResult> SSHService(SSHServiceViewModel viewModel, string? command = null) public async Task<IActionResult> SSHService(SSHServiceViewModel viewModel, string? command = null)
{ {
if (!await CanShowSSHService()) if (!CanShowSSHService())
return NotFound(); return NotFound();
if (command is "Save") if (command is "Save")

View file

@ -32,7 +32,7 @@ namespace BTCPayServer.Controllers
}; };
[HttpGet("{storeId}/lightning/{cryptoCode}")] [HttpGet("{storeId}/lightning/{cryptoCode}")]
public async Task<IActionResult> Lightning(string storeId, string cryptoCode) public IActionResult Lightning(string storeId, string cryptoCode)
{ {
var store = HttpContext.GetStoreData(); var store = HttpContext.GetStoreData();
if (store == null) if (store == null)
@ -43,7 +43,7 @@ namespace BTCPayServer.Controllers
CryptoCode = cryptoCode, CryptoCode = cryptoCode,
StoreId = storeId StoreId = storeId
}; };
await SetExistingValues(store, vm); SetExistingValues(store, vm);
if (vm.LightningNodeType == LightningNodeType.Internal) if (vm.LightningNodeType == LightningNodeType.Internal)
{ {
@ -93,7 +93,7 @@ namespace BTCPayServer.Controllers
} }
[HttpGet("{storeId}/lightning/{cryptoCode}/setup")] [HttpGet("{storeId}/lightning/{cryptoCode}/setup")]
public async Task<IActionResult> SetupLightningNode(string storeId, string cryptoCode) public IActionResult SetupLightningNode(string storeId, string cryptoCode)
{ {
var store = HttpContext.GetStoreData(); var store = HttpContext.GetStoreData();
if (store == null) if (store == null)
@ -104,7 +104,7 @@ namespace BTCPayServer.Controllers
CryptoCode = cryptoCode, CryptoCode = cryptoCode,
StoreId = storeId StoreId = storeId
}; };
await SetExistingValues(store, vm); SetExistingValues(store, vm);
return View(vm); return View(vm);
} }
@ -116,7 +116,7 @@ namespace BTCPayServer.Controllers
if (store == null) if (store == null)
return NotFound(); return NotFound();
vm.CanUseInternalNode = await CanUseInternalLightning(); vm.CanUseInternalNode = CanUseInternalLightning();
if (vm.CryptoCode == null) if (vm.CryptoCode == null)
{ {
@ -130,7 +130,7 @@ namespace BTCPayServer.Controllers
LightningSupportedPaymentMethod? paymentMethod = null; LightningSupportedPaymentMethod? paymentMethod = null;
if (vm.LightningNodeType == LightningNodeType.Internal) if (vm.LightningNodeType == LightningNodeType.Internal)
{ {
if (!await CanUseInternalLightning()) if (!CanUseInternalLightning())
{ {
ModelState.AddModelError(nameof(vm.ConnectionString), "You are not authorized to use the internal lightning node"); ModelState.AddModelError(nameof(vm.ConnectionString), "You are not authorized to use the internal lightning node");
return View(vm); return View(vm);
@ -213,7 +213,7 @@ namespace BTCPayServer.Controllers
} }
[HttpGet("{storeId}/lightning/{cryptoCode}/settings")] [HttpGet("{storeId}/lightning/{cryptoCode}/settings")]
public async Task<IActionResult> LightningSettings(string storeId, string cryptoCode) public IActionResult LightningSettings(string storeId, string cryptoCode)
{ {
var store = HttpContext.GetStoreData(); var store = HttpContext.GetStoreData();
if (store == null) if (store == null)
@ -239,7 +239,7 @@ namespace BTCPayServer.Controllers
LightningPrivateRouteHints = storeBlob.LightningPrivateRouteHints, LightningPrivateRouteHints = storeBlob.LightningPrivateRouteHints,
OnChainWithLnInvoiceFallback = storeBlob.OnChainWithLnInvoiceFallback OnChainWithLnInvoiceFallback = storeBlob.OnChainWithLnInvoiceFallback
}; };
await SetExistingValues(store, vm); SetExistingValues(store, vm);
if (lightning != null) if (lightning != null)
{ {
@ -360,14 +360,14 @@ namespace BTCPayServer.Controllers
return RedirectToAction(nameof(LightningSettings), new { storeId, cryptoCode }); return RedirectToAction(nameof(LightningSettings), new { storeId, cryptoCode });
} }
private async Task<bool> CanUseInternalLightning() private bool CanUseInternalLightning()
{ {
return User.IsInRole(Roles.ServerAdmin) || (await _settingsRepository.GetPolicies()).AllowLightningInternalNodeForAll; return User.IsInRole(Roles.ServerAdmin) || _policiesSettings.AllowLightningInternalNodeForAll;
} }
private async Task SetExistingValues(StoreData store, LightningNodeViewModel vm) private void SetExistingValues(StoreData store, LightningNodeViewModel vm)
{ {
vm.CanUseInternalNode = await CanUseInternalLightning(); vm.CanUseInternalNode = CanUseInternalLightning();
var lightning = GetExistingLightningSupportedPaymentMethod(vm.CryptoCode, store); var lightning = GetExistingLightningSupportedPaymentMethod(vm.CryptoCode, store);
if (lightning != null) if (lightning != null)

View file

@ -787,8 +787,7 @@ namespace BTCPayServer.Controllers
private async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet() private async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet()
{ {
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>(); return await _authorizationService.CanUseHotWallet(_policiesSettings, User);
return await _authorizationService.CanUseHotWallet(policies, User);
} }
private async Task<string> ReadAllText(IFormFile file) private async Task<string> ReadAllText(IFormFile file)

View file

@ -55,7 +55,7 @@ namespace BTCPayServer.Controllers
ExplorerClientProvider explorerProvider, ExplorerClientProvider explorerProvider,
LanguageService langService, LanguageService langService,
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary, PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
SettingsRepository settingsRepository, PoliciesSettings policiesSettings,
IAuthorizationService authorizationService, IAuthorizationService authorizationService,
EventAggregator eventAggregator, EventAggregator eventAggregator,
AppService appService, AppService appService,
@ -72,7 +72,7 @@ namespace BTCPayServer.Controllers
_TokenController = tokenController; _TokenController = tokenController;
_WalletProvider = walletProvider; _WalletProvider = walletProvider;
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary; _paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
_settingsRepository = settingsRepository; _policiesSettings = policiesSettings;
_authorizationService = authorizationService; _authorizationService = authorizationService;
_appService = appService; _appService = appService;
DataProtector = dataProtector.CreateProtector("ConfigProtector"); DataProtector = dataProtector.CreateProtector("ConfigProtector");
@ -100,7 +100,7 @@ namespace BTCPayServer.Controllers
private readonly ExplorerClientProvider _ExplorerProvider; private readonly ExplorerClientProvider _ExplorerProvider;
private readonly LanguageService _LangService; private readonly LanguageService _LangService;
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary; private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
private readonly SettingsRepository _settingsRepository; private readonly PoliciesSettings _policiesSettings;
private readonly IAuthorizationService _authorizationService; private readonly IAuthorizationService _authorizationService;
private readonly AppService _appService; private readonly AppService _appService;
private readonly EventAggregator _EventAggregator; private readonly EventAggregator _EventAggregator;

View file

@ -1,18 +0,0 @@
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Services;
namespace BTCPayServer
{
public static class SettingsRepositoryExtensions
{
public static async Task<PoliciesSettings> GetPolicies(this ISettingsRepository settingsRepository)
{
return await settingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
}
public static async Task<ThemeSettings> GetTheme(this ISettingsRepository settingsRepository)
{
return await settingsRepository.GetSettingAsync<ThemeSettings>() ?? new ThemeSettings();
}
}
}

View file

@ -28,8 +28,7 @@ namespace BTCPayServer.Filters
{ {
if (context.RouteContext.RouteData.Values.ContainsKey("appId")) if (context.RouteContext.RouteData.Values.ContainsKey("appId"))
return true; return true;
var settingsRepository = context.RouteContext.HttpContext.RequestServices.GetService<ISettingsRepository>(); var policies = context.RouteContext.HttpContext.RequestServices.GetService<PoliciesSettings>();
var policies = settingsRepository.GetPolicies().GetAwaiter().GetResult();
if (policies?.DomainToAppMapping is { } mapping) if (policies?.DomainToAppMapping is { } mapping)
{ {
var matchedDomainMapping = mapping.FirstOrDefault(item => var matchedDomainMapping = mapping.FirstOrDefault(item =>

View file

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Services;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.Extensions.DependencyInjection;
namespace BTCPayServer.Filters
{
public class ExperimentalRouteAttribute : Attribute, IActionConstraint
{
public int Order => 100;
public bool Accept(ActionConstraintContext context)
{
return context.RouteContext.HttpContext.RequestServices.GetRequiredService<PoliciesSettings>().Experimental;
}
}
}

View file

@ -110,7 +110,10 @@ namespace BTCPayServer.Hosting
o.GetRequiredService<IOptions<BTCPayServerOptions>>().Value); o.GetRequiredService<IOptions<BTCPayServerOptions>>().Value);
// Don't move this StartupTask, we depend on it being right here // Don't move this StartupTask, we depend on it being right here
services.AddStartupTask<MigrationStartupTask>(); services.AddStartupTask<MigrationStartupTask>();
// //
AddSettingsAccessor<PoliciesSettings>(services);
AddSettingsAccessor<ThemeSettings>(services);
//
services.AddStartupTask<BlockExplorerLinkStartupTask>(); services.AddStartupTask<BlockExplorerLinkStartupTask>();
services.TryAddSingleton<InvoiceRepository>(); services.TryAddSingleton<InvoiceRepository>();
services.AddSingleton<PaymentService>(); services.AddSingleton<PaymentService>();
@ -477,6 +480,15 @@ namespace BTCPayServer.Hosting
return services; return services;
} }
private static void AddSettingsAccessor<T>(IServiceCollection services) where T : class, new()
{
services.TryAddSingleton<ISettingsAccessor<T>, SettingsAccessor<T>>();
services.AddSingleton<IHostedService>(provider => (SettingsAccessor<T>)provider.GetRequiredService<ISettingsAccessor<T>>());
services.AddSingleton<IStartupTask>(provider => (SettingsAccessor<T>)provider.GetRequiredService<ISettingsAccessor<T>>());
// Singletons shouldn't reference the settings directly, but ISettingsAccessor<T>, since singletons won't have refreshed values of the setting
services.AddTransient<T>(provider => provider.GetRequiredService<ISettingsAccessor<T>>().Settings);
}
public static void SkipModelValidation<T>(this IServiceCollection services) public static void SkipModelValidation<T>(this IServiceCollection services)
{ {
services.AddSingleton<SkippableObjectValidatorProvider.ISkipValidation, SkippableObjectValidatorProvider.SkipValidationType<T>>(); services.AddSingleton<SkippableObjectValidatorProvider.ISkipValidation, SkippableObjectValidatorProvider.SkipValidationType<T>>();

View file

@ -1,4 +1,4 @@
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Contracts;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
@ -11,11 +11,16 @@ public class DefaultSwaggerProvider: ISwaggerProvider
{ {
private readonly IFileProvider _fileProvider; private readonly IFileProvider _fileProvider;
public DefaultSwaggerProvider(IWebHostEnvironment webHostEnvironment) public DefaultSwaggerProvider(IWebHostEnvironment webHostEnvironment, ISettingsAccessor<PoliciesSettings> policies)
{ {
_fileProvider = webHostEnvironment.WebRootFileProvider; _fileProvider = webHostEnvironment.WebRootFileProvider;
Policies = policies;
} }
public BTCPayServerEnvironment Env { get; }
public ISettingsAccessor<PoliciesSettings> Policies { get; }
public async Task<JObject> Fetch() public async Task<JObject> Fetch()
{ {
@ -25,7 +30,10 @@ public class DefaultSwaggerProvider: ISwaggerProvider
{ {
await using var stream = fi.CreateReadStream(); await using var stream = fi.CreateReadStream();
using var reader = new StreamReader(fi.CreateReadStream()); using var reader = new StreamReader(fi.CreateReadStream());
json.Merge(JObject.Parse(await reader.ReadToEndAsync())); var jObject = JObject.Parse(await reader.ReadToEndAsync());
if (jObject.Remove("x_experimental") && !Policies.Settings.Experimental)
continue;
json.Merge(jObject);
} }
return json; return json;

View file

@ -0,0 +1,55 @@
#nullable enable
using System;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Events;
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json;
namespace BTCPayServer.Services
{
public interface ISettingsAccessor<T>
{
T Settings { get; }
}
class SettingsAccessor<T> : ISettingsAccessor<T>, IStartupTask, IHostedService where T : class, new()
{
T? _Settings;
public T Settings => _Settings ?? throw new InvalidOperationException($"Settings {typeof(T)} not yet initialized");
public EventAggregator Aggregator { get; }
public ISettingsRepository SettingsRepository { get; }
IDisposable? disposable;
public SettingsAccessor(EventAggregator aggregator, ISettingsRepository settings)
{
Aggregator = aggregator;
SettingsRepository = settings;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
if (_Settings != null)
return;
_Settings = await SettingsRepository.GetSettingAsync<T>() ?? new T();
disposable = Aggregator.Subscribe<SettingsChanged<T>>(v => _Settings = Clone(v.Settings));
}
private T Clone(T settings)
{
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(settings))!;
}
public Task StopAsync(CancellationToken cancellationToken)
{
disposable?.Dispose();
return Task.CompletedTask;
}
public async Task ExecuteAsync(CancellationToken cancellationToken = default)
{
await StartAsync(cancellationToken);
}
}
}

View file

@ -7,6 +7,7 @@ namespace BTCPayServer.Services.Mails
{ {
public class EmailSenderFactory public class EmailSenderFactory
{ {
public ISettingsAccessor<PoliciesSettings> PoliciesSettings { get; }
public Logs Logs { get; } public Logs Logs { get; }
private readonly IBackgroundJobClient _jobClient; private readonly IBackgroundJobClient _jobClient;
@ -15,23 +16,25 @@ namespace BTCPayServer.Services.Mails
public EmailSenderFactory(IBackgroundJobClient jobClient, public EmailSenderFactory(IBackgroundJobClient jobClient,
SettingsRepository settingsSettingsRepository, SettingsRepository settingsSettingsRepository,
ISettingsAccessor<PoliciesSettings> policiesSettings,
StoreRepository storeRepository, StoreRepository storeRepository,
Logs logs) Logs logs)
{ {
Logs = logs; Logs = logs;
_jobClient = jobClient; _jobClient = jobClient;
_settingsRepository = settingsSettingsRepository; _settingsRepository = settingsSettingsRepository;
PoliciesSettings = policiesSettings;
_storeRepository = storeRepository; _storeRepository = storeRepository;
} }
public async Task<IEmailSender> GetEmailSender(string storeId = null) public Task<IEmailSender> GetEmailSender(string storeId = null)
{ {
var serverSender = new ServerEmailSender(_settingsRepository, _jobClient, Logs); var serverSender = new ServerEmailSender(_settingsRepository, _jobClient, Logs);
if (string.IsNullOrEmpty(storeId)) if (string.IsNullOrEmpty(storeId))
return serverSender; return Task.FromResult<IEmailSender>(serverSender);
return new StoreEmailSender(_storeRepository, return Task.FromResult<IEmailSender>(new StoreEmailSender(_storeRepository,
!(await _settingsRepository.GetPolicies()).DisableStoresToUseServerEmailSettings ? serverSender : null, _jobClient, !PoliciesSettings.Settings.DisableStoresToUseServerEmailSettings ? serverSender : null, _jobClient,
storeId, Logs); storeId, Logs));
} }
} }
} }

View file

@ -44,6 +44,8 @@ namespace BTCPayServer.Services
public List<BlockExplorerOverrideItem> BlockExplorerLinks { get; set; } = new List<BlockExplorerOverrideItem>(); public List<BlockExplorerOverrideItem> BlockExplorerLinks { get; set; } = new List<BlockExplorerOverrideItem>();
public List<DomainToAppMappingItem> DomainToAppMapping { get; set; } = new List<DomainToAppMappingItem>(); public List<DomainToAppMappingItem> DomainToAppMapping { get; set; } = new List<DomainToAppMappingItem>();
[Display(Name = "Enable experimental features")]
public bool Experimental { get; set; }
public class BlockExplorerOverrideItem public class BlockExplorerOverrideItem
{ {

View file

@ -1,11 +1,11 @@
@using BTCPayServer.Abstractions.Contracts @using BTCPayServer.Abstractions.Contracts
@inject ISettingsRepository _settingsRepository @inject ISettingsRepository _settingsRepository
@inject BTCPayServer.Services.PoliciesSettings PoliciesSettings
@addTagHelper *, BundlerMinifier.TagHelpers @addTagHelper *, BundlerMinifier.TagHelpers
@{ var policies = await _settingsRepository.GetPolicies(); }
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="~/favicon.ico" type="image/x-icon"> <link rel="icon" href="~/favicon.ico" type="image/x-icon">
@if (policies.DiscourageSearchEngines) @if (PoliciesSettings.DiscourageSearchEngines)
{ {
<meta name="robots" content="noindex"> <meta name="robots" content="noindex">
} }

View file

@ -1,12 +1,13 @@
@using BTCPayServer.Abstractions.Contracts @using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Abstractions.Extensions @using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Services
@inject ISettingsRepository _settingsRepository @inject ISettingsRepository _settingsRepository
@inject ThemeSettings Theme
@addTagHelper *, BundlerMinifier.TagHelpers @addTagHelper *, BundlerMinifier.TagHelpers
@{ var theme = await _settingsRepository.GetTheme(); }
@if (theme.CustomTheme) @if (Theme.CustomTheme)
{ {
<link href="@Context.Request.GetRelativePathOrAbsolute(theme.CssUri)" rel="stylesheet" asp-append-version="true" /> <link href="@Context.Request.GetRelativePathOrAbsolute(Theme.CssUri)" rel="stylesheet" asp-append-version="true" />
} }
else else
{ {

View file

@ -2,11 +2,11 @@
@inject BTCPayServer.Services.BTCPayServerEnvironment _env @inject BTCPayServer.Services.BTCPayServerEnvironment _env
@inject SignInManager<ApplicationUser> _signInManager @inject SignInManager<ApplicationUser> _signInManager
@inject UserManager<ApplicationUser> _userManager @inject UserManager<ApplicationUser> _userManager
@inject ISettingsRepository _settingsRepository
@inject LinkGenerator _linkGenerator @inject LinkGenerator _linkGenerator
@inject BTCPayServer.Services.PoliciesSettings PoliciesSettings
@{ @{
var notificationDisabled = (await _settingsRepository.GetPolicies()).DisableInstantNotifications; var notificationDisabled = PoliciesSettings.DisableInstantNotifications;
if (!notificationDisabled) if (!notificationDisabled)
{ {
var user = await _userManager.GetUserAsync(User); var user = await _userManager.GetUserAsync(User);

View file

@ -1,14 +1,14 @@
@addTagHelper *, BundlerMinifier.TagHelpers @addTagHelper *, BundlerMinifier.TagHelpers
@inject ISettingsRepository _settingsRepository @inject BTCPayServer.Services.ThemeSettings Theme
@using BTCPayServer.Services.Apps @using BTCPayServer.Services.Apps
@using BTCPayServer.Abstractions.Contracts @using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Abstractions.Extensions @using BTCPayServer.Abstractions.Extensions
@model BTCPayServer.Models.AppViewModels.ViewPointOfSaleViewModel @model BTCPayServer.Models.AppViewModels.ViewPointOfSaleViewModel
@{ @{
ViewData["Title"] = Model.Title; ViewData["Title"] = Model.Title;
Layout = null; Layout = null;
var theme = await _settingsRepository.GetTheme();
} }
<!DOCTYPE html> <!DOCTYPE html>
@ -22,7 +22,7 @@
<link rel="apple-touch-startup-image" href="~/img/splash.png"> <link rel="apple-touch-startup-image" href="~/img/splash.png">
<link rel="manifest" href="~/manifest.json"> <link rel="manifest" href="~/manifest.json">
<bundle name="wwwroot/bundles/main-bundle.min.css" asp-append-version="true" /> <bundle name="wwwroot/bundles/main-bundle.min.css" asp-append-version="true" />
<link href="@Context.Request.GetRelativePathOrAbsolute(theme.CssUri)" rel="stylesheet" asp-append-version="true"/> <link href="@Context.Request.GetRelativePathOrAbsolute(Theme.CssUri)" rel="stylesheet" asp-append-version="true"/>
@if (Model.CustomCSSLink != null) @if (Model.CustomCSSLink != null)
{ {
<link href="@Model.CustomCSSLink" rel="stylesheet" /> <link href="@Model.CustomCSSLink" rel="stylesheet" />

View file

@ -1,6 +1,6 @@
@using BTCPayServer.Abstractions.Contracts @using BTCPayServer.Abstractions.Contracts
@model LoginViewModel @model LoginViewModel
@inject ISettingsRepository _settingsRepository @inject BTCPayServer.Services.PoliciesSettings PoliciesSettings
@{ @{
ViewData["Title"] = "Sign in"; ViewData["Title"] = "Sign in";
Layout = "_LayoutSignedOut"; Layout = "_LayoutSignedOut";
@ -35,7 +35,7 @@
<form asp-action="LoginWithCode" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" id="logincode-form"> <form asp-action="LoginWithCode" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" id="logincode-form">
<input asp-for="LoginCode" type="hidden" class="form-control"/> <input asp-for="LoginCode" type="hidden" class="form-control"/>
</form> </form>
@if (!(await _settingsRepository.GetPolicies()).LockSubscription) @if (!PoliciesSettings.LockSubscription)
{ {
<p class="text-center mt-2 mb-0"> <p class="text-center mt-2 mb-0">
<a id="Register" style="font-size:1.15rem" asp-action="Register" asp-route-returnurl="@ViewData["ReturnUrl"]" tabindex="4">Create your account</a> <a id="Register" style="font-size:1.15rem" asp-action="Register" asp-route-returnurl="@ViewData["ReturnUrl"]" tabindex="4">Create your account</a>

View file

@ -3,10 +3,10 @@
@inject ISettingsRepository SettingsRepository @inject ISettingsRepository SettingsRepository
@using BTCPayServer.Abstractions.Contracts @using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Models.AppViewModels @using BTCPayServer.Models.AppViewModels
@inject BTCPayServer.Services.ThemeSettings Theme
@{ @{
ViewData["Title"] = Model.Title; ViewData["Title"] = Model.Title;
Layout = null; Layout = null;
var theme = await SettingsRepository.GetTheme();
} }
<!DOCTYPE html> <!DOCTYPE html>
@ -264,7 +264,7 @@
</div> </div>
<div class="card-footer text-muted d-flex flex-wrap align-items-center"> <div class="card-footer text-muted d-flex flex-wrap align-items-center">
<div class="me-3" v-text="`Updated ${lastUpdated}`">Updated @Model.Info.LastUpdated</div> <div class="me-3" v-text="`Updated ${lastUpdated}`">Updated @Model.Info.LastUpdated</div>
@if (!theme.CustomTheme) @if (!Theme.CustomTheme)
{ {
<vc:theme-switch css-class="text-muted me-3" responsive="none"/> <vc:theme-switch css-class="text-muted me-3" responsive="none"/>
} }

View file

@ -5,10 +5,10 @@
@addTagHelper *, BundlerMinifier.TagHelpers @addTagHelper *, BundlerMinifier.TagHelpers
@inject BTCPayServer.Services.BTCPayServerEnvironment env @inject BTCPayServer.Services.BTCPayServerEnvironment env
@inject ISettingsRepository _settingsRepository @inject ISettingsRepository _settingsRepository
@inject BTCPayServer.Services.ThemeSettings Theme
@{ @{
ViewData["Title"] = Model.Title; ViewData["Title"] = Model.Title;
Layout = null; Layout = null;
var theme = await _settingsRepository.GetTheme();
string StatusClass(InvoiceState state) string StatusClass(InvoiceState state)
{ {
switch (state.Status.ToModernStatus()) switch (state.Status.ToModernStatus())
@ -365,7 +365,7 @@
<span class="text-muted mx-2"> <span class="text-muted mx-2">
Powered by <a href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener">BTCPay Server</a> Powered by <a href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener">BTCPay Server</a>
</span> </span>
@if (!theme.CustomTheme) @if (!Theme.CustomTheme)
{ {
<vc:theme-switch css-class="text-muted mx-2" responsive="none"/> <vc:theme-switch css-class="text-muted mx-2" responsive="none"/>
} }

View file

@ -5,9 +5,9 @@
@using BTCPayServer.Abstractions.Extensions @using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Lightning @using BTCPayServer.Lightning
@model BTCPayServer.Controllers.ShowLightningNodeInfoViewModel @model BTCPayServer.Controllers.ShowLightningNodeInfoViewModel
@inject BTCPayServer.Services.ThemeSettings Theme
@{ @{
Layout = null; Layout = null;
var theme = await _settingsRepository.GetTheme();
} }
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
@ -20,7 +20,7 @@
<link rel="apple-touch-startup-image" href="~/img/splash.png" asp-append-version="true"> <link rel="apple-touch-startup-image" href="~/img/splash.png" asp-append-version="true">
<link rel="manifest" href="~/manifest.json"> <link rel="manifest" href="~/manifest.json">
<bundle name="wwwroot/bundles/main-bundle.min.css" asp-append-version="true" /> <bundle name="wwwroot/bundles/main-bundle.min.css" asp-append-version="true" />
<link href="@Context.Request.GetRelativePathOrAbsolute(theme.CssUri)" rel="stylesheet" asp-append-version="true"/> <link href="@Context.Request.GetRelativePathOrAbsolute(Theme.CssUri)" rel="stylesheet" asp-append-version="true"/>
<link href="~/main/qrcode.css" rel="stylesheet" asp-append-version="true" /> <link href="~/main/qrcode.css" rel="stylesheet" asp-append-version="true" />
</head> </head>
<body> <body>

View file

@ -1,15 +1,15 @@
@inject BTCPayServer.Services.BTCPayServerEnvironment env
@inject BTCPayServer.Services.ThemeSettings Theme
@using NUglify.Helpers @using NUglify.Helpers
@using BTCPayServer.Abstractions.Contracts @using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Abstractions.Extensions @using BTCPayServer.Abstractions.Extensions
@model BTCPayServer.Models.ViewPullPaymentModel @model BTCPayServer.Models.ViewPullPaymentModel
@addTagHelper *, BundlerMinifier.TagHelpers @addTagHelper *, BundlerMinifier.TagHelpers
@inject BTCPayServer.Services.BTCPayServerEnvironment env
@inject ISettingsRepository _settingsRepository
@{ @{
ViewData["Title"] = Model.Title; ViewData["Title"] = Model.Title;
Layout = null; Layout = null;
var theme = await _settingsRepository.GetTheme();
string StatusTextClass(string status) string StatusTextClass(string status)
{ {
switch (status) switch (status)
@ -196,7 +196,7 @@
<span class="text-muted mx-2"> <span class="text-muted mx-2">
Powered by <a href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener">BTCPay Server</a> Powered by <a href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener">BTCPay Server</a>
</span> </span>
@if (!theme.CustomTheme) @if (!Theme.CustomTheme)
{ {
<vc:theme-switch css-class="text-muted mx-2" responsive="none"/> <vc:theme-switch css-class="text-muted mx-2" responsive="none"/>
} }

View file

@ -111,6 +111,10 @@
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span> <span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a> </a>
<span asp-validation-for="DiscourageSearchEngines" class="text-danger"></span> <span asp-validation-for="DiscourageSearchEngines" class="text-danger"></span>
</div>
<div class="form-check my-3">
<input asp-for="Experimental" type="checkbox" class="form-check-input"/>
<label asp-for="Experimental" class="form-check-label"></label>
</div> </div>
</div> </div>

View file

@ -1,9 +1,10 @@
{ {
"x_experimental": true,
"paths": { "paths": {
"/api/v1/custodians": { "/api/v1/custodians": {
"get": { "get": {
"tags": [ "tags": [
"Custodians (Experimental)" "Custodians"
], ],
"summary": "List supported custodians", "summary": "List supported custodians",
"description": "List all supported custodians for the BTCPay instance. You can install plugins to add more custodians.", "description": "List all supported custodians for the BTCPay instance. You can install plugins to add more custodians.",
@ -34,7 +35,7 @@
"/api/v1/stores/{storeId}/custodian-accounts": { "/api/v1/stores/{storeId}/custodian-accounts": {
"get": { "get": {
"tags": [ "tags": [
"Custodians (Experimental)" "Custodians"
], ],
"summary": "List store custodian accounts", "summary": "List store custodian accounts",
"parameters": [ "parameters": [
@ -79,7 +80,7 @@
}, },
"post": { "post": {
"tags": [ "tags": [
"Custodians (Experimental)" "Custodians"
], ],
"summary": "Add a custodial account to a store.", "summary": "Add a custodial account to a store.",
"description": "Add a custodial account to a store.", "description": "Add a custodial account to a store.",
@ -123,7 +124,7 @@
"/api/v1/stores/{storeId}/custodian-accounts/{accountId}": { "/api/v1/stores/{storeId}/custodian-accounts/{accountId}": {
"get": { "get": {
"tags": [ "tags": [
"Custodians (Experimental)" "Custodians"
], ],
"summary": "Get store custodian account", "summary": "Get store custodian account",
"parameters": [ "parameters": [
@ -174,7 +175,7 @@
}, },
"put": { "put": {
"tags": [ "tags": [
"Custodians (Experimental)" "Custodians"
], ],
"summary": "Update custodial account", "summary": "Update custodial account",
"description": "Update custodial account", "description": "Update custodial account",
@ -216,7 +217,7 @@
}, },
"delete": { "delete": {
"tags": [ "tags": [
"Custodians (Experimental)" "Custodians"
], ],
"summary": "Delete store custodian account", "summary": "Delete store custodian account",
"description": "Deletes a custodial account", "description": "Deletes a custodial account",
@ -233,7 +234,7 @@
"/api/v1/stores/{storeId}/custodian-accounts/{accountId}/trades/quote": { "/api/v1/stores/{storeId}/custodian-accounts/{accountId}/trades/quote": {
"get": { "get": {
"tags": [ "tags": [
"Custodians (Experimental)" "Custodians"
], ],
"summary": "Get quote for trading one asset for another", "summary": "Get quote for trading one asset for another",
"description": "Get the current bid and ask price for converting one asset into another.", "description": "Get the current bid and ask price for converting one asset into another.",
@ -306,7 +307,7 @@
"/api/v1/stores/{storeId}/custodian-accounts/{accountId}/trades/market": { "/api/v1/stores/{storeId}/custodian-accounts/{accountId}/trades/market": {
"post": { "post": {
"tags": [ "tags": [
"Custodians (Experimental)" "Custodians"
], ],
"summary": "Trade one asset for another", "summary": "Trade one asset for another",
"description": "Trade one asset for another using a market order (=instant purchase with instant result or failure). A suitable asset pair will automatically be selected. If no asset pair is available, the call will fail.", "description": "Trade one asset for another using a market order (=instant purchase with instant result or failure). A suitable asset pair will automatically be selected. If no asset pair is available, the call will fail.",
@ -371,7 +372,7 @@
"/api/v1/stores/{storeId}/custodian-accounts/{accountId}/addresses/{paymentMethod}": { "/api/v1/stores/{storeId}/custodian-accounts/{accountId}/addresses/{paymentMethod}": {
"get": { "get": {
"tags": [ "tags": [
"Custodians (Experimental)" "Custodians"
], ],
"summary": "Get a deposit address for custodian", "summary": "Get a deposit address for custodian",
"description": "Get a new deposit address for the custodian using the specified payment method (network + crypto code).", "description": "Get a new deposit address for the custodian using the specified payment method (network + crypto code).",
@ -445,7 +446,7 @@
"/api/v1/stores/{storeId}/custodian-accounts/{accountId}/withdrawals": { "/api/v1/stores/{storeId}/custodian-accounts/{accountId}/withdrawals": {
"post": { "post": {
"tags": [ "tags": [
"Custodians (Experimental)" "Custodians"
], ],
"summary": "Withdraw to store wallet", "summary": "Withdraw to store wallet",
"description": "Withdraw an asset to your store wallet.", "description": "Withdraw an asset to your store wallet.",
@ -531,7 +532,7 @@
"/api/v1/stores/{storeId}/custodian-accounts/{accountId}/withdrawals/{withdrawalId}": { "/api/v1/stores/{storeId}/custodian-accounts/{accountId}/withdrawals/{withdrawalId}": {
"post": { "post": {
"tags": [ "tags": [
"Custodians (Experimental)" "Custodians"
], ],
"summary": "Get withdrawal info", "summary": "Get withdrawal info",
"description": "Get the details about a past withdrawal.", "description": "Get the details about a past withdrawal.",
@ -1034,7 +1035,7 @@
}, },
"tags": [ "tags": [
{ {
"name": "Custodians (Experimental)" "name": "Custodians"
} }
] ]
} }