1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-05 18:12:48 -05:00

[PM-9925] Tokenable for User Verification on Two Factor Authenticator settings (#4558)

* initial changes

* Fixing some bits

* fixing issue when feature flag is `false`; also names;

* consume OTP on read if FF true

* comment typo

* fix formatting

* check access code first to not consume token

* add docs

* revert checking access code first

* update error messages

* remove line number from comment

---------

Co-authored-by: Jake Fink <jfink@bitwarden.com>
This commit is contained in:
Ike
2024-07-25 07:51:23 -07:00
committed by GitHub
parent f211e969c7
commit aba2f023cd
6 changed files with 130 additions and 14 deletions

View File

@ -0,0 +1,62 @@
using Bit.Core.Entities;
using Bit.Core.Tokens;
using Newtonsoft.Json;
namespace Bit.Core.Auth.Models.Business.Tokenables;
/// <summary>
/// A tokenable object that gives a user the ability to update their authenticator two factor settings.
/// </summary>
/// <remarks>
/// We protect two factor updates behind user verification (re-authentication) to protect against attacks of opportunity
/// (e.g. a user leaves their web vault unlocked). Most two factor options only require user verification (UV) when
/// enabling or disabling the option, retrieving the current status usually isn't a sensitive operation. However,
/// the status of authenticator two factor is sensitive because it reveals the user's secret key, which means both
/// operations must be protected by UV.
///
/// TOTP as a UV option is only allowed to be used once, so we return this tokenable when retrieving the current status
/// (and secret key) of authenticator two factor to give the user a means of passing UV when updating (enabling/disabling).
/// </remarks>
public class TwoFactorAuthenticatorUserVerificationTokenable : ExpiringTokenable
{
private static readonly TimeSpan _tokenLifetime = TimeSpan.FromMinutes(30);
public const string ClearTextPrefix = "TwoFactorAuthenticatorUserVerification";
public const string DataProtectorPurpose = "TwoFactorAuthenticatorUserVerificationTokenDataProtector";
public const string TokenIdentifier = "TwoFactorAuthenticatorUserVerificationToken";
public string Identifier { get; set; } = TokenIdentifier;
public Guid UserId { get; set; }
public string Key { get; set; }
public override bool Valid => Identifier == TokenIdentifier &&
UserId != default;
[JsonConstructor]
public TwoFactorAuthenticatorUserVerificationTokenable()
{
ExpirationDate = DateTime.UtcNow.Add(_tokenLifetime);
}
public TwoFactorAuthenticatorUserVerificationTokenable(User user, string key) : this()
{
UserId = user?.Id ?? default;
Key = key;
}
public bool TokenIsValid(User user, string key)
{
if (UserId == default
|| user == null
|| string.IsNullOrWhiteSpace(key))
{
return false;
}
return UserId == user.Id && Key == key;
}
protected override bool TokenIsValid() =>
Identifier == TokenIdentifier
&& UserId != default
&& !string.IsNullOrWhiteSpace(Key);
}

View File

@ -131,6 +131,7 @@ public static class FeatureFlagKeys
public const string AC2828_ProviderPortalMembersPage = "AC-2828_provider-portal-members-page";
public const string ProviderClientVaultPrivacyBanner = "ac-2833-provider-client-vault-privacy-banner";
public const string DeviceTrustLogging = "pm-8285-device-trust-logging";
public const string AuthenticatorTwoFactorToken = "authenticator-2fa-token";
public static List<string> GetAllKeys()
{