diff --git a/BTCPayServer/Controllers/ManageController.2FA.cs b/BTCPayServer/Controllers/ManageController.2FA.cs index 753d0e3fe..7e9e81283 100644 --- a/BTCPayServer/Controllers/ManageController.2FA.cs +++ b/BTCPayServer/Controllers/ManageController.2FA.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using BTCPayServer.Data; +using BTCPayServer.Models; using BTCPayServer.Models.ManageViewModels; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -26,7 +27,6 @@ namespace BTCPayServer.Controllers var model = new TwoFactorAuthenticationViewModel { - HasAuthenticator = await _userManager.GetAuthenticatorKeyAsync(user) != null, Is2faEnabled = user.TwoFactorEnabled, RecoveryCodesLeft = await _userManager.CountRecoveryCodesAsync(user), }; @@ -49,11 +49,19 @@ namespace BTCPayServer.Controllers $"Unexpected error occurred disabling 2FA for user with ID '{user.Id}'."); } - return View(nameof(Disable2fa)); + return View("Confirm", + new ConfirmModel() + { + Title = $"Disable two-factor authentication (2FA)", + DescriptionHtml = true, + Description = + $"Disabling 2FA does not change the keys used in authenticator apps. If you wish to change the key used in an authenticator app you should reset your authenticator keys.", + Action = "Disable 2FA", + ActionUrl = Url.ActionLink(nameof(Disable2fa)) + }); } [HttpPost] - [ValidateAntiForgeryToken] public async Task Disable2fa() { var user = await _userManager.GetUserAsync(User); @@ -105,7 +113,8 @@ namespace BTCPayServer.Controllers } // Strip spaces and hypens - var verificationCode = model.Code.Replace(" ", string.Empty, StringComparison.OrdinalIgnoreCase).Replace("-", string.Empty, StringComparison.OrdinalIgnoreCase); + var verificationCode = model.Code.Replace(" ", string.Empty, StringComparison.OrdinalIgnoreCase) + .Replace("-", string.Empty, StringComparison.OrdinalIgnoreCase); var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync( user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode); @@ -122,17 +131,24 @@ namespace BTCPayServer.Controllers var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); TempData[RecoveryCodesKey] = recoveryCodes.ToArray(); - return RedirectToAction(nameof(GenerateRecoveryCodes)); + return RedirectToAction(nameof(GenerateRecoveryCodes), new {confirm = false}); } [HttpGet] public IActionResult ResetAuthenticatorWarning() { - return View(nameof(ResetAuthenticator)); + return View("Confirm", + new ConfirmModel() + { + Title = $"Reset authenticator key", + Description = + $"This process disables 2FA until you verify your authenticator app and will also reset your 2FA recovery codes.{Environment.NewLine}If you do not complete your authenticator app configuration you may lose access to your account.", + Action = "Reset", + ActionUrl = Url.ActionLink(nameof(ResetAuthenticator)) + }); } [HttpPost] - [ValidateAntiForgeryToken] public async Task ResetAuthenticator() { var user = await _userManager.GetUserAsync(User); @@ -149,15 +165,39 @@ namespace BTCPayServer.Controllers } [HttpGet] - public IActionResult GenerateRecoveryCodes() + public async Task GenerateRecoveryCodes(bool confirm = true) + { + if (!confirm) + { + return await GenerateRecoveryCodes(); + } + + return View("Confirm", + new ConfirmModel() + { + Title = $"Are you sure you want to generate new recovery codes?", + Description = "Your existing recovery codes will no longer be valid!", + Action = "Generate", + ActionUrl = Url.ActionLink(nameof(GenerateRecoveryCodes)) + }); + } + + [HttpPost] + public async Task GenerateRecoveryCodes() { var recoveryCodes = (string[])TempData[RecoveryCodesKey]; if (recoveryCodes == null) { - return RedirectToAction(nameof(TwoFactorAuthentication)); + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + recoveryCodes = (await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10)).ToArray(); } - var model = new GenerateRecoveryCodesViewModel { RecoveryCodes = recoveryCodes }; + var model = new GenerateRecoveryCodesViewModel {RecoveryCodes = recoveryCodes}; return View(model); } diff --git a/BTCPayServer/Models/ConfirmModel.cs b/BTCPayServer/Models/ConfirmModel.cs index e4834ca53..34fa0ab4b 100644 --- a/BTCPayServer/Models/ConfirmModel.cs +++ b/BTCPayServer/Models/ConfirmModel.cs @@ -19,6 +19,9 @@ namespace BTCPayServer.Models { get; set; } + + public bool DescriptionHtml { get; set; } = false; + public string Action { get; set; diff --git a/BTCPayServer/Models/ManageViewModels/TwoFactorAuthenticationViewModel.cs b/BTCPayServer/Models/ManageViewModels/TwoFactorAuthenticationViewModel.cs index a69823ce3..13bbb8ccd 100644 --- a/BTCPayServer/Models/ManageViewModels/TwoFactorAuthenticationViewModel.cs +++ b/BTCPayServer/Models/ManageViewModels/TwoFactorAuthenticationViewModel.cs @@ -2,7 +2,6 @@ namespace BTCPayServer.Models.ManageViewModels { public class TwoFactorAuthenticationViewModel { - public bool HasAuthenticator { get; set; } public int RecoveryCodesLeft { get; set; } diff --git a/BTCPayServer/Views/Manage/Disable2fa.cshtml b/BTCPayServer/Views/Manage/Disable2fa.cshtml deleted file mode 100644 index 0e6db3466..000000000 --- a/BTCPayServer/Views/Manage/Disable2fa.cshtml +++ /dev/null @@ -1,23 +0,0 @@ -@{ - ViewData.SetActivePageAndTitle(ManageNavPages.TwoFactorAuthentication, "Disable two-factor authentication (2FA)"); -} - - - -
-
- -
-
diff --git a/BTCPayServer/Views/Manage/GenerateRecoveryCodes.cshtml b/BTCPayServer/Views/Manage/GenerateRecoveryCodes.cshtml index 0e1b83dc1..0f3cd1c43 100644 --- a/BTCPayServer/Views/Manage/GenerateRecoveryCodes.cshtml +++ b/BTCPayServer/Views/Manage/GenerateRecoveryCodes.cshtml @@ -22,6 +22,6 @@ diff --git a/BTCPayServer/Views/Manage/ResetAuthenticator.cshtml b/BTCPayServer/Views/Manage/ResetAuthenticator.cshtml deleted file mode 100644 index 027811e12..000000000 --- a/BTCPayServer/Views/Manage/ResetAuthenticator.cshtml +++ /dev/null @@ -1,21 +0,0 @@ -@{ - ViewData.SetActivePageAndTitle(ManageNavPages.TwoFactorAuthentication, "Reset authenticator key"); -} - - -
-
- -
-
diff --git a/BTCPayServer/Views/Manage/TwoFactorAuthentication.cshtml b/BTCPayServer/Views/Manage/TwoFactorAuthentication.cshtml index 53bb2567e..bb4baf202 100644 --- a/BTCPayServer/Views/Manage/TwoFactorAuthentication.cshtml +++ b/BTCPayServer/Views/Manage/TwoFactorAuthentication.cshtml @@ -20,7 +20,7 @@

