LoginResult DoVerify(ISqlCallContext ctx, SqlCommand hashReader, string password, object objectKey, bool actualLogin)
        {
            if (string.IsNullOrEmpty(password))
            {
                return(new LoginResult(KnownLoginFailureCode.InvalidCredentials));
            }
            // 1 - Get the PwdHash and UserId.
            //     hash is null if the user is not a UserPassword: we'll try to migrate it.
            var read = ctx[Database].ExecuteSingleRow(hashReader,
                                                      row => row == null
                                    ? (0, null)
                                    : (UserId: row.GetInt32(1),
                                       PwdHash: row.IsDBNull(0)
                                                    ? null
                                                    : row.GetBytes(0)));

            if (read.UserId == 0)
            {
                return(new LoginResult(KnownLoginFailureCode.InvalidUserKey));
            }

            // If hash is null here, it means that the user is not registered.
            PasswordVerificationResult result   = PasswordVerificationResult.Failed;
            PasswordHasher             p        = null;
            IUserPasswordMigrator      migrator = null;

            // 2 - Handle external password migration or check the hash.
            if (read.PwdHash == null)
            {
                migrator = _package.PasswordMigrator;
                if (migrator != null && migrator.VerifyPassword(ctx, read.UserId, password))
                {
                    result = PasswordVerificationResult.SuccessRehashNeeded;
                    p      = new PasswordHasher(HashIterationCount);
                }
            }
            else
            {
                p      = new PasswordHasher(HashIterationCount);
                result = p.VerifyHashedPassword(read.PwdHash, password);
            }
            // 3 - Handle result.
            var mode = actualLogin ? UCLMode.WithActualLogin : UCLMode.WithCheckLogin;

            if (result == PasswordVerificationResult.SuccessRehashNeeded)
            {
                // 3.1 - If migration occurred, create the user with its password.
                //       Else rehash the password and update the database.
                mode |= UCLMode.CreateOrUpdate;
                UCLResult r = PasswordUserUCL(ctx, 1, read.UserId, p.HashPassword(password), mode, null);
                if (r.OperationResult != UCResult.None && migrator != null)
                {
                    migrator.MigrationDone(ctx, read.UserId);
                }
                return(r.LoginResult);
            }
            // 4 - Challenges the database login checks.
            int?failureCode = null;

            if (result == PasswordVerificationResult.Failed)
            {
                failureCode = (int)KnownLoginFailureCode.InvalidCredentials;
            }
            return(PasswordUserUCL(ctx, 1, read.UserId, null, mode, failureCode).LoginResult);
        }
        LoginResult DoVerify(ISqlCallContext ctx, SqlCommand hashReader, string password, object objectKey, bool actualLogin)
        {
            if (string.IsNullOrEmpty(password))
            {
                return(new LoginResult(KnownLoginFailureCode.InvalidCredentials));
            }
            // 1 - Get the PwdHash and UserId.
            //     hash is null if the user is not a UserPassword: we'll try to migrate it.
            //     hash can be empty iif a previous attempt to migrate it has failed.
            var read = ctx[Database].ExecuteSingleRow(hashReader,
                                                      row => row == null
                                    ? (0, null, -1)
                                    : (UserId: row.GetInt32(1),
                                       PwdHash:
                                       row.IsDBNull(0) ? null : row.GetBytes(0),
                                       FailedAttemptCount:
                                       row.IsDBNull(2) ? -1 : row.GetByte(2)));

            if (read.UserId == 0)
            {
                return(new LoginResult(KnownLoginFailureCode.InvalidUserKey));
            }

            // If hash is null here, it means that the user is not registered.
            PasswordVerificationResult result   = PasswordVerificationResult.Failed;
            PasswordHasher             p        = null;
            IUserPasswordMigrator      migrator = null;

            // 2 - Handle external password migration or check the hash.
            if (read.PwdHash == null || read.PwdHash.Length == 0)
            {
                migrator = UserPasswordPackage.PasswordMigrator;
                if (migrator != null && migrator.VerifyPassword(ctx, read.UserId, password))
                {
                    result = PasswordVerificationResult.SuccessRehashNeeded;
                    p      = new PasswordHasher(HashIterationCount);
                }
            }
            else
            {
                p      = new PasswordHasher(HashIterationCount);
                result = p.VerifyHashedPassword(read.PwdHash, password);
            }
            // 3 - Handle result.
            var mode = actualLogin ? UCLMode.WithActualLogin : UCLMode.WithCheckLogin;

            if (result == PasswordVerificationResult.SuccessRehashNeeded)
            {
                // 3.1 - If migration occurred, create the user with its password.
                //       Otherwise, rehash the password and update the database.
                mode |= UCLMode.CreateOrUpdate;
                UCLResult r = PasswordUserUCL(ctx, 1, read.UserId, p.HashPassword(password), mode, null);
                if (r.OperationResult != UCResult.None && migrator != null)
                {
                    migrator.MigrationDone(ctx, read.UserId);
                }
                return(r.LoginResult);
            }
            if (result == PasswordVerificationResult.Failed && migrator != null)
            {
                // 3.2 - Migration failed, create the user with an empty hash.
                //       so that FailedAttemptCount (or other) can be handled.
                mode |= UCLMode.CreateOnly;
                mode &= ~UCLMode.UpdateOnly;
                // KnownLoginFailureCode is UnregisteredUser: the fact that we have a password migrator
                // (that failed to migrate) results in the user actually not registered in the UserPassword provider.
                UCLResult r = PasswordUserUCL(ctx, 1, read.UserId, Array.Empty <byte>(), mode, (int)KnownLoginFailureCode.UnregisteredUser);
                return(r.LoginResult);
            }
            // 4 - Challenges the database login checks.
            int?failureCode = null;

            if (result == PasswordVerificationResult.Failed)
            {
                failureCode = (int)KnownLoginFailureCode.InvalidCredentials;
            }
            return(PasswordUserUCL(ctx, 1, read.UserId, null, mode, failureCode).LoginResult);
        }