Refactor: Cleanup useless code (#7224)
What changed, and why it matters
This commit is a large cleanup/refactoring change that removes unused code, simplifies controllers, and updates tests. It also removes the temporary file URL feature entirely. There are a few small security-relevant changes, such as switching pull-payment lookups to use context data set by an authorization filter and removing explicit store-id checks in some actions, but these appear to be moving checks into a shared filter rather than removing them. The commit title and message do not describe this as a security fix.
Review the SetContextFilter and UIStoresController authorization paths to confirm store-scoped authorization is still enforced after removing inline checks. Verify that removing temporary file URLs does not break any legitimate workflows or leave orphaned routes. Treat this as a routine refactor unless additional context shows it fixes a reported vulnerability.
Security signals we found
Authorization filter now loads pull-payment data into HttpContext, replacing direct DB lookups in some actions
Inline store-id validation removed from label and dashboard actions; authorization now relies on CurrentStore/SetContextFilter
Temporary file URL feature and local temporary file controller completely removed
Rates POST action signature changed to require explicit storeId parameter
No vendor description of security relevance or CVE in commit message
Evidence from the diff
The commit removes dead code including AddFile(Uri,…), GetTemporaryFileUrl, the temporary local file controller, and related UI/tests. It refactors UIPullPaymentController to primary-constructor syntax and changes several actions to retrieve pull-payment data via HttpContext.GetPullPaymentDataOrNull() set by SetContextFilter. UIStoresController actions (Dashboard, Labels, Rates) are updated to rely on CurrentStore/HttpContext.GetStoreData() and the route-level authorization filter instead of inline store-id checks. The Rates POST action now requires storeId as a non-nullable parameter. Several test files are updated to match new method signatures and add WaitForLoadStateAsync calls. No explicit security bug fix is described.
Changed components
BTCPayServer/Controllers/UIPullPaymentController.csBTCPayServer/Controllers/UIStoresController.Dashboard.csBTCPayServer/Controllers/UIStoresController.Labels.csBTCPayServer/Controllers/UIStoresController.Rates.csBTCPayServer/Controllers/UIStoresController.csBTCPayServer/Security/SetContextFilter.csBTCPayServer/Extensions.csBTCPayServer/Storage/Services/FileService.csBTCPayServer/Storage/Services/Providers/FileSystemStorage/TemporaryLocalFileController.csBTCPayServer/Storage/Services/Providers/FileSystemStorage/TemporaryLocalFileDescriptor.csBTCPayServer/Views/UIServer/CreateTemporaryFileUrl.cshtmlInspect captured patch +81 / −405
diff --git a/BTCPayServer.Abstractions/Contracts/IFileService.cs b/BTCPayServer.Abstractions/Contracts/IFileService.cs
index db1427d..41a3c58 100644
--- a/BTCPayServer.Abstractions/Contracts/IFileService.cs
+++ b/BTCPayServer.Abstractions/Contracts/IFileService.cs
@@ -10,10 +10,7 @@ public interface IFileService
{
Task<bool> IsAvailable();
Task<IStoredFile> AddFile(IFormFile file, string userId);
- Task<IStoredFile> AddFile(Uri file, string userId);
Task<string?> GetFileUrl(Uri baseUri, string fileId);
- Task<string?> GetTemporaryFileUrl(Uri baseUri, string fileId, DateTimeOffset expiry,
- bool isDownload);
Task RemoveFile(string fileId, string userId);
Task<UploadImageResultModel> UploadImage(IFormFile file, string userId, long maxFileSizeInBytes = 1_000_000);
}
diff --git a/BTCPayServer.Abstractions/Extensions/ViewsRazor.cs b/BTCPayServer.Abstractions/Extensions/ViewsRazor.cs
index 50ac21c..bae22d7 100644
--- a/BTCPayServer.Abstractions/Extensions/ViewsRazor.cs
+++ b/BTCPayServer.Abstractions/Extensions/ViewsRazor.cs
@@ -30,6 +30,7 @@ namespace BTCPayServer.Abstractions.Extensions
return viewData["BlazorAllowed"] is not false;
}
+ [Obsolete("Use SetLayoutModel instead")]
public static void SetActivePage<T>(this ViewDataDictionary viewData, T activePage, string title = null, string activeId = null)
where T : IConvertible
{
@@ -55,6 +56,7 @@ namespace BTCPayServer.Abstractions.Extensions
public static bool IsCategory(this ViewDataDictionary viewData, WellKnownCategories category) =>
IsCategory(viewData, LayoutModel.Map(category));
+ [Obsolete("Use SetLayoutModel instead")]
public static void SetActivePage(this ViewDataDictionary viewData, string activePage, string category, string title = null, string activeId = null)
=> viewData.SetLayoutModel(new(activePage, title){ SubMenuItemId = activeId, ActiveCategory = category } );
diff --git a/BTCPayServer.Tests/FakeServer.cs b/BTCPayServer.Tests/FakeServer.cs
index 93d5485..28f6aa7 100644
--- a/BTCPayServer.Tests/FakeServer.cs
+++ b/BTCPayServer.Tests/FakeServer.cs
@@ -8,24 +8,21 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
namespace BTCPayServer.Tests
{
public class FakeServer : IDisposable
{
IHost host;
- readonly SemaphoreSlim semaphore;
+ readonly SemaphoreSlim semaphore = new(0);
readonly CancellationTokenSource cts = new CancellationTokenSource();
- public FakeServer()
- {
- _channel = Channel.CreateUnbounded<HttpContext>();
- semaphore = new SemaphoreSlim(0);
- }
- readonly Channel<HttpContext> _channel;
+ readonly Channel<HttpContext> _channel = Channel.CreateUnbounded<HttpContext>();
public async Task Start()
{
host = Host.CreateDefaultBuilder()
+ .ConfigureLogging(p => p.ClearProviders())
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder
diff --git a/BTCPayServer.Tests/PlaywrightTests.cs b/BTCPayServer.Tests/PlaywrightTests.cs
index f386568..5705320 100644
--- a/BTCPayServer.Tests/PlaywrightTests.cs
+++ b/BTCPayServer.Tests/PlaywrightTests.cs
@@ -2362,6 +2362,7 @@ namespace BTCPayServer.Tests
document.getElementById('EndDate').value = yst.toISOString();
");
await s.ClickPagePrimary();
+ await s.Page.WaitForLoadStateAsync(LoadState.NetworkIdle);
var pageContent = await s.Page.ContentAsync();
Assert.Contains("End date cannot be before start date", pageContent);
Assert.DoesNotContain("App updated", pageContent);
@@ -2476,6 +2477,7 @@ namespace BTCPayServer.Tests
await s.PayInvoice(true, 20);
invoiceId = s.Page.Url[(s.Page.Url.LastIndexOf("/", StringComparison.Ordinal) + 1)..];
await s.GoToInvoice(invoiceId);
+ await s.Page.WaitForLoadStateAsync(LoadState.NetworkIdle);
pageContent = await s.Page.ContentAsync();
Assert.Contains("test-with-perk@crowdfund.com", pageContent);
}
diff --git a/BTCPayServer.Tests/ThirdPartyTests.cs b/BTCPayServer.Tests/ThirdPartyTests.cs
index 4dbc838..971f1db 100644
--- a/BTCPayServer.Tests/ThirdPartyTests.cs
+++ b/BTCPayServer.Tests/ThirdPartyTests.cs
@@ -632,9 +632,9 @@ retry:
string currency = "USD")
{
var storeController = user.GetController<UIStoresController>();
- var vm = (RatesViewModel)((ViewResult)await storeController.Rates()).Model;
+ var vm = await storeController.Rates().AssertViewModelAsync<RatesViewModel>();
vm.PrimarySource.PreferredExchange = exchange;
- await storeController.Rates(vm);
+ await storeController.Rates(vm,vm.StoreId);
var invoice2 = await user.BitPay.CreateInvoiceAsync(
new Invoice()
{
diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs
index 6dbf77e..6900b59 100644
--- a/BTCPayServer.Tests/UnitTest1.cs
+++ b/BTCPayServer.Tests/UnitTest1.cs
@@ -553,9 +553,9 @@ namespace BTCPayServer.Tests
Assert.Null(GetRatesResult?.Data);
var store = acc.GetController<UIStoresController>();
- var ratesVM = (RatesViewModel)(Assert.IsType<ViewResult>(await store.Rates()).Model);
+ var ratesVM = await store.Rates().AssertViewModelAsync<RatesViewModel>();
ratesVM.DefaultCurrencyPairs = "BTC_USD,LTC_USD";
- await store.Rates(ratesVM);
+ await store.Rates(ratesVM, ratesVM.StoreId);
store = acc.GetController<UIStoresController>();
rateController = acc.GetController<BitpayRateController>();
GetRatesResult = JObject.Parse(((OkObjectResult)rateController.GetRates(null, default)
@@ -799,13 +799,13 @@ namespace BTCPayServer.Tests
Assert.Equal(Money.Coins(1.0m), invoice1.BtcPrice);
var storeController = user.GetController<UIStoresController>();
- var vm = (RatesViewModel)((ViewResult)await storeController.Rates()).Model;
+ var vm = await storeController.Rates().AssertViewModelAsync<RatesViewModel>();
Assert.Equal(0.0, vm.Spread);
vm.Spread = 40;
- await storeController.Rates(vm);
+ await storeController.Rates(vm, vm.StoreId);
- var invoice2 = user.BitPay.CreateInvoice(
+ var invoice2 = await user.BitPay.CreateInvoiceAsync(
new Invoice()
{
Price = 5000.0m,
@@ -2735,25 +2735,6 @@ namespace BTCPayServer.Tests
var data = await net.GetStringAsync(new Uri(viewFilesViewModel.DirectUrlByFiles[fileId]));
Assert.Equal(fileContent, data);
- //create a temporary link to file
- Assert.IsType<RedirectToActionResult>(await controller.CreateTemporaryFileUrl(fileId,
- new UIServerController.CreateTemporaryFileUrlViewModel
- {
- IsDownload = true,
- TimeAmount = 1,
- TimeType = UIServerController.CreateTemporaryFileUrlViewModel.TmpFileTimeType.Minutes
- }));
- var statusMessageModel = controller.TempData.GetStatusMessageModel();
- Assert.NotNull(statusMessageModel);
- Assert.Equal(StatusMessageModel.StatusSeverity.Success, statusMessageModel.Severity);
- var index = statusMessageModel.Html.IndexOf("target='_blank'>");
- var url = statusMessageModel.Html.Substring(index)
- .Replace("</a>", string.Empty)
- .Replace("target='_blank'>", string.Empty);
- //verify tmpfile is available and the same
- data = await net.GetStringAsync(new Uri(url));
- Assert.Equal(fileContent, data);
-
return fileId;
}
diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldPullPaymentController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldPullPaymentController.cs
index 532b42a..e92d3e3 100644
--- a/BTCPayServer/Controllers/GreenField/GreenfieldPullPaymentController.cs
+++ b/BTCPayServer/Controllers/GreenField/GreenfieldPullPaymentController.cs
@@ -585,11 +585,10 @@ retry:
[Authorize(Policy = Policies.CanArchivePullPayments, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public async Task<IActionResult> ArchivePullPayment(string storeId, string pullPaymentId)
{
- using var ctx = _dbContextFactory.CreateContext();
- var pp = await ctx.PullPayments.FindAsync(pullPaymentId);
- if (pp is null || pp.StoreId != storeId)
+ var pp = HttpContext.GetPullPaymentDataOrNull();
+ if (pp is null)
return PullPaymentNotFound();
- await _pullPaymentService.Cancel(new PullPaymentHostedService.CancelRequest(pullPaymentId));
+ await _pullPaymentService.Cancel(new PullPaymentHostedService.CancelRequest(pp.Id));
return Ok();
}
diff --git a/BTCPayServer/Controllers/UIPaymentRequestController.cs b/BTCPayServer/Controllers/UIPaymentRequestController.cs
index 1dd986d..6528eac 100644
--- a/BTCPayServer/Controllers/UIPaymentRequestController.cs
+++ b/BTCPayServer/Controllers/UIPaymentRequestController.cs
@@ -51,7 +51,6 @@ namespace BTCPayServer.Controllers
private readonly StoreLabelRepository _storeLabelRepository;
- private FormComponentProviders FormProviders { get; }
public FormDataService FormDataService { get; }
public IStringLocalizer StringLocalizer { get; }
public ViewLocalizer ViewLocalizer { get; }
@@ -66,7 +65,6 @@ namespace BTCPayServer.Controllers
StoreRepository storeRepository,
UriResolver uriResolver,
InvoiceRepository invoiceRepository,
- FormComponentProviders formProviders,
FormDataService formDataService,
IStringLocalizer stringLocalizer,
ViewLocalizer viewLocalizer,
@@ -84,7 +82,6 @@ namespace BTCPayServer.Controllers
_uriResolver = uriResolver;
_InvoiceRepository = invoiceRepository;
_dbContextFactory = dbContextFactory;
- FormProviders = formProviders;
FormDataService = formDataService;
StringLocalizer = stringLocalizer;
ViewLocalizer = viewLocalizer;
@@ -111,7 +108,7 @@ namespace BTCPayServer.Controllers
StoreId = store.Id,
Skip = model.Skip,
Count = model.Count,
- Status = fs.GetFilterArray("status")?.Select(s => Enum.Parse<Client.Models.PaymentRequestStatus>(s, true)).ToArray(),
+ Status = fs.GetFilterArray("status")?.Select(s => Enum.Parse<PaymentRequestStatus>(s, true)).ToArray(),
IncludeArchived = fs.GetFilterBool("includearchived") ?? false,
SearchText = model.SearchText,
StartDate = startDate,
@@ -470,7 +467,7 @@ namespace BTCPayServer.Controllers
{
var store = await _storeRepository.FindStore(result.StoreId);
var prData = await _PaymentRequestRepository.FindPaymentRequest(result.Id, null, cancellationToken);
- var newInvoice = await _InvoiceController.CreatePaymentRequestInvoice(prData, amount, result.AmountDue, store, Request, cancellationToken);
+ var newInvoice = await _InvoiceController.CreatePaymentRequestInvoice(prData, amount, result.AmountDue, store!, Request, cancellationToken);
if (redirectToInvoice)
{
return RedirectToAction("Checkout", "UIInvoice", new { invoiceId = newInvoice.Id });
@@ -500,7 +497,8 @@ namespace BTCPayServer.Controllers
}
var invoices = result.Invoices.Where(requestInvoice =>
- requestInvoice.State.Status == InvoiceStatus.New && !requestInvoice.Payments.Any());
+ requestInvoice.State.Status == InvoiceStatus.New && !requestInvoice.Payments.Any())
+ .ToArray();
if (!invoices.Any())
{
@@ -527,9 +525,8 @@ namespace BTCPayServer.Controllers
{
var store = GetCurrentStore();
var result = await EditPaymentRequest(store.Id, payReqId);
- if (result is ViewResult viewResult)
+ if (result is ViewResult { Model: UpdatePaymentRequestViewModel model })
{
- var model = (UpdatePaymentRequestViewModel)viewResult.Model;
model.Id = null;
model.Archived = false;
model.ExpiryDate = null;
diff --git a/BTCPayServer/Controllers/UIPullPaymentController.cs b/BTCPayServer/Controllers/UIPullPaymentController.cs
index 0faf2e4..f6aa41b 100644
--- a/BTCPayServer/Controllers/UIPullPaymentController.cs
+++ b/BTCPayServer/Controllers/UIPullPaymentController.cs
@@ -24,58 +24,31 @@ using Microsoft.Extensions.Localization;
namespace BTCPayServer.Controllers
{
- public partial class UIPullPaymentController : Controller
+ public class UIPullPaymentController(
+ ApplicationDbContextFactory dbContextFactory,
+ CurrencyNameTable currencyNameTable,
+ DisplayFormatter displayFormatter,
+ UriResolver uriResolver,
+ PullPaymentHostedService pullPaymentHostedService,
+ BTCPayNetworkProvider networkProvider,
+ BTCPayNetworkJsonSerializerSettings serializerSettings,
+ PayoutMethodHandlerDictionary payoutHandlers,
+ StoreRepository storeRepository,
+ IStringLocalizer stringLocalizer)
+ : Controller
{
- private readonly ApplicationDbContextFactory _dbContextFactory;
- private readonly CurrencyNameTable _currencyNameTable;
- private readonly DisplayFormatter _displayFormatter;
- private readonly UriResolver _uriResolver;
- private readonly PullPaymentHostedService _pullPaymentHostedService;
- private readonly BTCPayNetworkProvider _networkProvider;
- private readonly BTCPayNetworkJsonSerializerSettings _serializerSettings;
- private readonly PayoutMethodHandlerDictionary _payoutHandlers;
- private readonly StoreRepository _storeRepository;
- private readonly BTCPayServerEnvironment _env;
- private readonly SettingsRepository _settingsRepository;
- public IStringLocalizer StringLocalizer { get; }
-
- public UIPullPaymentController(ApplicationDbContextFactory dbContextFactory,
- CurrencyNameTable currencyNameTable,
- DisplayFormatter displayFormatter,
- UriResolver uriResolver,
- PullPaymentHostedService pullPaymentHostedService,
- BTCPayNetworkProvider networkProvider,
- BTCPayNetworkJsonSerializerSettings serializerSettings,
- PayoutMethodHandlerDictionary payoutHandlers,
- StoreRepository storeRepository,
- BTCPayServerEnvironment env,
- IStringLocalizer stringLocalizer,
- SettingsRepository settingsRepository)
- {
- _dbContextFactory = dbContextFactory;
- _currencyNameTable = currencyNameTable;
- _displayFormatter = displayFormatter;
- _uriResolver = uriResolver;
- _pullPaymentHostedService = pullPaymentHostedService;
- _serializerSettings = serializerSettings;
- _payoutHandlers = payoutHandlers;
- _storeRepository = storeRepository;
- _env = env;
- _settingsRepository = settingsRepository;
- _networkProvider = networkProvider;
- StringLocalizer = stringLocalizer;
- }
+ public IStringLocalizer StringLocalizer { get; } = stringLocalizer;
[AllowAnonymous]
[HttpGet("pull-payments/{pullPaymentId}")]
public async Task<IActionResult> ViewPullPayment(string pullPaymentId)
{
- using var ctx = _dbContextFactory.CreateContext();
+ using var ctx = dbContextFactory.CreateContext();
var pp = await ctx.PullPayments.FindAsync(pullPaymentId);
- if (pp is null || _networkProvider.DefaultNetwork?.CryptoCode is not {} cryptoCode)
+ if (pp is null || networkProvider.DefaultNetwork?.CryptoCode is not {} cryptoCode)
return NotFound();
- var store = await _storeRepository.FindStore(pp.StoreId);
+ var store = await storeRepository.FindStore(pp.StoreId);
if (store is null)
return NotFound();
@@ -86,10 +59,10 @@ namespace BTCPayServer.Controllers
.Select(o => new
{
Entity = o,
- Blob = o.GetBlob(_serializerSettings),
- ProofBlob = _payoutHandlers.TryGet(o.GetPayoutMethodId())?.ParseProof(o)
- });
- var cd = _currencyNameTable.GetCurrencyData(pp.Currency, false);
+ Blob = o.GetBlob(serializerSettings),
+ ProofBlob = payoutHandlers.TryGet(o.GetPayoutMethodId())?.ParseProof(o)
+ }).ToList();
+ var cd = currencyNameTable.GetCurrencyData(pp.Currency, false);
var totalPaid = payouts.Where(p => p.Entity.State != PayoutState.Cancelled).Select(p => p.Entity.OriginalAmount).Sum();
var amountDue = pp.Limit - totalPaid;
@@ -105,7 +78,7 @@ namespace BTCPayServer.Controllers
{
Id = entity.Entity.Id,
Amount = entity.Entity.OriginalAmount,
- AmountFormatted = _displayFormatter.Currency(entity.Entity.OriginalAmount, entity.Entity.OriginalCurrency),
+ AmountFormatted = displayFormatter.Currency(entity.Entity.OriginalAmount, entity.Entity.OriginalCurrency),
Currency = entity.Entity.OriginalCurrency,
Status = entity.Entity.State,
Destination = entity.Blob.Destination,
@@ -115,9 +88,9 @@ namespace BTCPayServer.Controllers
}).ToList()
};
vm.IsPending &= vm.AmountDue > 0.0m;
- vm.StoreBranding = await StoreBrandingViewModel.CreateAsync(Request, _uriResolver, storeBlob);
+ vm.StoreBranding = await StoreBrandingViewModel.CreateAsync(Request, uriResolver, storeBlob);
- if (_pullPaymentHostedService.SupportsLNURL(pp))
+ if (pullPaymentHostedService.SupportsLNURL(pp))
{
var url = Url.Action(nameof(UILNURLController.GetLNURLForPullPayment), "UILNURL", new { cryptoCode, pullPaymentId = vm.Id }, Request.Scheme, Request.Host.ToString());
vm.LnurlEndpoint = url != null ? new Uri(url) : null;
@@ -135,7 +108,7 @@ namespace BTCPayServer.Controllers
{
pullPaymentId = vm.Id,
onExisting = onExisting.ToString()
- }, Request.Scheme, Request.Host.ToString());
+ }, Request.Scheme, Request.Host.ToString()) ?? "";
registerUrl = Uri.EscapeDataString(registerUrl);
return registerUrl;
}
@@ -144,8 +117,7 @@ namespace BTCPayServer.Controllers
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> EditPullPayment(string storeId, string pullPaymentId)
{
- using var ctx = _dbContextFactory.CreateContext();
- Data.PullPaymentData pp = await ctx.PullPayments.FindAsync(pullPaymentId);
+ var pp = HttpContext.GetPullPaymentDataOrNull();
if (pp == null && !string.IsNullOrEmpty(pullPaymentId))
{
return NotFound();
@@ -159,14 +131,11 @@ namespace BTCPayServer.Controllers
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> EditPullPayment(string storeId, string pullPaymentId, UpdatePullPaymentModel viewModel)
{
- using var ctx = _dbContextFactory.CreateContext();
-
- var pp = await ctx.PullPayments.FindAsync(pullPaymentId);
- if (pp == null && !string.IsNullOrEmpty(pullPaymentId))
- {
+ await using var ctx = dbContextFactory.CreateContext();
+ var pp = HttpContext.GetPullPaymentDataOrNull();
+ if (pp == null)
return NotFound();
- }
-
+ ctx.Attach(pp);
if (!ModelState.IsValid)
{
return View(viewModel);
@@ -202,7 +171,7 @@ namespace BTCPayServer.Controllers
if (vm.ClaimedAmount == 0)
vm.ClaimedAmount = null;
- await using var ctx = _dbContextFactory.CreateContext();
+ await using var ctx = dbContextFactory.CreateContext();
var pp = await ctx.PullPayments.FindAsync(pullPaymentId);
if (pp is null)
{
@@ -225,7 +194,7 @@ namespace BTCPayServer.Controllers
{
foreach (var pmId in supported)
{
- var handler = _payoutHandlers.TryGet(pmId);
+ var handler = payoutHandlers.TryGet(pmId);
(IClaimDestination dst, string err) = handler == null
? (null, StringLocalizer["No payment handler found for this payment method"])
: await handler.ParseAndValidateClaimDestination(vm.Destination, ppBlob, cancellationToken);
@@ -242,7 +211,7 @@ namespace BTCPayServer.Controllers
else
{
payoutMethodId = supported.FirstOrDefault(id => vm.SelectedPayoutMethod == id.ToString());
- payoutHandler = payoutMethodId is null ? null : _payoutHandlers.TryGet(payoutMethodId);
+ payoutHandler = payoutMethodId is null ? null : payoutHandlers.TryGet(payoutMethodId);
if (payoutHandler is not null)
{
(destination, error) = await payoutHandler.ParseAndValidateClaimDestination(vm.Destination, ppBlob, cancellationToken);
@@ -269,7 +238,7 @@ namespace BTCPayServer.Controllers
return await ViewPullPayment(pullPaymentId);
}
- var result = await _pullPaymentHostedService.Claim(new ClaimRequest
+ var result = await pullPaymentHostedService.Claim(new ClaimRequest
{
Destination = destination,
PullPaymentId = pullPaymentId,
@@ -293,8 +262,8 @@ namespace BTCPayServer.Controllers
{
(null, PayoutState.AwaitingApproval) => $"Your claim request to {vm.Destination} has been submitted and is awaiting approval",
(null, PayoutState.AwaitingPayment) => $"Your claim request to {vm.Destination} has been submitted and is awaiting payment",
- ({ } a, PayoutState.AwaitingApproval) => $"Your claim request of {_displayFormatter.Currency(a, pp.Currency, DisplayFormatter.CurrencyFormat.Symbol)} to {vm.Destination} has been submitted and is awaiting approval",
- ({ } a, PayoutState.AwaitingPayment) => $"Your claim request of {_displayFormatter.Currency(a, pp.Currency, DisplayFormatter.CurrencyFormat.Symbol)} to {vm.Destination} has been submitted and is awaiting payment",
+ ({ } a, PayoutState.AwaitingApproval) => $"Your claim request of {displayFormatter.Currency(a, pp.Currency, DisplayFormatter.CurrencyFormat.Symbol)} to {vm.Destination} has been submitted and is awaiting approval",
+ ({ } a, PayoutState.AwaitingPayment) => $"Your claim request of {displayFormatter.Currency(a, pp.Currency, DisplayFormatter.CurrencyFormat.Symbol)} to {vm.Destination} has been submitted and is awaiting payment",
_ => $"Unexpected payout state ({result.PayoutData.State})"
},
Severity = StatusMessageModel.StatusSeverity.Success
diff --git a/BTCPayServer/Controllers/UIServerController.Storage.cs b/BTCPayServer/Controllers/UIServerController.Storage.cs
index 1b422b0..cf32898 100644
--- a/BTCPayServer/Controllers/UIServerController.Storage.cs
+++ b/BTCPayServer/Controllers/UIServerController.Storage.cs
@@ -89,86 +89,6 @@ namespace BTCPayServer.Controllers
}
}
- [HttpGet("server/files/{fileId}/tmp")]
- public async Task<IActionResult> CreateTemporaryFileUrl(string fileId)
- {
- var file = await _StoredFileRepository.GetFile(fileId);
-
- if (file == null)
- {
- return NotFound();
- }
-
- return View(new CreateTemporaryFileUrlViewModel());
- }
-
- [HttpPost("server/files/{fileId}/tmp")]
- public async Task<IActionResult> CreateTemporaryFileUrl(string fileId,
- CreateTemporaryFileUrlViewModel viewModel)
- {
- if (viewModel.TimeAmount <= 0)
- {
- ModelState.AddModelError(nameof(viewModel.TimeAmount), StringLocalizer["Time must be at least 1"]);
- }
-
- if (!ModelState.IsValid)
- {
- return View(viewModel);
- }
-
- var file = await _StoredFileRepository.GetFile(fileId);
-
- if (file == null)
- {
- return NotFound();
- }
-
- var expiry = DateTimeOffset.UtcNow;
- switch (viewModel.TimeType)
- {
- case CreateTemporaryFileUrlViewModel.TmpFileTimeType.Seconds:
- expiry = expiry.AddSeconds(viewModel.TimeAmount);
- break;
- case CreateTemporaryFileUrlViewModel.TmpFileTimeType.Minutes:
- expiry = expiry.AddMinutes(viewModel.TimeAmount);
- break;
- case CreateTemporaryFileUrlViewModel.TmpFileTimeType.Hours:
- expiry = expiry.AddHours(viewModel.TimeAmount);
- break;
- case CreateTemporaryFileUrlViewModel.TmpFileTimeType.Days:
- expiry = expiry.AddDays(viewModel.TimeAmount);
- break;
- default:
- throw new ArgumentOutOfRangeException();
- }
-
- var url = await _fileService.GetTemporaryFileUrl(Request.GetAbsoluteRootUri(), fileId, expiry, viewModel.IsDownload);
- TempData.SetStatusMessageModel(new StatusMessageModel()
- {
- Severity = StatusMessageModel.StatusSeverity.Success,
- Html = $"Generated Temporary Url for file {file.FileName} which expires at {expiry.ToBrowserDate()}. <a href='{url}' target='_blank'>{url}</a>"
- });
- return RedirectToAction(nameof(Files), new
- {
- fileIds = new string[] { fileId }
- });
-
- }
-
- public class CreateTemporaryFileUrlViewModel
- {
- public enum TmpFileTimeType
- {
- Seconds,
- Minutes,
- Hours,
- Days
- }
- public int TimeAmount { get; set; }
- public TmpFileTimeType TimeType { get; set; }
- public bool IsDownload { get; set; }
- }
-
[HttpPost("server/files/upload")]
public async Task<IActionResult> CreateFiles(List<IFormFile> files)
{
diff --git a/BTCPayServer/Controllers/UIStoresController.Dashboard.cs b/BTCPayServer/Controllers/UIStoresController.Dashboard.cs
index d79f676..d7f8976 100644
--- a/BTCPayServer/Controllers/UIStoresController.Dashboard.cs
+++ b/BTCPayServer/Controllers/UIStoresController.Dashboard.cs
@@ -23,9 +23,7 @@ public partial class UIStoresController
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> Dashboard()
{
- var store = CurrentStore;
- if (store is null)
- return NotFound();
+ var store = HttpContext.GetStoreData();
HttpContext.SetPreferredStoreId(store.Id);
var storeBlob = store.GetStoreBlob();
diff --git a/BTCPayServer/Controllers/UIStoresController.Labels.cs b/BTCPayServer/Controllers/UIStoresController.Labels.cs
index a724cf6..2a84297 100644
--- a/BTCPayServer/Controllers/UIStoresController.Labels.cs
+++ b/BTCPayServer/Controllers/UIStoresController.Labels.cs
@@ -21,10 +21,6 @@ public partial class UIStoresController
bool excludeTypes = true,
string? linkedType = null)
{
- var store = CurrentStore;
- if (store is null || !string.Equals(store.Id, storeId, StringComparison.Ordinal))
- return NotFound();
-
if (string.IsNullOrEmpty(linkedType))
return BadRequest("linkedType is required.");
@@ -52,10 +48,6 @@ public partial class UIStoresController
[ValidateAntiForgeryToken]
public async Task<IActionResult> UpdateStoreLabels(string storeId, [FromBody] UpdateStoreLabelsRequest request)
{
- var store = CurrentStore;
- if (store is null || !string.Equals(store.Id, storeId, StringComparison.Ordinal))
- return NotFound();
-
if (string.IsNullOrWhiteSpace(request.Type) || string.IsNullOrWhiteSpace(request.Id))
return BadRequest();
diff --git a/BTCPayServer/Controllers/UIStoresController.Rates.cs b/BTCPayServer/Controllers/UIStoresController.Rates.cs
index 805ed3b..d34dcf9 100644
--- a/BTCPayServer/Controllers/UIStoresController.Rates.cs
+++ b/BTCPayServer/Controllers/UIStoresController.Rates.cs
@@ -29,16 +29,16 @@ public partial class UIStoresController
[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)
+ public async Task<IActionResult> Rates(RatesViewModel model, string storeId, string? command = null, CancellationToken cancellationToken = default)
{
- model.StoreId = storeId ?? model.StoreId;
+ model.StoreId = CurrentStore.Id;
var storeBlob = CurrentStore.GetStoreBlob();
try
{
var currencyPairs = model.DefaultCurrencyPairs?
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
- .Select(p => CurrencyPair.Parse(p))
+ .Select(CurrencyPair.Parse)
.ToArray();
storeBlob.DefaultCurrencyPairs = currencyPairs;
}
@@ -163,8 +163,7 @@ public partial class UIStoresController
blob.RateScripting = model.ShowScripting;
if (model.ShowScripting)
{
- RateRules? rules;
- if (!RateRules.TryParse(model.Script, out rules, out var errors))
+ if (!RateRules.TryParse(model.Script, out var rules, out var errors))
{
errors ??= [];
var errorString = string.Join(", ", errors.ToArray());
@@ -183,7 +182,6 @@ public partial class UIStoresController
if (model.PreferredExchange is not null && GetAvailableExchanges().All(a => a.Id != model.PreferredExchange))
{
ModelState.AddModelError(nameof(model.PreferredExchange), StringLocalizer["Unsupported exchange"]);
- return;
}
}
diff --git a/BTCPayServer/Controllers/UIStoresController.cs b/BTCPayServer/Controllers/UIStoresController.cs
index 1492bc5..23aa0b5 100644
--- a/BTCPayServer/Controllers/UIStoresController.cs
+++ b/BTCPayServer/Controllers/UIStoresController.cs
@@ -27,7 +27,6 @@ using StoreData = BTCPayServer.Data.StoreData;
namespace BTCPayServer.Controllers;
[Route("stores")]
-[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public partial class UIStoresController : Controller
{
diff --git a/BTCPayServer/Extensions.cs b/BTCPayServer/Extensions.cs
index 204d526..4bfb106 100644
--- a/BTCPayServer/Extensions.cs
+++ b/BTCPayServer/Extensions.cs
@@ -831,6 +831,12 @@ namespace BTCPayServer
ctx.Items["BTCPAY.PAYMENTREQUESTDATA"] = paymentRequestData;
}
+ public static PullPaymentData? GetPullPaymentDataOrNull(this HttpContext ctx)
+ => ctx.Items.TryGet("BTCPAY.PULLPAYMENTDATA") as PullPaymentData;
+
+ public static void SetPullPaymentData(this HttpContext ctx, PullPaymentData? pullPaymentData)
+ => ctx.Items["BTCPAY.PULLPAYMENTDATA"] = pullPaymentData;
+
public static AppData? GetAppDataOrNull(this HttpContext ctx)
=> ctx.Items.TryGet("BTCPAY.APPDATA") as AppData;
diff --git a/BTCPayServer/Extensions/UrlHelperExtensions.cs b/BTCPayServer/Extensions/UrlHelperExtensions.cs
index bb80a2d..073f48d 100644
--- a/BTCPayServer/Extensions/UrlHelperExtensions.cs
+++ b/BTCPayServer/Extensions/UrlHelperExtensions.cs
@@ -122,7 +122,9 @@ namespace Microsoft.AspNetCore.Mvc
RequestBaseUrl requestBaseUrl,
FragmentString fragment = default,
LinkOptions? options = null) => generator.GetUriByAction(action, controller, values, requestBaseUrl.Scheme, requestBaseUrl.Host, requestBaseUrl.PathBase, fragment, options) ?? throw new InvalidOperationException($"Bug, unable to generate link for {controller}.{action}");
+
#nullable restore
+
public static string PayoutLink(this LinkGenerator urlHelper, string walletIdOrStoreId, string pullPaymentId, PayoutState payoutState, string scheme, HostString host, string pathbase)
{
WalletId.TryParse(walletIdOrStoreId, out var wallet);
diff --git a/BTCPayServer/Security/SetContextFilter.cs b/BTCPayServer/Security/SetContextFilter.cs
index f0cc2b1..3d7a4c3 100644
--- a/BTCPayServer/Security/SetContextFilter.cs
+++ b/BTCPayServer/Security/SetContextFilter.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using BTCPayServer.Data;
using BTCPayServer.Abstractions.Constants;
+using BTCPayServer.HostedServices;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.PaymentRequests;
@@ -16,6 +17,7 @@ public class SetContextFilter(
PaymentRequestRepository paymentRequestRepository,
InvoiceRepository invoiceRepository,
AppService appService,
+ PullPaymentHostedService pullPaymentHostedService,
StoreRepository storeRepository) : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
@@ -67,6 +69,13 @@ public class SetContextFilter(
if (invoice is not null)
httpContext.SetInvoiceData(invoice);
break;
+ case "pullPaymentId":
+ {
+ var pp = await pullPaymentHostedService.GetPullPayment(additionalScope.Scope, false);
+ if (pp is not null)
+ httpContext.SetPullPaymentData(pp);
+ break;
+ }
}
}
}
diff --git a/BTCPayServer/Storage/Services/FileService.cs b/BTCPayServer/Storage/Services/FileService.cs
index 0060ee1..c836e3d 100644
--- a/BTCPayServer/Storage/Services/FileService.cs
+++ b/BTCPayServer/Storage/Services/FileService.cs
@@ -104,36 +104,6 @@ namespace BTCPayServer.Storage.Services
return storedFile;
}
- public async Task<IStoredFile> AddFile(Uri url, string userId)
- {
- if (!await IsAvailable())
- throw new InvalidOperationException("StoreSettings not configured");
-
- var fileName = Sanitize(Path.GetFileName(url.AbsolutePath));
- if (!fileName.IsValidFileName())
- throw new InvalidOperationException("Invalid file name");
-
- // download
- var filePath = Path.Join(_dataDirectories.Value.TempDir, fileName);
- var httClient = _httpClientFactory.CreateClient();
- using var resp = await httClient.GetAsync(url);
- await using var stream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite);
- await resp.Content.CopyToAsync(stream);
- var file = new FormFile(stream, 0, stream.Length, fileName, fileName)
- {
- Headers = new HeaderDictionary(),
- ContentType = GetContentType(filePath)
- };
- await stream.FlushAsync();
-
- var storedFile = await AddFile(file, userId);
-
- // cleanup
- File.Delete(filePath);
-
- return storedFile;
- }
-
public async Task<string?> GetFileUrl(Uri baseUri, string fileId)
{
var settings = await _settingsRepository.GetSettingAsync<StorageSettings>();
@@ -144,17 +114,6 @@ namespace BTCPayServer.Storage.Services
return storedFile == null ? null : await provider.GetFileUrl(baseUri, storedFile, settings);
}
- public async Task<string?> GetTemporaryFileUrl(Uri baseUri, string fileId, DateTimeOffset expiry,
- bool isDownload)
- {
- var settings = await _settingsRepository.GetSettingAsync<StorageSettings>();
- if (settings is null)
- return null;
- var provider = GetProvider(settings);
- var storedFile = await _fileRepository.GetFile(fileId);
- return storedFile == null ? null : await provider.GetTemporaryFileUrl(baseUri, storedFile, settings, expiry, isDownload);
- }
-
public async Task RemoveFile(string fileId, string userId)
{
var settings = await _settingsRepository.GetSettingAsync<StorageSettings>();
diff --git a/BTCPayServer/Storage/Services/Providers/BaseTwentyTwentyStorageFileProviderServiceBase.cs b/BTCPayServer/Storage/Services/Providers/BaseTwentyTwentyStorageFileProviderServiceBase.cs
index 7a8c2e5..3b3cda5 100644
--- a/BTCPayServer/Storage/Services/Providers/BaseTwentyTwentyStorageFileProviderServiceBase.cs
+++ b/BTCPayServer/Storage/Services/Providers/BaseTwentyTwentyStorageFileProviderServiceBase.cs
@@ -46,25 +46,6 @@ namespace BTCPayServer.Storage.Services.Providers
return provider.GetBlobUrl(providerConfiguration.ContainerName, storedFile.StorageFileName);
}
- public virtual async Task<string> GetTemporaryFileUrl(Uri baseUri, StoredFile storedFile,
- StorageSettings configuration,
- DateTimeOffset expiry, bool isDownload, BlobUrlAccess access = BlobUrlAccess.Read)
- {
- var providerConfiguration = GetProviderConfiguration(configuration);
- var provider = await GetStorageProvider(providerConfiguration);
- if (isDownload)
- {
- var descriptor =
- await provider.GetBlobDescriptorAsync(providerConfiguration.ContainerName,
- storedFile.StorageFileName);
- return provider.GetBlobSasUrl(providerConfiguration.ContainerName, storedFile.StorageFileName, expiry,
- true, storedFile.FileName, descriptor.ContentType, access);
- }
-
- return provider.GetBlobSasUrl(providerConfiguration.ContainerName, storedFile.StorageFileName, expiry,
- false, null, null, access);
- }
-
public async Task RemoveFile(StoredFile storedFile, StorageSettings configuration)
{
var providerConfiguration = GetProviderConfiguration(configuration);
diff --git a/BTCPayServer/Storage/Services/Providers/FileSystemStorage/FileSystemFileProviderService.cs b/BTCPayServer/Storage/Services/Providers/FileSystemStorage/FileSystemFileProviderService.cs
index 54dc318..702da84 100644
--- a/BTCPayServer/Storage/Services/Providers/FileSystemStorage/FileSystemFileProviderService.cs
+++ b/BTCPayServer/Storage/Services/Providers/FileSystemStorage/FileSystemFileProviderService.cs
@@ -46,30 +46,5 @@ namespace BTCPayServer.Storage.Services.Providers.FileSystemStorage
r = r.Replace(Path.DirectorySeparatorChar, '/');
return r;
}
-
- public override async Task<string> GetTemporaryFileUrl(Uri baseUri, StoredFile storedFile,
- StorageSettings configuration, DateTimeOffset expiry, bool isDownload,
- BlobUrlAccess access = BlobUrlAccess.Read)
- {
-
- var localFileDescriptor = new TemporaryLocalFileDescriptor
- {
- Expiry = expiry,
- FileId = storedFile.Id,
- IsDownload = isDownload
- };
- var name = Guid.NewGuid().ToString();
- var fullPath = Path.Combine(_datadirs.Value.TempStorageDir, name);
- var fileInfo = new FileInfo(fullPath);
- if (!fileInfo.Exists)
- {
- fileInfo.Directory?.Create();
- await File.Create(fileInfo.FullName).DisposeAsync();
- }
-
- await File.WriteAllTextAsync(Path.Combine(_datadirs.Value.TempStorageDir, name), JsonConvert.SerializeObject(localFileDescriptor));
-
- return new Uri(baseUri, $"{LocalStorageDirectoryName}tmp/{name}{(isDownload ? "?download" : string.Empty)}").AbsoluteUri;
- }
}
}
diff --git a/BTCPayServer/Storage/Services/Providers/FileSystemStorage/TemporaryLocalFileController.cs b/BTCPayServer/Storage/Services/Providers/FileSystemStorage/TemporaryLocalFileController.cs
deleted file mode 100644
index 4869eb8..0000000
--- a/BTCPayServer/Storage/Services/Providers/FileSystemStorage/TemporaryLocalFileController.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-using System;
-using System.IO;
-using System.Net.Mime;
-using System.Threading.Tasks;
-using BTCPayServer.Configuration;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Options;
-using Newtonsoft.Json;
-
-namespace BTCPayServer.Storage.Services.Providers.FileSystemStorage;
-
-public class TemporaryLocalFileController : Controller
-{
- private readonly StoredFileRepository _storedFileRepository;
- private readonly IOptions<DataDirectories> _dataDirectories;
-
- public TemporaryLocalFileController(StoredFileRepository storedFileRepository,
- IOptions<DataDirectories> dataDirectories)
- {
- _storedFileRepository = storedFileRepository;
- _dataDirectories = dataDirectories;
- }
-
- [HttpGet($"~/{FileSystemFileProviderService.LocalStorageDirectoryName}tmp/{{tmpFileId}}")]
- public async Task<IActionResult> GetTmpLocalFile(string tmpFileId)
- {
- var path = Path.Combine(_dataDirectories.Value.TempStorageDir, tmpFileId);
-
- if (!System.IO.File.Exists(path))
- {
- return NotFound();
- }
-
- var text = await System.IO.File.ReadAllTextAsync(path);
- var descriptor = JsonConvert.DeserializeObject<TemporaryLocalFileDescriptor>(text);
- if (descriptor.Expiry < DateTime.UtcNow)
- {
- System.IO.File.Delete(path);
- return NotFound();
- }
-
- var storedFile = _storedFileRepository.GetFile(descriptor.FileId).GetAwaiter().GetResult();
-
- ControllerContext.HttpContext.Response.Headers["Content-Disposition"] =
- ControllerContext.HttpContext.Request.Query.ContainsKey("download") ? "attachment" : "inline";
- ControllerContext.HttpContext.Response.Headers["Content-Security-Policy"] = "script-src ;";
- ControllerContext.HttpContext.Response.Headers["X-Content-Type-Options"] = "nosniff";
- path = Path.Combine(_dataDirectories.Value.StorageDir, storedFile.StorageFileName);
- var fileContent = await System.IO.File.ReadAllBytesAsync(path);
- return File(fileContent, MediaTypeNames.Application.Octet, storedFile.FileName);
- }
-}
diff --git a/BTCPayServer/Storage/Services/Providers/FileSystemStorage/TemporaryLocalFileDescriptor.cs b/BTCPayServer/Storage/Services/Providers/FileSystemStorage/TemporaryLocalFileDescriptor.cs
deleted file mode 100644
index f9e3d9a..0000000
--- a/BTCPayServer/Storage/Services/Providers/FileSystemStorage/TemporaryLocalFileDescriptor.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using System;
-
-namespace BTCPayServer.Storage.Services.Providers.FileSystemStorage
-{
- public class TemporaryLocalFileDescriptor
- {
- public string FileId { get; set; }
- public bool IsDownload { get; set; }
- public DateTimeOffset Expiry { get; set; }
- }
-}
diff --git a/BTCPayServer/Storage/Services/Providers/IStorageProviderService.cs b/BTCPayServer/Storage/Services/Providers/IStorageProviderService.cs
index 2933312..878b689 100644
--- a/BTCPayServer/Storage/Services/Providers/IStorageProviderService.cs
+++ b/BTCPayServer/Storage/Services/Providers/IStorageProviderService.cs
@@ -13,8 +13,6 @@ namespace BTCPayServer.Storage.Services.Providers
Task<StoredFile> AddFile(IFormFile formFile, StorageSettings configuration);
Task RemoveFile(StoredFile storedFile, StorageSettings configuration);
Task<string> GetFileUrl(Uri baseUri, StoredFile storedFile, StorageSettings configuration);
- Task<string> GetTemporaryFileUrl(Uri baseUri, StoredFile storedFile, StorageSettings configuration,
- DateTimeOffset expiry, bool isDownload, BlobUrlAccess access = BlobUrlAccess.Read);
StorageProvider StorageProvider();
}
}
diff --git a/BTCPayServer/Views/UIForms/Modify.cshtml b/BTCPayServer/Views/UIForms/Modify.cshtml
index cf89e77..9fd466c 100644
--- a/BTCPayServer/Views/UIForms/Modify.cshtml
+++ b/BTCPayServer/Views/UIForms/Modify.cshtml
@@ -6,7 +6,7 @@
var storeId = Context.GetCurrentStoreId();
var formId = Context.GetRouteValue("id");
var isNew = formId is null;
- ViewData.SetActivePage(StoreNavPages.Forms, isNew ? StringLocalizer["Create Form"] : StringLocalizer["Edit Form"], Model.Name);
+ ViewData.SetLayoutModel(new LayoutModel(nameof(StoreNavPages.Forms), isNew ? StringLocalizer["Create Form"] : StringLocalizer["Edit Form"]).SetCategory(WellKnownCategories.Store));
}
@section PageHeadContent {
diff --git a/BTCPayServer/Views/UIServer/CreateTemporaryFileUrl.cshtml b/BTCPayServer/Views/UIServer/CreateTemporaryFileUrl.cshtml
deleted file mode 100644
index cdf0164..0000000
--- a/BTCPayServer/Views/UIServer/CreateTemporaryFileUrl.cshtml
+++ /dev/null
@@ -1,42 +0,0 @@
-@using BTCPayServer.Controllers
-@model BTCPayServer.Controllers.UIServerController.CreateTemporaryFileUrlViewModel
-@{
- ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Services), StringLocalizer["Create temporary file link"])
- .SetCategory(WellKnownCategories.Server));
-}
-
-<form method="post">
- <div class="sticky-header">
- <h2>@ViewData["Title"]</h2>
- <button id="page-primary" type="submit" class="btn btn-primary" name="command" value="Generate" text-translate="true">Generate</button>
- </div>
- <partial name="_StatusMessage" />
-
- <div class="row">
- <div class="col-xl-8 col-xxl-constrain">
- @if (!ViewContext.ModelState.IsValid)
- {
- <div asp-validation-summary="All"></div>
- }
- <div class="form-group form-check">
- <input type="checkbox" class="form-check-input" asp-for="IsDownload"/>
- <label asp-for="IsDownload" class="form-check-label"></label>
- <span asp-validation-for="IsDownload" class="text-danger"></span>
- </div>
- <div class="form-group">
- <label asp-for="TimeAmount" class="form-label"></label>
- <div class="input-group">
- <input type="number" inputmode="decimal" asp-for="TimeAmount" class="form-control">
- <select asp-for="TimeType" asp-items="@Html.GetEnumSelectList<UIServerController.CreateTemporaryFileUrlViewModel.TmpFileTimeType>()" class="form-select"></select>
- </div>
-
- <span asp-validation-for="TimeAmount" class="text-danger"></span>
- <span asp-validation-for="TimeType" class="text-danger"></span>
- </div>
- </div>
- </div>
-</form>
-
-@section PageFootContent {
- <partial name="_ValidationScriptsPartial" />
-}
Why this scored 29/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.