mirror of
https://github.com/bitwarden/browser
synced 2026-02-08 12:40:26 +00:00
refactor(input-password-flows): [Auth/PM-27086] JIT MP org user flow - remove masterKey generation from InputPasswordComponent (#18006)
- Updates `InputPasswordComponent` to emit raw data instead of generating cryptographic properties (`newMasterKey`, `newServerMasterKeyHash`, `newLocalMasterKeyHash`). - This helps us in moving away from using the deprecated `makeMasterKey()` method in the component (which takes email as salt) as we seek to eventually separate the email from the salt. - Updates the `JIT_PROVISIONED_MP_ORG_USER` case of the switch to handle the flow when the `PM27086_UpdateAuthenticationApisForInputPassword` flag is on. Feature Flag: `PM27086_UpdateAuthenticationApisForInputPassword`
This commit is contained in:
@@ -10,7 +10,9 @@ import {
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||
@@ -209,6 +211,7 @@ export class InputPasswordComponent implements OnInit {
|
||||
constructor(
|
||||
private auditService: AuditService,
|
||||
private cipherService: CipherService,
|
||||
private configService: ConfigService,
|
||||
private dialogService: DialogService,
|
||||
private formBuilder: FormBuilder,
|
||||
private i18nService: I18nService,
|
||||
@@ -312,7 +315,7 @@ export class InputPasswordComponent implements OnInit {
|
||||
}
|
||||
|
||||
if (!this.email) {
|
||||
throw new Error("Email is required to create master key.");
|
||||
throw new Error("Email not found.");
|
||||
}
|
||||
|
||||
// 1. Determine kdfConfig
|
||||
@@ -320,13 +323,13 @@ export class InputPasswordComponent implements OnInit {
|
||||
this.kdfConfig = DEFAULT_KDF_CONFIG;
|
||||
} else {
|
||||
if (!this.userId) {
|
||||
throw new Error("userId not passed down");
|
||||
throw new Error("userId not found.");
|
||||
}
|
||||
this.kdfConfig = await firstValueFrom(this.kdfConfigService.getKdfConfig$(this.userId));
|
||||
}
|
||||
|
||||
if (this.kdfConfig == null) {
|
||||
throw new Error("KdfConfig is required to create master key.");
|
||||
throw new Error("KdfConfig not found.");
|
||||
}
|
||||
|
||||
const salt =
|
||||
@@ -334,7 +337,7 @@ export class InputPasswordComponent implements OnInit {
|
||||
? await firstValueFrom(this.masterPasswordService.saltForUser$(this.userId))
|
||||
: this.masterPasswordService.emailToSalt(this.email);
|
||||
if (salt == null) {
|
||||
throw new Error("Salt is required to create master key.");
|
||||
throw new Error("Salt not found.");
|
||||
}
|
||||
|
||||
// 2. Verify current password is correct (if necessary)
|
||||
@@ -361,6 +364,41 @@ export class InputPasswordComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
// When you unwind the flag in PM-28143, also remove the ConfigService if it is un-used.
|
||||
const newApisWithInputPasswordFlagEnabled = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.PM27086_UpdateAuthenticationApisForInputPassword,
|
||||
);
|
||||
|
||||
if (newApisWithInputPasswordFlagEnabled) {
|
||||
// 4. Build a PasswordInputResult object
|
||||
const passwordInputResult: PasswordInputResult = {
|
||||
newPassword,
|
||||
kdfConfig: this.kdfConfig,
|
||||
salt,
|
||||
newPasswordHint,
|
||||
newApisWithInputPasswordFlagEnabled, // To be removed in PM-28143
|
||||
};
|
||||
|
||||
if (
|
||||
this.flow === InputPasswordFlow.ChangePassword ||
|
||||
this.flow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation
|
||||
) {
|
||||
passwordInputResult.currentPassword = currentPassword;
|
||||
}
|
||||
|
||||
if (this.flow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation) {
|
||||
passwordInputResult.rotateUserKey = this.formGroup.controls.rotateUserKey?.value;
|
||||
}
|
||||
|
||||
// 5. Emit and return PasswordInputResult object
|
||||
this.onPasswordFormSubmit.emit(passwordInputResult);
|
||||
return passwordInputResult;
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
* The following code (within this `try`) to be removed in PM-28143
|
||||
*******************************************************************/
|
||||
|
||||
// 4. Create cryptographic keys and build a PasswordInputResult object
|
||||
const newMasterKey = await this.keyService.makeMasterKey(
|
||||
newPassword,
|
||||
|
||||
@@ -6,14 +6,12 @@ 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:
|
||||
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. Generates cryptographic properties based on the form inputs (e.g. `newMasterKey`,
|
||||
`newServerMasterKeyHash`, etc.)
|
||||
4. Emits the generated properties to the parent component
|
||||
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
|
||||
@@ -30,7 +28,6 @@ those values as needed.
|
||||
- [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)
|
||||
@@ -44,20 +41,20 @@ those values as needed.
|
||||
**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.
|
||||
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 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.
|
||||
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 generate a master key
|
||||
- `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 on
|
||||
the parent
|
||||
verify that a current password is correct, and perform validation prior to user key rotation (if
|
||||
selected) on the parent
|
||||
|
||||
**Optional**
|
||||
|
||||
@@ -87,8 +84,7 @@ These `@Inputs` are truly optional.
|
||||
## 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.
|
||||
logic of the component. It is important for the dev to understand when to use each flow.
|
||||
|
||||
### Use Cases
|
||||
|
||||
@@ -106,8 +102,9 @@ Used in scenarios where we do have an existing and authed user, and thus an acti
|
||||
|
||||
- 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 "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
|
||||
@@ -117,8 +114,9 @@ Used in scenarios where we do have an existing and authed user, and thus an acti
|
||||
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 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 />
|
||||
|
||||
@@ -126,7 +124,7 @@ Used in scenarios where we do have an existing and authed user, and thus an acti
|
||||
|
||||
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
|
||||
- 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
|
||||
@@ -156,26 +154,10 @@ 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:
|
||||
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`**:
|
||||
@@ -183,7 +165,7 @@ credentials, but we need to keep them separate for the following reasons:
|
||||
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:
|
||||
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
|
||||
@@ -223,25 +205,16 @@ When the form is submitted, the `InputPasswordComponent` does the following in o
|
||||
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.
|
||||
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;
|
||||
currentMasterKey?: MasterKey;
|
||||
currentServerMasterKeyHash?: string;
|
||||
currentLocalMasterKeyHash?: string;
|
||||
|
||||
newPassword: string;
|
||||
newPasswordHint?: string;
|
||||
newMasterKey?: MasterKey;
|
||||
newServerMasterKeyHash?: string;
|
||||
newLocalMasterKeyHash?: string;
|
||||
|
||||
kdfConfig?: KdfConfig;
|
||||
salt?: MasterPasswordSalt;
|
||||
newPasswordHint?: string;
|
||||
rotateUserKey?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -10,6 +10,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
|
||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||
@@ -59,6 +60,13 @@ export default {
|
||||
getAllDecrypted: () => Promise.resolve([]),
|
||||
},
|
||||
},
|
||||
// Can remove ConfigService from component and stories in PM-28143 (if it is no longer used)
|
||||
{
|
||||
provide: ConfigService,
|
||||
useValue: {
|
||||
getFeatureFlag: () => false, // default to false since flag does not effect UI
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: KdfConfigService,
|
||||
useValue: {
|
||||
|
||||
@@ -10,6 +10,20 @@ export interface PasswordInputResult {
|
||||
newPasswordHint?: string;
|
||||
rotateUserKey?: boolean;
|
||||
|
||||
/**
|
||||
* Temporary property that persists the flag state through the entire set/change password process.
|
||||
* This allows flows to consume this value instead of re-checking the flag state via ConfigService themselves.
|
||||
*
|
||||
* The ChangePasswordDelegation flows (Emergency Access Takeover and Account Recovery), however, only ever
|
||||
* require a raw newPassword from the InputPasswordComponent regardless of whether the flag is on or off.
|
||||
* Flagging for those 2 flows will be done via the ConfigService in their respective services.
|
||||
*
|
||||
* To be removed in PM-28143
|
||||
*/
|
||||
newApisWithInputPasswordFlagEnabled?: boolean;
|
||||
|
||||
// The deprecated properties below will be removed in PM-28143: https://bitwarden.atlassian.net/browse/PM-28143
|
||||
|
||||
/** @deprecated This low-level cryptographic state will be removed. It will be replaced by high level calls to masterpassword service, in the consumers of this interface. */
|
||||
currentMasterKey?: MasterKey;
|
||||
/** @deprecated */
|
||||
|
||||
Reference in New Issue
Block a user