Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A new password change wizard #38728

Merged
merged 17 commits into from
Mar 6, 2024
Merged

A new password change wizard #38728

merged 17 commits into from
Mar 6, 2024

Conversation

bl-nero
Copy link
Contributor

@bl-nero bl-nero commented Feb 28, 2024

Closes #36233
Closes #36232
Partially implements RFD 0159.
Figma designs

What's this?

This change turns the old password change dialog into a two-step wizard. The first step is picking the identity verification method (skipped if no devices available or MFA not turned on in the cluster):

Screenshot 2024-02-28 at 15 28 18

The second step is the actual password change; the data required depends on the verification method selected. For passkeys, we take advantage of strong identity verification and don't ask for the current password. This is the most important feature, and the actual fix for #36233. It allows passwordless users to set a new password easily. See RFD 0159 for detailed technical design.

Screenshot 2024-02-28 at 15 28 34

For regular MFA devices or no verification at all, the current password is required.

Screenshot 2024-02-28 at 15 28 48

For authenticator apps, we additionally require an OTP code.

Screenshot 2024-02-28 at 15 29 03

To reviewers

This is quite a big change, so let me try to make it easier for you and provide some guidelines:

  • The backend changes add a specific challenge scope for setting passwords and allow setting a new password without giving an old one, provided that the user used a strong identity verification using a passwordless authentication device.
  • On the frontend side, the ChangePasswordWizard.tsx is the main chunk of new code, along with the accompanying test and story.
  • The backend interface got changed in auth.ts.
  • The rest of frontend changes is mostly wiring the new dialog with existing account settings view, and fixing types here and there. Most notably, the way we tell difference between passkeys and other devices was changed in MfaDevice. This was necessary to compute the list of identity verification options.
  • There's an upcoming tiny fix to the Enterprise part to address changes to MfaDevice type.

Changelog: Added a new password change wizard.
Changelog: Allowed changing password without providing the current one if the user verifies their identity using a passwordless key (see RFD 0159).

Copy link

The PR changelog entry failed validation: Changelog entry not found in the PR body. Please add a "no-changelog" label to the PR, or changelog lines starting with changelog: followed by the changelog entries for the PR.

@zmb3
Copy link
Collaborator

zmb3 commented Feb 28, 2024

I was able to successfully add a password to a passwordless user 👍

My only feedback is that I was caught by surprise by this screen:
Screenshot 2024-02-28 at 1 50 05 PM

