Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
**/[Bb]in/
**/[Oo]bj/
.vs/
.git/
*.user
1 change: 1 addition & 0 deletions Application/Application.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>

<ItemGroup>
Expand Down
3 changes: 3 additions & 0 deletions Application/ApplicationServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using Application.Features.Appointments.Services;
using Application.Features.Auth.Interfaces;
using Application.Features.Auth.Services;
using Application.Features.Payment.Interfaces;
using Application.Features.Payment.Services;
using Application.Features.Service.Services;
using Application.Features.Tenant.Services;
using Domain.Interfaces;
Expand All @@ -18,6 +20,7 @@ public static IServiceCollection AddApplication(this IServiceCollection services
services.AddScoped<IPasswordRecoveryService, PasswordRecoveryService>();
services.AddScoped<TenantServiceContract,TenantService>();
services.AddScoped<TenantProviderContract, TenantProvider>();
services.AddScoped<PaymentServiceContract, PaymentService>();
return services;
}
}
11 changes: 11 additions & 0 deletions Application/Features/Auth/DTOs/IdentityResponseDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Application.Features.Auth.DTOs;

public class IdentifyByEmailResponseDto
{
public List<IdentityResponseDto> IdentityResponseDtos { get; set; }
}
public class IdentityResponseDto
{
public Guid TenantId { get; set; }
public string Name { get; set; }
}
10 changes: 8 additions & 2 deletions Application/Features/Auth/DTOs/LoginUserResponseDto.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
using Domain.Entities;

namespace Application.Features.Auth.DTOs;

public record LoginUserResponseDto(
string AccessToken,
string? RefreshToken);
string? AccessToken,
string RefreshToken,
List<IdentityResponse>? IdentityResponses);

public record IdentityResponse(
Guid tenantId,
string Name);
public record RefreshTokenRequest (string RefreshToken);
6 changes: 6 additions & 0 deletions Application/Features/Auth/DTOs/SelectTenantRequestDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Application.Features.Auth.DTOs;

public record SelectTenantRequestDto(
string email,
string password,
Guid tenantId);
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ namespace Application.Features.Auth.Interfaces;

public interface IPasswordRecoveryService
{
Task ForgetPasswordAsync(string email);

Task ResetPasswordAsync(string email, string code, string newPassword);
// Task ForgetPasswordAsync(string email);
//
// Task ResetPasswordAsync(string email, string code, string newPassword);
}
4 changes: 3 additions & 1 deletion Application/Features/Auth/Interfaces/IUserRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ namespace Application.Features.Auth.Interfaces;
public interface IUserRepository
{
Task RegisterUserAsync(User user);
Task<User?> GetUserByEmailAsync(string email);
Task<List<User>?> GetUsersByEmailAsync(string email);
Task<List<Domain.Entities.Tenant?>> GetIdentityByEmailAsync(string email);
Task<bool> IsUserExistsByIdAsync(Guid userId);
Task<bool> IsUserExistsByEmailAsync(string email);
Task SaveChangesAsync();
Task<List<ViewUser>> GetAllUsersAsync();
Task<User?> GetUserByIdAsync(Guid userId);
Task<User?> GetUserByEmailAndTenantIdAsync(string email, Guid tenantId);
}
1 change: 1 addition & 0 deletions Application/Features/Auth/Interfaces/IUserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public interface IUserService
Task<string> RegisterUserAsync(RegisterUserRequestDto registerUserRequestDto);
Task<LoginUserResponseDto> LoginUserAsync(LoginUserRequestDto loginUserRequestDto);
Task ChangeRoleTo(UserRole role);
Task<LoginUserResponseDto> SelectTenantIdAsync(SelectTenantRequestDto dto);
Task<string> LogoutUserAsync();
Task<LoginUserResponseDto> RefreshTokenAsync(string refreshToken);
Task<ProfileResponseDto> ViewProfileAsync();
Expand Down
91 changes: 46 additions & 45 deletions Application/Features/Auth/Services/PasswordRecoveryService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,51 +17,52 @@ public PasswordRecoveryService(IHasher hasher, IUserRepository userRepository, I
_codeGenerator = codeGenerator;
}

//Todo: fix the logic of reset (Multi-Tenants)

