mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
refactor(set-change-password): [Auth/PM-18458] Create new ChangePasswordComponent (#14226)
This PR creates a new ChangePasswordComponent. The first use-case of the ChangePasswordComponent is to place it inside a new PasswordSettingsComponent, which is accessed by going to Account Settings > Security. The ChangePasswordComponent will be updated in future PRs to handle more change password scenarios. Feature Flags: PM16117_ChangeExistingPasswordRefactor
This commit is contained in:
@@ -6,9 +6,10 @@ import * as stories from "./input-password.stories.ts";
|
||||
|
||||
# InputPassword Component
|
||||
|
||||
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 `InputPasswordComponent` allows a user to enter master password related credentials. On form
|
||||
submission, the component creates cryptographic properties (`newMasterKey`,
|
||||
`newServerMasterKeyHash`, etc.) and emits those properties to the parent (along with the other
|
||||
values defined 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
|
||||
@@ -16,12 +17,27 @@ the parent component to act on those values as needed.
|
||||
|
||||
<br />
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [@Inputs](#inputs)
|
||||
- [@Outputs](#outputs)
|
||||
- [The InputPasswordFlow](#the-inputpasswordflow)
|
||||
- [HTML - Form Fields](#html---form-fields)
|
||||
- [TypeScript - Credential Generation](#typescript---credential-generation)
|
||||
- [Difference between AccountRegistration and SetInitialPasswordAuthedUser](#difference-between-accountregistration-and-setinitialpasswordautheduser)
|
||||
- [Validation](#validation)
|
||||
- [Submit Logic](#submit-logic)
|
||||
- [Example](#example)
|
||||
|
||||
<br />
|
||||
|
||||
## `@Input()`'s
|
||||
|
||||
**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.
|
||||
- `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.
|
||||
- `email` - the parent component must provide an email so that the `InputPasswordComponent` can
|
||||
create a master key.
|
||||
|
||||
@@ -29,13 +45,15 @@ the parent component to act on those values as needed.
|
||||
|
||||
- `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.
|
||||
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
|
||||
@@ -45,25 +63,31 @@ the parent component to act on those values as needed.
|
||||
|
||||
<br />
|
||||
|
||||
## Form Input Fields
|
||||
## The `InputPasswordFlow`
|
||||
|
||||
The `InputPasswordComponent` can handle up to 6 different form input fields, depending on the
|
||||
`InputPasswordFlow` provided by the parent component.
|
||||
The `InputPasswordFlow` is a crucial and required `@Input` that influences both the HTML and the
|
||||
credential generation logic of the component.
|
||||
|
||||
**InputPasswordFlow.SetInitialPassword**
|
||||
<br />
|
||||
|
||||
### HTML - Form Fields
|
||||
|
||||
The `InputPasswordFlow` determines which form fields get displayed in the UI.
|
||||
|
||||
**`InputPasswordFlow.AccountRegistration`** and **`InputPasswordFlow.SetInitialPasswordAuthedUser`**
|
||||
|
||||
- Input: New password
|
||||
- Input: Confirm new password
|
||||
- Input: Hint
|
||||
- Checkbox: Check for breaches
|
||||
|
||||
**InputPasswordFlow.ChangePassword**
|
||||
**`InputPasswordFlow.ChangePassword`**
|
||||
|
||||
Includes everything above, plus:
|
||||
|
||||
- Input: Current password (as the first element in the UI)
|
||||
|
||||
**InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation**
|
||||
**`InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation`**
|
||||
|
||||
Includes everything above, plus:
|
||||
|
||||
@@ -71,49 +95,122 @@ Includes everything above, plus:
|
||||
|
||||
<br />
|
||||
|
||||
### TypeScript - Credential Generation
|
||||
|
||||
- The `AccountRegistration` and `SetInitialPasswordAuthedUser` 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`).
|
||||
- The `ChangePassword` and `ChangePasswordWithOptionalUserKeyRotation` 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 />
|
||||
|
||||
### Difference between `AccountRegistration` 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:
|
||||
|
||||
- `AccountRegistration` involves 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 />
|
||||
|
||||
- `SetInitialPasswordAuthedUser` involves 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
|
||||
|
||||
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
|
||||
`AccountRegistration` 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 `AccountRegistration`, 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
|
||||
|
||||
Validation ensures that:
|
||||
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
|
||||
|
||||
Additional submit logic validation ensures that:
|
||||
|
||||
- The new password adheres to any enforced master password policy options (that were passed down
|
||||
from the parent)
|
||||
|
||||
<br />
|
||||
|
||||
## On Submit
|
||||
## Submit Logic
|
||||
|
||||
When the form is submitted, the `InputPasswordComponent` does the following in order:
|
||||
|
||||
1. If the user selected the checkbox to check for password breaches, they will recieve a popup
|
||||
dialog if their entered password is found in a breach. The popup will give them the option to
|
||||
continue with the password or to back out and choose a different password.
|
||||
2. If there is a master password policy being enforced by an org, it will check to make sure the
|
||||
entered master password meets the policy requirements.
|
||||
3. The component will use the password, email, and default kdfConfig to create a master key and
|
||||
master key hash.
|
||||
4. The component will emit the following values (defined in the `PasswordInputResult` interface) to
|
||||
be used by the parent component as needed:
|
||||
1. Verifies inputs:
|
||||
- Checks that the current password is correct (if it was required in the flow)
|
||||
- Checks if the new password is found in a breach and warns the user if so (if the user selected
|
||||
the checkbox)
|
||||
- Checks that the new password meets any master password policy requirements enforced by an org
|
||||
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 {
|
||||
// Properties starting with "current..." are included if the flow is ChangePassword or ChangePasswordWithOptionalUserKeyRotation
|
||||
currentPassword?: string;
|
||||
currentMasterKey?: MasterKey;
|
||||
currentServerMasterKeyHash?: string;
|
||||
currentLocalMasterKeyHash?: string;
|
||||
|
||||
newPassword: string;
|
||||
hint: string;
|
||||
kdfConfig: PBKDF2KdfConfig;
|
||||
masterKey: MasterKey;
|
||||
serverMasterKeyHash: string;
|
||||
localMasterKeyHash: string;
|
||||
currentPassword?: string; // included if the flow is ChangePassword or ChangePasswordWithOptionalUserKeyRotation
|
||||
newPasswordHint: string;
|
||||
newMasterKey: MasterKey;
|
||||
newServerMasterKeyHash: string;
|
||||
newLocalMasterKeyHash: string;
|
||||
|
||||
kdfConfig: KdfConfig;
|
||||
rotateUserKey?: boolean; // included if the flow is ChangePasswordWithOptionalUserKeyRotation
|
||||
}
|
||||
```
|
||||
|
||||
# Example - InputPasswordFlow.SetInitialPassword
|
||||
# Example
|
||||
|
||||
<Story of={stories.SetInitialPassword} />
|
||||
**`InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation`**
|
||||
|
||||
<br />
|
||||
|
||||
# Example - With Policy Requrements
|
||||
|
||||
<Story of={stories.WithPolicies} />
|
||||
<Story of={stories.ChangePasswordWithOptionalUserKeyRotation} />
|
||||
|
||||
Reference in New Issue
Block a user