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
No known key found for this signature in database
GPG key ID: 6618763EF09186FE
11 changed files with 54 additions and 21 deletions

View file

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

View file

@ -44,7 +44,7 @@ namespace BTCPayServer.Controllers
DescriptionHtml = true,
Description = $"Any application using the API key <strong>{key.Label ?? key.Id}<strong> will immediately lose access.",
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
{
[HttpGet("notifications")]
[HttpGet("/notifications/settings")]
public async Task<IActionResult> NotificationSettings([FromServices] IEnumerable<INotificationHandler> notificationHandlers)
{
var user = await _userManager.GetUserAsync(User);
@ -32,7 +32,7 @@ namespace BTCPayServer.Controllers
return View(new NotificationSettingsViewModel() { DisabledNotifications = notifications });
}
[HttpPost("notifications")]
[HttpPost("/notifications/settings")]
public async Task<IActionResult> NotificationSettings(NotificationSettingsViewModel vm, string command)
{
var user = await _userManager.GetUserAsync(User);

View file

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

View file

@ -20,7 +20,7 @@ namespace BTCPayServer.Controllers
{
[BitpayAPIConstraint(false)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanViewNotificationsForUser)]
[Route("[controller]/[action]")]
[Route("notifications/{action:lowercase=Index}")]
public class UINotificationsController : Controller
{
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()
.AddPlugins(services, Configuration, LoggerFactory)
.AddControllersAsServices();
LowercaseTransformer.Register(services);
ValidateControllerNameTransformer.Register(services);
services.TryAddScoped<ContentSecurityPolicies>();
@ -270,7 +272,7 @@ namespace BTCPayServer.Hosting
PaymentRequestHub.Register(endpoints);
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapControllerRoute("default", "{controller:validate=UIHome}/{action=Index}/{id?}");
endpoints.MapControllerRoute("default", "{controller:validate=UIHome}/{action:lowercase=Index}/{id?}");
});
app.UsePlugins();
}

View file

@ -8,26 +8,27 @@ namespace BTCPayServer.Models
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;
Description = desc;
Action = action;
ActionName = actionName;
ControllerName = controllerName;
ButtonClass = buttonClass;
if (Description.Contains("<strong>", StringComparison.InvariantCultureIgnoreCase))
{
DescriptionHtml = true;
}
ActionUrl = actionUrl;
}
public string Title { get; set; }
public string Description { get; set; }
public bool DescriptionHtml { 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 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-content">
<div class="modal-header">
@ -24,7 +32,7 @@
@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>
<label for="ConfirmInput" class="form-label">Confirm the action by typing <strong id="ConfirmInputText"></strong>:</label>
<input id="ConfirmInput" class="form-control"/>

View file

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

View file

@ -37,12 +37,12 @@
<button type="submit" id="save" class="btn btn-primary">Save</button>
<h4 class="mt-5 mb-3">Delete Account</h4>
<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>
</form>
<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 {
<partial name="_ValidationScriptsPartial"/>