diff --git a/web/packages/teleport/src/Account/Account.test.tsx b/web/packages/teleport/src/Account/Account.test.tsx index 6fb23549a0e36..7dcf86f471adb 100644 --- a/web/packages/teleport/src/Account/Account.test.tsx +++ b/web/packages/teleport/src/Account/Account.test.tsx @@ -243,9 +243,11 @@ test('adding an MFA device', async () => { const user = userEvent.setup(); const ctx = createTeleportContext(); jest.spyOn(ctx.mfaService, 'fetchDevices').mockResolvedValue([testPasskey]); - jest - .spyOn(auth, 'getChallenge') - .mockResolvedValue({ webauthnPublicKey: null, totpChallenge: true }); + jest.spyOn(auth, 'getChallenge').mockResolvedValue({ + webauthnPublicKey: null, + totpChallenge: true, + ssoChallenge: null, + }); jest .spyOn(auth, 'createNewWebAuthnDevice') .mockResolvedValueOnce(dummyCredential); @@ -325,9 +327,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/EventEmitterWebAuthnSender.ts b/web/packages/teleport/src/lib/EventEmitterMfaSender.ts similarity index 91% rename from web/packages/teleport/src/lib/EventEmitterWebAuthnSender.ts rename to web/packages/teleport/src/lib/EventEmitterMfaSender.ts index 834746c866bf6..68eae3367f6ea 100644 --- a/web/packages/teleport/src/lib/EventEmitterWebAuthnSender.ts +++ b/web/packages/teleport/src/lib/EventEmitterMfaSender.ts @@ -20,7 +20,7 @@ import { EventEmitter } from 'events'; import { WebauthnAssertionResponse } from 'teleport/services/auth'; -class EventEmitterWebAuthnSender extends EventEmitter { +class EventEmitterMfaSender extends EventEmitter { constructor() { super(); } @@ -31,4 +31,4 @@ class EventEmitterWebAuthnSender extends EventEmitter { } } -export { EventEmitterWebAuthnSender }; +export { EventEmitterMfaSender }; diff --git a/web/packages/teleport/src/lib/tdp/client.ts b/web/packages/teleport/src/lib/tdp/client.ts index 098cb9d824fa6..6f000b083d820 100644 --- a/web/packages/teleport/src/lib/tdp/client.ts +++ b/web/packages/teleport/src/lib/tdp/client.ts @@ -24,7 +24,7 @@ import init, { } from 'teleport/ironrdp/pkg/ironrdp'; import { WebsocketCloseCode, TermEvent } from 'teleport/lib/term/enums'; -import { EventEmitterWebAuthnSender } from 'teleport/lib/EventEmitterWebAuthnSender'; +import { EventEmitterMfaSender } from 'teleport/lib/EventEmitterMfaSender'; import { AuthenticatedWebSocket } from 'teleport/lib/AuthenticatedWebSocket'; import Codec, { @@ -93,7 +93,7 @@ export enum LogType { // sending client commands, and receiving and processing server messages. Its creator is responsible for // ensuring the websocket gets closed and all of its event listeners cleaned up when it is no longer in use. // For convenience, this can be done in one fell swoop by calling Client.shutdown(). -export default class Client extends EventEmitterWebAuthnSender { +export default class Client extends EventEmitterMfaSender { protected codec: Codec; protected socket: AuthenticatedWebSocket | undefined; private socketAddr: string; diff --git a/web/packages/teleport/src/lib/term/tty.ts b/web/packages/teleport/src/lib/term/tty.ts index 6bd9014234323..2eb11957b8fbd 100644 --- a/web/packages/teleport/src/lib/term/tty.ts +++ b/web/packages/teleport/src/lib/term/tty.ts @@ -18,7 +18,7 @@ import Logger from 'shared/libs/logger'; -import { EventEmitterWebAuthnSender } from 'teleport/lib/EventEmitterWebAuthnSender'; +import { EventEmitterMfaSender } from 'teleport/lib/EventEmitterMfaSender'; import { WebauthnAssertionResponse } from 'teleport/services/auth'; import { AuthenticatedWebSocket } from 'teleport/lib/AuthenticatedWebSocket'; @@ -31,7 +31,7 @@ const defaultOptions = { buffered: true, }; -class Tty extends EventEmitterWebAuthnSender { +class Tty extends EventEmitterMfaSender { socket = null; _buffered = true; diff --git a/web/packages/teleport/src/lib/useMfa.ts b/web/packages/teleport/src/lib/useMfa.ts new file mode 100644 index 0000000000000..8d55cf4c73f75 --- /dev/null +++ b/web/packages/teleport/src/lib/useMfa.ts @@ -0,0 +1,148 @@ +/** + * Teleport + * Copyright (C) 2023 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { useState, useEffect, useCallback } from 'react'; + +import { EventEmitterMfaSender } from 'teleport/lib/EventEmitterMfaSender'; +import { TermEvent } from 'teleport/lib/term/enums'; +import { + makeMfaAuthenticateChallenge, + makeWebauthnAssertionResponse, + SSOChallenge, +} from 'teleport/services/auth'; + +export function useMfa(emitterSender: EventEmitterMfaSender): MfaState { + const [state, setState] = useState<{ + errorText: string; + addMfaToScpUrls: boolean; + webauthnPublicKey: PublicKeyCredentialRequestOptions; + ssoChallenge: SSOChallenge; + totpChallenge: boolean; + }>({ + addMfaToScpUrls: false, + errorText: '', + webauthnPublicKey: null, + ssoChallenge: null, + totpChallenge: false, + }); + + // 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 + console.error('not yet implemented'); + } + + function onWebauthnAuthenticate() { + if (!window.PublicKeyCredential) { + const errorText = + 'This browser does not support WebAuthn required for hardware tokens, \ + please try the latest version of Chrome, Firefox or Safari.'; + + setState({ + ...state, + errorText, + }); + return; + } + + navigator.credentials + .get({ publicKey: state.webauthnPublicKey }) + .then(res => { + setState(prevState => ({ + ...prevState, + errorText: '', + webauthnPublicKey: null, + })); + const credential = makeWebauthnAssertionResponse(res); + emitterSender.sendWebAuthn(credential); + }) + .catch((err: Error) => { + setErrorText(err.message); + }); + } + + const onChallenge = useCallback(challengeJson => { + const { webauthnPublicKey, ssoChallenge, totpChallenge } = + makeMfaAuthenticateChallenge(challengeJson); + + setState(prevState => ({ + ...prevState, + ssoChallenge, + webauthnPublicKey, + totpChallenge, + })); + }, []); + + useEffect(() => { + if (emitterSender) { + emitterSender.on(TermEvent.WEBAUTHN_CHALLENGE, onChallenge); + + return () => { + emitterSender.removeListener(TermEvent.WEBAUTHN_CHALLENGE, onChallenge); + }; + } + }, [emitterSender, onChallenge]); + + function setErrorText(newErrorText: string) { + setState(prevState => ({ ...prevState, errorText: newErrorText })); + } + + // if any challenge exists, requested is true + const requested = !!( + state.webauthnPublicKey || + state.totpChallenge || + state.ssoChallenge + ); + + return { + requested, + onWebauthnAuthenticate, + onSsoAuthenticate, + addMfaToScpUrls: state.addMfaToScpUrls, + setErrorText, + errorText: state.errorText, + webauthnPublicKey: state.webauthnPublicKey, + ssoChallenge: state.ssoChallenge, + }; +} + +export type MfaState = { + onWebauthnAuthenticate: () => void; + onSsoAuthenticate: () => void; + setErrorText: (errorText: string) => void; + errorText: string; + requested: boolean; + addMfaToScpUrls: boolean; + webauthnPublicKey: PublicKeyCredentialRequestOptions; + ssoChallenge: SSOChallenge; +}; + +// used for testing +export function makeDefaultMfaState(): MfaState { + return { + onWebauthnAuthenticate: () => null, + onSsoAuthenticate: () => null, + setErrorText: () => null, + errorText: '', + requested: false, + addMfaToScpUrls: false, + webauthnPublicKey: null, + ssoChallenge: null, + }; +} diff --git a/web/packages/teleport/src/lib/useWebAuthn.ts b/web/packages/teleport/src/lib/useWebAuthn.ts deleted file mode 100644 index 730065299ceed..0000000000000 --- a/web/packages/teleport/src/lib/useWebAuthn.ts +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Teleport - * Copyright (C) 2023 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import { useState, useEffect, Dispatch, SetStateAction } from 'react'; - -import { EventEmitterWebAuthnSender } from 'teleport/lib/EventEmitterWebAuthnSender'; -import { TermEvent } from 'teleport/lib/term/enums'; -import { - makeMfaAuthenticateChallenge, - makeWebauthnAssertionResponse, -} from 'teleport/services/auth'; - -export default function useWebAuthn( - emitterSender: EventEmitterWebAuthnSender -): WebAuthnState { - const [state, setState] = useState({ - addMfaToScpUrls: false, - requested: false, - errorText: '', - publicKey: null as PublicKeyCredentialRequestOptions, - }); - - function authenticate() { - if (!window.PublicKeyCredential) { - const errorText = - 'This browser does not support WebAuthn required for hardware tokens, \ - please try the latest version of Chrome, Firefox or Safari.'; - - setState({ - ...state, - errorText, - }); - return; - } - - navigator.credentials - .get({ publicKey: state.publicKey }) - .then(res => { - const credential = makeWebauthnAssertionResponse(res); - emitterSender.sendWebAuthn(credential); - - setState({ - ...state, - requested: false, - errorText: '', - }); - }) - .catch((err: Error) => { - setState({ - ...state, - errorText: err.message, - }); - }); - } - - const onChallenge = challengeJson => { - const challenge = JSON.parse(challengeJson); - const publicKey = makeMfaAuthenticateChallenge(challenge).webauthnPublicKey; - - setState({ - ...state, - requested: true, - addMfaToScpUrls: true, - publicKey, - }); - }; - - useEffect(() => { - if (emitterSender) { - emitterSender.on(TermEvent.WEBAUTHN_CHALLENGE, onChallenge); - - return () => { - emitterSender.removeListener(TermEvent.WEBAUTHN_CHALLENGE, onChallenge); - }; - } - }, [emitterSender]); - - return { - errorText: state.errorText, - requested: state.requested, - authenticate, - setState, - addMfaToScpUrls: state.addMfaToScpUrls, - }; -} - -export type WebAuthnState = { - errorText: string; - requested: boolean; - authenticate: () => void; - setState: Dispatch< - SetStateAction<{ - addMfaToScpUrls: boolean; - requested: boolean; - errorText: string; - publicKey: PublicKeyCredentialRequestOptions; - }> - >; - addMfaToScpUrls: boolean; -}; diff --git a/web/packages/teleport/src/services/auth/makeMfa.ts b/web/packages/teleport/src/services/auth/makeMfa.ts index 0637967483911..506cca4a874c7 100644 --- a/web/packages/teleport/src/services/auth/makeMfa.ts +++ b/web/packages/teleport/src/services/auth/makeMfa.ts @@ -50,12 +50,15 @@ export function makeMfaRegistrationChallenge(json): MfaRegistrationChallenge { } // makeMfaAuthenticateChallenge formats fetched authenticate challenge JSON. -// Webauthn challange contains Base64URL(byte) fields that needs to +// Webauthn challenge contains Base64URL(byte) fields that needs to // be converted to ArrayBuffer expected by navigator.credentials.get: // - challenge // - allowCredentials[i].id export function makeMfaAuthenticateChallenge(json): MfaAuthenticateChallenge { - const webauthnPublicKey = json.webauthn_challenge?.publicKey; + const challenge = typeof json === 'string' ? JSON.parse(json) : json; + const { sso_challenge, webauthn_challenge } = challenge; + + const webauthnPublicKey = webauthn_challenge?.publicKey; if (webauthnPublicKey) { const challenge = webauthnPublicKey.challenge || ''; const allowCredentials = webauthnPublicKey.allowCredentials || []; @@ -70,6 +73,12 @@ export function makeMfaAuthenticateChallenge(json): MfaAuthenticateChallenge { } return { + ssoChallenge: sso_challenge + ? { + redirectUrl: sso_challenge.redirect_url, + requestId: sso_challenge.request_id, + } + : null, totpChallenge: json.totp_challenge, webauthnPublicKey: webauthnPublicKey, }; diff --git a/web/packages/teleport/src/services/auth/types.ts b/web/packages/teleport/src/services/auth/types.ts index 11057cd185645..170d4eedee272 100644 --- a/web/packages/teleport/src/services/auth/types.ts +++ b/web/packages/teleport/src/services/auth/types.ts @@ -32,7 +32,13 @@ export type AuthnChallengeRequest = { userCred: UserCredentials; }; +export type SSOChallenge = { + redirectUrl: string; + requestId: string; +}; + export type MfaAuthenticateChallenge = { + ssoChallenge: SSOChallenge; totpChallenge: boolean; webauthnPublicKey: PublicKeyCredentialRequestOptions; };