mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 14:04:26 +01:00
FIDO2/WebAuthN Support (#2356)
* FIDO2/WebAuthN Support This adds initial support for WebAuthN/FIDO2 as another MFA mode. U2F is still intact and runs alongside it for now. Once this is merged, I will start work on migrating U2F support to happen over the FIDO2 protocol instead. * Refactor and future proof system (prep work of seamless u2f migration) * attempt js fix for mobile devices * Apply suggestions from code review Co-authored-by: d11n <mail@dennisreimann.de> * fix fido name saving * do not spam logs and hide loader when failed * PR Changes * Apply suggestions from code review Co-authored-by: d11n <mail@dennisreimann.de> * attempt fido2 bump * add name if not named for credentials Co-authored-by: d11n <mail@dennisreimann.de>
This commit is contained in:
@@ -1,24 +1,25 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Security.Policy;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Fido2;
|
||||
using BTCPayServer.Fido2.Models;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.AccountViewModels;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.U2F;
|
||||
using BTCPayServer.U2F.Models;
|
||||
using Fido2NetLib;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NicolasDorier.RateLimits;
|
||||
using U2F.Core.Exceptions;
|
||||
|
||||
@@ -35,7 +36,7 @@ namespace BTCPayServer.Controllers
|
||||
readonly Configuration.BTCPayServerOptions _Options;
|
||||
private readonly BTCPayServerEnvironment _btcPayServerEnvironment;
|
||||
public U2FService _u2FService;
|
||||
private readonly RateLimitService _rateLimitService;
|
||||
private readonly Fido2Service _fido2Service;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
readonly ILogger _logger;
|
||||
|
||||
@@ -47,8 +48,8 @@ namespace BTCPayServer.Controllers
|
||||
Configuration.BTCPayServerOptions options,
|
||||
BTCPayServerEnvironment btcPayServerEnvironment,
|
||||
U2FService u2FService,
|
||||
RateLimitService rateLimitService,
|
||||
EventAggregator eventAggregator)
|
||||
EventAggregator eventAggregator,
|
||||
Fido2Service fido2Service)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_signInManager = signInManager;
|
||||
@@ -57,7 +58,7 @@ namespace BTCPayServer.Controllers
|
||||
_Options = options;
|
||||
_btcPayServerEnvironment = btcPayServerEnvironment;
|
||||
_u2FService = u2FService;
|
||||
_rateLimitService = rateLimitService;
|
||||
_fido2Service = fido2Service;
|
||||
_eventAggregator = eventAggregator;
|
||||
_logger = Logs.PayServer;
|
||||
}
|
||||
@@ -125,7 +126,9 @@ namespace BTCPayServer.Controllers
|
||||
return View(model);
|
||||
}
|
||||
|
||||
if (!await _userManager.IsLockedOutAsync(user) && await _u2FService.HasDevices(user.Id))
|
||||
var u2fDevices = await _u2FService.HasDevices(user.Id);
|
||||
var fido2Devices = await _fido2Service.HasCredentials(user.Id);
|
||||
if (!await _userManager.IsLockedOutAsync(user) && u2fDevices || fido2Devices)
|
||||
{
|
||||
if (await _userManager.CheckPasswordAsync(user, model.Password))
|
||||
{
|
||||
@@ -144,7 +147,8 @@ namespace BTCPayServer.Controllers
|
||||
return View("SecondaryLogin", new SecondaryLoginViewModel()
|
||||
{
|
||||
LoginWith2FaViewModel = twoFModel,
|
||||
LoginWithU2FViewModel = await BuildU2FViewModel(model.RememberMe, user)
|
||||
LoginWithU2FViewModel = u2fDevices? await BuildU2FViewModel(model.RememberMe, user) : null,
|
||||
LoginWithFido2ViewModel = fido2Devices? await BuildFido2ViewModel(model.RememberMe, user): null,
|
||||
});
|
||||
}
|
||||
else
|
||||
@@ -210,6 +214,77 @@ namespace BTCPayServer.Controllers
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<LoginWithFido2ViewModel> BuildFido2ViewModel(bool rememberMe, ApplicationUser user)
|
||||
{
|
||||
if (_btcPayServerEnvironment.IsSecure)
|
||||
{
|
||||
var r = await _fido2Service.RequestLogin(user.Id);
|
||||
if (r is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new LoginWithFido2ViewModel()
|
||||
{
|
||||
Data = r,
|
||||
UserId = user.Id,
|
||||
RememberMe = rememberMe
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> LoginWithFido2(LoginWithFido2ViewModel viewModel, string returnUrl = null)
|
||||
{
|
||||
if (!CanLoginOrRegister())
|
||||
{
|
||||
return RedirectToAction("Login");
|
||||
}
|
||||
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
var user = await _userManager.FindByIdAsync(viewModel.UserId);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var errorMessage = string.Empty;
|
||||
try
|
||||
{
|
||||
if (await _fido2Service.CompleteLogin(viewModel.UserId, JObject.Parse(viewModel.Response).ToObject<AuthenticatorAssertionRawResponse>()))
|
||||
{
|
||||
await _signInManager.SignInAsync(user, viewModel.RememberMe, "FIDO2");
|
||||
_logger.LogInformation("User logged in.");
|
||||
return RedirectToLocal(returnUrl);
|
||||
}
|
||||
|
||||
errorMessage = "Invalid login attempt.";
|
||||
}
|
||||
catch (U2fException e)
|
||||
{
|
||||
errorMessage = e.Message;
|
||||
}
|
||||
|
||||
ModelState.AddModelError(string.Empty, errorMessage);
|
||||
viewModel.Response = null;
|
||||
return View("SecondaryLogin", new SecondaryLoginViewModel()
|
||||
{
|
||||
LoginWithFido2ViewModel = viewModel,
|
||||
LoginWithU2FViewModel = (await _u2FService.HasDevices(user.Id)) ? await BuildU2FViewModel(viewModel.RememberMe, user) : null,
|
||||
LoginWith2FaViewModel = !user.TwoFactorEnabled
|
||||
? null
|
||||
: new LoginWith2faViewModel()
|
||||
{
|
||||
RememberMe = viewModel.RememberMe
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
@@ -280,7 +355,8 @@ namespace BTCPayServer.Controllers
|
||||
return View("SecondaryLogin", new SecondaryLoginViewModel()
|
||||
{
|
||||
LoginWith2FaViewModel = new LoginWith2faViewModel { RememberMe = rememberMe },
|
||||
LoginWithU2FViewModel = (await _u2FService.HasDevices(user.Id)) ? await BuildU2FViewModel(rememberMe, user) : null
|
||||
LoginWithU2FViewModel = (await _u2FService.HasDevices(user.Id)) ? await BuildU2FViewModel(rememberMe, user) : null,
|
||||
LoginWithFido2ViewModel = (await _fido2Service.HasCredentials(user.Id)) ? await BuildFido2ViewModel(rememberMe, user) : null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -326,7 +402,8 @@ namespace BTCPayServer.Controllers
|
||||
return View("SecondaryLogin", new SecondaryLoginViewModel()
|
||||
{
|
||||
LoginWith2FaViewModel = model,
|
||||
LoginWithU2FViewModel = (await _u2FService.HasDevices(user.Id)) ? await BuildU2FViewModel(rememberMe, user) : null
|
||||
LoginWithU2FViewModel = (await _u2FService.HasDevices(user.Id)) ? await BuildU2FViewModel(rememberMe, user) : null,
|
||||
LoginWithFido2ViewModel = (await _fido2Service.HasCredentials(user.Id)) ? await BuildFido2ViewModel(rememberMe, user) : null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user