diff --git a/build/dependencies.props b/build/dependencies.props index eead70ae75..4b6a766d64 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -4,62 +4,62 @@ 2.1.0-preview1-15651 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 2.2.1 2.3.2 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 - 2.1.0-preview1-27965 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 + 2.1.0-preview1-28061 3.14.2 5.2.0-preview2-41113220915 2.0.0 2.1.0-preview1-26016-05 15.3.0 3.0.1 - 2.1.0-preview1-27965 + 2.1.0-preview1-28061 4.7.49 4.5.0-preview1-26016-05 0.8.0 diff --git a/src/UI/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs index ee507fc299..51ceaa5491 100644 --- a/src/UI/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs @@ -3,6 +3,7 @@ using System; using System.ComponentModel.DataAnnotations; +using System.Linq; using System.Text; using System.Text.Encodings.Web; using System.Threading.Tasks; @@ -34,6 +35,9 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage public string AuthenticatorUri { get; set; } + [TempData] + public string[] RecoveryCodes { get; set; } + [BindProperty] public InputModel Input { get; set; } @@ -88,7 +92,10 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage await _userManager.SetTwoFactorEnabledAsync(user, true); _logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", user.Id); - return RedirectToPage("./GenerateRecoveryCodes"); + + var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); + RecoveryCodes = recoveryCodes.ToArray(); + return RedirectToPage("./ShowRecoveryCodes"); } private async Task LoadSharedKeyAndQrCodeUriAsync(IdentityUser user) diff --git a/src/UI/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml b/src/UI/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml index d05825429f..5966480d05 100644 --- a/src/UI/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml +++ b/src/UI/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml @@ -1,7 +1,7 @@ @page @model GenerateRecoveryCodesModel @{ - ViewData["Title"] = "Recovery codes"; + ViewData["Title"] = "Generate two-factor authentication (2FA) recovery codes"; ViewData["ActivePage"] = "TwoFactorAuthentication"; } @@ -14,12 +14,13 @@

If you lose your device and don't have the recovery codes you will lose access to your account.

+

+ Generating new recovery codes 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. +

+ +
+
+ +
-
-
- @for (var row = 0; row < Model.RecoveryCodes.Count(); row += 2) - { - @Model.RecoveryCodes[row] @Model.RecoveryCodes[row + 1]
- } -
-
\ No newline at end of file diff --git a/src/UI/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs index bd30a163f9..4ff6551ca0 100644 --- a/src/UI/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs +++ b/src/UI/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs @@ -23,6 +23,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage _logger = logger; } + [TempData] public string[] RecoveryCodes { get; set; } public async Task OnGetAsync() @@ -33,6 +34,22 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); } + if (!user.TwoFactorEnabled) + { + throw new InvalidOperationException($"Cannot generate recovery codes for user with ID '{user.Id}' because they do not have 2FA enabled."); + } + + return Page(); + } + + public async Task OnPostAsync() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + if (!user.TwoFactorEnabled) { throw new InvalidOperationException($"Cannot generate recovery codes for user with ID '{user.Id}' as they do not have 2FA enabled."); @@ -43,7 +60,7 @@ namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage _logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", user.Id); - return Page(); + return RedirectToPage("./ShowRecoveryCodes"); } } -} \ No newline at end of file +} diff --git a/src/UI/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml b/src/UI/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml new file mode 100644 index 0000000000..b3e520b0fc --- /dev/null +++ b/src/UI/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml @@ -0,0 +1,25 @@ +@page +@model ShowRecoveryCodesModel +@{ + ViewData["Title"] = "Recovery codes"; + ViewData["ActivePage"] = "TwoFactorAuthentication"; +} + +

@ViewData["Title"]

+ +
+
+ @for (var row = 0; row < Model.RecoveryCodes.Length; row += 2) + { + @Model.RecoveryCodes[row] @Model.RecoveryCodes[row + 1]
+ } +
+
diff --git a/src/UI/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml.cs b/src/UI/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml.cs new file mode 100644 index 0000000000..c865922898 --- /dev/null +++ b/src/UI/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml.cs @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage +{ + public class ShowRecoveryCodesModel : PageModel + { + [TempData] + public string[] RecoveryCodes { get; set; } + + public IActionResult OnGet() + { + if (RecoveryCodes == null || RecoveryCodes.Length == 0) + { + return RedirectToPage("./TwoFactorAuthentication"); + } + + return Page(); + } + } +}