-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
A new password change wizard #38728
Conversation
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 |
There was a problem hiding this 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?).
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'; | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zmb3 That screen is actually covered by the previous PRs (the ones that added 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. |
There was a problem hiding this 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 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).
- 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.
@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. |
You can branch one PR from the other if they have a hard dependency. |
I've extracted the backend part to a separate PR. |
There was a problem hiding this 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:
- 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)
- for the account setting, for each dialog, the buttons should be bigger as depicted on figma? the same size as login form buttons but 🤷♀️
@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. |
Merged with backend changes and retested manually:
All with both success and failure cases. |
Merged in the backend changes, retested manually:
|
return { | ||
id, | ||
name, | ||
description, | ||
registeredDate: new Date(addedAt), | ||
lastUsedDate: new Date(lastUsed), | ||
residentKey, |
There was a problem hiding this comment.
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.
* 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
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):
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.
For regular MFA devices or no verification at all, the current password is required.
For authenticator apps, we additionally require an OTP code.
To reviewers
This is quite a big change, so let me try to make it easier for you and provide some guidelines:
ChangePasswordWizard.tsx
is the main chunk of new code, along with the accompanying test and story.auth.ts
.MfaDevice
. This was necessary to compute the list of identity verification options.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).