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); }
/// <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); }
/// <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); }
/// <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); }