147 lines
5.8 KiB
C#
147 lines
5.8 KiB
C#
using System.Security.Cryptography;
|
|
using Application.Common.Results;
|
|
using Application.DTOs;
|
|
using Application.Errors;
|
|
using Application.Interfaces;
|
|
using Application.Models;
|
|
using Application.Validators;
|
|
using Domain.Entities;
|
|
using Domain.Interface;
|
|
using Infrastructure.Utilities;
|
|
using Microsoft.AspNetCore.Identity;
|
|
|
|
namespace Application.Services;
|
|
|
|
public class AuthenticationService(
|
|
IUnitOfWork unitOfWork,
|
|
IUserRepository iUserRepository,
|
|
LoginRequestValidator loginRequestValidator,
|
|
RegisterRequestValidator registerRequestValidator,
|
|
IJwtService jwtService,
|
|
IEmailService emailService) : IAuthenticationService
|
|
{
|
|
public async Task<Result> RegisterAsync(RegisterRequest registerRequest)
|
|
{
|
|
var validationResult = await registerRequestValidator.ValidateAsync(registerRequest);
|
|
if (!validationResult.IsValid)
|
|
{
|
|
var errors = validationResult.Errors.Select(x => x.ErrorMessage);
|
|
return Result.Failure(AuthError.CreateInvalidRegisterRequestError(errors));
|
|
}
|
|
|
|
var emailExists = await iUserRepository.GetUserByEmailAsync(registerRequest.Email);
|
|
if (emailExists is not null) return Result.Failure(AuthError.EmailAlreadyExists);
|
|
|
|
var usernameExists = await iUserRepository.GetUserByUsernameAsync(registerRequest.Username);
|
|
if (usernameExists is not null) return Result.Failure(AuthError.UsernameAlreadyExists);
|
|
|
|
var user = new User
|
|
{
|
|
Username = registerRequest.Username,
|
|
Email = registerRequest.Email,
|
|
Password = registerRequest.Password,
|
|
UserRoles = [new UserRole { RoleId = 3 }]
|
|
};
|
|
|
|
var passwordHasher = new PasswordHasher<User>();
|
|
var hashedPassword = passwordHasher.HashPassword(user, registerRequest.Password);
|
|
user.Password = hashedPassword;
|
|
|
|
await iUserRepository.AddAsync(user);
|
|
await unitOfWork.CommitAsync();
|
|
return Result.Success("User registered successfully.");
|
|
}
|
|
|
|
public async Task<Result> LoginAsync(LoginRequest loginRequest)
|
|
{
|
|
var validationResult = await loginRequestValidator.ValidateAsync(loginRequest);
|
|
if (!validationResult.IsValid)
|
|
{
|
|
var errors = validationResult.Errors.Select(x => x.ErrorMessage);
|
|
return Result.Failure(AuthError.CreateInvalidLoginRequestError(errors));
|
|
}
|
|
|
|
var (email, password) = loginRequest;
|
|
var user = await iUserRepository.GetUserByEmailAsync(email);
|
|
if (user is null) return Result.Failure(AuthError.UserNotFound);
|
|
|
|
var passwordHasher = new PasswordHasher<User>();
|
|
var verificationResult = passwordHasher.VerifyHashedPassword(user, user.Password, password);
|
|
if (verificationResult == PasswordVerificationResult.Failed)
|
|
return Result.Failure(AuthError.InvalidPassword);
|
|
|
|
user.LastLogin = DateTime.UtcNow;
|
|
iUserRepository.Update(user);
|
|
|
|
|
|
var token = new TokenResponse
|
|
{
|
|
AccessToken = await jwtService.GenerateTokenAsync(user),
|
|
RefreshToken = await jwtService.GenerateAndSaveRefreshTokenAsync(user)
|
|
};
|
|
var result = new
|
|
{
|
|
Token = token, user.Username
|
|
};
|
|
|
|
return Result.Success(result);
|
|
}
|
|
|
|
public async Task<Result> RefreshTokensAsync(RefreshTokenRequest request)
|
|
{
|
|
var user = await jwtService.ValidateRefreshTokenAsync(request.UserId, request.RefreshToken);
|
|
if (user is null) return Result.Failure(AuthError.UserNotFound);
|
|
|
|
var result = new TokenResponse
|
|
{
|
|
AccessToken = await jwtService.GenerateTokenAsync(user),
|
|
RefreshToken = await jwtService.GenerateAndSaveRefreshTokenAsync(user)
|
|
};
|
|
|
|
return Result.Success(result);
|
|
}
|
|
|
|
public async Task<Result> SendResetEmailAsync(string email)
|
|
{
|
|
var user = await iUserRepository.GetUserByEmailAsync(email);
|
|
if (user is null) return Result.Failure(AuthError.UserNotFound);
|
|
|
|
var tokenBytes = RandomNumberGenerator.GetBytes(64);
|
|
var emailToken = Convert.ToBase64String(tokenBytes);
|
|
user.ResetPasswordToken = emailToken;
|
|
user.ResetPasswordTokenExpiryTime = DateTime.UtcNow.AddMinutes(15);
|
|
var emailModel = new EmailRequest(email, "Reset Password!!", EmailBody.EmailStringBody(email, emailToken));
|
|
emailService.SendEmailAsync(emailModel);
|
|
iUserRepository.Update(user);
|
|
await unitOfWork.CommitAsync();
|
|
return Result.Success("Reset email sent successfully");
|
|
}
|
|
|
|
public async Task<Result> ResetPasswordAsync(ResetPasswordDto resetPasswordDto)
|
|
{
|
|
// Normalize the incoming token if '+' was converted to space by transport layers.
|
|
var normalizedToken = (resetPasswordDto.EmailToken ?? string.Empty).Replace(" ", "+");
|
|
|
|
var user = await iUserRepository.GetUserByEmailAsync(resetPasswordDto.Email);
|
|
if (user is null) return Result.Failure(AuthError.UserNotFound);
|
|
|
|
var tokenCode = user.ResetPasswordToken;
|
|
var emailTokenExpiryTime = user.ResetPasswordTokenExpiryTime;
|
|
|
|
// Validate token and expiration using UTC to match stored times
|
|
if (string.IsNullOrWhiteSpace(normalizedToken) || tokenCode != normalizedToken ||
|
|
emailTokenExpiryTime < DateTime.UtcNow) return Result.Failure(AuthError.InvalidResetLink);
|
|
|
|
var passwordHasher = new PasswordHasher<User>();
|
|
var hashedPassword = passwordHasher.HashPassword(user, resetPasswordDto.NewPassword);
|
|
user.Password = hashedPassword;
|
|
|
|
// Invalidate the reset token after successful use
|
|
user.ResetPasswordToken = null;
|
|
user.ResetPasswordTokenExpiryTime = default;
|
|
|
|
iUserRepository.Update(user);
|
|
await unitOfWork.CommitAsync();
|
|
return Result.Success("Password reset successfully");
|
|
}
|
|
} |