Example #1
0
        public async Task <TotpResult> ValidateAccountTOTPAsync(string globalId, string code, Func <string, string, Task <bool> > dupeChecker = null)
        {
            var result = new TotpResult
            {
                Code      = code,
                Timestamp = DateTime.UtcNow
            };

            if (!Int32.TryParse(code, out int number))
            {
                return(result);
            }

            var account = await _store.LoadByGuid(globalId);

            if (account != null)
            {
                var token = account.Tokens.Where(t => t.Type == AccountTokenType.TOTP).FirstOrDefault();
                if (token != null)
                {
                    var otp = new OtpNet.Totp(Encoding.UTF8.GetBytes(token.Hash)); //, mode: OtpHashMode.Sha256);
                    if (otp.VerifyTotp(code, out long matchedStep, new OtpNet.VerificationWindow(1, 1)))
                    {
                        result.Valid = (dupeChecker != null)
                            ? await dupeChecker.Invoke(globalId, matchedStep.ToString())
                            : true;
                    }
                }
            }
            return(result);
        }
Example #2
0
        private TotpResult ValidateParameters(ClaimsPrincipal principal, string securityToken, string phoneNumberOrEmail)
        {
            var hasSecurityToken = !string.IsNullOrEmpty(securityToken);
            var hasPrincipal     = principal != null;

            if (hasSecurityToken && hasPrincipal)
            {
                return(TotpResult.ErrorResult(Localizer["You can either provide a principal or your own security token."]));
            }
            var hasPhoneNumberOrEmail = !string.IsNullOrEmpty(phoneNumberOrEmail);

            if (hasSecurityToken && !hasPhoneNumberOrEmail)
            {
                return(TotpResult.ErrorResult(Localizer["If you provide your own security token, please make sure you also provide a phone number or email."]));
            }
            return(TotpResult.SuccessResult);
        }
Example #3
0
        /// <inheritdoc />
        public async Task <TotpResult> Verify(ClaimsPrincipal principal, string code, TotpProviderType?provider = null, string purpose = null, string securityToken = null, string phoneNumberOrEmail = null)
        {
            var totpResult = ValidateParameters(principal, securityToken, phoneNumberOrEmail);

            if (!totpResult.Success)
            {
                return(totpResult);
            }
            purpose ??= TotpConstants.TokenGenerationPurpose.StrongCustomerAuthentication;
            if (principal != null)
            {
                var user = await UserManager.GetUserAsync(principal);

                var providerName = provider.HasValue ? $"{provider}" : TokenOptions.DefaultPhoneProvider;
                var verified     = await UserManager.VerifyUserTokenAsync(user, providerName, purpose, code);

                if (verified)
                {
                    await UserManager.UpdateSecurityStampAsync(user);

                    return(TotpResult.SuccessResult);
                }
                else
                {
                    return(TotpResult.ErrorResult(Localizer["The verification code is invalid."]));
                }
            }
            if (!string.IsNullOrEmpty(securityToken))
            {
                if (!int.TryParse(code, out var codeInt))
                {
                    return(TotpResult.ErrorResult(Localizer["Totp must be an integer value."]));
                }
                var modifier     = GetModifier(purpose, phoneNumberOrEmail);
                var encodedToken = Encoding.Unicode.GetBytes(securityToken);
                var isValidTotp  = Rfc6238AuthenticationService.ValidateCode(encodedToken, codeInt, modifier);
                totpResult.Success = isValidTotp;
            }
            return(totpResult);
        }
