diff --git a/web/packages/teleport/src/Account/ChangePasswordWizard/ChangePasswordWizard.tsx b/web/packages/teleport/src/Account/ChangePasswordWizard/ChangePasswordWizard.tsx index 1a152f2ddcfbf..cbaed3f4de01c 100644 --- a/web/packages/teleport/src/Account/ChangePasswordWizard/ChangePasswordWizard.tsx +++ b/web/packages/teleport/src/Account/ChangePasswordWizard/ChangePasswordWizard.tsx @@ -178,7 +178,10 @@ export function ReauthenticateStep({ m === 'passwordless' ? 'required' : 'discouraged', }); - const response = await auth.getMfaChallengeResponse(challenge); + const response = await auth.getMfaChallengeResponse( + challenge, + 'webauthn' + ); // TODO(Joerger): handle non-webauthn response. onAuthenticated(response.webauthn_response); diff --git a/web/packages/teleport/src/Console/DocumentSsh/useGetScpUrl.ts b/web/packages/teleport/src/Console/DocumentSsh/useGetScpUrl.ts index a984a6f983986..478ccbcc5fa59 100644 --- a/web/packages/teleport/src/Console/DocumentSsh/useGetScpUrl.ts +++ b/web/packages/teleport/src/Console/DocumentSsh/useGetScpUrl.ts @@ -39,7 +39,10 @@ export default function useGetScpUrl(addMfaToScpUrls: boolean) { scope: MfaChallengeScope.USER_SESSION, }); - const response = await auth.getMfaChallengeResponse(challenge); + const response = await auth.getMfaChallengeResponse( + challenge, + 'webauthn' + ); setAttempt({ status: 'success', diff --git a/web/packages/teleport/src/components/ReAuthenticate/useReAuthenticate.ts b/web/packages/teleport/src/components/ReAuthenticate/useReAuthenticate.ts index 8bb9e13a7661d..7b2746de0a25c 100644 --- a/web/packages/teleport/src/components/ReAuthenticate/useReAuthenticate.ts +++ b/web/packages/teleport/src/components/ReAuthenticate/useReAuthenticate.ts @@ -59,7 +59,7 @@ export default function useReAuthenticate(props: Props) { if ('onMfaResponse' in props) { auth .getMfaChallenge({ scope: props.challengeScope }) - .then(auth.getMfaChallengeResponse) + .then(challenge => auth.getMfaChallengeResponse(challenge, 'webauthn')) .catch(handleError); return; diff --git a/web/packages/teleport/src/services/auth/auth.ts b/web/packages/teleport/src/services/auth/auth.ts index 75423dbbc55a3..d1581c78e6067 100644 --- a/web/packages/teleport/src/services/auth/auth.ts +++ b/web/packages/teleport/src/services/auth/auth.ts @@ -241,7 +241,7 @@ const auth = { headlessSSOAccept(transactionId: string) { return auth .getMfaChallenge({ scope: MfaChallengeScope.HEADLESS_LOGIN }) - .then(auth.getMfaChallengeResponse) + .then(challenge => auth.getMfaChallengeResponse(challenge, 'webauthn')) .then(res => { const request = { action: 'accept', @@ -286,19 +286,43 @@ const auth = { // getChallengeResponse gets an MFA challenge response for the provided parameters. // If is_mfa_required_req is provided and it is found that MFA is not required, returns null instead. - async getMfaChallengeResponse(challenge: MfaAuthenticateChallenge) { - // TODO(Joerger): Handle sso and otp. We need to provide some global context which we - // can use to display the MFA component currently found in useMFA. - return auth.getWebAuthnChallengeResponse(challenge); + async getMfaChallengeResponse( + challenge: MfaAuthenticateChallenge, + mfaType?: DeviceType, + totpCode?: string + ): Promise { + // TODO(Joerger): If mfaType is not provided by a parent component, use some global context + // to display a component, similar to the one used in useMfa. For now we just default to + // whichever method we can succeed with first. + if (!mfaType) { + if (totpCode) { + mfaType == 'totp'; + } else if (challenge.webauthnPublicKey) { + mfaType == 'webauthn'; + } + } + + if (mfaType === 'webauthn') { + return auth.getWebAuthnChallengeResponse(challenge.webauthnPublicKey); + } + + if (mfaType === 'totp') { + return { + totp_code: totpCode, + }; + } + + // No viable challenge, return empty response. + return null; }, async getWebAuthnChallengeResponse( - challenge: MfaAuthenticateChallenge + webauthnPublicKey: PublicKeyCredentialRequestOptions ): Promise { return auth.checkWebauthnSupport().then(() => navigator.credentials .get({ - publicKey: challenge.webauthnPublicKey, + publicKey: webauthnPublicKey, }) .then(cred => { return makeWebauthnAssertionResponse(cred); @@ -365,7 +389,7 @@ const auth = { return auth .getMfaChallenge({ scope, allowReuse, isMfaRequiredRequest }, abortSignal) - .then(challenge => auth.getMfaChallengeResponse(challenge)) + .then(challenge => auth.getMfaChallengeResponse(challenge, 'webauthn')) .then(res => makeWebauthnAssertionResponse(res.webauthn_response)); },