public async Task ResetPasswordAsync(string email,string code,string newPassword)
{
var user=await _userRepository.GetUserByEmailAsync(email);
if (user == null)
throw new InvalidOperationException("Invalid request");

var codeHash=_hasher.Hash(code);

if (!user.CanUseResetPassword(codeHash, DateTime.UtcNow))
{
user.IncreasePasswordResetAttemptCount();
await _userRepository.SaveChangesAsync();
throw new InvalidOperationException("Incorrect or expired code");
}

var hashedPassword = _hasher.Hash(newPassword);
user.ResetPassword(hashedPassword, DateTime.UtcNow);
user.ClearPasswordResetCode();

await _userRepository.SaveChangesAsync();
}
// public async Task ResetPasswordAsync(string email,string code,string newPassword)
// {
// var user=await _userRepository.GetUserByEmailAsync(email);
// if (user == null)
// throw new InvalidOperationException("Invalid request");
//
// var codeHash=_hasher.Hash(code);
//
// if (!user.CanUseResetPassword(codeHash, DateTime.UtcNow))
// {
// user.IncreasePasswordResetAttemptCount();
// await _userRepository.SaveChangesAsync();
// throw new InvalidOperationException("Incorrect or expired code");
// }
//
// var hashedPassword = _hasher.Hash(newPassword);
// user.ResetPassword(hashedPassword, DateTime.UtcNow);
// user.ClearPasswordResetCode();
//
// await _userRepository.SaveChangesAsync();
// }

