From f8fe5c5c7081ea36637d3db165499c28ccb5a9bc Mon Sep 17 00:00:00 2001 From: Brian Joerger Date: Tue, 10 Dec 2024 10:54:14 -0800 Subject: [PATCH] [v17] WebUI MFA types refactor (#49974) * Unify mfa types. * Move DeviceUsage to mfa/types. * Refactor mfa options and move to services/mfa. * Lint fix. * Fix mfa challenge json. * Add MFA Option constants. * Fix lint; Fix test. * Address comments. * Add license. * Fix lint. * typescript version upgrade to v5.7.2. --- package.json | 3 +- pnpm-lock.yaml | 186 +++++++++--------- web/packages/shared/utils/createMfaOptions.ts | 2 + web/packages/teleport/src/Account/Account.tsx | 4 +- .../Account/ManageDevices/useManageDevices.ts | 4 +- .../wizards/AddAuthDeviceWizard.story.tsx | 3 +- .../wizards/AddAuthDeviceWizard.tsx | 3 +- .../TestConnection/TestConnection.tsx | 4 +- .../TestConnection/TestConnection.tsx | 4 +- .../TestConnection/useTestConnection.ts | 4 +- .../Server/TestConnection/TestConnection.tsx | 4 +- .../useConnectionDiagnostic.ts | 4 +- .../src/Welcome/NewCredentials/types.ts | 3 +- web/packages/teleport/src/Welcome/useToken.ts | 2 +- .../AuthnDialog/AuthnDialog.test.tsx | 3 +- .../ReAuthenticate/useReAuthenticate.ts | 4 +- web/packages/teleport/src/config.ts | 2 +- .../teleport/src/lib/EventEmitterMfaSender.ts | 2 +- web/packages/teleport/src/lib/tdp/client.ts | 2 +- web/packages/teleport/src/lib/term/tty.ts | 7 +- web/packages/teleport/src/lib/useMfa.ts | 13 +- .../teleport/src/services/agents/types.ts | 4 +- web/packages/teleport/src/services/api/api.ts | 2 +- .../teleport/src/services/auth/auth.ts | 24 +-- .../teleport/src/services/auth/index.ts | 2 +- .../teleport/src/services/auth/makeMfa.ts | 167 ---------------- .../teleport/src/services/auth/types.ts | 34 +--- .../teleport/src/services/mfa/index.ts | 1 + .../teleport/src/services/mfa/makeMfa.ts | 159 +++++++++++++++ .../src/services/mfa/mfaOptions.test.ts | 105 ++++++++++ .../teleport/src/services/mfa/mfaOptions.ts | 78 ++++++++ .../teleport/src/services/mfa/types.ts | 92 ++++++++- .../teleport/src/services/user/user.ts | 2 +- 33 files changed, 584 insertions(+), 349 deletions(-) delete mode 100644 web/packages/teleport/src/services/auth/makeMfa.ts create mode 100644 web/packages/teleport/src/services/mfa/makeMfa.ts create mode 100644 web/packages/teleport/src/services/mfa/mfaOptions.test.ts create mode 100644 web/packages/teleport/src/services/mfa/mfaOptions.ts diff --git a/package.json b/package.json index f662ef6987204..383af8fa4af00 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "prettier": "^3.3.3", "react-select-event": "^5.5.1", "storybook": "^8.3.4", - "typescript": "^5.6.2", + "typescript": "^5.7.2", "vite": "^5.4.8" }, "dependencies": { @@ -101,3 +101,4 @@ }, "packageManager": "pnpm@9.9.0" } + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d1e1b3e75d117..6da061c3afeaf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -109,10 +109,10 @@ importers: version: 8.3.4(storybook@8.3.4) '@storybook/react': specifier: ^8.3.4 - version: 8.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.4)(typescript@5.6.2) + version: 8.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.4)(typescript@5.7.2) '@storybook/react-vite': specifier: ^8.3.4 - version: 8.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.23.0)(storybook@8.3.4)(typescript@5.6.2)(vite@5.4.8(@types/node@20.16.10)(terser@5.31.1))(webpack-sources@3.2.3) + version: 8.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.23.0)(storybook@8.3.4)(typescript@5.7.2)(vite@5.4.8(@types/node@20.16.10)(terser@5.31.1))(webpack-sources@3.2.3) '@storybook/test-runner': specifier: ^0.19.1 version: 0.19.1(@types/node@20.16.10)(babel-plugin-macros@3.1.0)(storybook@8.3.4) @@ -163,10 +163,10 @@ importers: version: 1.13.1 msw: specifier: ^2.4.9 - version: 2.4.9(typescript@5.6.2) + version: 2.4.9(typescript@5.7.2) msw-storybook-addon: specifier: ^2.0.3 - version: 2.0.3(msw@2.4.9(typescript@5.6.2)) + version: 2.0.3(msw@2.4.9(typescript@5.7.2)) playwright: specifier: ^1.47.2 version: 1.47.2 @@ -180,8 +180,8 @@ importers: specifier: ^8.3.4 version: 8.3.4 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.7.2 + version: 5.7.2 vite: specifier: ^5.4.8 version: 5.4.8(@types/node@20.16.10)(terser@5.31.1) @@ -222,16 +222,16 @@ importers: version: 8.57.0 eslint-import-resolver-typescript: specifier: ^3.6.3 - version: 3.6.3(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.6.2))(eslint-plugin-import@2.30.0)(eslint@8.57.0) + version: 3.6.3(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.30.0)(eslint@8.57.0) eslint-plugin-babel: specifier: ^5.3.1 version: 5.3.1(eslint@8.57.0) eslint-plugin-import: specifier: 2.30.0 - version: 2.30.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + version: 2.30.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) eslint-plugin-jest: specifier: ^28.8.3 - version: 28.8.3(@typescript-eslint/eslint-plugin@7.14.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(jest@29.7.0(@types/node@22.7.4)(babel-plugin-macros@3.1.0))(typescript@5.6.2) + version: 28.8.3(@typescript-eslint/eslint-plugin@7.14.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(jest@29.7.0(@types/node@22.7.4)(babel-plugin-macros@3.1.0))(typescript@5.7.2) eslint-plugin-jest-dom: specifier: ^5.4.0 version: 5.4.0(@testing-library/dom@10.1.0)(eslint@8.57.0) @@ -243,7 +243,7 @@ importers: version: 4.6.2(eslint@8.57.0) eslint-plugin-testing-library: specifier: ^6.3.0 - version: 6.3.0(eslint@8.57.0)(typescript@5.6.2) + version: 6.3.0(eslint@8.57.0)(typescript@5.7.2) jest-environment-jsdom: specifier: ^29.7.0 version: 29.7.0 @@ -258,13 +258,13 @@ importers: version: 5.12.0(rollup@4.23.0) typescript-eslint: specifier: ^7.14.1 - version: 7.14.1(eslint@8.57.0)(typescript@5.6.2) + version: 7.14.1(eslint@8.57.0)(typescript@5.7.2) vite-plugin-wasm: specifier: ^3.3.0 version: 3.3.0(vite@5.4.8(@types/node@22.7.4)(terser@5.31.1)) vite-tsconfig-paths: specifier: ^5.0.1 - version: 5.0.1(typescript@5.6.2)(vite@5.4.8(@types/node@22.7.4)(terser@5.31.1)) + version: 5.0.1(typescript@5.7.2)(vite@5.4.8(@types/node@22.7.4)(terser@5.31.1)) web/packages/design: dependencies: @@ -468,7 +468,7 @@ importers: version: 33.1.0 electron-builder: specifier: ^25.1.7 - version: 25.1.7(electron-builder-squirrel-windows@25.1.7(dmg-builder@25.1.7)) + version: 25.1.7(electron-builder-squirrel-windows@25.1.7) electron-vite: specifier: ^2.3.0 version: 2.3.0(@swc/core@1.7.26)(vite@5.4.8(@types/node@22.7.4)(terser@5.31.1)) @@ -6582,8 +6582,8 @@ packages: typescript: optional: true - typescript@5.6.2: - resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} + typescript@5.7.2: + resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} engines: {node: '>=14.17'} hasBin: true @@ -8517,15 +8517,15 @@ snapshots: '@types/yargs': 17.0.29 chalk: 4.1.2 - '@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@5.6.2)(vite@5.4.8(@types/node@20.16.10)(terser@5.31.1))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@5.7.2)(vite@5.4.8(@types/node@20.16.10)(terser@5.31.1))': dependencies: glob: 7.2.3 glob-promise: 4.2.2(glob@7.2.3) magic-string: 0.27.0 - react-docgen-typescript: 2.2.2(typescript@5.6.2) + react-docgen-typescript: 2.2.2(typescript@5.7.2) vite: 5.4.8(@types/node@20.16.10)(terser@5.31.1) optionalDependencies: - typescript: 5.6.2 + typescript: 5.7.2 '@jridgewell/gen-mapping@0.3.5': dependencies: @@ -9026,7 +9026,7 @@ snapshots: dependencies: storybook: 8.3.4 - '@storybook/builder-vite@8.3.4(storybook@8.3.4)(typescript@5.6.2)(vite@5.4.8(@types/node@20.16.10)(terser@5.31.1))(webpack-sources@3.2.3)': + '@storybook/builder-vite@8.3.4(storybook@8.3.4)(typescript@5.7.2)(vite@5.4.8(@types/node@20.16.10)(terser@5.31.1))(webpack-sources@3.2.3)': dependencies: '@storybook/csf-plugin': 8.3.4(storybook@8.3.4)(webpack-sources@3.2.3) '@types/find-cache-dir': 3.2.1 @@ -9040,7 +9040,7 @@ snapshots: ts-dedent: 2.2.0 vite: 5.4.8(@types/node@20.16.10)(terser@5.31.1) optionalDependencies: - typescript: 5.6.2 + typescript: 5.7.2 transitivePeerDependencies: - supports-color - webpack-sources @@ -9108,12 +9108,12 @@ snapshots: react-dom: 18.3.1(react@18.3.1) storybook: 8.3.4 - '@storybook/react-vite@8.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.23.0)(storybook@8.3.4)(typescript@5.6.2)(vite@5.4.8(@types/node@20.16.10)(terser@5.31.1))(webpack-sources@3.2.3)': + '@storybook/react-vite@8.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.23.0)(storybook@8.3.4)(typescript@5.7.2)(vite@5.4.8(@types/node@20.16.10)(terser@5.31.1))(webpack-sources@3.2.3)': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@5.6.2)(vite@5.4.8(@types/node@20.16.10)(terser@5.31.1)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@5.7.2)(vite@5.4.8(@types/node@20.16.10)(terser@5.31.1)) '@rollup/pluginutils': 5.1.2(rollup@4.23.0) - '@storybook/builder-vite': 8.3.4(storybook@8.3.4)(typescript@5.6.2)(vite@5.4.8(@types/node@20.16.10)(terser@5.31.1))(webpack-sources@3.2.3) - '@storybook/react': 8.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.4)(typescript@5.6.2) + '@storybook/builder-vite': 8.3.4(storybook@8.3.4)(typescript@5.7.2)(vite@5.4.8(@types/node@20.16.10)(terser@5.31.1))(webpack-sources@3.2.3) + '@storybook/react': 8.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.4)(typescript@5.7.2) find-up: 5.0.0 magic-string: 0.30.11 react: 18.3.1 @@ -9132,7 +9132,7 @@ snapshots: - vite-plugin-glimmerx - webpack-sources - '@storybook/react@8.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.4)(typescript@5.6.2)': + '@storybook/react@8.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.4)(typescript@5.7.2)': dependencies: '@storybook/components': 8.3.4(storybook@8.3.4) '@storybook/global': 5.0.0 @@ -9158,7 +9158,7 @@ snapshots: type-fest: 2.19.0 util-deprecate: 1.0.2 optionalDependencies: - typescript: 5.6.2 + typescript: 5.7.2 '@storybook/test-runner@0.19.1(@types/node@20.16.10)(babel-plugin-macros@3.1.0)(storybook@8.3.4)': dependencies: @@ -9638,34 +9638,34 @@ snapshots: '@types/node': 20.16.10 optional: true - '@typescript-eslint/eslint-plugin@7.14.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2)': + '@typescript-eslint/eslint-plugin@7.14.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2)': dependencies: '@eslint-community/regexpp': 4.10.1 - '@typescript-eslint/parser': 7.14.1(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/parser': 7.14.1(eslint@8.57.0)(typescript@5.7.2) '@typescript-eslint/scope-manager': 7.14.1 - '@typescript-eslint/type-utils': 7.14.1(eslint@8.57.0)(typescript@5.6.2) - '@typescript-eslint/utils': 7.14.1(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/type-utils': 7.14.1(eslint@8.57.0)(typescript@5.7.2) + '@typescript-eslint/utils': 7.14.1(eslint@8.57.0)(typescript@5.7.2) '@typescript-eslint/visitor-keys': 7.14.1 eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 - ts-api-utils: 1.3.0(typescript@5.6.2) + ts-api-utils: 1.3.0(typescript@5.7.2) optionalDependencies: - typescript: 5.6.2 + typescript: 5.7.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.6.2)': + '@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2)': dependencies: '@typescript-eslint/scope-manager': 7.14.1 '@typescript-eslint/types': 7.14.1 - '@typescript-eslint/typescript-estree': 7.14.1(typescript@5.6.2) + '@typescript-eslint/typescript-estree': 7.14.1(typescript@5.7.2) '@typescript-eslint/visitor-keys': 7.14.1 debug: 4.3.7 eslint: 8.57.0 optionalDependencies: - typescript: 5.6.2 + typescript: 5.7.2 transitivePeerDependencies: - supports-color @@ -9679,15 +9679,15 @@ snapshots: '@typescript-eslint/types': 7.14.1 '@typescript-eslint/visitor-keys': 7.14.1 - '@typescript-eslint/type-utils@7.14.1(eslint@8.57.0)(typescript@5.6.2)': + '@typescript-eslint/type-utils@7.14.1(eslint@8.57.0)(typescript@5.7.2)': dependencies: - '@typescript-eslint/typescript-estree': 7.14.1(typescript@5.6.2) - '@typescript-eslint/utils': 7.14.1(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/typescript-estree': 7.14.1(typescript@5.7.2) + '@typescript-eslint/utils': 7.14.1(eslint@8.57.0)(typescript@5.7.2) debug: 4.3.7 eslint: 8.57.0 - ts-api-utils: 1.3.0(typescript@5.6.2) + ts-api-utils: 1.3.0(typescript@5.7.2) optionalDependencies: - typescript: 5.6.2 + typescript: 5.7.2 transitivePeerDependencies: - supports-color @@ -9695,7 +9695,7 @@ snapshots: '@typescript-eslint/types@7.14.1': {} - '@typescript-eslint/typescript-estree@5.62.0(typescript@5.6.2)': + '@typescript-eslint/typescript-estree@5.62.0(typescript@5.7.2)': dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 @@ -9703,13 +9703,13 @@ snapshots: globby: 11.1.0 is-glob: 4.0.3 semver: 7.6.3 - tsutils: 3.21.0(typescript@5.6.2) + tsutils: 3.21.0(typescript@5.7.2) optionalDependencies: - typescript: 5.6.2 + typescript: 5.7.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@7.14.1(typescript@5.6.2)': + '@typescript-eslint/typescript-estree@7.14.1(typescript@5.7.2)': dependencies: '@typescript-eslint/types': 7.14.1 '@typescript-eslint/visitor-keys': 7.14.1 @@ -9718,20 +9718,20 @@ snapshots: is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.6.3 - ts-api-utils: 1.3.0(typescript@5.6.2) + ts-api-utils: 1.3.0(typescript@5.7.2) optionalDependencies: - typescript: 5.6.2 + typescript: 5.7.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.6.2)': + '@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.7.2)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@types/json-schema': 7.0.13 '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.6.2) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.7.2) eslint: 8.57.0 eslint-scope: 5.1.1 semver: 7.6.3 @@ -9739,12 +9739,12 @@ snapshots: - supports-color - typescript - '@typescript-eslint/utils@7.14.1(eslint@8.57.0)(typescript@5.6.2)': + '@typescript-eslint/utils@7.14.1(eslint@8.57.0)(typescript@5.7.2)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@typescript-eslint/scope-manager': 7.14.1 '@typescript-eslint/types': 7.14.1 - '@typescript-eslint/typescript-estree': 7.14.1(typescript@5.6.2) + '@typescript-eslint/typescript-estree': 7.14.1(typescript@5.7.2) eslint: 8.57.0 transitivePeerDependencies: - supports-color @@ -9929,7 +9929,7 @@ snapshots: app-builder-bin@5.0.0-alpha.10: {} - app-builder-lib@25.1.7(dmg-builder@25.1.7(electron-builder-squirrel-windows@25.1.7))(electron-builder-squirrel-windows@25.1.7(dmg-builder@25.1.7)): + app-builder-lib@25.1.7(dmg-builder@25.1.7)(electron-builder-squirrel-windows@25.1.7): dependencies: '@develar/schema-utils': 2.6.5 '@electron/notarize': 2.5.0 @@ -10562,7 +10562,7 @@ snapshots: config-file-ts@0.2.8-rc1: dependencies: glob: 10.4.5 - typescript: 5.6.2 + typescript: 5.7.2 console-control-strings@1.1.0: {} @@ -10862,7 +10862,7 @@ snapshots: dmg-builder@25.1.7(electron-builder-squirrel-windows@25.1.7): dependencies: - app-builder-lib: 25.1.7(dmg-builder@25.1.7(electron-builder-squirrel-windows@25.1.7))(electron-builder-squirrel-windows@25.1.7(dmg-builder@25.1.7)) + app-builder-lib: 25.1.7(dmg-builder@25.1.7)(electron-builder-squirrel-windows@25.1.7) builder-util: 25.1.7 builder-util-runtime: 9.2.10 fs-extra: 10.1.0 @@ -10948,7 +10948,7 @@ snapshots: electron-builder-squirrel-windows@25.1.7(dmg-builder@25.1.7): dependencies: - app-builder-lib: 25.1.7(dmg-builder@25.1.7(electron-builder-squirrel-windows@25.1.7))(electron-builder-squirrel-windows@25.1.7(dmg-builder@25.1.7)) + app-builder-lib: 25.1.7(dmg-builder@25.1.7)(electron-builder-squirrel-windows@25.1.7) archiver: 5.3.2 builder-util: 25.1.7 fs-extra: 10.1.0 @@ -10957,9 +10957,9 @@ snapshots: - dmg-builder - supports-color - electron-builder@25.1.7(electron-builder-squirrel-windows@25.1.7(dmg-builder@25.1.7)): + electron-builder@25.1.7(electron-builder-squirrel-windows@25.1.7): dependencies: - app-builder-lib: 25.1.7(dmg-builder@25.1.7(electron-builder-squirrel-windows@25.1.7))(electron-builder-squirrel-windows@25.1.7(dmg-builder@25.1.7)) + app-builder-lib: 25.1.7(dmg-builder@25.1.7)(electron-builder-squirrel-windows@25.1.7) builder-util: 25.1.7 builder-util-runtime: 9.2.10 chalk: 4.1.2 @@ -11232,43 +11232,43 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.6.2))(eslint-plugin-import@2.30.0)(eslint@8.57.0): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.30.0)(eslint@8.57.0): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.6 enhanced-resolve: 5.17.0 eslint: 8.57.0 - eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.6.2))(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.1.0 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.6.2))(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 7.14.1(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/parser': 7.14.1(eslint@8.57.0)(typescript@5.7.2) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.6.2))(eslint-plugin-import@2.30.0)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.30.0)(eslint@8.57.0) transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.2(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.6.2))(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.8.2(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 7.14.1(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/parser': 7.14.1(eslint@8.57.0)(typescript@5.7.2) eslint: 8.57.0 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.6.2))(eslint-plugin-import@2.30.0)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.30.0)(eslint@8.57.0) transitivePeerDependencies: - supports-color @@ -11277,7 +11277,7 @@ snapshots: eslint: 8.57.0 eslint-rule-composer: 0.3.0 - eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0): + eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -11288,7 +11288,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.6.2))(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -11299,7 +11299,7 @@ snapshots: semver: 6.3.1 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 7.14.1(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/parser': 7.14.1(eslint@8.57.0)(typescript@5.7.2) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -11313,12 +11313,12 @@ snapshots: optionalDependencies: '@testing-library/dom': 10.1.0 - eslint-plugin-jest@28.8.3(@typescript-eslint/eslint-plugin@7.14.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(jest@29.7.0(@types/node@22.7.4)(babel-plugin-macros@3.1.0))(typescript@5.6.2): + eslint-plugin-jest@28.8.3(@typescript-eslint/eslint-plugin@7.14.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(jest@29.7.0(@types/node@22.7.4)(babel-plugin-macros@3.1.0))(typescript@5.7.2): dependencies: - '@typescript-eslint/utils': 7.14.1(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/utils': 7.14.1(eslint@8.57.0)(typescript@5.7.2) eslint: 8.57.0 optionalDependencies: - '@typescript-eslint/eslint-plugin': 7.14.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/eslint-plugin': 7.14.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2) jest: 29.7.0(@types/node@22.7.4)(babel-plugin-macros@3.1.0) transitivePeerDependencies: - supports-color @@ -11350,9 +11350,9 @@ snapshots: string.prototype.matchall: 4.0.11 string.prototype.repeat: 1.0.0 - eslint-plugin-testing-library@6.3.0(eslint@8.57.0)(typescript@5.6.2): + eslint-plugin-testing-library@6.3.0(eslint@8.57.0)(typescript@5.7.2): dependencies: - '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.7.2) eslint: 8.57.0 transitivePeerDependencies: - supports-color @@ -13101,12 +13101,12 @@ snapshots: ms@2.1.3: {} - msw-storybook-addon@2.0.3(msw@2.4.9(typescript@5.6.2)): + msw-storybook-addon@2.0.3(msw@2.4.9(typescript@5.7.2)): dependencies: is-node-process: 1.2.0 - msw: 2.4.9(typescript@5.6.2) + msw: 2.4.9(typescript@5.7.2) - msw@2.4.9(typescript@5.6.2): + msw@2.4.9(typescript@5.7.2): dependencies: '@bundled-es-modules/cookie': 2.0.0 '@bundled-es-modules/statuses': 1.0.1 @@ -13126,7 +13126,7 @@ snapshots: type-fest: 4.26.1 yargs: 17.7.2 optionalDependencies: - typescript: 5.6.2 + typescript: 5.7.2 mute-stream@1.0.0: {} @@ -13579,9 +13579,9 @@ snapshots: '@types/node': 22.7.4 '@types/react': 18.3.10 - react-docgen-typescript@2.2.2(typescript@5.6.2): + react-docgen-typescript@2.2.2(typescript@5.7.2): dependencies: - typescript: 5.6.2 + typescript: 5.7.2 react-docgen@7.0.3: dependencies: @@ -14390,15 +14390,15 @@ snapshots: dependencies: utf8-byte-length: 1.0.4 - ts-api-utils@1.3.0(typescript@5.6.2): + ts-api-utils@1.3.0(typescript@5.7.2): dependencies: - typescript: 5.6.2 + typescript: 5.7.2 ts-dedent@2.2.0: {} - tsconfck@3.1.0(typescript@5.6.2): + tsconfck@3.1.0(typescript@5.7.2): optionalDependencies: - typescript: 5.6.2 + typescript: 5.7.2 tsconfig-paths@3.15.0: dependencies: @@ -14419,10 +14419,10 @@ snapshots: tslib@2.7.0: {} - tsutils@3.21.0(typescript@5.6.2): + tsutils@3.21.0(typescript@5.7.2): dependencies: tslib: 1.14.1 - typescript: 5.6.2 + typescript: 5.7.2 type-check@0.4.0: dependencies: @@ -14484,18 +14484,18 @@ snapshots: dependencies: is-typedarray: 1.0.0 - typescript-eslint@7.14.1(eslint@8.57.0)(typescript@5.6.2): + typescript-eslint@7.14.1(eslint@8.57.0)(typescript@5.7.2): dependencies: - '@typescript-eslint/eslint-plugin': 7.14.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2) - '@typescript-eslint/parser': 7.14.1(eslint@8.57.0)(typescript@5.6.2) - '@typescript-eslint/utils': 7.14.1(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/eslint-plugin': 7.14.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2) + '@typescript-eslint/parser': 7.14.1(eslint@8.57.0)(typescript@5.7.2) + '@typescript-eslint/utils': 7.14.1(eslint@8.57.0)(typescript@5.7.2) eslint: 8.57.0 optionalDependencies: - typescript: 5.6.2 + typescript: 5.7.2 transitivePeerDependencies: - supports-color - typescript@5.6.2: {} + typescript@5.7.2: {} unbox-primitive@1.0.2: dependencies: @@ -14598,11 +14598,11 @@ snapshots: dependencies: vite: 5.4.8(@types/node@22.7.4)(terser@5.31.1) - vite-tsconfig-paths@5.0.1(typescript@5.6.2)(vite@5.4.8(@types/node@22.7.4)(terser@5.31.1)): + vite-tsconfig-paths@5.0.1(typescript@5.7.2)(vite@5.4.8(@types/node@22.7.4)(terser@5.31.1)): dependencies: debug: 4.3.6 globrex: 0.1.2 - tsconfck: 3.1.0(typescript@5.6.2) + tsconfck: 3.1.0(typescript@5.7.2) optionalDependencies: vite: 5.4.8(@types/node@22.7.4)(terser@5.31.1) transitivePeerDependencies: diff --git a/web/packages/shared/utils/createMfaOptions.ts b/web/packages/shared/utils/createMfaOptions.ts index ecfec092a326d..da5d47541ffef 100644 --- a/web/packages/shared/utils/createMfaOptions.ts +++ b/web/packages/shared/utils/createMfaOptions.ts @@ -18,6 +18,8 @@ import { Auth2faType, PreferredMfaType } from 'shared/services/types'; +// Deprecated: use getMfaRegisterOptions or getMfaChallengeOptions instead. +// TODO(Joerger): Delete once no longer used. export default function createMfaOptions(opts: Options) { const { auth2faType, required = false } = opts; const mfaOptions: MfaOption[] = []; diff --git a/web/packages/teleport/src/Account/Account.tsx b/web/packages/teleport/src/Account/Account.tsx index ca86669ac0f1c..9cd8c47720e2b 100644 --- a/web/packages/teleport/src/Account/Account.tsx +++ b/web/packages/teleport/src/Account/Account.tsx @@ -38,10 +38,10 @@ import { import cfg from 'teleport/config'; -import { DeviceUsage } from 'teleport/services/auth'; - import { PasswordState } from 'teleport/services/user'; +import { DeviceUsage } from 'teleport/services/mfa'; + import { AuthDeviceList } from './ManageDevices/AuthDeviceList/AuthDeviceList'; import useManageDevices, { State as ManageDevicesState, diff --git a/web/packages/teleport/src/Account/ManageDevices/useManageDevices.ts b/web/packages/teleport/src/Account/ManageDevices/useManageDevices.ts index 95929bf9d9714..287bb9a77afc6 100644 --- a/web/packages/teleport/src/Account/ManageDevices/useManageDevices.ts +++ b/web/packages/teleport/src/Account/ManageDevices/useManageDevices.ts @@ -21,8 +21,8 @@ import useAttempt from 'shared/hooks/useAttemptNext'; import Ctx from 'teleport/teleportContext'; import cfg from 'teleport/config'; -import auth, { DeviceUsage } from 'teleport/services/auth'; -import { MfaDevice } from 'teleport/services/mfa'; +import auth from 'teleport/services/auth'; +import { DeviceUsage, MfaDevice } from 'teleport/services/mfa'; import { MfaChallengeScope } from 'teleport/services/auth/auth'; export default function useManageDevices(ctx: Ctx) { diff --git a/web/packages/teleport/src/Account/ManageDevices/wizards/AddAuthDeviceWizard.story.tsx b/web/packages/teleport/src/Account/ManageDevices/wizards/AddAuthDeviceWizard.story.tsx index 2598c48acd9b4..46b2bfc235e02 100644 --- a/web/packages/teleport/src/Account/ManageDevices/wizards/AddAuthDeviceWizard.story.tsx +++ b/web/packages/teleport/src/Account/ManageDevices/wizards/AddAuthDeviceWizard.story.tsx @@ -24,12 +24,13 @@ import Dialog from 'design/Dialog'; import { http, HttpResponse, delay } from 'msw'; -import { DeviceUsage } from 'teleport/services/auth'; import { createTeleportContext } from 'teleport/mocks/contexts'; import { ContextProvider } from 'teleport/index'; import cfg from 'teleport/config'; +import { DeviceUsage } from 'teleport/services/mfa'; + import { AddAuthDeviceWizardStepProps, CreateDeviceStep, diff --git a/web/packages/teleport/src/Account/ManageDevices/wizards/AddAuthDeviceWizard.tsx b/web/packages/teleport/src/Account/ManageDevices/wizards/AddAuthDeviceWizard.tsx index 800d7078da822..79cabebb57a1d 100644 --- a/web/packages/teleport/src/Account/ManageDevices/wizards/AddAuthDeviceWizard.tsx +++ b/web/packages/teleport/src/Account/ManageDevices/wizards/AddAuthDeviceWizard.tsx @@ -40,10 +40,9 @@ import { StepHeader } from 'design/StepSlider'; import { P } from 'design/Text/Text'; import auth from 'teleport/services/auth/auth'; -import { DeviceUsage } from 'teleport/services/auth'; import useTeleport from 'teleport/useTeleport'; -import { MfaDevice } from 'teleport/services/mfa'; +import { DeviceUsage, MfaDevice } from 'teleport/services/mfa'; import { PasskeyBlurb } from '../../../components/Passkeys/PasskeyBlurb'; diff --git a/web/packages/teleport/src/Discover/ConnectMyComputer/TestConnection/TestConnection.tsx b/web/packages/teleport/src/Discover/ConnectMyComputer/TestConnection/TestConnection.tsx index 3c8084966bd5c..1b5ae9e6a780a 100644 --- a/web/packages/teleport/src/Discover/ConnectMyComputer/TestConnection/TestConnection.tsx +++ b/web/packages/teleport/src/Discover/ConnectMyComputer/TestConnection/TestConnection.tsx @@ -57,7 +57,7 @@ import { NodeMeta } from '../../useDiscover'; import type { Option } from 'shared/components/Select'; import type { AgentStepProps } from '../../types'; -import type { MfaAuthnResponse } from 'teleport/services/mfa'; +import type { MfaChallengeResponse } from 'teleport/services/mfa'; import type { ConnectionDiagnosticRequest } from 'teleport/services/agents'; export function TestConnection(props: AgentStepProps) { @@ -144,7 +144,7 @@ export function TestConnection(props: AgentStepProps) { function testConnection(args: { login: string; sshPrincipalSelectionMode: ConnectionDiagnosticRequest['sshPrincipalSelectionMode']; - mfaResponse?: MfaAuthnResponse; + mfaResponse?: MfaChallengeResponse; }) { return runConnectionDiagnostic( { diff --git a/web/packages/teleport/src/Discover/Database/TestConnection/TestConnection.tsx b/web/packages/teleport/src/Discover/Database/TestConnection/TestConnection.tsx index 20ce37bbea8c0..5faaf84b6080a 100644 --- a/web/packages/teleport/src/Discover/Database/TestConnection/TestConnection.tsx +++ b/web/packages/teleport/src/Discover/Database/TestConnection/TestConnection.tsx @@ -32,7 +32,7 @@ import { CustomInputFieldForAsterisks } from 'teleport/Discover/Shared/CustomInp import { MfaChallengeScope } from 'teleport/services/auth/auth'; import { DbMeta, useDiscover } from 'teleport/Discover/useDiscover'; -import { MfaAuthnResponse } from 'teleport/services/mfa'; +import { MfaChallengeResponse } from 'teleport/services/mfa'; import { WILD_CARD } from 'teleport/Discover/Shared/const'; import { @@ -93,7 +93,7 @@ export function TestConnection() { function testConnection( validator: Validator, - mfaResponse?: MfaAuthnResponse + mfaResponse?: MfaChallengeResponse ) { if (!validator.validate()) { return; diff --git a/web/packages/teleport/src/Discover/Kubernetes/TestConnection/useTestConnection.ts b/web/packages/teleport/src/Discover/Kubernetes/TestConnection/useTestConnection.ts index 4c182604a85af..2ceeeba7cecd1 100644 --- a/web/packages/teleport/src/Discover/Kubernetes/TestConnection/useTestConnection.ts +++ b/web/packages/teleport/src/Discover/Kubernetes/TestConnection/useTestConnection.ts @@ -22,7 +22,7 @@ import { KubeMeta } from '../../useDiscover'; import type { KubeImpersonation } from 'teleport/services/agents'; import type { AgentStepProps } from '../../types'; -import type { MfaAuthnResponse } from 'teleport/services/mfa'; +import type { MfaChallengeResponse } from 'teleport/services/mfa'; /** * @deprecated Refactor Discover/Kubernetes/TestConnection away from the container component @@ -34,7 +34,7 @@ export function useTestConnection(props: AgentStepProps) { function testConnection( impersonate: KubeImpersonation, - mfaResponse?: MfaAuthnResponse + mfaResponse?: MfaChallengeResponse ) { runConnectionDiagnostic( { diff --git a/web/packages/teleport/src/Discover/Server/TestConnection/TestConnection.tsx b/web/packages/teleport/src/Discover/Server/TestConnection/TestConnection.tsx index 0df0fd0c42a09..54195cb3e6cc1 100644 --- a/web/packages/teleport/src/Discover/Server/TestConnection/TestConnection.tsx +++ b/web/packages/teleport/src/Discover/Server/TestConnection/TestConnection.tsx @@ -39,7 +39,7 @@ import { NodeMeta } from '../../useDiscover'; import type { Option } from 'shared/components/Select'; import type { AgentStepProps } from '../../types'; -import type { MfaAuthnResponse } from 'teleport/services/mfa'; +import type { MfaChallengeResponse } from 'teleport/services/mfa'; export function TestConnection(props: AgentStepProps) { const { @@ -65,7 +65,7 @@ export function TestConnection(props: AgentStepProps) { openNewTab(url); } - function testConnection(login: string, mfaResponse?: MfaAuthnResponse) { + function testConnection(login: string, mfaResponse?: MfaChallengeResponse) { runConnectionDiagnostic( { resourceKind: 'node', diff --git a/web/packages/teleport/src/Discover/Shared/ConnectionDiagnostic/useConnectionDiagnostic.ts b/web/packages/teleport/src/Discover/Shared/ConnectionDiagnostic/useConnectionDiagnostic.ts index ddedd3ffb7a42..3f5b7fc95097b 100644 --- a/web/packages/teleport/src/Discover/Shared/ConnectionDiagnostic/useConnectionDiagnostic.ts +++ b/web/packages/teleport/src/Discover/Shared/ConnectionDiagnostic/useConnectionDiagnostic.ts @@ -30,7 +30,7 @@ import type { ConnectionDiagnostic, ConnectionDiagnosticRequest, } from 'teleport/services/agents'; -import type { MfaAuthnResponse } from 'teleport/services/mfa'; +import type { MfaChallengeResponse } from 'teleport/services/mfa'; import type { ResourceSpec } from 'teleport/Discover/SelectResource'; export function useConnectionDiagnostic() { @@ -60,7 +60,7 @@ export function useConnectionDiagnostic() { */ async function runConnectionDiagnostic( req: ConnectionDiagnosticRequest, - mfaAuthnResponse?: MfaAuthnResponse + mfaAuthnResponse?: MfaChallengeResponse ): Promise<{ mfaRequired: boolean }> { setDiagnosis(null); // reset since user's can re-test connection. setRanDiagnosis(true); diff --git a/web/packages/teleport/src/Welcome/NewCredentials/types.ts b/web/packages/teleport/src/Welcome/NewCredentials/types.ts index 410480c318183..b88ff36b6396f 100644 --- a/web/packages/teleport/src/Welcome/NewCredentials/types.ts +++ b/web/packages/teleport/src/Welcome/NewCredentials/types.ts @@ -24,8 +24,9 @@ import { NewFlow, StepComponentProps } from 'design/StepSlider'; import { ReactElement } from 'react'; -import { DeviceUsage, RecoveryCodes, ResetToken } from 'teleport/services/auth'; +import { RecoveryCodes, ResetToken } from 'teleport/services/auth'; import { RecoveryCodesProps } from 'teleport/components/RecoveryCodes'; +import { DeviceUsage } from 'teleport/services/mfa'; export type UseTokenState = { auth2faType: Auth2faType; diff --git a/web/packages/teleport/src/Welcome/useToken.ts b/web/packages/teleport/src/Welcome/useToken.ts index cacf87ef2596d..6e00cea7e7965 100644 --- a/web/packages/teleport/src/Welcome/useToken.ts +++ b/web/packages/teleport/src/Welcome/useToken.ts @@ -23,13 +23,13 @@ import cfg from 'teleport/config'; import history from 'teleport/services/history'; import auth, { ChangedUserAuthn, - DeviceUsage, RecoveryCodes, ResetPasswordReqWithEvent, ResetPasswordWithWebauthnReqWithEvent, ResetToken, } from 'teleport/services/auth'; import { UseTokenState } from 'teleport/Welcome/NewCredentials/types'; +import { DeviceUsage } from 'teleport/services/mfa'; export default function useToken(tokenId: string): UseTokenState { const [resetToken, setResetToken] = useState(); diff --git a/web/packages/teleport/src/components/AuthnDialog/AuthnDialog.test.tsx b/web/packages/teleport/src/components/AuthnDialog/AuthnDialog.test.tsx index 5066113d24400..e4752390357e7 100644 --- a/web/packages/teleport/src/components/AuthnDialog/AuthnDialog.test.tsx +++ b/web/packages/teleport/src/components/AuthnDialog/AuthnDialog.test.tsx @@ -20,7 +20,8 @@ import React from 'react'; import { render, screen, fireEvent } from 'design/utils/testing'; import { makeDefaultMfaState, MfaState } from 'teleport/lib/useMfa'; -import { SSOChallenge } from 'teleport/services/auth'; + +import { SSOChallenge } from 'teleport/services/mfa'; import AuthnDialog from './AuthnDialog'; diff --git a/web/packages/teleport/src/components/ReAuthenticate/useReAuthenticate.ts b/web/packages/teleport/src/components/ReAuthenticate/useReAuthenticate.ts index 3e60ee586c2dc..d6500860c8ae4 100644 --- a/web/packages/teleport/src/components/ReAuthenticate/useReAuthenticate.ts +++ b/web/packages/teleport/src/components/ReAuthenticate/useReAuthenticate.ts @@ -22,7 +22,7 @@ import cfg from 'teleport/config'; import auth from 'teleport/services/auth'; import { MfaChallengeScope } from 'teleport/services/auth/auth'; -import type { MfaAuthnResponse } from 'teleport/services/mfa'; +import type { MfaChallengeResponse } from 'teleport/services/mfa'; // useReAuthenticate will have different "submit" behaviors depending on: // - If prop field `onMfaResponse` is defined, after a user submits, the @@ -121,7 +121,7 @@ type BaseProps = { // that accepts a MFA response. No // authentication has been done at this point. type MfaResponseProps = BaseProps & { - onMfaResponse(res: MfaAuthnResponse): void; + onMfaResponse(res: MfaChallengeResponse): void; /** * The MFA challenge scope of the action to perform, as defined in webauthn.proto. */ diff --git a/web/packages/teleport/src/config.ts b/web/packages/teleport/src/config.ts index 445269fada1c9..11befae58598c 100644 --- a/web/packages/teleport/src/config.ts +++ b/web/packages/teleport/src/config.ts @@ -34,7 +34,7 @@ import type { import type { SortType } from 'teleport/services/agents'; import type { RecordingType } from 'teleport/services/recordings'; -import type { WebauthnAssertionResponse } from './services/auth'; +import type { WebauthnAssertionResponse } from './services/mfa'; import type { PluginKind, Regions, diff --git a/web/packages/teleport/src/lib/EventEmitterMfaSender.ts b/web/packages/teleport/src/lib/EventEmitterMfaSender.ts index 473ab8129221a..da30f1201e0c9 100644 --- a/web/packages/teleport/src/lib/EventEmitterMfaSender.ts +++ b/web/packages/teleport/src/lib/EventEmitterMfaSender.ts @@ -21,7 +21,7 @@ import { EventEmitter } from 'events'; import { MfaChallengeResponse, WebauthnAssertionResponse, -} from 'teleport/services/auth'; +} from 'teleport/services/mfa'; class EventEmitterMfaSender extends EventEmitter { constructor() { diff --git a/web/packages/teleport/src/lib/tdp/client.ts b/web/packages/teleport/src/lib/tdp/client.ts index 98e3121b5dbc7..ca18c58744124 100644 --- a/web/packages/teleport/src/lib/tdp/client.ts +++ b/web/packages/teleport/src/lib/tdp/client.ts @@ -57,7 +57,7 @@ import type { SyncKeys, SharedDirectoryTruncateResponse, } from './codec'; -import type { WebauthnAssertionResponse } from 'teleport/services/auth'; +import type { WebauthnAssertionResponse } from 'teleport/services/mfa'; export enum TdpClientEvent { TDP_CLIENT_SCREEN_SPEC = 'tdp client screen spec', diff --git a/web/packages/teleport/src/lib/term/tty.ts b/web/packages/teleport/src/lib/term/tty.ts index d5d43845b1141..6b28a592e659f 100644 --- a/web/packages/teleport/src/lib/term/tty.ts +++ b/web/packages/teleport/src/lib/term/tty.ts @@ -19,12 +19,11 @@ import Logger from 'shared/libs/logger'; import { EventEmitterMfaSender } from 'teleport/lib/EventEmitterMfaSender'; -import { - MfaChallengeResponse, - WebauthnAssertionResponse, -} from 'teleport/services/auth'; +import { WebauthnAssertionResponse } from 'teleport/services/mfa'; import { AuthenticatedWebSocket } from 'teleport/lib/AuthenticatedWebSocket'; +import { MfaChallengeResponse } from 'teleport/services/mfa'; + import { EventType, TermEvent, WebsocketCloseCode } from './enums'; import { Protobuf, MessageTypeEnum } from './protobuf'; diff --git a/web/packages/teleport/src/lib/useMfa.ts b/web/packages/teleport/src/lib/useMfa.ts index 7b4b5fc68f839..d5c82e678d3b9 100644 --- a/web/packages/teleport/src/lib/useMfa.ts +++ b/web/packages/teleport/src/lib/useMfa.ts @@ -21,10 +21,13 @@ import { useState, useEffect, useCallback } from 'react'; import { EventEmitterMfaSender } from 'teleport/lib/EventEmitterMfaSender'; import { TermEvent } from 'teleport/lib/term/enums'; import { - makeMfaAuthenticateChallenge, + parseMfaChallengeJson as parseMfaChallenge, makeWebauthnAssertionResponse, +} from 'teleport/services/mfa/makeMfa'; +import { + MfaAuthenticateChallengeJson, SSOChallenge, -} from 'teleport/services/auth'; +} from 'teleport/services/mfa'; export function useMfa(emitterSender: EventEmitterMfaSender): MfaState { const [state, setState] = useState<{ @@ -129,8 +132,12 @@ export function useMfa(emitterSender: EventEmitterMfaSender): MfaState { useEffect(() => { let ssoChallengeAbortController: AbortController | undefined; const challengeHandler = (challengeJson: string) => { + const challenge = JSON.parse( + challengeJson + ) as MfaAuthenticateChallengeJson; + const { webauthnPublicKey, ssoChallenge, totpChallenge } = - makeMfaAuthenticateChallenge(challengeJson); + parseMfaChallenge(challenge); setState(prevState => ({ ...prevState, diff --git a/web/packages/teleport/src/services/agents/types.ts b/web/packages/teleport/src/services/agents/types.ts index 5dafd082404ff..b306910da95c3 100644 --- a/web/packages/teleport/src/services/agents/types.ts +++ b/web/packages/teleport/src/services/agents/types.ts @@ -26,7 +26,7 @@ import { Desktop } from 'teleport/services/desktops'; import { UserGroup } from '../userGroups'; -import type { MfaAuthnResponse } from '../mfa'; +import type { MfaChallengeResponse } from '../mfa'; import type { Platform } from 'design/platform'; export type UnifiedResource = @@ -143,7 +143,7 @@ export type ConnectionDiagnosticRequest = { sshNodeSetupMethod?: 'script' | 'connect_my_computer'; // `json:"ssh_node_setup_method"` kubeImpersonation?: KubeImpersonation; // `json:"kubernetes_impersonation"` dbTester?: DatabaseTester; - mfaAuthnResponse?: MfaAuthnResponse; + mfaAuthnResponse?: MfaChallengeResponse; }; export type KubeImpersonation = { diff --git a/web/packages/teleport/src/services/api/api.ts b/web/packages/teleport/src/services/api/api.ts index f1259558fee2e..9491a3bfbafd5 100644 --- a/web/packages/teleport/src/services/api/api.ts +++ b/web/packages/teleport/src/services/api/api.ts @@ -21,7 +21,7 @@ import auth, { MfaChallengeScope } from 'teleport/services/auth/auth'; import websession from 'teleport/services/websession'; import { storageService } from '../storageService'; -import { WebauthnAssertionResponse } from '../auth'; +import { WebauthnAssertionResponse } from '../mfa'; import parseError, { ApiError } from './parseError'; diff --git a/web/packages/teleport/src/services/auth/auth.ts b/web/packages/teleport/src/services/auth/auth.ts index 4dec9b2ade0f0..0d126151e20bf 100644 --- a/web/packages/teleport/src/services/auth/auth.ts +++ b/web/packages/teleport/src/services/auth/auth.ts @@ -18,25 +18,25 @@ import api from 'teleport/services/api'; import cfg from 'teleport/config'; -import { DeviceType } from 'teleport/services/mfa'; +import { DeviceType, DeviceUsage } from 'teleport/services/mfa'; import { CaptureEvent, userEventService } from 'teleport/services/userEvent'; -import makePasswordToken from './makePasswordToken'; -import { makeChangedUserAuthn } from './make'; import { - makeMfaAuthenticateChallenge, - makeMfaRegistrationChallenge, + parseMfaChallengeJson, + parseMfaRegistrationChallengeJson, makeWebauthnAssertionResponse, makeWebauthnCreationResponse, -} from './makeMfa'; +} from '../mfa/makeMfa'; + +import makePasswordToken from './makePasswordToken'; +import { makeChangedUserAuthn } from './make'; import { ResetPasswordReqWithEvent, ResetPasswordWithWebauthnReqWithEvent, UserCredentials, ChangePasswordReq, CreateNewHardwareDeviceRequest, - DeviceUsage, CreateAuthenticateChallengeRequest, } from './types'; @@ -65,7 +65,7 @@ const auth = { deviceType, deviceUsage, }) - .then(makeMfaRegistrationChallenge); + .then(parseMfaRegistrationChallengeJson); }, /** @@ -94,7 +94,7 @@ const auth = { createMfaAuthnChallengeWithToken(tokenId: string) { return api .post(cfg.getAuthnChallengeWithTokenUrl(tokenId)) - .then(makeMfaAuthenticateChallenge); + .then(parseMfaChallengeJson); }, // mfaLoginBegin retrieves users mfa challenges for their @@ -107,7 +107,7 @@ const auth = { user: creds?.username, pass: creds?.password, }) - .then(makeMfaAuthenticateChallenge); + .then(parseMfaChallengeJson); }, login(userId: string, password: string, otpCode: string) { @@ -270,7 +270,7 @@ const auth = { }, abortSignal ) - .then(makeMfaAuthenticateChallenge); + .then(parseMfaChallengeJson); }, async fetchWebAuthnChallenge( @@ -291,7 +291,7 @@ const auth = { }, abortSignal ) - .then(makeMfaAuthenticateChallenge) + .then(parseMfaChallengeJson) ) .then(res => navigator.credentials.get({ diff --git a/web/packages/teleport/src/services/auth/index.ts b/web/packages/teleport/src/services/auth/index.ts index 49a512d6ba553..5ad320f541ba5 100644 --- a/web/packages/teleport/src/services/auth/index.ts +++ b/web/packages/teleport/src/services/auth/index.ts @@ -18,7 +18,7 @@ import service from './auth'; -export * from './makeMfa'; +export * from '../mfa/makeMfa'; export * from './make'; export * from './types'; export default service; diff --git a/web/packages/teleport/src/services/auth/makeMfa.ts b/web/packages/teleport/src/services/auth/makeMfa.ts deleted file mode 100644 index 163bcd007b041..0000000000000 --- a/web/packages/teleport/src/services/auth/makeMfa.ts +++ /dev/null @@ -1,167 +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 { base64urlToBuffer, bufferToBase64url } from 'shared/utils/base64'; - -import { MfaAuthenticateChallenge, MfaRegistrationChallenge } from './types'; - -// makeMfaRegistrationChallenge formats fetched register challenge JSON. -// Webauthn challange contains Base64URL(byte) fields that needs to -// be converted to ArrayBuffer expected by navigator.credentials.create: -// - challenge -// - user.id -// - excludeCredentials[i].id -export function makeMfaRegistrationChallenge(json): MfaRegistrationChallenge { - const webauthnPublicKey = json.webauthn?.publicKey; - if (webauthnPublicKey) { - const challenge = webauthnPublicKey.challenge || ''; - const id = webauthnPublicKey.user?.id || ''; - const excludeCredentials = webauthnPublicKey.excludeCredentials || []; - - webauthnPublicKey.challenge = base64urlToBuffer(challenge); - webauthnPublicKey.user.id = base64urlToBuffer(id); - webauthnPublicKey.excludeCredentials = excludeCredentials.map( - (credential, i) => { - excludeCredentials[i].id = base64urlToBuffer(credential.id); - return excludeCredentials[i]; - } - ); - } - - return { - qrCode: json.totp?.qrCode, - webauthnPublicKey, - }; -} - -// makeMfaAuthenticateChallenge formats fetched authenticate challenge JSON. -// 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 challenge = typeof json === 'string' ? JSON.parse(json) : json; - const { sso_challenge, webauthn_challenge, totp_challenge } = challenge; - - const webauthnPublicKey = webauthn_challenge?.publicKey; - if (webauthnPublicKey) { - const challenge = webauthnPublicKey.challenge || ''; - const allowCredentials = webauthnPublicKey.allowCredentials || []; - - webauthnPublicKey.challenge = base64urlToBuffer(challenge); - webauthnPublicKey.allowCredentials = allowCredentials.map( - (credential, i) => { - allowCredentials[i].id = base64urlToBuffer(credential.id); - return allowCredentials[i]; - } - ); - } - - return { - ssoChallenge: sso_challenge, - totpChallenge: totp_challenge, - webauthnPublicKey: webauthnPublicKey, - }; -} - -// makeWebauthnCreationResponse takes response from navigator.credentials.create -// and creates a credential object expected by the server with ArrayBuffer -// fields converted to Base64URL: -// - rawId -// - response.attestationObject -// - response.clientDataJSON -export function makeWebauthnCreationResponse(res) { - // Response can be null if no Credential object can be created. - if (!res) { - throw new Error('error creating credential, please try again'); - } - - const clientExtentions = res.getClientExtensionResults(); - return { - id: res.id, - type: res.type, - extensions: { - appid: Boolean(clientExtentions?.appid), - credProps: clientExtentions?.credProps, - }, - rawId: bufferToBase64url(res.rawId), - response: { - attestationObject: bufferToBase64url(res.response?.attestationObject), - clientDataJSON: bufferToBase64url(res.response?.clientDataJSON), - }, - }; -} - -// makeWebauthnAssertionResponse takes response from navigator.credentials.get -// and creates a credential object expected by the server with ArrayBuffer -// fields converted to Base64URL: -// - rawId -// - response.authenticatorData -// - response.clientDataJSON -// - response.signature -// - response.userHandle -export function makeWebauthnAssertionResponse(res): WebauthnAssertionResponse { - // Response can be null if Credential cannot be unambiguously obtained. - if (!res) { - throw new Error( - 'error obtaining credential from the hardware key, please try again' - ); - } - - const clientExtentions = res.getClientExtensionResults(); - - return { - id: res.id, - type: res.type, - extensions: { - appid: Boolean(clientExtentions?.appid), - }, - rawId: bufferToBase64url(res.rawId), - response: { - authenticatorData: bufferToBase64url(res.response?.authenticatorData), - clientDataJSON: bufferToBase64url(res.response?.clientDataJSON), - signature: bufferToBase64url(res.response?.signature), - userHandle: bufferToBase64url(res.response?.userHandle), - }, - }; -} - -export type SsoChallengeResponse = { - requestId: string; - token: string; -}; - -export type WebauthnAssertionResponse = { - id: string; - type: string; - extensions: { - appid: boolean; - }; - rawId: string; - response: { - authenticatorData: string; - clientDataJSON: string; - signature: string; - userHandle: string; - }; -}; - -export type MfaChallengeResponse = { - webauthn_response?: WebauthnAssertionResponse; - sso_response?: SsoChallengeResponse; -}; diff --git a/web/packages/teleport/src/services/auth/types.ts b/web/packages/teleport/src/services/auth/types.ts index 734cb53a53112..ae9818ef2ebb7 100644 --- a/web/packages/teleport/src/services/auth/types.ts +++ b/web/packages/teleport/src/services/auth/types.ts @@ -16,10 +16,10 @@ * along with this program. If not, see . */ -import { AuthProviderType } from 'shared/services'; - import { EventMeta } from 'teleport/services/userEvent'; +import { DeviceUsage } from '../mfa'; + import { IsMfaRequiredRequest, MfaChallengeScope } from './auth'; export type Base64urlString = string; @@ -29,33 +29,6 @@ export type UserCredentials = { password: string; }; -export type AuthnChallengeRequest = { - tokenId?: string; - userCred: UserCredentials; -}; - -export type SSOChallenge = { - channelId: string; - redirectUrl: string; - requestId: string; - device: { - connectorId: string; - connectorType: AuthProviderType; - displayName: string; - }; -}; - -export type MfaAuthenticateChallenge = { - ssoChallenge: SSOChallenge; - totpChallenge: boolean; - webauthnPublicKey: PublicKeyCredentialRequestOptions; -}; - -export type MfaRegistrationChallenge = { - qrCode: Base64urlString; - webauthnPublicKey: PublicKeyCredentialCreationOptions; -}; - export type RecoveryCodes = { codes?: string[]; createdDate: Date; @@ -108,6 +81,3 @@ export type CreateNewHardwareDeviceRequest = { tokenId: string; deviceUsage?: DeviceUsage; }; - -/** The intended usage of the device (as an MFA method or a passkey). */ -export type DeviceUsage = 'passwordless' | 'mfa'; diff --git a/web/packages/teleport/src/services/mfa/index.ts b/web/packages/teleport/src/services/mfa/index.ts index ca78004d1c6a3..724c5e785d071 100644 --- a/web/packages/teleport/src/services/mfa/index.ts +++ b/web/packages/teleport/src/services/mfa/index.ts @@ -20,3 +20,4 @@ import MfaService from './mfa'; export default MfaService; export * from './types'; +export * from './mfaOptions'; diff --git a/web/packages/teleport/src/services/mfa/makeMfa.ts b/web/packages/teleport/src/services/mfa/makeMfa.ts new file mode 100644 index 0000000000000..0ec7c28f88071 --- /dev/null +++ b/web/packages/teleport/src/services/mfa/makeMfa.ts @@ -0,0 +1,159 @@ +/** + * Teleport + * Copyright (C) 2024 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 { base64urlToBuffer, bufferToBase64url } from 'shared/utils/base64'; + +import { + MfaAuthenticateChallenge, + MfaAuthenticateChallengeJson, + MfaRegistrationChallenge, + MfaRegistrationChallengeJson, + WebauthnAssertionResponse, + WebauthnAttestationResponse, +} from './types'; + +// parseMfaRegistrationChallengeJson formats fetched register challenge JSON. +export function parseMfaRegistrationChallengeJson( + challenge: MfaRegistrationChallengeJson +): MfaRegistrationChallenge { + // WebAuthn challenge contains Base64URL(byte) fields that needs to + // be converted to ArrayBuffer expected by navigator.credentials.create: + // - challenge + // - user.id + // - excludeCredentials[i].id + const webauthnPublicKeyFromJson = ( + json: PublicKeyCredentialCreationOptionsJSON + ) => + ({ + ...json, + challenge: base64urlToBuffer(json.challenge), + user: { + ...json.user, + id: base64urlToBuffer(json.user?.id || ''), + }, + excludeCredentials: json.excludeCredentials?.map(credential => ({ + ...credential, + id: base64urlToBuffer(credential.id), + })), + }) as PublicKeyCredentialCreationOptions; + + return { + qrCode: challenge.totp?.qrCode, + webauthnPublicKey: challenge.webauthn + ? webauthnPublicKeyFromJson(challenge.webauthn.publicKey) + : null, + }; +} + +// parseMfaChallengeJson formats fetched authenticate challenge JSON. +export function parseMfaChallengeJson( + challenge: MfaAuthenticateChallengeJson +): MfaAuthenticateChallenge { + // WebAuthn challenge contains Base64URL(byte) fields that needs to + // be converted to ArrayBuffer expected by navigator.credentials.get: + // - challenge + // - allowCredentials[i].id + const webauthnPublicKeyFromJson = ( + json: PublicKeyCredentialRequestOptionsJSON + ) => + ({ + ...json, + challenge: base64urlToBuffer(json.challenge), + allowCredentials: json.allowCredentials?.map(credential => ({ + ...credential, + id: base64urlToBuffer(credential.id), + })), + }) as PublicKeyCredentialRequestOptions; + + return { + ssoChallenge: challenge.sso_challenge, + totpChallenge: challenge.totp_challenge, + webauthnPublicKey: challenge.webauthn_challenge + ? webauthnPublicKeyFromJson(challenge.webauthn_challenge.publicKey) + : null, + }; +} + +// makeWebauthnCreationResponse takes a credential returned from navigator.credentials.create +// and returns the credential attestation response. +export function makeWebauthnCreationResponse( + cred: Credential +): WebauthnAttestationResponse { + const publicKey = cred as PublicKeyCredential; + + // Response can be null if no Credential object can be created. + if (!publicKey) { + throw new Error('error creating credential, please try again'); + } + + const clientExtentions = publicKey.getClientExtensionResults(); + const attestationResponse = + publicKey.response as AuthenticatorAttestationResponse; + + return { + id: cred.id, + type: cred.type, + extensions: { + appid: Boolean(clientExtentions?.appid), + credProps: clientExtentions?.credProps, + }, + rawId: bufferToBase64url(publicKey.rawId), + response: { + attestationObject: bufferToBase64url( + attestationResponse?.attestationObject + ), + clientDataJSON: bufferToBase64url(attestationResponse?.clientDataJSON), + }, + }; +} + +// makeWebauthnAssertionResponse takes a credential returned from navigator.credentials.get +// and returns the credential assertion response. +export function makeWebauthnAssertionResponse( + cred: Credential +): WebauthnAssertionResponse { + const publicKey = cred as PublicKeyCredential; + + // Response can be null if Credential cannot be unambiguously obtained. + if (!publicKey) { + throw new Error( + 'error obtaining credential from the hardware key, please try again' + ); + } + + const clientExtentions = publicKey.getClientExtensionResults(); + const assertionResponse = + publicKey.response as AuthenticatorAssertionResponse; + + return { + id: cred.id, + type: cred.type, + extensions: { + appid: Boolean(clientExtentions?.appid), + }, + rawId: bufferToBase64url(publicKey.rawId), + response: { + authenticatorData: bufferToBase64url( + assertionResponse?.authenticatorData + ), + clientDataJSON: bufferToBase64url(assertionResponse?.clientDataJSON), + signature: bufferToBase64url(assertionResponse?.signature), + userHandle: bufferToBase64url(assertionResponse?.userHandle), + }, + }; +} diff --git a/web/packages/teleport/src/services/mfa/mfaOptions.test.ts b/web/packages/teleport/src/services/mfa/mfaOptions.test.ts new file mode 100644 index 0000000000000..5c430a05be8a8 --- /dev/null +++ b/web/packages/teleport/src/services/mfa/mfaOptions.test.ts @@ -0,0 +1,105 @@ +/** + * Teleport + * Copyright (C) 2024 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 { Auth2faType } from 'shared/services'; + +import { SSOChallenge } from 'gen-proto-ts/teleport/lib/teleterm/v1/tshd_events_service_pb'; + +import { getMfaChallengeOptions, getMfaRegisterOptions } from './mfaOptions'; +import { DeviceType, MfaAuthenticateChallenge } from './types'; + +describe('test retrieving mfa options from Auth2faType', () => { + const testCases: { + name: string; + type?: Auth2faType; + expect: DeviceType[]; + }[] = [ + { + name: 'type undefined', + expect: [], + }, + { + name: 'type on', + type: 'on', + expect: ['webauthn', 'totp'], + }, + { + name: 'type webauthn only', + type: 'webauthn', + expect: ['webauthn'], + }, + { + name: 'type otp only', + type: 'otp', + expect: ['totp'], + }, + ]; + + test.each(testCases)('$name', testCase => { + const mfa = getMfaRegisterOptions(testCase.type).map(o => o.value); + expect(mfa).toEqual(testCase.expect); + }); +}); + +describe('test retrieving mfa options from MFA Challenge', () => { + const testCases: { + name: string; + challenge?: MfaAuthenticateChallenge; + expect: DeviceType[]; + }[] = [ + { + name: 'type undefined', + expect: [], + }, + { + name: 'challenge totp', + challenge: { + totpChallenge: true, + }, + expect: ['totp'], + }, + { + name: 'challenge webauthn', + challenge: { + webauthnPublicKey: {} as PublicKeyCredentialRequestOptions, + }, + expect: ['webauthn'], + }, + { + name: 'challenge sso', + challenge: { + ssoChallenge: Object.create(SSOChallenge), + }, + expect: ['sso'], + }, + { + name: 'challenge all', + challenge: { + totpChallenge: true, + webauthnPublicKey: {} as PublicKeyCredentialRequestOptions, + ssoChallenge: Object.create(SSOChallenge), + }, + expect: ['webauthn', 'totp', 'sso'], + }, + ]; + + test.each(testCases)('$name', testCase => { + const mfa = getMfaChallengeOptions(testCase.challenge).map(o => o.value); + expect(mfa).toEqual(testCase.expect); + }); +}); diff --git a/web/packages/teleport/src/services/mfa/mfaOptions.ts b/web/packages/teleport/src/services/mfa/mfaOptions.ts new file mode 100644 index 0000000000000..4bbe1dceb65f1 --- /dev/null +++ b/web/packages/teleport/src/services/mfa/mfaOptions.ts @@ -0,0 +1,78 @@ +/** + * Teleport + * Copyright (C) 2024 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 { Auth2faType } from 'shared/services'; + +import { DeviceType, MfaAuthenticateChallenge, SSOChallenge } from './types'; + +export function getMfaChallengeOptions(mfaChallenge: MfaAuthenticateChallenge) { + const mfaOptions: MfaOption[] = []; + + if (mfaChallenge?.webauthnPublicKey) { + mfaOptions.push(MFA_OPTION_WEBAUTHN); + } + + if (mfaChallenge?.totpChallenge) { + mfaOptions.push(MFA_OPTION_TOTP); + } + + if (mfaChallenge?.ssoChallenge) { + mfaOptions.push(getSsoOption(mfaChallenge.ssoChallenge)); + } + + return mfaOptions; +} + +export function getMfaRegisterOptions(auth2faType: Auth2faType) { + const mfaOptions: MfaOption[] = []; + + if (auth2faType === 'webauthn' || auth2faType === 'on') { + mfaOptions.push(MFA_OPTION_WEBAUTHN); + } + + if (auth2faType === 'otp' || auth2faType === 'on') { + mfaOptions.push(MFA_OPTION_TOTP); + } + + return mfaOptions; +} + +export type MfaOption = { + value: DeviceType; + label: string; +}; + +const MFA_OPTION_WEBAUTHN: MfaOption = { + value: 'webauthn', + label: 'Passkey or Security Key', +}; + +const MFA_OPTION_TOTP: MfaOption = { + value: 'totp', + label: 'Authenticator App', +}; + +const getSsoOption = (ssoChallenge: SSOChallenge): MfaOption => { + return { + value: 'sso', + label: + ssoChallenge.device?.displayName || + ssoChallenge.device?.connectorId || + 'SSO', + }; +}; diff --git a/web/packages/teleport/src/services/mfa/types.ts b/web/packages/teleport/src/services/mfa/types.ts index 6d1752c4082a1..f1292c50c99cd 100644 --- a/web/packages/teleport/src/services/mfa/types.ts +++ b/web/packages/teleport/src/services/mfa/types.ts @@ -16,10 +16,16 @@ * along with this program. If not, see . */ -import { WebauthnAssertionResponse } from '../auth'; -import { DeviceUsage } from '../auth/types'; +import { AuthProviderType } from 'shared/services'; + +import { Base64urlString } from '../auth/types'; import { CreateNewHardwareDeviceRequest } from '../auth/types'; +export type DeviceType = 'totp' | 'webauthn' | 'sso'; + +/** The intended usage of the device (as an MFA method or a passkey). */ +export type DeviceUsage = 'passwordless' | 'mfa'; + export interface MfaDevice { id: string; name: string; @@ -45,9 +51,81 @@ export type SaveNewHardwareDeviceRequest = { credential: Credential; }; -export type DeviceType = 'totp' | 'webauthn' | 'sso'; +export type MfaAuthenticateChallengeJson = { + sso_challenge?: SSOChallenge; + totp_challenge?: boolean; + webauthn_challenge?: { + publicKey: PublicKeyCredentialRequestOptionsJSON; + }; +}; + +export type MfaAuthenticateChallenge = { + ssoChallenge?: SSOChallenge; + totpChallenge?: boolean; + webauthnPublicKey?: PublicKeyCredentialRequestOptions; +}; + +export type SSOChallenge = { + channelId: string; + redirectUrl: string; + requestId: string; + device: { + connectorId: string; + connectorType: AuthProviderType; + displayName: string; + }; +}; + +export type MfaRegistrationChallengeJson = { + totp?: { + qrCode: Base64urlString; + }; + webauthn?: { + publicKey: PublicKeyCredentialCreationOptionsJSON; + }; +}; -// MfaAuthnResponse is a response to a MFA device challenge. -export type MfaAuthnResponse = - | { totp_code: string } - | { webauthn_response: WebauthnAssertionResponse }; +export type MfaRegistrationChallenge = { + qrCode: Base64urlString; + webauthnPublicKey: PublicKeyCredentialCreationOptions; +}; + +export type MfaChallengeResponse = { + totp_code?: string; + webauthn_response?: WebauthnAssertionResponse; + sso_response?: SsoChallengeResponse; +}; + +export type SsoChallengeResponse = { + requestId: string; + token: string; +}; + +export type WebauthnAssertionResponse = { + id: string; + type: string; + extensions: { + appid: boolean; + }; + rawId: string; + response: { + authenticatorData: string; + clientDataJSON: string; + signature: string; + userHandle: string; + }; +}; + +export type WebauthnAttestationResponse = { + id: string; + type: string; + extensions: { + appid: boolean; + credProps: CredentialPropertiesOutput; + }; + rawId: string; + response: { + attestationObject: string; + clientDataJSON: string; + }; +}; diff --git a/web/packages/teleport/src/services/user/user.ts b/web/packages/teleport/src/services/user/user.ts index 5584159528580..fc897d31fbcdc 100644 --- a/web/packages/teleport/src/services/user/user.ts +++ b/web/packages/teleport/src/services/user/user.ts @@ -20,7 +20,7 @@ import api from 'teleport/services/api'; import cfg from 'teleport/config'; import session from 'teleport/services/websession'; -import { WebauthnAssertionResponse } from '../auth'; +import { WebauthnAssertionResponse } from '../mfa'; import makeUserContext from './makeUserContext'; import { makeResetToken } from './makeResetToken';