SA-14 Updated User model structure and Services

This commit is contained in:
shchoholiev 2023-10-16 03:22:49 +00:00
parent 4102312fe9
commit 39bed12f30
12 changed files with 249 additions and 208 deletions

View File

@ -14,6 +14,12 @@
// "protocol": "https"
// }
// }
// Container is not working on M1 Mac
// "runArgs": [
// "--platform=linux/amd64"
// ],
"customizations": {
"vscode": {
"extensions": [

View File

@ -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);
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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; }
}

View File

@ -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
);

View File

@ -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}$";

View File

@ -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);
}
}

View 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.");
}
}

View File

@ -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);
}
}