diff --git a/apps/web/src/app/auth/core/services/login/web-login-component.service.ts b/apps/web/src/app/auth/core/services/login/web-login-component.service.ts index 36e7143ccd0..3177cfdd596 100644 --- a/apps/web/src/app/auth/core/services/login/web-login-component.service.ts +++ b/apps/web/src/app/auth/core/services/login/web-login-component.service.ts @@ -11,11 +11,14 @@ import { } from "@bitwarden/auth/angular"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -42,6 +45,7 @@ export class WebLoginComponentService ssoLoginService: SsoLoginServiceAbstraction, private router: Router, private accountService: AccountService, + private configService: ConfigService, ) { super( cryptoFunctionService, @@ -95,12 +99,25 @@ export class WebLoginComponentService const isPolicyAndAutoEnrollEnabled = resetPasswordPolicy[1] && resetPasswordPolicy[0].autoEnrollEnabled; - const enforcedPasswordPolicyOptions = await firstValueFrom( - this.accountService.activeAccount$.pipe( - getUserId, - switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId, policies)), - ), - ); + let enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions; + + if ( + await this.configService.getFeatureFlag(FeatureFlag.PM16117_ChangeExistingPasswordRefactor) + ) { + // Properly error if we don't have an org invite with + enforcedPasswordPolicyOptions = await firstValueFrom( + this.policyService.masterPasswordPolicyOptions$(orgInvite.userId, policies), + ); + } else { + enforcedPasswordPolicyOptions = await firstValueFrom( + this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => + this.policyService.masterPasswordPolicyOptions$(userId, policies), + ), + ), + ); + } return { policies, diff --git a/apps/web/src/app/auth/organization-invite/organization-invite.ts b/apps/web/src/app/auth/organization-invite/organization-invite.ts index 65414113e74..d52d5e41d02 100644 --- a/apps/web/src/app/auth/organization-invite/organization-invite.ts +++ b/apps/web/src/app/auth/organization-invite/organization-invite.ts @@ -3,6 +3,8 @@ import { Params } from "@angular/router"; import { Jsonify } from "type-fest"; +import { UserId } from "@bitwarden/common/types/guid"; + export class OrganizationInvite { email: string; initOrganization: boolean; @@ -12,6 +14,7 @@ export class OrganizationInvite { organizationName: string; organizationUserId: string; token: string; + userId: UserId; static fromJSON(json: Jsonify): OrganizationInvite | null { if (json == null) { @@ -35,6 +38,7 @@ export class OrganizationInvite { organizationName: params.organizationName, organizationUserId: params.organizationUserId, token: params.token, + userId: params.userId, }); } } diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index e812edd8f32..415dae50c74 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -62,6 +62,7 @@ import { VaultTimeoutStringType, } from "@bitwarden/common/key-management/vault-timeout"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService, Urls, @@ -312,6 +313,7 @@ const safeProviders: SafeProvider[] = [ SsoLoginServiceAbstraction, Router, AccountService, + ConfigService, ], }), safeProvider({ diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 08bcaa2165c..e2da58acee1 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -487,6 +487,7 @@ const safeProviders: SafeProvider[] = [ VaultTimeoutSettingsService, KdfConfigService, TaskSchedulerService, + ConfigService, ], }), safeProvider({ diff --git a/libs/auth/src/angular/login/login.component.ts b/libs/auth/src/angular/login/login.component.ts index 8674453cf10..2b1ed1cff78 100644 --- a/libs/auth/src/angular/login/login.component.ts +++ b/libs/auth/src/angular/login/login.component.ts @@ -18,9 +18,11 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ClientType, HttpStatusCode } from "@bitwarden/common/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -123,6 +125,7 @@ export class LoginComponent implements OnInit, OnDestroy { private logService: LogService, private validationService: ValidationService, private loginSuccessHandlerService: LoginSuccessHandlerService, + private configService: ConfigService, ) { this.clientType = this.platformUtilsService.getClientType(); } @@ -226,7 +229,20 @@ export class LoginComponent implements OnInit, OnDestroy { return; } - const credentials = new PasswordLoginCredentials(email, masterPassword); + let credentials: PasswordLoginCredentials; + + if ( + (await this.configService.getFeatureFlag( + FeatureFlag.PM16117_ChangeExistingPasswordRefactor, + )) && + this.loginComponentService.getOrgPoliciesFromOrgInvite + ) { + const orgPoliciesFromInvite = await this.loginComponentService.getOrgPoliciesFromOrgInvite(); + const orgPolicies = orgPoliciesFromInvite?.enforcedPasswordPolicyOptions ?? undefined; + credentials = new PasswordLoginCredentials(email, masterPassword, undefined, orgPolicies); + } else { + credentials = new PasswordLoginCredentials(email, masterPassword); + } try { const authResult = await this.loginStrategyService.logIn(credentials); @@ -312,26 +328,30 @@ export class LoginComponent implements OnInit, OnDestroy { // Determine where to send the user next // The AuthGuard will handle routing to update-temp-password based on state - // TODO: PM-18269 - evaluate if we can combine this with the - // password evaluation done in the password login strategy. - // If there's an existing org invite, use it to get the org's password policies - // so we can evaluate the MP against the org policies - if (this.loginComponentService.getOrgPoliciesFromOrgInvite) { - const orgPolicies: PasswordPolicies | null = - await this.loginComponentService.getOrgPoliciesFromOrgInvite(); + if ( + !(await this.configService.getFeatureFlag(FeatureFlag.PM16117_ChangeExistingPasswordRefactor)) + ) { + // TODO: PM-18269 - evaluate if we can combine this with the + // password evaluation done in the password login strategy. + // If there's an existing org invite, use it to get the org's password policies + // so we can evaluate the MP against the org policies + if (this.loginComponentService.getOrgPoliciesFromOrgInvite) { + const orgPolicies: PasswordPolicies | null = + await this.loginComponentService.getOrgPoliciesFromOrgInvite(); - if (orgPolicies) { - // Since we have retrieved the policies, we can go ahead and set them into state for future use - // e.g., the update-password page currently only references state for policy data and - // doesn't fallback to pulling them from the server like it should if they are null. - await this.setPoliciesIntoState(authResult.userId, orgPolicies.policies); + if (orgPolicies) { + // Since we have retrieved the policies, we can go ahead and set them into state for future use + // e.g., the update-password page currently only references state for policy data and + // doesn't fallback to pulling them from the server like it should if they are null. + await this.setPoliciesIntoState(authResult.userId, orgPolicies.policies); - const isPasswordChangeRequired = await this.isPasswordChangeRequiredByOrgPolicy( - orgPolicies.enforcedPasswordPolicyOptions, - ); - if (isPasswordChangeRequired) { - await this.router.navigate(["update-password"]); - return; + const isPasswordChangeRequired = await this.isPasswordChangeRequiredByOrgPolicy( + orgPolicies.enforcedPasswordPolicyOptions, + ); + if (isPasswordChangeRequired) { + await this.router.navigate(["update-password"]); + return; + } } } } diff --git a/libs/auth/src/common/login-strategies/login.strategy.ts b/libs/auth/src/common/login-strategies/login.strategy.ts index 6e66d65b654..d00e75bb630 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.ts @@ -26,6 +26,7 @@ import { } from "@bitwarden/common/key-management/vault-timeout"; import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -92,6 +93,7 @@ export abstract class LoginStrategy { protected vaultTimeoutSettingsService: VaultTimeoutSettingsService, protected KdfConfigService: KdfConfigService, protected environmentService: EnvironmentService, + protected configService: ConfigService, ) {} abstract exportCache(): CacheData; diff --git a/libs/auth/src/common/login-strategies/password-login.strategy.ts b/libs/auth/src/common/login-strategies/password-login.strategy.ts index b314b7fddbb..1c97650f111 100644 --- a/libs/auth/src/common/login-strategies/password-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/password-login.strategy.ts @@ -12,6 +12,7 @@ import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/ide import { IdentityDeviceVerificationResponse } from "@bitwarden/common/auth/models/response/identity-device-verification.response"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { HashPurpose } from "@bitwarden/common/platform/enums"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; @@ -75,7 +76,7 @@ export class PasswordLoginStrategy extends LoginStrategy { this.localMasterKeyHash$ = this.cache.pipe(map((state) => state.localMasterKeyHash)); } - override async logIn(credentials: PasswordLoginCredentials) { + override async logIn(credentials: PasswordLoginCredentials): Promise { const { email, masterPassword, twoFactor } = credentials; const data = new PasswordLoginStrategyData(); @@ -167,11 +168,24 @@ export class PasswordLoginStrategy extends LoginStrategy { } // The identity result can contain master password policies for the user's organizations - const masterPasswordPolicyOptions = - this.getMasterPasswordPolicyOptionsFromResponse(identityResponse); + let masterPasswordPolicyOptions; - if (!masterPasswordPolicyOptions?.enforceOnLogin) { - return; + if ( + await this.configService.getFeatureFlag(FeatureFlag.PM16117_ChangeExistingPasswordRefactor) + ) { + masterPasswordPolicyOptions = credentials.masterPasswordPolicies; + authResult.orgInviteAndWeakPassword = true; + + if (!masterPasswordPolicyOptions.enforceOnLogin) { + return; + } + } else { + masterPasswordPolicyOptions = + this.getMasterPasswordPolicyOptionsFromResponse(identityResponse); + + if (!masterPasswordPolicyOptions?.enforceOnLogin) { + return; + } } // If there is a policy active, evaluate the supplied password before its no longer in memory diff --git a/libs/auth/src/common/models/domain/login-credentials.ts b/libs/auth/src/common/models/domain/login-credentials.ts index bce8ce54de5..3b255e5bdf9 100644 --- a/libs/auth/src/common/models/domain/login-credentials.ts +++ b/libs/auth/src/common/models/domain/login-credentials.ts @@ -2,6 +2,7 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; import { WebAuthnLoginAssertionResponseRequest } from "@bitwarden/common/auth/services/webauthn-login/request/webauthn-login-assertion-response.request"; @@ -15,6 +16,7 @@ export class PasswordLoginCredentials { public email: string, public masterPassword: string, public twoFactor?: TokenTwoFactorRequest, + public masterPasswordPolicies?: MasterPasswordPolicyOptions, ) {} } diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts index a9b7ef250bc..6900e5e5872 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts @@ -26,6 +26,7 @@ import { VaultTimeoutSettingsService } from "@bitwarden/common/key-management/va import { PreloginRequest } from "@bitwarden/common/models/request/prelogin.request"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -131,6 +132,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { protected vaultTimeoutSettingsService: VaultTimeoutSettingsService, protected kdfConfigService: KdfConfigService, protected taskSchedulerService: TaskSchedulerService, + protected configService: ConfigService, ) { this.currentAuthnTypeState = this.stateProvider.get(CURRENT_LOGIN_STRATEGY_KEY); this.loginStrategyCacheState = this.stateProvider.get(CACHE_KEY); @@ -400,6 +402,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { this.vaultTimeoutSettingsService, this.kdfConfigService, this.environmentService, + this.configService, ]; return source.pipe( diff --git a/libs/common/src/auth/models/domain/auth-result.ts b/libs/common/src/auth/models/domain/auth-result.ts index a61a35eeb1d..13e68f54d26 100644 --- a/libs/common/src/auth/models/domain/auth-result.ts +++ b/libs/common/src/auth/models/domain/auth-result.ts @@ -18,6 +18,7 @@ export class AuthResult { email: string; requiresEncryptionKeyMigration: boolean; requiresDeviceVerification: boolean; + orgInviteAndWeakPassword?: boolean; get requiresTwoFactor() { return this.twoFactorProviders != null;