mirror of
https://github.com/Shchoholiev/shopping-assistant-api.git
synced 2025-04-02 23:59:35 +00:00
SA-14 Updated User model structure and Services
This commit is contained in:
parent
4102312fe9
commit
39bed12f30
@ -14,6 +14,12 @@
|
||||
// "protocol": "https"
|
||||
// }
|
||||
// }
|
||||
|
||||
// Container is not working on M1 Mac
|
||||
// "runArgs": [
|
||||
// "--platform=linux/amd64"
|
||||
// ],
|
||||
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
|
@ -7,14 +7,14 @@ namespace ShoppingAssistantApi.Api.Mutations;
|
||||
public class AccessMutation
|
||||
{
|
||||
public Task<TokensModel> LoginAsync(AccessUserModel login, CancellationToken cancellationToken,
|
||||
[Service] IUserManager userManager)
|
||||
=> userManager.LoginAsync(login, cancellationToken);
|
||||
[Service] IUserManager userManager)
|
||||
=> userManager.LoginAsync(login, cancellationToken);
|
||||
|
||||
public Task<TokensModel> AccessGuestAsync(AccessGuestModel guest, CancellationToken cancellationToken,
|
||||
[Service] IUserManager userManager)
|
||||
=> userManager.AccessGuestAsync(guest, cancellationToken);
|
||||
[Service] IUserManager userManager)
|
||||
=> userManager.AccessGuestAsync(guest, cancellationToken);
|
||||
|
||||
public Task<TokensModel> RefreshUserTokenAsync(TokensModel model, CancellationToken cancellationToken,
|
||||
[Service] ITokensService tokensService)
|
||||
=> tokensService.RefreshUserAsync(model, cancellationToken);
|
||||
public Task<TokensModel> RefreshAccessTokenAsync(TokensModel model, CancellationToken cancellationToken,
|
||||
[Service] IUserManager userManager)
|
||||
=> userManager.RefreshAccessTokenAsync(model, cancellationToken);
|
||||
}
|
@ -1,22 +1,14 @@
|
||||
using ShoppingAssistantApi.Application.IServices.Identity;
|
||||
using HotChocolate.Authorization;
|
||||
using ShoppingAssistantApi.Application.IServices;
|
||||
using ShoppingAssistantApi.Application.Models.CreateDtos;
|
||||
using ShoppingAssistantApi.Application.Models.Dtos;
|
||||
using ShoppingAssistantApi.Application.Models.Identity;
|
||||
|
||||
namespace ShoppingAssistantApi.Api.Mutations;
|
||||
|
||||
[ExtendObjectType(OperationTypeNames.Mutation)]
|
||||
public class RolesMutation
|
||||
{
|
||||
public Task<TokensModel> AddToRoleAsync(string roleName, string id, CancellationToken cancellationToken,
|
||||
[Service] IUserManager userManager)
|
||||
=> userManager.AddToRoleAsync(roleName, id, cancellationToken);
|
||||
|
||||
public Task<TokensModel> RemoveFromRoleAsync(string roleName, string id, CancellationToken cancellationToken,
|
||||
[Service] IUserManager userManager)
|
||||
=> userManager.RemoveFromRoleAsync(roleName, id, cancellationToken);
|
||||
|
||||
[Authorize]
|
||||
public Task<RoleDto> AddRole(RoleCreateDto roleDto, CancellationToken cancellationToken,
|
||||
[Service] IRolesService rolesService)
|
||||
=> rolesService.AddRoleAsync(roleDto, cancellationToken);
|
||||
|
@ -10,11 +10,21 @@ public class UsersMutation
|
||||
{
|
||||
[Authorize]
|
||||
public Task<UpdateUserModel> UpdateUserAsync(UserDto userDto, CancellationToken cancellationToken,
|
||||
[Service] IUserManager userManager)
|
||||
=> userManager.UpdateAsync(userDto, cancellationToken);
|
||||
[Service] IUserManager userManager)
|
||||
=> userManager.UpdateAsync(userDto, cancellationToken);
|
||||
|
||||
[Authorize]
|
||||
public Task<UpdateUserModel> UpdateUserByAdminAsync(string id, UserDto userDto, CancellationToken cancellationToken,
|
||||
[Service] IUserManager userManager)
|
||||
=> userManager.UpdateUserByAdminAsync(id, userDto, cancellationToken);
|
||||
public Task<UserDto> UpdateUserByAdminAsync(string id, UserDto userDto, CancellationToken cancellationToken,
|
||||
[Service] IUserManager userManager)
|
||||
=> userManager.UpdateUserByAdminAsync(id, userDto, cancellationToken);
|
||||
|
||||
[Authorize]
|
||||
public Task<UserDto> AddToRoleAsync(string roleName, string userId, CancellationToken cancellationToken,
|
||||
[Service] IUserManager userManager)
|
||||
=> userManager.AddToRoleAsync(roleName, userId, cancellationToken);
|
||||
|
||||
[Authorize]
|
||||
public Task<UserDto> RemoveFromRoleAsync(string roleName, string userId, CancellationToken cancellationToken,
|
||||
[Service] IUserManager userManager)
|
||||
=> userManager.RemoveFromRoleAsync(roleName, userId, cancellationToken);
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
using ShoppingAssistantApi.Application.Models.Identity;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace ShoppingAssistantApi.Application.IServices.Identity;
|
||||
|
||||
@ -9,5 +8,5 @@ public interface ITokensService
|
||||
|
||||
string GenerateRefreshToken();
|
||||
|
||||
Task<TokensModel> RefreshUserAsync(TokensModel tokensModel, CancellationToken cancellationToken);
|
||||
ClaimsPrincipal GetPrincipalFromExpiredToken(string token);
|
||||
}
|
@ -10,11 +10,13 @@ public interface IUserManager
|
||||
|
||||
Task<TokensModel> LoginAsync(AccessUserModel login, CancellationToken cancellationToken);
|
||||
|
||||
Task<TokensModel> AddToRoleAsync(string roleName, string id, CancellationToken cancellationToken);
|
||||
Task<UserDto> AddToRoleAsync(string roleName, string userId, CancellationToken cancellationToken);
|
||||
|
||||
Task<TokensModel> RemoveFromRoleAsync(string roleName, string id, CancellationToken cancellationToken);
|
||||
Task<UserDto> RemoveFromRoleAsync(string roleName, string userId, CancellationToken cancellationToken);
|
||||
|
||||
Task<UpdateUserModel> UpdateAsync(UserDto userDto, CancellationToken cancellationToken);
|
||||
|
||||
Task<UpdateUserModel> UpdateUserByAdminAsync(string id, UserDto userDto, CancellationToken cancellationToken);
|
||||
Task<UserDto> UpdateUserByAdminAsync(string id, UserDto userDto, CancellationToken cancellationToken);
|
||||
|
||||
Task<TokensModel> RefreshAccessTokenAsync(TokensModel tokensModel, CancellationToken cancellationToken);
|
||||
}
|
@ -13,8 +13,4 @@ public class User : EntityBase
|
||||
public string? Email { get; set; }
|
||||
|
||||
public string? PasswordHash { get; set; }
|
||||
|
||||
public string RefreshToken { get; set; }
|
||||
|
||||
public DateTime RefreshTokenExpiryDate { get; set; }
|
||||
}
|
||||
|
@ -5,10 +5,7 @@ using System.Text;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using MongoDB.Bson;
|
||||
using ShoppingAssistantApi.Application.IRepositories;
|
||||
using ShoppingAssistantApi.Application.IServices.Identity;
|
||||
using ShoppingAssistantApi.Application.Models.Identity;
|
||||
|
||||
namespace ShoppingAssistantApi.Infrastructure.Services.Identity;
|
||||
|
||||
@ -16,50 +13,16 @@ public class TokensService : ITokensService
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
private readonly IUsersRepository _usersRepository;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public TokensService(IConfiguration configuration, IUsersRepository usersRepository,
|
||||
ILogger<TokensService> logger)
|
||||
public TokensService(
|
||||
IConfiguration configuration,
|
||||
ILogger<TokensService> logger)
|
||||
{
|
||||
this._configuration = configuration;
|
||||
this._usersRepository = usersRepository;
|
||||
this._logger = logger;
|
||||
}
|
||||
|
||||
public async Task<TokensModel> RefreshUserAsync(TokensModel tokensModel, CancellationToken cancellationToken)
|
||||
{
|
||||
var principal = this.GetPrincipalFromExpiredToken(tokensModel.AccessToken);
|
||||
|
||||
var userId = principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
|
||||
if (!ObjectId.TryParse(userId, out var objectId))
|
||||
{
|
||||
throw new InvalidDataException("Provided id is invalid.");
|
||||
}
|
||||
|
||||
var user = await this._usersRepository.GetUserAsync(objectId, cancellationToken);
|
||||
if (user == null || user?.RefreshToken != tokensModel.RefreshToken
|
||||
|| user?.RefreshTokenExpiryDate <= DateTime.UtcNow)
|
||||
{
|
||||
throw new SecurityTokenExpiredException();
|
||||
}
|
||||
|
||||
var newAccessToken = this.GenerateAccessToken(principal.Claims);
|
||||
var newRefreshToken = this.GenerateRefreshToken();
|
||||
user.RefreshToken = newRefreshToken;
|
||||
user.RefreshTokenExpiryDate = DateTime.UtcNow.AddDays(30);
|
||||
await this._usersRepository.UpdateUserAsync(user, cancellationToken);
|
||||
|
||||
this._logger.LogInformation($"Refreshed user tokens.");
|
||||
|
||||
return new TokensModel
|
||||
{
|
||||
AccessToken = newAccessToken,
|
||||
RefreshToken = newRefreshToken
|
||||
};
|
||||
}
|
||||
|
||||
public string GenerateAccessToken(IEnumerable<Claim> claims)
|
||||
{
|
||||
var tokenOptions = GetTokenOptions(claims);
|
||||
@ -73,18 +36,16 @@ public class TokensService : ITokensService
|
||||
public string GenerateRefreshToken()
|
||||
{
|
||||
var randomNumber = new byte[32];
|
||||
using (var rng = RandomNumberGenerator.Create())
|
||||
{
|
||||
rng.GetBytes(randomNumber);
|
||||
var refreshToken = Convert.ToBase64String(randomNumber);
|
||||
using var rng = RandomNumberGenerator.Create();
|
||||
rng.GetBytes(randomNumber);
|
||||
var refreshToken = Convert.ToBase64String(randomNumber);
|
||||
|
||||
this._logger.LogInformation($"Generated new refresh token.");
|
||||
this._logger.LogInformation($"Generated new refresh token.");
|
||||
|
||||
return refreshToken;
|
||||
}
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
private ClaimsPrincipal GetPrincipalFromExpiredToken(string token)
|
||||
public ClaimsPrincipal GetPrincipalFromExpiredToken(string token)
|
||||
{
|
||||
var tokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
@ -96,11 +57,10 @@ public class TokensService : ITokensService
|
||||
ValidateLifetime = false
|
||||
};
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
SecurityToken securityToken;
|
||||
var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out securityToken);
|
||||
var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out var securityToken);
|
||||
var jwtSecurityToken = securityToken as JwtSecurityToken;
|
||||
if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256,
|
||||
StringComparison.InvariantCultureIgnoreCase))
|
||||
if (jwtSecurityToken == null
|
||||
|| !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
|
||||
throw new SecurityTokenException("Invalid token");
|
||||
|
||||
this._logger.LogInformation($"Returned data from expired access token.");
|
||||
@ -117,7 +77,7 @@ public class TokensService : ITokensService
|
||||
var tokenOptions = new JwtSecurityToken(
|
||||
issuer: _configuration.GetValue<string>("JsonWebTokenKeys:ValidIssuer"),
|
||||
audience: _configuration.GetValue<string>("JsonWebTokenKeys:ValidAudience"),
|
||||
expires: DateTime.UtcNow.AddMinutes(5),
|
||||
expires: DateTime.UtcNow.AddMinutes(15),
|
||||
claims: claims,
|
||||
signingCredentials: signinCredentials
|
||||
);
|
||||
|
@ -1,7 +1,6 @@
|
||||
using AutoMapper;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.Win32;
|
||||
using MongoDB.Bson;
|
||||
using ShoppingAssistantApi.Application.Exceptions;
|
||||
using ShoppingAssistantApi.Application.GlobalInstances;
|
||||
@ -14,23 +13,32 @@ using ShoppingAssistantApi.Domain.Entities;
|
||||
using System.Security.Claims;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
|
||||
namespace ShoppingAssistantApi.Infrastructure.Services.Identity;
|
||||
public class UserManager : IUserManager
|
||||
|
||||
public class UserManager : ServiceBase, IUserManager
|
||||
{
|
||||
private readonly IUsersRepository _usersRepository;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly IPasswordHasher _passwordHasher;
|
||||
|
||||
private readonly ITokensService _tokensService;
|
||||
|
||||
private readonly IRolesRepository _rolesRepository;
|
||||
|
||||
private readonly IRefreshTokensRepository _refreshTokensRepository;
|
||||
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
private readonly IRolesRepository _rolesRepository;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public UserManager(IUsersRepository usersRepository, ILogger<UserManager> logger, IPasswordHasher passwordHasher, ITokensService tokensService, IMapper mapper, IRolesRepository rolesRepository)
|
||||
public UserManager(
|
||||
IUsersRepository usersRepository,
|
||||
IPasswordHasher passwordHasher,
|
||||
ITokensService tokensService,
|
||||
IRolesRepository rolesRepository,
|
||||
IRefreshTokensRepository refreshTokensRepository,
|
||||
IMapper mapper,
|
||||
ILogger<UserManager> logger)
|
||||
{
|
||||
this._usersRepository = usersRepository;
|
||||
this._logger = logger;
|
||||
@ -38,15 +46,16 @@ public class UserManager : IUserManager
|
||||
this._tokensService = tokensService;
|
||||
this._mapper = mapper;
|
||||
this._rolesRepository = rolesRepository;
|
||||
|
||||
this._refreshTokensRepository = refreshTokensRepository;
|
||||
}
|
||||
|
||||
public async Task<TokensModel> LoginAsync(AccessUserModel login, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = login.Email != null
|
||||
? await this._usersRepository.GetUserAsync(x => x.Email == login.Email, cancellationToken)
|
||||
: await this._usersRepository.GetUserAsync(x => x.Phone == login.Phone, cancellationToken);
|
||||
_logger.LogInformation($"Logging in user with email: {login.Email} and phone: {login.Phone}.");
|
||||
|
||||
var user = string.IsNullOrEmpty(login.Phone)
|
||||
? await this._usersRepository.GetUserAsync(u => u.Email == login.Email, cancellationToken)
|
||||
: await this._usersRepository.GetUserAsync(u => u.Phone == login.Phone, cancellationToken);
|
||||
if (user == null)
|
||||
{
|
||||
throw new EntityNotFoundException<User>();
|
||||
@ -57,197 +66,216 @@ public class UserManager : IUserManager
|
||||
throw new InvalidDataException("Invalid password!");
|
||||
}
|
||||
|
||||
user.RefreshToken = this.GetRefreshToken();
|
||||
user.RefreshTokenExpiryDate = DateTime.UtcNow.AddDays(30);
|
||||
await this._usersRepository.UpdateUserAsync(user, cancellationToken);
|
||||
var tokens = this.GetUserTokens(user);
|
||||
var refreshToken = await AddRefreshToken(user.Id, cancellationToken);
|
||||
|
||||
this._logger.LogInformation($"Logged in user with email: {login.Email}.");
|
||||
var tokens = this.GetUserTokens(user, refreshToken);
|
||||
|
||||
this._logger.LogInformation($"Logged in user with email: {login.Email} and phone: {login.Phone}.");
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
public async Task<TokensModel> AccessGuestAsync(AccessGuestModel guest, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation($"Logging in / Registering guest with guest id: {guest.GuestId}.");
|
||||
|
||||
var user = await this._usersRepository.GetUserAsync(x => x.GuestId == guest.GuestId, cancellationToken);
|
||||
|
||||
if (user != null)
|
||||
if (user == null)
|
||||
{
|
||||
user.RefreshToken = this.GetRefreshToken();
|
||||
await this._usersRepository.UpdateUserAsync(user, cancellationToken);
|
||||
var userTokens = this.GetUserTokens(user);
|
||||
var role = await this._rolesRepository.GetRoleAsync(r => r.Name == "Guest", cancellationToken);
|
||||
user = new User
|
||||
{
|
||||
GuestId = guest.GuestId,
|
||||
Roles = new List<Role> { role },
|
||||
CreatedDateUtc = DateTime.UtcNow,
|
||||
CreatedById = ObjectId.Empty // Default value for all new users
|
||||
};
|
||||
|
||||
this._logger.LogInformation($"Logged in guest with guest id: {guest.GuestId}.");
|
||||
await this._usersRepository.AddAsync(user, cancellationToken);
|
||||
|
||||
return userTokens;
|
||||
this._logger.LogInformation($"Created guest with guest id: {guest.GuestId}.");
|
||||
}
|
||||
|
||||
var role = await this._rolesRepository.GetRoleAsync(r => r.Name == "Guest", cancellationToken);
|
||||
var refreshToken = await AddRefreshToken(user.Id, cancellationToken);
|
||||
var tokens = this.GetUserTokens(user, refreshToken);
|
||||
|
||||
var newUser = new User
|
||||
{
|
||||
GuestId = guest.GuestId,
|
||||
Roles = new List<Role> { role },
|
||||
RefreshToken = this.GetRefreshToken(),
|
||||
RefreshTokenExpiryDate = DateTime.Now.AddDays(30),
|
||||
CreatedDateUtc = DateTime.UtcNow,
|
||||
LastModifiedDateUtc = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await this._usersRepository.AddAsync(newUser, cancellationToken);
|
||||
var tokens = this.GetUserTokens(newUser);
|
||||
|
||||
this._logger.LogInformation($"Created guest with guest id: {guest.GuestId}.");
|
||||
this._logger.LogInformation($"Logged in guest with guest id: {guest.GuestId}.");
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
public async Task<TokensModel> AddToRoleAsync(string roleName, string id, CancellationToken cancellationToken)
|
||||
public async Task<TokensModel> RefreshAccessTokenAsync(TokensModel tokensModel, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation($"Refreshing access token.");
|
||||
|
||||
var principal = _tokensService.GetPrincipalFromExpiredToken(tokensModel.AccessToken);
|
||||
var userId = ParseObjectId(principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value);
|
||||
|
||||
var refreshTokenModel = await this._refreshTokensRepository
|
||||
.GetOneAsync(r =>
|
||||
r.Token == tokensModel.RefreshToken
|
||||
&& r.CreatedById == userId
|
||||
&& r.IsDeleted == false, cancellationToken);
|
||||
if (refreshTokenModel == null || refreshTokenModel.ExpiryDateUTC < DateTime.UtcNow)
|
||||
{
|
||||
throw new SecurityTokenExpiredException();
|
||||
}
|
||||
|
||||
var refreshToken = refreshTokenModel.Token;
|
||||
|
||||
// Update Refresh token if it expires in less than 7 days to keep user constantly logged in if he uses the app
|
||||
if (refreshTokenModel.ExpiryDateUTC.AddDays(-7) < DateTime.UtcNow)
|
||||
{
|
||||
await _refreshTokensRepository.DeleteAsync(refreshTokenModel, cancellationToken);
|
||||
|
||||
var newRefreshToken = await AddRefreshToken(userId, cancellationToken);
|
||||
refreshToken = newRefreshToken.Token;
|
||||
}
|
||||
|
||||
var tokens = new TokensModel
|
||||
{
|
||||
AccessToken = _tokensService.GenerateAccessToken(principal.Claims),
|
||||
RefreshToken = refreshToken
|
||||
};
|
||||
|
||||
this._logger.LogInformation($"Refreshed access token.");
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
public async Task<UserDto> AddToRoleAsync(string roleName, string userId, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation($"Adding Role: {roleName} to User with Id: {userId}.");
|
||||
|
||||
var role = await this._rolesRepository.GetRoleAsync(r => r.Name == roleName, cancellationToken);
|
||||
if (role == null)
|
||||
{
|
||||
throw new EntityNotFoundException<Role>();
|
||||
}
|
||||
|
||||
if (!ObjectId.TryParse(id, out var objectId))
|
||||
{
|
||||
throw new InvalidDataException("Provided id is invalid.");
|
||||
}
|
||||
|
||||
var user = await this._usersRepository.GetUserAsync(objectId, cancellationToken);
|
||||
var userObjectId = ParseObjectId(userId);
|
||||
var user = await this._usersRepository.GetUserAsync(userObjectId, cancellationToken);
|
||||
if (user == null)
|
||||
{
|
||||
throw new EntityNotFoundException<User>();
|
||||
}
|
||||
|
||||
user.Roles.Add(role);
|
||||
await this._usersRepository.UpdateUserAsync(user, cancellationToken);
|
||||
var tokens = this.GetUserTokens(user);
|
||||
var updatedUser = await this._usersRepository.UpdateUserAsync(user, cancellationToken);
|
||||
var userDto = this._mapper.Map<UserDto>(updatedUser);
|
||||
|
||||
this._logger.LogInformation($"Added role {roleName} to user with id: {id}.");
|
||||
this._logger.LogInformation($"Added Role: {roleName} to User with Id: {userId}.");
|
||||
|
||||
return tokens;
|
||||
return userDto;
|
||||
}
|
||||
|
||||
public async Task<TokensModel> RemoveFromRoleAsync(string roleName, string id, CancellationToken cancellationToken)
|
||||
public async Task<UserDto> RemoveFromRoleAsync(string roleName, string userId, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation($"Removing Role: {roleName} from User with Id: {userId}.");
|
||||
|
||||
var role = await this._rolesRepository.GetRoleAsync(r => r.Name == roleName, cancellationToken);
|
||||
if (role == null)
|
||||
{
|
||||
throw new EntityNotFoundException<Role>();
|
||||
}
|
||||
|
||||
if (!ObjectId.TryParse(id, out var objectId))
|
||||
{
|
||||
throw new InvalidDataException("Provided id is invalid.");
|
||||
}
|
||||
|
||||
var user = await this._usersRepository.GetUserAsync(objectId, cancellationToken);
|
||||
var userObjectId = ParseObjectId(userId);
|
||||
var user = await this._usersRepository.GetUserAsync(userObjectId, cancellationToken);
|
||||
if (user == null)
|
||||
{
|
||||
throw new EntityNotFoundException<User>();
|
||||
}
|
||||
|
||||
var deletedRole = user.Roles.Find(x => x.Name == role.Name);
|
||||
|
||||
user.Roles.Remove(deletedRole);
|
||||
await this._usersRepository.UpdateUserAsync(user, cancellationToken);
|
||||
var tokens = this.GetUserTokens(user);
|
||||
|
||||
this._logger.LogInformation($"Added role {roleName} to user with id: {id}.");
|
||||
var updatedUser = await this._usersRepository.UpdateUserAsync(user, cancellationToken);
|
||||
var userDto = this._mapper.Map<UserDto>(updatedUser);
|
||||
|
||||
return tokens;
|
||||
this._logger.LogInformation($"Removed Role: {roleName} from User with Id: {userId}.");
|
||||
|
||||
return userDto;
|
||||
}
|
||||
|
||||
public async Task<UpdateUserModel> UpdateAsync(UserDto userDto, CancellationToken cancellationToken)
|
||||
{
|
||||
if (userDto.Email != null) ValidateEmail(userDto.Email);
|
||||
if (userDto.Phone != null) ValidateNumber(userDto.Phone);
|
||||
|
||||
if (userDto.Roles.Any(x => x.Name == "Guest") && !userDto.Roles.Any(x => x.Name == "User"))
|
||||
{
|
||||
if (userDto.Password != null && (userDto.Email != null || userDto.Phone != null))
|
||||
{
|
||||
var roleEntity = await this._rolesRepository.GetRoleAsync(x => x.Name == "User", cancellationToken);
|
||||
var roleDto = this._mapper.Map<RoleDto>(roleEntity);
|
||||
userDto.Roles.Add(roleDto);
|
||||
}
|
||||
}
|
||||
_logger.LogInformation($"Updating user with id: {GlobalUser.Id}.");
|
||||
|
||||
var user = await this._usersRepository.GetUserAsync(x => x.Id == GlobalUser.Id, cancellationToken);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
throw new EntityNotFoundException<User>();
|
||||
}
|
||||
|
||||
if (userDto.Roles.Any(x => x.Name == "User") && userDto.Email != null)
|
||||
{
|
||||
if (await this._usersRepository.GetUserAsync(x => x.Email == userDto.Email, cancellationToken) != null)
|
||||
{
|
||||
throw new EntityAlreadyExistsException<User>("email", userDto.Email);
|
||||
}
|
||||
}
|
||||
if (userDto.Roles.Any(x => x.Name == "User") && userDto.Phone != null)
|
||||
{
|
||||
if (await this._usersRepository.GetUserAsync(x => x.Phone == userDto.Phone, cancellationToken) != null)
|
||||
{
|
||||
throw new EntityAlreadyExistsException<User>("phone", userDto.Phone);
|
||||
}
|
||||
}
|
||||
await ValidateUserAsync(userDto, user, cancellationToken);
|
||||
|
||||
this._mapper.Map(userDto, user);
|
||||
if (!userDto.Password.IsNullOrEmpty())
|
||||
if (!string.IsNullOrEmpty(userDto.Password))
|
||||
{
|
||||
user.PasswordHash = this._passwordHasher.Hash(userDto.Password);
|
||||
}
|
||||
user.RefreshToken = this.GetRefreshToken();
|
||||
await this._usersRepository.UpdateUserAsync(user, cancellationToken);
|
||||
await CheckAndUpgradeToUserAsync(user, cancellationToken);
|
||||
|
||||
var tokens = this.GetUserTokens(user);
|
||||
var updatedUser = await this._usersRepository.UpdateUserAsync(user, cancellationToken);
|
||||
|
||||
this._logger.LogInformation($"Update user with id: {GlobalUser.Id.ToString()}.");
|
||||
var refreshToken = await AddRefreshToken(user.Id, cancellationToken);
|
||||
var tokens = this.GetUserTokens(user, refreshToken);
|
||||
|
||||
return new UpdateUserModel() { Tokens = tokens, User = this._mapper.Map<UserDto>(user) };
|
||||
var updatedUserDto = this._mapper.Map<UserDto>(updatedUser);
|
||||
|
||||
this._logger.LogInformation($"Update user with id: {GlobalUser.Id}.");
|
||||
|
||||
return new UpdateUserModel()
|
||||
{
|
||||
Tokens = tokens,
|
||||
User = updatedUserDto
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<UpdateUserModel> UpdateUserByAdminAsync(string id, UserDto userDto, CancellationToken cancellationToken)
|
||||
public async Task<UserDto> UpdateUserByAdminAsync(string id, UserDto userDto, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!ObjectId.TryParse(id, out var objectId))
|
||||
{
|
||||
throw new InvalidDataException("Provided id is invalid.");
|
||||
}
|
||||
|
||||
var user = await this._usersRepository.GetUserAsync(objectId, cancellationToken);
|
||||
_logger.LogInformation($"Admin updating User with Id: {id}.");
|
||||
|
||||
var userObjectId = ParseObjectId(id);
|
||||
var user = await this._usersRepository.GetUserAsync(userObjectId, cancellationToken);
|
||||
if (user == null)
|
||||
{
|
||||
throw new EntityNotFoundException<User>();
|
||||
}
|
||||
|
||||
await ValidateUserAsync(userDto, user, cancellationToken);
|
||||
|
||||
this._mapper.Map(userDto, user);
|
||||
var updatedUser = await this._usersRepository.UpdateUserAsync(user, cancellationToken);
|
||||
|
||||
user.RefreshToken = this.GetRefreshToken();
|
||||
await this._usersRepository.UpdateUserAsync(user, cancellationToken);
|
||||
var updatedUserDto = this._mapper.Map<UserDto>(updatedUser);
|
||||
|
||||
this._logger.LogInformation($"Admin updated User with Id: {id}.");
|
||||
|
||||
var tokens = this.GetUserTokens(user);
|
||||
|
||||
this._logger.LogInformation($"Update user with id: {id}.");
|
||||
|
||||
return new UpdateUserModel() { Tokens = tokens, User = this._mapper.Map<UserDto>(user) };
|
||||
return updatedUserDto;
|
||||
}
|
||||
|
||||
private string GetRefreshToken()
|
||||
private async Task<RefreshToken> AddRefreshToken(ObjectId userId, CancellationToken cancellationToken)
|
||||
{
|
||||
var refreshToken = this._tokensService.GenerateRefreshToken();
|
||||
_logger.LogInformation($"Adding new refresh token for user with Id : {userId}.");
|
||||
|
||||
this._logger.LogInformation($"Returned new refresh token.");
|
||||
var refreshToken = new RefreshToken
|
||||
{
|
||||
Token = _tokensService.GenerateRefreshToken(),
|
||||
ExpiryDateUTC = DateTime.UtcNow.AddDays(30),
|
||||
CreatedById = userId,
|
||||
CreatedDateUtc = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await this._refreshTokensRepository.AddAsync(refreshToken, cancellationToken);
|
||||
|
||||
this._logger.LogInformation($"Added new refresh token.");
|
||||
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
private TokensModel GetUserTokens(User user)
|
||||
private TokensModel GetUserTokens(User user, RefreshToken refreshToken)
|
||||
{
|
||||
var claims = this.GetClaims(user);
|
||||
var accessToken = this._tokensService.GenerateAccessToken(claims);
|
||||
@ -257,7 +285,7 @@ public class UserManager : IUserManager
|
||||
return new TokensModel
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
RefreshToken = user.RefreshToken,
|
||||
RefreshToken = refreshToken.Token,
|
||||
};
|
||||
}
|
||||
|
||||
@ -265,21 +293,56 @@ public class UserManager : IUserManager
|
||||
{
|
||||
var claims = new List<Claim>()
|
||||
{
|
||||
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
||||
new Claim(ClaimTypes.Email, user.Email ?? string.Empty),
|
||||
new Claim(ClaimTypes.MobilePhone, user.Phone ?? string.Empty),
|
||||
new (ClaimTypes.NameIdentifier, user.Id.ToString()),
|
||||
new (ClaimTypes.Email, user.Email ?? string.Empty),
|
||||
new (ClaimTypes.MobilePhone, user.Phone ?? string.Empty),
|
||||
};
|
||||
|
||||
foreach (var role in user.Roles)
|
||||
{
|
||||
claims.Add(new Claim(ClaimTypes.Role, role.Name));
|
||||
claims.Add(new (ClaimTypes.Role, role.Name));
|
||||
}
|
||||
|
||||
this._logger.LogInformation($"Returned claims for user with id: {user.Id.ToString()}.");
|
||||
this._logger.LogInformation($"Returned claims for User with Id: {user.Id}.");
|
||||
|
||||
return claims;
|
||||
}
|
||||
|
||||
private async Task CheckAndUpgradeToUserAsync(User user, CancellationToken cancellationToken)
|
||||
{
|
||||
if (user.Roles.Any(x => x.Name == "Guest") && !user.Roles.Any(x => x.Name == "User"))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(user.PasswordHash) && (!string.IsNullOrEmpty(user.Email) || !string.IsNullOrEmpty(user.Phone)))
|
||||
{
|
||||
var role = await this._rolesRepository.GetRoleAsync(x => x.Name == "User", cancellationToken);
|
||||
user.Roles.Add(role);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ValidateUserAsync(UserDto userDto, User user, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(userDto.Email))
|
||||
{
|
||||
ValidateEmail(userDto.Email);
|
||||
if (userDto.Email != user.Email
|
||||
&& await this._usersRepository.ExistsAsync(x => x.Email == userDto.Email, cancellationToken))
|
||||
{
|
||||
throw new EntityAlreadyExistsException<User>("email", userDto.Email);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(userDto.Phone))
|
||||
{
|
||||
ValidatePhone(userDto.Phone);
|
||||
if (userDto.Phone != user.Phone
|
||||
&& await this._usersRepository.ExistsAsync(x => x.Phone == userDto.Phone, cancellationToken))
|
||||
{
|
||||
throw new EntityAlreadyExistsException<User>("phone", userDto.Phone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateEmail(string email)
|
||||
{
|
||||
string regex = @"^[^@\s]+@[^@\s]+\.[^@\s]+$";
|
||||
@ -290,7 +353,7 @@ public class UserManager : IUserManager
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateNumber(string phone)
|
||||
private void ValidatePhone(string phone)
|
||||
{
|
||||
string regex = @"^\+[0-9]{1,15}$";
|
||||
|
||||
|
@ -33,14 +33,16 @@ public class RolesService : IRolesService
|
||||
entity.CreatedDateUtc = DateTime.UtcNow;
|
||||
entity.LastModifiedDateUtc = DateTime.UtcNow;
|
||||
await this._repository.AddAsync(entity, cancellationToken);
|
||||
|
||||
return this._mapper.Map<RoleDto>(entity);
|
||||
}
|
||||
|
||||
public async Task<PagedList<RoleDto>> GetRolesPageAsync(int pageNumber, int pageSize, CancellationToken cancellationToken)
|
||||
{
|
||||
var entities = await this._repository.GetPageAsync(pageNumber, pageSize, cancellationToken);
|
||||
var dtos = this._mapper.Map<List<RoleDto>>(entities);
|
||||
var count = await this._repository.GetTotalCountAsync();
|
||||
var dtos = this._mapper.Map<List<RoleDto>>(entities);
|
||||
|
||||
return new PagedList<RoleDto>(dtos, pageNumber, pageSize, count);
|
||||
}
|
||||
}
|
||||
|
15
ShoppingAssistantApi.Infrastructure/Services/ServiceBase.cs
Normal file
15
ShoppingAssistantApi.Infrastructure/Services/ServiceBase.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace ShoppingAssistantApi.Infrastructure.Services;
|
||||
using MongoDB.Bson;
|
||||
|
||||
public abstract class ServiceBase
|
||||
{
|
||||
public ObjectId ParseObjectId(string? id)
|
||||
{
|
||||
if (ObjectId.TryParse(id, out ObjectId objectId))
|
||||
{
|
||||
return objectId;
|
||||
}
|
||||
|
||||
throw new InvalidDataException("Provided id cannot be parsed to a MongoDb ObjectId.");
|
||||
}
|
||||
}
|
@ -27,8 +27,6 @@ public class UsersRepository : BaseRepository<User>, IUsersRepository
|
||||
var updateDefinition = Builders<User>.Update
|
||||
.Set(u => u.Email, user.Email)
|
||||
.Set(u => u.Phone, user.Phone)
|
||||
.Set(u => u.RefreshToken, user.RefreshToken)
|
||||
.Set(u => u.RefreshTokenExpiryDate, user.RefreshTokenExpiryDate)
|
||||
.Set(u => u.GuestId, user.GuestId)
|
||||
.Set(u => u.Roles, user.Roles)
|
||||
.Set(u => u.PasswordHash, user.PasswordHash)
|
||||
@ -42,7 +40,5 @@ public class UsersRepository : BaseRepository<User>, IUsersRepository
|
||||
|
||||
return await this._collection.FindOneAndUpdateAsync(
|
||||
Builders<User>.Filter.Eq(u => u.Id, user.Id), updateDefinition, options, cancellationToken);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user