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 master password related credentials. Specifically, it does the following: 1. Displays form fields in the UI 2. Validates form fields 3. Generates cryptographic properties based on the form inputs (e.g. `newMasterKey`, `newServerMasterKeyHash`, etc.) 4. Emits the generated properties 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) - [TypeScript - Credential Generation](#typescript---credential-generation) - [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 cryptographic keys will be created and emitted. [Click here](#the-inputpasswordflow) to learn more about the different `InputPasswordFlow` options. **Optional (sometimes)** These two `@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 generate a master key - `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 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 credential generation 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 a starting role that 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 the org admin upgraded the user to a role that now requires them 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 an 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.
### TypeScript - Credential Generation - **`SetInitialPasswordAccountRegistration`** and **`SetInitialPasswordAuthedUser`** - These flows involve a user setting their password for the first time. Therefore on submit the component will only generate new credentials (`newMasterKey`) and not current credentials (`currentMasterKey`).

- **`ChangePassword`** and **`ChangePasswordWithOptionalUserKeyRotation`** - These flows both require the user to enter a current password along with a new password. Therefore on submit the component will generate current credentials (`currentMasterKey`) along with new credentials (`newMasterKey`).

- **`ChangePasswordDelegation`** - This flow does not generate any credentials, but simply validates the new password and emits it up to the parent.
### Difference between `SetInitialPasswordAccountRegistration` and `SetInitialPasswordAuthedUser` These two flows are similar in that they display the same form fields and only generate new credentials, 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` prior to key generation: - 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. Uses the form inputs to create cryptographic properties (`newMasterKey`, `newServerMasterKeyHash`, etc.) 3. Emits those cryptographic properties 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; currentMasterKey?: MasterKey; currentServerMasterKeyHash?: string; currentLocalMasterKeyHash?: string; newPassword: string; newPasswordHint?: string; newMasterKey?: MasterKey; newServerMasterKeyHash?: string; newLocalMasterKeyHash?: string; kdfConfig?: KdfConfig; 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`**