diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 9f8cda396..6299c721a 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -34,7 +34,7 @@ - + @@ -47,7 +47,7 @@ - + diff --git a/BTCPayServer/Controllers/AccountController.cs b/BTCPayServer/Controllers/AccountController.cs index a335fe5e9..a8e2fbfd1 100644 --- a/BTCPayServer/Controllers/AccountController.cs +++ b/BTCPayServer/Controllers/AccountController.cs @@ -183,7 +183,7 @@ namespace BTCPayServer.Controllers { Version = u2fChallenge[0].version, Challenge = u2fChallenge[0].challenge, - Challenges = JsonConvert.SerializeObject(u2fChallenge), + Challenges = u2fChallenge, AppId = u2fChallenge[0].appId, UserId = user.Id, RememberMe = rememberMe diff --git a/BTCPayServer/Controllers/AppsController.Crowdfund.cs b/BTCPayServer/Controllers/AppsController.Crowdfund.cs index 9c742177d..15e78530f 100644 --- a/BTCPayServer/Controllers/AppsController.Crowdfund.cs +++ b/BTCPayServer/Controllers/AppsController.Crowdfund.cs @@ -132,7 +132,7 @@ namespace BTCPayServer.Controllers EnforceTargetAmount = vm.EnforceTargetAmount, StartDate = vm.StartDate?.ToUniversalTime(), TargetCurrency = vm.TargetCurrency, - Description = _htmlSanitizer.Sanitize( vm.Description), + Description = vm.Description, EndDate = vm.EndDate?.ToUniversalTime(), TargetAmount = vm.TargetAmount, CustomCSSLink = vm.CustomCSSLink, diff --git a/BTCPayServer/Controllers/AppsController.cs b/BTCPayServer/Controllers/AppsController.cs index e3da777cc..a72117a75 100644 --- a/BTCPayServer/Controllers/AppsController.cs +++ b/BTCPayServer/Controllers/AppsController.cs @@ -30,7 +30,6 @@ namespace BTCPayServer.Controllers EventAggregator eventAggregator, BTCPayNetworkProvider networkProvider, CurrencyNameTable currencies, - HtmlSanitizer htmlSanitizer, EmailSenderFactory emailSenderFactory, AppService AppService) { @@ -39,7 +38,6 @@ namespace BTCPayServer.Controllers _EventAggregator = eventAggregator; _NetworkProvider = networkProvider; _currencies = currencies; - _htmlSanitizer = htmlSanitizer; _emailSenderFactory = emailSenderFactory; _AppService = AppService; } @@ -49,7 +47,6 @@ namespace BTCPayServer.Controllers private readonly EventAggregator _EventAggregator; private BTCPayNetworkProvider _NetworkProvider; private readonly CurrencyNameTable _currencies; - private readonly HtmlSanitizer _htmlSanitizer; private readonly EmailSenderFactory _emailSenderFactory; private AppService _AppService; diff --git a/BTCPayServer/Controllers/ManageController.2FA.cs b/BTCPayServer/Controllers/ManageController.2FA.cs index effc013ee..b1f545a11 100644 --- a/BTCPayServer/Controllers/ManageController.2FA.cs +++ b/BTCPayServer/Controllers/ManageController.2FA.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; +using BTCPayServer.Models; using BTCPayServer.Models.ManageViewModels; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -11,6 +12,7 @@ namespace BTCPayServer.Controllers { public partial class ManageController { + private const string RecoveryCodesKey = nameof(RecoveryCodesKey); private const string AuthenicatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6"; [HttpGet] @@ -80,18 +82,8 @@ namespace BTCPayServer.Controllers throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); } - var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user); - if (string.IsNullOrEmpty(unformattedKey)) - { - await _userManager.ResetAuthenticatorKeyAsync(user); - unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user); - } - - var model = new EnableAuthenticatorViewModel - { - SharedKey = FormatKey(unformattedKey), - AuthenticatorUri = GenerateQrCodeUri(user.Email, unformattedKey) - }; + var model = new EnableAuthenticatorViewModel(); + await LoadSharedKeyAndQrCodeUriAsync(user, model); return View(model); } @@ -100,32 +92,36 @@ namespace BTCPayServer.Controllers [ValidateAntiForgeryToken] public async Task EnableAuthenticator(EnableAuthenticatorViewModel model) { - if (!ModelState.IsValid) - { - return View(model); - } - var user = await _userManager.GetUserAsync(User); if (user == null) { throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); } + if (!ModelState.IsValid) + { + await LoadSharedKeyAndQrCodeUriAsync(user, model); + return View(model); + } + // Strip spaces and hypens - var verificationCode = model.Code.Replace(" ", string.Empty, StringComparison.InvariantCulture) - .Replace("-", string.Empty, StringComparison.InvariantCulture); + var verificationCode = model.Code.Replace(" ", string.Empty).Replace("-", string.Empty); var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync( user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode); if (!is2faTokenValid) { - ModelState.AddModelError(nameof(model.Code), "Verification code is invalid."); + ModelState.AddModelError("Code", "Verification code is invalid."); + await LoadSharedKeyAndQrCodeUriAsync(user, model); return View(model); } await _userManager.SetTwoFactorEnabledAsync(user, true); _logger.LogInformation("User with ID {UserId} has enabled 2FA with an authenticator app.", user.Id); + var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); + TempData[RecoveryCodesKey] = recoveryCodes.ToArray(); + return RedirectToAction(nameof(GenerateRecoveryCodes)); } @@ -153,7 +149,20 @@ namespace BTCPayServer.Controllers } [HttpGet] - public async Task GenerateRecoveryCodes() + public IActionResult GenerateRecoveryCodes() + { + var recoveryCodes = (string[])TempData[RecoveryCodesKey]; + if (recoveryCodes == null) + { + return RedirectToAction(nameof(TwoFactorAuthentication)); + } + + var model = new GenerateRecoveryCodesViewModel {RecoveryCodes = recoveryCodes}; + return View(model); + } + + [HttpGet] + public async Task GenerateRecoveryCodesWarning() { var user = await _userManager.GetUserAsync(User); if (user == null) @@ -163,16 +172,10 @@ namespace BTCPayServer.Controllers if (!user.TwoFactorEnabled) { - throw new ApplicationException( - $"Cannot generate recovery codes for user with ID '{user.Id}' as they do not have 2FA enabled."); + throw new ApplicationException($"Cannot generate recovery codes for user with ID '{user.Id}' because they do not have 2FA enabled."); } - var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); - var model = new GenerateRecoveryCodesViewModel {RecoveryCodes = recoveryCodes.ToArray()}; - - _logger.LogInformation("User with ID {UserId} has generated new 2FA recovery codes.", user.Id); - - return View(model); + return View(nameof(GenerateRecoveryCodesWarning)); } private string GenerateQrCodeUri(string email, string unformattedKey) @@ -201,5 +204,19 @@ namespace BTCPayServer.Controllers return result.ToString().ToLowerInvariant(); } + + + private async Task LoadSharedKeyAndQrCodeUriAsync(ApplicationUser user, EnableAuthenticatorViewModel model) + { + var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user); + if (string.IsNullOrEmpty(unformattedKey)) + { + await _userManager.ResetAuthenticatorKeyAsync(user); + unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user); + } + + model.SharedKey = FormatKey(unformattedKey); + model.AuthenticatorUri = GenerateQrCodeUri(user.Email, unformattedKey); + } } } diff --git a/BTCPayServer/Controllers/PaymentRequestController.cs b/BTCPayServer/Controllers/PaymentRequestController.cs index 0b5db7312..b06dffc30 100644 --- a/BTCPayServer/Controllers/PaymentRequestController.cs +++ b/BTCPayServer/Controllers/PaymentRequestController.cs @@ -41,7 +41,6 @@ namespace BTCPayServer.Controllers private readonly PaymentRequestService _PaymentRequestService; private readonly EventAggregator _EventAggregator; private readonly CurrencyNameTable _Currencies; - private readonly HtmlSanitizer _htmlSanitizer; private readonly InvoiceRepository _InvoiceRepository; public PaymentRequestController( @@ -52,7 +51,6 @@ namespace BTCPayServer.Controllers PaymentRequestService paymentRequestService, EventAggregator eventAggregator, CurrencyNameTable currencies, - HtmlSanitizer htmlSanitizer, InvoiceRepository invoiceRepository) { _InvoiceController = invoiceController; @@ -62,7 +60,6 @@ namespace BTCPayServer.Controllers _PaymentRequestService = paymentRequestService; _EventAggregator = eventAggregator; _Currencies = currencies; - _htmlSanitizer = htmlSanitizer; _InvoiceRepository = invoiceRepository; } @@ -152,7 +149,7 @@ namespace BTCPayServer.Controllers blob.Title = viewModel.Title; blob.Email = viewModel.Email; - blob.Description = _htmlSanitizer.Sanitize(viewModel.Description); + blob.Description = viewModel.Description; blob.Amount = viewModel.Amount; blob.ExpiryDate = viewModel.ExpiryDate?.ToUniversalTime(); blob.Currency = viewModel.Currency; diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index d12641411..8768715bf 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -126,6 +126,7 @@ namespace BTCPayServer.Hosting }); services.TryAddSingleton(); + services.TryAddTransient(); services.TryAddSingleton(o => { diff --git a/BTCPayServer/Models/ManageViewModels/EnableAuthenticatorViewModel.cs b/BTCPayServer/Models/ManageViewModels/EnableAuthenticatorViewModel.cs index 9ba033259..7ace4349f 100644 --- a/BTCPayServer/Models/ManageViewModels/EnableAuthenticatorViewModel.cs +++ b/BTCPayServer/Models/ManageViewModels/EnableAuthenticatorViewModel.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; namespace BTCPayServer.Models.ManageViewModels { @@ -15,9 +16,10 @@ namespace BTCPayServer.Models.ManageViewModels [Display(Name = "Verification Code")] public string Code { get; set; } - [ReadOnly(true)] + [BindNever] public string SharedKey { get; set; } + [BindNever] public string AuthenticatorUri { get; set; } } } diff --git a/BTCPayServer/Services/Apps/AppService.cs b/BTCPayServer/Services/Apps/AppService.cs index f41a2366d..0d13aa1a7 100644 --- a/BTCPayServer/Services/Apps/AppService.cs +++ b/BTCPayServer/Services/Apps/AppService.cs @@ -283,10 +283,10 @@ namespace BTCPayServer.Services.Apps .Where(kv => kv.Value != null) .Select(c => new ViewPointOfSaleViewModel.Item() { - Description = _HtmlSanitizer.Sanitize(c.GetDetailString("description")), + Description = c.GetDetailString("description"), Id = c.Key, - Image = _HtmlSanitizer.Sanitize(c.GetDetailString("image")), - Title = _HtmlSanitizer.Sanitize(c.GetDetailString("title") ?? c.Key), + Image = c.GetDetailString("image"), + Title = c.GetDetailString("title") ?? c.Key, Price = c.GetDetail("price") .Select(cc => new ViewPointOfSaleViewModel.Item.ItemPrice() { diff --git a/BTCPayServer/Services/Safe.cs b/BTCPayServer/Services/Safe.cs new file mode 100644 index 000000000..169d2e961 --- /dev/null +++ b/BTCPayServer/Services/Safe.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ganss.XSS; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace BTCPayServer.Services +{ + public class Safe + { + private readonly IHtmlHelper _htmlHelper; + private readonly IJsonHelper _jsonHelper; + private readonly HtmlSanitizer _htmlSanitizer; + + public Safe(IHtmlHelper htmlHelper, IJsonHelper jsonHelper, HtmlSanitizer htmlSanitizer) + { + _htmlHelper = htmlHelper; + _jsonHelper = jsonHelper; + _htmlSanitizer = htmlSanitizer; + } + + public IHtmlContent Raw(string value) + { + return _htmlHelper.Raw(_htmlSanitizer.Sanitize(value)); + } + + public IHtmlContent Json(object model) + { + return _htmlHelper.Raw(_jsonHelper.Serialize(model)); + } + } +} diff --git a/BTCPayServer/U2F/Models/LoginWithU2FViewModel.cs b/BTCPayServer/U2F/Models/LoginWithU2FViewModel.cs index 33daa2efe..41dd915b3 100644 --- a/BTCPayServer/U2F/Models/LoginWithU2FViewModel.cs +++ b/BTCPayServer/U2F/Models/LoginWithU2FViewModel.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace BTCPayServer.Services.U2F.Models @@ -18,7 +19,7 @@ namespace BTCPayServer.Services.U2F.Models public string DeviceResponse { get; set; } [Display(Name = "Challenges")] - public string Challenges { get; set; } + public List Challenges { get; set; } [Display(Name = "Challenge")] public string Challenge { get; set; } diff --git a/BTCPayServer/Views/Account/LoginWithU2F.cshtml b/BTCPayServer/Views/Account/LoginWithU2F.cshtml index 2e7476467..c8202525c 100644 --- a/BTCPayServer/Views/Account/LoginWithU2F.cshtml +++ b/BTCPayServer/Views/Account/LoginWithU2F.cshtml @@ -37,9 +37,9 @@ }; setTimeout(function() { window.u2f.sign( - "@Model.AppId", - "@Model.Challenge", - @Html.Raw(@Model.Challenges), function (data) { + @Safe.Json(Model.AppId), + @Safe.Json(Model.Challenge), + @Safe.Json(Model.Challenges), function (data) { if (data.errorCode) { $("#error-response").text(errorMap[data.errorCode]); return; diff --git a/BTCPayServer/Views/AppsPublic/Crowdfund/ContributeForm.cshtml b/BTCPayServer/Views/AppsPublic/Crowdfund/ContributeForm.cshtml index ae576a126..978268372 100644 --- a/BTCPayServer/Views/AppsPublic/Crowdfund/ContributeForm.cshtml +++ b/BTCPayServer/Views/AppsPublic/Crowdfund/ContributeForm.cshtml @@ -30,17 +30,17 @@ @item.Price.Value if (item.Custom) { - Html.Raw("or more"); + Safe.Raw("or more"); } } else if (item.Custom) { - Html.Raw("Any amount"); + Safe.Raw("Any amount"); } -

@Html.Raw(item.Description)

+

@Safe.Raw(item.Description)

@if (Model.ViewCrowdfundViewModel.PerkCount.ContainsKey(item.Id)) diff --git a/BTCPayServer/Views/AppsPublic/Crowdfund/MinimalCrowdfund.cshtml b/BTCPayServer/Views/AppsPublic/Crowdfund/MinimalCrowdfund.cshtml index ed9bea259..1ccfa9d83 100644 --- a/BTCPayServer/Views/AppsPublic/Crowdfund/MinimalCrowdfund.cshtml +++ b/BTCPayServer/Views/AppsPublic/Crowdfund/MinimalCrowdfund.cshtml @@ -131,7 +131,7 @@
-
@Html.Raw(Model.Description)
+
@Safe.Raw(Model.Description)
- var srvModel = @Html.Raw(Json.Serialize(Model)); + var srvModel = @Safe.Json(Model); @@ -33,7 +33,7 @@ @if (!string.IsNullOrEmpty(Model.EmbeddedCSS)) { } diff --git a/BTCPayServer/Views/AppsPublic/ViewPointOfSale.cshtml b/BTCPayServer/Views/AppsPublic/ViewPointOfSale.cshtml index c8e63464d..dede86e3c 100644 --- a/BTCPayServer/Views/AppsPublic/ViewPointOfSale.cshtml +++ b/BTCPayServer/Views/AppsPublic/ViewPointOfSale.cshtml @@ -31,7 +31,7 @@ { } diff --git a/BTCPayServer/Views/Home/BitpayTranslator.cshtml b/BTCPayServer/Views/Home/BitpayTranslator.cshtml index 864ba8eee..98b25abd1 100644 --- a/BTCPayServer/Views/Home/BitpayTranslator.cshtml +++ b/BTCPayServer/Views/Home/BitpayTranslator.cshtml @@ -10,7 +10,7 @@

You need to pay @Model.Amount to @Model.Address

-
+

Open in wallet @@ -52,7 +52,7 @@ @@ -114,8 +114,8 @@

@@ -34,7 +34,7 @@ @if (!string.IsNullOrEmpty(Model.EmbeddedCSS)) { } diff --git a/BTCPayServer/Views/PublicLightningNodeInfo/ShowLightningNodeInfo.cshtml b/BTCPayServer/Views/PublicLightningNodeInfo/ShowLightningNodeInfo.cshtml index 5ace66639..2ae4e3042 100644 --- a/BTCPayServer/Views/PublicLightningNodeInfo/ShowLightningNodeInfo.cshtml +++ b/BTCPayServer/Views/PublicLightningNodeInfo/ShowLightningNodeInfo.cshtml @@ -23,7 +23,7 @@ + diff --git a/BTCPayServer/Views/Shared/_StatusMessage.cshtml b/BTCPayServer/Views/Shared/_StatusMessage.cshtml index 2d86ab5af..00e73787e 100644 --- a/BTCPayServer/Views/Shared/_StatusMessage.cshtml +++ b/BTCPayServer/Views/Shared/_StatusMessage.cshtml @@ -16,7 +16,7 @@ } @if (!string.IsNullOrEmpty(parsedModel.Html)) { - @Html.Raw(parsedModel.Html) + @Safe.Raw(parsedModel.Html) }
} diff --git a/BTCPayServer/Views/Stores/PayButton.cshtml b/BTCPayServer/Views/Stores/PayButton.cshtml index e3ab352e7..b68edc8e6 100644 --- a/BTCPayServer/Views/Stores/PayButton.cshtml +++ b/BTCPayServer/Views/Stores/PayButton.cshtml @@ -209,7 +209,7 @@ @section Scripts { + @await Html.PartialAsync("_ValidationScriptsPartial") } diff --git a/BTCPayServer/Views/_ViewImports.cshtml b/BTCPayServer/Views/_ViewImports.cshtml index 19eb1a735..5cc9d6a5b 100644 --- a/BTCPayServer/Views/_ViewImports.cshtml +++ b/BTCPayServer/Views/_ViewImports.cshtml @@ -6,4 +6,5 @@ @using BTCPayServer.Models.InvoicingModels @using BTCPayServer.Models.ManageViewModels @using BTCPayServer.Models.StoreViewModels +@inject BTCPayServer.Services.Safe Safe @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/BTCPayServer/wwwroot/locales/ja-JP.json b/BTCPayServer/wwwroot/locales/ja-JP.json index a721842a9..f75adab25 100644 --- a/BTCPayServer/wwwroot/locales/ja-JP.json +++ b/BTCPayServer/wwwroot/locales/ja-JP.json @@ -47,5 +47,5 @@ "Pay with CoinSwitch": "CoinSwitchでのお支払い", "Pay with Changelly": "Changellyでのお支払い", "Close": "閉じる", - "NotPaid_ExtraTransaction": "The invoice hasn't been paid in full. Please send another transaction to cover amount Due." + "NotPaid_ExtraTransaction": "請求金額の全額が支払われていません。未払い分の別のトランザクションをお送りください。" } \ No newline at end of file