1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00
Files
browser/libs/auth/src/angular/input-password/input-password.mdx

315 lines
12 KiB
Plaintext

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 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.
<br />
## 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)
<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 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
<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
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<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 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&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 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<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 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)<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 />
### 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`).<br /><br />
- **`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`).<br /><br />
- **`ChangePasswordDelegation`**
- This flow does not generate any credentials, but simply validates the new password and emits it
up to the parent.
<br />
### 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.
<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. 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
<!-- 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} />