diff --git a/AyCode.Core/Consts/AcErrorCode.cs b/AyCode.Core/Consts/AcErrorCode.cs index 0bcb72d..be23635 100644 --- a/AyCode.Core/Consts/AcErrorCode.cs +++ b/AyCode.Core/Consts/AcErrorCode.cs @@ -55,5 +55,7 @@ DisableAddUser = 44, PhoneNumberFormatIsNotValid = 45, RefreshTokenUpdateError = 50, + + EmailIsNullOrEmpty = 55, } } diff --git a/AyCode.Interfaces.Server/Logins/IAcLoginServiceServer.cs b/AyCode.Interfaces.Server/Logins/IAcLoginServiceServer.cs index 1352f2c..d0b24bc 100644 --- a/AyCode.Interfaces.Server/Logins/IAcLoginServiceServer.cs +++ b/AyCode.Interfaces.Server/Logins/IAcLoginServiceServer.cs @@ -1,4 +1,5 @@ using System.Net; +using AyCode.Core.Consts; using AyCode.Interfaces.Addresses; using AyCode.Interfaces.Logins; using AyCode.Interfaces.Profiles; @@ -21,4 +22,6 @@ public interface IAcLoginServiceServer LoginAsync(string? email, string password); + + public AcErrorCode UpdatePassword(TUser user, string password); } \ No newline at end of file diff --git a/AyCode.Interfaces/Logins/IAcLoginServiceCommon.cs b/AyCode.Interfaces/Logins/IAcLoginServiceCommon.cs index d1a359d..9aec120 100644 --- a/AyCode.Interfaces/Logins/IAcLoginServiceCommon.cs +++ b/AyCode.Interfaces/Logins/IAcLoginServiceCommon.cs @@ -23,4 +23,7 @@ public interface IAcLoginServiceCommon ChangePasswordAsync(Guid userId, string oldPassword, string newPassword); + + public AcErrorCode ForgotPassword(string email, string newPassword); + public Task ForgotPasswordAsync(string email, string newPassword); } \ No newline at end of file diff --git a/AyCode.Models/Users/AcChangePasswordDto.cs b/AyCode.Models/Users/AcChangePasswordDto.cs index 1812714..912c504 100644 --- a/AyCode.Models/Users/AcChangePasswordDto.cs +++ b/AyCode.Models/Users/AcChangePasswordDto.cs @@ -6,19 +6,18 @@ using System.Threading.Tasks; namespace AyCode.Models.Users { - public abstract class AcChangePasswordDto + public abstract class AcChangePasswordDto : AcPasswordDtoBase { public Guid UserId { get; set; } public string OldPassword { get; set; } - public string NewPassword { get; set; } - protected AcChangePasswordDto() { } + protected AcChangePasswordDto() : base() + { } - protected AcChangePasswordDto(Guid userId, string oldPassword, string newPassword) : this() + protected AcChangePasswordDto(Guid userId, string oldPassword, string newPassword) : base(newPassword) { UserId = userId; OldPassword = oldPassword; - NewPassword = newPassword; } } } diff --git a/AyCode.Models/Users/AcForgotPasswordDto.cs b/AyCode.Models/Users/AcForgotPasswordDto.cs new file mode 100644 index 0000000..1708252 --- /dev/null +++ b/AyCode.Models/Users/AcForgotPasswordDto.cs @@ -0,0 +1,14 @@ +namespace AyCode.Models.Users; + +public abstract class AcForgotPasswordDto : AcPasswordDtoBase +{ + public string Email { get; set; } + + protected AcForgotPasswordDto() : base() + { } + + protected AcForgotPasswordDto(string email, string newPassword) : base(newPassword) + { + Email = email; + } +} \ No newline at end of file diff --git a/AyCode.Models/Users/AcPasswordDtoBase.cs b/AyCode.Models/Users/AcPasswordDtoBase.cs new file mode 100644 index 0000000..97bbd98 --- /dev/null +++ b/AyCode.Models/Users/AcPasswordDtoBase.cs @@ -0,0 +1,13 @@ +namespace AyCode.Models.Users; + +public abstract class AcPasswordDtoBase +{ + public string NewPassword { get; set; } + + protected AcPasswordDtoBase() { } + + protected AcPasswordDtoBase(string newPassword) : this() + { + NewPassword = newPassword; + } +} \ No newline at end of file diff --git a/AyCode.Services.Server.Tests/LoginServices/AcLoginServiceServerTestBase.cs b/AyCode.Services.Server.Tests/LoginServices/AcLoginServiceServerTestBase.cs index 0b55c2c..649a3bd 100644 --- a/AyCode.Services.Server.Tests/LoginServices/AcLoginServiceServerTestBase.cs +++ b/AyCode.Services.Server.Tests/LoginServices/AcLoginServiceServerTestBase.cs @@ -101,32 +101,38 @@ namespace AyCode.Services.Server.Tests.LoginServices } [DataTestMethod] - [DataRow(["", "", "", ""])] - public virtual void AcBase_ChangePassword_ReturnUser_WhenUserLoggedInWithNewPassword(string[] userIdOldPasswordNewPasswordDbBackupHashStrings) + [DataRow(["", "", ""])] + public virtual void AcBase_ChangePassword_ReturnUser_WhenUserLoggedInWithNewPassword(string[] userIdOriginalPasswordNewPasswordStrings) { - var userId = Guid.Parse(userIdOldPasswordNewPasswordDbBackupHashStrings[0]); - var oldPassword = userIdOldPasswordNewPasswordDbBackupHashStrings[1]; - var newPassword = userIdOldPasswordNewPasswordDbBackupHashStrings[2]; - var oldPasswordBackupHash = userIdOldPasswordNewPasswordDbBackupHashStrings[3]; - - var user = Dal.GetUserById(userId, false)!; - - //Visszaállítjuk az eredeti jelszót... - J. - if (!PasswordHasher.VerifyPassword(oldPassword, user.Password, PasswordHasher.GenerateDynamicSalt(userId))) - { - user.Password = oldPasswordBackupHash; - Dal.UpdateUser(user); - } + var userId = Guid.Parse(userIdOriginalPasswordNewPasswordStrings[0]); + var originalPassword = userIdOriginalPasswordNewPasswordStrings[1]; + var newPassword = userIdOriginalPasswordNewPasswordStrings[2]; var loginService = Activator.CreateInstance(typeof(TLoginServiceServer), Dal, AcEnv.AppConfiguration) as TLoginServiceServer; Assert.IsNotNull(loginService); - var errorCode = loginService.ChangePassword(userId, oldPassword, newPassword); + var user = Dal.GetUserById(userId, false)!; + RestoreOriginalPassword(loginService, user, originalPassword); + + var errorCode = loginService.ChangePassword(userId, originalPassword, newPassword); Assert.IsTrue(errorCode == AcErrorCode.Unset, $"{errorCode}"); var loggedInModel = loginService.Login(user.EmailAddress, newPassword); + Assert.IsNotNull(loggedInModel); + Assert.IsTrue(loggedInModel.IsLoggedIn); + Assert.IsTrue(loggedInModel.LoggedInUser.Id == userId); + Assert.IsTrue(PasswordHasher.VerifyPassword(newPassword, loggedInModel.LoggedInUser.Password, PasswordHasher.GenerateDynamicSalt(userId))); + + RestoreOriginalPassword(loginService, loggedInModel.LoggedInUser, originalPassword); + } + + protected void RestoreOriginalPassword(TLoginServiceServer loginService, TUser user, string originalPassword) + { + if (PasswordHasher.VerifyPassword(originalPassword, user.Password, PasswordHasher.GenerateDynamicSalt(user.Id))) return; + + loginService.UpdatePassword(user, originalPassword); } } } \ No newline at end of file diff --git a/AyCode.Services.Server/Logins/AcLoginServiceServer.cs b/AyCode.Services.Server/Logins/AcLoginServiceServer.cs index 953eaf6..1980a52 100644 --- a/AyCode.Services.Server/Logins/AcLoginServiceServer.cs +++ b/AyCode.Services.Server/Logins/AcLoginServiceServer.cs @@ -117,6 +117,10 @@ public class AcLoginServiceServer RegistrationAsync(Guid userId, string email, string password, string? phoneNumber = null, Guid? referralId = null) => TaskHelper.ToThreadPoolTask(() => Registration(userId, email, password, phoneNumber, referralId)); + + public virtual Task ChangePasswordAsync(Guid userId, string oldPassword, string newPassword) + => TaskHelper.ToThreadPoolTask(() => ChangePassword(userId, oldPassword, newPassword)); + public virtual AcErrorCode ChangePassword(Guid userId, string oldPassword, string newPassword) { try @@ -127,11 +131,7 @@ public class AcLoginServiceServer ChangePasswordAsync(Guid userId, string oldPassword, string newPassword) - => TaskHelper.ToThreadPoolTask(() => ChangePassword(userId, oldPassword, newPassword)); + + public Task ForgotPasswordAsync(string email, string newPassword) + => TaskHelper.ToThreadPoolTask(() => ForgotPassword(email, newPassword)); + + public AcErrorCode ForgotPassword(string email, string newPassword) + { + try + { + if (email.IsNullOrEmpty()) return AcErrorCode.EmailIsNullOrEmpty; + if (!AcValidate.IsValidPasswordFormat(newPassword, out var errorCode)) return errorCode; + + var user = userDal.GetUserByEmail(email, false); + return user == null ? AcErrorCode.EntityIsNull : UpdatePassword(user, newPassword); + } + catch (Exception) + { + // ignored + } + + return AcErrorCode.UnknownError; + } + + + public AcErrorCode UpdatePassword(TUser user, string password) + { + user.Password = PasswordHasher.HashPassword(password, PasswordHasher.GenerateDynamicSalt(user.Id)); + + return userDal.UpdateUser(user) == null ? AcErrorCode.DatabaseError : AcErrorCode.Unset; + } public virtual bool SendConfirmationToken(string? email, string confirmationToken) { diff --git a/AyCode.Services/Logins/AcLoginServiceClient.cs b/AyCode.Services/Logins/AcLoginServiceClient.cs index d1878ca..5777b6c 100644 --- a/AyCode.Services/Logins/AcLoginServiceClient.cs +++ b/AyCode.Services/Logins/AcLoginServiceClient.cs @@ -66,4 +66,14 @@ public class AcLoginServiceClient ForgotPasswordAsync(string email, string newPassword) + { + throw new NotImplementedException(); + } } \ No newline at end of file