Feature: Plugin can extend whether an account can login or not

This commit is contained in:
Nicolas Dorier
2025-11-14 15:56:14 +09:00
parent a914d798f3
commit 41f5588257
13 changed files with 440 additions and 288 deletions

View File

@@ -21,6 +21,7 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
@@ -31,60 +32,32 @@ using NicolasDorier.RateLimits;
namespace BTCPayServer.Controllers
{
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public class UIAccountController : Controller
public class UIAccountController(
UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager,
SignInManager<ApplicationUser> signInManager,
PoliciesSettings policiesSettings,
SettingsRepository settingsRepository,
Configuration.BTCPayServerOptions options,
BTCPayServerEnvironment btcPayServerEnvironment,
EventAggregator eventAggregator,
Fido2Service fido2Service,
UserLoginCodeService userLoginCodeService,
LnurlAuthService lnurlAuthService,
EmailSenderFactory emailSenderFactory,
CallbackGenerator callbackGenerator,
IStringLocalizer stringLocalizer,
ViewLocalizer viewLocalizer,
UserService userService,
Logs logs)
: Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
readonly RoleManager<IdentityRole> _RoleManager;
readonly Configuration.BTCPayServerOptions _Options;
private readonly BTCPayServerEnvironment _btcPayServerEnvironment;
readonly SettingsRepository _SettingsRepository;
private readonly Fido2Service _fido2Service;
private readonly LnurlAuthService _lnurlAuthService;
private readonly CallbackGenerator _callbackGenerator;
private readonly UserLoginCodeService _userLoginCodeService;
private readonly EventAggregator _eventAggregator;
readonly ILogger _logger;
readonly ILogger _logger = logs.PayServer;
public PoliciesSettings PoliciesSettings { get; }
public EmailSenderFactory EmailSenderFactory { get; }
public IStringLocalizer StringLocalizer { get; }
public Logs Logs { get; }
public UIAccountController(
UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager,
SignInManager<ApplicationUser> signInManager,
PoliciesSettings policiesSettings,
SettingsRepository settingsRepository,
Configuration.BTCPayServerOptions options,
BTCPayServerEnvironment btcPayServerEnvironment,
EventAggregator eventAggregator,
Fido2Service fido2Service,
UserLoginCodeService userLoginCodeService,
LnurlAuthService lnurlAuthService,
EmailSenderFactory emailSenderFactory,
CallbackGenerator callbackGenerator,
IStringLocalizer stringLocalizer,
Logs logs)
{
_userManager = userManager;
_signInManager = signInManager;
PoliciesSettings = policiesSettings;
_SettingsRepository = settingsRepository;
_RoleManager = roleManager;
_Options = options;
_btcPayServerEnvironment = btcPayServerEnvironment;
_fido2Service = fido2Service;
_lnurlAuthService = lnurlAuthService;
EmailSenderFactory = emailSenderFactory;
_callbackGenerator = callbackGenerator;
_userLoginCodeService = userLoginCodeService;
_eventAggregator = eventAggregator;
_logger = logs.PayServer;
Logs = logs;
StringLocalizer = stringLocalizer;
}
public PoliciesSettings PoliciesSettings { get; } = policiesSettings;
public EmailSenderFactory EmailSenderFactory { get; } = emailSenderFactory;
public IStringLocalizer StringLocalizer { get; } = stringLocalizer;
public Logs Logs { get; } = logs;
[TempData]
public string ErrorMessage
@@ -152,31 +125,31 @@ namespace BTCPayServer.Controllers
if (!string.IsNullOrEmpty(loginCode))
{
var code = loginCode.Split(';').First();
var userId = _userLoginCodeService.Verify(code);
var userId = userLoginCodeService.Verify(code);
if (userId is null)
{
TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Login code was invalid"].Value;
return await Login(returnUrl);
}
var user = await _userManager.FindByIdAsync(userId);
if (!UserService.TryCanLogin(user, out var message))
var user = await userManager.FindByIdAsync(userId);
var loginContext = CreateLoginContext(user);
if (!await userService.CanLogin(loginContext))
{
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = user == null ? StatusMessageModel.StatusSeverity.Error : StatusMessageModel.StatusSeverity.Warning,
Message = message
});
TempData.SetStatusLoginResult(loginContext);
return await Login(returnUrl);
}
_logger.LogInformation("User {Email} logged in with a login code", user!.Email);
await _signInManager.SignInAsync(user, false, "LoginCode");
await signInManager.SignInAsync(user, false, "LoginCode");
return RedirectToLocal(returnUrl);
}
return await Login(returnUrl);
}
private UserService.CanLoginContext CreateLoginContext(ApplicationUser user)
=> new(user, StringLocalizer, viewLocalizer, this.HttpContext.Request.GetRequestBaseUrl());
[HttpPost("/login")]
[AllowAnonymous]
[ValidateAntiForgeryToken]
@@ -192,30 +165,27 @@ namespace BTCPayServer.Controllers
if (ModelState.IsValid)
{
// Require the user to pass basic checks (approval, confirmed email, not disabled) before they can log on
var user = await _userManager.FindByEmailAsync(model.Email);
var user = await userManager.FindByEmailAsync(model.Email);
var errorMessage = StringLocalizer["Invalid login attempt."].Value;
if (!UserService.TryCanLogin(user, out var message))
var loginContext = CreateLoginContext(user);
if (!await userService.CanLogin(loginContext))
{
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = user == null ? StatusMessageModel.StatusSeverity.Error : StatusMessageModel.StatusSeverity.Warning,
Message = message
});
TempData.SetStatusLoginResult(loginContext);
return View(model);
}
var fido2Devices = await _fido2Service.HasCredentials(user!.Id);
var lnurlAuthCredentials = await _lnurlAuthService.HasCredentials(user.Id);
if (!await _userManager.IsLockedOutAsync(user) && (fido2Devices || lnurlAuthCredentials))
var fido2Devices = await fido2Service.HasCredentials(user!.Id);
var lnurlAuthCredentials = await lnurlAuthService.HasCredentials(user.Id);
if (!await userManager.IsLockedOutAsync(user) && (fido2Devices || lnurlAuthCredentials))
{
if (await _userManager.CheckPasswordAsync(user, model.Password))
if (await userManager.CheckPasswordAsync(user, model.Password))
{
LoginWith2faViewModel twoFModel = null;
if (user.TwoFactorEnabled)
{
// we need to do an actual sign in attempt so that 2fa can function in next step
await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: true);
await signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: true);
twoFModel = new LoginWith2faViewModel
{
RememberMe = model.RememberMe
@@ -230,12 +200,12 @@ namespace BTCPayServer.Controllers
});
}
await _userManager.AccessFailedAsync(user);
await userManager.AccessFailedAsync(user);
ModelState.AddModelError(string.Empty, errorMessage!);
return View(model);
}
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: true);
var result = await signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
_logger.LogInformation("User {Email} logged in", user.Email);
@@ -267,9 +237,9 @@ namespace BTCPayServer.Controllers
private async Task<LoginWithFido2ViewModel> BuildFido2ViewModel(bool rememberMe, ApplicationUser user)
{
if (_btcPayServerEnvironment.IsSecure(HttpContext))
if (btcPayServerEnvironment.IsSecure(HttpContext))
{
var r = await _fido2Service.RequestLogin(user.Id);
var r = await fido2Service.RequestLogin(user.Id);
if (r is null)
{
return null;
@@ -286,9 +256,9 @@ namespace BTCPayServer.Controllers
private async Task<LoginWithLNURLAuthViewModel> BuildLNURLAuthViewModel(bool rememberMe, ApplicationUser user)
{
if (_btcPayServerEnvironment.IsSecure(HttpContext))
if (btcPayServerEnvironment.IsSecure(HttpContext))
{
var r = await _lnurlAuthService.RequestLogin(user.Id);
var r = await lnurlAuthService.RequestLogin(user.Id);
if (r is null)
{
return null;
@@ -297,7 +267,7 @@ namespace BTCPayServer.Controllers
{
RememberMe = rememberMe,
UserId = user.Id,
LNURLEndpoint = new Uri(_callbackGenerator.ForLNUrlAuth(user, r, Request))
LNURLEndpoint = new Uri(callbackGenerator.ForLNUrlAuth(user, r, Request))
};
}
return null;
@@ -315,25 +285,22 @@ namespace BTCPayServer.Controllers
ViewData["ReturnUrl"] = returnUrl;
var errorMessage = StringLocalizer["Invalid login attempt."].Value;
var user = await _userManager.FindByIdAsync(viewModel.UserId);
if (!UserService.TryCanLogin(user, out var message))
var user = await userManager.FindByIdAsync(viewModel.UserId);
var loggingContext = CreateLoginContext(user);
if (!await userService.CanLogin(loggingContext))
{
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = user == null ? StatusMessageModel.StatusSeverity.Error : StatusMessageModel.StatusSeverity.Warning,
Message = message
});
TempData.SetStatusLoginResult(loggingContext);
return RedirectToAction("Login");
}
try
{
var k1 = Encoders.Hex.DecodeData(viewModel.LNURLEndpoint.ParseQueryString().Get("k1"));
if (_lnurlAuthService.FinalLoginStore.TryRemove(viewModel.UserId, out var storedk1) &&
if (lnurlAuthService.FinalLoginStore.TryRemove(viewModel.UserId, out var storedk1) &&
storedk1.SequenceEqual(k1))
{
_lnurlAuthService.FinalLoginStore.TryRemove(viewModel.UserId, out _);
await _signInManager.SignInAsync(user!, viewModel.RememberMe, "FIDO2");
lnurlAuthService.FinalLoginStore.TryRemove(viewModel.UserId, out _);
await signInManager.SignInAsync(user!, viewModel.RememberMe, "FIDO2");
_logger.LogInformation("User logged in");
return RedirectToLocal(returnUrl);
}
@@ -349,7 +316,7 @@ namespace BTCPayServer.Controllers
}
return View("SecondaryLogin", new SecondaryLoginViewModel
{
LoginWithFido2ViewModel = await _fido2Service.HasCredentials(user!.Id) ? await BuildFido2ViewModel(viewModel.RememberMe, user) : null,
LoginWithFido2ViewModel = await fido2Service.HasCredentials(user!.Id) ? await BuildFido2ViewModel(viewModel.RememberMe, user) : null,
LoginWithLNURLAuthViewModel = viewModel,
LoginWith2FaViewModel = !user.TwoFactorEnabled
? null
@@ -372,22 +339,19 @@ namespace BTCPayServer.Controllers
ViewData["ReturnUrl"] = returnUrl;
var errorMessage = "Invalid login attempt.";
var user = await _userManager.FindByIdAsync(viewModel.UserId);
if (!UserService.TryCanLogin(user, out var message))
var user = await userManager.FindByIdAsync(viewModel.UserId);
var loginContext = CreateLoginContext(user);
if (!await userService.CanLogin(loginContext))
{
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = user == null ? StatusMessageModel.StatusSeverity.Error : StatusMessageModel.StatusSeverity.Warning,
Message = message
});
TempData.SetStatusLoginResult(loginContext);
return RedirectToAction("Login");
}
try
{
if (await _fido2Service.CompleteLogin(viewModel.UserId, System.Text.Json.JsonSerializer.Deserialize<AuthenticatorAssertionRawResponse>(viewModel.Response)))
if (await fido2Service.CompleteLogin(viewModel.UserId, System.Text.Json.JsonSerializer.Deserialize<AuthenticatorAssertionRawResponse>(viewModel.Response)))
{
await _signInManager.SignInAsync(user!, viewModel.RememberMe, "FIDO2");
await signInManager.SignInAsync(user!, viewModel.RememberMe, "FIDO2");
_logger.LogInformation("User {Email} logged in with FIDO2", user.Email);
return RedirectToLocal(returnUrl);
}
@@ -405,7 +369,7 @@ namespace BTCPayServer.Controllers
return View("SecondaryLogin", new SecondaryLoginViewModel
{
LoginWithFido2ViewModel = viewModel,
LoginWithLNURLAuthViewModel = await _lnurlAuthService.HasCredentials(user!.Id) ? await BuildLNURLAuthViewModel(viewModel.RememberMe, user) : null,
LoginWithLNURLAuthViewModel = await lnurlAuthService.HasCredentials(user!.Id) ? await BuildLNURLAuthViewModel(viewModel.RememberMe, user) : null,
LoginWith2FaViewModel = !user.TwoFactorEnabled
? null
: new LoginWith2faViewModel
@@ -425,7 +389,7 @@ namespace BTCPayServer.Controllers
}
// Ensure the user has gone through the username & password screen first
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
var user = await signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
throw new ApplicationException($"Unable to load two-factor authentication user.");
@@ -436,8 +400,8 @@ namespace BTCPayServer.Controllers
return View("SecondaryLogin", new SecondaryLoginViewModel
{
LoginWith2FaViewModel = new LoginWith2faViewModel { RememberMe = rememberMe },
LoginWithFido2ViewModel = await _fido2Service.HasCredentials(user.Id) ? await BuildFido2ViewModel(rememberMe, user) : null,
LoginWithLNURLAuthViewModel = await _lnurlAuthService.HasCredentials(user.Id) ? await BuildLNURLAuthViewModel(rememberMe, user) : null,
LoginWithFido2ViewModel = await fido2Service.HasCredentials(user.Id) ? await BuildFido2ViewModel(rememberMe, user) : null,
LoginWithLNURLAuthViewModel = await lnurlAuthService.HasCredentials(user.Id) ? await BuildLNURLAuthViewModel(rememberMe, user) : null,
});
}
@@ -456,23 +420,16 @@ namespace BTCPayServer.Controllers
return View(model);
}
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
var user = await signInManager.GetTwoFactorAuthenticationUserAsync();
var loginContext = CreateLoginContext(user);
if (!await userService.CanLogin(loginContext))
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!UserService.TryCanLogin(user, out var message))
{
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Warning,
Message = message
});
TempData.SetStatusLoginResult(loginContext);
return View(model);
}
var authenticatorCode = model.TwoFactorCode.Replace(" ", string.Empty, StringComparison.InvariantCulture).Replace("-", string.Empty, StringComparison.InvariantCulture);
var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, model.RememberMachine);
var result = await signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, model.RememberMachine);
if (result.Succeeded)
{
_logger.LogInformation("User {Email} logged in with 2FA", user.Email);
@@ -484,8 +441,8 @@ namespace BTCPayServer.Controllers
return View("SecondaryLogin", new SecondaryLoginViewModel
{
LoginWith2FaViewModel = model,
LoginWithFido2ViewModel = await _fido2Service.HasCredentials(user.Id) ? await BuildFido2ViewModel(rememberMe, user) : null,
LoginWithLNURLAuthViewModel = await _lnurlAuthService.HasCredentials(user.Id) ? await BuildLNURLAuthViewModel(rememberMe, user) : null,
LoginWithFido2ViewModel = await fido2Service.HasCredentials(user.Id) ? await BuildFido2ViewModel(rememberMe, user) : null,
LoginWithLNURLAuthViewModel = await lnurlAuthService.HasCredentials(user.Id) ? await BuildLNURLAuthViewModel(rememberMe, user) : null,
});
}
@@ -499,7 +456,7 @@ namespace BTCPayServer.Controllers
}
// Ensure the user has gone through the username & password screen first
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
var user = await signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
throw new ApplicationException($"Unable to load two-factor authentication user.");
@@ -525,23 +482,16 @@ namespace BTCPayServer.Controllers
return View(model);
}
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
var user = await signInManager.GetTwoFactorAuthenticationUserAsync();
var loginContext = CreateLoginContext(user);
if (!await userService.CanLogin(loginContext))
{
throw new ApplicationException("Unable to load two-factor authentication user.");
}
if (!UserService.TryCanLogin(user, out var message))
{
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Warning,
Message = message
});
TempData.SetStatusLoginResult(loginContext);
return View(model);
}
var recoveryCode = model.RecoveryCode.Replace(" ", string.Empty, StringComparison.InvariantCulture);
var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);
var result = await signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);
if (result.Succeeded)
{
_logger.LogInformation("User {Email} logged in with a recovery code", user.Email);
@@ -592,14 +542,14 @@ namespace BTCPayServer.Controllers
ViewData["ReturnUrl"] = returnUrl;
ViewData["Logon"] = logon.ToString(CultureInfo.InvariantCulture).ToLowerInvariant();
var policies = await _SettingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
var policies = await settingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
if (policies.LockSubscription && !User.IsInRole(Roles.ServerAdmin))
return RedirectToAction(nameof(UIHomeController.Index), "UIHome");
if (ModelState.IsValid)
{
var anyAdmin = (await _userManager.GetUsersInRoleAsync(Roles.ServerAdmin)).Any();
var isFirstAdmin = !anyAdmin || (model.IsAdmin && _Options.CheatMode);
var anyAdmin = (await userManager.GetUsersInRoleAsync(Roles.ServerAdmin)).Any();
var isFirstAdmin = !anyAdmin || (model.IsAdmin && options.CheatMode);
var user = new ApplicationUser
{
UserName = model.Email,
@@ -609,21 +559,21 @@ namespace BTCPayServer.Controllers
Created = DateTimeOffset.UtcNow,
Approved = isFirstAdmin // auto-approve first admin
};
var result = await _userManager.CreateAsync(user, model.Password);
var result = await userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
if (isFirstAdmin)
{
await _RoleManager.CreateAsync(new IdentityRole(Roles.ServerAdmin));
await _userManager.AddToRoleAsync(user, Roles.ServerAdmin);
var settings = await _SettingsRepository.GetSettingAsync<ThemeSettings>() ?? new ThemeSettings();
await roleManager.CreateAsync(new IdentityRole(Roles.ServerAdmin));
await userManager.AddToRoleAsync(user, Roles.ServerAdmin);
var settings = await settingsRepository.GetSettingAsync<ThemeSettings>() ?? new ThemeSettings();
settings.FirstRun = false;
await _SettingsRepository.UpdateSetting(settings);
await _SettingsRepository.FirstAdminRegistered(policies, _Options.UpdateUrl != null, _Options.DisableRegistration, Logs);
await settingsRepository.UpdateSetting(settings);
await settingsRepository.FirstAdminRegistered(policies, options.UpdateUrl != null, options.DisableRegistration, Logs);
RegisteredAdmin = true;
}
_eventAggregator.Publish(await UserEvent.Registered.Create(user, _callbackGenerator, Request));
eventAggregator.Publish(await UserEvent.Registered.Create(user, callbackGenerator, Request));
RegisteredUserId = user.Id;
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Account created."].Value;
@@ -643,7 +593,7 @@ namespace BTCPayServer.Controllers
}
if (logon)
{
await _signInManager.SignInAsync(user, isPersistent: false);
await signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation("User {Email} logged in", user.Email);
return RedirectToLocal(returnUrl);
}
@@ -665,9 +615,9 @@ namespace BTCPayServer.Controllers
[HttpGet("/logout")]
public async Task<IActionResult> Logout()
{
var userId = _signInManager.UserManager.GetUserId(HttpContext.User);
var user = await _userManager.FindByIdAsync(userId);
await _signInManager.SignOutAsync();
var userId = signInManager.UserManager.GetUserId(HttpContext.User);
var user = await userManager.FindByIdAsync(userId);
await signInManager.SignOutAsync();
HttpContext.DeleteUserPrefsCookie();
_logger.LogInformation("User {Email} logged out", user!.Email);
return RedirectToAction(nameof(Login));
@@ -681,19 +631,19 @@ namespace BTCPayServer.Controllers
{
return RedirectToAction(nameof(UIHomeController.Index), "UIHome");
}
var user = await _userManager.FindByIdAsync(userId);
var user = await userManager.FindByIdAsync(userId);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{userId}'.");
}
var result = await _userManager.ConfirmEmailAsync(user, code);
var result = await userManager.ConfirmEmailAsync(user, code);
if (result.Succeeded)
{
var approvalLink = _callbackGenerator.ForApproval(user, Request);
_eventAggregator.Publish(new UserEvent.ConfirmedEmail(user, approvalLink));
var approvalLink = callbackGenerator.ForApproval(user, Request);
eventAggregator.Publish(new UserEvent.ConfirmedEmail(user, approvalLink));
var hasPassword = await _userManager.HasPasswordAsync(user);
var hasPassword = await userManager.HasPasswordAsync(user);
if (hasPassword)
{
TempData.SetStatusMessageModel(new StatusMessageModel
@@ -730,14 +680,14 @@ namespace BTCPayServer.Controllers
{
if (ModelState.IsValid && await EmailSenderFactory.IsComplete())
{
var user = await _userManager.FindByEmailAsync(model.Email);
if (!UserService.TryCanLogin(user, out _))
var user = await userManager.FindByEmailAsync(model.Email);
var loginContext = CreateLoginContext(user);
if (!await userService.CanLogin(loginContext))
{
// Don't reveal that the user does not exist or is not confirmed
return RedirectToAction(nameof(ForgotPasswordConfirmation));
}
var callbackUri = await _callbackGenerator.ForPasswordReset(user, Request);
_eventAggregator.Publish(new UserEvent.PasswordResetRequested(user, callbackUri));
var callbackUri = await callbackGenerator.ForPasswordReset(user, Request);
eventAggregator.Publish(new UserEvent.PasswordResetRequested(user, callbackUri));
return RedirectToAction(nameof(ForgotPasswordConfirmation));
}
@@ -761,8 +711,8 @@ namespace BTCPayServer.Controllers
throw new ApplicationException("A code must be supplied for this action.");
}
var user = string.IsNullOrEmpty(userId) ? null : await _userManager.FindByIdAsync(userId);
var hasPassword = user != null && await _userManager.HasPasswordAsync(user);
var user = string.IsNullOrEmpty(userId) ? null : await userManager.FindByIdAsync(userId);
var hasPassword = user != null && await userManager.HasPasswordAsync(user);
if (!string.IsNullOrEmpty(userId))
{
email = user?.Email;
@@ -787,17 +737,17 @@ namespace BTCPayServer.Controllers
return View(model);
}
var user = await _userManager.FindByEmailAsync(model.Email);
var hasPassword = user != null && await _userManager.HasPasswordAsync(user);
var needsInitialPassword = user != null && !await _userManager.HasPasswordAsync(user);
// Let unapproved users set a password. Otherwise, don't reveal that the user does not exist.
if (!UserService.TryCanLogin(user, out var message) && !needsInitialPassword || user == null)
{
_logger.LogWarning("User {Email} tried to reset password, but failed: {Message}", user?.Email ?? "(NO EMAIL)", message);
var user = await userManager.FindByEmailAsync(model.Email);
if (user is null)
return RedirectToAction(nameof(Login));
var hasPassword = await userManager.HasPasswordAsync(user);
var needsInitialPassword = !await userManager.HasPasswordAsync(user);
// Let unapproved users set a password. Otherwise, don't reveal that the user does not exist.
var loginContext = CreateLoginContext(user);
if (!await userService.CanLogin(loginContext) && !needsInitialPassword)
return RedirectToAction(nameof(Login));
}
var result = await _userManager.ResetPasswordAsync(user!, model.Code, model.Password);
var result = await userManager.ResetPasswordAsync(user!, model.Code, model.Password);
if (result.Succeeded)
{
TempData.SetStatusMessageModel(new StatusMessageModel
@@ -809,12 +759,12 @@ namespace BTCPayServer.Controllers
});
// see if we can sign in user after accepting an invitation and setting the password
if (needsInitialPassword && UserService.TryCanLogin(user, out _))
loginContext = CreateLoginContext(user);
if (needsInitialPassword && await userService.CanLogin(loginContext))
{
var signInResult = await _signInManager.PasswordSignInAsync(user.Email!, model.Password, true, true);
var signInResult = await signInManager.PasswordSignInAsync(user.Email!, model.Password, true, true);
if (signInResult.Succeeded)
{
_logger.LogInformation("User {Email} logged in", user.Email);
return RedirectToLocal(returnUrl);
}
}
@@ -822,7 +772,7 @@ namespace BTCPayServer.Controllers
}
AddErrors(result);
model.HasPassword = await _userManager.HasPasswordAsync(user);
model.HasPassword = await userManager.HasPasswordAsync(user);
return View(model);
}
@@ -835,14 +785,14 @@ namespace BTCPayServer.Controllers
return NotFound();
}
var user = await _userManager.FindByInvitationTokenAsync<ApplicationUser>(userId, Uri.UnescapeDataString(code));
var user = await userManager.FindByInvitationTokenAsync<ApplicationUser>(userId, Uri.UnescapeDataString(code));
if (user == null)
{
return NotFound();
}
var requiresEmailConfirmation = user.RequiresEmailConfirmation && !user.EmailConfirmed;
var requiresSetPassword = !await _userManager.HasPasswordAsync(user);
var requiresSetPassword = !await userManager.HasPasswordAsync(user);
if (requiresEmailConfirmation)
{
return await RedirectToConfirmEmail(user);
@@ -868,13 +818,13 @@ namespace BTCPayServer.Controllers
private async Task<IActionResult> RedirectToConfirmEmail(ApplicationUser user)
{
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var code = await userManager.GenerateEmailConfirmationTokenAsync(user);
return RedirectToAction(nameof(ConfirmEmail), new { userId = user.Id, code });
}
private async Task<IActionResult> RedirectToSetPassword(ApplicationUser user)
{
var code = await _userManager.GeneratePasswordResetTokenAsync(user);
var code = await userManager.GeneratePasswordResetTokenAsync(user);
return RedirectToAction(nameof(SetPassword), new { userId = user.Id, email = user.Email, code });
}
@@ -912,7 +862,7 @@ namespace BTCPayServer.Controllers
private bool CanLoginOrRegister()
{
return _btcPayServerEnvironment.IsDeveloping || _btcPayServerEnvironment.IsSecure(HttpContext);
return btcPayServerEnvironment.IsDeveloping || btcPayServerEnvironment.IsSecure(HttpContext);
}
private void SetInsecureFlags()