diff --git a/AyCode.Core/Helpers/PasswordHasher.cs b/AyCode.Core/Helpers/PasswordHasher.cs index 5d8fcfb..8875381 100644 --- a/AyCode.Core/Helpers/PasswordHasher.cs +++ b/AyCode.Core/Helpers/PasswordHasher.cs @@ -8,8 +8,10 @@ namespace AyCode.Core.Helpers { public class PasswordHasher { - public static string HashPassword(string password, string? dynamicSalt = null) + public static string HashPassword(string? password, string? dynamicSalt = null) { + if (string.IsNullOrWhiteSpace(password)) throw new ArgumentNullException(nameof(password)); + // Generate a random salt var salt = new byte[16]; using (var rng = RandomNumberGenerator.Create()) rng.GetBytes(salt); @@ -29,6 +31,9 @@ namespace AyCode.Core.Helpers // Extract the salt and hashed password from the combined hash var parts = hashedPassword.Split('$'); + + if (parts.Length != 5) return false; + var salt = Convert.FromBase64String(parts[3].Replace("salt=", string.Empty)); var storedHash = parts[4].Replace("hash=", string.Empty); diff --git a/AyCode.Database.Tests/Users/AcUserDalTestBase.cs b/AyCode.Database.Tests/Users/AcUserDalTestBase.cs index 8230842..c44bf04 100644 --- a/AyCode.Database.Tests/Users/AcUserDalTestBase.cs +++ b/AyCode.Database.Tests/Users/AcUserDalTestBase.cs @@ -21,7 +21,7 @@ namespace AyCode.Database.Tests.Users protected TUser AcBase_GetUserById_ReturnsUser_WhenUserExists(string userIdString) { var userId = Guid.Parse(userIdString); - var user = Dal.GetUserById(userId); + var user = Dal.GetUserById(userId, false); Assert.IsNotNull(user, "User is null"); Assert.IsNotNull(user.Profile, "Profile is null"); @@ -34,7 +34,7 @@ namespace AyCode.Database.Tests.Users protected TUser AcBase_GetUserByEmail_ReturnsUser_WhenUserExists(string email) { - var user = Dal.GetUserByEmail(email); + var user = Dal.GetUserByEmail(email, false); Assert.IsNotNull(user, "User is null"); Assert.IsNotNull(user.Profile, "Profile is null"); @@ -49,7 +49,7 @@ namespace AyCode.Database.Tests.Users { TUser? user = null; - user = await Dal.GetUserByEmailAsync(email).ConfigureAwait(false); + user = await Dal.GetUserByEmailAsync(email, false).ConfigureAwait(false); //user = await Dal.SessionAsync(ctx => ctx.Users.FirstOrDefault(x => x.EmailAddress == email)).ConfigureAwait(false); diff --git a/AyCode.Database/DataLayers/Users/AcUserDalBase.cs b/AyCode.Database/DataLayers/Users/AcUserDalBase.cs index 970434a..156739a 100644 --- a/AyCode.Database/DataLayers/Users/AcUserDalBase.cs +++ b/AyCode.Database/DataLayers/Users/AcUserDalBase.cs @@ -27,11 +27,11 @@ namespace AyCode.Database.DataLayers.Users where TUserToServiceProvider : class, IAcUserToServiceProviderBase where TProfileAddress : class, IAcAddress { - public TUser? GetUserById(Guid userId) => Session(x => x.GetUserById(userId)); - public Task GetUserByIdAsync(Guid userId) => SessionAsync(x => x.GetUserById(userId)); + public TUser? GetUserById(Guid userId, bool onlyConfirmed) => Session(x => x.GetUserById(userId, onlyConfirmed)); + public Task GetUserByIdAsync(Guid userId, bool onlyConfirmed) => SessionAsync(x => x.GetUserById(userId, onlyConfirmed)); - public TUser? GetUserByEmail(string? email) => Session(x => x.GetUserByEmail(email)); - public Task GetUserByEmailAsync(string? email) => SessionAsync(x => x.GetUserByEmail(email)); + public TUser? GetUserByEmail(string? email, bool onlyConfirmed) => Session(x => x.GetUserByEmail(email, onlyConfirmed)); + public Task GetUserByEmailAsync(string? email, bool onlyConfirmed) => SessionAsync(x => x.GetUserByEmail(email, onlyConfirmed)); public Task AddUserAsync(TUser user) { @@ -72,18 +72,19 @@ namespace AyCode.Database.DataLayers.Users profile.Address = address; profile.AddressId = address.Id; + return profile; } public Task RemoveUserAsync(TUser? user) => TransactionAsync(ctx => RemoveUserTransactionBody(user, ctx)); public Task RemoveUserAsync(Guid userId) => TransactionAsync(ctx => { - var user = ctx.GetUserById(userId); + var user = ctx.GetUserById(userId, false); return RemoveUserTransactionBody(user, ctx); }); - protected bool RemoveUserTransactionBody(TUser? user, TDbContext ctx) + protected virtual bool RemoveUserTransactionBody(TUser? user, TDbContext ctx) { if (user == null) return false; @@ -106,15 +107,24 @@ namespace AyCode.Database.DataLayers.Users var result = Transaction(ctx => { - user = ctx.GetUserByEmail(email); - if (user is { EmailConfirmed: true } && string.Equals(user.EmailAddress, email, StringComparison.CurrentCultureIgnoreCase) - && PasswordHasher.VerifyPassword(password, user.Password, PasswordHasher.GenerateDynamicSalt(user.Id))) + try { - if (ctx.UpdateJwtRefreshToken(user, refreshToken)) return true; + //var passwordHash = PasswordHasher.HashPassword(password, PasswordHasher.GenerateDynamicSalt(userId)); - errorCodeInner = AcErrorCode.RefreshTokenUpdateError; + user = ctx.GetUserByEmail(email, true); + if (user is { EmailConfirmed: true } && string.Equals(user.EmailAddress, email, StringComparison.CurrentCultureIgnoreCase) + && PasswordHasher.VerifyPassword(password, user.Password, PasswordHasher.GenerateDynamicSalt(user.Id))) + { + if (ctx.UpdateJwtRefreshToken(user, refreshToken)) return true; + + errorCodeInner = AcErrorCode.RefreshTokenUpdateError; + } + else errorCodeInner = AcErrorCode.WrongLoginData; + } + catch (Exception ex) + { + errorCodeInner = AcErrorCode.UnknownError; } - else errorCodeInner = AcErrorCode.WrongLoginData; return false; }); diff --git a/AyCode.Database/DbSets/Users/AcUserDbSetExtensions.cs b/AyCode.Database/DbSets/Users/AcUserDbSetExtensions.cs index 49fb333..cd2bf6a 100644 --- a/AyCode.Database/DbSets/Users/AcUserDbSetExtensions.cs +++ b/AyCode.Database/DbSets/Users/AcUserDbSetExtensions.cs @@ -9,23 +9,31 @@ namespace AyCode.Database.DbSets.Users; public static class AcUserDbSetExtensions { - public static TUser? GetUserById(this IAcUserDbSetBase ctx, Guid userId) where TUser : class, IAcUserBase - => ctx.GetUsersById(userId).FirstOrDefault(); - - public static TUser? GetUserByEmail(this IAcUserDbSetBase ctx, string? email) where TUser : class, IAcUserBase + public static TUser? AuthenticateUser(this IAcUserDbSetBase ctx, string? email, string? passwordHash, bool onlyConfirmed) where TUser : class, IAcUserBase { - return string.IsNullOrWhiteSpace(email) ? null : ctx.GetUsersByEmail(email).FirstOrDefault(); + if (string.IsNullOrWhiteSpace(email) || string.IsNullOrWhiteSpace(passwordHash)) + return null; + + return ctx.GetUsersByEmail(email, onlyConfirmed).SingleOrDefault(u => u.Password == passwordHash); } - public static IQueryable GetUsersById(this IAcUserDbSetBase ctx, Guid userId) where TUser : class, IAcUserBase - => ctx.Users.Where(u => u.Id == userId); + public static TUser? GetUserById(this IAcUserDbSetBase ctx, Guid userId, bool onlyConfirmed) where TUser : class, IAcUserBase + => ctx.GetUsersById(userId, onlyConfirmed).FirstOrDefault(); - public static IQueryable GetUsersByEmail(this IAcUserDbSetBase ctx, string email) where TUser : class, IAcUserBase + public static TUser? GetUserByEmail(this IAcUserDbSetBase ctx, string? email, bool onlyConfirmed) where TUser : class, IAcUserBase + { + return string.IsNullOrWhiteSpace(email) ? null : ctx.GetUsersByEmail(email, onlyConfirmed).FirstOrDefault(); + } + + public static IQueryable GetUsersById(this IAcUserDbSetBase ctx, Guid userId, bool onlyConfirmed) where TUser : class, IAcUserBase + => ctx.Users.Where(u => u.Id == userId && (!onlyConfirmed || u.EmailConfirmed)); + + public static IQueryable GetUsersByEmail(this IAcUserDbSetBase ctx, string email, bool onlyConfirmed) where TUser : class, IAcUserBase { Logger.Info($"GetUserByEmail: {email}"); var emailLower = email.ToLower(); - return ctx.Users.Where(u => u.EmailAddress == emailLower); + return ctx.Users.Where(u => u.EmailAddress == emailLower && (!onlyConfirmed || u.EmailConfirmed)); } public static bool AddUser(this IAcUserDbSetBase ctx, TUser user) where TUser : class, IAcUserBase @@ -42,7 +50,7 @@ public static class AcUserDbSetExtensions public static bool RemoveUser(this IAcUserDbSetBase ctx, Guid userId) where TUser : class, IAcUserBase { - var user = ctx.GetUserById(userId); + var user = ctx.GetUserById(userId, false); if (user == null) return false; @@ -65,7 +73,7 @@ public static class AcUserDbSetExtensions if (string.IsNullOrWhiteSpace(refreshToken)) return null; - var existingUser = ctx.GetUserByEmail(email); + var existingUser = ctx.GetUserByEmail(email, true); if (existingUser == null) return null; existingUser.RefreshToken = refreshToken;