From 3f558a4a7370d3e3656ab017c90a6d6c968875d3 Mon Sep 17 00:00:00 2001 From: Brian Joerger Date: Mon, 22 Apr 2024 11:00:35 -0700 Subject: [PATCH] Fix regenerating recovery codes webauthn scope (#40633) * Always provide MANAGE_DEVICES webauthn scope for creating privileged tokens. * Make challenge scope exclusive to onMfaResponse flow. * Fix UI lint. --- web/packages/teleport/src/Account/Account.tsx | 3 --- .../AddAuthDeviceWizard.tsx | 6 ++---- .../ReAuthenticate/ReAuthenticate.story.tsx | 3 --- .../ReAuthenticate/ReAuthenticate.tsx | 3 +-- .../ReAuthenticate/useReAuthenticate.ts | 21 +++++++++---------- .../teleport/src/services/auth/auth.ts | 15 +++++++------ 6 files changed, 22 insertions(+), 29 deletions(-) diff --git a/web/packages/teleport/src/Account/Account.tsx b/web/packages/teleport/src/Account/Account.tsx index 5c037224db27c..4293a904a2d4c 100644 --- a/web/packages/teleport/src/Account/Account.tsx +++ b/web/packages/teleport/src/Account/Account.tsx @@ -32,8 +32,6 @@ import { import ReAuthenticate from 'teleport/components/ReAuthenticate'; import { RemoveDialog } from 'teleport/components/MfaDeviceList'; -import { MfaChallengeScope } from 'teleport/services/auth/auth'; - import cfg from 'teleport/config'; import { DeviceUsage } from 'teleport/services/mfa'; @@ -232,7 +230,6 @@ export function Account({ onAuthenticated={setToken} onClose={hideReAuthenticate} actionText="registering a new device" - challengeScope={MfaChallengeScope.MANAGE_DEVICES} /> )} {EnterpriseComponent && ( diff --git a/web/packages/teleport/src/Account/ManageDevices/AddAuthDeviceWizard/AddAuthDeviceWizard.tsx b/web/packages/teleport/src/Account/ManageDevices/AddAuthDeviceWizard/AddAuthDeviceWizard.tsx index 7297675b911cf..36b09856c7251 100644 --- a/web/packages/teleport/src/Account/ManageDevices/AddAuthDeviceWizard/AddAuthDeviceWizard.tsx +++ b/web/packages/teleport/src/Account/ManageDevices/AddAuthDeviceWizard/AddAuthDeviceWizard.tsx @@ -40,7 +40,7 @@ import { PasskeyIcons } from 'teleport/components/PasskeyIcons'; import { DialogHeader } from 'teleport/Account/DialogHeader'; import useReAuthenticate from 'teleport/components/ReAuthenticate/useReAuthenticate'; -import auth, { MfaChallengeScope } from 'teleport/services/auth/auth'; +import auth from 'teleport/services/auth/auth'; import { DeviceUsage } from 'teleport/services/mfa'; import useTeleport from 'teleport/useTeleport'; @@ -126,7 +126,6 @@ export function ReauthenticateStep({ onClose, onAuthenticated: onAuthenticatedProp, }: ReauthenticateStepProps) { - const challengeScope = MfaChallengeScope.MANAGE_DEVICES; const onAuthenticated = (privilegeToken: string) => { onAuthenticatedProp(privilegeToken); next(); @@ -134,7 +133,6 @@ export function ReauthenticateStep({ const { attempt, clearAttempt, submitWithTotp, submitWithWebauthn } = useReAuthenticate({ onAuthenticated, - challengeScope, }); const mfaOptions = createMfaOptions({ auth2faType, @@ -155,7 +153,7 @@ export function ReauthenticateStep({ e.preventDefault(); if (!validator.validate()) return; if (mfaOption === 'webauthn') { - submitWithWebauthn(challengeScope); + submitWithWebauthn(); } if (mfaOption === 'otp') { submitWithTotp(authCode); diff --git a/web/packages/teleport/src/components/ReAuthenticate/ReAuthenticate.story.tsx b/web/packages/teleport/src/components/ReAuthenticate/ReAuthenticate.story.tsx index 8ee1a0187bd14..088bedae810a2 100644 --- a/web/packages/teleport/src/components/ReAuthenticate/ReAuthenticate.story.tsx +++ b/web/packages/teleport/src/components/ReAuthenticate/ReAuthenticate.story.tsx @@ -18,8 +18,6 @@ import React from 'react'; -import { MfaChallengeScope } from 'teleport/services/auth/auth'; - import { State } from './useReAuthenticate'; import { ReAuthenticate } from './ReAuthenticate'; @@ -49,5 +47,4 @@ const props: State = { onClose: () => null, auth2faType: 'on', actionText: 'performing this action', - challengeScope: MfaChallengeScope.UNSPECIFIED, }; diff --git a/web/packages/teleport/src/components/ReAuthenticate/ReAuthenticate.tsx b/web/packages/teleport/src/components/ReAuthenticate/ReAuthenticate.tsx index fdd5dd756af3b..e6d363d94c8cd 100644 --- a/web/packages/teleport/src/components/ReAuthenticate/ReAuthenticate.tsx +++ b/web/packages/teleport/src/components/ReAuthenticate/ReAuthenticate.tsx @@ -47,7 +47,6 @@ export function ReAuthenticate({ auth2faType, preferredMfaType, actionText, - challengeScope, }: State) { const [otpToken, setOtpToken] = useState(''); const mfaOptions = createMfaOptions({ @@ -61,7 +60,7 @@ export function ReAuthenticate({ e.preventDefault(); if (mfaOption?.value === 'webauthn') { - submitWithWebauthn(challengeScope); + submitWithWebauthn(); } if (mfaOption?.value === 'otp') { submitWithTotp(otpToken); diff --git a/web/packages/teleport/src/components/ReAuthenticate/useReAuthenticate.ts b/web/packages/teleport/src/components/ReAuthenticate/useReAuthenticate.ts index bc995de22352a..e0bd001bd1e9c 100644 --- a/web/packages/teleport/src/components/ReAuthenticate/useReAuthenticate.ts +++ b/web/packages/teleport/src/components/ReAuthenticate/useReAuthenticate.ts @@ -32,7 +32,7 @@ import type { MfaAuthnResponse } from 'teleport/services/mfa'; // token, and after successfully obtaining the token, the function // `onAuthenticated` will be called with this token. export default function useReAuthenticate(props: Props) { - const { onClose, actionText = defaultActionText, challengeScope } = props; + const { onClose, actionText = defaultActionText } = props; // Note that attempt state "success" is not used or required. // After the user submits, the control is passed back @@ -53,12 +53,12 @@ export default function useReAuthenticate(props: Props) { .catch(handleError); } - function submitWithWebauthn(scope: MfaChallengeScope) { + function submitWithWebauthn() { setAttempt({ status: 'processing' }); if ('onMfaResponse' in props) { auth - .getWebauthnResponse(scope) + .getWebauthnResponse(props.challengeScope) .then(webauthnResponse => props.onMfaResponse({ webauthn_response: webauthnResponse }) ) @@ -67,7 +67,7 @@ export default function useReAuthenticate(props: Props) { } auth - .createPrivilegeTokenWithWebauthn(scope) + .createPrivilegeTokenWithWebauthn() .then(props.onAuthenticated) .catch((err: Error) => { // This catches a webauthn frontend error that occurs on Firefox and replaces it with a more helpful error message. @@ -97,7 +97,6 @@ export default function useReAuthenticate(props: Props) { auth2faType: cfg.getAuth2faType(), preferredMfaType: cfg.getPreferredMfaType(), actionText, - challengeScope, onClose, }; } @@ -116,12 +115,6 @@ type BaseProps = { * * */ actionText?: string; - /** - * The MFA challenge scope of the action to perform, as defined in webauthn.proto. - * - * TODO(Joerger): change to required field once provided by /e references - */ - challengeScope?: MfaChallengeScope; }; // MfaResponseProps defines a function @@ -129,6 +122,10 @@ type BaseProps = { // authentication has been done at this point. type MfaResponseProps = BaseProps & { onMfaResponse(res: MfaAuthnResponse): void; + /** + * The MFA challenge scope of the action to perform, as defined in webauthn.proto. + */ + challengeScope: MfaChallengeScope; onAuthenticated?: never; }; @@ -139,6 +136,8 @@ type MfaResponseProps = BaseProps & { type DefaultProps = BaseProps & { onAuthenticated(privilegeTokenId: string): void; onMfaResponse?: never; + // TODO(Joerger): change type to 'never' once it is no longer expected in /e + challengeScope?: MfaChallengeScope; }; export type Props = MfaResponseProps | DefaultProps; diff --git a/web/packages/teleport/src/services/auth/auth.ts b/web/packages/teleport/src/services/auth/auth.ts index e4f7109545f91..07072b3d5f700 100644 --- a/web/packages/teleport/src/services/auth/auth.ts +++ b/web/packages/teleport/src/services/auth/auth.ts @@ -297,12 +297,15 @@ const auth = { ); }, - createPrivilegeTokenWithWebauthn(scope: MfaChallengeScope) { - return auth.fetchWebAuthnChallenge({ scope }).then(res => - api.post(cfg.api.createPrivilegeTokenPath, { - webauthnAssertionResponse: makeWebauthnAssertionResponse(res), - }) - ); + createPrivilegeTokenWithWebauthn() { + // Creating privilege tokens always expects the MANAGE_DEVICES webauthn scope. + return auth + .fetchWebAuthnChallenge({ scope: MfaChallengeScope.MANAGE_DEVICES }) + .then(res => + api.post(cfg.api.createPrivilegeTokenPath, { + webauthnAssertionResponse: makeWebauthnAssertionResponse(res), + }) + ); }, createRestrictedPrivilegeToken() {