1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-08 12:40:26 +00:00

feat(change-password-component): Change Password Update [18720] - More touchups, very close to complete.

This commit is contained in:
Patrick Pimentel
2025-05-21 14:01:09 -04:00
parent 0d1b50a249
commit 4ccdebe984
11 changed files with 29 additions and 139 deletions

View File

@@ -98,7 +98,7 @@ export class WebLoginComponentService
const enforcedPasswordPolicyOptions = await firstValueFrom(
this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId)),
switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId, policies)),
),
);

View File

@@ -3,7 +3,6 @@
</div>
<div class="tw-max-w-lg tw-mb-12">
<bit-callout type="warning">{{ "loggedOutWarning" | i18n }}</bit-callout>
<auth-change-password [inputPasswordFlow]="inputPasswordFlow"></auth-change-password>
</div>

View File

@@ -155,8 +155,10 @@ const routes: Routes = [
},
],
data: {
pageIcon: undefined,
pageIcon: LockIcon,
pageTitle: { key: "updateMasterPassword" },
hideFooter: true,
maxWidth: "lg",
} satisfies AnonLayoutWrapperData,
},
],
@@ -168,7 +170,7 @@ const routes: Routes = [
canActivate: [
canAccessFeature(
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
false,
true,
"/change-password",
false,
),
@@ -182,7 +184,7 @@ const routes: Routes = [
canActivate: [
canAccessFeature(
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
false,
true,
"/change-password",
false,
),

View File

@@ -1788,6 +1788,9 @@
"loggedOutWarning": {
"message": "Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour."
},
"changePasswordWarning": {
"message": "After changing your password, you will need to log in with your new password. Active sessions on other devices will be logged out within one hour."
},
"emailChanged": {
"message": "Email saved"
},

View File

@@ -34,7 +34,7 @@ export interface AnonLayoutWrapperData {
/**
* Optional flag to set the max-width of the page. Defaults to 'md' if not provided.
*/
maxWidth?: "md" | "3xl";
maxWidth?: "sm" | "md" | "lg" | "xl" | "2xl" | "3xl";
/**
* Optional flag to set the max-width of the title area. Defaults to null if not provided.
*/

View File

@@ -37,7 +37,14 @@
<div
class="tw-grow tw-w-full tw-max-w-md tw-mx-auto tw-flex tw-flex-col tw-items-center sm:tw-min-w-[28rem]"
[ngClass]="{ 'tw-max-w-md': maxWidth === 'md', 'tw-max-w-3xl': maxWidth === '3xl' }"
[ngClass]="{
'tw-max-w-sm': maxWidth === 'sm',
'tw-max-w-md': maxWidth === 'md',
'tw-max-w-lg': maxWidth === 'lg',
'tw-max-w-xl': maxWidth === 'xl',
'tw-max-w-2xl': maxWidth === '2xl',
'tw-max-w-3xl': maxWidth === '3xl',
}"
>
<div
class="tw-rounded-2xl tw-mb-6 sm:tw-mb-10 tw-mx-auto tw-w-full sm:tw-bg-background sm:tw-border sm:tw-border-solid sm:tw-border-secondary-300 sm:tw-p-8"

View File

@@ -53,10 +53,10 @@ export class AnonLayoutComponent implements OnInit, OnChanges {
*
* @default 'md'
*/
@Input() maxWidth: "md" | "3xl" = "md";
@Input() maxWidth: "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" = "md";
protected logo = BitwardenLogo;
protected year = "2024";
protected year = "";
protected clientType: ClientType;
protected hostname: string;
protected version: string;

View File

@@ -15,8 +15,8 @@
[inlineButtons]="true"
[primaryButtonText]="{ key: 'changeMasterPassword' }"
(onPasswordFormSubmit)="handlePasswordFormSubmit($event)"
[secondaryButtonText]="secondaryButtonText"
(onSecondaryButtonClick)="performSecondaryAction()"
[secondaryButtonText]="{ key: 'cancel' }"
(onSecondaryButtonClick)="logOut()"
>
</auth-input-password>
}

View File

@@ -5,25 +5,15 @@ import { firstValueFrom } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SyncService } from "@bitwarden/common/platform/sync";
import { UserId } from "@bitwarden/common/types/guid";
import { UserKey } from "@bitwarden/common/types/key";
import { DialogService, ToastService, Translation } from "@bitwarden/components";
import { KeyService } from "@bitwarden/key-management";
import { I18nPipe } from "@bitwarden/ui-common";
// import {
// AcceptOrganizationInviteService
// } from "@bitwarden/web-vault/src/app/auth/organization-invite/accept-organization.service";
// import { RouterService } from "@bitwarden/web-vault/src/app/core";
import {
InputPasswordComponent,
@@ -48,18 +38,15 @@ export class ChangePasswordComponent implements OnInit {
masterPasswordPolicyOptions?: MasterPasswordPolicyOptions;
initializing = true;
submitting = false;
userkeyRotationV2 = false;
formPromise?: Promise<any>;
secondaryButtonText?: Translation = undefined;
forceSetPasswordReason: ForceSetPasswordReason = ForceSetPasswordReason.None;
warningText?: Translation;
constructor(
private accountService: AccountService,
private changePasswordService: ChangePasswordService,
private configService: ConfigService,
private i18nService: I18nService,
private keyService: KeyService,
private masterPasswordApiService: MasterPasswordApiService,
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
private messagingService: MessagingService,
private policyService: PolicyService,
@@ -72,8 +59,6 @@ export class ChangePasswordComponent implements OnInit {
) {}
async ngOnInit() {
this.userkeyRotationV2 = await this.configService.getFeatureFlag(FeatureFlag.UserKeyRotationV2);
this.activeAccount = await firstValueFrom(this.accountService.activeAccount$);
this.activeUserId = this.activeAccount?.id;
this.email = this.activeAccount?.email;
@@ -90,26 +75,10 @@ export class ChangePasswordComponent implements OnInit {
this.masterPasswordService.forceSetPasswordReason$(this.activeUserId),
);
if (
this.forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset ||
this.forceSetPasswordReason === ForceSetPasswordReason.WeakMasterPassword
) {
this.secondaryButtonText = { key: "cancel" };
} else {
this.secondaryButtonText = undefined;
}
this.initializing = false;
}
async performSecondaryAction() {
if (
this.forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset ||
this.forceSetPasswordReason === ForceSetPasswordReason.WeakMasterPassword
) {
await this.logOut();
} else {
await this.cancel();
if (this.masterPasswordPolicyOptions?.enforceOnLogin) {
this.warningText = { key: "masterPasswordInvalidWarning" };
}
}
@@ -126,24 +95,9 @@ export class ChangePasswordComponent implements OnInit {
}
}
async cancel() {
// clearing the login redirect url so that the user
// does not join the organization if they cancel
// await this.routerService.getAndClearLoginRedirectUrl();
// await this.acceptOrganizationInviteService.clearOrganizationInvitation();
// await this.router.navigate(["/vault"]);
}
async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) {
this.submitting = true;
if (this.userkeyRotationV2) {
await this.submitNew(passwordInputResult);
} else {
await this.submitOld(passwordInputResult);
}
}
private async submitNew(passwordInputResult: PasswordInputResult) {
try {
if (passwordInputResult.rotateUserKey) {
if (this.activeAccount == null) {
@@ -187,83 +141,4 @@ export class ChangePasswordComponent implements OnInit {
this.submitting = false;
}
}
private async submitOld(passwordInputResult: PasswordInputResult) {
if (!this.activeUserId) {
throw new Error("userId not found");
}
if (passwordInputResult.currentServerMasterKeyHash == null) {
throw new Error("currentServerMasterKeyHash not found");
}
if (passwordInputResult.rotateUserKey) {
await this.syncService.fullSync(true);
}
let newMasterKeyEncryptedUserKey: [UserKey, EncString] | null = null;
const userKey = await this.keyService.getUserKey();
if (userKey == null) {
newMasterKeyEncryptedUserKey = await this.keyService.makeUserKey(
passwordInputResult.newMasterKey,
);
} else {
newMasterKeyEncryptedUserKey = await this.keyService.encryptUserKeyWithMasterKey(
passwordInputResult.newMasterKey,
);
}
const request = new PasswordRequest();
request.masterPasswordHash = passwordInputResult.currentServerMasterKeyHash;
request.newMasterPasswordHash = passwordInputResult.newServerMasterKeyHash;
request.masterPasswordHint = passwordInputResult.newPasswordHint;
request.key = newMasterKeyEncryptedUserKey[1].encryptedString as string;
try {
if (passwordInputResult.rotateUserKey) {
this.formPromise = this.masterPasswordApiService.postPassword(request).then(async () => {
// we need to save this for local masterkey verification during rotation
await this.masterPasswordService.setMasterKeyHash(
passwordInputResult.newLocalMasterKeyHash,
this.activeUserId as UserId,
);
await this.masterPasswordService.setMasterKey(
passwordInputResult.newMasterKey,
this.activeUserId as UserId,
);
return this.updateKey(passwordInputResult.newPassword);
});
} else {
this.formPromise = this.masterPasswordApiService.postPassword(request);
}
await this.formPromise;
this.toastService.showToast({
variant: "success",
title: this.i18nService.t("masterPasswordChanged"),
message: this.i18nService.t("logBackIn"),
});
this.messagingService.send("logout");
} catch {
this.toastService.showToast({
variant: "error",
title: "",
message: this.i18nService.t("errorOccurred"),
});
}
}
private async updateKey(newPassword: string) {
if (this.activeAccount == null) {
throw new Error("activeAccount not found");
}
await this.changePasswordService.rotateUserKeyAndEncryptedDataLegacy(
newPassword,
this.activeAccount,
);
}
}

View File

@@ -1,4 +1,6 @@
<form [formGroup]="formGroup" [bitSubmit]="submit">
<bit-callout type="warning">{{ "changePasswordWarning" | i18n }}</bit-callout>
<auth-password-callout
*ngIf="masterPasswordPolicyOptions"
[policy]="masterPasswordPolicyOptions"

View File

@@ -20,6 +20,7 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi
import {
AsyncActionsModule,
ButtonModule,
CalloutComponent,
CheckboxModule,
DialogService,
FormFieldModule,
@@ -95,6 +96,7 @@ interface InputPasswordForm {
PasswordCalloutComponent,
PasswordStrengthV2Component,
JslibModule,
CalloutComponent,
],
})
export class InputPasswordComponent implements OnInit {