public async Task ForgetPasswordAsync(string email)
{
var user = await _userRepository.GetUserByEmailAsync(email);
if (user == null)
throw new NotFoundException($"user with email : {email} not found");

var code = _codeGenerator.Generate6DigitCode();
var codeHash = _hasher.Hash(code);

user.ResetPassword(
codeHash,
DateTime.UtcNow.AddMinutes(5)
);

await _userRepository.SaveChangesAsync();

//Implement Email Service

// await _emailService.SendAsync(
// email,
// "Password Reset Code",
// $"Your verification code is: {code}"
// );
}
// public async Task ForgetPasswordAsync(string email)
// {
// var user = await _userRepository.GetUserByEmailAsync(email);
// if (user == null)
// throw new NotFoundException($"user with email : {email} not found");
//
// var code = _codeGenerator.Generate6DigitCode();
// var codeHash = _hasher.Hash(code);
//
// user.ResetPassword(
// codeHash,
// DateTime.UtcNow.AddMinutes(5)
// );
//
// await _userRepository.SaveChangesAsync();
//
// //Implement Email Service
//
// // await _emailService.SendAsync(
// // email,
// // "Password Reset Code",
// // $"Your verification code is: {code}"
// // );
// }
}
83 changes: 64 additions & 19 deletions Application/Features/Auth/Services/UserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public async Task<string> RegisterUserAsync(RegisterUserRequestDto registerUserR

if (await _userRepository.IsUserExistsByEmailAsync(registerUserRequestDto.Email))
throw new DuplicateUserException("Email already exists");

var user = new User(registerUserRequestDto.FullName, registerUserRequestDto.Email,
registerUserRequestDto.PhoneNumber, password);
await _userRepository.RegisterUserAsync(user);
Expand All @@ -47,19 +47,71 @@ public async Task<string> RegisterUserAsync(RegisterUserRequestDto registerUserR

public async Task<LoginUserResponseDto> LoginUserAsync(LoginUserRequestDto loginUserRequestDto)
{
var user = await _userRepository.GetUserByEmailAsync(loginUserRequestDto.Email);
var users = await _userRepository.GetUsersByEmailAsync(loginUserRequestDto.Email);

if (user == null)
if (users == null)
throw new UnauthorizedAccessException("Invalid email or password");

var isValid = _passwordHasher.Verify(user.Password, loginUserRequestDto.Password);
var matchedUsers = users?
.Where(us => _passwordHasher.Verify(us.Password, loginUserRequestDto.Password))
.ToList();
if (matchedUsers.Count == 0)
throw new UnauthorizedAccessException("Invalid email or password");

if (!isValid)
if (matchedUsers.Count == 1)
{
var user = matchedUsers.First();

var token = _jwtTokenService.GenerateJwtToken(user);
var refreshTokenValue = _jwtTokenService.GenerateRefreshToken();
var refreshTokenHash = _hasher.Hash(refreshTokenValue);
var refreshToken = new RefreshToken
{
Id = Guid.NewGuid(),
Token = refreshTokenHash,
UserId = user.Id,
ExpiresAt = DateTime.UtcNow.AddDays(7)
};

await _refreshTokenRepository.AddAsync(refreshToken);
await _refreshTokenRepository.SaveChangesAsync();

return new LoginUserResponseDto(token, refreshTokenValue,null);
}

//Todo: challenge token

return new LoginUserResponseDto(null, null,matchedUsers.Select(x => new IdentityResponse
(
x.Tenant.Id,
x.Tenant.Name
)).ToList()
);
}

public async Task ChangeRoleTo(UserRole role)
{
var userId = _userContext.UserId;
var user = await _userRepository.GetUserByIdAsync(userId);
user?.ChangeRoleTo(role);
}

public async Task<LoginUserResponseDto> SelectTenantIdAsync(SelectTenantRequestDto dto)
{
var user = await _userRepository.GetUserByEmailAndTenantIdAsync(dto.email,dto.tenantId);

if (user == null)
throw new UnauthorizedAccessException("Invalid email or password");

var token = _jwtTokenService.GenerateJwtToken(user);
var isValid=_passwordHasher.Verify(user.Password,dto.password);

if (!isValid)
throw new UnauthorizedAccessException("Invalid email or password");

var accessToken = _jwtTokenService.GenerateJwtToken(user);
var refreshTokenValue = _jwtTokenService.GenerateRefreshToken();
var refreshTokenHash = _hasher.Hash(refreshTokenValue);

var refreshToken = new RefreshToken
{
Id = Guid.NewGuid(),
Expand All @@ -71,14 +123,7 @@ public async Task<LoginUserResponseDto> LoginUserAsync(LoginUserRequestDto login
await _refreshTokenRepository.AddAsync(refreshToken);
await _refreshTokenRepository.SaveChangesAsync();

return new LoginUserResponseDto(token, refreshTokenValue);
}

public async Task ChangeRoleTo(UserRole role)
{
var userId =_userContext.UserId;
var user =await _userRepository.GetUserByIdAsync(userId);
user?.ChangeRoleTo(role);
return new LoginUserResponseDto(accessToken,refreshTokenValue,null);
}

public async Task<string> LogoutUserAsync()
Expand Down Expand Up @@ -132,7 +177,7 @@ await _refreshTokenRepository.AddAsync(new RefreshToken
await _refreshTokenRepository.SaveChangesAsync();
return new LoginUserResponseDto(
newAccessToken,
newRefreshTokenValue ?? refreshToken
newRefreshTokenValue ?? refreshToken,null
);
}

Expand All @@ -154,10 +199,10 @@ public async Task<ProfileResponseDto> UpdateProfileAsync(UpdateProfileRequestDto
{
var userId = _userContext.UserId;
var user = await _userRepository.GetUserByIdAsync(userId)
?? throw new UnauthorizedAccessException("Invalid user id");
user.UpdateProfile(dto.FullName,dto.PhoneNumber);
?? throw new UnauthorizedAccessException("Invalid user id");

user.UpdateProfile(dto.FullName, dto.PhoneNumber);

await _userRepository.SaveChangesAsync();

return new ProfileResponseDto()
Expand Down
7 changes: 7 additions & 0 deletions Application/Features/Payment/DTOs/CreatePaymentDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Application.Features.Payment.DTOs;

public class CreatePaymentDto
{
public long Amount { get; set; }
public string PhoneNumber { get; set; }
}
6 changes: 6 additions & 0 deletions Application/Features/Payment/DTOs/CreatePaymentResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Application.Features.Payment.DTOs;

public class CreatePaymentResponse
{
public string PayUrl { get; set; }
}
12 changes: 12 additions & 0 deletions Application/Features/Payment/DTOs/SepTokenResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Application.Features.Payment.DTOs;

public class SepTokenResponse
{
public int status { get; set; }

public string? token { get; set; }

public string? errorCode { get; set; }

public string? errorDesc { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Application.Features.Payment.Interfaces;

public interface PaymentServiceContract
{
Task<string> GenerateResNum();
}
12 changes: 12 additions & 0 deletions Application/Features/Payment/Services/PaymentService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Security.Cryptography;
using Application.Features.Payment.Interfaces;

namespace Application.Features.Payment.Services;

public class PaymentService:PaymentServiceContract
{
public async Task<string> GenerateResNum()
{
return $"{Guid.NewGuid():N}";
}
}
Loading
Loading