Split showing and generating recovery codes
This commit is contained in:
parent
e4ecd070eb
commit
16c23b846e
|
|
@ -5,6 +5,6 @@
|
|||
}
|
||||
|
||||
<header>
|
||||
<h1 class="text-danger">ViewData["Title"]</h1>
|
||||
<h1 class="text-danger">@ViewData["Title"]</h1>
|
||||
<p class="text-danger">You do not have access to this resource.</p>
|
||||
</header>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ namespace Company.WebApplication1.Pages.Account.Manage
|
|||
private readonly ILogger<EnableAuthenticatorModel> _logger;
|
||||
private readonly UrlEncoder _urlEncoder;
|
||||
|
||||
private const string AuthenicatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
|
||||
private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
|
||||
|
||||
public EnableAuthenticatorModel(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
|
|
@ -90,7 +90,10 @@ namespace Company.WebApplication1.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);
|
||||
TempData["RecoveryCodes"] = recoveryCodes.ToArray();
|
||||
return RedirectToPage("./ShowRecoveryCodes");
|
||||
}
|
||||
|
||||
private async Task LoadSharedKeyAndQrCodeUriAsync(ApplicationUser user)
|
||||
|
|
@ -127,7 +130,7 @@ namespace Company.WebApplication1.Pages.Account.Manage
|
|||
private string GenerateQrCodeUri(string email, string unformattedKey)
|
||||
{
|
||||
return string.Format(
|
||||
AuthenicatorUriFormat,
|
||||
AuthenticatorUriFormat,
|
||||
_urlEncoder.Encode("Company.WebApplication1"),
|
||||
_urlEncoder.Encode(email),
|
||||
unformattedKey);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
@page
|
||||
@model GenerateRecoveryCodesModel
|
||||
@{
|
||||
ViewData["Title"] = "Recovery codes";
|
||||
ViewData["Title"] = "Generate two-factor authentication (2FA) recovery codes";
|
||||
ViewData["ActivePage"] = "TwoFactorAuthentication";
|
||||
}
|
||||
|
||||
|
|
@ -9,17 +9,19 @@
|
|||
<div class="alert alert-warning" role="alert">
|
||||
<p>
|
||||
<span class="glyphicon glyphicon-warning-sign"></span>
|
||||
<strong>Put these codes in a safe place.</strong>
|
||||
<strong>This action generates new recovery codes.</strong>
|
||||
</p>
|
||||
<p>
|
||||
If you lose your device and don't have the recovery codes you will lose access to your account.
|
||||
</p>
|
||||
<p>
|
||||
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 <a asp-action="ResetAuthenticatorWarning">reset your authenticator keys.</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<form asp-action="GenerateRecoveryCodes" method="post" class="form-group">
|
||||
<button class="btn btn-danger" type="submit">Generate Recovery Codes</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
@for (var row = 0; row < Model.RecoveryCodes.Count(); row += 2)
|
||||
{
|
||||
<code>@Model.RecoveryCodes[row]</code><text> </text><code>@Model.RecoveryCodes[row + 1]</code><br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -23,8 +23,6 @@ namespace Company.WebApplication1.Pages.Account.Manage
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
public string[] RecoveryCodes { get; set; }
|
||||
|
||||
public async Task<IActionResult> OnGetAsync()
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
|
|
@ -33,17 +31,33 @@ namespace Company.WebApplication1.Pages.Account.Manage
|
|||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
if (!user.TwoFactorEnabled)
|
||||
{
|
||||
throw new ApplicationException($"Cannot generate recovery codes for user with ID '{user.Id}' because they do not have 2FA enabled.");
|
||||
}
|
||||
|
||||
return Page();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync()
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
if (!user.TwoFactorEnabled)
|
||||
{
|
||||
throw new ApplicationException($"Cannot generate recovery codes for user with ID '{user.Id}' as they do not have 2FA enabled.");
|
||||
}
|
||||
|
||||
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
|
||||
RecoveryCodes = recoveryCodes.ToArray();
|
||||
TempData["RecoveryCodes"] = recoveryCodes.ToArray();
|
||||
|
||||
_logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", user.Id);
|
||||
|
||||
return Page();
|
||||
return RedirectToPage("./ShowRecoveryCodes");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
@page
|
||||
@model ShowRecoveryCodesModel
|
||||
@{
|
||||
ViewData["Title"] = "Recovery codes";
|
||||
ViewData["ActivePage"] = "TwoFactorAuthentication";
|
||||
}
|
||||
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<p>
|
||||
<span class="glyphicon glyphicon-warning-sign"></span>
|
||||
<strong>Put these codes in a safe place.</strong>
|
||||
</p>
|
||||
<p>
|
||||
If you lose your device and don't have the recovery codes you will lose access to your account.
|
||||
</p>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
@for (var row = 0; row < Model.RecoveryCodes.Length; row += 2)
|
||||
{
|
||||
<code>@Model.RecoveryCodes[row]</code><text> </text><code>@Model.RecoveryCodes[row + 1]</code><br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace Company.WebApplication1.Pages.Account.Manage
|
||||
{
|
||||
public class ShowRecoveryCodesModel : PageModel
|
||||
{
|
||||
public string[] RecoveryCodes { get; private set; }
|
||||
|
||||
public IActionResult OnGet()
|
||||
{
|
||||
RecoveryCodes = (string[])TempData["RecoveryCodes"];
|
||||
if (RecoveryCodes == null)
|
||||
{
|
||||
return RedirectToPage("TwoFactorAuthentication");
|
||||
}
|
||||
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,8 @@ namespace Company.WebApplication1.Controllers
|
|||
private readonly ILogger _logger;
|
||||
private readonly UrlEncoder _urlEncoder;
|
||||
|
||||
private const string AuthenicatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
|
||||
private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
|
||||
private const string RecoveryCodesKey = nameof(RecoveryCodesKey);
|
||||
|
||||
public ManageController(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
|
|
@ -408,7 +409,23 @@ namespace Company.WebApplication1.Controllers
|
|||
|
||||
await _userManager.SetTwoFactorEnabledAsync(user, true);
|
||||
_logger.LogInformation("User with ID {UserId} has enabled 2FA with an authenticator app.", user.Id);
|
||||
return RedirectToAction(nameof(GenerateRecoveryCodes));
|
||||
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
|
||||
TempData[RecoveryCodesKey] = recoveryCodes.ToArray();
|
||||
|
||||
return RedirectToAction(nameof(ShowRecoveryCodes));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult ShowRecoveryCodes()
|
||||
{
|
||||
var recoveryCodes = (string[])TempData[RecoveryCodesKey];
|
||||
if (recoveryCodes == null)
|
||||
{
|
||||
return RedirectToAction(nameof(TwoFactorAuthentication));
|
||||
}
|
||||
|
||||
var model = new ShowRecoveryCodesViewModel { RecoveryCodes = recoveryCodes };
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
|
|
@ -435,6 +452,24 @@ namespace Company.WebApplication1.Controllers
|
|||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GenerateRecoveryCodesWarning()
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
if (!user.TwoFactorEnabled)
|
||||
{
|
||||
throw new ApplicationException($"Cannot generate recovery codes for user with ID '{user.Id}' because they do not have 2FA enabled.");
|
||||
}
|
||||
|
||||
return View(nameof(GenerateRecoveryCodes));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> GenerateRecoveryCodes()
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
|
|
@ -449,11 +484,11 @@ namespace Company.WebApplication1.Controllers
|
|||
}
|
||||
|
||||
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);
|
||||
var model = new ShowRecoveryCodesViewModel { RecoveryCodes = recoveryCodes.ToArray() };
|
||||
|
||||
return View(nameof(ShowRecoveryCodes), model);
|
||||
}
|
||||
|
||||
#region Helpers
|
||||
|
|
@ -486,7 +521,7 @@ namespace Company.WebApplication1.Controllers
|
|||
private string GenerateQrCodeUri(string email, string unformattedKey)
|
||||
{
|
||||
return string.Format(
|
||||
AuthenicatorUriFormat,
|
||||
AuthenticatorUriFormat,
|
||||
_urlEncoder.Encode("Company.WebApplication1"),
|
||||
_urlEncoder.Encode(email),
|
||||
unformattedKey);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace Company.WebApplication1.Models.ManageViewModels
|
||||
{
|
||||
public class GenerateRecoveryCodesViewModel
|
||||
public class ShowRecoveryCodesViewModel
|
||||
{
|
||||
public string[] RecoveryCodes { get; set; }
|
||||
}
|
||||
|
|
@ -3,6 +3,6 @@
|
|||
}
|
||||
|
||||
<header>
|
||||
<h2 class="text-danger">ViewData["Title"]</h2>
|
||||
<h2 class="text-danger">@ViewData["Title"]</h2>
|
||||
<p class="text-danger">You do not have access to this resource.</p>
|
||||
</header>
|
||||
|
|
|
|||
|
|
@ -1,24 +1,26 @@
|
|||
@model GenerateRecoveryCodesViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Recovery codes";
|
||||
@{
|
||||
ViewData["Title"] = "Generate two-factor authentication (2FA) recovery codes";
|
||||
ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication);
|
||||
}
|
||||
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<h2>@ViewData["Title"]</h2>
|
||||
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<p>
|
||||
<span class="glyphicon glyphicon-warning-sign"></span>
|
||||
<strong>Put these codes in a safe place.</strong>
|
||||
<strong>This action generates new recovery codes.</strong>
|
||||
</p>
|
||||
<p>
|
||||
If you lose your device and don't have the recovery codes you will lose access to your account.
|
||||
</p>
|
||||
<p>
|
||||
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 <a asp-action="ResetAuthenticatorWarning">reset your authenticator keys.</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<form asp-action="GenerateRecoveryCodes" method="post" class="form-group">
|
||||
<button class="btn btn-danger" type="submit">Generate Recovery Codes</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
@for (var row = 0; row < Model.RecoveryCodes.Count(); row += 2)
|
||||
{
|
||||
<code>@Model.RecoveryCodes[row]</code><text> </text><code>@Model.RecoveryCodes[row + 1]</code><br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
@model ShowRecoveryCodesViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Recovery codes";
|
||||
ViewData.AddActivePage(ManageNavPages.TwoFactorAuthentication);
|
||||
}
|
||||
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<p>
|
||||
<span class="glyphicon glyphicon-warning-sign"></span>
|
||||
<strong>Put these codes in a safe place.</strong>
|
||||
</p>
|
||||
<p>
|
||||
If you lose your device and don't have the recovery codes you will lose access to your account.
|
||||
</p>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
@for (var row = 0; row < Model.RecoveryCodes.Length; row += 2)
|
||||
{
|
||||
<code>@Model.RecoveryCodes[row]</code><text> </text><code>@Model.RecoveryCodes[row + 1]</code><br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
}
|
||||
|
||||
<a asp-action="Disable2faWarning" class="btn btn-default">Disable 2FA</a>
|
||||
<a asp-action="GenerateRecoveryCodes" class="btn btn-default">Reset recovery codes</a>
|
||||
<a asp-action="GenerateRecoveryCodesWarning" class="btn btn-default">Reset recovery codes</a>
|
||||
}
|
||||
|
||||
<h5>Authenticator app</h5>
|
||||
|
|
|
|||
Loading…
Reference in New Issue