diff --git a/src/JsonSchema/AppSettings.cs b/src/JsonSchema/AppSettings.cs index 4af6685d8a..8db2bbb8c7 100644 --- a/src/JsonSchema/AppSettings.cs +++ b/src/JsonSchema/AppSettings.cs @@ -82,6 +82,7 @@ namespace JsonSchema public BasicAuthSettings BasicAuth { get; set; } public PackageMigrationSettings PackageMigration { get; set; } + public LegacyPasswordMigrationSettings LegacyPasswordMigration { get; set; } } /// diff --git a/src/Umbraco.Core/Configuration/Models/LegacyPasswordMigrationSettings.cs b/src/Umbraco.Core/Configuration/Models/LegacyPasswordMigrationSettings.cs new file mode 100644 index 0000000000..c3909ed619 --- /dev/null +++ b/src/Umbraco.Core/Configuration/Models/LegacyPasswordMigrationSettings.cs @@ -0,0 +1,30 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.ComponentModel; + +namespace Umbraco.Cms.Core.Configuration.Models +{ + /// + /// Typed configuration options for legacy machine key settings used for migration of members from a v8 solution. + /// + [UmbracoOptions(Constants.Configuration.ConfigLegacyPasswordMigration)] + public class LegacyPasswordMigrationSettings + { + private const string StaticDecryptionKey = ""; + + /// + /// Gets the decryption algorithm. + /// + /// + /// Currently only AES is supported. This should include all machine keys generated by Umbraco. + /// + public string MachineKeyDecryption => "AES"; + + /// + /// Gets or sets the decryption hex-formatted string key found in legacy web.config machineKey configuration-element. + /// + [DefaultValue(StaticDecryptionKey)] + public string MachineKeyDecryptionKey { get; set; } = StaticDecryptionKey; + } +} diff --git a/src/Umbraco.Core/Constants-Configuration.cs b/src/Umbraco.Core/Constants-Configuration.cs index c36f5813ab..063d733821 100644 --- a/src/Umbraco.Core/Constants-Configuration.cs +++ b/src/Umbraco.Core/Constants-Configuration.cs @@ -26,6 +26,7 @@ public const string ConfigHostingDebug = ConfigHostingPrefix + "Debug"; public const string ConfigCustomErrorsMode = ConfigCustomErrorsPrefix + "Mode"; public const string ConfigActiveDirectory = ConfigPrefix + "ActiveDirectory"; + public const string ConfigLegacyPasswordMigration = ConfigPrefix + "LegacyPasswordMigration"; public const string ConfigContent = ConfigPrefix + "Content"; public const string ConfigCoreDebug = ConfigCorePrefix + "Debug"; public const string ConfigExceptionFilter = ConfigPrefix + "ExceptionFilter"; diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs index 0506a66ad2..b509c12ff5 100644 --- a/src/Umbraco.Core/Constants-Security.cs +++ b/src/Umbraco.Core/Constants-Security.cs @@ -66,6 +66,7 @@ namespace Umbraco.Cms.Core public const string AspNetCoreV2PasswordHashAlgorithmName = "PBKDF2.ASPNETCORE.V2"; public const string AspNetUmbraco8PasswordHashAlgorithmName = "HMACSHA256"; public const string AspNetUmbraco4PasswordHashAlgorithmName = "HMACSHA1"; + public const string UnknownPasswordConfigJson = "{\"hashAlgorithm\":\"Unknown\"}"; } } } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs index 9b31ed7056..6ef87464e8 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs @@ -73,6 +73,7 @@ namespace Umbraco.Cms.Core.DependencyInjection .AddUmbracoOptions() .AddUmbracoOptions() .AddUmbracoOptions() + .AddUmbracoOptions() .AddUmbracoOptions(); return builder; diff --git a/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs b/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs index bf56c3161d..8f6813e7ba 100644 --- a/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs +++ b/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs @@ -50,9 +50,33 @@ namespace Umbraco.Cms.Core.Security if (dbPassword.StartsWith(Constants.Security.EmptyPasswordPrefix)) return false; - var storedHashedPass = ParseStoredHashPassword(algorithm, dbPassword, out var salt); - var hashed = HashPassword(algorithm, password, salt); - return storedHashedPass == hashed; + try + { + var storedHashedPass = ParseStoredHashPassword(algorithm, dbPassword, out var salt); + var hashed = HashPassword(algorithm, password, salt); + return storedHashedPass == hashed; + } + catch (ArgumentOutOfRangeException) + { + //This can happen if the length of the password is wrong and a salt cannot be extracted. + return false; + } + + } + + /// + /// Verify a legacy hashed password (HMACSHA1) + /// + public bool VerifyLegacyHashedPassword(string password, string dbPassword) + { + var hashAlgorith = new HMACSHA1 + { + //the legacy salt was actually the password :( + Key = Encoding.Unicode.GetBytes(password) + }; + var hashed = Convert.ToBase64String(hashAlgorith.ComputeHash(Encoding.Unicode.GetBytes(password))); + + return dbPassword == hashed; } /// diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs index 4031971ddc..265e8344c8 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs @@ -774,6 +774,9 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { memberDto.PasswordConfig = DefaultPasswordConfigJson; changedCols.Add("passwordConfig"); + }else if (memberDto.PasswordConfig == Constants.Security.UnknownPasswordConfigJson) + { + changedCols.Add("passwordConfig"); } // do NOT update the password if it has not changed or if it is null or empty diff --git a/src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs b/src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs index 2d2cad1624..e470bf0a6c 100644 --- a/src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs +++ b/src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs @@ -1,9 +1,15 @@ using System; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; using Microsoft.AspNetCore.Identity; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Cms.Core.Security; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Security @@ -16,9 +22,27 @@ namespace Umbraco.Cms.Core.Security /// public class MemberPasswordHasher : UmbracoPasswordHasher { + private readonly IOptions _legacyMachineKeySettings; + private readonly ILogger _logger; + + [Obsolete("Use ctor with all params")] public MemberPasswordHasher(LegacyPasswordSecurity legacyPasswordHasher, IJsonSerializer jsonSerializer) + : this(legacyPasswordHasher, + jsonSerializer, + StaticServiceProvider.Instance.GetRequiredService>(), + StaticServiceProvider.Instance.GetRequiredService>()) + { + } + + public MemberPasswordHasher( + LegacyPasswordSecurity legacyPasswordHasher, + IJsonSerializer jsonSerializer, + IOptions legacyMachineKeySettings, + ILogger logger) : base(legacyPasswordHasher, jsonSerializer) { + _legacyMachineKeySettings = legacyMachineKeySettings; + _logger = logger; } /// @@ -36,10 +60,21 @@ namespace Umbraco.Cms.Core.Security throw new ArgumentNullException(nameof(user)); } + var isPasswordAlgorithmKnown = user.PasswordConfig.IsNullOrWhiteSpace() == false && + user.PasswordConfig != Constants.Security.UnknownPasswordConfigJson; // if there's password config use the base implementation - if (!user.PasswordConfig.IsNullOrWhiteSpace()) + if (isPasswordAlgorithmKnown) { - return base.VerifyHashedPassword(user, hashedPassword, providedPassword); + var result = base.VerifyHashedPassword(user, hashedPassword, providedPassword); + if (result != PasswordVerificationResult.Failed) + { + return result; + } + } + // We need to check for clear text passwords from members as the first thing. This was possible in v8 :( + else if (IsSuccessfulLegacyPassword(hashedPassword, providedPassword)) + { + return PasswordVerificationResult.SuccessRehashNeeded; } // Else we need to detect what the password is. This will be the case @@ -66,7 +101,16 @@ namespace Umbraco.Cms.Core.Security return base.VerifyHashedPassword(user, hashedPassword, providedPassword); } - throw new InvalidOperationException("unable to determine member password hashing algorith"); + if (isPasswordAlgorithmKnown) + { + _logger.LogError("Unable to determine member password hashing algorithm"); + } + else + { + _logger.LogDebug("Unable to determine member password hashing algorithm, but this can happen when member enters a wrong password, before it has be rehashed"); + } + + return PasswordVerificationResult.Failed; } var isValid = LegacyPasswordSecurity.VerifyPassword( @@ -76,5 +120,65 @@ namespace Umbraco.Cms.Core.Security return isValid ? PasswordVerificationResult.SuccessRehashNeeded : PasswordVerificationResult.Failed; } + + private bool IsSuccessfulLegacyPassword(string hashedPassword, string providedPassword) + { + if (!string.IsNullOrEmpty(_legacyMachineKeySettings.Value.MachineKeyDecryptionKey)) + { + try + { + var decryptedPassword = DecryptLegacyPassword(hashedPassword, _legacyMachineKeySettings.Value.MachineKeyDecryption, _legacyMachineKeySettings.Value.MachineKeyDecryptionKey); + return decryptedPassword == providedPassword; + } + catch (Exception ex) + { + _logger.LogError(ex, "Could not decrypt password even that a DecryptionKey is provided. This means the DecryptionKey is wrong."); + return false; + } + } + + var result = LegacyPasswordSecurity.VerifyPassword(Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName, providedPassword, hashedPassword); + return result || LegacyPasswordSecurity.VerifyPassword(Constants.Security.AspNetUmbraco4PasswordHashAlgorithmName, providedPassword, hashedPassword); + } + + private static string DecryptLegacyPassword(string encryptedPassword, string algorithmName, string decryptionKey) + { + SymmetricAlgorithm algorithm; + switch (algorithmName) + { + case "AES": + algorithm = new AesCryptoServiceProvider() + { + Key = StringToByteArray(decryptionKey), + IV = new byte[16] + }; + break; + default: + throw new NotSupportedException($"The algorithm ({algorithmName}) is not supported"); + } + + using (algorithm) + { + return DecryptLegacyPassword(encryptedPassword, algorithm); + } + } + + private static string DecryptLegacyPassword(string encryptedPassword, SymmetricAlgorithm algorithm) + { + using var memoryStream = new MemoryStream(); + ICryptoTransform cryptoTransform = algorithm.CreateDecryptor(); + var cryptoStream = new CryptoStream((Stream)memoryStream, cryptoTransform, CryptoStreamMode.Write); + var buf = Convert.FromBase64String(encryptedPassword); + cryptoStream.Write(buf, 0, 32); + cryptoStream.FlushFinalBlock(); + + return Encoding.Unicode.GetString(memoryStream.ToArray()); + } + + private static byte[] StringToByteArray(string hex) => + Enumerable.Range(0, hex.Length) + .Where(x => x % 2 == 0) + .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) + .ToArray(); } } diff --git a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs index 086ff6a66c..d757cfb088 100644 --- a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs +++ b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs @@ -592,6 +592,12 @@ namespace Umbraco.Cms.Core.Security member.PasswordConfiguration = identityUser.PasswordConfig; } + if (member.PasswordConfiguration != identityUser.PasswordConfig) + { + changeType = MemberDataChangeType.FullSave; + member.PasswordConfiguration = identityUser.PasswordConfig; + } + if (member.SecurityStamp != identityUser.SecurityStamp) { changeType = MemberDataChangeType.FullSave; diff --git a/src/Umbraco.Infrastructure/Security/UmbracoPasswordHasher.cs b/src/Umbraco.Infrastructure/Security/UmbracoPasswordHasher.cs index 73d6d2b025..da08bc8713 100644 --- a/src/Umbraco.Infrastructure/Security/UmbracoPasswordHasher.cs +++ b/src/Umbraco.Infrastructure/Security/UmbracoPasswordHasher.cs @@ -53,6 +53,13 @@ namespace Umbraco.Cms.Core.Security if (LegacyPasswordSecurity.SupportHashAlgorithm(deserialized.HashAlgorithm)) { var result = LegacyPasswordSecurity.VerifyPassword(deserialized.HashAlgorithm, providedPassword, hashedPassword); + + //We need to special handle this case, apparently v8 still saves the user algorithm as {"hashAlgorithm":"HMACSHA256"}, when using legacy encoding and hasinging. + if (result == false) + { + result = LegacyPasswordSecurity.VerifyLegacyHashedPassword(providedPassword, hashedPassword); + } + return result ? PasswordVerificationResult.SuccessRehashNeeded : PasswordVerificationResult.Failed; diff --git a/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs b/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs index bf198af1c3..dfef27242b 100644 --- a/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs +++ b/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs @@ -96,7 +96,7 @@ namespace Umbraco.Cms.Core.Security string password = _passwordGenerator.GeneratePassword(); return password; } - + /// /// Used to validate the password without an identity user /// Validation code is based on the default ValidatePasswordAsync code @@ -205,6 +205,8 @@ namespace Umbraco.Cms.Core.Security await lockoutStore.ResetAccessFailedCountAsync(user, CancellationToken.None); + //Ensure the password config is null, so it is set to the default in repository + user.PasswordConfig = null; return await UpdateAsync(user); } @@ -234,6 +236,11 @@ namespace Umbraco.Cms.Core.Security // here we are persisting the value for the back office } + if (string.IsNullOrEmpty(user.PasswordConfig)) + { + //We cant pass null as that would be interpreted as the default algoritm, but due to the failing attempt we dont know. + user.PasswordConfig = Constants.Security.UnknownPasswordConfigJson; + } IdentityResult result = await UpdateAsync(user); return result; } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberPasswordHasherTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberPasswordHasherTests.cs index 3dd2826baa..c581747c24 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberPasswordHasherTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberPasswordHasherTests.cs @@ -1,6 +1,10 @@ using System; using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; using NUnit.Framework; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Infrastructure.Security; using Umbraco.Cms.Infrastructure.Serialization; @@ -10,65 +14,35 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Security [TestFixture] public class MemberPasswordHasherTests { - private MemberPasswordHasher CreateSut() => new MemberPasswordHasher(new LegacyPasswordSecurity(), new JsonNetSerializer()); - [Test] - public void VerifyHashedPassword_GivenAnAspNetIdentity2PasswordHash_ThenExpectSuccessRehashNeeded() + [TestCase("Password123!", "AQAAAAEAACcQAAAAEGF/tTVoL6ef3bQPZFYfbgKFu1CDQIAMgyY1N4EDt9jqdG/hsOX93X1U6LNvlIQ3mw==", null, ExpectedResult = PasswordVerificationResult.Success, Description = "AspNetCoreIdentityPasswordHash: Correct password")] + [TestCase("wrongPassword", "AQAAAAEAACcQAAAAEGF/tTVoL6ef3bQPZFYfbgKFu1CDQIAMgyY1N4EDt9jqdG/hsOX93X1U6LNvlIQ3mw==", null, ExpectedResult = PasswordVerificationResult.Failed, Description = "AspNetCoreIdentityPasswordHash: Wrong password")] + [TestCase("Password123!", "yDiU2YyuYZU4jz6F0fpErQ==BxNRHkXBVyJs9gwWF6ktWdfDwYf5bwm+rvV7tOcNNx8=", null, ExpectedResult = PasswordVerificationResult.SuccessRehashNeeded, Description = "GivenALegacyPasswordHash: Correct password")] + [TestCase("wrongPassword", "yDiU2YyuYZU4jz6F0fpErQ==BxNRHkXBVyJs9gwWF6ktWdfDwYf5bwm+rvV7tOcNNx8=", null, ExpectedResult = PasswordVerificationResult.Failed, Description = "GivenALegacyPasswordHash: Wrong password")] + [TestCase("Password123!", "AJszAsQqxOYbASKfL3JVUu6cjU18ouizXDfX4j7wLlir8SWj2yQaTepE9e5bIohIsQ==", null, ExpectedResult = PasswordVerificationResult.SuccessRehashNeeded, Description = "GivenALegacyPasswordHash: Correct password")] + [TestCase("wrongPassword", "AJszAsQqxOYbASKfL3JVUu6cjU18ouizXDfX4j7wLlir8SWj2yQaTepE9e5bIohIsQ==", null, ExpectedResult = PasswordVerificationResult.Failed, Description = "GivenALegacyPasswordHash: Wrong password")] + [TestCase("1234567890", "1234567890", null, ExpectedResult = PasswordVerificationResult.Failed, Description = "ClearText: Correct password, but not supported")] + [TestCase("wrongPassword", "1234567890", null, ExpectedResult = PasswordVerificationResult.Failed, Description = "ClearText: Wrong password")] + [TestCase("1234567890", "XyFRG4/xJ5JGQJYqqIFK70BjHdM=", null, ExpectedResult = PasswordVerificationResult.SuccessRehashNeeded, Description = "Hashed: Correct password")] + [TestCase("wrongPassword", "XyFRG4/xJ5JGQJYqqIFK70BjHdM=", null, ExpectedResult = PasswordVerificationResult.Failed, Description = "Hashed: Wrong password")] + [TestCase("1234567890", "K2JPOhoqNoysfnnD67QsWDSliHrjoSTRTvv9yiaKf30=", "1D43BFA074DF6DCEF6E44A7F5B5F56CDDD60BE198FBBB0222C96A5BD696F3CAA", ExpectedResult = PasswordVerificationResult.SuccessRehashNeeded, Description = "Encrypted: Correct password and correct decryptionKey")] + [TestCase("wrongPassword", "K2JPOhoqNoysfnnD67QsWDSliHrjoSTRTvv9yiaKf30=", "1D43BFA074DF6DCEF6E44A7F5B5F56CDDD60BE198FBBB0222C96A5BD696F3CAA", ExpectedResult = PasswordVerificationResult.Failed, Description = "Encrypted: Wrong password but correct decryptionKey")] + [TestCase("1234567890", "qiuwRr4K7brpTcIzLFfR3iGG9zj4/z4ewHCVZmYUDKM=", "B491B602E0CE1D52450A8089FD2013B340743A7EFCC12B039BD11977A083ACA1", ExpectedResult = PasswordVerificationResult.Failed, Description = "Encrypted: Correct password but wrong decryptionKey")] + [TestCase("1234567890", "qiuwRr4K7brpTcIzLFfR3iGG9zj4/z4ewHCVZmYUDKM=", "InvalidDecryptionKey", ExpectedResult = PasswordVerificationResult.Failed, Description = "Encrypted: Invalid decryptionKey")] + public PasswordVerificationResult VerifyHashedPassword(string password, string encryptedPassword, string decryptionKey) { - const string password = "Password123!"; - const string hash = "AJszAsQqxOYbASKfL3JVUu6cjU18ouizXDfX4j7wLlir8SWj2yQaTepE9e5bIohIsQ=="; + var member = new MemberIdentityUser() { PasswordConfig = null }; - var sut = CreateSut(); - var result = sut.VerifyHashedPassword(new MemberIdentityUser(), hash, password); + var sut = new MemberPasswordHasher( + new LegacyPasswordSecurity(), + new JsonNetSerializer(), + Options.Create(new LegacyPasswordMigrationSettings() + { + MachineKeyDecryptionKey = decryptionKey + }), + NullLoggerFactory.Instance.CreateLogger()); - Assert.AreEqual(result, PasswordVerificationResult.SuccessRehashNeeded); - } - - [Test] - public void VerifyHashedPassword_GivenAnAspNetCoreIdentityPasswordHash_ThenExpectSuccess() - { - const string password = "Password123!"; - const string hash = "AQAAAAEAACcQAAAAEGF/tTVoL6ef3bQPZFYfbgKFu1CDQIAMgyY1N4EDt9jqdG/hsOX93X1U6LNvlIQ3mw=="; - - var sut = CreateSut(); - var result = sut.VerifyHashedPassword(new MemberIdentityUser(), hash, password); - - Assert.AreEqual(result, PasswordVerificationResult.Success); - } - - [Test] - public void VerifyHashedPassword_GivenALegacyPasswordHash_ThenExpectSuccessRehashNeeded() - { - const string password = "Password123!"; - const string hash = "yDiU2YyuYZU4jz6F0fpErQ==BxNRHkXBVyJs9gwWF6ktWdfDwYf5bwm+rvV7tOcNNx8="; - - var sut = CreateSut(); - var result = sut.VerifyHashedPassword(new MemberIdentityUser(), hash, password); - - Assert.AreEqual(result, PasswordVerificationResult.SuccessRehashNeeded); - } - - [Test] - public void VerifyHashedPassword_GivenAnUnknownBase64Hash_ThenExpectInvalidOperationException() - { - var hashBytes = new byte[] {3, 2, 1}; - var hash = Convert.ToBase64String(hashBytes); - - var sut = CreateSut(); - Assert.Throws(() => sut.VerifyHashedPassword(new MemberIdentityUser(), hash, "password")); - } - - [TestCase("AJszAsQqxOYbASKfL3JVUu6cjU18ouizXDfX4j7wLlir8SWj2yQaTepE9e5bIohIsQ==")] - [TestCase("AQAAAAEAACcQAAAAEGF/tTVoL6ef3bQPZFYfbgKFu1CDQIAMgyY1N4EDt9jqdG/hsOX93X1U6LNvlIQ3mw==")] - [TestCase("yDiU2YyuYZU4jz6F0fpErQ==BxNRHkXBVyJs9gwWF6ktWdfDwYf5bwm+rvV7tOcNNx8=")] - public void VerifyHashedPassword_GivenAnInvalidPassword_ThenExpectFailure(string hash) - { - const string invalidPassword = "nope"; - - var sut = CreateSut(); - var result = sut.VerifyHashedPassword(new MemberIdentityUser(), hash, invalidPassword); - - Assert.AreEqual(result, PasswordVerificationResult.Failed); + return sut.VerifyHashedPassword(member, encryptedPassword, password); } } }