From 95b5fd14d680f0c122edb3ef460c698cc00a41db Mon Sep 17 00:00:00 2001 From: joerger Date: Wed, 17 Apr 2024 12:08:11 -0700 Subject: [PATCH 1/4] Always provide MANAGE_DEVICES webauthn scope for creating privileged tokens. --- .../src/components/ReAuthenticate/useReAuthenticate.ts | 2 +- web/packages/teleport/src/services/auth/auth.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/web/packages/teleport/src/components/ReAuthenticate/useReAuthenticate.ts b/web/packages/teleport/src/components/ReAuthenticate/useReAuthenticate.ts index 781a4680840f6..f7d06c0c9af9a 100644 --- a/web/packages/teleport/src/components/ReAuthenticate/useReAuthenticate.ts +++ b/web/packages/teleport/src/components/ReAuthenticate/useReAuthenticate.ts @@ -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. diff --git a/web/packages/teleport/src/services/auth/auth.ts b/web/packages/teleport/src/services/auth/auth.ts index f72d92007c7a3..b6e40d2129412 100644 --- a/web/packages/teleport/src/services/auth/auth.ts +++ b/web/packages/teleport/src/services/auth/auth.ts @@ -268,8 +268,9 @@ const auth = { ); }, - createPrivilegeTokenWithWebauthn(scope: MfaChallengeScope) { - return auth.fetchWebAuthnChallenge({ scope }).then(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), }) From 7cee96f413e0d1d92df93c547bd0b5bb16d0b7ac Mon Sep 17 00:00:00 2001 From: joerger Date: Wed, 17 Apr 2024 12:53:31 -0700 Subject: [PATCH 2/4] Make challenge scope exclusive to onMfaResponse flow. --- web/packages/teleport/src/Account/Account.tsx | 1 - .../AddAuthDeviceWizard/AddAuthDeviceWizard.tsx | 4 +--- .../ReAuthenticate/ReAuthenticate.story.tsx | 1 - .../ReAuthenticate/ReAuthenticate.tsx | 3 +-- .../ReAuthenticate/useReAuthenticate.ts | 17 +++++++++-------- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/web/packages/teleport/src/Account/Account.tsx b/web/packages/teleport/src/Account/Account.tsx index 5c037224db27c..46afd06aa0fee 100644 --- a/web/packages/teleport/src/Account/Account.tsx +++ b/web/packages/teleport/src/Account/Account.tsx @@ -232,7 +232,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 14404706d77a9..2386dc6fe85c4 100644 --- a/web/packages/teleport/src/Account/ManageDevices/AddAuthDeviceWizard/AddAuthDeviceWizard.tsx +++ b/web/packages/teleport/src/Account/ManageDevices/AddAuthDeviceWizard/AddAuthDeviceWizard.tsx @@ -130,7 +130,6 @@ export function ReauthenticateStep({ onClose, onAuthenticated: onAuthenticatedProp, }: AddAuthDeviceWizardStepProps) { - const challengeScope = MfaChallengeScope.MANAGE_DEVICES; const onAuthenticated = (privilegeToken: string) => { onAuthenticatedProp(privilegeToken); next(); @@ -138,7 +137,6 @@ export function ReauthenticateStep({ const { attempt, clearAttempt, submitWithTotp, submitWithWebauthn } = useReAuthenticate({ onAuthenticated, - challengeScope, }); const mfaOptions = createMfaOptions({ auth2faType, @@ -159,7 +157,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..e928af30e76c8 100644 --- a/web/packages/teleport/src/components/ReAuthenticate/ReAuthenticate.story.tsx +++ b/web/packages/teleport/src/components/ReAuthenticate/ReAuthenticate.story.tsx @@ -49,5 +49,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 f7d06c0c9af9a..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 }) ) @@ -97,7 +97,6 @@ export default function useReAuthenticate(props: Props) { auth2faType: cfg.getAuth2faType(), preferredMfaType: cfg.getPreferredMfaType(), actionText, - challengeScope, onClose, }; } @@ -116,10 +115,6 @@ type BaseProps = { * * */ actionText?: string; - /** - * The MFA challenge scope of the action to perform, as defined in webauthn.proto. - */ - challengeScope: MfaChallengeScope; }; // MfaResponseProps defines a function @@ -127,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; }; @@ -137,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; From 1ecfbc232ebbc8f2e7653f673c19e5c750f38a3c Mon Sep 17 00:00:00 2001 From: joerger Date: Mon, 22 Apr 2024 10:50:20 -0700 Subject: [PATCH 3/4] Fix UI lint. --- web/packages/teleport/src/Account/Account.tsx | 2 -- .../AddAuthDeviceWizard/AddAuthDeviceWizard.tsx | 2 +- .../ReAuthenticate/ReAuthenticate.story.tsx | 2 -- web/packages/teleport/src/services/auth/auth.ts | 12 +++++++----- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/web/packages/teleport/src/Account/Account.tsx b/web/packages/teleport/src/Account/Account.tsx index 46afd06aa0fee..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'; diff --git a/web/packages/teleport/src/Account/ManageDevices/AddAuthDeviceWizard/AddAuthDeviceWizard.tsx b/web/packages/teleport/src/Account/ManageDevices/AddAuthDeviceWizard/AddAuthDeviceWizard.tsx index 2386dc6fe85c4..51761f78a030b 100644 --- a/web/packages/teleport/src/Account/ManageDevices/AddAuthDeviceWizard/AddAuthDeviceWizard.tsx +++ b/web/packages/teleport/src/Account/ManageDevices/AddAuthDeviceWizard/AddAuthDeviceWizard.tsx @@ -39,7 +39,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'; diff --git a/web/packages/teleport/src/components/ReAuthenticate/ReAuthenticate.story.tsx b/web/packages/teleport/src/components/ReAuthenticate/ReAuthenticate.story.tsx index e928af30e76c8..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'; diff --git a/web/packages/teleport/src/services/auth/auth.ts b/web/packages/teleport/src/services/auth/auth.ts index b6e40d2129412..0286a432f6e15 100644 --- a/web/packages/teleport/src/services/auth/auth.ts +++ b/web/packages/teleport/src/services/auth/auth.ts @@ -270,11 +270,13 @@ const auth = { 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), - }) - ); + return auth + .fetchWebAuthnChallenge({ scope: MfaChallengeScope.MANAGE_DEVICES }) + .then(res => + api.post(cfg.api.createPrivilegeTokenPath, { + webauthnAssertionResponse: makeWebauthnAssertionResponse(res), + }) + ); }, createRestrictedPrivilegeToken() { From 3c1afdfde4c053744551fd4959c00fcdf1646074 Mon Sep 17 00:00:00 2001 From: joerger Date: Mon, 22 Apr 2024 10:57:38 -0700 Subject: [PATCH 4/4] Address TODO; update e ref. --- e | 2 +- .../src/components/ReAuthenticate/useReAuthenticate.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/e b/e index bdc82dd8c5f94..3eb56cfff375c 160000 --- a/e +++ b/e @@ -1 +1 @@ -Subproject commit bdc82dd8c5f94dba7228d2385f19f3b5ffd30deb +Subproject commit 3eb56cfff375c720ddae2541967a485efd4ae4f4 diff --git a/web/packages/teleport/src/components/ReAuthenticate/useReAuthenticate.ts b/web/packages/teleport/src/components/ReAuthenticate/useReAuthenticate.ts index e0bd001bd1e9c..3e60ee586c2dc 100644 --- a/web/packages/teleport/src/components/ReAuthenticate/useReAuthenticate.ts +++ b/web/packages/teleport/src/components/ReAuthenticate/useReAuthenticate.ts @@ -136,8 +136,7 @@ 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; + challengeScope?: never; }; export type Props = MfaResponseProps | DefaultProps;