public async Task <Response <Models.TrustedBrowser, Status> > AddTrustedBrowserAsync(string subjectId, string browserId, string description)
        {
            _logger.LogDebug("Adding trusted browser");

            if (string.IsNullOrEmpty(subjectId) || string.IsNullOrEmpty(browserId))
            {
                _logger.LogDebug("Subject id or browser id was missing.");
                return(Response.Error <Models.TrustedBrowser>(_localizer["Subject id or browser id was missing."]));
            }

            var dbBrowser = new TrustedBrowser()
            {
                SubjectId     = subjectId,
                BrowserIdHash = FastHashService.GetHash(browserId, subjectId),
                Description   = description?.Substring(0, description.Length),
                AddedOn       = DateTime.UtcNow,
            };

            await _context.AddAsync(dbBrowser);

            var count = await _context.SaveChangesAsync();

            if (count == 0)
            {
                return(Response.Error <Models.TrustedBrowser>(_localizer["Failed to save trusted browser."]));
            }

            return(Response.Success(dbBrowser.ToModel(), _localizer["Trusted browser saved."]));
        }
        private async Task <Response <CheckOneTimeCodeResult, CheckOneTimeCodeStatus> > ExpireTokenAndValidateNonceAsync(OneTimeCode otc, string clientNonce)
        {
            _logger.LogTrace("Validating nonce");

            _logger.LogDebug("Expiring the token so it cannot be used again and so a new token can be generated");
            await _oneTimeCodeStore.ExpireOneTimeCodeAsync(otc.SentTo);

            if (FastHashService.ValidateHash(otc.ClientNonceHash, clientNonce, otc.SentTo))
            {
                _logger.LogDebug("Client nonce was valid");
                return(new Response <CheckOneTimeCodeResult, CheckOneTimeCodeStatus>(
                           new CheckOneTimeCodeResult(otc),
                           CheckOneTimeCodeStatus.Success(_localizer["The one time code was verified."], CheckOneTimeCodeStatusCode.VerifiedWithNonce)));
            }

            _logger.LogDebug("Client nonce was missing or invalid");
            return(new Response <CheckOneTimeCodeResult, CheckOneTimeCodeStatus>(
                       new CheckOneTimeCodeResult(otc),
                       CheckOneTimeCodeStatus.Success(_localizer["The one time code was verified."], CheckOneTimeCodeStatusCode.VerifiedWithoutNonce)));
        }
        public async Task <Response <Models.TrustedBrowser, Status> > GetTrustedBrowserAsync(string subjectId, string browserId)
        {
            _logger.LogDebug("Looking for trusted browser.");

            if (string.IsNullOrEmpty(subjectId) || string.IsNullOrEmpty(browserId))
            {
                _logger.LogDebug("Subject id or browser id was missing.");
                return(Response.Error <Models.TrustedBrowser>(_localizer["Subject id or browser id was missing."]));
            }
            var browserIdHash = FastHashService.GetHash(browserId, subjectId);
            var model         = (await _context.TrustedBrowsers.SingleOrDefaultAsync(x => x.BrowserIdHash == browserIdHash))?.ToModel();

            if (model == null)
            {
                _logger.LogDebug("Trusted browser was not found.");
                return(Response.Error <Models.TrustedBrowser>(_localizer["Trusted browser was not found."]));
            }
            _logger.LogDebug("Trusted browser was found.");
            return(Response.Success(model, _localizer["Trusted browser found."]));
        }
        public async Task <Response <GetOneTimeCodeResult, GetOneTimeCodeStatus> > GetOneTimeCodeAsync(string sendTo, TimeSpan validity, string redirectUrl = null)
        {
            var response = await _oneTimeCodeStore.GetOneTimeCodeAsync(sendTo);

            var otc = response.Result;

            if (otc != null &&
                otc.ExpiresUTC > DateTime.UtcNow.AddMinutes(PasswordlessLoginConstants.OneTimeCode.IssueNewCodeIfValidityLessThanXMinutes) &&
                otc.ExpiresUTC < DateTime.UtcNow.AddMinutes(_options.OneTimeCodeValidityMinutes))
            {
                _logger.LogDebug("A once time code exists that has enough time left to use");
                // existing code has at least X minutes of validity remaining, so resend it
                // if more than default validity (e.g. first code sent to new user), user could accidentally
                // lock the code and not be able to confirm or access the account (terrible UX)
                if (otc.SentCount >= PasswordlessLoginConstants.OneTimeCode.MaxResendCount)
                {
                    _logger.LogDebug("The existing one time code has been sent too many times.");
                    return(new Response <GetOneTimeCodeResult, GetOneTimeCodeStatus>(
                               GetOneTimeCodeStatus.Error(_localizer["Too many requests."], GetOneTimeCodeStatusCode.TooManyRequests)));
                }

                _logger.LogDebug("Updating the record of how many times the code has been sent");
                await _oneTimeCodeStore.UpdateOneTimeCodeSentCountAsync(sendTo, otc.SentCount + 1, redirectUrl);

                _logger.LogDebug("Returning the still valid code without a client nonce, which can only be delivered once.");
                return(new Response <GetOneTimeCodeResult, GetOneTimeCodeStatus>(
                           new GetOneTimeCodeResult
                {
                    ClientNonce = null,     // only given out when code is first generated
                    ShortCode = otc.ShortCode,
                    LongCode = otc.LongCode
                },
                           GetOneTimeCodeStatus.Success(_localizer["One time code found."])));
            }

            _logger.LogDebug("Generating a new one time code, link, and client nonce.");
            var rngProvider = new RNGCryptoServiceProvider();
            var byteArray   = new byte[8];

            rngProvider.GetBytes(byteArray);
            var clientNonceUInt = BitConverter.ToUInt64(byteArray, 0);
            var clientNonce     = clientNonceUInt.ToString();
            var clientNonceHash = FastHashService.GetHash(clientNonce, sendTo);

            rngProvider.GetBytes(byteArray);
            var longCodeUInt = BitConverter.ToUInt64(byteArray, 0);
            var longCode     = longCodeUInt.ToString();

            var shortCode = (longCodeUInt % 1000000).ToString("000000");

            otc = new OneTimeCode()
            {
                SentTo             = sendTo,
                ClientNonceHash    = clientNonceHash,
                ShortCode          = shortCode,
                ExpiresUTC         = DateTime.UtcNow.Add(validity),
                LongCode           = longCode,
                RedirectUrl        = redirectUrl,
                FailedAttemptCount = 0,
                SentCount          = 1
            };
            await _oneTimeCodeStore.RemoveOneTimeCodeAsync(sendTo);

            var codeSavedStatus = await _oneTimeCodeStore.AddOneTimeCodeAsync(otc);

            if (codeSavedStatus.HasError)
            {
                _logger.LogError("Failed to store the code.");
                return(new Response <GetOneTimeCodeResult, GetOneTimeCodeStatus>(
                           GetOneTimeCodeStatus.Error(_localizer["Failed to save the one time code."], GetOneTimeCodeStatusCode.ServiceFailure)));
            }

            return(new Response <GetOneTimeCodeResult, GetOneTimeCodeStatus>(
                       new GetOneTimeCodeResult
            {
                ClientNonce = clientNonce,
                ShortCode = shortCode,
                LongCode = longCode
            },
                       GetOneTimeCodeStatus.Success(_localizer["One time code was generated."])));
        }