My default reaction was to click the purple next button (without scanning the code, I didn't really read the instructions well). Then I go the next screen and it asked for a code and realized I had to go back and scan the QR code.

I feel like most TOTP workflows I've used show the QR code on the same screen where they ask you to enter the code, but I'm fine either way if this was the intent.

Copy link
Contributor

@gzdunek gzdunek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code looks really solid 👍
I tested it and didn't find any issues.

I'd only recommend requesting a review for the backend changes from someone more familiar with the user auth (perhaps Alan?).

Comment on lines 101 to 133
export function createReauthOptions(
auth2faType: Auth2faType,
passwordlessEnabled: boolean,
devices: MfaDevice[]
) {
const options: ReauthenticationOption[] = [];

if (passwordlessEnabled) {
options.push({ value: 'passwordless', label: 'Passkey' });
}

const mfaEnabled = auth2faType === 'on' || auth2faType === 'optional';

if (auth2faType === 'webauthn' || mfaEnabled) {
options.push({ value: 'mfaDevice', label: 'MFA Device' });
}

if (auth2faType === 'otp' || mfaEnabled) {
options.push({ value: 'otp', label: 'Authenticator App' });
}

const allowedMethods = {};
for (const d of devices) {
allowedMethods[reauthMethodForDevice(d)] = true;
}

return options.filter(o => o.value in allowedMethods);
}

function reauthMethodForDevice(d: MfaDevice): ReauthenticationMethod {
if (d.usage === 'passwordless') return 'passwordless';
return d.type === 'totp' ? 'otp' : 'mfaDevice';
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of merging these functions into a single function? To me it was quite hard at first to understand how the two sets of options interact with each other.

So for example check for passwordless would look like this:

  if (passwordlessEnabled && devices.find(d => d.usage === 'passwordless')) {
    options.push({ value: 'passwordless', label: 'Passkey' });
  }

It's more verbose, but also more explicit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to avoid using find(), since technically, this is a collection of unknown size. But I agree this fragment is a bit cryptic, I'll change it.

<StepSlider
flows={wizardFlows}
currFlow={
reauthRequired ? 'withReauthentication' : 'withoutReauthentication'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

I think we should hide step 1 of 1, it doesn't provide any information.

@bl-nero
Copy link
Contributor Author

bl-nero commented Feb 29, 2024

I feel like most TOTP workflows I've used show the QR code on the same screen where they ask you to enter the code, but I'm fine either way if this was the intent.

@zmb3 That screen is actually covered by the previous PRs (the ones that added AddAuthDeviceWizard), but since you brought it up: yes, I agree that it may be confusing, so let's see if our users have any strong opinions about it. The problem with OTPs, unfortunately, is that they are short-living by nature (30-60 seconds, to be precise). To make sure that they don't expire by the time they are consumed, they need to be in the last step (note that in the password flow, it's deliberately the very last field the user fills). Since the change that the UX team wanted to see is to set the auth method name after the auth method was configured, that's what we ended up with. Note that it can be changed, but this will require more work (either by implementing a more complex flow on the backend that includes renaming auth devices, or by cramming all authenticator device configuration into one step and then extending the StepSlider to support changing flows in the middle). That's why I'd rather release it as it is and look if we get any feedback.

If you'd like to discuss MFA/passkeys further, I propose to move the discussion somewhere else, because it's outside this PR's scope.

Copy link
Contributor

@codingllama codingllama left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some initial high-level comments:

  1. +1 for the new flow, looks good!

I feel like most TOTP workflows I've used show the QR code on the same screen where they ask you to enter the code, but I'm fine either way if this was the intent.

+1 to this comment by Zac, although I would suggest a follow up PR for it (if addressed at all).

  1. The PR is a bit on the large side. I would split server-side and frontend changes into different PRs - that ought to make it much smaller and you can get "specialized" reviewers from each side.

I'd say keep this one for the FE changes, due to current reviewers and approvals, and "break out" the server-side PR from it.

@bl-nero
Copy link
Contributor Author

bl-nero commented Feb 29, 2024

3. The PR is a bit on the large side. I would split server-side and frontend changes into different PRs

@codingllama Unfortunately, the both are tightly coupled, because to change the server side, I'd also have to change the frontend interface, make the old UI use the new logic, and so on - I decided to take my chances early, because I counted on it not being as big. Well, it ended up being big. If you insist on merging these separately, I can make an attempt to split out the backend code, perhaps making the old frontend compatible will not be as hard as I thought.

@codingllama
Copy link
Contributor

You can branch one PR from the other if they have a hard dependency.

@bl-nero bl-nero changed the base branch from master to bl-nero/change-password-backend February 29, 2024 18:59
@bl-nero
Copy link
Contributor Author

bl-nero commented Feb 29, 2024

I've extracted the backend part to a separate PR.

Copy link
Contributor

@kimlisa kimlisa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, i just have nits that aren't blockers:

  1. the sliding animation looks weird/squished because there is no padding on the component's meant for sliding between transitions. simple fix is to remove the padding on the dialog, and define padding for each component meant for sliding.

probably not the best way to do things, but it's the fastest without tinkering the stepslider (if we have time to tinker, we can maybe pass in a padding prop to stepslider so stepslider can add the padding to each component, but it's going to require adding this padding to the height calculations between transition)

  1. for the account setting, for each dialog, the buttons should be bigger as depicted on figma? the same size as login form buttons but 🤷‍♀️

@bl-nero
Copy link
Contributor Author

bl-nero commented Mar 4, 2024

@kimlisa Thanks for the feedback! Yes, the slider looks much better now. As for the buttons, I deliberately didn't tinker with them, since we'd like to take care of the buttons globally later this quarter.

@bl-nero
Copy link
Contributor Author

bl-nero commented Mar 5, 2024

Merged with backend changes and retested manually:

  • Changing password with a passwordless MFA
  • Changing password with a YubiKey
  • Changing password with OTP device
  • Changing password without confirmation (user doesn't have any auth device).

All with both success and failure cases.

Base automatically changed from bl-nero/change-password-backend to master March 5, 2024 14:10
@bl-nero
Copy link
Contributor Author

bl-nero commented Mar 6, 2024

Merged in the backend changes, retested manually:

  • Changing password (both in success and failure cases):
    • Changing password with a passwordless MFA
    • Changing password with a YubiKey
    • Changing password with OTP device
    • Changing password without confirmation (user doesn't have any auth device).
  • Signing in using the changed password and various MFA options.

@bl-nero bl-nero enabled auto-merge March 6, 2024 13:04
@bl-nero bl-nero added this pull request to the merge queue Mar 6, 2024
Merged via the queue into master with commit 924ac92 Mar 6, 2024
33 checks passed
@bl-nero bl-nero deleted the bl-nero/change-password branch March 6, 2024 13:45
@public-teleport-github-review-bot

@bl-nero See the table below for backport results.

Branch Result
branch/v15 Failed

return {
id,
name,
description,
registeredDate: new Date(addedAt),
lastUsedDate: new Date(lastUsed),
residentKey,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: dropping this field broke e/ builds.

Example: https://github.com/gravitational/teleport.e/actions/runs/8173863278/job/22347267175

yarn run v1.22.21
$ NODE_OPTIONS='--max-old-space-size=4096' tsc
e/web/teleport/src/Recovery/RecoveryFlow/RecoveryFlow.test.tsx(7[5](https://github.com/gravitational/teleport.e/actions/runs/8173863278/job/22347267175#step:8:6),11): error TS2353: Object literal may only specify known properties, and 'residentKey' does not exist in type 'MfaDevice'.
e/web/teleport/src/Recovery/RecoveryFlow/RecoveryFlow.test.tsx(83,11): error TS2353: Object literal may only specify known properties, and 'residentKey' does not exist in type 'MfaDevice'.
error Command failed with exit code 2.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Error: Process completed with exit code 2.

@bl-nero

@public-teleport-github-review-bot

@bl-nero See the table below for backport results.

Branch Result
branch/v15 Failed

@public-teleport-github-review-bot

@bl-nero See the table below for backport results.

Branch Result
branch/v15 Failed

bl-nero added a commit that referenced this pull request Mar 8, 2024
* A new password change wizard

* Review and lint

* Allow password change with identity verificaiton

* Review

* Review

* Revert renaming the webauthnAssertionResponse field

* review

* Fix a bug, add a test

* Fix a broken test

* Add license
github-merge-queue bot pushed a commit that referenced this pull request Mar 12, 2024
* A new password change wizard

* Review and lint

* Allow password change with identity verificaiton

* Review

* Review

* Revert renaming the webauthnAssertionResponse field

* review

* Fix a bug, add a test

* Fix a broken test

* Add license
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Change password with passwordless identification New dialog for identity verification
5 participants