@@ -1,4 +1,9 @@
using Microsoft.AspNetCore.Authentication ;
using System ;
using System.Linq ;
using System.Text ;
using System.Text.Encodings.Web ;
using System.Threading.Tasks ;
using Microsoft.AspNetCore.Authentication ;
using Microsoft.AspNetCore.Authorization ;
using Microsoft.AspNetCore.Identity ;
using Microsoft.AspNetCore.Mvc ;
@@ -6,495 +11,489 @@ using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Microsoft.eShopWeb.Infrastructure.Identity ;
using Microsoft.eShopWeb.Web.Services ;
using Microsoft.eShopWeb.Web.ViewModels.Manage ;
using System ;
using System.Linq ;
using System.Text ;
using System.Text.Encodings.Web ;
using System.Threading.Tasks ;
namespace Microsoft.eShopWeb.Web.Controllers
namespace Microsoft.eShopWeb.Web.Controllers ;
[ApiExplorerSettings(IgnoreApi = true)]
[Authorize] // Controllers that mainly require Authorization still use Controller/View; other pages use Pages
[Route("[controller] / [ action ] ")]
public class ManageController : Controller
{
[ApiExplorerSettings(IgnoreApi = true)]
[Authorize] // Controllers that mainly require Authorization still use Controller/View; other pages use Pages
[Route("[controller] / [ action ] ")]
public class ManageController : Controller
private readonly UserManager < ApplicationUser > _userManager ;
private readonly SignInManager < ApplicationUser > _signInManager ;
private readonly IEmailSender _emailSender ;
private readonly IAppLogger < ManageController > _logger ;
private readonly UrlEncoder _urlEncoder ;
private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6" ;
public ManageController (
UserManager < ApplicationUser > userManager ,
SignInManager < ApplicationUser > signInManager ,
IEmailSender emailSender ,
IAppLogger < ManageController > logger ,
UrlEncoder urlEncoder )
{
private readonly UserManager < ApplicationUser > _ userManager;
private readonly SignInManager < ApplicationUser > _ signInManager;
private readonly IE mailSender _ emailSender;
private readonly IAppLogger < ManageController > _ logger;
private readonly U rlEncoder _ urlEncoder;
_userManager = userManager ;
_signInManager = signInManager ;
_e mailSender = emailSender ;
_logger = logger ;
_u rlEncoder = urlEncoder ;
}
private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6" ;
[TempData]
public string StatusMessage { get ; set ; }
public ManageController (
UserManager < ApplicationUser > userManager ,
SignInManager < ApplicationUser > signInManager ,
IEmailSender emailSender ,
IAppLogger < ManageController > logger ,
UrlEncoder urlEncoder )
[HttpGet]
public async Task < IActionResult > MyAccount ( )
{
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
{
_userManager = userManager ;
_signInManager = signInManager ;
_emailSender = emailSender ;
_logger = logger ;
_urlEncoder = urlEncoder ;
throw new ApplicationException ( $"Unable to load user with ID '{_userManager.GetUserId(User)}'." ) ;
}
[TempData]
public string StatusMessage { get ; set ; }
[HttpGet]
public async Task < IActionResult > MyAccount ( )
var model = new IndexViewModel
{
var user = await _userManag er. Get UserAsync ( User ) ;
if ( user = = nul l)
{
throw new ApplicationException ( $"Unable to load user with ID '{_userManager.GetUserId(User)}'." ) ;
}
Username = us er. UserName ,
Email = user . Emai l,
PhoneNumber = user . PhoneNumber ,
IsEmailConfirmed = user . EmailConfirmed ,
StatusMessage = StatusMessage
} ;
var model = new IndexViewModel
{
Username = user . UserName ,
Email = user . Email ,
PhoneNumber = user . PhoneNumber ,
IsEmailConfirmed = user . EmailConfirmed ,
StatusMessage = StatusMessage
} ;
return View ( model ) ;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task < IActionResult > MyAccount ( IndexViewModel model )
{
if ( ! ModelState . IsValid )
{
return View ( model ) ;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task < IActionResult > MyAccount ( IndexViewModel model )
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
{
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)}'." ) ;
}
var email = user . Email ;
if ( model . Email ! = email )
{
var setEmailResult = await _userManager . SetEmailAsync ( user , model . Email ) ;
if ( ! setEmailResult . Succeeded )
{
throw new ApplicationException ( $"Unexpected error occurred setting email for user with ID '{user.Id}'." ) ;
}
}
var phoneNumber = user . PhoneNumber ;
if ( model . PhoneNumber ! = phoneNumber )
{
var setPhoneResult = await _userManager . SetPhoneNumberAsync ( user , model . PhoneNumber ) ;
if ( ! setPhoneResult . Succeeded )
{
throw new ApplicationException ( $"Unexpected error occurred setting phone number for user with ID '{user.Id}'." ) ;
}
}
StatusMessage = "Your profile has been updated" ;
return RedirectToAction ( nameof ( MyAccount ) ) ;
throw new ApplicationException ( $"Unable to load user with ID '{_userManager.GetUserId(User)}'." ) ;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task < IActionResult > SendVerificationEmail ( IndexViewModel model )
var email = user . Email ;
if ( model . Email ! = email )
{
if ( ! ModelState . IsValid )
var setEmailResult = await _userManager . SetEmailAsync ( user , model . Email ) ;
if ( ! setEmailResult . Succeeded )
{
return View ( model ) ;
throw new ApplicationException ( $"Unexpected error occurred setting email for user with ID '{user.Id}'." ) ;
}
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
{
throw new ApplicationException ( $"Unable to load user with ID '{_userManager.GetUserId(User)}'." ) ;
}
var code = await _userManager . GenerateEmailConfirmationTokenAsync ( user ) ;
var callbackUrl = Url . EmailConfirmationLink ( user . Id , code , Request . Scheme ) ;
var email = user . Email ;
await _emailSender . SendEmailConfirmationAsync ( email , callbackUrl ) ;
StatusMessage = "Verification email sent. Please check your email." ;
return RedirectToAction ( nameof ( MyAccount ) ) ;
}
[HttpGet]
public async Task < IActionResult > ChangePassword ( )
var phoneNumber = user . PhoneNumber ;
if ( model . PhoneNumber ! = phoneNumber )
{
var u ser = await _userManager . G etUs erAsync( Us er) ;
if ( user = = null )
var setPhoneResult = await _userManager . S etPhoneNumb erAsync( user , model . PhoneNumb er) ;
if ( ! setPhoneResult . Succeeded )
{
throw new ApplicationException ( $"Unable to load user with ID '{_ userManager.GetUserId(User) }'." ) ;
throw new ApplicationException ( $"Unexpected error occurred setting phone number for user with ID '{user.Id }'." ) ;
}
}
var hasPassword = await _userManager . HasPasswordAsync ( user ) ;
if ( ! hasPassword )
{
return RedirectToAction ( nameof ( SetPassword ) ) ;
}
StatusMessage = "Your profile has been updated" ;
return RedirectToAction ( nameof ( MyAccount ) ) ;
}
var model = new ChangePasswordViewModel { StatusMessage = StatusMessage } ;
[HttpPost]
[ValidateAntiForgeryToken]
public async Task < IActionResult > SendVerificationEmail ( IndexViewModel model )
{
if ( ! ModelState . IsValid )
{
return View ( model ) ;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task < IActionResult > ChangePassword ( ChangePasswordViewModel model )
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
{
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)}'." ) ;
}
var changePasswordResult = await _userManager . ChangePasswordAsync ( user , model . OldPassword , model . NewPassword ) ;
if ( ! changePasswordResult . Succeeded )
{
AddErrors ( changePasswordResult ) ;
return View ( model ) ;
}
await _signInManager . SignInAsync ( user , isPersistent : false ) ;
_logger . LogInformation ( "User changed their password successfully." ) ;
StatusMessage = "Your password has been changed." ;
return RedirectToAction ( nameof ( ChangePassword ) ) ;
throw new ApplicationException ( $"Unable to load user with ID '{_userManager.GetUserId(User)}'." ) ;
}
[HttpGet]
public async Task < IActionResult > SetPassword ( )
var code = await _userManager . GenerateEmailConfirmationTokenAsync ( user ) ;
var callbackUrl = Url . EmailConfirmationLink ( user . Id , code , Request . Scheme ) ;
var email = user . Email ;
await _emailSender . SendEmailConfirmationAsync ( email , callbackUrl ) ;
StatusMessage = "Verification email sent. Please check your email." ;
return RedirectToAction ( nameof ( MyAccount ) ) ;
}
[HttpGet]
public async Task < IActionResult > ChangePassword ( )
{
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
{
var user = await _userManager. GetUserAsync ( User ) ;
if ( user = = null )
{
throw new ApplicationException ( $"Unable to load user with ID '{_userManager.GetUserId(User)}'." ) ;
}
var hasPassword = await _userManager . HasPasswordAsync ( user ) ;
if ( hasPassword )
{
return RedirectToAction ( nameof ( ChangePassword ) ) ;
}
var model = new SetPasswordViewModel { StatusMessage = StatusMessage } ;
return View ( model ) ;
throw new ApplicationException ( $"Unable to load user with ID '{ _userManager. GetUserId(User)}'." ) ;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task < IActionResult > SetPassword ( SetPasswordViewModel model )
var hasPassword = await _userManager . HasPasswordAsync ( user ) ;
if ( ! hasPassword )
{
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)}'." ) ;
}
var addPasswordResult = await _userManager . AddPasswordAsync ( user , model . NewPassword ) ;
if ( ! addPasswordResult . Succeeded )
{
AddErrors ( addPasswordResult ) ;
return View ( model ) ;
}
await _signInManager . SignInAsync ( user , isPersistent : false ) ;
StatusMessage = "Your password has been set." ;
return RedirectToAction ( nameof ( SetPassword ) ) ;
}
[HttpGet]
public async Task < IActionResult > ExternalLogins ( )
var model = new ChangePasswordViewModel { StatusMessage = StatusMessage } ;
return View ( model ) ;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task < IActionResult > ChangePassword ( ChangePasswordViewModel model )
{
if ( ! ModelState . IsValid )
{
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
{
throw new ApplicationException ( $"Unable to load user with ID '{_userManager.GetUserId(User)}'." ) ;
}
var model = new ExternalLoginsViewModel { CurrentLogins = await _userManager . GetLoginsAsync ( user ) } ;
model . OtherLogins = ( await _signInManager . GetExternalAuthenticationSchemesAsync ( ) )
. Where ( auth = > model . CurrentLogins . All ( ul = > auth . Name ! = ul . LoginProvider ) )
. ToList ( ) ;
model . ShowRemoveButton = await _userManager . HasPasswordAsync ( user ) | | model . CurrentLogins . Count > 1 ;
model . StatusMessage = StatusMessage ;
return View ( model ) ;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task < IActionResult > LinkLogin ( string provider )
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
{
// Clear the existing external cookie to ensure a clean login process
await HttpContext . SignOutAsync ( IdentityConstants . ExternalScheme ) ;
// Request a redirect to the external login provider to link a login for the current user
var redirectUrl = Url . Action ( nameof ( LinkLoginCallback ) ) ;
var properties = _signInManager . ConfigureExternalAuthenticationProperties ( provider , redirectUrl , _userManager . GetUserId ( User ) ) ;
return new ChallengeResult ( provider , properties ) ;
throw new ApplicationException ( $"Unable to load user with ID '{_userManager.GetUserId(User)}'." ) ;
}
[HttpGet]
public async Task < IActionResult > LinkLoginCallback ( )
var changePasswordResult = await _userManager . ChangePasswordAsync ( user , model . OldPassword , model . NewPassword ) ;
if ( ! changePasswordResult . Succeeded )
{
var user = await _userM ana ger . GetUserAsync ( User ) ;
if ( user = = null )
{
throw new ApplicationException ( $"Unable to load user with ID '{_userManager.GetUserId(User)}'." ) ;
}
var info = await _signInManager . GetExternalLoginInfoAsync ( user . Id ) ;
if ( info = = null )
{
throw new ApplicationException ( $"Unexpected error occurred loading external login info for user with ID '{user.Id}'." ) ;
}
var result = await _userManager . AddLoginAsync ( user , info ) ;
if ( ! result . Succeeded )
{
throw new ApplicationException ( $"Unexpected error occurred adding external login for user with ID '{user.Id}'." ) ;
}
// Clear the existing external cookie to ensure a clean login process
await HttpContext . SignOutAsync ( IdentityConstants . ExternalScheme ) ;
StatusMessage = "The external login was added." ;
return RedirectToAction ( nameof ( ExternalLogins ) ) ;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task < IActionResult > RemoveLogin ( RemoveLoginViewModel model )
{
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
{
throw new ApplicationException ( $"Unable to load user with ID '{_userManager.GetUserId(User)}'." ) ;
}
var result = await _userManager . RemoveLoginAsync ( user , model . LoginProvider , model . ProviderKey ) ;
if ( ! result . Succeeded )
{
throw new ApplicationException ( $"Unexpected error occurred removing external login for user with ID '{user.Id}'." ) ;
}
await _signInManager . SignInAsync ( user , isPersistent : false ) ;
StatusMessage = "The external login was removed." ;
return RedirectToAction ( nameof ( ExternalLogins ) ) ;
}
[HttpGet]
public async Task < IActionResult > TwoFactorAuthentication ( )
{
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
{
throw new ApplicationException ( $"Unable to load user with ID '{_userManager.GetUserId(User)}'." ) ;
}
var model = new TwoFactorAuthenticationViewModel
{
HasAuthenticator = await _userManager . GetAuthenticatorKeyAsync ( user ) ! = null ,
Is2faEnabled = user . TwoFactorEnabled ,
RecoveryCodesLeft = await _userManager . CountRecoveryCodesAsync ( user ) ,
} ;
AddErrors ( ch angePasswordResult ) ;
return View ( model ) ;
}
[HttpGet]
public async Task < IActionResult > Disable2faWarning ( )
await _signInManager . SignInAsync ( user , isPersistent : false ) ;
_logger . LogInformation ( "User changed their password successfully." ) ;
StatusMessage = "Your password has been changed." ;
return RedirectToAction ( nameof ( ChangePassword ) ) ;
}
[HttpGet]
public async Task < IActionResult > SetPassword ( )
{
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
{
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 ( $"Unexpected error occured disabling 2FA for user with ID '{user.Id}'." ) ;
}
return View ( nameof ( Disable2fa ) ) ;
throw new ApplicationException ( $"Unable to load user with ID '{ _userManager. GetUserId(User)}'." ) ;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task < IActionResult > Disable2fa ( )
var hasPassword = await _userManager . HasPasswordAsync ( user ) ;
if ( hasPassword )
{
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
{
throw new ApplicationException ( $"Unable to load user with ID '{_userManager.GetUserId(User)}'." ) ;
}
var disable2faResult = await _userManager . SetTwoFactorEnabledAsync ( user , false ) ;
if ( ! disable2faResult . Succeeded )
{
throw new ApplicationException ( $"Unexpected error occured disabling 2FA for user with ID '{user.Id}'." ) ;
}
_logger . LogInformation ( "User with ID {UserId} has disabled 2fa." , user . Id ) ;
return RedirectToAction ( nameof ( TwoFactorAuthentication ) ) ;
return RedirectToAction ( nameof ( ChangePassword ) ) ;
}
[HttpGet]
public async Task < IActionResult > EnableAuthenticator ( )
var model = new SetPasswordViewModel { StatusMessage = StatusMessage } ;
return View ( model ) ;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task < IActionResult > SetPassword ( SetPasswordViewModel model )
{
if ( ! ModelState . IsValid )
{
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
{
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 )
} ;
return View ( model ) ;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task < IActionResult > EnableAuthenticator ( EnableAuthenticatorViewModel model )
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
{
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)}'." ) ;
}
// Strip spaces and hypens
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 ( "model.TwoFactorCode" , "Verification code is invalid." ) ;
return View ( model ) ;
}
await _userManager . SetTwoFactorEnabledAsync ( user , true ) ;
_logger . LogInformation ( "User with ID {UserId} has enabled 2FA with an authenticator app." , user . Id ) ;
return RedirectToAction ( nameof ( GenerateRecoveryCodes ) ) ;
throw new ApplicationException ( $"Unable to load user with ID '{_userManager.GetUserId(User)}'." ) ;
}
[HttpGet]
public IActionResult ResetAuthenticatorWarning ( )
var addPasswordResult = await _userManager . AddPasswordAsync ( user , model . NewPassword ) ;
if ( ! addPasswordResult . Succeeded )
{
return View ( nameof ( ResetAuthenticator ) ) ;
AddErrors ( addPasswordResult ) ;
return View ( model ) ;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task < IActionResult > ResetAuthenticator ( )
{
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
{
throw new ApplicationException ( $"Unable to load user with ID '{_userManager.GetUserId(User)}'." ) ;
}
await _signInManager . SignInAsync ( user , isPersistent : false ) ;
StatusMessage = "Your password has been set." ;
await _userManager . SetTwoFactorEnabledAsync ( user , false ) ;
return RedirectToAction ( nameof ( SetPassword ) ) ;
}
[HttpGet]
public async Task < IActionResult > ExternalLogins ( )
{
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
{
throw new ApplicationException ( $"Unable to load user with ID '{_userManager.GetUserId(User)}'." ) ;
}
var model = new ExternalLoginsViewModel { CurrentLogins = await _userManager . GetLoginsAsync ( user ) } ;
model . OtherLogins = ( await _signInManager . GetExternalAuthenticationSchemesAsync ( ) )
. Where ( auth = > model . CurrentLogins . All ( ul = > auth . Name ! = ul . LoginProvider ) )
. ToList ( ) ;
model . ShowRemoveButton = await _userManager . HasPasswordAsync ( user ) | | model . CurrentLogins . Count > 1 ;
model . StatusMessage = StatusMessage ;
return View ( model ) ;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task < IActionResult > LinkLogin ( string provider )
{
// Clear the existing external cookie to ensure a clean login process
await HttpContext . SignOutAsync ( IdentityConstants . ExternalScheme ) ;
// Request a redirect to the external login provider to link a login for the current user
var redirectUrl = Url . Action ( nameof ( LinkLoginCallback ) ) ;
var properties = _signInManager . ConfigureExternalAuthenticationProperties ( provider , redirectUrl , _userManager . GetUserId ( User ) ) ;
return new ChallengeResult ( provider , properties ) ;
}
[HttpGet]
public async Task < IActionResult > LinkLoginCallback ( )
{
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
{
throw new ApplicationException ( $"Unable to load user with ID '{_userManager.GetUserId(User)}'." ) ;
}
var info = await _signInManager . GetExternalLoginInfoAsync ( user . Id ) ;
if ( info = = null )
{
throw new ApplicationException ( $"Unexpected error occurred loading external login info for user with ID '{user.Id}'." ) ;
}
var result = await _userManager . AddLoginAsync ( user , info ) ;
if ( ! result . Succeeded )
{
throw new ApplicationException ( $"Unexpected error occurred adding external login for user with ID '{user.Id}'." ) ;
}
// Clear the existing external cookie to ensure a clean login process
await HttpContext . SignOutAsync ( IdentityConstants . ExternalScheme ) ;
StatusMessage = "The external login was added." ;
return RedirectToAction ( nameof ( ExternalLogins ) ) ;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task < IActionResult > RemoveLogin ( RemoveLoginViewModel model )
{
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
{
throw new ApplicationException ( $"Unable to load user with ID '{_userManager.GetUserId(User)}'." ) ;
}
var result = await _userManager . RemoveLoginAsync ( user , model . LoginProvider , model . ProviderKey ) ;
if ( ! result . Succeeded )
{
throw new ApplicationException ( $"Unexpected error occurred removing external login for user with ID '{user.Id}'." ) ;
}
await _signInManager . SignInAsync ( user , isPersistent : false ) ;
StatusMessage = "The external login was removed." ;
return RedirectToAction ( nameof ( ExternalLogins ) ) ;
}
[HttpGet]
public async Task < IActionResult > TwoFactorAuthentication ( )
{
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
{
throw new ApplicationException ( $"Unable to load user with ID '{_userManager.GetUserId(User)}'." ) ;
}
var model = new TwoFactorAuthenticationViewModel
{
HasAuthenticator = await _userManager . GetAuthenticatorKeyAsync ( user ) ! = null ,
Is2faEnabled = user . TwoFactorEnabled ,
RecoveryCodesLeft = await _userManager . CountRecoveryCodesAsync ( user ) ,
} ;
return View ( model ) ;
}
[HttpGet]
public async Task < IActionResult > Disable2faWarning ( )
{
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 ( $"Unexpected error occured disabling 2FA for user with ID '{user.Id}'." ) ;
}
return View ( nameof ( Disable2fa ) ) ;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task < IActionResult > Disable2fa ( )
{
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
{
throw new ApplicationException ( $"Unable to load user with ID '{_userManager.GetUserId(User)}'." ) ;
}
var disable2faResult = await _userManager . SetTwoFactorEnabledAsync ( user , false ) ;
if ( ! disable2faResult . Succeeded )
{
throw new ApplicationException ( $"Unexpected error occured disabling 2FA for user with ID '{user.Id}'." ) ;
}
_logger . LogInformation ( "User with ID {UserId} has disabled 2fa." , user . Id ) ;
return RedirectToAction ( nameof ( TwoFactorAuthentication ) ) ;
}
[HttpGet]
public async Task < IActionResult > EnableAuthenticator ( )
{
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
{
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 ) ;
_logger . LogInformation ( "User with id '{UserId}' has reset their authentication app key." , user . Id ) ;
return RedirectToAction ( nameof ( EnableAuthenticator ) ) ;
unformattedKey = await _userManager . GetAuthenticatorKeyAsync ( user ) ;
}
[HttpGet]
public async Task < IActionResult > GenerateRecoveryCodes ( )
var model = new EnableAuthenticatorViewModel
{
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
{
throw new ApplicationException ( $"Unable to load user with ID '{_userManager.GetUserId(User)}'." ) ;
}
SharedKey = FormatKey ( unformattedKey ) ,
AuthenticatorUri = GenerateQrCodeUri ( user . Email , unformattedKey )
} ;
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 ) ;
var model = new GenerateRecoveryCodesViewModel { RecoveryCodes = recoveryCodes . ToArray ( ) } ;
_logger . LogInformation ( "User with ID {UserId} has generated new 2FA recovery codes." , user . Id ) ;
return View ( model ) ;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task < IActionResult > EnableAuthenticator ( EnableAuthenticatorViewModel model )
{
if ( ! ModelState . IsValid )
{
return View ( model ) ;
}
private void AddErrors ( IdentityResult result )
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
{
foreach ( var error in result . Errors )
{
ModelState . AddModelError ( string . Empty , error . Description ) ;
}
throw new ApplicationException ( $"Unable to load user with ID '{_userManager.GetUserId(User)}'." ) ;
}
private string FormatKey ( string unformattedKey )
{
var result = new StringBuilder ( ) ;
int currentPosition = 0 ;
while ( currentPosition + 4 < unformattedKey . Length )
{
result . Append ( unformattedKey . Substring ( currentPosition , 4 ) ) . Append ( " " ) ;
currentPosition + = 4 ;
}
if ( currentPosition < unformattedKey . Length )
{
result . Append ( unformattedKey . Substring ( currentPosition ) ) ;
}
// Strip spaces and hypens
var verificationCode = model . Code . Replace ( " " , string . Empty ) . Replace ( "-" , string . Empty ) ;
return result . ToString ( ) . ToLowerInvariant ( ) ;
var is2faTokenValid = await _userManager . VerifyTwoFactorTokenAsync (
user , _userManager . Options . Tokens . AuthenticatorTokenProvider , verificationCode ) ;
if ( ! is2faTokenValid )
{
ModelState . AddModelError ( "model.TwoFactorCode" , "Verification code is invalid." ) ;
return View ( model ) ;
}
private string GenerateQrCodeUri ( string email , string unformattedKey )
await _userManager . SetTwoFactorEnabledAsync ( user , true ) ;
_logger . LogInformation ( "User with ID {UserId} has enabled 2FA with an authenticator app." , user . Id ) ;
return RedirectToAction ( nameof ( GenerateRecoveryCodes ) ) ;
}
[HttpGet]
public IActionResult ResetAuthenticatorWarning ( )
{
return View ( nameof ( ResetAuthenticator ) ) ;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task < IActionResult > ResetAuthenticator ( )
{
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
{
return string . Format (
AuthenticatorUriFormat ,
_urlEncoder . Encode ( "eShopOnWeb" ) ,
_urlEncoder . Encode ( email ) ,
unformattedKey ) ;
throw new ApplicationException ( $"Unable to load user with ID '{_userManager.GetUserId(User)}'." ) ;
}
await _userManager . SetTwoFactorEnabledAsync ( user , false ) ;
await _userManager . ResetAuthenticatorKeyAsync ( user ) ;
_logger . LogInformation ( "User with id '{UserId}' has reset their authentication app key." , user . Id ) ;
return RedirectToAction ( nameof ( EnableAuthenticator ) ) ;
}
[HttpGet]
public async Task < IActionResult > GenerateRecoveryCodes ( )
{
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 ) ;
var model = new GenerateRecoveryCodesViewModel { RecoveryCodes = recoveryCodes . ToArray ( ) } ;
_logger . LogInformation ( "User with ID {UserId} has generated new 2FA recovery codes." , user . Id ) ;
return View ( model ) ;
}
private void AddErrors ( IdentityResult result )
{
foreach ( var error in result . Errors )
{
ModelState . AddModelError ( string . Empty , error . Description ) ;
}
}
private string FormatKey ( string unformattedKey )
{
var result = new StringBuilder ( ) ;
int currentPosition = 0 ;
while ( currentPosition + 4 < unformattedKey . Length )
{
result . Append ( unformattedKey . Substring ( currentPosition , 4 ) ) . Append ( " " ) ;
currentPosition + = 4 ;
}
if ( currentPosition < unformattedKey . Length )
{
result . Append ( unformattedKey . Substring ( currentPosition ) ) ;
}
return result . ToString ( ) . ToLowerInvariant ( ) ;
}
private string GenerateQrCodeUri ( string email , string unformattedKey )
{
return string . Format (
AuthenticatorUriFormat ,
_urlEncoder . Encode ( "eShopOnWeb" ) ,
_urlEncoder . Encode ( email ) ,
unformattedKey ) ;
}
}