Support Admin being able to view stores (#5782)

* Support Admin being able to view stores

* fix null check

* Delete obsolete empty view

* Add test

* Apply CanViewStoreSettings policy changes

Taken from #5719

* Fix Selenium tests

* Update dashboard permission requirement

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
This commit is contained in:
Andrew Camilleri 2024-03-14 10:25:40 +01:00 committed by GitHub
parent f1ff913cbe
commit e497903bf4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 440 additions and 286 deletions

View file

@ -645,5 +645,26 @@ retry:
Driver.FindElement(By.Id($"SectionNav-{navPages}")).Click();
}
}
public void AssertPageAccess(bool shouldHaveAccess, string url)
{
GoToUrl(url);
Assert.DoesNotMatch("404 - Page not found</h", Driver.PageSource);
if (shouldHaveAccess)
Assert.DoesNotMatch("- Denied</h", Driver.PageSource);
else
Assert.Contains("- Denied</h", Driver.PageSource);
}
public (string appName, string appId) CreateApp(string type, string name = null)
{
if (string.IsNullOrEmpty(name)) name = $"{type}-{Guid.NewGuid().ToString()[..14]}";
Driver.FindElement(By.Id($"StoreNav-Create{type}")).Click();
Driver.FindElement(By.Name("AppName")).SendKeys(name);
Driver.FindElement(By.Id("Create")).Click();
Assert.Contains("App successfully created", FindAlertMessage().Text);
var appId = Driver.Url.Split('/')[4];
return (name, appId);
}
}
}

View file

@ -998,7 +998,7 @@ namespace BTCPayServer.Tests
var storeLink = s.Driver.FindElement(By.Id($"Store-{storeId}"));
Assert.Contains(storeName, storeLink.Text);
storeLink.Click();
s.GoToStore(storeId);
s.Driver.FindElement(By.Id("btn-archive-toggle")).Click();
Assert.Contains("The store has been unarchived and will appear in the stores list by default again.", s.FindAlertMessage().Text);
}
@ -3359,6 +3359,55 @@ retry:
Assert.Contains("Malice",s.Driver.PageSource);
Assert.DoesNotContain(Policies.CanModifyServerSettings,s.Driver.PageSource);
}
[Fact]
[Trait("Selenium", "Selenium")]
[Trait("Lightning", "Lightning")]
public async Task CanAccessUserStoreAsAdmin()
{
using var s = CreateSeleniumTester(newDb: true);
s.Server.ActivateLightning();
await s.StartAsync();
await s.Server.EnsureChannelsSetup();
var storeSettingsPaths = new [] {"settings", "rates", "checkout", "tokens", "users", "roles", "webhooks", "payout-processors", "payout-processors/onchain-automated/BTC", "payout-processors/lightning-automated/BTC", "emails", "email-settings", "forms"};
// Setup user, store and wallets
s.RegisterNewUser();
(_, string storeId) = s.CreateNewStore();
s.GoToStore();
s.GenerateWallet(isHotWallet: true);
s.AddLightningNode(LightningConnectionType.CLightning, false);
// Add apps
(_, string posId) = s.CreateApp("PointOfSale");
(_, string crowdfundId) = s.CreateApp("Crowdfund");
s.Logout();
// Setup admin and check access
s.GoToRegister();
s.RegisterNewUser(true);
string GetStorePath(string subPath) => $"/stores/{storeId}/{subPath}";
// Owner access
s.AssertPageAccess(false, GetStorePath(""));
s.AssertPageAccess(true, GetStorePath("reports"));
s.AssertPageAccess(true, GetStorePath("invoices"));
s.AssertPageAccess(false, GetStorePath("invoices/create"));
s.AssertPageAccess(true, GetStorePath("payment-requests"));
s.AssertPageAccess(false, GetStorePath("payment-requests/edit"));
s.AssertPageAccess(true, GetStorePath("pull-payments"));
s.AssertPageAccess(true, GetStorePath("payouts"));
s.AssertPageAccess(false, GetStorePath("onchain/BTC"));
s.AssertPageAccess(false, GetStorePath("onchain/BTC/settings"));
s.AssertPageAccess(false, GetStorePath("lightning/BTC"));
s.AssertPageAccess(false, GetStorePath("lightning/BTC/settings"));
s.AssertPageAccess(false, GetStorePath("apps/create"));
foreach (var path in storeSettingsPaths)
{ // should have view access to settings, but no submit buttons or create links
s.AssertPageAccess(true, $"stores/{storeId}/{path}");
s.Driver.ElementDoesNotExist(By.CssSelector("#mainContent .btn-primary"));
}
}
private static void CanBrowseContent(SeleniumTester s)
{

View file

@ -26,16 +26,16 @@
{
@if (Model.Store != null)
{
<div class="accordion-item" permission="@Policies.CanModifyStoreSettings">
<div class="accordion-item" permission="@Policies.CanViewStoreSettings">
<div class="accordion-body">
<ul class="navbar-nav">
<li class="nav-item">
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<a asp-area="" asp-controller="UIStores" asp-action="Dashboard" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.Dashboard)" id="StoreNav-Dashboard">
<vc:icon symbol="home"/>
<span>Dashboard</span>
</a>
</li>
<li class="nav-item">
<li class="nav-item" permission="@Policies.CanViewStoreSettings">
<a asp-area="" asp-controller="UIStores" asp-action="GeneralSettings" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(new [] {StoreNavPages.Rates, StoreNavPages.CheckoutAppearance, StoreNavPages.General, StoreNavPages.Tokens, StoreNavPages.Users, StoreNavPages.Webhooks, StoreNavPages.PayoutProcessors, StoreNavPages.Emails})" id="StoreNav-StoreSettings">
<vc:icon symbol="settings"/>
<span>Settings</span>
@ -115,7 +115,6 @@
</a>
</li>
}
</ul>
</div>
</div>

View file

@ -76,17 +76,17 @@ namespace BTCPayServer.Controllers
if (storeId != null)
{
// verify store exists and redirect to it
var store = await _storeRepository.FindStore(storeId, userId);
var store = await _storeRepository.FindStore(storeId);
if (store != null)
{
return RedirectToStore(userId, store);
return RedirectToStore(userId, storeId);
}
}
var stores = await _storeRepository.GetStoresByUserId(userId);
var activeStore = stores.FirstOrDefault(s => !s.Archived);
return activeStore != null
? RedirectToStore(userId, activeStore)
? RedirectToStore(userId, activeStore.Id)
: RedirectToAction(nameof(UIUserStoresController.CreateStore), "UIUserStores");
}
@ -198,14 +198,9 @@ namespace BTCPayServer.Controllers
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
public static RedirectToActionResult RedirectToStore(string userId, StoreData store)
public static RedirectToActionResult RedirectToStore(string userId, string storeId)
{
var perms = store.GetPermissionSet(userId);
if (perms.Contains(Policies.CanModifyStoreSettings, store.Id))
return new RedirectToActionResult("Dashboard", "UIStores", new {storeId = store.Id});
if (perms.Contains(Policies.CanViewInvoices, store.Id))
return new RedirectToActionResult("ListInvoices", "UIInvoice", new { storeId = store.Id });
return new RedirectToActionResult("Index", "UIStores", new {storeId = store.Id});
return new RedirectToActionResult("Index", "UIStores", new {storeId});
}
}
}

View file

