import { Meta, Story } from "@storybook/addon-docs/blocks"; import * as stories from "./input-password.stories.ts"; # InputPassword Component The `InputPasswordComponent` allows a user to enter a new master password for the purpose of setting an initial password or changing an existing password. Specifically, it does the following: 1. Displays form fields in the UI 2. Validates form fields 3. Emits values to the parent component The `InputPasswordComponent` is central to our set/change password flows, allowing us to keep our form UI and validation logic consistent. As such, it is intended for re-use in different set/change password scenarios throughout the Bitwarden application. It is mostly presentational and simply emits values rather than acting on them itself. It is the job of the parent component to act on those values as needed.
## Table of Contents - [@Inputs](#inputs) - [@Outputs](#outputs) - [The InputPasswordFlow](#the-inputpasswordflow) - [Use Cases](#use-cases) - [HTML - Form Fields](#html---form-fields) - [Difference between SetInitialPasswordAccountRegistration and SetInitialPasswordAuthedUser](#difference-between-setinitialpasswordaccountregistration-and-setinitialpasswordautheduser) - [Validation](#validation) - [Submit Logic](#submit-logic) - [Submitting From a Parent Dialog Component](#submitting-from-a-parent-dialog-component) - [Example](#example)
## `@Input()`'s **Required** - `flow` - the parent component must provide an `InputPasswordFlow`, which is used to determine which form input elements will be displayed in the UI and which values will be emitted. [Click here](#the-inputpasswordflow) to learn more about the different `InputPasswordFlow` options. **Optional (sometimes)** These `@Inputs` are optional on some flows, but required on others. Therefore these `@Inputs` are not marked as `{ required: true }`, but there _is_ component logic that ensures (requires) that the `email` and/or `userId` is present in certain flows, while not present in other flows. - `email` - allows the `InputPasswordComponent` to use the email as a salt (if needed) - `userId` - allows the `InputPasswordComponent` to do things like get the user's `kdfConfig`, verify that a current password is correct, and perform validation prior to user key rotation (if selected) on the parent **Optional** These `@Inputs` are truly 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` is 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 ([see more below](#submit-logic)). - `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.)
## The `InputPasswordFlow` The `InputPasswordFlow` is a crucial and required `@Input` that influences both the HTML and the logic of the component. It is important for the dev to understand when to use each flow. ### Use Cases **`SetInitialPasswordAccountRegistration`** Used in scenarios where we have no existing user, and thus NO active account `userId`: - Standard Account Registration - Email Invite Account Registration - Trial Initiation Account registration

**`SetInitialPasswordAuthedUser`** Used in scenarios where we do have an existing and authed user, and thus an active account `userId`: - A "just-in-time" (JIT) provisioned user joins a master password (MP) encryption org and must set their initial password - A "just-in-time" (JIT) provisioned user joins a trusted device encryption (TDE) org with the reset password permission ("manage account recovery") from the start, which requires them to have/set their initial password - A note on JIT provisioned user flows: - Even though a JIT provisioned user is a brand-new user who was “just” created, we consider them to be an “existing authed user” _from the perspective of the set-password flow_. This is because at the time they set their initial password, their account already exists in the database (before setting their password) and they have already authenticated via SSO. - The same is not true in the account registration flows above—that is, during account registration when a user reaches the `/finish-signup` or `/trial-initiation` page to set their initial password, their account does not yet exist in the database, and will only be created once they set an initial password. - An existing user in a TDE org logs in after an org admin upgraded the user to have the reset password persmission ("manage account recovery"), which now requires the user to have/set their initial password - An existing user logs in after their org admin offboarded the org from TDE, and the user must now have/set their initial password

**`ChangePassword`** Used in scenarios where we simply want to offer the user the ability to change their password: - User clicks an org email invite link and logs in with their password which does not meet the org's policy requirements - User logs in with password that does not meet the org's policy requirements - User logs in after their password was reset via Account Recovery (and now they must change their password)

**`ChangePasswordWithOptionalUserKeyRotation`** Used in scenarios where we want to offer users the additional option of rotating their user key: - Account Settings (Web) - change password screen Note that the user key rotation itself does not happen on the `InputPasswordComponent`, but rather on the parent component. The `InputPasswordComponent` simply emits a boolean value that indicates whether or not the user key should be rotated.

**`ChangePasswordDelegation`** Used in scenarios where one user changes the password for another user's account: - Emergency Access Takeover - Account Recovery

### HTML - Form Fields Click through the individual Stories in Storybook to see how the `InputPassswordFlow` determines which form field UI elements get displayed.
### Difference between `SetInitialPasswordAccountRegistration` and `SetInitialPasswordAuthedUser` These two flows are similar in that they display the same form fields, but we need to keep them separate for the following reasons: - `SetInitialPasswordAccountRegistration` involves scenarios where we have no existing user, and **thus NO active account `userId`**: - `SetInitialPasswordAuthedUser` involves scenarios where we do have an existing and authed user, and **thus an active account `userId`**: The presence or absence of an active account `userId` is important because it determines how we get the correct `kdfConfig`: - If there is no `userId` passed down from the parent, we default to `DEFAULT_KDF_CONFIG` - If there is a `userId` passed down from the parent, we get the `kdfConfig` from state using the `userId` That said, we cannot mark the `userId` as a required via `@Input({ required: true })` because `SetInitialPasswordAccountRegistration` flows will not have a `userId`. But we still want to require a `userId` in a `SetInitialPasswordAuthedUser` flow. Therefore the `InputPasswordComponent` has init logic that ensures the following: - If the passed down flow is `SetInitialPasswordAccountRegistration`, require that the parent **MUST NOT** have passed down a `userId` - If the passed down flow is `SetInitialPasswordAuthedUser` require that the parent must also have passed down a `userId` If either of these checks is not met, the component throws to alert the dev of a mistake.
## Validation Form validators ensure 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
## Submit Logic When the form is submitted, the `InputPasswordComponent` does the following in order: 1. Verifies inputs: - Checks that the current password is correct (if it was required in the flow) - Checks that the new password is not weak or found in any breaches (if the user selected the checkbox) - Checks that the new password adheres to any enforced master password policies that were optionally passed down by the parent 2. Emits values up to the parent (along with other values defined in `PasswordInputResult`) to be used by the parent as needed. ```typescript export interface PasswordInputResult { currentPassword?: string; newPassword: string; kdfConfig?: KdfConfig; salt?: MasterPasswordSalt; newPasswordHint?: string; rotateUserKey?: boolean; } ``` ## Submitting From a Parent Dialog Component Some of our set/change password flows use dialogs, such as Emergency Access Takeover and Account Recovery. These are covered by the `ChangePasswordDelegation` flow. Because dialogs have their own buttons, we don't want to display an additional Submit button in the `InputPasswordComponent` when embedded in a dialog. Therefore we do the following: - The `InputPasswordComponent` hides the button in the UI and exposes its `submit()` method as a public method. - The parent dialog component can then access this method via `@ViewChild()`. - When the user clicks the primary button on the parent dialog, we call the `submit()` method on the `InputPasswordComponent`. ```html
``` ```typescript // emergency-access-takeover-dialog.component.ts export class EmergencyAccessTakeoverDialogComponent implements OnInit { @ViewChild(InputPasswordComponent) inputPasswordComponent: InputPasswordComponent; // ... handlePrimaryButtonClick = async () => { await this.inputPasswordComponent.submit(); }; async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) { // ... run logic that handles the `PasswordInputResult` object emission } } ```
# Example **`InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation`**