From 22678768605b5554c0b2b22043c508261d9a8b47 Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Mon, 7 Apr 2025 11:58:50 -0700 Subject: [PATCH] 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. --- apps/browser/src/_locales/en/messages.json | 3 + apps/desktop/src/locales/en/messages.json | 3 + .../web-registration-finish.service.spec.ts | 14 +- .../complete-trial-initiation.component.html | 6 +- .../complete-trial-initiation.component.ts | 10 +- apps/web/src/locales/en/messages.json | 3 + .../input-password.component.html | 76 +++++++-- .../input-password.component.ts | 156 ++++++++++++++---- .../angular/input-password/input-password.mdx | 90 +++++++--- .../input-password/input-password.stories.ts | 91 +++++++++- .../input-password/password-input-result.ts | 12 +- ...efault-registration-finish.service.spec.ts | 6 +- .../default-registration-finish.service.ts | 2 +- .../registration-finish.component.html | 3 +- .../registration-finish.component.ts | 9 +- .../default-set-password-jit.service.spec.ts | 6 +- .../default-set-password-jit.service.ts | 6 +- .../set-password-jit.component.html | 3 +- .../set-password-jit.component.ts | 6 +- .../set-password-jit.service.abstraction.ts | 2 +- 20 files changed, 394 insertions(+), 113 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 54024b83f9..586e7e1f2c 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -2297,6 +2297,9 @@ "privacyPolicy": { "message": "Privacy Policy" }, + "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { + "message": "Your new password cannot be the same as your current password." + }, "hintEqualsPassword": { "message": "Your password hint cannot be the same as your password." }, diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index f1c6378079..8850cbe5a3 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -2072,6 +2072,9 @@ "personalOwnershipSubmitError": { "message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." }, + "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { + "message": "Your new password cannot be the same as your current password." + }, "hintEqualsPassword": { "message": "Your password hint cannot be the same as your password." }, diff --git a/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts b/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts index 48b74dc5e2..aa02e28b3b 100644 --- a/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts +++ b/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts @@ -187,11 +187,11 @@ describe("WebRegistrationFinishService", () => { 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: "newPassword", }; userKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey; @@ -239,7 +239,7 @@ describe("WebRegistrationFinishService", () => { expect.objectContaining({ email, emailVerificationToken: emailVerificationToken, - masterPasswordHash: passwordInputResult.masterKeyHash, + masterPasswordHash: passwordInputResult.serverMasterKeyHash, masterPasswordHint: passwordInputResult.hint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { @@ -277,7 +277,7 @@ describe("WebRegistrationFinishService", () => { expect.objectContaining({ email, emailVerificationToken: undefined, - masterPasswordHash: passwordInputResult.masterKeyHash, + masterPasswordHash: passwordInputResult.serverMasterKeyHash, masterPasswordHint: passwordInputResult.hint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { @@ -320,7 +320,7 @@ describe("WebRegistrationFinishService", () => { expect.objectContaining({ email, emailVerificationToken: undefined, - masterPasswordHash: passwordInputResult.masterKeyHash, + masterPasswordHash: passwordInputResult.serverMasterKeyHash, masterPasswordHint: passwordInputResult.hint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { @@ -365,7 +365,7 @@ describe("WebRegistrationFinishService", () => { expect.objectContaining({ email, emailVerificationToken: undefined, - masterPasswordHash: passwordInputResult.masterKeyHash, + masterPasswordHash: passwordInputResult.serverMasterKeyHash, masterPasswordHint: passwordInputResult.hint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { @@ -412,7 +412,7 @@ describe("WebRegistrationFinishService", () => { expect.objectContaining({ email, emailVerificationToken: undefined, - masterPasswordHash: passwordInputResult.masterKeyHash, + masterPasswordHash: passwordInputResult.serverMasterKeyHash, masterPasswordHint: passwordInputResult.hint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { diff --git a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html index 416d400426..8118b1e512 100644 --- a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html +++ b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html @@ -1,19 +1,21 @@
diff --git a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts index 27ce4dc9f5..e2a4149a58 100644 --- a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts +++ b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts @@ -6,7 +6,11 @@ import { FormBuilder, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { firstValueFrom, Subject, switchMap, takeUntil } from "rxjs"; -import { PasswordInputResult, RegistrationFinishService } from "@bitwarden/auth/angular"; +import { + InputPasswordFlow, + PasswordInputResult, + RegistrationFinishService, +} from "@bitwarden/auth/angular"; import { LoginStrategyServiceAbstraction, PasswordLoginCredentials } from "@bitwarden/auth/common"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; @@ -47,6 +51,8 @@ export type InitiationPath = export class CompleteTrialInitiationComponent implements OnInit, OnDestroy { @ViewChild("stepper", { static: false }) verticalStepper: VerticalStepperComponent; + InputPasswordFlow = InputPasswordFlow; + /** Password Manager or Secrets Manager */ product: ProductType; /** The tier of product being subscribed to */ @@ -363,7 +369,7 @@ export class CompleteTrialInitiationComponent implements OnInit, OnDestroy { return; } - await this.logIn(passwordInputResult.password, captchaToken); + await this.logIn(passwordInputResult.newPassword, captchaToken); this.submitting = false; diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 6c730338fe..3300f73eb3 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -5707,6 +5707,9 @@ "webAuthnSuccess": { "message": "WebAuthn verified successfully! You may close this tab." }, + "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { + "message": "Your new password cannot be the same as your current password." + }, "hintEqualsPassword": { "message": "Your password hint cannot be the same as your password." }, diff --git a/libs/auth/src/angular/input-password/input-password.component.html b/libs/auth/src/angular/input-password/input-password.component.html index 114d9b8fb8..39995f9f44 100644 --- a/libs/auth/src/angular/input-password/input-password.component.html +++ b/libs/auth/src/angular/input-password/input-password.component.html @@ -4,14 +4,36 @@ [policy]="masterPasswordPolicyOptions" > + + {{ "currentMasterPass" | i18n }} + + + +
- {{ "masterPassword" | i18n }} + {{ "newMasterPass" | i18n }}
@@ -38,10 +60,10 @@ {{ "confirmMasterPassword" | i18n }} + + + {{ "rotateAccountEncKey" | i18n }} + + + + + + +
+ + + +
diff --git a/libs/auth/src/angular/input-password/input-password.component.ts b/libs/auth/src/angular/input-password/input-password.component.ts index c613cf5f53..2f8e5d5b01 100644 --- a/libs/auth/src/angular/input-password/input-password.component.ts +++ b/libs/auth/src/angular/input-password/input-password.component.ts @@ -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(); + @Output() onSecondaryButtonClick = new EventEmitter(); - @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).addControl( + "currentPassword", + this.formBuilder.control("", Validators.required), + ); + } + + if (this.inputPasswordFlow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation) { + // https://github.com/angular/angular/issues/48794 + (this.formGroup as FormGroup).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, ) ) { diff --git a/libs/auth/src/angular/input-password/input-password.mdx b/libs/auth/src/angular/input-password/input-password.mdx index 5110e2b313..c1edcc254a 100644 --- a/libs/auth/src/angular/input-password/input-password.mdx +++ b/libs/auth/src/angular/input-password/input-password.mdx @@ -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.)
## 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) + +
+ +## 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
@@ -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 - +
-# With Policy Requrements +# Example - With Policy Requrements - + diff --git a/libs/auth/src/angular/input-password/input-password.stories.ts b/libs/auth/src/angular/input-password/input-password.stories.ts index 41577328f8..beda97a2c1 100644 --- a/libs/auth/src/angular/input-password/input-password.stories.ts +++ b/libs/auth/src/angular/input-password/input-password.stories.ts @@ -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; -export const Default: Story = { +export const SetInitialPassword: Story = { render: (args) => ({ props: args, template: ` - + `, }), }; -export const WithPolicy: Story = { +export const ChangePassword: Story = { render: (args) => ({ props: args, template: ` - + + `, + }), +}; + +export const ChangePasswordWithOptionalUserKeyRotation: Story = { + render: (args) => ({ + props: args, + template: ` + + `, + }), +}; + +export const WithPolicies: Story = { + render: (args) => ({ + props: args, + template: ` + + `, + }), +}; + +export const SecondaryButton: Story = { + render: (args) => ({ + props: args, + template: ` + + `, + }), +}; + +export const SecondaryButtonWithPlaceHolderText: Story = { + render: (args) => ({ + props: args, + template: ` + `, }), }; @@ -123,7 +179,24 @@ export const InlineButton: Story = { render: (args) => ({ props: args, template: ` - + + `, + }), +}; + +export const InlineButtons: Story = { + render: (args) => ({ + props: args, + template: ` + `, }), }; diff --git a/libs/auth/src/angular/input-password/password-input-result.ts b/libs/auth/src/angular/input-password/password-input-result.ts index 07157aaf4c..c64225e2eb 100644 --- a/libs/auth/src/angular/input-password/password-input-result.ts +++ b/libs/auth/src/angular/input-password/password-input-result.ts @@ -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; } diff --git a/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.spec.ts b/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.spec.ts index 14600cebf1..c288f30d81 100644 --- a/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.spec.ts +++ b/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.spec.ts @@ -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: { diff --git a/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.ts b/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.ts index 72e2672013..7d844ce8cb 100644 --- a/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.ts +++ b/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.ts @@ -81,7 +81,7 @@ export class DefaultRegistrationFinishService implements RegistrationFinishServi const registerFinishRequest = new RegisterFinishRequest( email, - passwordInputResult.masterKeyHash, + passwordInputResult.serverMasterKeyHash, passwordInputResult.hint, encryptedUserKey, userAsymmetricKeysRequest, diff --git a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.html b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.html index e42ed91166..ccc502bd7b 100644 --- a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.html +++ b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.html @@ -5,8 +5,9 @@ diff --git a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts index c419e1f427..506b7475db 100644 --- a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts +++ b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts @@ -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(); + 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, ); diff --git a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts index bd62092a4b..cbcebd1452 100644 --- a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts +++ b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts @@ -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, diff --git a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts index 42d964f3de..174760aae2 100644 --- a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts +++ b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts @@ -44,7 +44,7 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService { async setPassword(credentials: SetPasswordCredentials): Promise { 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); } } diff --git a/libs/auth/src/angular/set-password-jit/set-password-jit.component.html b/libs/auth/src/angular/set-password-jit/set-password-jit.component.html index aa6a122993..3a4956ef7e 100644 --- a/libs/auth/src/angular/set-password-jit/set-password-jit.component.html +++ b/libs/auth/src/angular/set-password-jit/set-password-jit.component.html @@ -13,7 +13,8 @@