diff --git a/cspell.json b/cspell.json index 5cc365f37ff12..a25fee56fe73f 100644 --- a/cspell.json +++ b/cspell.json @@ -756,6 +756,7 @@ "Warchoł", "WDYR", "webapps", + "webauthn", "webcredentials", "webrtc", "welldone", diff --git a/src/CONST/index.ts b/src/CONST/index.ts index ba2f5a4d872a1..e67ff098d619c 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -7,6 +7,7 @@ import type {ValueOf} from 'type-fest'; import type {SearchFilterKey} from '@components/Search/types'; import type ResponsiveLayoutResult from '@hooks/useResponsiveLayout/types'; import type {MileageRate} from '@libs/DistanceRequestUtils'; +import MULTIFACTOR_AUTHENTICATION_VALUES from '@libs/MultifactorAuthentication/Biometrics/VALUES'; import addTrailingForwardSlash from '@libs/UrlUtils'; import variables from '@styles/variables'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -423,6 +424,8 @@ const CONST = { MAX_AGE: 150, }, + MULTIFACTOR_AUTHENTICATION: MULTIFACTOR_AUTHENTICATION_VALUES, + DESKTOP_SHORTCUT_ACCELERATOR: { PASTE_AND_MATCH_STYLE: 'Option+Shift+CmdOrCtrl+V', PASTE_AS_PLAIN_TEXT: 'CmdOrCtrl+Shift+V', @@ -5501,7 +5504,7 @@ const CONST = { DISABLED: 'DISABLED', DISABLE: 'DISABLE', }, - MULTIFACTOR_AUTHENTICATION_NOTIFICATION_TYPE: { + MULTIFACTOR_AUTHENTICATION_OUTCOME_TYPE: { SUCCESS: 'success', FAILURE: 'failure', }, diff --git a/src/ROUTES.ts b/src/ROUTES.ts index cfcfe09941dfa..599500c171609 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -3719,11 +3719,11 @@ const ROUTES = { MULTIFACTOR_AUTHENTICATION_MAGIC_CODE: `${MULTIFACTOR_AUTHENTICATION_PROTECTED_ROUTES.FACTOR}/magic-code`, MULTIFACTOR_AUTHENTICATION_BIOMETRICS_TEST: 'multifactor-authentication/scenario/biometrics-test', - // The exact notification & prompt type will be added as a part of Multifactor Authentication config in another PR, + // The exact outcome & prompt type will be added as a part of Multifactor Authentication config in another PR, // for now a string is accepted to avoid blocking this PR. - MULTIFACTOR_AUTHENTICATION_NOTIFICATION: { - route: 'multifactor-authentication/notification/:notificationType', - getRoute: (notificationType: ValueOf) => `multifactor-authentication/notification/${notificationType}` as const, + MULTIFACTOR_AUTHENTICATION_OUTCOME: { + route: 'multifactor-authentication/outcome/:outcomeType', + getRoute: (outcomeType: ValueOf) => `multifactor-authentication/outcome/${outcomeType}` as const, }, MULTIFACTOR_AUTHENTICATION_PROMPT: { diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 617ab3c4d9a64..0d6fd09a39d32 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -908,7 +908,7 @@ const SCREENS = { MULTIFACTOR_AUTHENTICATION: { MAGIC_CODE: 'Multifactor_Authentication_Magic_Code', BIOMETRICS_TEST: 'Multifactor_Authentication_Biometrics_Test', - NOTIFICATION: 'Multifactor_Authentication_Notification', + OUTCOME: 'Multifactor_Authentication_Outcome', PROMPT: 'Multifactor_Authentication_Prompt', NOT_FOUND: 'Multifactor_Authentication_Not_Found', }, diff --git a/src/components/MultifactorAuthentication/config/index.ts b/src/components/MultifactorAuthentication/config/index.ts new file mode 100644 index 0000000000000..58f3b23f5bed8 --- /dev/null +++ b/src/components/MultifactorAuthentication/config/index.ts @@ -0,0 +1,12 @@ +/** + * Configuration exports for multifactor authentication UI components and scenarios. + */ +import mapMultifactorAuthenticationOutcomes from './mapMultifactorAuthenticationOutcomes'; +import MULTIFACTOR_AUTHENTICATION_SCENARIO_CONFIG from './scenarios'; + +const MULTIFACTOR_AUTHENTICATION_OUTCOME_MAP = mapMultifactorAuthenticationOutcomes(MULTIFACTOR_AUTHENTICATION_SCENARIO_CONFIG); + +export {MULTIFACTOR_AUTHENTICATION_SCENARIO_CONFIG, MULTIFACTOR_AUTHENTICATION_OUTCOME_MAP}; +export {default as MULTIFACTOR_AUTHENTICATION_PROMPT_UI} from './scenarios/prompts'; +export {default as MULTIFACTOR_AUTHENTICATION_DEFAULT_UI} from './scenarios/DefaultUserInterface'; +export type {Payloads as MultifactorAuthenticationScenarioPayload} from './scenarios'; diff --git a/src/components/MultifactorAuthentication/config/mapMultifactorAuthenticationOutcomes.ts b/src/components/MultifactorAuthentication/config/mapMultifactorAuthenticationOutcomes.ts new file mode 100644 index 0000000000000..81d3a9256e527 --- /dev/null +++ b/src/components/MultifactorAuthentication/config/mapMultifactorAuthenticationOutcomes.ts @@ -0,0 +1,97 @@ +import StringUtils from '@libs/StringUtils'; +import type { + MultifactorAuthenticationOutcomeMap, + MultifactorAuthenticationOutcomeOptions, + MultifactorAuthenticationOutcomeRecord, + MultifactorAuthenticationScenario, + MultifactorAuthenticationScenarioConfigRecord, +} from './types'; + +/** + * This utility module provides functions to map multifactor authentication scenario configurations + * to an outcome map with kebab-case keys. + * + * This allows outcome pages to reference the config based on its OutcomeType in url. + * + * e.g. + * + * { + * "BIOMETRICS-TEST": { + * // ... + * OUTCOMES: { + * success: { + * title: "...", + * // ... + * }, + * failure: { + * title: "...", + * // ... + * }, + * // ... + * } + * }, + * "AUTHORIZE-TRANSACTION": { + * // ... + * } + * } + * + * is mapped to: + * + * { + * "biometrics-test-success": { + * title: "...", + * // ... + * }, + * "biometrics-test-failure": { + * title: "...", + * // ... + * }, + * "authorize-transaction-success": { + * // ... + * } + * // ... + * } + */ + +/** + * Creates an outcome record from multifactor authentication scenario configuration. + * For details refer to the example above. + */ +const createOutcomeRecord = (mfaConfig: MultifactorAuthenticationScenarioConfigRecord): MultifactorAuthenticationOutcomeRecord => { + const entries = Object.entries({...mfaConfig}); + return entries.reduce((record, [key, {OUTCOMES}]) => { + // eslint-disable-next-line no-param-reassign + record[key as MultifactorAuthenticationScenario] = {...OUTCOMES}; + return record; + }, {} as MultifactorAuthenticationOutcomeRecord); +}; + +/** + * Creates an outcome key by combining scenario and outcome name in kebab-case format. + * e.g. a scenario key of "BIOMETRICS-TEST" and outcome name of "success" will produce "biometrics-test-success". + */ +const createOutcomeKey = (key: string, name: string) => { + const scenarioKebabCase = StringUtils.toLowerCase(key as MultifactorAuthenticationScenario); + const outcomeName = StringUtils.camelToKebabCase(name as MultifactorAuthenticationOutcomeOptions); + + return `${scenarioKebabCase}-${outcomeName}` as const; +}; + +/** + * Maps multifactor authentication scenario configuration to an outcome map with kebab-case keys. + */ +const mapMultifactorAuthenticationOutcomes = (mfaConfig: MultifactorAuthenticationScenarioConfigRecord) => { + const recordEntries = Object.entries(createOutcomeRecord({...mfaConfig})); + + const outcomes: Partial = {}; + + for (const [key, config] of recordEntries) { + for (const [name, ui] of Object.entries(config)) { + outcomes[createOutcomeKey(key, name)] = {...ui}; + } + } + + return outcomes as MultifactorAuthenticationOutcomeMap; +}; + +export default mapMultifactorAuthenticationOutcomes; diff --git a/src/components/MultifactorAuthentication/config/scenarios/BiometricsTest.ts b/src/components/MultifactorAuthentication/config/scenarios/BiometricsTest.ts new file mode 100644 index 0000000000000..6c9f0f44af50d --- /dev/null +++ b/src/components/MultifactorAuthentication/config/scenarios/BiometricsTest.ts @@ -0,0 +1,25 @@ +import type {MultifactorAuthenticationScenarioCustomConfig} from '@components/MultifactorAuthentication/config/types'; +import {troubleshootMultifactorAuthentication} from '@userActions/MultifactorAuthentication'; +import CONST from '@src/CONST'; +import SCREENS from '@src/SCREENS'; + +/** + * Configuration for the biometrics test multifactor authentication scenario. + */ +export default { + allowedAuthenticationMethods: [CONST.MULTIFACTOR_AUTHENTICATION.TYPE.BIOMETRICS], + action: troubleshootMultifactorAuthentication, + screen: SCREENS.MULTIFACTOR_AUTHENTICATION.BIOMETRICS_TEST, + pure: true, + OUTCOMES: { + success: { + headerTitle: 'multifactorAuthentication.biometricsTest.biometricsTest', + }, + failure: { + headerTitle: 'multifactorAuthentication.biometricsTest.biometricsTest', + }, + outOfTime: { + headerTitle: 'multifactorAuthentication.biometricsTest.biometricsTest', + }, + }, +} as const satisfies MultifactorAuthenticationScenarioCustomConfig; diff --git a/src/components/MultifactorAuthentication/config/scenarios/DefaultUserInterface.ts b/src/components/MultifactorAuthentication/config/scenarios/DefaultUserInterface.ts new file mode 100644 index 0000000000000..2c827de8d53ef --- /dev/null +++ b/src/components/MultifactorAuthentication/config/scenarios/DefaultUserInterface.ts @@ -0,0 +1,105 @@ +import type {MultifactorAuthenticationDefaultUIConfig, MultifactorAuthenticationScenarioCustomConfig} from '@components/MultifactorAuthentication/config/types'; +import NoEligibleMethodsDescription from '@components/MultifactorAuthentication/NoEligibleMethodsDescription'; +// Spacing utilities are needed for icon padding configuration in outcomes defaults +// eslint-disable-next-line no-restricted-imports +import spacing from '@styles/utils/spacing'; +import variables from '@styles/variables'; + +/** + * Default UI configuration for all multifactor authentication scenarios with modals and outcomes. + */ +const DEFAULT_CONFIG = { + OUTCOMES: { + success: { + illustration: 'OpenPadlock', + iconWidth: variables.openPadlockWidth, + iconHeight: variables.openPadlockHeight, + padding: spacing.p2, + headerTitle: 'multifactorAuthentication.biometricsTest.biometricsAuthentication', + title: 'multifactorAuthentication.biometricsTest.authenticationSuccessful', + description: 'multifactorAuthentication.biometricsTest.successfullyAuthenticatedUsing', + }, + failure: { + illustration: 'HumptyDumpty', + iconWidth: variables.humptyDumptyWidth, + iconHeight: variables.humptyDumptyHeight, + padding: spacing.p0, + headerTitle: 'multifactorAuthentication.biometricsTest.biometricsAuthentication', + title: 'multifactorAuthentication.oops', + description: 'multifactorAuthentication.biometricsTest.yourAttemptWasUnsuccessful', + }, + outOfTime: { + illustration: 'RunOutOfTime', + iconWidth: variables.runOutOfTimeWidth, + iconHeight: variables.runOutOfTimeHeight, + padding: spacing.p0, + headerTitle: 'multifactorAuthentication.biometricsTest.biometricsAuthentication', + title: 'multifactorAuthentication.youRanOutOfTime', + description: 'multifactorAuthentication.looksLikeYouRanOutOfTime', + }, + noEligibleMethods: { + illustration: 'HumptyDumpty', + iconWidth: variables.humptyDumptyWidth, + iconHeight: variables.humptyDumptyHeight, + padding: spacing.p0, + headerTitle: 'multifactorAuthentication.biometricsTest.biometricsAuthentication', + title: 'multifactorAuthentication.biometricsTest.youCouldNotBeAuthenticated', + description: 'multifactorAuthentication.biometricsTest.youCouldNotBeAuthenticated', + customDescription: NoEligibleMethodsDescription, + }, + }, + MODALS: { + cancelConfirmation: { + title: 'common.areYouSure', + description: 'multifactorAuthentication.biometricsTest.areYouSureToReject', + confirmButtonText: 'multifactorAuthentication.biometricsTest.rejectAuthentication', + cancelButtonText: 'common.cancel', + }, + }, + nativePromptTitle: 'multifactorAuthentication.letsVerifyItsYou', +} as const satisfies MultifactorAuthenticationDefaultUIConfig; + +/** + * Merges custom scenario configuration with default UI configuration for modals and outcomes. + */ +function customConfig>(config: T) { + const MODALS = { + ...DEFAULT_CONFIG.MODALS, + ...config.MODALS, + cancelConfirmation: { + ...DEFAULT_CONFIG.MODALS.cancelConfirmation, + ...config.MODALS?.cancelConfirmation, + }, + } as const; + + const OUTCOMES = { + ...DEFAULT_CONFIG.OUTCOMES, + ...config.OUTCOMES, + success: { + ...DEFAULT_CONFIG.OUTCOMES.success, + ...config.OUTCOMES?.success, + }, + failure: { + ...DEFAULT_CONFIG.OUTCOMES.failure, + ...config.OUTCOMES?.failure, + }, + outOfTime: { + ...DEFAULT_CONFIG.OUTCOMES.outOfTime, + ...config.OUTCOMES?.outOfTime, + }, + noEligibleMethods: { + ...DEFAULT_CONFIG.OUTCOMES.noEligibleMethods, + ...config.OUTCOMES?.noEligibleMethods, + }, + } as const; + + return { + ...DEFAULT_CONFIG, + ...config, + MODALS, + OUTCOMES, + } as const; +} + +export default DEFAULT_CONFIG; +export {customConfig}; diff --git a/src/components/MultifactorAuthentication/config/scenarios/index.ts b/src/components/MultifactorAuthentication/config/scenarios/index.ts new file mode 100644 index 0000000000000..b89521d273681 --- /dev/null +++ b/src/components/MultifactorAuthentication/config/scenarios/index.ts @@ -0,0 +1,28 @@ +import type {EmptyObject} from 'type-fest'; +import type {MultifactorAuthenticationScenarioConfigRecord} from '@components/MultifactorAuthentication/config/types'; +import CONST from '@src/CONST'; +import BiometricsTest from './BiometricsTest'; +import {customConfig} from './DefaultUserInterface'; + +/** + * Payload types for multifactor authentication scenarios. + * Since the BiometricsTest does not require any payload, it is an empty object for now. + * The AuthorizeTransaction Scenario will change it, as it needs the transactionID to be provided as well. + * + * { + * "AUTHORIZE-TRANSACTION": { + * transactionID: string; + * } + * } + */ +type Payloads = EmptyObject; + +/** + * Configuration records for all multifactor authentication scenarios. + */ +const Configs = { + [CONST.MULTIFACTOR_AUTHENTICATION.SCENARIO.BIOMETRICS_TEST]: customConfig(BiometricsTest), +} as const satisfies MultifactorAuthenticationScenarioConfigRecord; + +export default Configs; +export type {Payloads}; diff --git a/src/components/MultifactorAuthentication/config/scenarios/names.ts b/src/components/MultifactorAuthentication/config/scenarios/names.ts new file mode 100644 index 0000000000000..c305a7313369c --- /dev/null +++ b/src/components/MultifactorAuthentication/config/scenarios/names.ts @@ -0,0 +1,20 @@ +/** + * Multifactor authentication scenario names. + * + * The names need to be a kebab-case string to satisfy the requirements of the URL schema. + * Moreover, they are exported to a separate file to avoid circular dependencies + * as the Multifactor Authentication configs imports SCREENS, actions, and other shared modules, + * and at the same time the config is imported in the CONSTs. + */ +const SCENARIO_NAMES = { + BIOMETRICS_TEST: 'BIOMETRICS-TEST', +} as const; + +/** + * Prompt identifiers for multifactor authentication scenarios. + */ +const PROMPT_NAMES = { + ENABLE_BIOMETRICS: 'enable-biometrics', +}; + +export {SCENARIO_NAMES, PROMPT_NAMES}; diff --git a/src/components/MultifactorAuthentication/config/scenarios/prompts.ts b/src/components/MultifactorAuthentication/config/scenarios/prompts.ts new file mode 100644 index 0000000000000..729cf39b88d45 --- /dev/null +++ b/src/components/MultifactorAuthentication/config/scenarios/prompts.ts @@ -0,0 +1,15 @@ +import LottieAnimations from '@components/LottieAnimations'; +import type {MultifactorAuthenticationPrompt} from '@components/MultifactorAuthentication/config/types'; +import VALUES from '@libs/MultifactorAuthentication/Biometrics/VALUES'; + +/** + * Configuration for multifactor authentication prompt UI with animations and translations. + * Exported to a separate file to avoid circular dependencies. + */ +export default { + [VALUES.PROMPT.ENABLE_BIOMETRICS]: { + animation: LottieAnimations.Fingerprint, + title: 'multifactorAuthentication.verifyYourself.biometrics', + subtitle: 'multifactorAuthentication.enableQuickVerification.biometrics', + }, +} as const satisfies MultifactorAuthenticationPrompt; diff --git a/src/components/MultifactorAuthentication/config/types.ts b/src/components/MultifactorAuthentication/config/types.ts new file mode 100644 index 0000000000000..6e17c121c1a0f --- /dev/null +++ b/src/components/MultifactorAuthentication/config/types.ts @@ -0,0 +1,267 @@ +/** + * Configuration types for multifactor authentication UI and scenarios. + */ +import type {ViewStyle} from 'react-native'; +import type {EmptyObject, KebabCase, Replace, ValueOf} from 'type-fest'; +import type {IllustrationName} from '@components/Icon/chunks/illustrations.chunk'; +import type DotLottieAnimation from '@components/LottieAnimations/types'; +import type { + AllMultifactorAuthenticationFactors, + MultifactorAuthenticationActionParams, + MultifactorAuthenticationKeyInfo, + MultifactorAuthenticationReason, +} from '@libs/MultifactorAuthentication/Biometrics/types'; +import type CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; +import type SCREENS from '@src/SCREENS'; +import type {MULTIFACTOR_AUTHENTICATION_SCENARIO_CONFIG, MultifactorAuthenticationScenarioPayload} from './index'; + +/** + * Configuration for cancel confirmation modal in multifactor authentication. + */ +type MultifactorAuthenticationCancelConfirm = { + description?: TranslationPaths; + cancelButtonText?: TranslationPaths; + confirmButtonText?: TranslationPaths; + title?: TranslationPaths; +}; + +/** + * Configuration for multifactor authentication prompt display with animation and translations. + */ +type MultifactorAuthenticationPromptConfig = { + animation: DotLottieAnimation; + title: TranslationPaths; + subtitle: TranslationPaths; +}; + +/** + * Configuration for displaying multifactor authentication outcomes with illustrations and text. + */ +type MultifactorAuthenticationOutcomeConfig = { + illustration: IllustrationName; + iconWidth: number; + iconHeight: number; + padding: ViewStyle; + headerTitle: TranslationPaths; + title: TranslationPaths; + description: TranslationPaths; + customDescription?: React.FunctionComponent; +}; + +/** + * Collection of prompts keyed by prompt identifier. + */ +type MultifactorAuthenticationPrompt = Record; + +/** + * Collection of outcomes keyed by an outcome type. + */ +type MultifactorAuthenticationOutcome = Record; + +/** + * Configuration for modals in multifactor authentication flows. + */ +type MultifactorAuthenticationModal = { + cancelConfirmation: MultifactorAuthenticationCancelConfirm; +}; + +/** + * Override configuration for modals with partial properties. + * This allows customization of specific modal aspects without redefining the entire structure. + * e.g. "Authentication attempt" in the cancel confirmation modal can be changed to "Transaction approval". + */ +type MultifactorAuthenticationModalOptional = { + cancelConfirmation?: Partial; +}; + +/** + * Optional outcome configuration with partial properties for scenario overrides. + */ +type MultifactorAuthenticationOutcomeOptional = Record>; + +/** + * Type representation of the scenario configuration constant. + */ +type MultifactorAuthenticationConfigRecordConst = typeof MULTIFACTOR_AUTHENTICATION_SCENARIO_CONFIG; + +/** + * Maps each scenario to its outcomes configuration type. + */ +type MultifactorAuthenticationScenarioOutcomeConst = { + [K in MultifactorAuthenticationScenario]: MultifactorAuthenticationConfigRecordConst[K]['OUTCOMES']; +}; + +/** + * Available outcome options for each scenario. + */ +type MultifactorAuthenticationOutcomeScenarioOptions = { + [K in MultifactorAuthenticationScenario]: keyof MultifactorAuthenticationScenarioOutcomeConst[K]; +}; + +/** + * Maps scenarios to their outcome configurations. + */ +type MultifactorAuthenticationOutcomeRecord = Record; + +/** + * Constructs a kebab-case outcome type string from scenario and outcome name. + */ +type MultifactorAuthenticationOutcomeType = `${Lowercase}-${KebabCase}`; + +type MultifactorAuthenticationUI = { + MODALS: MultifactorAuthenticationModal; + OUTCOMES: MultifactorAuthenticationOutcome; +}; + +/** + * All possible outcome types key across all scenarios. + */ +type AllMultifactorAuthenticationOutcomeType = MultifactorAuthenticationOutcomeType; + +/** + * Maps all outcome type keys to their configurations. + */ +type MultifactorAuthenticationOutcomeMap = Record; + +/** + * All available outcome options across scenarios. + */ +type MultifactorAuthenticationOutcomeOptions = keyof MultifactorAuthenticationScenarioOutcomeConst[MultifactorAuthenticationScenario]; + +/** + * Outcome type suffixes for a specific scenario (removes the scenario prefix). + */ +type MultifactorAuthenticationOutcomeSuffixes = Replace}-`, ''>; + +/** + * Response from a multifactor authentication scenario action. + */ +type MultifactorAuthenticationScenarioResponse = { + httpCode: number; + reason: MultifactorAuthenticationReason; +}; + +/** + * Multifactor authentication screen identifiers. + */ +type MultifactorAuthenticationScreen = ValueOf; + +/** + * Pure function type for scenario actions that return HTTP response and reason. + */ +type MultifactorAuthenticationScenarioPureMethod> = ( + params: MultifactorAuthenticationActionParams, +) => Promise; + +/** + * Complete scenario configuration including action, UI, and metadata. + */ +type MultifactorAuthenticationScenarioConfig = EmptyObject> = { + action: MultifactorAuthenticationScenarioPureMethod; + allowedAuthenticationMethods: Array>; + screen: MultifactorAuthenticationScreen; + + /** + * Whether the scenario does not require any additional parameters except for the native biometrics data. + * If it is the case, the scenario needs to be defined as such + * so the absence of payload will be tolerated at the run-time. + */ + pure?: true; + nativePromptTitle: TranslationPaths; +} & MultifactorAuthenticationUI; + +/** + * Scenario configuration for custom scenarios with optional overrides. + */ +type MultifactorAuthenticationScenarioCustomConfig = EmptyObject> = Omit< + MultifactorAuthenticationScenarioConfig, + 'MODALS' | 'OUTCOMES' | 'nativePromptTitle' +> & { + nativePromptTitle?: TranslationPaths; + MODALS?: MultifactorAuthenticationModalOptional; + OUTCOMES: MultifactorAuthenticationOutcomeOptional; +}; + +/** + * Default UI configuration shared across scenarios. + */ +type MultifactorAuthenticationDefaultUIConfig = Pick; + +/** + * Record mapping all scenarios to their configurations. + */ +type MultifactorAuthenticationScenarioConfigRecord = Record>; + +/** + * Additional parameters specific to a scenario. + */ +type MultifactorAuthenticationScenarioAdditionalParams = T extends keyof MultifactorAuthenticationScenarioPayload + ? MultifactorAuthenticationScenarioPayload[T] + : EmptyObject; + +/** + * Optional authentication factors with scenario-specific parameters. + */ +type MultifactorAuthenticationScenarioParams = Partial & + MultifactorAuthenticationScenarioAdditionalParams; + +/** + * All required authentication factors with scenario-specific parameters. + */ +type MultifactorAuthenticationProcessScenarioParameters = AllMultifactorAuthenticationFactors & + MultifactorAuthenticationScenarioAdditionalParams; + +/** + * Scenario response with success status indicator. + */ +type MultifactorAuthenticationScenarioResponseWithSuccess = { + httpCode: number | undefined; + successful: boolean; +}; + +/** + * Parameters required for biometrics registration scenario. + */ +type RegisterBiometricsParams = MultifactorAuthenticationActionParams< + { + keyInfo: MultifactorAuthenticationKeyInfo<'biometric'>; + }, + 'validateCode' +>; + +/** + * Type-safe parameters for each multifactor authentication scenario. + */ +type MultifactorAuthenticationScenarioParameters = { + [key in MultifactorAuthenticationScenario]: MultifactorAuthenticationActionParams< + key extends keyof MultifactorAuthenticationScenarioPayload ? MultifactorAuthenticationScenarioPayload[key] : EmptyObject, + 'signedChallenge' + >; +} & { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'REGISTER-BIOMETRICS': RegisterBiometricsParams; +}; + +/** + * Identifier for different multifactor authentication scenarios. + */ +type MultifactorAuthenticationScenario = ValueOf; + +export type { + MultifactorAuthenticationPrompt, + MultifactorAuthenticationOutcomeRecord, + MultifactorAuthenticationOutcomeMap, + MultifactorAuthenticationScenarioResponseWithSuccess, + MultifactorAuthenticationScenarioAdditionalParams, + MultifactorAuthenticationScenarioParameters, + MultifactorAuthenticationScenario, + MultifactorAuthenticationOutcomeOptions, + MultifactorAuthenticationScenarioParams, + MultifactorAuthenticationScenarioConfig, + MultifactorAuthenticationScenarioConfigRecord, + MultifactorAuthenticationProcessScenarioParameters, + MultifactorAuthenticationDefaultUIConfig, + MultifactorAuthenticationOutcomeSuffixes, + MultifactorAuthenticationScenarioCustomConfig, +}; diff --git a/src/components/MultifactorAuthentication/types.ts b/src/components/MultifactorAuthentication/types.ts new file mode 100644 index 0000000000000..4130f888c0d3e --- /dev/null +++ b/src/components/MultifactorAuthentication/types.ts @@ -0,0 +1,13 @@ +/** + * Type definitions for multifactor authentication components. + */ +import type {ValueOf} from 'type-fest'; +import type {SECURE_STORE_VALUES} from '@libs/MultifactorAuthentication/Biometrics/SecureStore'; + +/** + * Authentication type name derived from secure store values. + */ +type AuthTypeName = ValueOf['NAME']; + +// eslint-disable-next-line import/prefer-default-export +export type {AuthTypeName}; diff --git a/src/components/TestToolMenu.tsx b/src/components/TestToolMenu.tsx index f4f83bfab0008..eadc8f7d0989d 100644 --- a/src/components/TestToolMenu.tsx +++ b/src/components/TestToolMenu.tsx @@ -22,6 +22,9 @@ import TestCrash from './TestCrash'; import TestToolRow from './TestToolRow'; import Text from './Text'; +// Temporary hardcoded value until MultifactorAuthenticationContext is implemented +const TEMP_BIOMETRICS_REGISTERED_STATUS = false; + function TestToolMenu() { const [network] = useOnyx(ONYXKEYS.NETWORK, {canBeMissing: true}); const [isUsingImportedState] = useOnyx(ONYXKEYS.IS_USING_IMPORTED_STATE, {canBeMissing: true}); @@ -33,6 +36,11 @@ function TestToolMenu() { const {singleExecution} = useSingleExecution(); const waitForNavigate = useWaitForNavigation(); + + /** + * The wrapper is needed to prevent rapid double‑taps on native from triggering multiple navigations. + * Context: https://github.com/Expensify/App/pull/79475#discussion_r2708230681 + */ const navigateToBiometricsTestPage = singleExecution( waitForNavigate(() => { Navigation.navigate(ROUTES.MULTIFACTOR_AUTHENTICATION_BIOMETRICS_TEST); @@ -43,7 +51,7 @@ function TestToolMenu() { const isAuthenticated = useIsAuthenticated(); // Temporary hardcoded false, expected behavior: status fetched from the MultifactorAuthenticationContext - const biometricsTitle = translate('multifactorAuthentication.biometricsTest.troubleshootBiometricsStatus', {registered: false}); + const biometricsTitle = translate('multifactorAuthentication.biometricsTest.troubleshootBiometricsStatus', {registered: TEMP_BIOMETRICS_REGISTERED_STATUS}); return ( <> @@ -100,7 +108,7 @@ function TestToolMenu() { /> - {/* Allows you to test the Biometrics flow */} + {/* Allows testing the biometric multifactor authentication flow */}