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
|
|
|
|
{
|
2022-01-07 04:32:00 +01:00
|
|
|
public partial class UIManageController
|
2019-05-02 14:01:08 +02:00
|
|
|
{
|
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),
|
2021-12-31 08:59:02 +01:00
|
|
|
Credentials = await _fido2Service.GetCredentials(_userManager.GetUserId(User))
|
2019-05-02 14:01:08 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
return View(model);
|
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
2021-12-31 08:59:02 +01:00
|
|
|
return RedirectToAction(nameof(GenerateRecoveryCodes), new { confirm = false });
|
2019-05-02 14:01:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2020-07-13 08:13:27 +02:00
|
|
|
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
|
|
|
|
2021-12-31 08:59:02 +01: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)
|
|
|
|
{
|
2021-10-06 05:53:41 +02:00
|
|
|
result.Append(unformattedKey.Substring(currentPosition, 4)).Append(' ');
|
2019-05-02 14:01:08 +02:00
|
|
|
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);
|
|
|
|
}
|
2021-11-11 13:03:08 +01:00
|
|
|
|
|
|
|
[HttpPost]
|
|
|
|
public IActionResult CreateCredential(string name, Fido2Credential.CredentialType type)
|
|
|
|
{
|
|
|
|
switch (type)
|
|
|
|
{
|
|
|
|
case Fido2Credential.CredentialType.FIDO2:
|
|
|
|
return RedirectToAction("Create", "UIFido2", new { name });
|
|
|
|
case Fido2Credential.CredentialType.LNURLAuth:
|
2022-01-25 03:51:14 +01:00
|
|
|
return RedirectToAction("Create", "UILNURLAuth", new { name });
|
2021-11-11 13:03:08 +01:00
|
|
|
default:
|
|
|
|
throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
|
|
|
}
|
|
|
|
}
|
2019-05-02 14:01:08 +02:00
|
|
|
}
|
|
|
|
}
|