1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-07 12:13:45 +00:00

feat(change-password-component): Change Password Update [18720] - Org invite is seemingly working, found one bug to iron out.

This commit is contained in:
Patrick Pimentel
2025-05-30 10:40:27 -04:00
parent 06039927bb
commit bb11db344f
10 changed files with 96 additions and 30 deletions

View File

@@ -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,

View File

@@ -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>): OrganizationInvite | null {
if (json == null) {
@@ -35,6 +38,7 @@ export class OrganizationInvite {
organizationName: params.organizationName,
organizationUserId: params.organizationUserId,
token: params.token,
userId: params.userId,
});
}
}

View File

@@ -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({

View File

@@ -487,6 +487,7 @@ const safeProviders: SafeProvider[] = [
VaultTimeoutSettingsService,
KdfConfigService,
TaskSchedulerService,
ConfigService,
],
}),
safeProvider({

View File

@@ -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;
}
}
}
}

View File

@@ -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;

View File

@@ -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<AuthResult> {
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

View File

@@ -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,
) {}
}

View File

@@ -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(

View File

@@ -18,6 +18,7 @@ export class AuthResult {
email: string;
requiresEncryptionKeyMigration: boolean;
requiresDeviceVerification: boolean;
orgInviteAndWeakPassword?: boolean;
get requiresTwoFactor() {
return this.twoFactorProviders != null;