import { Meta, Story } from "@storybook/addon-docs/blocks";

import * as stories from "./input-password.stories.ts";

<Meta of={stories} />

# 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.

<br />

## 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)

<br />

## `@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

<br />

## `@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.)

<br />

## 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<br /><br />

**`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&mdash;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<br /><br />

**`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)<br /><br />

**`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.<br /><br />

**`ChangePasswordDelegation`**

Used in scenarios where one user changes the password for another user's account:

- Emergency Access Takeover
- Account Recovery<br /><br />

### HTML - Form Fields

Click through the individual Stories in Storybook to see how the `InputPassswordFlow` determines
which form field UI elements get displayed.

<br />

### 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.

<br />

## 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

<br />

## 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
<!-- emergency-access-takeover-dialog.component.html -->

<bit-dialog dialogSize="large">
  <span bitDialogTitle><!-- ... --></span>

  <div bitDialogContent>
    <auth-input-password
      [flow]="inputPasswordFlow"
      [masterPasswordPolicyOptions]="masterPasswordPolicyOptions"
      (onPasswordFormSubmit)="handlePasswordFormSubmit($event)"
    ></auth-input-password>
  </div>

  <ng-container bitDialogFooter>
    <button type="button" bitButton buttonType="primary" (click)="handlePrimaryButtonClick()">
      {{ "save" | i18n }}
    </button>
    <button type="button" bitButton buttonType="secondary" bitDialogClose>
      {{ "cancel" | i18n }}
    </button>
  </ng-container>
</bit-dialog>
```

```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
  }
}
```

<br />

# Example

**`InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation`**

<Story of={stories.ChangePasswordWithOptionalUserKeyRotation} />