@ -1144,7 +1144,7 @@ namespace BTCPayServer.Controllers
[HttpGet("/stores/{storeId}/invoices/create")]
[HttpGet("invoices/create")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanCreateInvoice, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[BitpayAPIConstraint(false)]
public async Task<IActionResult> CreateInvoice(InvoicesModel? model = null)
{
@ -1154,7 +1154,7 @@ namespace BTCPayServer.Controllers
return RedirectToAction(nameof(UIHomeController.Index), "UIHome");
}
var store = await _StoreRepository.FindStore(model.StoreId, GetUserId());
var store = await _StoreRepository.FindStore(model.StoreId);
if (store == null)
return NotFound();

View file

@ -108,7 +108,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("/stores/{storeId}/payment-requests/edit/{payReqId?}")]
[Authorize(Policy = Policies.CanViewPaymentRequests, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanModifyPaymentRequests, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> EditPaymentRequest(string storeId, string payReqId)
{
var store = GetCurrentStore();

View file

@ -83,7 +83,7 @@ namespace BTCPayServer.Controllers
Message = "You must enable at least one payment method before creating a pull payment.",
Severity = StatusMessageModel.StatusSeverity.Error
});
return RedirectToAction(nameof(UIStoresController.Dashboard), "UIStores", new { storeId });
return RedirectToAction(nameof(UIStoresController.Index), "UIStores", new { storeId });
}
return View(new NewPullPaymentModel
@ -161,6 +161,7 @@ namespace BTCPayServer.Controllers
return RedirectToAction(nameof(PullPayments), new { storeId = storeId });
}
[Authorize(Policy = Policies.CanViewPullPayments, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[HttpGet("stores/{storeId}/pull-payments")]
public async Task<IActionResult> PullPayments(
string storeId,
@ -199,7 +200,7 @@ namespace BTCPayServer.Controllers
Message = "You must enable at least one payment method before creating a pull payment.",
Severity = StatusMessageModel.StatusSeverity.Error
});
return RedirectToAction(nameof(UIStoresController.Dashboard), "UIStores", new { storeId });
return RedirectToAction(nameof(UIStoresController.Index), "UIStores", new { storeId });
}
var vm = this.ParseListQuery(new PullPaymentsModel
@ -482,7 +483,7 @@ namespace BTCPayServer.Controllers
Message = "You must enable at least one payment method before creating a payout.",
Severity = StatusMessageModel.StatusSeverity.Error
});
return RedirectToAction(nameof(UIStoresController.Dashboard), "UIStores", new { storeId });
return RedirectToAction(nameof(UIStoresController.Index), "UIStores", new { storeId });
}
var vm = this.ParseListQuery(new PayoutsModel

View file

@ -1,12 +1,15 @@
#nullable enable
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Client;
using BTCPayServer.Components.StoreLightningBalance;
using BTCPayServer.Components.StoreNumbers;
using BTCPayServer.Components.StoreRecentInvoices;
using BTCPayServer.Components.StoreRecentTransactions;
using BTCPayServer.Data;
using BTCPayServer.Models.StoreViewModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Controllers
@ -14,9 +17,13 @@ namespace BTCPayServer.Controllers
public partial class UIStoresController
{
[HttpGet("{storeId}")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> Dashboard()
{
var store = CurrentStore;
if (store is null)
return NotFound();
var storeBlob = store.GetStoreBlob();
AddPaymentMethods(store, storeBlob,
@ -38,16 +45,17 @@ namespace BTCPayServer.Controllers
};
// Widget data
if (!vm.WalletEnabled && !vm.LightningEnabled)
if (vm is { WalletEnabled: false, LightningEnabled: false })
return View(vm);
var userId = GetUserId();
if (userId is null)
return NotFound();
var apps = await _appService.GetAllApps(userId, false, store.Id);
foreach (var app in apps)
{
var appData = await _appService.GetAppDataIfOwner(userId, app.Id);
var appData = await _appService.GetAppData(userId, app.Id);
vm.Apps.Add(appData);
}
@ -55,6 +63,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{storeId}/dashboard/{cryptoCode}/lightning/balance")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult LightningBalance(string storeId, string cryptoCode)
{
var store = HttpContext.GetStoreData();
@ -66,6 +75,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{storeId}/dashboard/{cryptoCode}/numbers")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult StoreNumbers(string storeId, string cryptoCode)
{
var store = HttpContext.GetStoreData();
@ -77,6 +87,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{storeId}/dashboard/{cryptoCode}/recent-transactions")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult RecentTransactions(string storeId, string cryptoCode)
{
var store = HttpContext.GetStoreData();
@ -88,6 +99,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{storeId}/dashboard/{cryptoCode}/recent-invoices")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult RecentInvoices(string storeId, string cryptoCode)
{
var store = HttpContext.GetStoreData();

View file

@ -7,9 +7,11 @@ using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client;
using BTCPayServer.Data;
using BTCPayServer.Models;
using BTCPayServer.Services.Mails;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MimeKit;
@ -43,6 +45,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/emails")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> StoreEmails(string storeId, StoreEmailRuleViewModel vm, string command)
{
vm.Rules ??= new List<StoreEmailRule>();
@ -185,13 +188,13 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/email-settings")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> StoreEmailSettings(string storeId, EmailsViewModel model, string command)
{
var store = HttpContext.GetStoreData();
if (store == null)
return NotFound();
var emailSender = await _emailSenderFactory.GetEmailSender(store.Id) as StoreEmailSender;
var fallbackSettings = await _emailSenderFactory.GetEmailSender(store.Id) is StoreEmailSender { FallbackSender: not null } storeSender
? await storeSender.FallbackSender.GetEmailSettings()
: null;

View file

@ -4,10 +4,12 @@ using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Data;
using BTCPayServer.Models;
using BTCPayServer.Models.StoreViewModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using NBitcoin;
using NBitcoin.DataEncoders;
@ -46,6 +48,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{storeId}/webhooks/new")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult NewWebhook()
{
return View(nameof(ModifyWebhook), new EditWebhookViewModel
@ -58,6 +61,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{storeId}/webhooks/{webhookId}/remove")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> DeleteWebhook(string webhookId)
{
var webhook = await _Repo.GetWebhook(CurrentStore.Id, webhookId);
@ -68,6 +72,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/webhooks/{webhookId}/remove")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> DeleteWebhookPost(string webhookId)
{
var webhook = await _Repo.GetWebhook(CurrentStore.Id, webhookId);
@ -80,6 +85,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/webhooks/new")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> NewWebhook(string storeId, EditWebhookViewModel viewModel)
{
if (!ModelState.IsValid)
@ -91,6 +97,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{storeId}/webhooks/{webhookId}")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> ModifyWebhook(string webhookId)
{
var webhook = await _Repo.GetWebhook(CurrentStore.Id, webhookId);
@ -107,6 +114,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/webhooks/{webhookId}")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> ModifyWebhook(string webhookId, EditWebhookViewModel viewModel)
{
var webhook = await _Repo.GetWebhook(CurrentStore.Id, webhookId);
@ -121,6 +129,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{storeId}/webhooks/{webhookId}/test")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> TestWebhook(string webhookId)
{
var webhook = await _Repo.GetWebhook(CurrentStore.Id, webhookId);
@ -131,6 +140,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/webhooks/{webhookId}/test")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> TestWebhook(string webhookId, TestWebhookViewModel viewModel, CancellationToken cancellationToken)
{
var result = await WebhookNotificationManager.TestWebhook(CurrentStore.Id, webhookId, viewModel.Type, cancellationToken);
@ -148,6 +158,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}/redeliver")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> RedeliverWebhook(string webhookId, string deliveryId)
{
var delivery = await _Repo.GetWebhookDelivery(CurrentStore.Id, webhookId, deliveryId);
@ -168,6 +179,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}/request")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> WebhookDelivery(string webhookId, string deliveryId)
{
var delivery = await _Repo.GetWebhookDelivery(CurrentStore.Id, webhookId, deliveryId);

View file

@ -5,7 +5,7 @@ using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Components.StoreLightningBalance;
using BTCPayServer.Client;
using BTCPayServer.Configuration;
using BTCPayServer.Data;
using BTCPayServer.Lightning;
@ -14,7 +14,7 @@ using BTCPayServer.Models;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
@ -22,8 +22,8 @@ namespace BTCPayServer.Controllers
{
public partial class UIStoresController
{
[HttpGet("{storeId}/lightning/{cryptoCode}")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult Lightning(string storeId, string cryptoCode)
{
var store = HttpContext.GetStoreData();
@ -85,6 +85,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{storeId}/lightning/{cryptoCode}/setup")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult SetupLightningNode(string storeId, string cryptoCode)
{
var store = HttpContext.GetStoreData();
@ -101,6 +102,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/lightning/{cryptoCode}/setup")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> SetupLightningNode(string storeId, LightningNodeViewModel vm, string command, string cryptoCode)
{
vm.CryptoCode = cryptoCode;
@ -217,6 +219,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{storeId}/lightning/{cryptoCode}/settings")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult LightningSettings(string storeId, string cryptoCode)
{
var store = HttpContext.GetStoreData();
@ -257,6 +260,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/lightning/{cryptoCode}/settings")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> LightningSettings(LightningSettingsViewModel vm)
{
var store = HttpContext.GetStoreData();
@ -310,6 +314,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/lightning/{cryptoCode}/status")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> SetLightningNodeEnabled(string storeId, string cryptoCode, bool enabled)
{
var store = HttpContext.GetStoreData();

View file

@ -1,5 +1,4 @@
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
@ -8,12 +7,12 @@ using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client;
using BTCPayServer.Data;
using BTCPayServer.Events;
using BTCPayServer.Models;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using NBitcoin;
@ -27,6 +26,7 @@ namespace BTCPayServer.Controllers
public partial class UIStoresController
{
[HttpGet("{storeId}/onchain/{cryptoCode}")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public ActionResult SetupWallet(WalletSetupViewModel vm)
{
var checkResult = IsAvailable(vm.CryptoCode, out var store, out _);
@ -42,6 +42,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{storeId}/onchain/{cryptoCode}/import/{method?}")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> ImportWallet(WalletSetupViewModel vm)
{
var checkResult = IsAvailable(vm.CryptoCode, out _, out var network);
@ -71,6 +72,7 @@ namespace BTCPayServer.Controllers
[HttpPost("{storeId}/onchain/{cryptoCode}/modify")]
[HttpPost("{storeId}/onchain/{cryptoCode}/import/{method}")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> UpdateWallet(WalletSetupViewModel vm)
{
var checkResult = IsAvailable(vm.CryptoCode, out var store, out var network);
@ -197,6 +199,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{storeId}/onchain/{cryptoCode}/generate/{method?}")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> GenerateWallet(WalletSetupViewModel vm)
{
var checkResult = IsAvailable(vm.CryptoCode, out _, out var network);
@ -235,8 +238,11 @@ namespace BTCPayServer.Controllers
return View(vm.ViewName, vm);
}
internal GenerateWalletResponse GenerateWalletResponse;
[HttpPost("{storeId}/onchain/{cryptoCode}/generate/{method}")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> GenerateWallet(string storeId, string cryptoCode, WalletSetupMethod method, WalletSetupRequest request)
{
var checkResult = IsAvailable(cryptoCode, out _, out var network);
@ -356,6 +362,7 @@ namespace BTCPayServer.Controllers
// The purpose of this action is to show the user a success message, which confirms
// that the store settings have been updated after generating a new wallet.
[HttpGet("{storeId}/onchain/{cryptoCode}/generate/confirm")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public ActionResult GenerateWalletConfirm(string storeId, string cryptoCode)
{
var checkResult = IsAvailable(cryptoCode, out _, out var network);
@ -371,6 +378,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{storeId}/onchain/{cryptoCode}/settings")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> WalletSettings(string storeId, string cryptoCode)
{
var checkResult = IsAvailable(cryptoCode, out var store, out var network);
@ -440,6 +448,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/onchain/{cryptoCode}/settings/wallet")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> UpdateWalletSettings(WalletSettingsViewModel vm)
{
var checkResult = IsAvailable(vm.CryptoCode, out var store, out _);
@ -549,6 +558,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/onchain/{cryptoCode}/settings/payment")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> UpdatePaymentSettings(WalletSettingsViewModel vm)
{
var checkResult = IsAvailable(vm.CryptoCode, out var store, out _);
@ -612,6 +622,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{storeId}/onchain/{cryptoCode}/seed")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> WalletSeed(string storeId, string cryptoCode, CancellationToken cancellationToken = default)
{
var checkResult = IsAvailable(cryptoCode, out var store, out var network);
@ -658,6 +669,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{storeId}/onchain/{cryptoCode}/replace")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public ActionResult ReplaceWallet(string storeId, string cryptoCode)
{
var checkResult = IsAvailable(cryptoCode, out var store, out var network);
@ -677,6 +689,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/onchain/{cryptoCode}/replace")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult ConfirmReplaceWallet(string storeId, string cryptoCode)
{
var checkResult = IsAvailable(cryptoCode, out var store, out _);
@ -695,6 +708,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{storeId}/onchain/{cryptoCode}/delete")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public ActionResult DeleteWallet(string storeId, string cryptoCode)
{
var checkResult = IsAvailable(cryptoCode, out var store, out var network);
@ -714,6 +728,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/onchain/{cryptoCode}/delete")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> ConfirmDeleteWallet(string storeId, string cryptoCode)
{
var checkResult = IsAvailable(cryptoCode, out var store, out var network);

View file

@ -1,15 +1,12 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using Amazon.S3.Transfer;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client;
using BTCPayServer.Models.ServerViewModels;
using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Controllers
@ -60,19 +57,19 @@ namespace BTCPayServer.Controllers
ModelState.Remove(nameof(role));
return View(new UpdateRoleViewModel());
}
else
var roleData = await storeRepository.GetStoreRole(new StoreRoleId(storeId, role));
if (roleData == null)
return NotFound();
return View(new UpdateRoleViewModel
{
var roleData = await storeRepository.GetStoreRole(new StoreRoleId(storeId, role));
if (roleData == null)
return NotFound();
return View(new UpdateRoleViewModel()
{
Policies = roleData.Permissions,
Role = roleData.Role
});
}
}
Policies = roleData.Permissions,
Role = roleData.Role
});
}
[HttpPost("{storeId}/roles/{role}")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> CreateOrEditRole(
string storeId,
[FromServices] StoreRepository storeRepository,
@ -119,10 +116,9 @@ namespace BTCPayServer.Controllers
return RedirectToAction(nameof(ListRoles), new { storeId });
}
[HttpGet("{storeId}/roles/{role}/delete")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> DeleteRole(
string storeId,
[FromServices] StoreRepository storeRepository,
@ -142,6 +138,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/roles/{role}/delete")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> DeleteRolePost(
string storeId,
[FromServices] StoreRepository storeRepository,

View file

@ -12,7 +12,6 @@ using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client;
using BTCPayServer.Configuration;
using BTCPayServer.Data;
using BTCPayServer.HostedServices;
using BTCPayServer.HostedServices.Webhooks;
using BTCPayServer.Models;
using BTCPayServer.Models.StoreViewModels;
@ -39,10 +38,9 @@ using StoreData = BTCPayServer.Data.StoreData;
namespace BTCPayServer.Controllers
{
[Route("stores")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[AutoValidateAntiforgeryToken]
public partial class UIStoresController : Controller
{
@ -145,21 +143,19 @@ namespace BTCPayServer.Controllers
if (string.IsNullOrEmpty(userId))
return Forbid();
var store = await _Repo.FindStore(storeId, userId);
var store = await _Repo.FindStore(storeId);
if (store is null)
{
return Forbid();
}
HttpContext.SetStoreData(store);
if (store.GetPermissionSet(userId).Contains(Policies.CanModifyStoreSettings, storeId))
return NotFound();
if ((await _authorizationService.AuthorizeAsync(User, Policies.CanModifyStoreSettings)).Succeeded)
{
return RedirectToAction("Dashboard", new { storeId });
}
if (store.GetPermissionSet(userId).Contains(Policies.CanViewInvoices, storeId))
if ((await _authorizationService.AuthorizeAsync(User, Policies.CanViewInvoices)).Succeeded)
{
return RedirectToAction("ListInvoices", "UIInvoice", new { storeId });
}
return View();
return Forbid();
}
[HttpGet("{storeId}/users")]
@ -182,9 +178,10 @@ namespace BTCPayServer.Controllers
}).ToList();
}
public StoreData CurrentStore => HttpContext.GetStoreData();
public StoreData? CurrentStore => HttpContext.GetStoreData();
[HttpPost("{storeId}/users")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> StoreUsers(string storeId, StoreUsersViewModel vm)
{
await FillUsers(vm);
@ -217,6 +214,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{storeId}/users/{userId}/delete")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> DeleteStoreUser(string userId)
{
var user = await _UserManager.FindByIdAsync(userId);
@ -226,6 +224,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/users/{userId}/delete")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> DeleteStoreUserPost(string storeId, string userId)
{
if (await _Repo.RemoveStoreUser(storeId, userId))
@ -255,6 +254,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/rates")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> Rates(RatesViewModel model, string? command = null, string? storeId = null, CancellationToken cancellationToken = default)
{
if (command == "scripting-on")
@ -373,6 +373,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{storeId}/rates/confirm")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult ShowRateRules(bool scripting)
{
return View("Confirm", new ConfirmModel
@ -387,6 +388,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/rates/confirm")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> ShowRateRulesPost(bool scripting)
{
var blob = CurrentStore.GetStoreBlob();
@ -487,6 +489,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/checkout")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> CheckoutAppearance(CheckoutAppearanceViewModel model, [FromForm] bool RemoveSoundFile = false)
{
bool needUpdate = false;
@ -723,6 +726,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/settings")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> GeneralSettings(
GeneralSettingsViewModel model,
[FromForm] bool RemoveLogoFile = false,
@ -863,7 +867,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/archive")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyStoreSettings)]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> ToggleArchive(string storeId)
{
CurrentStore.Archived = !CurrentStore.Archived;
@ -880,12 +884,14 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{storeId}/delete")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult DeleteStore(string storeId)
{
return View("Confirm", new ConfirmModel("Delete store", "The store will be permanently deleted. This action will also delete all invoices, apps and data associated with the store. Are you sure?", "Delete"));
}
[HttpPost("{storeId}/delete")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> DeleteStorePost(string storeId)
{
await _Repo.DeleteStore(CurrentStore.Id);
@ -945,6 +951,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{storeId}/tokens/{tokenId}/revoke")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> RevokeToken(string tokenId)
{
var token = await _TokenRepository.GetToken(tokenId);
@ -954,6 +961,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/tokens/{tokenId}/revoke")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> RevokeTokenConfirm(string tokenId)
{
var token = await _TokenRepository.GetToken(tokenId);
@ -967,6 +975,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{storeId}/tokens/{tokenId}")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> ShowToken(string tokenId)
{
var token = await _TokenRepository.GetToken(tokenId);
@ -976,6 +985,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{storeId}/tokens/create")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult CreateToken(string storeId)
{
var model = new CreateTokenViewModel();
@ -987,6 +997,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/tokens/create")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> CreateToken(string storeId, CreateTokenViewModel model)
{
if (!ModelState.IsValid)
@ -1065,6 +1076,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/tokens/apikey")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> GenerateAPIKey(string storeId, string command = "")
{
var store = HttpContext.GetStoreData();
@ -1129,6 +1141,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("/api-access-request")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> Pair(string pairingCode, string storeId)
{
if (pairingCode == null)

View file

@ -439,7 +439,7 @@ namespace BTCPayServer.Controllers
{
if (walletId?.StoreId == null)
return NotFound();
var store = await Repository.FindStore(walletId.StoreId, GetUserId());
var store = await Repository.FindStore(walletId.StoreId);
var paymentMethod = GetDerivationSchemeSettings(walletId);
if (paymentMethod == null || store is null)
return NotFound();
@ -564,7 +564,7 @@ namespace BTCPayServer.Controllers
{
if (walletId?.StoreId == null)
return NotFound();
var store = await Repository.FindStore(walletId.StoreId, GetUserId());
var store = await Repository.FindStore(walletId.StoreId);
if (store == null)
return NotFound();
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);

View file

@ -10,7 +10,7 @@ namespace BTCPayServer
{
public static StoreRole? GetStoreRoleOfUser(this StoreData store, string userId)
{
return store.UserStores.FirstOrDefault(r => r.ApplicationUserId == userId)?.StoreRole;
return store.UserStores?.FirstOrDefault(r => r.ApplicationUserId == userId)?.StoreRole;
}
public static PermissionSet GetPermissionSet(this StoreRole storeRole, string storeId)

View file

@ -22,7 +22,7 @@ using Newtonsoft.Json.Linq;
namespace BTCPayServer.Forms;
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public class UIFormsController : Controller
{
private readonly FormDataService _formDataService;
@ -48,6 +48,7 @@ public class UIFormsController : Controller
}
[HttpGet("~/stores/{storeId}/forms/new")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult Create(string storeId)
{
var vm = new ModifyForm { FormConfig = new Form().ToString() };
@ -55,6 +56,7 @@ public class UIFormsController : Controller
}
[HttpGet("~/stores/{storeId}/forms/modify/{id}")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> Modify(string storeId, string id)
{
var form = await _formDataService.GetForm(storeId, id);
@ -66,6 +68,7 @@ public class UIFormsController : Controller
}
[HttpPost("~/stores/{storeId}/forms/modify/{id?}")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> Modify(string storeId, string? id, ModifyForm modifyForm)
{
if (id is not null)
@ -122,6 +125,7 @@ public class UIFormsController : Controller
}
[HttpPost("~/stores/{storeId}/forms/{id}/remove")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> Remove(string storeId, string id)
{
await _formDataService.RemoveForm(id, storeId);

View file

@ -31,9 +31,10 @@ public class UILightningAutomatedPayoutProcessorsController : Controller
_lightningAutomatedPayoutSenderFactory = lightningAutomatedPayoutSenderFactory;
_payoutProcessorService = payoutProcessorService;
}
[HttpGet("~/stores/{storeId}/payout-processors/lightning-automated/{cryptocode}")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> Configure(string storeId, string cryptoCode)
{
if (!_lightningAutomatedPayoutSenderFactory.GetSupportedPaymentMethods().Any(id =>

View file

@ -34,10 +34,9 @@ public class UIOnChainAutomatedPayoutProcessorsController : Controller
_payoutProcessorService = payoutProcessorService;
}
[HttpGet("~/stores/{storeId}/payout-processors/onchain-automated/{cryptocode}")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> Configure(string storeId, string cryptoCode)
{
if (!_onChainAutomatedPayoutSenderFactory.GetSupportedPaymentMethods().Any(id =>

View file

@ -34,7 +34,7 @@ public class UIPayoutProcessorsController : Controller
[HttpGet("~/stores/{storeId}/payout-processors")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> ConfigureStorePayoutProcessors(string storeId)
{
var activeProcessors =

View file

@ -379,7 +379,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
return View("Views/UIForms/View", viewModel);
}
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[HttpGet("{appId}/settings/crowdfund")]
public async Task<IActionResult> UpdateCrowdfund(string appId)
{

View file

@ -558,7 +558,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
return Json(recent);
}
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[HttpGet("{appId}/settings/pos")]
public async Task<IActionResult> UpdatePointOfSale(string appId)
{

View file

@ -1,5 +1,5 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Contracts;
@ -42,7 +42,10 @@ namespace BTCPayServer.Security
_pluginHookService = pluginHookService;
_paymentRequestRepository = paymentRequestRepository;
}
//TODO: In the future, we will add these store permissions to actual aspnet roles, and remove this class.
private static readonly PermissionSet ServerAdminRolePermissions =
new PermissionSet(new[] {Permission.Create(Policies.CanViewStoreSettings, null)});
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PolicyRequirement requirement)
{
if (context.User.Identity.AuthenticationType != AuthenticationSchemes.Cookie)
@ -67,7 +70,10 @@ namespace BTCPayServer.Security
storeId = s;
}
else
{
storeId = _httpContext.GetImplicitStoreId();
store = _httpContext.GetStoreData();
}
var routeData = _httpContext.GetRouteData();
if (routeData != null)
{
@ -124,12 +130,9 @@ namespace BTCPayServer.Security
storeId = null;
}
if (!string.IsNullOrEmpty(storeId))
if (!string.IsNullOrEmpty(storeId) && store is null)
{
var cachedStore = _httpContext.GetStoreData();
store = cachedStore?.Id == storeId
? cachedStore
: await _storeRepository.FindStore(storeId, userId);
store = await _storeRepository.FindStore(storeId, userId);
}
if (Policies.IsServerPolicy(policy) && isAdmin)
@ -142,14 +145,18 @@ namespace BTCPayServer.Security
}
else if (Policies.IsStorePolicy(policy))
{
if (store is not null)
if (isAdmin && storeId is not null)
{
if (store.HasPermission(userId,policy))
{
success = true;
}
success = ServerAdminRolePermissions.HasPermission(policy, storeId);
}
else if (requiredUnscoped)
if (!success && store?.HasPermission(userId, policy) is true)
{
success = true;
}
if (!success && store is null && requiredUnscoped)
{
success = true;
}
@ -166,6 +173,11 @@ namespace BTCPayServer.Security
context.Succeed(requirement);
if (!explicitResource)
{
if (storeId is not null && store is null)
{
store = await _storeRepository.FindStore(storeId);
}
if (store != null)
{
if (_httpContext.GetStoreData()?.Id != store.Id)

View file

@ -1,8 +1,6 @@
@using BTCPayServer.Client
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using BTCPayServer.Views.Apps
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Abstractions.TagHelpers
@using BTCPayServer.Plugins.Crowdfund
@using BTCPayServer.Services.Apps
@inject AppService AppService;
@ -14,20 +12,30 @@
@if (store != null)
{
var appType = AppService.GetAppType(CrowdfundAppType.AppType)!;
var apps = Model.Apps.Where(app => app.AppType == appType.Type).ToList();
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<a asp-area="" asp-controller="UIApps" asp-action="CreateApp" asp-route-storeId="@store.Id" asp-route-appType="@appType.Type" class="nav-link @ViewData.IsActivePage(AppsNavPages.Create, appType.Type)" id="@($"StoreNav-Create{appType.Type}")">
<vc:icon symbol="crowdfund" />
<span>@appType.Description</span>
</a>
</li>
@foreach (var app in Model.Apps.Where(app => app.AppType == appType.Type))
@if (apps.Any())
{
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyStoreSettings">
<li class="nav-item" not-permission="@Policies.CanModifyStoreSettings" permission="@Policies.CanViewStoreSettings">
<span class="nav-link">
<vc:icon symbol="crowdfund" />
<span>@appType.Description</span>
</span>
</li>
}
@foreach (var app in apps)
{
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
<a asp-area="" asp-controller="UICrowdfund" asp-action="UpdateCrowdfund" asp-route-appId="@app.Id" class="nav-link @ViewData.IsActivePage(AppsNavPages.Update, app.Id)" id="@($"StoreNav-App-{app.Id}")">
<span>@app.AppName</span>
</a>
</li>
<li class="nav-item nav-item-sub" not-permission="@Policies.CanModifyStoreSettings">
<li class="nav-item nav-item-sub" not-permission="@Policies.CanViewStoreSettings">
<a asp-area="" asp-controller="UICrowdfund" asp-action="ViewCrowdfund" asp-route-appId="@app.Id" class="nav-link">
<span>@app.AppName</span>
</a>

View file

@ -1,6 +1,7 @@
@using System.Globalization
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Abstractions.Models
@using BTCPayServer.Client
@using BTCPayServer.TagHelpers
@using BTCPayServer.Views.Apps
@using Microsoft.AspNetCore.Mvc.TagHelpers
@ -29,10 +30,10 @@
<div class="sticky-header d-sm-flex align-items-center justify-content-between">
<h2 class="mb-0">@ViewData["Title"]</h2>
<div class="d-flex gap-3 mt-3 mt-sm-0">
<button type="submit" class="btn btn-primary order-sm-1" id="SaveSettings">Save</button>
<button type="submit" class="btn btn-primary order-sm-1" id="SaveSettings" permission="@Policies.CanModifyStoreSettings">Save</button>
@if (Model.Archived)
{
<button type="submit" class="btn btn-outline-secondary" name="Archived" value="False">Unarchive</button>
<button type="submit" class="btn btn-outline-secondary" name="Archived" value="False" permission="@Policies.CanModifyStoreSettings">Unarchive</button>
}
else if (Model.ModelWithMinimumData)
{
@ -348,7 +349,7 @@
<div class="d-grid d-sm-flex flex-wrap gap-3 mt-3">
<a class="btn btn-secondary" asp-action="ListInvoices" asp-controller="UIInvoice" asp-route-storeId="@Model.StoreId" asp-route-searchterm="@Model.SearchTerm">Invoices</a>
<form method="post" asp-controller="UIApps" asp-action="ToggleArchive" asp-route-appId="@Model.AppId">
<form method="post" asp-controller="UIApps" asp-action="ToggleArchive" asp-route-appId="@Model.AppId" permission="@Policies.CanModifyStoreSettings">
<button type="submit" class="w-100 btn btn-outline-secondary" id="btn-archive-toggle">
@if (Model.Archived)
{
@ -360,8 +361,8 @@
}
</button>
</form>
<a id="DeleteApp" class="btn btn-outline-danger" asp-controller="UIApps" asp-action="DeleteApp" asp-route-appId="@Model.AppId" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The app <strong>@Html.Encode(Model.AppName)</strong> and its settings will be permanently deleted." data-confirm-input="DELETE">Delete this app</a>
<a id="DeleteApp" class="btn btn-outline-danger" asp-controller="UIApps" asp-action="DeleteApp" asp-route-appId="@Model.AppId" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The app <strong>@Html.Encode(Model.AppName)</strong> and its settings will be permanently deleted." data-confirm-input="DELETE" permission="@Policies.CanModifyStoreSettings">Delete this app</a>
</div>
<partial name="_Confirm" model="@(new ConfirmModel("Delete app", "This app will be removed from this store.", "Delete"))" />
<partial name="_Confirm" model="@(new ConfirmModel("Delete app", "This app will be removed from this store.", "Delete"))" permission="@Policies.CanModifyStoreSettings" />

View file

@ -1,3 +1,6 @@
@using BTCPayServer.Client
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using BTCPayServer.Abstractions.TagHelpers
@model BTCPayServer.Models.EmailsViewModel
<div class="row">
@ -41,7 +44,7 @@
<div class="form-text">For many email providers (like Gmail) your login is your email address.</div>
<span asp-validation-for="Settings.Login" class="text-danger"></span>
</div>
<div class="form-group">
<div class="form-group" permission="@Policies.CanModifyStoreSettings">
@if (!Model.PasswordSet)
{
<label asp-for="Settings.Password" class="form-label"></label>
@ -74,21 +77,7 @@
</div>
</div>
</div>
<button type="submit" class="btn btn-primary mt-2" name="command" value="Save" id="Save">Save</button>
</div>
</div>
<div class="row">
<div class="col-xl-10 col-xxl-constrain">
<div class="form-group">
<h3 class="mt-5 mb-3">Testing</h3>
<p>
To test your settings, enter an email address below.
</p>
<label asp-for="TestEmail" class="form-label"></label>
<input asp-for="TestEmail" placeholder="Firstname Lastname <email@example.com>" class="form-control" />
<span asp-validation-for="TestEmail" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-secondary mt-2" name="command" value="Test" id="Test">Send Test Email</button>
<button type="submit" class="btn btn-primary mt-2" name="command" value="Save" id="Save" permission="@Policies.CanModifyStoreSettings">Save</button>
</div>
</div>

View file

@ -0,0 +1,13 @@
@model BTCPayServer.Models.EmailsViewModel
<h3 class="mt-5 mb-3">Testing</h3>
<div class="row">
<div class="col-xl-10 col-xxl-constrain">
<div class="form-group">
<label asp-for="TestEmail" class="form-label">To test your settings, enter an email address</label>
<input asp-for="TestEmail" placeholder="Firstname Lastname <email@example.com>" class="form-control" />
<span asp-validation-for="TestEmail" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-secondary mt-2" name="command" value="Test" id="Test">Send Test Email</button>
</div>
</div>

View file

@ -1,32 +1,24 @@
@using BTCPayServer.Components
@using BTCPayServer.Views.Server
@using BTCPayServer.Views.Stores
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Client
@model BTCPayServer.Models.ServerViewModels.RolesViewModel
@{
Layout = "_NavLayout.cshtml";
var storeId = Context.GetRouteValue("storeId") as string;
var controller = ViewContext.RouteData.Values["controller"].ToString().TrimEnd("Controller", StringComparison.InvariantCultureIgnoreCase);
if (storeId is null)
if (string.IsNullOrEmpty(storeId))
ViewData.SetActivePage(ServerNavPages.Roles);
else
{
ViewData.SetActivePage(StoreNavPages.Roles);
}
var permission = string.IsNullOrEmpty(storeId) ? Policies.CanModifyServerSettings : Policies.CanModifyStoreSettings;
var nextRoleSortOrder = (string) ViewData["NextRoleSortOrder"];
String roleSortOrder = null;
switch (nextRoleSortOrder)
var roleSortOrder = nextRoleSortOrder switch
{
case "asc":
roleSortOrder = "desc";
break;
case "desc":
roleSortOrder = "asc";
break;
}
"asc" => "desc",
"desc" => "asc",
_ => null
};
var sortIconClass = "fa-sort";
if (roleSortOrder != null)
@ -36,16 +28,12 @@
var sortByDesc = "Sort by descending...";
var sortByAsc = "Sort by ascending...";
var showInUseColumn = !Model.Roles.Any(r => r.IsUsed is null);
}
<div class="d-flex align-items-center justify-content-between mb-3">
<h3 class="mb-0">@ViewData["Title"]</h3>
<a asp-action="CreateOrEditRole" asp-route-storeId="@storeId" class="btn btn-primary" role="button" id="CreateRole" asp-route-role="create"
asp-controller="@controller">
Add Role
</a>
<a class="btn btn-primary" role="button" id="CreateRole" asp-controller="@controller" asp-action="CreateOrEditRole" asp-route-role="create" asp-route-storeId="@storeId" permission="@permission">Add Role</a>
</div>
<div class="table-responsive">
@ -61,15 +49,16 @@
class="text-nowrap"
title="@(nextRoleSortOrder == "desc" ? sortByAsc : sortByDesc)">
Role
<span class="fa @(sortIconClass)" />
<i class="fa @(sortIconClass)"></i>
</a>
</th>
<th >Permissions</th>
<th>Scope</th>
<th>Permissions</th>
@if (showInUseColumn)
{
<th>In use</th>
<th class="text-center">In use</th>
}
<th class="text-end">Actions</th>
<th class="actions-col" permission="@permission">Actions</th>
</tr>
</thead>
<tbody>
@ -78,21 +67,29 @@
<tr>
<td>
<div class="d-flex flex-wrap align-items-center gap-2">
<span class="me-1">@role.Role</span>
@if (role.IsServerRole)
<span>@role.Role</span>
@if (Model.DefaultRole == role.Id)
{
<span class="badge bg-dark">
Server-wide
<span class="badge bg-info">
Default
</span>
@if (Model.DefaultRole == role.Id)
{
<span class="badge bg-info">
Default
</span>
}
}
</div>
</td>
<td>
@if (role.IsServerRole)
{
<span class="badge bg-dark">
Server-wide
</span>
}
else
{
<span class="badge bg-light">
Store-level
</span>
}
</td>
<td>
@if (!role.Permissions.Any())
{
@ -122,28 +119,18 @@
}
</td>
}
<td class="text-end">
<a permission="@(role.IsServerRole ? Policies.CanModifyServerSettings : Policies.CanModifyStoreSettings)" asp-action="CreateOrEditRole" asp-route-storeId="@storeId" asp-route-role="@role.Role"
asp-controller="@(role.IsServerRole ? "UIServer" : "UIStores")">
Edit
</a> -
<a permission="@(role.IsServerRole ? Policies.CanModifyServerSettings : Policies.CanModifyStoreSettings)" asp-action="DeleteRole" asp-route-storeId="@storeId" asp-route-role="@role.Role"
asp-controller="@(role.IsServerRole ? "UIServer" : "UIStores")">
Remove
</a>
@if (role.IsServerRole && Model.DefaultRole != role.Id)
{
<a permission="@Policies.CanModifyServerSettings" asp-action="SetDefaultRole" asp-route-role="@role.Role"
asp-controller="UIServer" id="SetDefault">
- Set as default
</a>
}
<td class="actions-col" permission="@permission">
<div class="d-inline-flex align-items-center gap-3">
@if (role.IsServerRole && Model.DefaultRole != role.Id)
{
<a permission="@Policies.CanModifyServerSettings" asp-action="SetDefaultRole" asp-route-role="@role.Role" asp-controller="UIServer" id="SetDefault">Set as default</a>
}
<a permission="@(role.IsServerRole ? Policies.CanModifyServerSettings : Policies.CanModifyStoreSettings)" asp-action="CreateOrEditRole" asp-route-storeId="@storeId" asp-route-role="@role.Role" asp-controller="@(role.IsServerRole ? "UIServer" : "UIStores")">Edit</a>
<a permission="@(role.IsServerRole ? Policies.CanModifyServerSettings : Policies.CanModifyStoreSettings)" asp-action="DeleteRole" asp-route-storeId="@storeId" asp-route-role="@role.Role" asp-controller="@(role.IsServerRole ? "UIServer" : "UIStores")">Remove</a>
</div>
</td>
</tr>
}
</tbody>
</table>
</div>
<vc:pager view-model="Model"></vc:pager>

View file

@ -1,8 +1,6 @@
@using BTCPayServer.Client
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using BTCPayServer.Views.Apps
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Abstractions.TagHelpers
@using BTCPayServer.Plugins.PointOfSale
@using BTCPayServer.Services.Apps
@inject AppService AppService;
@ -14,20 +12,30 @@
@if (store != null)
{
var appType = AppService.GetAppType(PointOfSaleAppType.AppType)!;
var apps = Model.Apps.Where(app => app.AppType == appType.Type).ToList();
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<a asp-area="" asp-controller="UIApps" asp-action="CreateApp" asp-route-storeId="@store.Id" asp-route-appType="@appType.Type" class="nav-link @ViewData.IsActivePage(AppsNavPages.Create, appType.Type)" id="@($"StoreNav-Create{appType.Type}")">
<vc:icon symbol="pointofsale" />
<span>@appType.Description</span>
</a>
</li>
@foreach (var app in Model.Apps.Where(app => app.AppType == appType.Type))
@if (apps.Any())
{
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyStoreSettings">
<li class="nav-item" not-permission="@Policies.CanModifyStoreSettings" permission="@Policies.CanViewStoreSettings">
<span class="nav-link">
<vc:icon symbol="pointofsale" />
<span>@appType.Description</span>
</span>
</li>
}
@foreach (var app in apps)
{
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
<a asp-area="" asp-controller="UIPointOfSale" asp-action="UpdatePointOfSale" asp-route-appId="@app.Id" class="nav-link @ViewData.IsActivePage(AppsNavPages.Update, app.Id)" id="@($"StoreNav-App-{app.Id}")">
<span>@app.AppName</span>
</a>
</li>
<li class="nav-item nav-item-sub" not-permission="@Policies.CanModifyStoreSettings">
<li class="nav-item nav-item-sub" not-permission="@Policies.CanViewStoreSettings">
<a asp-area="" asp-controller="UIPointOfSale" asp-action="ViewPointOfSale" asp-route-appId="@app.Id" class="nav-link">
<span>@app.AppName</span>
</a>

View file

@ -1,8 +1,10 @@
@using BTCPayServer.Abstractions.Models
@using BTCPayServer.Views.Apps
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Client
@using BTCPayServer.Plugins.PointOfSale
@using BTCPayServer.Forms
@using BTCPayServer.TagHelpers
@using Microsoft.AspNetCore.Mvc.TagHelpers
@inject FormDataService FormDataService
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
@model BTCPayServer.Plugins.PointOfSale.Models.UpdatePointOfSaleViewModel
@ -35,10 +37,10 @@
<div class="sticky-header d-sm-flex align-items-center justify-content-between">
<h2 class="mb-0">@ViewData["Title"]</h2>
<div class="d-flex gap-3 mt-3 mt-sm-0">
<button type="submit" class="btn btn-primary order-sm-1" id="SaveSettings">Save</button>
<button type="submit" class="btn btn-primary order-sm-1" id="SaveSettings" permission="@Policies.CanModifyStoreSettings">Save</button>
@if (Model.Archived)
{
<button type="submit" class="btn btn-outline-secondary" name="Archived" value="False">Unarchive</button>
<button type="submit" class="btn btn-outline-secondary" name="Archived" value="False" permission="@Policies.CanModifyStoreSettings">Unarchive</button>
}
else
{
@ -318,7 +320,7 @@
<div class="d-grid d-sm-flex flex-wrap gap-3 mt-3">
<a class="btn btn-secondary" asp-action="ListInvoices" asp-controller="UIInvoice" asp-route-storeId="@Model.StoreId" asp-route-searchterm="@Model.SearchTerm">Invoices</a>
<form method="post" asp-controller="UIApps" asp-action="ToggleArchive" asp-route-appId="@Model.Id">
<button type="submit" class="w-100 btn btn-outline-secondary" id="btn-archive-toggle">
<button type="submit" class="w-100 btn btn-outline-secondary" id="btn-archive-toggle" permission="@Policies.CanModifyStoreSettings">
@if (Model.Archived)
{
<span class="text-nowrap">Unarchive this app</span>
@ -329,11 +331,10 @@
}
</button>
</form>
<a id="DeleteApp" class="btn btn-outline-danger" asp-controller="UIApps" asp-action="DeleteApp" asp-route-appId="@Model.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The app <strong>@Html.Encode(Model.AppName)</strong> and its settings will be permanently deleted." data-confirm-input="DELETE">Delete this app</a>
<a id="DeleteApp" class="btn btn-outline-danger" asp-controller="UIApps" asp-action="DeleteApp" asp-route-appId="@Model.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The app <strong>@Html.Encode(Model.AppName)</strong> and its settings will be permanently deleted." data-confirm-input="DELETE" permission="@Policies.CanModifyStoreSettings">Delete this app</a>
</div>
<partial name="_Confirm" model="@(new ConfirmModel("Delete app", "This app will be removed from this store.", "Delete"))" />
<partial name="_Confirm" model="@(new ConfirmModel("Delete app", "This app will be removed from this store.", "Delete"))" permission="@Policies.CanModifyStoreSettings" />
<partial name="ShowQR" />

View file

@ -1,5 +1,6 @@
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using BTCPayServer.Abstractions.Models
@using BTCPayServer.Client
@model List<BTCPayServer.Data.FormData>
@{
Layout = "../Shared/_NavLayout.cshtml";
@ -17,7 +18,7 @@
<vc:icon symbol="info" />
</a>
</h3>
<a asp-action="Create" asp-route-storeId="@storeId" class="btn btn-primary mt-3 mt-sm-0" role="button" id="CreateForm">
<a asp-action="Create" asp-route-storeId="@storeId" class="btn btn-primary mt-3 mt-sm-0" role="button" id="CreateForm" permission="@Policies.CanModifyStoreSettings">
Create Form
</a>
</div>
@ -27,7 +28,7 @@
<thead>
<tr>
<th>Name</th>
<th class="text-end">Actions</th>
<th class="actions-col" permission="@Policies.CanModifyStoreSettings">Actions</th>
</tr>
</thead>
<tbody>
@ -35,10 +36,10 @@
{
<tr>
<td>
<a asp-action="Modify" asp-route-storeId="@item.StoreId" asp-route-id="@item.Id" id="Edit-@item.Name">@item.Name</a>
<a asp-action="Modify" asp-route-storeId="@item.StoreId" asp-route-id="@item.Id" id="Edit-@item.Name" permission="@Policies.CanModifyStoreSettings">@item.Name</a>
<a asp-action="ViewPublicForm" asp-route-formId="@item.Id" id="View-@item.Name" not-permission="@Policies.CanModifyStoreSettings">View</a>
</td>
<td class="text-end">
<td class="actions-col" permission="@Policies.CanModifyStoreSettings">
<a asp-action="Remove" asp-route-storeId="@item.StoreId" asp-route-id="@item.Id" id="Remove-@item.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-confirm-input="DELETE">Remove</a> -
<a asp-action="ViewPublicForm" asp-route-formId="@item.Id" id="View-@item.Name">View</a>
</td>
@ -56,4 +57,4 @@
</div>
</div>
<partial name="_Confirm" model="@(new ConfirmModel("Delete form", "This form will be removed from this store.", "Delete"))" />
<partial name="_Confirm" model="@(new ConfirmModel("Delete form", "This form will be removed from this store.", "Delete"))" permission="@Policies.CanModifyStoreSettings" />

View file

@ -107,7 +107,12 @@
<vc:icon symbol="info" />
</a>
</h2>
<a id="CreateNewInvoice" asp-action="CreateInvoice" asp-route-storeId="@Model.StoreId" asp-route-searchTerm="@Model.SearchTerm" class="btn btn-primary mt-3 mt-sm-0">
<a id="CreateNewInvoice"
permission="@Policies.CanCreateInvoice"
asp-action="CreateInvoice"
asp-route-storeId="@Model.StoreId"
asp-route-searchTerm="@Model.SearchTerm"
class="btn btn-primary mt-3 mt-sm-0">
Create Invoice
</a>
</div>

View file

@ -1,4 +1,4 @@
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Client
@using BTCPayServer.Views.Stores
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model BTCPayServer.PayoutProcessors.Lightning.UILightningAutomatedPayoutProcessorsController.LightningTransferViewModel
@ -41,7 +41,7 @@
<div class="form-text">If a payout fails this many times, it will be cancelled.</div>
</div>
<button name="command" type="submit" class="btn btn-primary mt-2" value="Save" id="Save">Save</button>
<button name="command" type="submit" class="btn btn-primary mt-2" value="Save" id="Save" permission="@Policies.CanModifyStoreSettings">Save</button>
</form>
</div>
</div>

View file

@ -1,5 +1,6 @@
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Client
@using BTCPayServer.Views.Stores
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model BTCPayServer.PayoutProcessors.OnChain.UIOnChainAutomatedPayoutProcessorsController.OnChainTransferViewModel
@{
ViewData["NavPartialName"] = "../UIStores/_Nav";
@ -48,7 +49,7 @@
</div>
<div class="form-text">Only process payouts when this payout sum is reached.</div>
</div>
<button name="command" type="submit" class="btn btn-primary mt-2" value="Save" id="Save">Save</button>
<button name="command" type="submit" class="btn btn-primary mt-2" value="Save" id="Save" permission="@Policies.CanModifyStoreSettings">Save</button>
</form>
</div>
</div>

View file

@ -2,6 +2,7 @@
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Abstractions.Models
@using BTCPayServer.Client
@model List<BTCPayServer.PayoutProcessors.UIPayoutProcessorsController.StorePayoutProcessorsView>
@{
ViewData["NavPartialName"] = "../UIStores/_Nav";
@ -25,7 +26,7 @@
<thead>
<tr>
<th>Payment Method</th>
<th class="text-end">Actions</th>
<th class="actions-col" permission="@Policies.CanModifyStoreSettings">Actions</th>
</tr>
</thead>
<tbody>
@ -35,7 +36,7 @@
<td>
@conf.Key.ToPrettyString()
</td>
<td class="text-end">
<td class="actions-col" permission="@Policies.CanModifyStoreSettings">
@if (conf.Value is null)
{
<a href="@processorsView.Factory.ConfigureLink(storeId, conf.Key, Context.Request)">Configure</a>
@ -66,7 +67,7 @@
</div>
</div>
<partial name="_Confirm" model="@(new ConfirmModel("Delete payout processor", "This payout processor will be removed from this store.", "Delete"))" />
<partial name="_Confirm" model="@(new ConfirmModel("Delete payout processor", "This payout processor will be removed from this store.", "Delete"))" permission="@Policies.CanModifyStoreSettings" />
@section PageFootContent {
<partial name="_ValidationScriptsPartial"/>
}

View file

@ -20,6 +20,7 @@
</div>
</div>
<partial name="EmailsBody" model="Model" />
<partial name="EmailsTest" model="Model" />
</form>
@section PageFootContent {

View file

@ -2,6 +2,7 @@
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using BTCPayServer.Abstractions.TagHelpers
@using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Client
@inject IFileService FileService
@model CheckoutAppearanceViewModel
@{
@ -147,7 +148,7 @@
<label asp-for="SoundFile" class="form-label"></label>
@if (!string.IsNullOrEmpty(Model.SoundFileId))
{
<button type="submit" class="btn btn-link p-0 text-danger" name="RemoveSoundFile" value="true">
<button type="submit" class="btn btn-link p-0 text-danger" name="RemoveSoundFile" value="true" permission="@Policies.CanModifyStoreSettings">
<span class="fa fa-times"></span> Remove
</button>
}
@ -270,7 +271,7 @@
<input asp-for="ReceiptOptions.ShowQR" type="checkbox" class="form-check-input" />
<label asp-for="ReceiptOptions.ShowQR" class="form-check-label"></label>
</div>
<button type="submit" class="btn btn-primary mt-4" id="Save">Save</button>
<button type="submit" class="btn btn-primary mt-4" id="Save" permission="@Policies.CanModifyStoreSettings">Save</button>
</form>
</div>
</div>

View file

@ -6,7 +6,6 @@
@using BTCPayServer.Components.StoreWalletBalance
@using BTCPayServer.Components.AppSales
@using BTCPayServer.Components.AppTopItems
@using BTCPayServer.Services.Apps
@using BTCPayServer.Client
@model StoreDashboardViewModel
@{

View file

@ -159,31 +159,33 @@
<span asp-validation-for="BOLT11Expiration" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary mt-2" id="Save">Save</button>
<button type="submit" class="btn btn-primary mt-2" id="Save" permission="@Policies.CanModifyStoreSettings">Save</button>
</form>
<h3 class="mt-5 mb-3">Additional Actions</h3>
<div id="danger-zone" class="d-flex flex-wrap align-items-center gap-3 mb-5 mt-2">
<form asp-action="ToggleArchive" asp-route-storeId="@Model.Id" method="post" permission="@Policies.CanModifyStoreSettings">
<button type="submit" class="btn btn-outline-secondary" id="btn-archive-toggle">
@if (Model.Archived)
{
<span class="text-nowrap" data-bs-toggle="tooltip" title="Unarchive this store">Unarchive this store</span>
}
else
{
<span class="text-nowrap" data-bs-toggle="tooltip" title="Archive this store so that it does not appear in the stores list by default">Archive this store</span>
}
</button>
</form>
@if (Model.CanDelete)
{
<a id="DeleteStore" class="btn btn-outline-danger" asp-action="DeleteStore" asp-route-storeId="@Model.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The store <strong>@Html.Encode(Model.StoreName)</strong> will be permanently deleted. This action will also delete all invoices, apps and data associated with the store." data-confirm-input="DELETE">Delete this store</a>
}
<div permission="@Policies.CanModifyStoreSettings">
<h3 class="mt-5 mb-3">Additional Actions</h3>
<div id="danger-zone" class="d-flex flex-wrap align-items-center gap-3 mb-5 mt-2">
<form asp-action="ToggleArchive" asp-route-storeId="@Model.Id" method="post">
<button type="submit" class="btn btn-outline-secondary" id="btn-archive-toggle">
@if (Model.Archived)
{
<span class="text-nowrap" data-bs-toggle="tooltip" title="Unarchive this store">Unarchive this store</span>
}
else
{
<span class="text-nowrap" data-bs-toggle="tooltip" title="Archive this store so that it does not appear in the stores list by default">Archive this store</span>
}
</button>
</form>
@if (Model.CanDelete)
{
<a id="DeleteStore" class="btn btn-outline-danger" asp-action="DeleteStore" asp-route-storeId="@Model.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The store <strong>@Html.Encode(Model.StoreName)</strong> will be permanently deleted. This action will also delete all invoices, apps and data associated with the store." data-confirm-input="DELETE">Delete this store</a>
}
</div>
</div>
</div>
</div>
<partial name="_Confirm" model="@(new ConfirmModel("Delete store", "The store will be permanently deleted. This action will also delete all invoices, apps and data associated with the store.", "Delete"))" />
<partial name="_Confirm" model="@(new ConfirmModel("Delete store", "The store will be permanently deleted. This action will also delete all invoices, apps and data associated with the store.", "Delete"))" permission="@Policies.CanModifyStoreSettings" />
@section PageFootContent {
<partial name="_ValidationScriptsPartial"/>

View file

@ -1,4 +1,6 @@
@using BTCPayServer.Abstractions.Models
@using BTCPayServer.Client
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model TokensViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
@ -27,7 +29,7 @@
<div class="d-flex align-items-center justify-content-between mt-5 mb-3">
<h3 class="mb-0">@ViewData["Title"]</h3>
<a id="CreateNewToken" asp-action="CreateToken" class="btn btn-primary" role="button" asp-route-storeId="@Context.GetRouteValue("storeId")">
<a id="CreateNewToken" asp-action="CreateToken" class="btn btn-primary" role="button" asp-route-storeId="@Context.GetRouteValue("storeId")" permission="@Policies.CanModifyStoreSettings">
Create Token
</a>
</div>
@ -43,7 +45,7 @@
<thead>
<tr>
<th>Label</th>
<th class="text-end">Actions</th>
<th class="text-end" permission="@Policies.CanModifyStoreSettings">Actions</th>
</tr>
</thead>
<tbody>
@ -51,7 +53,7 @@
{
<tr>
<td>@token.Label</td>
<td class="text-end">
<td class="text-end" permission="@Policies.CanModifyStoreSettings">
<a asp-action="ShowToken" asp-route-storeId="@Context.GetRouteValue("storeId")" asp-route-tokenId="@token.Id">See information</a> -
<a asp-action="RevokeToken" asp-route-storeId="@Context.GetRouteValue("storeId")" asp-route-tokenId="@token.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The access token with the label <strong>@Html.Encode(token.Label)</strong> will be revoked." data-confirm-input="REVOKE">Revoke</a>
</td>
@ -71,7 +73,7 @@
<p>Alternatively, you can use the invoice API by including the following HTTP Header in your requests:</p>
<p><code>Authorization: Basic @Model.EncodedApiKey</code></p>
<form method="post" asp-action="GenerateAPIKey" asp-route-storeId="@Context.GetRouteValue("storeId")">
<form method="post" asp-action="GenerateAPIKey" asp-route-storeId="@Context.GetRouteValue("storeId")" permission="@Policies.CanModifyStoreSettings">
<div class="form-group">
<label asp-for="ApiKey" class="form-label"></label>
<div class="d-flex">
@ -91,4 +93,4 @@
</div>
</div>
<partial name="_Confirm" model="@(new ConfirmModel("Revoke access token", "The access token will be revoked. Do you wish to continue?", "Revoke"))" />
<partial name="_Confirm" model="@(new ConfirmModel("Revoke access token", "The access token will be revoked. Do you wish to continue?", "Revoke"))" permission="@Policies.CanModifyStoreSettings" />

View file

@ -1,4 +1,7 @@
@using BTCPayServer.Abstractions.Models
@using BTCPayServer.Abstractions.TagHelpers
@using BTCPayServer.Client
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model BTCPayServer.Models.StoreViewModels.RatesViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
@ -141,7 +144,7 @@ X_X = kraken(X_X);</code></pre>
}
<div class="form-group">
<label class="d-flex align-items-center">
<button type="submit" id="ShowScripting" class="btcpay-toggle me-3 @if (Model.ShowScripting) { @("btcpay-toggle--active") }" value="scripting-@(Model.ShowScripting ? "off" : "on")" name="command" data-bs-toggle="modal" data-bs-target="#ConfirmModal">@(Model.ShowScripting ? "Disable" : "Enable") advanced rate rule scripting</button>
<button type="submit" id="ShowScripting" class="btcpay-toggle me-3 @if (Model.ShowScripting) { @("btcpay-toggle--active") }" value="scripting-@(Model.ShowScripting ? "off" : "on")" name="command" data-bs-toggle="modal" data-bs-target="#ConfirmModal" permission="@Policies.CanModifyStoreSettings">@(Model.ShowScripting ? "Disable" : "Enable") advanced rate rule scripting</button>
<div class="">
<span>Advanced rate rule scripting</span>
<div class="form-text">
@ -164,7 +167,7 @@ X_X = kraken(X_X);</code></pre>
<label asp-for="ScriptTest" class="form-label">Currency pairs to test against your rule (e.g. <code>DOGE_USD,DOGE_CAD,BTC_CAD,BTC_USD</code>)</label>
<div class="d-flex">
<input asp-for="ScriptTest" class="form-control" placeholder="BTC_USD, BTC_CAD" />
<button name="command" value="Test" type="submit" class="btn btn-secondary ms-3" title="Test">Test</button>
<button name="command" value="Test" type="submit" class="btn btn-secondary ms-3" title="Test" permission="@Policies.CanModifyStoreSettings">Test</button>
</div>
<span asp-validation-for="ScriptTest" class="text-danger"></span>
</div>
@ -175,13 +178,12 @@ X_X = kraken(X_X);</code></pre>
<input asp-for="DefaultCurrencyPairs" class="form-control" placeholder="BTC_USD, BTC_CAD" />
<span asp-validation-for="DefaultCurrencyPairs" class="text-danger"></span>
</div>
<button name="command" type="submit" class="btn btn-primary mt-2" value="Save">Save</button>
<button name="command" type="submit" class="btn btn-primary mt-2" value="Save" permission="@Policies.CanModifyStoreSettings">Save</button>
</form>
</div>
</div>
<partial name="_Confirm" model="@(new ConfirmModel("Rate rule scripting", Model.ShowScripting ? "This action will delete your rate script. Are you sure to turn off rate rules scripting?" : "This action will modify your current rate sources. Are you sure to turn on rate rules scripting? (Advanced users)", "Continue", Model.ShowScripting ? "btn-danger" : "btn-primary"))" />
<partial name="_Confirm" model="@(new ConfirmModel("Rate rule scripting", Model.ShowScripting ? "This action will delete your rate script. Are you sure to turn off rate rules scripting?" : "This action will modify your current rate sources. Are you sure to turn on rate rules scripting? (Advanced users)", "Continue", Model.ShowScripting ? "btn-danger" : "btn-primary"))" permission="@Policies.CanModifyStoreSettings" />
@section PageHeadContent {
<link rel="stylesheet" href="~/vendor/highlightjs/default.min.css" asp-append-version="true">

View file

@ -1,3 +1,5 @@
@using BTCPayServer.Client
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model BTCPayServer.Models.EmailsViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
@ -9,7 +11,7 @@
<div class="col-xl-10 col-xxl-constrain">
<div class="d-flex flex-wrap gap-3 align-items-center justify-content-between mt-n1 mb-4">
<h3 class="mb-0">Email Rules</h3>
<a class="btn btn-secondary" asp-action="StoreEmails" asp-controller="UIStores" asp-route-storeId="@Context.GetStoreData().Id" id="ConfigureEmailRules">
<a class="btn btn-secondary" asp-action="StoreEmails" asp-controller="UIStores" asp-route-storeId="@Context.GetStoreData().Id" id="ConfigureEmailRules" permission="@Policies.CanModifyStoreSettings">
Configure
</a>
</div>
@ -41,6 +43,8 @@
{
<partial name="EmailsBody" model="Model" />
}
<partial name="EmailsTest" model="Model" permission="@Policies.CanModifyStoreSettings" />
</form>
@section PageFootContent {

View file

@ -1,4 +1,7 @@
@using BTCPayServer.HostedServices.Webhooks
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using BTCPayServer.Client
@using BTCPayServer.Abstractions.TagHelpers
@model BTCPayServer.Controllers.UIStoresController.StoreEmailRuleViewModel
@inject WebhookSender WebhookSender
@ -15,7 +18,7 @@
<div class="col-xxl-constrain">
<div class="d-flex align-items-center justify-content-between mt-n1 mb-3">
<h3 class="mb-0">@ViewData["Title"]</h3>
<div class="d-flex gap-3">
<div class="d-flex gap-3" permission="@Policies.CanModifyStoreSettings">
@if (Model.Rules.Any())
{
<button class="btn btn-primary" name="command" type="submit" value="save" id="SaveEmailRules">

View file

@ -1,6 +1,8 @@
@using BTCPayServer.Abstractions.Models
@using BTCPayServer.Services.Stores
@using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Client
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model StoreUsersViewModel
@inject IScopeProvider ScopeProvider
@inject StoreRepository StoreRepository
@ -23,48 +25,37 @@
<div asp-validation-summary="All"></div>
}
<form method="post">
<div class="d-flex">
<div class="flex-grow-1">
<input asp-for="Email" type="text" class="form-control" placeholder="user@example.com">
</div>
<div class="ms-3">
<select asp-for="Role" class="form-select" asp-items="roles">
</select>
</div>
<div class="ms-3">
<button type="submit" role="button" class="btn btn-primary" id="AddUser">Add User</button>
</div>
</div>
<form method="post" class="d-flex flex-wrap align-items-center gap-3" permission="@Policies.CanModifyStoreSettings">
<input asp-for="Email" type="text" class="form-control" placeholder="user@example.com" style="flex: 1 1 14rem">
<select asp-for="Role" class="form-select w-auto" asp-items="roles"></select>
<button type="submit" role="button" class="btn btn-primary text-nowrap flex-grow-1 flex-sm-grow-0" id="AddUser">Add User</button>
</form>
<div class="form-group">
<table class="table table-hover table-responsive-md">
<thead>
<table class="table table-hover table-responsive-md">
<thead>
<tr>
<th>Email</th>
<th>Role</th>
<th class="actions-col" permission="@Policies.CanModifyStoreSettings">Actions</th>
</tr>
</thead>
<tbody>
@foreach (var user in Model.Users)
{
<tr>
<th>Email</th>
<th>Role</th>
<th style="text-align:right">Actions</th>
<td>@user.Email</td>
<td>@user.Role</td>
<td class="actions-col" permission="@Policies.CanModifyStoreSettings">
<a asp-action="DeleteStoreUser" asp-route-storeId="@Model.StoreId" asp-route-userId="@user.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="This action will prevent <strong>@Html.Encode(user.Email)</strong> from accessing this store and its settings." data-confirm-input="REMOVE">Remove</a>
</td>
</tr>
</thead>
<tbody>
@foreach (var user in Model.Users)
{
<tr>
<td>@user.Email</td>
<td>@user.Role</td>
<td style="text-align:right">
<a asp-action="DeleteStoreUser" asp-route-storeId="@Model.StoreId" asp-route-userId="@user.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="This action will prevent <strong>@Html.Encode(user.Email)</strong> from accessing this store and its settings." data-confirm-input="REMOVE">Remove</a>
</td>
</tr>
}
</tbody>
</table>
</div>
}
</tbody>
</table>
</div>
</div>
<partial name="_Confirm" model="@(new ConfirmModel("Remove store user", "This action will prevent the user from accessing this store and its settings. Are you sure?", "Delete"))" />
<partial name="_Confirm" model="@(new ConfirmModel("Remove store user", "This action will prevent the user from accessing this store and its settings. Are you sure?", "Delete"))" permission="@Policies.CanModifyStoreSettings" />
@section PageFootContent {
<partial name="_ValidationScriptsPartial" />

View file

@ -1,4 +1,6 @@
@using BTCPayServer.Abstractions.Models
@using BTCPayServer.Client
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model WebhooksViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
@ -8,7 +10,7 @@
<div class="col-xl-8 col-xxl-constrain">
<div class="d-flex align-items-center justify-content-between mb-3">
<h3 class="mb-0">@ViewData["Title"]</h3>
<a id="CreateWebhook" asp-action="NewWebhook" class="btn btn-primary" role="button" asp-route-storeId="@Context.GetRouteValue("storeId")">
<a id="CreateWebhook" asp-action="NewWebhook" class="btn btn-primary" role="button" asp-route-storeId="@Context.GetRouteValue("storeId")" permission="@Policies.CanModifyStoreSettings">
Create Webhook
</a>
</div>
@ -21,7 +23,7 @@
<tr>
<th>Status</th>
<th>Url</th>
<th class="text-end">Actions</th>
<th class="text-end" permission="@Policies.CanModifyStoreSettings">Actions</th>
</tr>
</thead>
<tbody>
@ -46,7 +48,7 @@
}
</td>
<td class="d-block text-break">@wh.Url</td>
<td class="text-end text-md-nowrap">
<td class="text-end text-md-nowrap" permission="@Policies.CanModifyStoreSettings">
<a asp-action="TestWebhook" asp-route-storeId="@Context.GetRouteValue("storeId")" asp-route-webhookId="@wh.Id">Test</a> -
<a asp-action="ModifyWebhook" asp-route-storeId="@Context.GetRouteValue("storeId")" asp-route-webhookId="@wh.Id">Modify</a> -
<a asp-action="DeleteWebhook" asp-route-storeId="@Context.GetRouteValue("storeId")" asp-route-webhookId="@wh.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-confirm-input="DELETE">Delete</a>
@ -56,7 +58,7 @@
</tbody>
</table>
<partial name="_Confirm" model="@(new ConfirmModel("Delete Webhook", "This webhook will be removed from this store.", "Delete"))" />
<partial name="_Confirm" model="@(new ConfirmModel("Delete Webhook", "This webhook will be removed from this store.", "Delete"))" permission="@Policies.CanModifyStoreSettings" />
}
else
{

View file

@ -6,18 +6,18 @@
<div class="sticky-header mb-l">
<h2 class="mt-1 mb-2 mb-lg-4">Store Settings</h2>
<nav id="SectionNav">
<nav id="SectionNav" permission="@Policies.CanViewStoreSettings">
<div class="nav">
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.General))" class="nav-link @ViewData.IsActivePage(StoreNavPages.General)" asp-controller="UIStores" asp-action="GeneralSettings" asp-route-storeId="@storeId">General</a>
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.Rates))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Rates)" asp-controller="UIStores" asp-action="Rates" asp-route-storeId="@storeId">Rates</a>
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.CheckoutAppearance))" class="nav-link @ViewData.IsActivePage(StoreNavPages.CheckoutAppearance)" asp-controller="UIStores" asp-action="CheckoutAppearance" asp-route-storeId="@storeId">Checkout Appearance</a>
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.Tokens))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Tokens)" asp-controller="UIStores" asp-action="ListTokens" asp-route-storeId="@storeId">Access Tokens</a>
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.Users))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Users)" asp-controller="UIStores" asp-action="StoreUsers" asp-route-storeId="@storeId">Users</a>
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.Roles))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Roles)" asp-controller="UIStores" asp-action="ListRoles" asp-route-storeId="@storeId">Roles</a>
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.Webhooks))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Webhooks)" asp-controller="UIStores" asp-action="Webhooks" asp-route-storeId="@storeId">Webhooks</a>
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.PayoutProcessors))" class="nav-link @ViewData.IsActivePage(StoreNavPages.PayoutProcessors)" asp-controller="UIPayoutProcessors" asp-action="ConfigureStorePayoutProcessors" asp-route-storeId="@storeId">Payout Processors</a>
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.Emails))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Emails)" asp-controller="UIStores" asp-action="StoreEmailSettings" asp-route-storeId="@storeId">Emails</a>
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.Forms))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Forms)" asp-controller="UIForms" asp-action="FormsList" asp-route-storeId="@storeId">Forms</a>
<a id="SectionNav-@(nameof(StoreNavPages.General))" class="nav-link @ViewData.IsActivePage(StoreNavPages.General)" asp-controller="UIStores" asp-action="GeneralSettings" asp-route-storeId="@storeId">General</a>
<a id="SectionNav-@(nameof(StoreNavPages.Rates))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Rates)" asp-controller="UIStores" asp-action="Rates" asp-route-storeId="@storeId">Rates</a>
<a id="SectionNav-@(nameof(StoreNavPages.CheckoutAppearance))" class="nav-link @ViewData.IsActivePage(StoreNavPages.CheckoutAppearance)" asp-controller="UIStores" asp-action="CheckoutAppearance" asp-route-storeId="@storeId">Checkout Appearance</a>
<a id="SectionNav-@(nameof(StoreNavPages.Tokens))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Tokens)" asp-controller="UIStores" asp-action="ListTokens" asp-route-storeId="@storeId">Access Tokens</a>
<a id="SectionNav-@(nameof(StoreNavPages.Users))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Users)" asp-controller="UIStores" asp-action="StoreUsers" asp-route-storeId="@storeId">Users</a>
<a id="SectionNav-@(nameof(StoreNavPages.Roles))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Roles)" asp-controller="UIStores" asp-action="ListRoles" asp-route-storeId="@storeId">Roles</a>
<a id="SectionNav-@(nameof(StoreNavPages.Webhooks))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Webhooks)" asp-controller="UIStores" asp-action="Webhooks" asp-route-storeId="@storeId">Webhooks</a>
<a id="SectionNav-@(nameof(StoreNavPages.PayoutProcessors))" class="nav-link @ViewData.IsActivePage(StoreNavPages.PayoutProcessors)" asp-controller="UIPayoutProcessors" asp-action="ConfigureStorePayoutProcessors" asp-route-storeId="@storeId">Payout Processors</a>
<a id="SectionNav-@(nameof(StoreNavPages.Emails))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Emails)" asp-controller="UIStores" asp-action="StoreEmailSettings" asp-route-storeId="@storeId">Emails</a>
<a id="SectionNav-@(nameof(StoreNavPages.Forms))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Forms)" asp-controller="UIForms" asp-action="FormsList" asp-route-storeId="@storeId">Forms</a>
<vc:ui-extension-point location="store-nav" model="@Model"/>
</div>
</nav>

View file

@ -24,13 +24,7 @@
{
<tr>
<td>
<a
permission="@Policies.CanModifyStoreSettings"
asp-action="GeneralSettings" asp-controller="UIStores" asp-route-storeId="@store.StoreId"
id="Store-@store.StoreId">
@store.StoreName
</a>
<span not-permission="@Policies.CanModifyStoreSettings">@store.StoreName</span>
<a asp-action="Index" asp-controller="UIStores" asp-route-storeId="@store.StoreId" id="Store-@store.StoreId">@store.StoreName</a>
@if (store.Archived)
{
<span class="badge bg-info ms-2">archived</span>

View file

@ -94,14 +94,14 @@
white-space: nowrap;
}
#mainNav .navbar-nav > li.nav-item .nav-link:focus,
#mainNav .navbar-nav > li.nav-item .nav-link:hover {
#mainNav .navbar-nav > li.nav-item a.nav-link:focus,
#mainNav .navbar-nav > li.nav-item a.nav-link:hover {
color: var(--btcpay-header-link-accent);
}
#mainNav .navbar-nav > li.nav-item .nav-link.active,
#mainNav .navbar-nav > li.nav-item .nav-link.active:focus,
#mainNav .navbar-nav > li.nav-item .nav-link.active:hover {
#mainNav .navbar-nav > li.nav-item a.nav-link.active,
#mainNav .navbar-nav > li.nav-item a.nav-link.active:focus,
#mainNav .navbar-nav > li.nav-item a.nav-link.active:hover {
color: var(--btcpay-header-link-active);
}