821 lines
32 KiB
C#
821 lines
32 KiB
C#
using e_suite.API.Common.exceptions;
|
|
using e_suite.API.Common.extensions;
|
|
using e_suite.API.Common.models;
|
|
using e_suite.API.Common.repository;
|
|
using e_suite.Database.Audit;
|
|
using e_suite.Database.Core.Models;
|
|
using e_suite.Database.Core.Tables.UserManager;
|
|
using e_suite.Modules.UserManager.Facade;
|
|
using e_suite.Modules.UserManager.Services;
|
|
using e_suite.Nuget.PasswordHasher;
|
|
using e_suite.Utilities.Pagination;
|
|
using eSuite.Core.Clock;
|
|
using eSuite.Core.MailService;
|
|
using eSuite.Core.Miscellaneous;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.Extensions.Configuration;
|
|
using System.ComponentModel.DataAnnotations;
|
|
using System.Linq.Expressions;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using e_suite.Modules.UserManager.Extensions;
|
|
using IUserManager = e_suite.API.Common.IUserManager;
|
|
using System.Data;
|
|
|
|
namespace e_suite.Modules.UserManager;
|
|
|
|
public partial class UserManager : IUserManager
|
|
{
|
|
private readonly IConfiguration _configuration;
|
|
private readonly IPasswordHasher<IPassword> _passwordHasher;
|
|
private readonly ITwoFactorAuthenticator _twoFactorAuthenticator;
|
|
private readonly IJwtService _jwtService;
|
|
private readonly IMailService _mailService;
|
|
private readonly IRandomNumberGenerator _randomNumberGenerator;
|
|
private readonly IClock _clock;
|
|
|
|
private readonly IUserManagerRepository _userManagerRepository;
|
|
private readonly IDomainRepository _domainRepository;
|
|
|
|
public UserManager(IConfiguration configuration, IPasswordHasher<IPassword> passwordHasher, ITwoFactorAuthenticator twoFactorAuthenticator, IJwtService jwtService, IMailService mailService, IRandomNumberGenerator randomNumberGenerator, IClock clock, IUserManagerRepository userManagerRepository, IDomainRepository domainRepository)
|
|
{
|
|
_configuration = configuration;
|
|
_passwordHasher = passwordHasher;
|
|
_twoFactorAuthenticator = twoFactorAuthenticator;
|
|
_jwtService = jwtService;
|
|
_mailService = mailService;
|
|
_randomNumberGenerator = randomNumberGenerator;
|
|
_clock = clock;
|
|
_userManagerRepository = userManagerRepository;
|
|
_domainRepository = domainRepository;
|
|
}
|
|
|
|
private void HashPassword(User user, string password)
|
|
{
|
|
user.Password = _passwordHasher.HashPassword(user, password);
|
|
}
|
|
|
|
private string GetEmailUserActionUrl(User existingUser, EmailUserAction emailConfirmation)
|
|
{
|
|
var emailToConfirmationToken = new EmailActionToken
|
|
{
|
|
Email = existingUser.Email,
|
|
EmailActionType = emailConfirmation.EmailActionType,
|
|
Token = emailConfirmation.Token
|
|
};
|
|
|
|
var jsonString = JsonSerializer.Serialize(emailToConfirmationToken);
|
|
var encodedBase64String = Convert.ToBase64String(Encoding.UTF8.GetBytes(jsonString));
|
|
|
|
var baseUrl = _configuration.GetConfigValue("BASE_URL", "baseUrl", "http://localhost:3000");
|
|
var confirmEmail = $"{baseUrl?.Trim().TrimEnd('/')}/emailuseraction/{encodedBase64String}";
|
|
return confirmEmail;
|
|
}
|
|
|
|
private async Task SendEmailUserAction(AuditUserDetails auditUserDetails, User user, EmailUserActionType type,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
if (user == null)
|
|
throw new Exception("User not found");
|
|
|
|
if (!user.Active)
|
|
throw new NotFoundException("Active User not found");
|
|
|
|
var emailUserAction = await CreateEmailUserAction(auditUserDetails, user, type, cancellationToken);
|
|
var emailUserActionUrl = GetEmailUserActionUrl(user, emailUserAction);
|
|
await SentEmailRequest(user, emailUserActionUrl, type, cancellationToken);
|
|
}
|
|
|
|
private async Task<EmailUserAction> CreateEmailUserAction(AuditUserDetails auditUserDetails, User existingUser,
|
|
EmailUserActionType type, CancellationToken cancellationToken)
|
|
{
|
|
var emailTimeout = GetEmailTimeoutHours();
|
|
|
|
var emailConfirmation = new EmailUserAction
|
|
{
|
|
Created = _clock.GetNow,
|
|
Expires = _clock.GetNow.AddHours(emailTimeout),
|
|
EmailActionType = type,
|
|
Token = Guid.NewGuid(),
|
|
UserId = existingUser.Id
|
|
};
|
|
|
|
await _userManagerRepository.AddEmailUserAction(auditUserDetails, emailConfirmation, cancellationToken);
|
|
return emailConfirmation;
|
|
}
|
|
|
|
private int GetEmailTimeoutHours()
|
|
{
|
|
return _configuration.GetValue("Smtp:EmailTimeoutHours", 48);
|
|
}
|
|
|
|
private async Task SentEmailRequest(User user, string emailUserActionUrl, EmailUserActionType type, CancellationToken cancellationToken)
|
|
{
|
|
var emailRequest = new MailRequest();
|
|
emailRequest.To.Add(user);
|
|
emailRequest.EmailType = GetMailType(type);
|
|
emailRequest.Parameters.Add("url", emailUserActionUrl);
|
|
|
|
await _mailService.RequestEMailAsync(emailRequest, cancellationToken);
|
|
}
|
|
|
|
private async Task SendEmailRequest(User user, MailType mailType, CancellationToken cancellationToken)
|
|
{
|
|
var emailRequest = new MailRequest();
|
|
emailRequest.To.Add(user);
|
|
emailRequest.EmailType = mailType;
|
|
|
|
await _mailService.RequestEMailAsync(emailRequest, cancellationToken);
|
|
}
|
|
|
|
private static MailType GetMailType(EmailUserActionType type)
|
|
{
|
|
return type switch
|
|
{
|
|
EmailUserActionType.ConfirmEmailAddress => MailType.ConfirmEmailAddress,
|
|
EmailUserActionType.DisableAuthenticator => MailType.DisableAuthenticator,
|
|
EmailUserActionType.PasswordReset => MailType.PasswordReset,
|
|
_ => throw new ArgumentException(message: $"{type} cannot be translated to and EmailType.", paramName: nameof(type))
|
|
};
|
|
}
|
|
|
|
public async Task<LoginResponse> Login(Login userLogin, CancellationToken cancellationToken)
|
|
{
|
|
var user = await _userManagerRepository.GetUserByEmail(userLogin.Email, cancellationToken);
|
|
|
|
if (user == null)
|
|
{
|
|
return new LoginResponse
|
|
{
|
|
Result = LoginResult.Failed
|
|
};
|
|
}
|
|
|
|
var result = _passwordHasher.VerifyHashedPassword(user, user.Password, userLogin.Password);
|
|
|
|
if (result == PasswordVerificationResult.Failed)
|
|
{
|
|
return new LoginResponse
|
|
{
|
|
Result = LoginResult.Failed
|
|
};
|
|
}
|
|
|
|
var auditUserDetails = new AuditUserDetails
|
|
{
|
|
UserId = user.Id,
|
|
UserDisplayName = user.DisplayName
|
|
};
|
|
|
|
if (result == PasswordVerificationResult.SuccessRehashNeeded)
|
|
{
|
|
HashPassword(user, userLogin.Password);
|
|
|
|
await _userManagerRepository.EditUser(auditUserDetails, user, cancellationToken);
|
|
}
|
|
|
|
if (userLogin.RequestTfaRemoval)
|
|
{
|
|
await SendEmailUserAction(auditUserDetails, user, EmailUserActionType.DisableAuthenticator, cancellationToken);
|
|
|
|
return new LoginResponse
|
|
{
|
|
Result = LoginResult.TwoFactorAuthenticationRemovalRequested
|
|
};
|
|
}
|
|
|
|
if (user.UsingTwoFactorAuthentication)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(userLogin.SecurityCode))
|
|
{
|
|
return new LoginResponse
|
|
{
|
|
Result = LoginResult.TwoFactorAuthenticationCodeRequired
|
|
};
|
|
}
|
|
|
|
var tfaResult =
|
|
_twoFactorAuthenticator.ValidateTwoFactorPIN(user.TwoFactorAuthenticationKey, userLogin.SecurityCode);
|
|
|
|
if (!tfaResult)
|
|
{
|
|
return new LoginResponse
|
|
{
|
|
Result = LoginResult.TwoFactorAuthenticationCodeIncorrect
|
|
};
|
|
}
|
|
}
|
|
|
|
return CreateLoginResponse(user);
|
|
}
|
|
public async Task<LoginResponse> LoginSso(long ssoId, string ssoUserId, CancellationToken cancellationToken = default!)
|
|
{
|
|
var user = await _userManagerRepository.GetUserSsoId(ssoId, ssoUserId, cancellationToken) ?? await _userManagerRepository.GetUserByDomainSsoId(ssoId, ssoUserId, cancellationToken);
|
|
|
|
return CreateLoginResponse(user);
|
|
}
|
|
|
|
private LoginResponse CreateLoginResponse(User? user)
|
|
{
|
|
if (user == null)
|
|
{
|
|
return new LoginResponse
|
|
{
|
|
Result = LoginResult.Failed
|
|
};
|
|
}
|
|
|
|
if (!user.Active)
|
|
{
|
|
return new LoginResponse
|
|
{
|
|
Result = LoginResult.Failed
|
|
};
|
|
}
|
|
|
|
if (!user.EmailConfirmed)
|
|
{
|
|
return new LoginResponse
|
|
{
|
|
Result = LoginResult.EmailNotConfirmed
|
|
};
|
|
}
|
|
|
|
var token = _jwtService.GenerateSecurityToken(user);
|
|
return new LoginResponse
|
|
{
|
|
Result = LoginResult.Success,
|
|
Token = token
|
|
};
|
|
}
|
|
|
|
public async Task CreateUser(AuditUserDetails auditUserDetails, UserRegistration userRegistration, CancellationToken cancellationToken)
|
|
{
|
|
await _userManagerRepository.TransactionAsync(async () =>
|
|
{
|
|
userRegistration.Email = userRegistration.Email.Trim();
|
|
ValidateEmail(userRegistration.Email);
|
|
await CheckForExistingUserByEmail(userRegistration.Email, cancellationToken);
|
|
|
|
var currentUser = await _userManagerRepository.GetUserById(new GeneralIdRef
|
|
{
|
|
Id = auditUserDetails.UserId
|
|
}, cancellationToken);
|
|
|
|
var domainId = currentUser!.DomainId;
|
|
if (userRegistration.DomainId != null )
|
|
{
|
|
var domain = await _userManagerRepository.GetDomainById(userRegistration.DomainId, cancellationToken)
|
|
?? throw new NotFoundException("unable to find domain");
|
|
domainId = domain.Id;
|
|
}
|
|
|
|
var user = new User
|
|
{
|
|
FirstName = userRegistration.FirstName.Trim(),
|
|
MiddleNames = userRegistration.MiddleNames.Trim(),
|
|
LastName = userRegistration.LastName.Trim(),
|
|
Email = userRegistration.Email,
|
|
EmailConfirmed = false,
|
|
DomainId = domainId
|
|
};
|
|
|
|
DisableTwoFactorAuthenticationForUser(user);
|
|
|
|
var password = _randomNumberGenerator.GetRandomString(10);
|
|
|
|
HashPassword(user, password);
|
|
|
|
await _userManagerRepository.AddUser(auditUserDetails, user, cancellationToken);
|
|
|
|
await SendEmailUserAction(auditUserDetails, user, EmailUserActionType.ConfirmEmailAddress, cancellationToken);
|
|
});
|
|
}
|
|
|
|
private async Task CheckForExistingUserByEmail(string emailAddress, CancellationToken cancellationToken)
|
|
{
|
|
var existingUser = await _userManagerRepository.GetUserByEmail(emailAddress, cancellationToken);
|
|
if (existingUser != null)
|
|
throw new ExistsException("User Already Exists");
|
|
}
|
|
|
|
private static void ValidateEmail(string email)
|
|
{
|
|
if (!(new EmailAddressAttribute()).IsValid(email))
|
|
throw new ArgumentException($"{email} is not a valid email address");
|
|
}
|
|
|
|
private void DisableTwoFactorAuthenticationForUser(User user)
|
|
{
|
|
user.UsingTwoFactorAuthentication = false;
|
|
user.TwoFactorAuthenticationKey = GenerateTwoFactorAuthenticationKey();
|
|
}
|
|
|
|
private string GenerateTwoFactorAuthenticationKey()
|
|
{
|
|
return _randomNumberGenerator.GetRandomString(_configuration.GetValue<int>("twoFactorAuthentication:keySize"));
|
|
}
|
|
|
|
public async Task<LoginResponse> RefreshToken(string email, CancellationToken cancellationToken)
|
|
{
|
|
var user = await _userManagerRepository.GetUserByEmail(email, cancellationToken);
|
|
return RefreshToken(user);
|
|
}
|
|
|
|
public async Task<LoginResponse> RefreshToken(IGeneralIdRef id, CancellationToken cancellationToken)
|
|
{
|
|
var user = await _userManagerRepository.GetUserById(id, cancellationToken);
|
|
return RefreshToken(user);
|
|
}
|
|
|
|
private LoginResponse RefreshToken(User? user)
|
|
{
|
|
return CreateLoginResponse(user);
|
|
}
|
|
|
|
public async Task ForgotPassword(string email, CancellationToken cancellationToken)
|
|
{
|
|
await _userManagerRepository.TransactionAsync( async () =>
|
|
{
|
|
var user = await GetUserByEmailAsync(email, cancellationToken);
|
|
|
|
if (!user.EmailConfirmed) {
|
|
throw new EmailNotConfirmedException("Email not confirmed");
|
|
}
|
|
|
|
var auditUserDetails = new AuditUserDetails
|
|
{
|
|
UserId = user.Id,
|
|
UserDisplayName = user.DisplayName
|
|
};
|
|
|
|
await SendEmailUserAction(auditUserDetails, user, EmailUserActionType.PasswordReset, cancellationToken);
|
|
});
|
|
}
|
|
|
|
public async Task<User> CompleteEmailAction(EmailActionToken token, CancellationToken cancellationToken)
|
|
{
|
|
return await _userManagerRepository.TransactionAsync(async () =>
|
|
{
|
|
var emailUserAction = await _userManagerRepository.GetEmailUserAction(token.Token, cancellationToken)
|
|
?? throw new TokenInvalidException("Your email token is invalid");
|
|
|
|
if ((emailUserAction.EmailActionType == EmailUserActionType.ConfirmEmailAddress ||
|
|
emailUserAction.EmailActionType == EmailUserActionType.PasswordReset))
|
|
CheckPasswordStrength(token.Password);
|
|
|
|
var user = emailUserAction.User;
|
|
|
|
if (!user.Email.Equals(token.Email, StringComparison.CurrentCultureIgnoreCase))
|
|
throw new InvalidEmailException("Your email is invalid");
|
|
|
|
switch (emailUserAction.EmailActionType)
|
|
{
|
|
case EmailUserActionType.ConfirmEmailAddress:
|
|
throw new ArgumentException($"EmailActionType {emailUserAction.EmailActionType} not supported, use the UI instead.");
|
|
case EmailUserActionType.DisableAuthenticator:
|
|
DisableTwoFactorAuthenticationForUser(user);
|
|
break;
|
|
case EmailUserActionType.PasswordReset:
|
|
AddPasswordHash(user,token.Password);
|
|
break;
|
|
default:
|
|
throw new ArgumentException($"EmailActionType {emailUserAction.EmailActionType} not implemented");
|
|
}
|
|
|
|
var auditUserDetails = new AuditUserDetails
|
|
{
|
|
UserId = user.Id,
|
|
UserDisplayName = user.DisplayName
|
|
};
|
|
|
|
await _userManagerRepository.EditUser(auditUserDetails, user, cancellationToken);
|
|
|
|
await _userManagerRepository.DeleteEmailUserAction(auditUserDetails, emailUserAction, cancellationToken);
|
|
|
|
if (emailUserAction.EmailActionType == EmailUserActionType.PasswordReset) {
|
|
await SendEmailRequest(user, MailType.PasswordResetCompleted, cancellationToken);
|
|
}
|
|
return user;
|
|
});
|
|
}
|
|
|
|
public async Task DeactivateUser(AuditUserDetails auditUserDetails, string email, CancellationToken cancellationToken)
|
|
{
|
|
await _userManagerRepository.TransactionAsync( async () =>
|
|
{
|
|
var user = await GetUserByEmailAsync(email, cancellationToken);
|
|
|
|
await DeactivateUser(auditUserDetails, user, cancellationToken);
|
|
});
|
|
}
|
|
|
|
public async Task DeactivateUser(AuditUserDetails auditUserDetails, GeneralIdRef generalIdRef, CancellationToken cancellationToken)
|
|
{
|
|
await _userManagerRepository.TransactionAsync(async () =>
|
|
{
|
|
var user = await _userManagerRepository.GetUserById(generalIdRef, cancellationToken)
|
|
?? throw new NotFoundException("Unable to find user");
|
|
|
|
await DeactivateUser(auditUserDetails, user, cancellationToken);
|
|
});
|
|
}
|
|
|
|
private async Task DeactivateUser(AuditUserDetails auditUserDetails, User user, CancellationToken cancellationToken)
|
|
{
|
|
if (!user.Active)
|
|
throw new InvalidOperationException("User already deactivated");
|
|
|
|
if (user.Id == auditUserDetails.UserId)
|
|
throw new InvalidOperationException("You can't delete yourself");
|
|
|
|
user.Active = false;
|
|
|
|
await _userManagerRepository.EditUser(auditUserDetails, user, cancellationToken);
|
|
}
|
|
|
|
public async Task<UserProfile> GetProfile(string email, CancellationToken cancellationToken)
|
|
{
|
|
var applicationName = _configuration.GetValue<string>("applicationName");
|
|
|
|
if (string.IsNullOrWhiteSpace(applicationName))
|
|
{
|
|
throw new SystemException("applicationName has not been set");
|
|
}
|
|
|
|
var user = await GetUserByEmailAsync(email, cancellationToken);
|
|
|
|
var setupInfo = _twoFactorAuthenticator.GenerateSetupCode(applicationName, user.Email, user.TwoFactorAuthenticationKey, false);
|
|
|
|
var twoFactorAuthenticationSettings = new TwoFactorAuthenticationSettings
|
|
{
|
|
QrCodeImageUrl = setupInfo.QrCodeSetupImageUrl,
|
|
ManualEntrySetupCode = setupInfo.ManualEntryKey
|
|
};
|
|
|
|
var ssoProviders = GetSsoProvidersForUser();
|
|
|
|
var userProfile = new UserProfile
|
|
{
|
|
FirstName = user.FirstName,
|
|
MiddleNames = user.MiddleNames,
|
|
LastName = user.LastName,
|
|
Email = user.Email,
|
|
UsingTwoFactorAuthentication = user.UsingTwoFactorAuthentication,
|
|
TwoFactorAuthenticationSettings = twoFactorAuthenticationSettings,
|
|
Created = user.Created,
|
|
DomainSsoProviderId = user.Domain.SsoProviderId,
|
|
SsoProviderId = user.SsoProviderId,
|
|
SsoSubject = user.SsoSubject,
|
|
SsoProviders = ssoProviders
|
|
.ToDictionary(x => x.Id, x => x.Name)
|
|
};
|
|
|
|
return userProfile;
|
|
}
|
|
|
|
private IEnumerable<SsoProvider> GetSsoProvidersForUser()
|
|
{
|
|
var ssoProviders = _userManagerRepository.GetSsoProviders().Where(x => x.Deleted == false && x.IsPublic == true);
|
|
|
|
return ssoProviders;
|
|
}
|
|
|
|
public async Task UpdateProfile(AuditUserDetails auditUserDetails, string email, UpdatedUserProfile userProfile, CancellationToken cancellationToken)
|
|
{
|
|
await _userManagerRepository.TransactionAsync(async () =>
|
|
{
|
|
var user = await GetUserByEmailAsync(email, cancellationToken);
|
|
userProfile.Email = userProfile.Email.Trim();
|
|
|
|
if (!string.IsNullOrWhiteSpace(userProfile.Email))
|
|
{
|
|
ValidateEmail(userProfile.Email);
|
|
|
|
if (user.Email != userProfile.Email)
|
|
{
|
|
await CheckForExistingUserByEmail(userProfile.Email, cancellationToken);
|
|
user.EmailConfirmed = false;
|
|
user.Email = userProfile.Email;
|
|
}
|
|
}
|
|
|
|
user.FirstName = userProfile.FirstName;
|
|
user.MiddleNames = userProfile.MiddleNames;
|
|
user.LastName = userProfile.LastName;
|
|
user.Email = userProfile.Email;
|
|
|
|
await SetInternalAuthenticationDetails(user, userProfile, true, cancellationToken);
|
|
|
|
await _userManagerRepository.EditUser(auditUserDetails, user, cancellationToken);
|
|
|
|
if (!user.EmailConfirmed)
|
|
{
|
|
await SendEmailUserAction(auditUserDetails, user, EmailUserActionType.ConfirmEmailAddress, cancellationToken);
|
|
}
|
|
});
|
|
}
|
|
|
|
private async Task SetInternalAuthenticationDetails(
|
|
User user,
|
|
UpdatedUserProfile userProfile,
|
|
bool sendEmail,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(userProfile.Password))
|
|
{
|
|
HashPassword(user, userProfile.Password);
|
|
if (sendEmail)
|
|
await SendEmailRequest(user, MailType.PasswordResetCompleted, cancellationToken);
|
|
}
|
|
|
|
if (userProfile.UsingTwoFactorAuthentication != user.UsingTwoFactorAuthentication)
|
|
{
|
|
if (userProfile.UsingTwoFactorAuthentication)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(userProfile.SecurityCode))
|
|
{
|
|
if (_twoFactorAuthenticator.ValidateTwoFactorPIN(user.TwoFactorAuthenticationKey,
|
|
userProfile.SecurityCode))
|
|
user.UsingTwoFactorAuthentication = true;
|
|
}
|
|
}
|
|
else
|
|
DisableTwoFactorAuthenticationForUser(user);
|
|
}
|
|
}
|
|
|
|
public async Task EditUser(AuditUserDetails auditUserDetails, EditUser user, CancellationToken cancellationToken)
|
|
{
|
|
await _userManagerRepository.TransactionAsync(async () =>
|
|
{
|
|
var editUser = await _userManagerRepository.GetUserById(user.Id! , cancellationToken)
|
|
?? throw new NotFoundException("unable to find user");
|
|
|
|
if (!editUser.Active)
|
|
throw new DeletedRowInaccessibleException("This user is inactive so cannot be modified. You will need to create the user again.");
|
|
|
|
var userDomain = await _userManagerRepository.GetDomainById(user.Domain, cancellationToken)
|
|
?? throw new NotFoundException("unable to find domain");
|
|
|
|
editUser.FirstName = user.FirstName;
|
|
editUser.MiddleNames = user.MiddleNames;
|
|
editUser.LastName = user.LastName;
|
|
editUser.Email = user.Email;
|
|
editUser.DomainId = userDomain.Id;
|
|
editUser.Domain = userDomain;
|
|
|
|
await _userManagerRepository.EditUser(auditUserDetails, editUser, cancellationToken);
|
|
});
|
|
}
|
|
|
|
public async Task<IPaginatedData<GetUser>> GetUsersAsync(Paging paging, CancellationToken cancellationToken)
|
|
{
|
|
var users = _userManagerRepository.GetUsers().Where(x => x.Active == true);
|
|
|
|
var paginatedData = await PaginatedData.Paginate(users, paging, KeySelector, FilterSelector, cancellationToken);
|
|
|
|
var paginatedResult = new PaginatedData<GetUser>
|
|
{
|
|
Count = paginatedData.Count,
|
|
Page = paginatedData.Page,
|
|
PageSize = paginatedData.PageSize,
|
|
Data = paginatedData.Data.Select(MapUser)
|
|
};
|
|
return paginatedResult;
|
|
}
|
|
|
|
public async Task<GetUser?> GetUserAsync(GeneralIdRef generalIdRef, CancellationToken cancellationToken)
|
|
{
|
|
var user = await _userManagerRepository.GetUserById(generalIdRef, cancellationToken)
|
|
?? throw new NotFoundException("User not found");
|
|
|
|
return MapUser(user);
|
|
}
|
|
|
|
public async Task<User> GetUserByEmailAsync(string email, CancellationToken cancellationToken)
|
|
{
|
|
var user = await _userManagerRepository.GetUserByEmail(email, cancellationToken)
|
|
?? throw new NotFoundException("User not found");
|
|
|
|
return user;
|
|
}
|
|
|
|
private GetUser MapUser(User user)
|
|
{
|
|
var getUser = new GetUser
|
|
{
|
|
Id = user.Id,
|
|
Guid = user.Guid,
|
|
FirstName = user.FirstName,
|
|
LastName = user.LastName,
|
|
MiddleNames = user.MiddleNames,
|
|
DisplayName = user.DisplayName,
|
|
Email = user.Email,
|
|
Created = user.Created,
|
|
LastUpdated = user.LastUpdated,
|
|
Domain = new GeneralIdRef
|
|
{
|
|
Id = user.Domain.Id,
|
|
Guid = user.Domain.Guid
|
|
},
|
|
DomainName = user.Domain.Name,
|
|
EmailConfirmed = user.EmailConfirmed
|
|
};
|
|
|
|
return getUser;
|
|
}
|
|
|
|
private Expression<Func<User, bool>> FilterSelector(string? key, string value)
|
|
{
|
|
return key?.ToLowerInvariant() switch
|
|
{
|
|
"firstname" => x => x.FirstName.Contains(value),
|
|
"lastname" => x => x.LastName.Contains(value),
|
|
"middlenames" => x => x.MiddleNames.Contains(value),
|
|
"displayname" => x => ( x.FirstName + " " + x.MiddleNames + " " + x.LastName ).Contains(value),
|
|
"domainname" => x => x.Domain.Name.Contains(value),
|
|
"created" => x => x.Created.ToString().Contains(value),
|
|
"lastupdated" => x => x.LastUpdated.ToString().Contains(value),
|
|
"email" => x => x.Email.Contains(value),
|
|
_ => x => x.Email.Contains(value)
|
|
};
|
|
}
|
|
|
|
private Expression<Func<User, object>> KeySelector(string? sortKey)
|
|
{
|
|
return sortKey?.ToLowerInvariant() switch
|
|
{
|
|
"firstname" => x => x.FirstName,
|
|
"lastname" => x => x.LastName,
|
|
"middlenames" => x => x.MiddleNames,
|
|
"displayname" => x => x.FirstName + " " + x.MiddleNames + " " + x.LastName,
|
|
"domainname" => x => x.Domain.Name,
|
|
"created" => x => x.Created,
|
|
"lastupdated" => x => x.LastUpdated,
|
|
"email" => x => x.Email,
|
|
_ => x => x.Email
|
|
};
|
|
}
|
|
|
|
private void AddPasswordHash(User user,string tokenPass)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(tokenPass))
|
|
throw new ArgumentException("Invalid password supplied");
|
|
HashPassword(user, tokenPass);
|
|
}
|
|
|
|
private static void CheckPasswordStrength(string password)
|
|
{
|
|
const int minPasswordLength = 12;
|
|
const string symbols = "~`! @#$%^&*()_+={[}|\\:;\"'<,->.?/";
|
|
|
|
if (password.Length < minPasswordLength)
|
|
throw new WeakPasswordException($"Password must be at least {minPasswordLength} characters");
|
|
|
|
if (!password.Contains(symbols.ToCharArray()))
|
|
throw new WeakPasswordException($"Password must contain at least one symbol: {symbols}");
|
|
|
|
if (!password.Any(char.IsDigit))
|
|
throw new WeakPasswordException($"Password must contain at least one number");
|
|
|
|
if (!password.Any(char.IsUpper))
|
|
throw new WeakPasswordException($"Password must contain at least one upper case character");
|
|
|
|
if (!password.Any(char.IsLower))
|
|
throw new WeakPasswordException($"Password must contain at least one lower case character");
|
|
}
|
|
|
|
public async Task ResendConfirmEmail(
|
|
AuditUserDetails auditUserDetails,
|
|
GeneralIdRef generalIdRef,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
var user = await _userManagerRepository.GetUserById(generalIdRef, cancellationToken)
|
|
?? throw new NotFoundException("User not found");
|
|
|
|
await SendEmailUserAction(auditUserDetails, user, EmailUserActionType.ConfirmEmailAddress, cancellationToken);
|
|
}
|
|
|
|
public async Task<string> GetCurrentEmailActionUrl( string emailAddress, EmailUserActionType emailUserActionType, CancellationToken cancellationToken)
|
|
{
|
|
var user = await _userManagerRepository.GetUserByEmail(emailAddress, cancellationToken)
|
|
?? throw new NotFoundException("User not found");
|
|
var emailUserAction = await _userManagerRepository.GetCurrentEmailUserAction(user.Id, emailUserActionType, cancellationToken)
|
|
?? throw new NotFoundException("Email action not found");
|
|
var emailUserActionUrl = GetEmailUserActionUrl(user, emailUserAction);
|
|
return emailUserActionUrl;
|
|
}
|
|
|
|
public async Task<SsoProvider?> GetSsoProviderForEmail(string loginEmail, CancellationToken cancellationToken)
|
|
{
|
|
var user = await _userManagerRepository.GetUserByEmail(loginEmail, cancellationToken);
|
|
|
|
if (user?.DomainId != null)
|
|
{
|
|
var domain =
|
|
await _domainRepository.GetDomainById(new GeneralIdRef { Id = user.DomainId }, cancellationToken);
|
|
|
|
if (domain?.SsoProvider != null)
|
|
return domain.SsoProvider;
|
|
}
|
|
|
|
return user?.SsoProvider;
|
|
}
|
|
|
|
public async Task<SsoProvider?> GetSsoProviderById(long ssoProviderId, CancellationToken cancellationToken)
|
|
{
|
|
var ssoProvider = await _userManagerRepository.GetSsoProviderById(ssoProviderId, cancellationToken);
|
|
return ssoProvider;
|
|
}
|
|
|
|
public async Task LinkSsoProfileToUser(AuditUserDetails auditUserDetails, User user, long ssoId, string ssoUserId, bool setEmailConfirmed, CancellationToken cancellationToken)
|
|
{
|
|
if (user == null)
|
|
throw new NullReferenceException();
|
|
|
|
|
|
if (user.Domain.SsoProviderId == null)
|
|
{
|
|
var ssoProvider = await _userManagerRepository.GetSsoProviderById(ssoId, cancellationToken) ?? throw new NotFoundException("SSO Provider not found");
|
|
|
|
user.SsoProviderId = ssoProvider.Id;
|
|
}
|
|
user.SsoSubject = ssoUserId;
|
|
|
|
if (setEmailConfirmed)
|
|
user.EmailConfirmed = true;
|
|
|
|
await _userManagerRepository.EditUser(auditUserDetails, user, cancellationToken);
|
|
}
|
|
|
|
public async Task TurnOfSsoForUser(AuditUserDetails auditUserDetails, GeneralIdRef generalIdRef, CancellationToken cancellationToken)
|
|
{
|
|
var user = await _userManagerRepository.GetUserById(generalIdRef, cancellationToken) ?? throw new NotFoundException("User not found");
|
|
|
|
user.SsoProviderId = null;
|
|
user.SsoSubject = string.Empty;
|
|
user.SsoProvider = null;
|
|
|
|
await _userManagerRepository.EditUser(auditUserDetails, user, cancellationToken);
|
|
}
|
|
|
|
public async Task<Guid> CreateSingleUseGuid(
|
|
AuditUserDetails auditUserDetails,
|
|
GeneralIdRef generalIdRef,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
var user = await _userManagerRepository.GetUserById(generalIdRef, cancellationToken) ?? throw new NotFoundException("User not found");
|
|
|
|
var singleUseGuid = new SingleUseGuid
|
|
{
|
|
UserId = user.Id,
|
|
User = user,
|
|
Guid = Guid.NewGuid(),
|
|
Expires = _clock.GetNow.AddHours(GetEmailTimeoutHours())
|
|
};
|
|
|
|
await _userManagerRepository.SaveSingleUseGuidForUser(singleUseGuid, cancellationToken);
|
|
|
|
return singleUseGuid.Guid;
|
|
}
|
|
|
|
public async Task<User?> GetUserWithSingleUseGuid(Guid guid, CancellationToken cancellationToken)
|
|
{
|
|
var user = await _userManagerRepository.GetUserBySingleUseGuid(guid, cancellationToken);
|
|
|
|
await _userManagerRepository.DeleteSingleUseGuid(guid, cancellationToken);
|
|
return user;
|
|
}
|
|
|
|
public async Task SetAuthentication(
|
|
AuditUserDetails auditUserDetails,
|
|
UserAuthenticationDetails userAuthenticationDetails,
|
|
bool setEmailConfirmed,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
await _userManagerRepository.TransactionAsync(async () =>
|
|
{
|
|
var user = await _userManagerRepository.GetUserById(userAuthenticationDetails.Id, cancellationToken)
|
|
?? throw new NotFoundException("User not found");
|
|
|
|
var userProfile = new UpdatedUserProfile
|
|
{
|
|
Password = userAuthenticationDetails.Password,
|
|
SecurityCode = userAuthenticationDetails.SecurityCode,
|
|
UsingTwoFactorAuthentication = userAuthenticationDetails.UsingTwoFactorAuthentication
|
|
};
|
|
|
|
await SetInternalAuthenticationDetails(user, userProfile, false, cancellationToken);
|
|
|
|
if (setEmailConfirmed)
|
|
user.EmailConfirmed = true;
|
|
|
|
await _userManagerRepository.EditUser(auditUserDetails, user, cancellationToken);
|
|
});
|
|
}
|
|
} |