1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-17 11:08:16 -05:00

PM-2128 Enforce one time use of TOTP (#3152)

* enforcing one time MFA token use

* Updated cache TTL

* renamed the cache

* removed IP limit, added comment, updated cache Key

* fixed build errors
This commit is contained in:
Ike 2023-09-09 14:35:08 -07:00 committed by GitHub
parent 4b482f0a34
commit 917c657439
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 32 additions and 5 deletions

View File

@ -26,6 +26,7 @@ using Bit.Core.Utilities;
using Bit.Identity.Utilities; using Bit.Identity.Utilities;
using IdentityServer4.Validation; using IdentityServer4.Validation;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Caching.Distributed;
namespace Bit.Identity.IdentityServer; namespace Bit.Identity.IdentityServer;
@ -45,6 +46,8 @@ public abstract class BaseRequestValidator<T> where T : class
private readonly GlobalSettings _globalSettings; private readonly GlobalSettings _globalSettings;
private readonly IUserRepository _userRepository; private readonly IUserRepository _userRepository;
private readonly IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> _tokenDataFactory; private readonly IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> _tokenDataFactory;
private readonly IDistributedCache _distributedCache;
private readonly DistributedCacheEntryOptions _cacheEntryOptions;
protected ICurrentContext CurrentContext { get; } protected ICurrentContext CurrentContext { get; }
protected IPolicyService PolicyService { get; } protected IPolicyService PolicyService { get; }
@ -69,7 +72,8 @@ public abstract class BaseRequestValidator<T> where T : class
IPolicyService policyService, IPolicyService policyService,
IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> tokenDataFactory, IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> tokenDataFactory,
IFeatureService featureService, IFeatureService featureService,
ISsoConfigRepository ssoConfigRepository) ISsoConfigRepository ssoConfigRepository,
IDistributedCache distributedCache)
{ {
_userManager = userManager; _userManager = userManager;
_deviceRepository = deviceRepository; _deviceRepository = deviceRepository;
@ -89,6 +93,14 @@ public abstract class BaseRequestValidator<T> where T : class
_tokenDataFactory = tokenDataFactory; _tokenDataFactory = tokenDataFactory;
FeatureService = featureService; FeatureService = featureService;
SsoConfigRepository = ssoConfigRepository; SsoConfigRepository = ssoConfigRepository;
_distributedCache = distributedCache;
_cacheEntryOptions = new DistributedCacheEntryOptions
{
// This sets the time an item is cached to 15 minutes. This value is hard coded
// to 15 because to it covers all time-out windows for both Authenticators and
// Email TOTP.
AbsoluteExpirationRelativeToNow = new TimeSpan(0, 15, 0)
};
} }
protected async Task ValidateAsync(T context, ValidatedTokenRequest request, protected async Task ValidateAsync(T context, ValidatedTokenRequest request,
@ -135,6 +147,15 @@ public abstract class BaseRequestValidator<T> where T : class
var verified = await VerifyTwoFactor(user, twoFactorOrganization, var verified = await VerifyTwoFactor(user, twoFactorOrganization,
twoFactorProviderType, twoFactorToken); twoFactorProviderType, twoFactorToken);
var cacheKey = "TOTP_" + user.Email;
var isOtpCached = Core.Utilities.DistributedCacheExtensions.TryGetValue(_distributedCache, cacheKey, out string _);
if (isOtpCached)
{
await BuildErrorResultAsync("Two-step token is invalid. Try again.", true, context, user);
return;
}
if ((!verified || isBot) && twoFactorProviderType != TwoFactorProviderType.Remember) if ((!verified || isBot) && twoFactorProviderType != TwoFactorProviderType.Remember)
{ {
await UpdateFailedAuthDetailsAsync(user, true, !validatorContext.KnownDevice); await UpdateFailedAuthDetailsAsync(user, true, !validatorContext.KnownDevice);
@ -148,6 +169,7 @@ public abstract class BaseRequestValidator<T> where T : class
await BuildTwoFactorResultAsync(user, twoFactorOrganization, context); await BuildTwoFactorResultAsync(user, twoFactorOrganization, context);
return; return;
} }
await Core.Utilities.DistributedCacheExtensions.SetAsync(_distributedCache, cacheKey, twoFactorToken, _cacheEntryOptions);
} }
else else
{ {

View File

@ -14,6 +14,7 @@ using IdentityModel;
using IdentityServer4.Extensions; using IdentityServer4.Extensions;
using IdentityServer4.Validation; using IdentityServer4.Validation;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Caching.Distributed;
#nullable enable #nullable enable
@ -42,11 +43,13 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
IUserRepository userRepository, IUserRepository userRepository,
IPolicyService policyService, IPolicyService policyService,
IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> tokenDataFactory, IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> tokenDataFactory,
IFeatureService featureService) IFeatureService featureService,
IDistributedCache distributedCache)
: base(userManager, deviceRepository, deviceService, userService, eventService, : base(userManager, deviceRepository, deviceService, userService, eventService,
organizationDuoWebTokenProvider, organizationRepository, organizationUserRepository, organizationDuoWebTokenProvider, organizationRepository, organizationUserRepository,
applicationCacheService, mailService, logger, currentContext, globalSettings, applicationCacheService, mailService, logger, currentContext, globalSettings,
userRepository, policyService, tokenDataFactory, featureService, ssoConfigRepository) userRepository, policyService, tokenDataFactory, featureService, ssoConfigRepository,
distributedCache)
{ {
_userManager = userManager; _userManager = userManager;
} }

View File

@ -13,6 +13,7 @@ using Bit.Core.Utilities;
using IdentityServer4.Models; using IdentityServer4.Models;
using IdentityServer4.Validation; using IdentityServer4.Validation;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Caching.Distributed;
namespace Bit.Identity.IdentityServer; namespace Bit.Identity.IdentityServer;
@ -44,11 +45,12 @@ public class ResourceOwnerPasswordValidator : BaseRequestValidator<ResourceOwner
IPolicyService policyService, IPolicyService policyService,
IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> tokenDataFactory, IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> tokenDataFactory,
IFeatureService featureService, IFeatureService featureService,
ISsoConfigRepository ssoConfigRepository) ISsoConfigRepository ssoConfigRepository,
IDistributedCache distributedCache)
: base(userManager, deviceRepository, deviceService, userService, eventService, : base(userManager, deviceRepository, deviceService, userService, eventService,
organizationDuoWebTokenProvider, organizationRepository, organizationUserRepository, organizationDuoWebTokenProvider, organizationRepository, organizationUserRepository,
applicationCacheService, mailService, logger, currentContext, globalSettings, userRepository, policyService, applicationCacheService, mailService, logger, currentContext, globalSettings, userRepository, policyService,
tokenDataFactory, featureService, ssoConfigRepository) tokenDataFactory, featureService, ssoConfigRepository, distributedCache)
{ {
_userManager = userManager; _userManager = userManager;
_userService = userService; _userService = userService;