Example #4
0
        /// <inheritdoc />
        public async Task <TotpResult> Send(ClaimsPrincipal principal, string message, TotpDeliveryChannel channel = TotpDeliveryChannel.Sms, string purpose = null, string securityToken = null, string phoneNumberOrEmail = null)
        {
            var totpResult = ValidateParameters(principal, securityToken, phoneNumberOrEmail);

            if (!totpResult.Success)
            {
                return(totpResult);
            }
            User user         = null;
            var  hasPrincipal = principal != null;

            if (hasPrincipal)
            {
                user = await UserManager.GetUserAsync(principal);

                if (user?.PhoneNumberConfirmed == false || string.IsNullOrEmpty(user?.PhoneNumber))
                {
                    return(TotpResult.ErrorResult(Localizer["Cannot send SMS. User's phone number is not verified."]));
                }
            }
            purpose ??= TotpConstants.TokenGenerationPurpose.StrongCustomerAuthentication;
            var token            = string.Empty;
            var hasSecurityToken = !string.IsNullOrEmpty(securityToken);

            if (hasSecurityToken)
            {
                var modifier     = GetModifier(purpose, phoneNumberOrEmail);
                var encodedToken = Encoding.Unicode.GetBytes(securityToken);
                token = Rfc6238AuthenticationService.GenerateCode(encodedToken, modifier).ToString("D6", CultureInfo.InvariantCulture);
            }
            if (hasPrincipal)
            {
                token = await UserManager.GenerateUserTokenAsync(user, TokenOptions.DefaultPhoneProvider, purpose);
            }
            var userName = user?.UserName ?? "Anonymous";
            var cacheKey = $"totp{(hasPrincipal ? $":{user.Id}" : string.Empty)}:{channel}:{token}:{purpose}";

            if (await CacheKeyExists(cacheKey))
            {
                Logger.LogInformation($"User: '******' - Last token has not expired yet. Throttling.");
                return(TotpResult.ErrorResult(Localizer["Last token has not expired yet. Please wait a few seconds and try again."]));
            }
            Logger.LogInformation($"User: '******' - Token generated successfully.");
            switch (channel)
            {
            case TotpDeliveryChannel.Sms:
                await SmsService.SendAsync(user?.PhoneNumber ?? phoneNumberOrEmail, Localizer["OTP"], Localizer[message, token]);

                break;

            case TotpDeliveryChannel.Email:
            case TotpDeliveryChannel.Viber:
            case TotpDeliveryChannel.Telephone:
            case TotpDeliveryChannel.EToken:
                throw new NotSupportedException($"EToken delivery channel {channel} is not implemented.");

            default:
                break;
            }
            await AddCacheKey(cacheKey);

            return(TotpResult.SuccessResult);
        }
Example #5
0
        /// <inheritdoc />
        public async Task <TotpResult> Send(ClaimsPrincipal principal, string message, TotpDeliveryChannel channel = TotpDeliveryChannel.Sms, string purpose = null, string securityToken = null, string phoneNumberOrEmail = null, string data = null, string classification = null, string subject = null)
        {
            var totpResult = ValidateParameters(principal, securityToken, phoneNumberOrEmail);

            if (!totpResult.Success)
            {
                return(totpResult);
            }
            User user         = null;
            var  hasPrincipal = principal != null;

            if (hasPrincipal)
            {
                user = await _userManager.GetUserAsync(principal);

                if (user?.PhoneNumberConfirmed == false || string.IsNullOrEmpty(user?.PhoneNumber))
                {
                    return(TotpResult.ErrorResult(_localizer["Cannot send SMS. User's phone number is not verified."]));
                }
            }
            purpose ??= TotpConstants.TokenGenerationPurpose.StrongCustomerAuthentication;
            var token            = string.Empty;
            var hasSecurityToken = !string.IsNullOrEmpty(securityToken);

            if (hasSecurityToken)
            {
                var modifier     = GetModifier(purpose, phoneNumberOrEmail);
                var encodedToken = Encoding.Unicode.GetBytes(securityToken);
                token = _rfc6238AuthenticationService.GenerateCode(encodedToken, modifier).ToString("D6", CultureInfo.InvariantCulture);
            }
            if (hasPrincipal)
            {
                token = await _userManager.GenerateUserTokenAsync(user, TokenOptions.DefaultPhoneProvider, purpose);
            }
            var userName = user?.UserName ?? "Anonymous";
            var cacheKey = $"totp{(hasPrincipal ? $":{user.Id}" : string.Empty)}:{channel}:{token}:{purpose}";

            if (await CacheKeyExists(cacheKey))
            {
                _logger.LogInformation("User: '******' - Last token has not expired yet. Throttling", userName);
                return(TotpResult.ErrorResult(_localizer["Last token has not expired yet. Please wait a few seconds and try again."]));
            }
            _logger.LogInformation("User: '******' - Token generated successfully", userName);
            switch (channel)
            {
            case TotpDeliveryChannel.Sms:
            case TotpDeliveryChannel.Viber:
                var smsService = _smsServiceFactory.Create(channel.ToString());
                await smsService.SendAsync(user?.PhoneNumber ?? phoneNumberOrEmail, _localizer[subject ?? "OTP"], _localizer[message, token]);

                break;

            case TotpDeliveryChannel.Email:
            case TotpDeliveryChannel.Telephone:
            case TotpDeliveryChannel.EToken:
                throw new NotSupportedException($"Delivery channel '{channel}' is not supported.");

            case TotpDeliveryChannel.PushNotification:
                if (_pushNotificationService == null)
                {
                    throw new ArgumentNullException(nameof(_pushNotificationService), $"Cannot send push notification since there is no implementation of {nameof(IPushNotificationService)}.");
                }
                await _pushNotificationService.SendAsync(builder =>
                                                         builder.To(user?.Id)
                                                         .WithToken(token)
                                                         .WithTitle(string.Format(message, token))
                                                         .WithData(data)
                                                         .WithClassification(classification));

                break;

            default:
                break;
            }
            await AddCacheKey(cacheKey);

            return(TotpResult.SuccessResult);
        }