mirror of
https://github.com/bitwarden/server
synced 2025-12-29 06:33:43 +00:00
[PM-24192] Move account recovery logic to command (#6184)
* Move account recovery logic to command (temporarily duplicated behind feature flag) * Move permission checks to authorization handler * Prevent user from recovering provider member account unless they are also provider member
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Platform.Push;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.AccountRecovery;
|
||||
|
||||
public class AdminRecoverAccountCommand(IOrganizationRepository organizationRepository,
|
||||
IPolicyRepository policyRepository,
|
||||
IUserRepository userRepository,
|
||||
IMailService mailService,
|
||||
IEventService eventService,
|
||||
IPushNotificationService pushNotificationService,
|
||||
IUserService userService,
|
||||
TimeProvider timeProvider) : IAdminRecoverAccountCommand
|
||||
{
|
||||
public async Task<IdentityResult> RecoverAccountAsync(Guid orgId,
|
||||
OrganizationUser organizationUser, string newMasterPassword, string key)
|
||||
{
|
||||
// Org must be able to use reset password
|
||||
var org = await organizationRepository.GetByIdAsync(orgId);
|
||||
if (org == null || !org.UseResetPassword)
|
||||
{
|
||||
throw new BadRequestException("Organization does not allow password reset.");
|
||||
}
|
||||
|
||||
// Enterprise policy must be enabled
|
||||
var resetPasswordPolicy =
|
||||
await policyRepository.GetByOrganizationIdTypeAsync(orgId, PolicyType.ResetPassword);
|
||||
if (resetPasswordPolicy == null || !resetPasswordPolicy.Enabled)
|
||||
{
|
||||
throw new BadRequestException("Organization does not have the password reset policy enabled.");
|
||||
}
|
||||
|
||||
// Org User must be confirmed and have a ResetPasswordKey
|
||||
if (organizationUser == null ||
|
||||
organizationUser.Status != OrganizationUserStatusType.Confirmed ||
|
||||
organizationUser.OrganizationId != orgId ||
|
||||
string.IsNullOrEmpty(organizationUser.ResetPasswordKey) ||
|
||||
!organizationUser.UserId.HasValue)
|
||||
{
|
||||
throw new BadRequestException("Organization User not valid");
|
||||
}
|
||||
|
||||
var user = await userService.GetUserByIdAsync(organizationUser.UserId.Value);
|
||||
if (user == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (user.UsesKeyConnector)
|
||||
{
|
||||
throw new BadRequestException("Cannot reset password of a user with Key Connector.");
|
||||
}
|
||||
|
||||
var result = await userService.UpdatePasswordHash(user, newMasterPassword);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
user.RevisionDate = user.AccountRevisionDate = timeProvider.GetUtcNow().UtcDateTime;
|
||||
user.LastPasswordChangeDate = user.RevisionDate;
|
||||
user.ForcePasswordReset = true;
|
||||
user.Key = key;
|
||||
|
||||
await userRepository.ReplaceAsync(user);
|
||||
await mailService.SendAdminResetPasswordEmailAsync(user.Email, user.Name, org.DisplayName());
|
||||
await eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_AdminResetPassword);
|
||||
await pushNotificationService.PushLogOutAsync(user.Id);
|
||||
|
||||
return IdentityResult.Success;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.AccountRecovery;
|
||||
|
||||
/// <summary>
|
||||
/// A command used to recover an organization user's account by an organization admin.
|
||||
/// </summary>
|
||||
public interface IAdminRecoverAccountCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Recovers an organization user's account by resetting their master password.
|
||||
/// </summary>
|
||||
/// <param name="orgId">The organization the user belongs to.</param>
|
||||
/// <param name="organizationUser">The organization user being recovered.</param>
|
||||
/// <param name="newMasterPassword">The user's new master password hash.</param>
|
||||
/// <param name="key">The user's new master-password-sealed user key.</param>
|
||||
/// <returns>An IdentityResult indicating success or failure.</returns>
|
||||
/// <exception cref="BadRequestException">When organization settings, policy, or user state is invalid.</exception>
|
||||
/// <exception cref="NotFoundException">When the user does not exist.</exception>
|
||||
Task<IdentityResult> RecoverAccountAsync(Guid orgId, OrganizationUser organizationUser,
|
||||
string newMasterPassword, string key);
|
||||
}
|
||||
Reference in New Issue
Block a user