Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: move user repo logic to service and handle errors #25

Merged
merged 13 commits into from
Mar 20, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ public async Task<ActionResult<BaseResponse<AuthenticationResponse>>> LoginAsync
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<BaseResponse<AuthenticationResponse>>> ResetPasswordAsync(PasswordResetRequest request)
public async Task<ActionResult<BaseResponse<bool>>> ResetPasswordAsync(PasswordResetRequest request)
{
BaseResponse<AuthenticationResponse> response = new(ResponseStatus.Fail);
BaseResponse<bool> response = new(ResponseStatus.Fail);

response.Data = await _authenticationService.ResetPasswordAsync(request).ConfigureAwait(false);
response.Status = ResponseStatus.Success;
Expand All @@ -78,9 +78,9 @@ public async Task<ActionResult<BaseResponse<AuthenticationResponse>>> ResetPassw
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<BaseResponse<string>>> ForgotPasswordAsync(string email)
public async Task<ActionResult<BaseResponse<bool>>> ForgotPasswordAsync(string email)
{
BaseResponse<string> response = new(ResponseStatus.Fail);
BaseResponse<bool> response = new(ResponseStatus.Fail);

response.Data = await _authenticationService.ForgotPasswordAsync(email).ConfigureAwait(false);
response.Status = ResponseStatus.Success;
Expand Down
2 changes: 1 addition & 1 deletion DotnetFoundation/DotnetFoundation.Api/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
},
"Emails": {
"ForgetPassword": {
"Path": "../DotnetFoundation.Api/wwwroot/Templates/Emails/ForgetPasswordTemplate.html",
"Path": "../DotnetFoundation.Api/wwwroot/Templates/Emails/ResetPasswordTemplate.html",
"Subject": "Forget password"
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using DotnetFoundation.Application.Models.DTOs.UserDTO;

namespace DotnetFoundation.Application.Interfaces.Integrations;

public interface IJwtTokenService
{
public string GenerateJwtToken(UserInfo user);

};
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ namespace DotnetFoundation.Application.Interfaces.Persistence;
public interface IUserRepository
{
public Task<string> AddUserAsync(RegisterRequest request);
public Task<string> LoginUserAsync(LoginRequest request);
public Task<UserInfo> LoginUserAsync(LoginRequest request);
public Task<List<User>> GetAllUsersAsync();
public Task<User?> GetUserByIdAsync(int userId);
public Task<string> ForgotPasswordAsync(string email);
public Task<string> ResetPasswordAsync(string email, string token, string newPassword);
public Task ResetPasswordAsync(string email, string token, string newPassword);
public Task<bool> AddUserRoleAsync(string email, Roles role);
public Task<User?> UpdateUserAsync(int userId, UpdateUserRequest request);
public Task<User?> DeleteUserAsync(int userId);
public Task<List<string>> GetUserRoleAsync(string email);

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ public interface IAuthenticationService
{
public Task<AuthenticationResponse> RegisterAsync(RegisterRequest request);
public Task<AuthenticationResponse> LoginAsync(LoginRequest request);
public Task<string> ForgotPasswordAsync(string email);
public Task<AuthenticationResponse> ResetPasswordAsync(PasswordResetRequest request);
public Task<bool> ForgotPasswordAsync(string email);
public Task<bool> ResetPasswordAsync(PasswordResetRequest request);
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
namespace DotnetFoundation.Infrastructure.Identity;
namespace DotnetFoundation.Application.Models.DTOs.UserDTO;
public record UserInfo(string Id, string Email, List<string> Roles);
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public static IServiceCollection AddInfrastructure(this IServiceCollection servi
// Configure service scope for repositories
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IEmailService, EmailService>();
services.AddScoped<IJwtTokenService, JwtTokenService>();
services.AddScoped<ITaskDetailsRepository, TaskDetailsRepository>();
services.AddHttpClient();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using DotnetFoundation.Application.Interfaces.Integrations;
using DotnetFoundation.Application.Models.DTOs.UserDTO;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;

namespace DotnetFoundation.Infrastructure.Integrations;

public class JwtTokenService : IJwtTokenService
{
private readonly IConfiguration _configuration;
public JwtTokenService(IConfiguration configuration)
{
_configuration = configuration;
}

public string GenerateJwtToken(UserInfo user)
{
List<Claim> claims = new List<Claim>
{
new(ClaimTypes.Email,user.Id.ToString()),
new(ClaimTypes.Name, user.Email),
// Add additional claims for roles
};
foreach (string role in user.Roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
string JWT_KEY = Environment.GetEnvironmentVariable("JWT_KEY") ?? throw new Exception("No JWT configuration");
SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JWT_KEY));
SigningCredentials credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

JwtSecurityToken token = new JwtSecurityToken(
_configuration["Jwt:Issuer"],
_configuration["Jwt:Audience"],
claims,
notBefore: DateTime.UtcNow,
expires: DateTime.UtcNow.AddHours(Convert.ToDouble(_configuration["Appsettings:LoginAuthTokenExpiryTimeInHrs"])),
signingCredentials: credentials
);

return new JwtSecurityTokenHandler().WriteToken(token);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
using DotnetFoundation.Domain.Entities;
using DotnetFoundation.Domain.Enums;
using DotnetFoundation.Infrastructure.Identity;
using DotnetFoundation.Infrastructure.Integrations;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
Expand All @@ -24,94 +26,52 @@ public class UserRepository : IUserRepository
private readonly UserManager<IdentityApplicationUser> _userManager;
private readonly RoleManager<IdentityRole> _roleManager;
private readonly IEmailService _emailService;
public UserRepository(IConfiguration configuration, SqlDatabaseContext sqlDatabaseContext, SignInManager<IdentityApplicationUser> signinManager, RoleManager<IdentityRole> roleManager, UserManager<IdentityApplicationUser> userManager, IEmailService emailService)
private readonly IJwtTokenService _jwtService;
public UserRepository(IConfiguration configuration, SqlDatabaseContext sqlDatabaseContext, SignInManager<IdentityApplicationUser> signinManager, RoleManager<IdentityRole> roleManager, UserManager<IdentityApplicationUser> userManager, IEmailService emailService, IJwtTokenService jwtService)
{
_dbContext = sqlDatabaseContext;
_configuration = configuration;
_roleManager = roleManager;
_signInManager = signinManager;
_userManager = userManager;
_emailService = emailService;
_jwtService = jwtService;
}

public string GenerateJwtToken(UserInfo user)
public async Task<string> AddUserAsync(RegisterRequest request)
{
List<Claim> claims = new List<Claim>
IdentityApplicationUser newIdentityApplicationUser = new()
{
new(ClaimTypes.Email,user.Id.ToString()),
new(ClaimTypes.Name, user.Email),
// Add additional claims for roles
UserName = request.Email,
Email = request.Email
};
foreach (string role in user.Roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
string JWT_KEY = Environment.GetEnvironmentVariable("JWT_KEY") ?? throw new Exception("No JWT configuration");
SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JWT_KEY));
SigningCredentials credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

JwtSecurityToken token = new JwtSecurityToken(
_configuration["Jwt:Issuer"],
_configuration["Jwt:Audience"],
claims,
notBefore: DateTime.UtcNow,
expires: DateTime.UtcNow.AddHours(Convert.ToDouble(_configuration["Appsettings:LoginAuthTokenExpiryTimeInHrs"])),
signingCredentials: credentials
);

return new JwtSecurityTokenHandler().WriteToken(token);
}

public async Task<string> AddUserAsync(RegisterRequest request)
{
using TransactionScope scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled);
IdentityResult identityResult = await _userManager.CreateAsync(newIdentityApplicationUser, request.Password).ConfigureAwait(false);

try
if (!identityResult.Succeeded)
{
IdentityApplicationUser newIdentityApplicationUser = new()
{
UserName = request.Email,
Email = request.Email
};

IdentityResult identityResult = await _userManager.CreateAsync(newIdentityApplicationUser, request.Password).ConfigureAwait(false);

if (!identityResult.Succeeded)
{
throw new Exception("Error creating identity user");
}
throw new Exception("Error creating identity user");
}

IdentityApplicationUser identityApplicationUser = await _userManager.FindByEmailAsync(request.Email).ConfigureAwait(false) ?? throw new Exception("Error finding user");
ApplicationUser applicationUser = new()
{
FirstName = request.FirstName,
LastName = request.LastName,
Country = request.Country,
PhoneNumber = request.PhoneNumber,
Email = request.Email,
IdentityApplicationUserId = newIdentityApplicationUser.Id
};

await AddUserRoleAsync(request.Email, 0).ConfigureAwait(false);
await _dbContext.ApplicationUsers.AddAsync(applicationUser).ConfigureAwait(false);
await _dbContext.SaveChangesAsync().ConfigureAwait(false);

ApplicationUser applicationUser = new()
{
FirstName = request.FirstName,
LastName = request.LastName,
Country = request.Country,
PhoneNumber = request.PhoneNumber,
Email = request.Email,
IdentityApplicationUserId = identityApplicationUser.Id
};

Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry<ApplicationUser> res = await _dbContext.ApplicationUsers.AddAsync(applicationUser).ConfigureAwait(false);
await _dbContext.SaveChangesAsync().ConfigureAwait(false);

UserInfo userInfo = new(identityApplicationUser.Id, request.Email, (await _userManager.GetRolesAsync(identityApplicationUser).ConfigureAwait(false)).ToList());

// If everything succeeds, commit the transaction
scope.Complete();
return GenerateJwtToken(userInfo);
}
catch (Exception ex)
{
return ex.Message;
}
finally
{
scope.Dispose(); // Ensure to dispose the TransactionScope
}
return applicationUser.IdentityApplicationUserId;
}
public async Task<List<string>> GetUserRoleAsync(string email)
{
IdentityApplicationUser identityApplicationUser = await _userManager.FindByEmailAsync(email).ConfigureAwait(false) ?? throw new Exception("Error finding user");
return (await _userManager.GetRolesAsync(identityApplicationUser).ConfigureAwait(false)).ToList();
}

public async Task<List<User>> GetAllUsersAsync()
Expand Down Expand Up @@ -188,7 +148,7 @@ public async Task<List<User>> GetAllUsersAsync()
return user;
}

public async Task<string> LoginUserAsync(LoginRequest request)
public async Task<UserInfo> LoginUserAsync(LoginRequest request)
{
SignInResult signInResult = await _signInManager.PasswordSignInAsync(request.Email, request.Password, false, false).ConfigureAwait(false);

Expand All @@ -197,27 +157,16 @@ public async Task<string> LoginUserAsync(LoginRequest request)
throw new Exception("Invalid Email or Password");
}
IdentityApplicationUser user = await _userManager.FindByEmailAsync(request.Email).ConfigureAwait(false) ?? throw new Exception("User does not exist");
UserInfo userInfo = new(user.Id, request.Email, (await _userManager.GetRolesAsync(user).ConfigureAwait(false)).ToList());
return GenerateJwtToken(userInfo);
return new(user.Id, request.Email, (await _userManager.GetRolesAsync(user).ConfigureAwait(false)).ToList());
}

public async Task<string> ForgotPasswordAsync(string email)
{
try
{
IdentityApplicationUser user = await _userManager.FindByEmailAsync(email).ConfigureAwait(false) ?? throw new Exception("Invalid Email");
string token = await _userManager.GeneratePasswordResetTokenAsync(user).ConfigureAwait(false);
await _emailService.SendForgetPasswordEmailAsync(email, token).ConfigureAwait(false);

return "Success";
}
catch (Exception ex)
{
return $"Error in ForgotPasswordAsync: {ex}";
}
IdentityApplicationUser user = await _userManager.FindByEmailAsync(email).ConfigureAwait(false) ?? throw new Exception("Invalid Email");
return await _userManager.GeneratePasswordResetTokenAsync(user).ConfigureAwait(false);
}

public async Task<string> ResetPasswordAsync(string email, string token, string newPassword)
public async Task ResetPasswordAsync(string email, string token, string newPassword)
{
IdentityApplicationUser user = await _userManager.FindByEmailAsync(email).ConfigureAwait(false) ?? throw new Exception("Invalid Email");
IdentityResult result = await _userManager.ResetPasswordAsync(user, token, newPassword).ConfigureAwait(false);
Expand All @@ -226,9 +175,6 @@ public async Task<string> ResetPasswordAsync(string email, string token, string
{
throw new Exception("Invalid token");
}

UserInfo userInfo = new(user.Id, email, (await _userManager.GetRolesAsync(user).ConfigureAwait(false)).ToList());
return GenerateJwtToken(userInfo);
}

public async Task<bool> AddUserRoleAsync(string email, Roles role)
Expand Down
Loading
Loading