diff --git a/web/packages/teleport/src/Account/Account.test.tsx b/web/packages/teleport/src/Account/Account.test.tsx index 6fb23549a0e36..5705422ca326d 100644 --- a/web/packages/teleport/src/Account/Account.test.tsx +++ b/web/packages/teleport/src/Account/Account.test.tsx @@ -245,7 +245,11 @@ test('adding an MFA device', async () => { jest.spyOn(ctx.mfaService, 'fetchDevices').mockResolvedValue([testPasskey]); jest .spyOn(auth, 'getChallenge') - .mockResolvedValue({ webauthnPublicKey: null, totpChallenge: true }); + .mockResolvedValue({ + webauthnPublicKey: null, + totpChallenge: true, + ssoChallenge: null, + }); jest .spyOn(auth, 'createNewWebAuthnDevice') .mockResolvedValueOnce(dummyCredential); @@ -325,9 +329,11 @@ test('removing an MFA method', async () => { const user = userEvent.setup(); const ctx = createTeleportContext(); jest.spyOn(ctx.mfaService, 'fetchDevices').mockResolvedValue([testMfaMethod]); - jest - .spyOn(auth, 'getChallenge') - .mockResolvedValue({ webauthnPublicKey: null, totpChallenge: false }); + jest.spyOn(auth, 'getChallenge').mockResolvedValue({ + webauthnPublicKey: null, + totpChallenge: false, + ssoChallenge: null, + }); jest .spyOn(auth, 'createPrivilegeTokenWithWebauthn') .mockResolvedValueOnce('webauthn-privilege-token'); diff --git a/web/packages/teleport/src/Console/DocumentKubeExec/DocumentKubeExec.tsx b/web/packages/teleport/src/Console/DocumentKubeExec/DocumentKubeExec.tsx index 1589c6ef7d347..3b405e034f04c 100644 --- a/web/packages/teleport/src/Console/DocumentKubeExec/DocumentKubeExec.tsx +++ b/web/packages/teleport/src/Console/DocumentKubeExec/DocumentKubeExec.tsx @@ -22,7 +22,7 @@ import { Box, Indicator } from 'design'; import * as stores from 'teleport/Console/stores/types'; import { Terminal, TerminalRef } from 'teleport/Console/DocumentSsh/Terminal'; -import useWebAuthn from 'teleport/lib/useWebAuthn'; +import { useMfa } from 'teleport/lib/useMfa'; import useKubeExecSession from 'teleport/Console/DocumentKubeExec/useKubeExecSession'; import Document from 'teleport/Console/Document'; @@ -39,11 +39,11 @@ export default function DocumentKubeExec({ doc, visible }: Props) { const terminalRef = useRef(); const { tty, status, closeDocument, sendKubeExecData } = useKubeExecSession(doc); - const webauthn = useWebAuthn(tty); + const mfa = useMfa(tty); useEffect(() => { // when switching tabs or closing tabs, focus on visible terminal terminalRef.current?.focus(); - }, [visible, webauthn.requested]); + }, [visible, mfa.requested]); const theme = useTheme(); const terminal = ( @@ -63,13 +63,7 @@ export default function DocumentKubeExec({ doc, visible }: Props) { )} - {webauthn.requested && ( - - )} + {mfa.requested && } {status === 'waiting-for-exec-data' && ( diff --git a/web/packages/teleport/src/Console/DocumentSsh/DocumentSsh.tsx b/web/packages/teleport/src/Console/DocumentSsh/DocumentSsh.tsx index eb2720d7f012e..aacafdc35808a 100644 --- a/web/packages/teleport/src/Console/DocumentSsh/DocumentSsh.tsx +++ b/web/packages/teleport/src/Console/DocumentSsh/DocumentSsh.tsx @@ -31,7 +31,7 @@ import { import * as stores from 'teleport/Console/stores'; import AuthnDialog from 'teleport/components/AuthnDialog'; -import useWebAuthn from 'teleport/lib/useWebAuthn'; +import { useMfa } from 'teleport/lib/useMfa'; import Document from '../Document'; @@ -50,13 +50,13 @@ export default function DocumentSshWrapper(props: PropTypes) { function DocumentSsh({ doc, visible }: PropTypes) { const terminalRef = useRef(); const { tty, status, closeDocument, session } = useSshSession(doc); - const webauthn = useWebAuthn(tty); + const mfa = useMfa(tty); const { getMfaResponseAttempt, getDownloader, getUploader, fileTransferRequests, - } = useFileTransfer(tty, session, doc, webauthn.addMfaToScpUrls); + } = useFileTransfer(tty, session, doc, mfa.addMfaToScpUrls); const theme = useTheme(); function handleCloseFileTransfer() { @@ -70,7 +70,7 @@ function DocumentSsh({ doc, visible }: PropTypes) { useEffect(() => { // when switching tabs or closing tabs, focus on visible terminal terminalRef.current?.focus(); - }, [visible, webauthn.requested]); + }, [visible, mfa.requested]); const terminal = ( )} - {webauthn.requested && ( - - )} + {mfa.requested && } {status === 'initialized' && terminal} {}, clientOnClipboardData: async () => {}, setTdpConnection: () => {}, - webauthn: { - errorText: '', - requested: false, - authenticate: () => {}, - setState: () => {}, - addMfaToScpUrls: false, - }, + mfa: makeDefaultMfaState(), showAnotherSessionActiveDialog: false, setShowAnotherSessionActiveDialog: () => {}, alerts: [], @@ -265,12 +260,15 @@ export const WebAuthnPrompt = () => ( writeState: 'granted', }} wsConnection={{ status: 'open' }} - webauthn={{ + mfa={{ errorText: '', requested: true, - authenticate: () => {}, - setState: () => {}, + setErrorText: () => null, addMfaToScpUrls: false, + onWebauthnAuthenticate: () => null, + onSsoAuthenticate: () => null, + webauthnPublicKey: null, + ssoChallenge: null, }} /> ); diff --git a/web/packages/teleport/src/DesktopSession/DesktopSession.tsx b/web/packages/teleport/src/DesktopSession/DesktopSession.tsx index 66a66825a209e..b1f188f2997c9 100644 --- a/web/packages/teleport/src/DesktopSession/DesktopSession.tsx +++ b/web/packages/teleport/src/DesktopSession/DesktopSession.tsx @@ -39,7 +39,7 @@ import useDesktopSession, { import TopBar from './TopBar'; import type { State, WebsocketAttempt } from './useDesktopSession'; -import type { WebAuthnState } from 'teleport/lib/useWebAuthn'; +import type { MfaState } from 'teleport/lib/useMfa'; export function DesktopSessionContainer() { const state = useDesktopSession(); @@ -54,7 +54,7 @@ declare global { export function DesktopSession(props: State) { const { - webauthn, + mfa, tdpClient, username, hostname, @@ -105,7 +105,7 @@ export function DesktopSession(props: State) { tdpConnection, wsConnection, showAnotherSessionActiveDialog, - webauthn + mfa ) ); }, [ @@ -113,7 +113,7 @@ export function DesktopSession(props: State) { tdpConnection, wsConnection, showAnotherSessionActiveDialog, - webauthn, + mfa, ]); return ( @@ -144,7 +144,7 @@ export function DesktopSession(props: State) { {screenState.screen === 'anotherSessionActive' && ( )} - {screenState.screen === 'mfa' && } + {screenState.screen === 'mfa' && } {screenState.screen === 'alert dialog' && ( )} @@ -181,20 +181,15 @@ export function DesktopSession(props: State) { ); } -const MfaDialog = ({ webauthn }: { webauthn: WebAuthnState }) => { +const MfaDialog = ({ mfa }: { mfa: MfaState }) => { return ( { - webauthn.setState(prevState => { - return { - ...prevState, - errorText: - 'This session requires multi factor authentication to continue. Please hit "Retry" and follow the prompts given by your browser to complete authentication.', - }; - }); + mfa.setErrorText( + 'This session requires multi factor authentication to continue. Please hit "Retry" and follow the prompts given by your browser to complete authentication.' + ); }} - errorText={webauthn.errorText} /> ); }; @@ -282,7 +277,7 @@ const nextScreenState = ( tdpConnection: Attempt, wsConnection: WebsocketAttempt, showAnotherSessionActiveDialog: boolean, - webauthn: WebAuthnState + webauthn: MfaState ): ScreenState => { // We always want to show the user the first alert that caused the session to fail/end, // so if we're already showing an alert, don't change the screen. diff --git a/web/packages/teleport/src/DesktopSession/useDesktopSession.tsx b/web/packages/teleport/src/DesktopSession/useDesktopSession.tsx index a49e6d4a268fc..1f642d38d8d96 100644 --- a/web/packages/teleport/src/DesktopSession/useDesktopSession.tsx +++ b/web/packages/teleport/src/DesktopSession/useDesktopSession.tsx @@ -22,7 +22,7 @@ import { useParams } from 'react-router'; import useAttempt from 'shared/hooks/useAttemptNext'; import { ButtonState } from 'teleport/lib/tdp'; -import useWebAuthn from 'teleport/lib/useWebAuthn'; +import { useMfa } from 'teleport/lib/useMfa'; import desktopService from 'teleport/services/desktops'; import userService from 'teleport/services/user'; @@ -130,7 +130,7 @@ export default function useDesktopSession() { }); const tdpClient = clientCanvasProps.tdpClient; - const webauthn = useWebAuthn(tdpClient); + const mfa = useMfa(tdpClient); const onShareDirectory = () => { try { @@ -205,7 +205,7 @@ export default function useDesktopSession() { fetchAttempt, tdpConnection, wsConnection, - webauthn, + mfa, setTdpConnection, showAnotherSessionActiveDialog, setShowAnotherSessionActiveDialog, diff --git a/web/packages/teleport/src/components/AuthnDialog/AuthnDialog.story.tsx b/web/packages/teleport/src/components/AuthnDialog/AuthnDialog.story.tsx index 73600b5a7fb1c..8ec5592c47a0c 100644 --- a/web/packages/teleport/src/components/AuthnDialog/AuthnDialog.story.tsx +++ b/web/packages/teleport/src/components/AuthnDialog/AuthnDialog.story.tsx @@ -18,6 +18,8 @@ import React from 'react'; +import { makeDefaultMfaState } from 'teleport/lib/useMfa'; + import AuthnDialog, { Props } from './AuthnDialog'; export default { @@ -26,12 +28,9 @@ export default { export const Loaded = () => ; -export const Error = () => ( - -); +export const Error = () => ; const props: Props = { - onContinue: () => null, + mfa: makeDefaultMfaState(), onCancel: () => null, - errorText: '', }; diff --git a/web/packages/teleport/src/components/AuthnDialog/AuthnDialog.tsx b/web/packages/teleport/src/components/AuthnDialog/AuthnDialog.tsx index a8b5cd532a1bf..05685c0d6a3eb 100644 --- a/web/packages/teleport/src/components/AuthnDialog/AuthnDialog.tsx +++ b/web/packages/teleport/src/components/AuthnDialog/AuthnDialog.tsx @@ -18,48 +18,51 @@ import React from 'react'; import Dialog, { - DialogFooter, DialogHeader, DialogTitle, DialogContent, } from 'design/Dialog'; import { Danger } from 'design/Alert'; -import { Text, ButtonPrimary, ButtonSecondary } from 'design'; +import { Text, ButtonPrimary, ButtonSecondary, Flex } from 'design'; -export default function AuthnDialog({ - onContinue, - onCancel, - errorText, -}: Props) { +import { MfaState } from 'teleport/lib/useMfa'; + +export default function AuthnDialog({ mfa, onCancel }: Props) { return ( - ({ width: '400px' })} open={true}> + ({ width: '500px' })} open={true}> Multi-factor authentication - {errorText && ( + {mfa.errorText && ( - {errorText} + {mfa.errorText} )} Re-enter your multi-factor authentication in the browser to continue. - - - {errorText ? 'Retry' : 'OK'} + + {/* TODO (avatus) this will eventually be conditionally rendered based on what + type of challenges exist. For now, its only webauthn. */} + + {mfa.errorText ? 'Retry' : 'OK'} Cancel - + ); } export type Props = { - onContinue: () => void; + mfa: MfaState; onCancel: () => void; - errorText: string; }; diff --git a/web/packages/teleport/src/lib/useMfa.ts b/web/packages/teleport/src/lib/useMfa.ts index 186414cd0bef9..8d55cf4c73f75 100644 --- a/web/packages/teleport/src/lib/useMfa.ts +++ b/web/packages/teleport/src/lib/useMfa.ts @@ -41,7 +41,7 @@ export function useMfa(emitterSender: EventEmitterMfaSender): MfaState { totpChallenge: false, }); - // TODO (avatus), this is stubbed for types but wont not be called + // TODO (avatus), this is stubbed for types but will not be called // until SSO as MFA backend is in. function onSsoAuthenticate() { // eslint-disable-next-line no-console