2019-05-02 14:01:08 +02:00
using System ;
using System.Globalization ;
using System.Linq ;
using System.Text ;
using System.Threading.Tasks ;
2019-08-29 17:24:42 +02:00
using BTCPayServer.Data ;
2020-07-13 08:13:27 +02:00
using BTCPayServer.Models ;
2019-05-02 14:01:08 +02:00
using BTCPayServer.Models.ManageViewModels ;
using Microsoft.AspNetCore.Mvc ;
using Microsoft.Extensions.Logging ;
namespace BTCPayServer.Controllers
{
public partial class ManageController
{
2019-08-10 07:05:11 +02:00
private const string RecoveryCodesKey = nameof ( RecoveryCodesKey ) ;
2019-05-02 14:01:08 +02:00
private const string AuthenicatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6" ;
[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
{
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 occurred disabling 2FA for user with ID '{user.Id}'." ) ;
}
2020-07-13 08:13:27 +02:00
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 <a href=\" { Url . Action ( nameof ( ResetAuthenticatorWarning ) ) } \ "> reset your authenticator keys</a>." ,
Action = "Disable 2FA" ,
ActionUrl = Url . ActionLink ( nameof ( Disable2fa ) )
} ) ;
2019-05-02 14:01:08 +02:00
}
[HttpPost]
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 occurred 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)}'." ) ;
}
2019-08-10 07:05:11 +02:00
var model = new EnableAuthenticatorViewModel ( ) ;
await LoadSharedKeyAndQrCodeUriAsync ( user , model ) ;
2019-05-02 14:01:08 +02:00
return View ( model ) ;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task < IActionResult > EnableAuthenticator ( EnableAuthenticatorViewModel model )
{
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
{
throw new ApplicationException ( $"Unable to load user with ID '{_userManager.GetUserId(User)}'." ) ;
}
2019-08-10 07:05:11 +02:00
if ( ! ModelState . IsValid )
{
await LoadSharedKeyAndQrCodeUriAsync ( user , model ) ;
return View ( model ) ;
}
2019-05-02 14:01:08 +02:00
// Strip spaces and hypens
2020-07-13 08:13:27 +02:00
var verificationCode = model . Code . Replace ( " " , string . Empty , StringComparison . OrdinalIgnoreCase )
. Replace ( "-" , string . Empty , StringComparison . OrdinalIgnoreCase ) ;
2019-05-02 14:01:08 +02:00
var is2faTokenValid = await _userManager . VerifyTwoFactorTokenAsync (
user , _userManager . Options . Tokens . AuthenticatorTokenProvider , verificationCode ) ;
if ( ! is2faTokenValid )
{
2019-08-10 07:05:11 +02:00
ModelState . AddModelError ( "Code" , "Verification code is invalid." ) ;
await LoadSharedKeyAndQrCodeUriAsync ( user , model ) ;
2019-05-02 14:01:08 +02:00
return View ( model ) ;
}
await _userManager . SetTwoFactorEnabledAsync ( user , true ) ;
_logger . LogInformation ( "User with ID {UserId} has enabled 2FA with an authenticator app." , user . Id ) ;
2019-08-10 07:05:11 +02:00
var recoveryCodes = await _userManager . GenerateNewTwoFactorRecoveryCodesAsync ( user , 10 ) ;
TempData [ RecoveryCodesKey ] = recoveryCodes . ToArray ( ) ;
2020-07-13 08:13:27 +02:00
return RedirectToAction ( nameof ( GenerateRecoveryCodes ) , new { confirm = false } ) ;
2019-05-02 14:01:08 +02:00
}
[HttpGet]
public IActionResult ResetAuthenticatorWarning ( )
{
2020-07-13 08:13:27 +02:00
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 ) )
} ) ;
2019-05-02 14:01:08 +02:00
}
[HttpPost]
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 _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]
2020-07-13 08:13:27 +02:00
public async Task < IActionResult > 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 < IActionResult > GenerateRecoveryCodes ( )
2019-08-10 07:05:11 +02:00
{
var recoveryCodes = ( string [ ] ) TempData [ RecoveryCodesKey ] ;
if ( recoveryCodes = = null )
{
2020-07-13 08:13:27 +02:00
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 ( ) ;
2019-08-10 07:05:11 +02:00
}
2020-06-28 10:55:27 +02:00
2020-07-13 08:13:27 +02:00
var model = new GenerateRecoveryCodesViewModel { RecoveryCodes = recoveryCodes } ;
2019-08-10 07:05:11 +02:00
return View ( model ) ;
}
2019-05-02 14:01:08 +02:00
private string GenerateQrCodeUri ( string email , string unformattedKey )
{
return string . Format ( CultureInfo . InvariantCulture ,
AuthenicatorUriFormat ,
_urlEncoder . Encode ( "BTCPayServer" ) ,
_urlEncoder . Encode ( email ) ,
unformattedKey ) ;
}
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 ( ) ;
}
2019-08-10 07:05:11 +02:00
private async Task LoadSharedKeyAndQrCodeUriAsync ( ApplicationUser user , EnableAuthenticatorViewModel model )
{
var unformattedKey = await _userManager . GetAuthenticatorKeyAsync ( user ) ;
if ( string . IsNullOrEmpty ( unformattedKey ) )
{
await _userManager . ResetAuthenticatorKeyAsync ( user ) ;
unformattedKey = await _userManager . GetAuthenticatorKeyAsync ( user ) ;
}
model . SharedKey = FormatKey ( unformattedKey ) ;
model . AuthenticatorUri = GenerateQrCodeUri ( user . Email , unformattedKey ) ;
}
2019-05-02 14:01:08 +02:00
}
}