Use nicer urls, part2 (Fix #921)

This commit is contained in:
nicolas.dorier
2022-01-14 20:16:28 +09:00
parent 1fb582c35d
commit b9fdd54538
11 changed files with 54 additions and 21 deletions

View File

@@ -143,8 +143,8 @@ namespace BTCPayServer.Tests
s.Driver.AssertNoError(); s.Driver.AssertNoError();
Assert.Contains("/login", s.Driver.Url); Assert.Contains("/login", s.Driver.Url);
s.GoToUrl("/UIManage/Index"); s.GoToUrl("/account");
Assert.Contains("ReturnUrl=%2FUIManage%2FIndex", s.Driver.Url); Assert.Contains("ReturnUrl=%2Faccount", s.Driver.Url);
// We should be redirected to login // We should be redirected to login
//Same User Can Log Back In //Same User Can Log Back In
@@ -153,7 +153,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("LoginButton")).Click(); s.Driver.FindElement(By.Id("LoginButton")).Click();
// We should be redirected to invoice // We should be redirected to invoice
Assert.EndsWith("/UIManage/Index", s.Driver.Url); Assert.EndsWith("/account", s.Driver.Url);
// Should not be able to reach server settings // Should not be able to reach server settings
s.GoToUrl("/server/users"); s.GoToUrl("/server/users");

View File

@@ -44,7 +44,7 @@ namespace BTCPayServer.Controllers
DescriptionHtml = true, DescriptionHtml = true,
Description = $"Any application using the API key <strong>{key.Label ?? key.Id}<strong> will immediately lose access.", Description = $"Any application using the API key <strong>{key.Label ?? key.Id}<strong> will immediately lose access.",
Action = "Delete", Action = "Delete",
ActionUrl = Url.ActionLink(nameof(DeleteAPIKeyPost), values: new { id }) ActionName = nameof(DeleteAPIKeyPost)
}); });
} }

View File

@@ -13,7 +13,7 @@ namespace BTCPayServer.Controllers
{ {
public partial class UIManageController public partial class UIManageController
{ {
[HttpGet("notifications")] [HttpGet("/notifications/settings")]
public async Task<IActionResult> NotificationSettings([FromServices] IEnumerable<INotificationHandler> notificationHandlers) public async Task<IActionResult> NotificationSettings([FromServices] IEnumerable<INotificationHandler> notificationHandlers)
{ {
var user = await _userManager.GetUserAsync(User); var user = await _userManager.GetUserAsync(User);
@@ -32,7 +32,7 @@ namespace BTCPayServer.Controllers
return View(new NotificationSettingsViewModel() { DisabledNotifications = notifications }); return View(new NotificationSettingsViewModel() { DisabledNotifications = notifications });
} }
[HttpPost("notifications")] [HttpPost("/notifications/settings")]
public async Task<IActionResult> NotificationSettings(NotificationSettingsViewModel vm, string command) public async Task<IActionResult> NotificationSettings(NotificationSettingsViewModel vm, string command)
{ {
var user = await _userManager.GetUserAsync(User); var user = await _userManager.GetUserAsync(User);

View File

@@ -23,7 +23,7 @@ namespace BTCPayServer.Controllers
{ {
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanViewProfile)] [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanViewProfile)]
[Route("[controller]/[action]")] [Route("account/{action:lowercase=Index}")]
public partial class UIManageController : Controller public partial class UIManageController : Controller
{ {
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;

View File

@@ -20,7 +20,7 @@ namespace BTCPayServer.Controllers
{ {
[BitpayAPIConstraint(false)] [BitpayAPIConstraint(false)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanViewNotificationsForUser)] [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanViewNotificationsForUser)]
[Route("[controller]/[action]")] [Route("notifications/{action:lowercase=Index}")]
public class UINotificationsController : Controller public class UINotificationsController : Controller
{ {
private readonly BTCPayServerEnvironment _env; private readonly BTCPayServerEnvironment _env;

View File

@@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
namespace BTCPayServer.Hosting
{
public class LowercaseTransformer : IOutboundParameterTransformer
{
public static void Register(IServiceCollection services)
{
services.AddRouting(opts =>
{
opts.ConstraintMap["lowercase"] = typeof(LowercaseTransformer);
});
}
public string TransformOutbound(object value)
{
if (value is not string str)
return null;
return str.ToLowerInvariant();
}
}
}

View File

@@ -141,6 +141,8 @@ namespace BTCPayServer.Hosting
.AddRazorRuntimeCompilation() .AddRazorRuntimeCompilation()
.AddPlugins(services, Configuration, LoggerFactory) .AddPlugins(services, Configuration, LoggerFactory)
.AddControllersAsServices(); .AddControllersAsServices();
LowercaseTransformer.Register(services);
ValidateControllerNameTransformer.Register(services); ValidateControllerNameTransformer.Register(services);
services.TryAddScoped<ContentSecurityPolicies>(); services.TryAddScoped<ContentSecurityPolicies>();
@@ -270,7 +272,7 @@ namespace BTCPayServer.Hosting
PaymentRequestHub.Register(endpoints); PaymentRequestHub.Register(endpoints);
endpoints.MapRazorPages(); endpoints.MapRazorPages();
endpoints.MapControllers(); endpoints.MapControllers();
endpoints.MapControllerRoute("default", "{controller:validate=UIHome}/{action=Index}/{id?}"); endpoints.MapControllerRoute("default", "{controller:validate=UIHome}/{action:lowercase=Index}/{id?}");
}); });
app.UsePlugins(); app.UsePlugins();
} }

View File

@@ -8,26 +8,27 @@ namespace BTCPayServer.Models
public ConfirmModel() { } public ConfirmModel() { }
public ConfirmModel(string title, string desc, string action = null, string buttonClass = ButtonClassDefault, string actionUrl = null) public ConfirmModel(string title, string desc, string action = null, string buttonClass = ButtonClassDefault, string actionName = null, string controllerName = null)
{ {
Title = title; Title = title;
Description = desc; Description = desc;
Action = action; Action = action;
ActionName = actionName;
ControllerName = controllerName;
ButtonClass = buttonClass; ButtonClass = buttonClass;
if (Description.Contains("<strong>", StringComparison.InvariantCultureIgnoreCase)) if (Description.Contains("<strong>", StringComparison.InvariantCultureIgnoreCase))
{ {
DescriptionHtml = true; DescriptionHtml = true;
} }
ActionUrl = actionUrl;
} }
public string Title { get; set; } public string Title { get; set; }
public string Description { get; set; } public string Description { get; set; }
public bool DescriptionHtml { get; set; } public bool DescriptionHtml { get; set; }
public string Action { get; set; } public string Action { get; set; }
public string ActionName { get; set; }
public string ControllerName { get; set; }
public string ButtonClass { get; set; } = ButtonClassDefault; public string ButtonClass { get; set; } = ButtonClassDefault;
public string ActionUrl { get; set; }
} }
} }

