1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

refactor(set-change-password): [Auth/PM-18206] Update InputPasswordComponent to handle multiple flows (#13745)

Updates the InputPasswordComponent so that it can eventually be used in multiple set/change password scenarios.

Most importantly, this PR adds an InputPasswordFlow enum and @Input so that parent components can dictate which UI elements to show.
This commit is contained in:
rr-bw
2025-04-07 11:58:50 -07:00
committed by GitHub
parent 7f58cee41b
commit 2267876860
20 changed files with 394 additions and 113 deletions

View File

@@ -4,14 +4,36 @@
[policy]="masterPasswordPolicyOptions"
></auth-password-callout>
<bit-form-field
*ngIf="
inputPasswordFlow === InputPasswordFlow.ChangePassword ||
inputPasswordFlow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation
"
>
<bit-label>{{ "currentMasterPass" | i18n }}</bit-label>
<input
id="input-password-form_current-password"
bitInput
type="password"
formControlName="currentPassword"
/>
<button
type="button"
bitIconButton
bitSuffix
bitPasswordInputToggle
[(toggled)]="showPassword"
></button>
</bit-form-field>
<div class="tw-mb-6">
<bit-form-field>
<bit-label>{{ "masterPassword" | i18n }}</bit-label>
<bit-label>{{ "newMasterPass" | i18n }}</bit-label>
<input
id="input-password-form_password"
id="input-password-form_new-password"
bitInput
type="password"
formControlName="password"
formControlName="newPassword"
/>
<button
type="button"
@@ -30,7 +52,7 @@
<tools-password-strength
[showText]="true"
[email]="email"
[password]="formGroup.controls.password.value"
[password]="formGroup.controls.newPassword.value"
(passwordStrengthScore)="getPasswordStrengthScore($event)"
></tools-password-strength>
</div>
@@ -38,10 +60,10 @@
<bit-form-field>
<bit-label>{{ "confirmMasterPassword" | i18n }}</bit-label>
<input
id="input-password-form_confirmed-password"
id="input-password-form_confirm-new-password"
bitInput
type="password"
formControlName="confirmedPassword"
formControlName="confirmNewPassword"
/>
<button
type="button"
@@ -65,16 +87,40 @@
<bit-label>{{ "checkForBreaches" | i18n }}</bit-label>
</bit-form-control>
<button
type="submit"
bitButton
bitFormButton
buttonType="primary"
[block]="btnBlock"
[loading]="loading"
<bit-form-control
*ngIf="inputPasswordFlow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation"
>
{{ buttonText || ("setMasterPassword" | i18n) }}
</button>
<input type="checkbox" bitCheckbox formControlName="rotateUserKey" />
<bit-label>
{{ "rotateAccountEncKey" | i18n }}
<a
href="https://bitwarden.com/help/account-encryption-key/#rotate-your-encryption-key"
target="_blank"
rel="noreferrer"
appA11yTitle="{{ 'impactOfRotatingYourEncryptionKey' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</bit-label>
</bit-form-control>
<div class="tw-flex tw-gap-2" [ngClass]="inlineButtons ? 'tw-flex-row' : 'tw-flex-col'">
<button type="submit" bitButton bitFormButton buttonType="primary" [loading]="loading">
{{ primaryButtonTextStr || ("setMasterPassword" | i18n) }}
</button>
<button
*ngIf="secondaryButtonText"
type="button"
bitButton
bitFormButton
buttonType="secondary"
[loading]="loading"
(click)="onSecondaryButtonClick.emit()"
>
{{ secondaryButtonTextStr }}
</button>
</div>
<bit-error-summary *ngIf="showErrorSummary" [formGroup]="formGroup"></bit-error-summary>
</form>

View File

@@ -1,7 +1,5 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { ReactiveFormsModule, FormBuilder, Validators } from "@angular/forms";
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { ReactiveFormsModule, FormBuilder, Validators, FormGroup } from "@angular/forms";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import {
@@ -23,6 +21,7 @@ import {
IconButtonModule,
InputModule,
ToastService,
Translation,
} from "@bitwarden/components";
import { DEFAULT_KDF_CONFIG, KeyService } from "@bitwarden/key-management";
@@ -36,6 +35,29 @@ import { PasswordCalloutComponent } from "../password-callout/password-callout.c
import { PasswordInputResult } from "./password-input-result";
/**
* Determines which form input elements will be displayed in the UI.
*/
export enum InputPasswordFlow {
/**
* - Input: New password
* - Input: Confirm new password
* - Input: Hint
* - Checkbox: Check for breaches
*/
SetInitialPassword,
/**
* Everything above, plus:
* - Input: Current password (as the first element in the UI)
*/
ChangePassword,
/**
* Everything above, plus:
* - Checkbox: Rotate account encryption key (as the last element in the UI)
*/
ChangePasswordWithOptionalUserKeyRotation,
}
@Component({
standalone: true,
selector: "auth-input-password",
@@ -54,44 +76,58 @@ import { PasswordInputResult } from "./password-input-result";
JslibModule,
],
})
export class InputPasswordComponent {
export class InputPasswordComponent implements OnInit {
@Output() onPasswordFormSubmit = new EventEmitter<PasswordInputResult>();
@Output() onSecondaryButtonClick = new EventEmitter<void>();
@Input({ required: true }) email: string;
@Input() buttonText: string;
@Input({ required: true }) inputPasswordFlow!: InputPasswordFlow;
@Input({ required: true }) email!: string;
@Input() loading = false;
@Input() masterPasswordPolicyOptions: MasterPasswordPolicyOptions | null = null;
@Input() loading: boolean = false;
@Input() btnBlock: boolean = true;
@Input() inlineButtons = false;
@Input() primaryButtonText?: Translation;
protected primaryButtonTextStr: string = "";
@Input() secondaryButtonText?: Translation;
protected secondaryButtonTextStr: string = "";
protected InputPasswordFlow = InputPasswordFlow;
private minHintLength = 0;
protected maxHintLength = 50;
protected minPasswordLength = Utils.minimumPasswordLength;
protected minPasswordMsg = "";
protected passwordStrengthScore: PasswordStrengthScore;
protected passwordStrengthScore: PasswordStrengthScore = 0;
protected showErrorSummary = false;
protected showPassword = false;
protected formGroup = this.formBuilder.group(
protected formGroup = this.formBuilder.nonNullable.group(
{
password: ["", [Validators.required, Validators.minLength(this.minPasswordLength)]],
confirmedPassword: ["", Validators.required],
newPassword: ["", [Validators.required, Validators.minLength(this.minPasswordLength)]],
confirmNewPassword: ["", Validators.required],
hint: [
"", // must be string (not null) because we check length in validation
[Validators.minLength(this.minHintLength), Validators.maxLength(this.maxHintLength)],
],
checkForBreaches: true,
checkForBreaches: [true],
},
{
validators: [
InputsFieldMatch.compareInputs(
"doNotMatch",
"currentPassword",
"newPassword",
this.i18nService.t("yourNewPasswordCannotBeTheSameAsYourCurrentPassword"),
),
InputsFieldMatch.compareInputs(
"match",
"password",
"confirmedPassword",
"newPassword",
"confirmNewPassword",
this.i18nService.t("masterPassDoesntMatch"),
),
InputsFieldMatch.compareInputs(
"doNotMatch",
"password",
"newPassword",
"hint",
this.i18nService.t("hintEqualsPassword"),
),
@@ -109,6 +145,41 @@ export class InputPasswordComponent {
private toastService: ToastService,
) {}
ngOnInit(): void {
if (
this.inputPasswordFlow === InputPasswordFlow.ChangePassword ||
this.inputPasswordFlow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation
) {
// https://github.com/angular/angular/issues/48794
(this.formGroup as FormGroup<any>).addControl(
"currentPassword",
this.formBuilder.control("", Validators.required),
);
}
if (this.inputPasswordFlow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation) {
// https://github.com/angular/angular/issues/48794
(this.formGroup as FormGroup<any>).addControl(
"rotateUserKey",
this.formBuilder.control(false),
);
}
if (this.primaryButtonText) {
this.primaryButtonTextStr = this.i18nService.t(
this.primaryButtonText.key,
...(this.primaryButtonText?.placeholders ?? []),
);
}
if (this.secondaryButtonText) {
this.secondaryButtonTextStr = this.i18nService.t(
this.secondaryButtonText.key,
...(this.secondaryButtonText?.placeholders ?? []),
);
}
}
get minPasswordLengthMsg() {
if (
this.masterPasswordPolicyOptions != null &&
@@ -132,10 +203,10 @@ export class InputPasswordComponent {
return;
}
const password = this.formGroup.controls.password.value;
const newPassword = this.formGroup.controls.newPassword.value;
const passwordEvaluatedSuccessfully = await this.evaluatePassword(
password,
const passwordEvaluatedSuccessfully = await this.evaluateNewPassword(
newPassword,
this.passwordStrengthScore,
this.formGroup.controls.checkForBreaches.value,
);
@@ -152,38 +223,55 @@ export class InputPasswordComponent {
}
const masterKey = await this.keyService.makeMasterKey(
password,
newPassword,
this.email.trim().toLowerCase(),
kdfConfig,
);
const masterKeyHash = await this.keyService.hashMasterKey(password, masterKey);
const serverMasterKeyHash = await this.keyService.hashMasterKey(
newPassword,
masterKey,
HashPurpose.ServerAuthorization,
);
const localMasterKeyHash = await this.keyService.hashMasterKey(
password,
newPassword,
masterKey,
HashPurpose.LocalAuthorization,
);
this.onPasswordFormSubmit.emit({
masterKey,
masterKeyHash,
localMasterKeyHash,
kdfConfig,
const passwordInputResult: PasswordInputResult = {
newPassword,
hint: this.formGroup.controls.hint.value,
password,
});
kdfConfig,
masterKey,
serverMasterKeyHash,
localMasterKeyHash,
};
if (
this.inputPasswordFlow === InputPasswordFlow.ChangePassword ||
this.inputPasswordFlow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation
) {
passwordInputResult.currentPassword = this.formGroup.get("currentPassword")?.value;
}
if (this.inputPasswordFlow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation) {
passwordInputResult.rotateUserKey = this.formGroup.get("rotateUserKey")?.value;
}
this.onPasswordFormSubmit.emit(passwordInputResult);
};
// Returns true if the password passes all checks, false otherwise
private async evaluatePassword(
password: string,
private async evaluateNewPassword(
newPassword: string,
passwordStrengthScore: PasswordStrengthScore,
checkForBreaches: boolean,
) {
// Check if the password is breached, weak, or both
const passwordIsBreached =
checkForBreaches && (await this.auditService.passwordLeaked(password));
checkForBreaches && (await this.auditService.passwordLeaked(newPassword));
const passwordWeak = passwordStrengthScore != null && passwordStrengthScore < 3;
@@ -224,7 +312,7 @@ export class InputPasswordComponent {
this.masterPasswordPolicyOptions != null &&
!this.policyService.evaluateMasterPassword(
this.passwordStrengthScore,
password,
newPassword,
this.masterPasswordPolicyOptions,
)
) {

View File

@@ -6,9 +6,9 @@ import * as stories from "./input-password.stories.ts";
# InputPassword Component
The `InputPasswordComponent` allows a user to enter a master password and hint. On submission it
creates a master key, master key hash, and emits those values to the parent (along with the hint and
default kdfConfig).
The `InputPasswordComponent` allows a user to enter master password related credentials. On
submission it creates a master key, master key hash, and emits those values to the parent (along
with the other values found in `PasswordInputResult`).
The component is intended for re-use in different scenarios throughout the application. Therefore it
is mostly presentational and simply emits values rather than acting on them itself. It is the job of
@@ -18,26 +18,66 @@ the parent component to act on those values as needed.
## `@Input()`'s
- `email` (**required**) - the parent component must provide an email so that the
`InputPasswordComponent` can create a master key.
- `buttonText` (optional) - an `i18n` translated string that can be used as button text (default
text is "Set master password").
- `masterPasswordPolicyOptions` (optional) - used to display and enforce master password policy
requirements.
**Required**
- `inputPasswordFlow` - the parent component must provide the correct flow, which is used to
determine which form input elements will be displayed in the UI.
- `email` - the parent component must provide an email so that the `InputPasswordComponent` can
create a master key.
**Optional**
- `loading` - a boolean used to indicate that the parent component is performing some
long-running/async operation and that the form should be disabled until the operation is complete.
The primary button will also show a spinner if `loading` true.
- `masterPasswordPolicyOptions` - used to display and enforce master password policy requirements.
- `inlineButtons` - takes a boolean that determines if the button(s) should be displayed inline (as
opposed to full-width)
- `primaryButtonText` - takes a `Translation` object that can be used as button text
- `secondaryButtonText` - takes a `Translation` object that can be used as button text
## `@Output()`'s
- `onPasswordFormSubmit` - on form submit, emits a `PasswordInputResult` object
- `onSecondaryButtonClick` - on click, emits a notice that the secondary button has been clicked.
The parent component can listen for this event and take some custom action as needed (go back,
cancel, logout, etc.)
<br />
## Form Input Fields
The `InputPasswordComponent` allows a user to enter:
The `InputPasswordComponent` can handle up to 6 different form input fields, depending on the
`InputPasswordFlow` provided by the parent component.
1. Master password
2. Master password confirmation
3. Hint (optional)
4. Chooses whether to check for password breaches (checkbox)
**InputPasswordFlow.SetInitialPassword**
Validation ensures that the master password and confirmed master password are the same, and that the
master password and hint values are not the same.
- Input: New password
- Input: Confirm new password
- Input: Hint
- Checkbox: Check for breaches
**InputPasswordFlow.ChangePassword**
Includes everything above, plus:
- Input: Current password (as the first element in the UI)
**InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation**
Includes everything above, plus:
- Checkbox: Rotate account encryption key (as the last element in the UI)
<br />
## Validation
Validation ensures that:
- The current password and new password are NOT the same
- The new password and confirmed new password are the same
- The new password and password hint are NOT the same
<br />
@@ -57,19 +97,23 @@ When the form is submitted, the `InputPasswordComponent` does the following in o
```typescript
export interface PasswordInputResult {
masterKey: MasterKey;
masterKeyHash: string;
kdfConfig: PBKDF2KdfConfig;
newPassword: string;
hint: string;
kdfConfig: PBKDF2KdfConfig;
masterKey: MasterKey;
serverMasterKeyHash: string;
localMasterKeyHash: string;
currentPassword?: string; // included if the flow is ChangePassword or ChangePasswordWithOptionalUserKeyRotation
rotateUserKey?: boolean; // included if the flow is ChangePasswordWithOptionalUserKeyRotation
}
```
# Default Example
# Example - InputPasswordFlow.SetInitialPassword
<Story of={stories.Default} />
<Story of={stories.SetInitialPassword} />
<br />
# With Policy Requrements
# Example - With Policy Requrements
<Story of={stories.WithPolicy} />
<Story of={stories.WithPolicies} />

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { importProvidersFrom } from "@angular/core";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { action } from "@storybook/addon-actions";
@@ -18,7 +16,7 @@ import { KeyService } from "@bitwarden/key-management";
// eslint-disable-next-line import/no-restricted-paths, no-restricted-imports
import { PreloadedEnglishI18nModule } from "../../../../../apps/web/src/app/core/tests";
import { InputPasswordComponent } from "./input-password.component";
import { InputPasswordComponent, InputPasswordFlow } from "./input-password.component";
export default {
title: "Auth/Input Password",
@@ -62,7 +60,7 @@ export default {
provide: PasswordStrengthServiceAbstraction,
useValue: {
getPasswordStrength: (password) => {
let score = 0;
let score: number | null = null;
if (password.length === 0) {
score = null;
} else if (password.length <= 4) {
@@ -88,6 +86,12 @@ export default {
}),
],
args: {
InputPasswordFlow: {
SetInitialPassword: InputPasswordFlow.SetInitialPassword,
ChangePassword: InputPasswordFlow.ChangePassword,
ChangePasswordWithOptionalUserKeyRotation:
InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation,
},
masterPasswordPolicyOptions: {
minComplexity: 4,
minLength: 14,
@@ -96,25 +100,77 @@ export default {
requireNumbers: true,
requireSpecial: true,
} as MasterPasswordPolicyOptions,
argTypes: {
onSecondaryButtonClick: { action: "onSecondaryButtonClick" },
},
},
} as Meta;
type Story = StoryObj<InputPasswordComponent>;
export const Default: Story = {
export const SetInitialPassword: Story = {
render: (args) => ({
props: args,
template: `
<auth-input-password></auth-input-password>
<auth-input-password [inputPasswordFlow]="InputPasswordFlow.SetInitialPassword"></auth-input-password>
`,
}),
};
export const WithPolicy: Story = {
export const ChangePassword: Story = {
render: (args) => ({
props: args,
template: `
<auth-input-password [masterPasswordPolicyOptions]="masterPasswordPolicyOptions"></auth-input-password>
<auth-input-password [inputPasswordFlow]="InputPasswordFlow.ChangePassword"></auth-input-password>
`,
}),
};
export const ChangePasswordWithOptionalUserKeyRotation: Story = {
render: (args) => ({
props: args,
template: `
<auth-input-password
[inputPasswordFlow]="InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation"
></auth-input-password>
`,
}),
};
export const WithPolicies: Story = {
render: (args) => ({
props: args,
template: `
<auth-input-password
[inputPasswordFlow]="InputPasswordFlow.SetInitialPassword"
[masterPasswordPolicyOptions]="masterPasswordPolicyOptions"
></auth-input-password>
`,
}),
};
export const SecondaryButton: Story = {
render: (args) => ({
props: args,
template: `
<auth-input-password
[inputPasswordFlow]="InputPasswordFlow.SetInitialPassword"
[secondaryButtonText]="{ key: 'cancel' }"
(onSecondaryButtonClick)="onSecondaryButtonClick()"
></auth-input-password>
`,
}),
};
export const SecondaryButtonWithPlaceHolderText: Story = {
render: (args) => ({
props: args,
template: `
<auth-input-password
[inputPasswordFlow]="InputPasswordFlow.SetInitialPassword"
[secondaryButtonText]="{ key: 'backTo', placeholders: ['homepage'] }"
(onSecondaryButtonClick)="onSecondaryButtonClick()"
></auth-input-password>
`,
}),
};
@@ -123,7 +179,24 @@ export const InlineButton: Story = {
render: (args) => ({
props: args,
template: `
<auth-input-password [btnBlock]="false" [masterPasswordPolicyOptions]="masterPasswordPolicyOptions"></auth-input-password>
<auth-input-password
[inputPasswordFlow]="InputPasswordFlow.SetInitialPassword"
[inlineButtons]="true"
></auth-input-password>
`,
}),
};
export const InlineButtons: Story = {
render: (args) => ({
props: args,
template: `
<auth-input-password
[inputPasswordFlow]="InputPasswordFlow.SetInitialPassword"
[secondaryButtonText]="{ key: 'cancel' }"
[inlineButtons]="true"
(onSecondaryButtonClick)="onSecondaryButtonClick()"
></auth-input-password>
`,
}),
};

View File

@@ -2,10 +2,12 @@ import { MasterKey } from "@bitwarden/common/types/key";
import { PBKDF2KdfConfig } from "@bitwarden/key-management";
export interface PasswordInputResult {
masterKey: MasterKey;
masterKeyHash: string;
localMasterKeyHash: string;
kdfConfig: PBKDF2KdfConfig;
newPassword: string;
hint: string;
password: string;
kdfConfig: PBKDF2KdfConfig;
masterKey: MasterKey;
serverMasterKeyHash: string;
localMasterKeyHash: string;
currentPassword?: string;
rotateUserKey?: boolean;
}

View File

@@ -60,11 +60,11 @@ describe("DefaultRegistrationFinishService", () => {
masterKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as MasterKey;
passwordInputResult = {
masterKey: masterKey,
masterKeyHash: "masterKeyHash",
serverMasterKeyHash: "serverMasterKeyHash",
localMasterKeyHash: "localMasterKeyHash",
kdfConfig: DEFAULT_KDF_CONFIG,
hint: "hint",
password: "password",
newPassword: "password",
};
userKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey;
@@ -101,7 +101,7 @@ describe("DefaultRegistrationFinishService", () => {
expect.objectContaining({
email,
emailVerificationToken: emailVerificationToken,
masterPasswordHash: passwordInputResult.masterKeyHash,
masterPasswordHash: passwordInputResult.serverMasterKeyHash,
masterPasswordHint: passwordInputResult.hint,
userSymmetricKey: userKeyEncString.encryptedString,
userAsymmetricKeys: {

View File

@@ -81,7 +81,7 @@ export class DefaultRegistrationFinishService implements RegistrationFinishServi
const registerFinishRequest = new RegisterFinishRequest(
email,
passwordInputResult.masterKeyHash,
passwordInputResult.serverMasterKeyHash,
passwordInputResult.hint,
encryptedUserKey,
userAsymmetricKeysRequest,

View File

@@ -5,8 +5,9 @@
<auth-input-password
*ngIf="!loading"
[email]="email"
[inputPasswordFlow]="InputPasswordFlow.SetInitialPassword"
[masterPasswordPolicyOptions]="masterPasswordPolicyOptions"
[loading]="submitting"
[primaryButtonText]="{ key: 'createAccount' }"
(onPasswordFormSubmit)="handlePasswordFormSubmit($event)"
[buttonText]="'createAccount' | i18n"
></auth-input-password>

View File

@@ -22,7 +22,10 @@ import {
PasswordLoginCredentials,
} from "../../../common";
import { AnonLayoutWrapperDataService } from "../../anon-layout/anon-layout-wrapper-data.service";
import { InputPasswordComponent } from "../../input-password/input-password.component";
import {
InputPasswordComponent,
InputPasswordFlow,
} from "../../input-password/input-password.component";
import { PasswordInputResult } from "../../input-password/password-input-result";
import { RegistrationFinishService } from "./registration-finish.service";
@@ -36,6 +39,8 @@ import { RegistrationFinishService } from "./registration-finish.service";
export class RegistrationFinishComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
InputPasswordFlow = InputPasswordFlow;
loading = true;
submitting = false;
email: string;
@@ -176,7 +181,7 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy {
try {
const credentials = new PasswordLoginCredentials(
this.email,
passwordInputResult.password,
passwordInputResult.newPassword,
captchaBypassToken,
null,
);

View File

@@ -112,11 +112,11 @@ describe("DefaultSetPasswordJitService", () => {
passwordInputResult = {
masterKey: masterKey,
masterKeyHash: "masterKeyHash",
serverMasterKeyHash: "serverMasterKeyHash",
localMasterKeyHash: "localMasterKeyHash",
hint: "hint",
kdfConfig: DEFAULT_KDF_CONFIG,
password: "password",
newPassword: "password",
};
credentials = {
@@ -131,7 +131,7 @@ describe("DefaultSetPasswordJitService", () => {
userDecryptionOptionsService.userDecryptionOptions$ = userDecryptionOptionsSubject;
setPasswordRequest = new SetPasswordRequest(
passwordInputResult.masterKeyHash,
passwordInputResult.serverMasterKeyHash,
protectedUserKey[1].encryptedString,
passwordInputResult.hint,
orgSsoIdentifier,

View File

@@ -44,7 +44,7 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService {
async setPassword(credentials: SetPasswordCredentials): Promise<void> {
const {
masterKey,
masterKeyHash,
serverMasterKeyHash,
localMasterKeyHash,
hint,
kdfConfig,
@@ -70,7 +70,7 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService {
const [keyPair, keysRequest] = await this.makeKeyPairAndRequest(protectedUserKey);
const request = new SetPasswordRequest(
masterKeyHash,
serverMasterKeyHash,
protectedUserKey[1].encryptedString,
hint,
orgSsoIdentifier,
@@ -92,7 +92,7 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService {
await this.masterPasswordService.setMasterKeyHash(localMasterKeyHash, userId);
if (resetPasswordAutoEnroll) {
await this.handleResetPasswordAutoEnroll(masterKeyHash, orgId, userId);
await this.handleResetPasswordAutoEnroll(serverMasterKeyHash, orgId, userId);
}
}

View File

@@ -13,7 +13,8 @@
</app-callout>
<auth-input-password
[buttonText]="'createAccount' | i18n"
[inputPasswordFlow]="InputPasswordFlow.SetInitialPassword"
[primaryButtonText]="{ key: 'createAccount' }"
[email]="email"
[loading]="submitting"
[masterPasswordPolicyOptions]="masterPasswordPolicyOptions"

View File

@@ -18,7 +18,10 @@ import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.serv
// FIXME: remove `src` and fix import
// eslint-disable-next-line no-restricted-imports
import { ToastService } from "../../../../components/src/toast";
import { InputPasswordComponent } from "../input-password/input-password.component";
import {
InputPasswordComponent,
InputPasswordFlow,
} from "../input-password/input-password.component";
import { PasswordInputResult } from "../input-password/password-input-result";
import {
@@ -33,6 +36,7 @@ import {
imports: [CommonModule, InputPasswordComponent, JslibModule],
})
export class SetPasswordJitComponent implements OnInit {
protected InputPasswordFlow = InputPasswordFlow;
protected email: string;
protected masterPasswordPolicyOptions: MasterPasswordPolicyOptions;
protected orgId: string;

View File

@@ -6,7 +6,7 @@ import { PBKDF2KdfConfig } from "@bitwarden/key-management";
export interface SetPasswordCredentials {
masterKey: MasterKey;
masterKeyHash: string;
serverMasterKeyHash: string;
localMasterKeyHash: string;
kdfConfig: PBKDF2KdfConfig;
hint: string;