public async Task <CheckOneTimeCodeResponse> CheckOneTimeCodeAsync(string sentTo, string shortCode)
        {
            var otc = await _oneTimeCodeStore.GetOneTimeCodeAsync(sentTo);

            if (otc == null)
            {
                return(new CheckOneTimeCodeResponse(CheckOneTimeCodeResult.NotFound));
            }
            if (otc.ExpiresUTC < DateTime.UtcNow)
            {
                return(new CheckOneTimeCodeResponse(CheckOneTimeCodeResult.Expired));
            }

            if (!string.IsNullOrEmpty(shortCode) && shortCode.Length <= 8)
            {
                if (otc.FailedAttemptCount > 1)
                {
                    // maximum of 2 attempts during code validity period to prevent guessing attacks
                    // long code remains valid, preventing account lockout attacks (and giving a fumbling but valid user another way in)
                    return(new CheckOneTimeCodeResponse(CheckOneTimeCodeResult.ShortCodeLocked));
                }
                var checkResult = _passwordHashService.CheckPasswordHash(otc.ShortCodeHash, shortCode);
                if (checkResult == CheckPaswordHashResult.Matches || checkResult == CheckPaswordHashResult.MatchesNeedsRehash)
                {
                    await _oneTimeCodeStore.ExpireOneTimeCodeAsync(sentTo);

                    return(new CheckOneTimeCodeResponse(CheckOneTimeCodeResult.Verified, sentTo, otc.RedirectUrl));
                }
            }

            await _oneTimeCodeStore.UpdateOneTimeCodeFailureAsync(sentTo, otc.FailedAttemptCount + 1);

            return(new CheckOneTimeCodeResponse(CheckOneTimeCodeResult.CodeIncorrect));
        }
        public async Task <CheckPasswordResult> CheckPasswordAsync(string uniqueIdentifier, string password)
        {
            var hashInfo = await _passwordHashStore.GetPasswordHashAsync(uniqueIdentifier);

            if (hashInfo == null)
            {
                return(CheckPasswordResult.NotFound);
            }
            if (hashInfo.TempLockUntilUTC > DateTime.UtcNow)
            {
                return(CheckPasswordResult.TemporarilyLocked);
            }
            var checkHashResult = _passwordHashService.CheckPasswordHash(hashInfo.Hash, password);

            switch (checkHashResult)
            {
            case CheckPaswordHashResult.DoesNotMatch:
                if (hashInfo.FailedAttemptCount > 3)               // todo, get from settings
                {
                    var lockUntil = DateTime.UtcNow.AddMinutes(5); // todo: get from settings
                    //todo: consider if failure count should be reset or not. (should first subsequent failure after lockout initiate another lockout period?)
                    await _passwordHashStore.TempLockPasswordHashAsync(uniqueIdentifier, lockUntil);

                    return(CheckPasswordResult.TemporarilyLocked);
                }
                else
                {
                    await _passwordHashStore.UpdatePasswordHashFailureAsync(uniqueIdentifier, hashInfo.FailedAttemptCount + 1);

                    return(CheckPasswordResult.PasswordIncorrect);
                }

            case CheckPaswordHashResult.MatchesNeedsRehash:
                var newHash = _passwordHashService.HashPassword(password);
                await _passwordHashStore.UpdatePasswordHashAsync(uniqueIdentifier, newHash);

                return(CheckPasswordResult.Success);

            case CheckPaswordHashResult.Matches:
                return(CheckPasswordResult.Success);

            default:
                // this should never happen
                return(CheckPasswordResult.ServiceFailure);
            }
        }
        public async Task <CheckPasswordStatus> CheckPasswordAsync(string uniqueIdentifier, string password, PasswordLockMode lockMode)
        {
            _logger.LogDebug("Checking password for {0}", uniqueIdentifier);
            var response = await _passwordHashStore.GetPasswordHashAsync(uniqueIdentifier);

            if (response.HasError)
            {
                var status = new CheckPasswordStatus();
                status.Add(response.Status);
                status.StatusCode = CheckPasswordStatusCode.NotFound;
                return(status);
            }
            var hashInfo = response.Result;


            if (AccountIsLocked(lockMode, hashInfo.FailedAttemptCount, hashInfo.TempLockUntilUTC))
            {
                return(CheckPasswordStatus.Error(_localizer["Password is temporarily locked."], CheckPasswordStatusCode.TemporarilyLocked));
            }

            var checkHashResult = _passwordHashService.CheckPasswordHash(hashInfo.Hash, password);

            switch (checkHashResult)
            {
            case CheckPasswordHashResult.DoesNotMatch:
                return(await ProcessDoesNotMatchAndReturnAsync(uniqueIdentifier, lockMode, hashInfo.FailedAttemptCount));

            case CheckPasswordHashResult.MatchesNeedsRehash:
                return(await ProcessMatchesNeedsRehashAndReturnAsync(uniqueIdentifier, password));

            case CheckPasswordHashResult.Matches:
                return(await ProcessMatchesAndReturnAsync(uniqueIdentifier));

            default:
                // this should never happen
                return(CheckPasswordStatus.Error(_localizer["An unexpected error occurred."], CheckPasswordStatusCode.ServiceFailure));
            }
        }