From 493cd19825c3cfd36effa0ee5c40e5b7f3caf7ed Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Wed, 8 Mar 2023 16:55:13 -0800 Subject: [PATCH] [AC-1070] Add support 2FA login flow Keep track of the reset password reason after a password login requires 2FA. During 2FA submission, check if there is a saved reason, and if so, force the user to update their password. --- src/Core/Services/AuthService.cs | 40 ++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/src/Core/Services/AuthService.cs b/src/Core/Services/AuthService.cs index 9f74e56ac..82038b4f8 100644 --- a/src/Core/Services/AuthService.cs +++ b/src/Core/Services/AuthService.cs @@ -6,7 +6,6 @@ using System.Threading.Tasks; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Exceptions; -using Bit.Core.Models.Data; using Bit.Core.Models.Domain; using Bit.Core.Models.Request; using Bit.Core.Models.Response; @@ -32,7 +31,10 @@ namespace Bit.Core.Services private readonly LazyResolve _watchDeviceService = new LazyResolve(); private SymmetricCryptoKey _key; - private MasterPasswordPolicyOptions _masterPasswordPolicy { get; set; } + + private string _authedUserId; + private MasterPasswordPolicyOptions _masterPasswordPolicy; + private ForcePasswordResetReason? _2faForcePasswordResetReason; public AuthService( ICryptoService cryptoService, @@ -140,6 +142,7 @@ namespace Bit.Core.Services public async Task LogInAsync(string email, string masterPassword, string captchaToken) { SelectedTwoFactorProviderType = null; + _2faForcePasswordResetReason = null; var key = await MakePreloginKeyAsync(masterPassword, email); var hashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key); var localHashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key, HashPurpose.LocalAuthorization); @@ -147,9 +150,18 @@ namespace Bit.Core.Services if (await RequirePasswordChange(email, masterPassword)) { - result.ForcePasswordReset = true; - await _stateService.SetForcePasswordResetReasonAsync( - ForcePasswordResetReason.WeakMasterPasswordOnLogin); + if (!string.IsNullOrEmpty(_authedUserId)) + { + // Authentication was successful, save the WeakMasterPasswordOnLogin flag for the user + result.ForcePasswordReset = true; + await _stateService.SetForcePasswordResetReasonAsync( + ForcePasswordResetReason.WeakMasterPasswordOnLogin, _authedUserId); + } + else + { + // Authentication not fully successful (likely 2FA), store flag for LogInTwoFactorAsync() + _2faForcePasswordResetReason = ForcePasswordResetReason.WeakMasterPasswordOnLogin; + } } return result; @@ -191,15 +203,26 @@ namespace Bit.Core.Services return await LogInHelperAsync(null, null, null, code, codeVerifier, redirectUrl, null, orgId: orgId); } - public Task LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken, + public async Task LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken, string captchaToken, bool? remember = null) { if (captchaToken != null) { CaptchaToken = captchaToken; } - return LogInHelperAsync(Email, MasterPasswordHash, LocalMasterPasswordHash, Code, CodeVerifier, SsoRedirectUrl, _key, + var result = await LogInHelperAsync(Email, MasterPasswordHash, LocalMasterPasswordHash, Code, CodeVerifier, SsoRedirectUrl, _key, twoFactorProvider, twoFactorToken, remember, CaptchaToken, authRequestId: AuthRequestId); + + // If we successfully authenticated and we have a saved _2faForcePasswordResetReason reason from LogInAsync() + if (!string.IsNullOrEmpty(_authedUserId) && _2faForcePasswordResetReason.HasValue) + { + // Save the forcePasswordReset reason with the state service to force a password reset for the user + result.ForcePasswordReset = true; + await _stateService.SetForcePasswordResetReasonAsync( + ForcePasswordResetReason.WeakMasterPasswordOnLogin, _authedUserId); + } + + return result; } public async Task LogInCompleteAsync(string email, string masterPassword, @@ -512,6 +535,7 @@ namespace Bit.Core.Services } + _authedUserId = _tokenService.GetUserId(); await _stateService.SetBiometricLockedAsync(false); _messagingService.Send("loggedIn"); return result; @@ -529,6 +553,8 @@ namespace Bit.Core.Services SsoRedirectUrl = null; TwoFactorProvidersData = null; SelectedTwoFactorProviderType = null; + _masterPasswordPolicy = null; + _authedUserId = null; } public async Task> GetPasswordlessLoginRequestsAsync()