View File

@@ -1,5 +1,13 @@
@model ConfirmModel @model ConfirmModel
@inject LinkGenerator linkGenerator
@{
string actionUrl = null;
if (Model.ActionName is not null)
{
var controllerName = Model.ControllerName ?? ((Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)this.Url.ActionContext.ActionDescriptor).ControllerName;
actionUrl = linkGenerator.GetPathByAction(Model.ActionName, controllerName, pathBase: this.Context.Request.PathBase);
}
}
<div class="modal-dialog modal-dialog-centered"> <div class="modal-dialog modal-dialog-centered">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@@ -24,7 +32,7 @@
@if (!string.IsNullOrEmpty(Model.Action)) @if (!string.IsNullOrEmpty(Model.Action))
{ {
<form id="ConfirmForm" method="post" action="@Model.ActionUrl" rel="noreferrer noopener"> <form id="ConfirmForm" method="post" action="@actionUrl" rel="noreferrer noopener">
<div class="modal-body pt-0" id="ConfirmText" hidden> <div class="modal-body pt-0" id="ConfirmText" hidden>
<label for="ConfirmInput" class="form-label">Confirm the action by typing <strong id="ConfirmInputText"></strong>:</label> <label for="ConfirmInput" class="form-label">Confirm the action by typing <strong id="ConfirmInputText"></strong>:</label>
<input id="ConfirmInput" class="form-control"/> <input id="ConfirmInput" class="form-control"/>

View File

@@ -1,5 +1,4 @@
@model ConfirmModel @model ConfirmModel
<div class="modal fade" id="ConfirmModal" tabindex="-1" aria-labelledby="ConfirmTitle" aria-hidden="true"> <div class="modal fade" id="ConfirmModal" tabindex="-1" aria-labelledby="ConfirmTitle" aria-hidden="true">
<partial name="ConfirmModal" model="Model" /> <partial name="ConfirmModal" model="Model" />
</div> </div>
@@ -19,8 +18,8 @@
const action = $target.dataset.action || ($target.nodeName === 'A' const action = $target.dataset.action || ($target.nodeName === 'A'
? $target.getAttribute('href') ? $target.getAttribute('href')
: $target.form.getAttribute('action')) : $target.form.getAttribute('action'))
if ($form) $form.setAttribute('action', action) if ($form && !$form.hasAttribute('action')) $form.setAttribute('action', action)
if (title) $title.textContent = title if (title) $title.textContent = title
if (description) $description.innerHTML = description if (description) $description.innerHTML = description
if (confirm) $continue.textContent = confirm if (confirm) $continue.textContent = confirm

View File

@@ -37,12 +37,12 @@
<button type="submit" id="save" class="btn btn-primary">Save</button> <button type="submit" id="save" class="btn btn-primary">Save</button>
<h4 class="mt-5 mb-3">Delete Account</h4> <h4 class="mt-5 mb-3">Delete Account</h4>
<div id="danger-zone"> <div id="danger-zone">
<a id="delete-user" class="btn btn-outline-danger mb-5" data-confirm-input="DELETE" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-action="DeleteUserPost" data-description="This action will also delete all stores, invoices, apps and data associated with the user.">Delete Account</a> <a id="delete-user" class="btn btn-outline-danger mb-5" data-confirm-input="DELETE" data-bs-toggle="modal" data-bs-target="#ConfirmModal" asp-action="DeleteUserPost" data-description="This action will also delete all stores, invoices, apps and data associated with the user.">Delete Account</a>
</div> </div>
</form> </form>
<partial name="_Confirm" <partial name="_Confirm"
model="@(new ConfirmModel("Delete user", "The user will be permanently deleted. This action will also delete all stores, invoices, apps and data associated with your user.", "Delete", actionUrl:"DeleteUserPost"))"/> model="@(new ConfirmModel("Delete user", "The user will be permanently deleted. This action will also delete all stores, invoices, apps and data associated with your user.", "Delete", actionName: nameof(BTCPayServer.Controllers.UIManageController.DeleteUserPost)))"/>
@section PageFootContent { @section PageFootContent {
<partial name="_ValidationScriptsPartial"/> <partial name="_ValidationScriptsPartial"/>