- You have 1 recovery code left. + You only have 1 recovery code left.

You can generate a new set of recovery codes.

@@ -30,31 +30,60 @@

- You have @Model.RecoveryCodesLeft recovery codes left. + You only have @Model.RecoveryCodesLeft recovery codes left.

You should generate a new set of recovery codes.

} -
Your current 2FA
- } -
Authenticator app
- + @if (Model.Is2faEnabled) + { + +
+
Disable 2FA
+

Disable two-factor authentication. Re-enabling will not require you to reconfigure your Authenticator app.

+
+ +
+ +
+
Reset recovery codes
+

Regenerate your two-factor recovery codes.

+
+ +
+ + +
+
Configure Authenticator app
+

Display the key or QR code to configure an authenticator app with your current setup.

+
+ +
+ +
+
Reset Authenticator app
+

Invalidates the current authenticator configuration. Useful if you believe your authenticator settings were compromised.

+
+ +
+ } + else + { + +
+
Enable 2FA
+

Enable two-factor authentication using TOTP with apps such as Google Authenticator.

+
+ +
+ } + + @section Scripts { @await Html.PartialAsync("_ValidationScriptsPartial") diff --git a/BTCPayServer/Views/Shared/Confirm.cshtml b/BTCPayServer/Views/Shared/Confirm.cshtml index d44105ae3..15f28f564 100644 --- a/BTCPayServer/Views/Shared/Confirm.cshtml +++ b/BTCPayServer/Views/Shared/Confirm.cshtml @@ -19,7 +19,16 @@