mirror of
https://github.com/bitwarden/mobile
synced 2025-12-20 10:13:42 +00:00
[AC-1070] Enforce master password policy on login/unlock (#2410)
* [AC-1070] Add EnforceOnLogin property to MasterPasswordPolicyOptions
* [AC-1070] Add MasterPasswordPolicy property to Identity responses
* [AC-1070] Add policy service dependency to auth service
* [AC-1070] Introduce logic to evaluate master password after successful login
* [AC-1070] Add optional ForcePasswordResetReason to profile / state service
* [AC-1070] Save ForcePasswordResetReason to state when a weak master password is found during login
- Additionally, save the AdminForcePasswordReset reason if the identity result indicates an admin password reset is in effect.
* [AC-1070] Check for a saved ForcePasswordReset reason on TabsPage load force show the update password page
* [AC-1070] Make InitAsync virtual
Allow the UpdateTempPasswordPage to override the InitAsync method to check for a reset password reason in the state service
* [AC-1070] Modify UpdateTempPassword page appearance
- Load the force password reset reason from the state service
- Make warning text dynamic based on force password reason
- Conditionally show the Current master password field if updating a weak master password
* [AC-1070] Add update password method to Api service
* [AC-1070] Introduce logic to update both temp and regular passwords
- Check the Reason to use the appropriate request/endpoint when submitting.
- Verify the users current password locally using the user verification service.
* [AC-1070] Introduce VerifyMasterPasswordResponse
* [AC-1070] Add logic to evaluate master password on unlock
* [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.
* [AC-1070] Formatting
* [AC-1070] Remove string key from service resolution
* [AC-1070] Change master password options to method variable to avoid class field
Add null check for password strength result and log an error as this is an unexpected flow
* [AC-1070] Remove usage of i18nService
* [AC-1070] Use AsyncCommand for SubmitCommand
* [AC-1070] Remove type from ShowToast call
* [AC-1070] Simplify UpdatePassword methods to accept string for the new encryption key
* [AC-1070] Use full text for key for the CurrentMasterPassword resource
* [AC-1070] Convert Reason to a private class field
* [AC-1070] Formatting changes
* [AC-1070] Simplify if statements in master password options policy service method
* [AC-1070] Use the saved force password reset reason after 2FA login
* [AC-1070] Use constant for ForceUpdatePassword message command
* [AC-1070] Move shared RequirePasswordChangeOnLogin method into PolicyService
* Revert "[AC-1070] Move shared RequirePasswordChangeOnLogin method into PolicyService"
This reverts commit e4feac130f.
* [AC-1070] Add check for null password strength response
* [AC-1070] Fix broken show password icon
* [AC-1070] Add show password icon for current master password
This commit is contained in:
@@ -26,11 +26,16 @@ namespace Bit.Core.Services
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly IKeyConnectorService _keyConnectorService;
|
||||
private readonly IPasswordGenerationService _passwordGenerationService;
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly bool _setCryptoKeys;
|
||||
|
||||
private readonly LazyResolve<IWatchDeviceService> _watchDeviceService = new LazyResolve<IWatchDeviceService>();
|
||||
private SymmetricCryptoKey _key;
|
||||
|
||||
private string _authedUserId;
|
||||
private MasterPasswordPolicyOptions _masterPasswordPolicy;
|
||||
private ForcePasswordResetReason? _2faForcePasswordResetReason;
|
||||
|
||||
public AuthService(
|
||||
ICryptoService cryptoService,
|
||||
ICryptoFunctionService cryptoFunctionService,
|
||||
@@ -44,6 +49,7 @@ namespace Bit.Core.Services
|
||||
IVaultTimeoutService vaultTimeoutService,
|
||||
IKeyConnectorService keyConnectorService,
|
||||
IPasswordGenerationService passwordGenerationService,
|
||||
IPolicyService policyService,
|
||||
bool setCryptoKeys = true)
|
||||
{
|
||||
_cryptoService = cryptoService;
|
||||
@@ -57,6 +63,7 @@ namespace Bit.Core.Services
|
||||
_messagingService = messagingService;
|
||||
_keyConnectorService = keyConnectorService;
|
||||
_passwordGenerationService = passwordGenerationService;
|
||||
_policyService = policyService;
|
||||
_setCryptoKeys = setCryptoKeys;
|
||||
|
||||
TwoFactorProviders = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
|
||||
@@ -136,11 +143,56 @@ namespace Bit.Core.Services
|
||||
public async Task<AuthResult> 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);
|
||||
return await LogInHelperAsync(email, hashedPassword, localHashedPassword, null, null, null, key, null, null,
|
||||
null, captchaToken);
|
||||
var result = await LogInHelperAsync(email, hashedPassword, localHashedPassword, null, null, null, key, null, null, null, captchaToken);
|
||||
|
||||
if (await RequirePasswordChangeAsync(email, masterPassword))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the supplied master password against the master password policy provided by the Identity response.
|
||||
/// </summary>
|
||||
/// <param name="email"></param>
|
||||
/// <param name="masterPassword"></param>
|
||||
/// <returns>True if the master password does NOT meet any policy requirements, false otherwise (or if no policy present)</returns>
|
||||
private async Task<bool> RequirePasswordChangeAsync(string email, string masterPassword)
|
||||
{
|
||||
// No policy with EnforceOnLogin enabled, we're done.
|
||||
if (!(_masterPasswordPolicy is { EnforceOnLogin: true }))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var strength = _passwordGenerationService.PasswordStrength(
|
||||
masterPassword,
|
||||
_passwordGenerationService.GetPasswordStrengthUserInput(email)
|
||||
)?.Score;
|
||||
|
||||
if (!strength.HasValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !await _policyService.EvaluateMasterPassword(strength.Value, masterPassword, _masterPasswordPolicy);
|
||||
}
|
||||
|
||||
public async Task<AuthResult> LogInPasswordlessAsync(string email, string accessCode, string authRequestId, byte[] decryptionKey, string userKeyCiphered, string localHashedPasswordCiphered)
|
||||
@@ -157,15 +209,26 @@ namespace Bit.Core.Services
|
||||
return await LogInHelperAsync(null, null, null, code, codeVerifier, redirectUrl, null, orgId: orgId);
|
||||
}
|
||||
|
||||
public Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken,
|
||||
public async Task<AuthResult> 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(
|
||||
_2faForcePasswordResetReason, _authedUserId);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<AuthResult> LogInCompleteAsync(string email, string masterPassword,
|
||||
@@ -367,6 +430,7 @@ namespace Bit.Core.Services
|
||||
TwoFactorProvidersData = response.TwoFactorResponse.TwoFactorProviders2;
|
||||
result.TwoFactorProviders = response.TwoFactorResponse.TwoFactorProviders2;
|
||||
CaptchaToken = response.TwoFactorResponse.CaptchaToken;
|
||||
_masterPasswordPolicy = response.TwoFactorResponse.MasterPasswordPolicy;
|
||||
await _tokenService.ClearTwoFactorTokenAsync(email);
|
||||
return result;
|
||||
}
|
||||
@@ -374,6 +438,7 @@ namespace Bit.Core.Services
|
||||
var tokenResponse = response.TokenResponse;
|
||||
result.ResetMasterPassword = tokenResponse.ResetMasterPassword;
|
||||
result.ForcePasswordReset = tokenResponse.ForcePasswordReset;
|
||||
_masterPasswordPolicy = tokenResponse.MasterPasswordPolicy;
|
||||
if (tokenResponse.TwoFactorToken != null)
|
||||
{
|
||||
await _tokenService.SetTwoFactorTokenAsync(tokenResponse.TwoFactorToken, email);
|
||||
@@ -391,6 +456,9 @@ namespace Bit.Core.Services
|
||||
KdfMemory = tokenResponse.KdfMemory,
|
||||
KdfParallelism = tokenResponse.KdfParallelism,
|
||||
HasPremiumPersonally = _tokenService.GetPremium(),
|
||||
ForcePasswordResetReason = result.ForcePasswordReset
|
||||
? ForcePasswordResetReason.AdminForcePasswordReset
|
||||
: (ForcePasswordResetReason?)null,
|
||||
},
|
||||
new Account.AccountTokens()
|
||||
{
|
||||
@@ -473,6 +541,7 @@ namespace Bit.Core.Services
|
||||
|
||||
}
|
||||
|
||||
_authedUserId = _tokenService.GetUserId();
|
||||
await _stateService.SetBiometricLockedAsync(false);
|
||||
_messagingService.Send("loggedIn");
|
||||
return result;
|
||||
@@ -490,6 +559,8 @@ namespace Bit.Core.Services
|
||||
SsoRedirectUrl = null;
|
||||
TwoFactorProvidersData = null;
|
||||
SelectedTwoFactorProviderType = null;
|
||||
_masterPasswordPolicy = null;
|
||||
_authedUserId = null;
|
||||
}
|
||||
|
||||
public async Task<List<PasswordlessLoginResponse>> GetPasswordlessLoginRequestsAsync()
|
||||
|
||||
Reference in New Issue
Block a user