From eb84a9b06c21e166c9d950665a3c8a974fdbcf63 Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Fri, 25 Apr 2025 16:00:29 -0700 Subject: [PATCH] update storybook stories and docs --- .../angular/input-password/input-password.mdx | 133 +++++++++++++++--- .../input-password/input-password.stories.ts | 25 ++-- 2 files changed, 129 insertions(+), 29 deletions(-) diff --git a/libs/auth/src/angular/input-password/input-password.mdx b/libs/auth/src/angular/input-password/input-password.mdx index f12dd7de23b..39be25aeeaf 100644 --- a/libs/auth/src/angular/input-password/input-password.mdx +++ b/libs/auth/src/angular/input-password/input-password.mdx @@ -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 found 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.
+## 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) + +
+ ## `@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 +
+ ## `@Output()`'s - `onPasswordFormSubmit` - on form submit, emits a `PasswordInputResult` object @@ -45,25 +63,31 @@ the parent component to act on those values as needed.
-## 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** +
+ +### 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,17 +95,88 @@ Includes everything above, plus:
+### 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`). + +
+ +### 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 + +
+ +- `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. + +
+ ## 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) +
-## On Submit +## Submit Logic When the form is submitted, the `InputPasswordComponent` does the following in order: @@ -113,12 +208,8 @@ export interface PasswordInputResult { } ``` -# Example - InputPasswordFlow.SetInitialPassword +# Example - +**`InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation`** -
- -# Example - With Policy Requrements - - + diff --git a/libs/auth/src/angular/input-password/input-password.stories.ts b/libs/auth/src/angular/input-password/input-password.stories.ts index 6cd387fe2ac..c8f74a73ba0 100644 --- a/libs/auth/src/angular/input-password/input-password.stories.ts +++ b/libs/auth/src/angular/input-password/input-password.stories.ts @@ -154,7 +154,16 @@ export const AccountRegistration: Story = { render: (args) => ({ props: args, template: ` - + + `, + }), +}; + +export const SetInitialPasswordAuthedUser: Story = { + render: (args) => ({ + props: args, + template: ` + `, }), }; @@ -163,7 +172,7 @@ export const ChangePassword: Story = { render: (args) => ({ props: args, template: ` - + `, }), }; @@ -173,7 +182,7 @@ export const ChangePasswordWithOptionalUserKeyRotation: Story = { props: args, template: ` `, }), @@ -184,7 +193,7 @@ export const WithPolicies: Story = { props: args, template: ` `, @@ -196,7 +205,7 @@ export const SecondaryButton: Story = { props: args, template: ` @@ -209,7 +218,7 @@ export const SecondaryButtonWithPlaceHolderText: Story = { props: args, template: ` @@ -222,7 +231,7 @@ export const InlineButton: Story = { props: args, template: ` `, @@ -234,7 +243,7 @@ export const InlineButtons: Story = { props: args, template: `