Split showing and generating recovery codes

This commit is contained in:
Pranav K 2017-12-06 13:55:51 -08:00
parent e4ecd070eb
commit 16c23b846e
12 changed files with 170 additions and 40 deletions

View File

@ -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>

View File

@ -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);

View File

@ -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>&nbsp;</text><code>@Model.RecoveryCodes[row + 1]</code><br />
}
</div>
</div>

View File

@ -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");
}
}
}

View File

@ -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>&nbsp;</text><code>@Model.RecoveryCodes[row + 1]</code><br />
}
</div>
</div>

View File

@ -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();
}
}
}

View File

@ -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);

View File

@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace Company.WebApplication1.Models.ManageViewModels
{
public class GenerateRecoveryCodesViewModel
public class ShowRecoveryCodesViewModel
{
public string[] RecoveryCodes { get; set; }
}

View File

@ -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>

View File

@ -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>&nbsp;</text><code>@Model.RecoveryCodes[row + 1]</code><br />
}
</div>
</div>

View File

@ -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>&nbsp;</text><code>@Model.RecoveryCodes[row + 1]</code><br />
}
</div>
</div>

View File

@ -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>