From e1b659f391330c34d4621ed199a7f212efbb222a Mon Sep 17 00:00:00 2001 From: VIHANGAGIT Date: Mon, 3 Aug 2026 17:35:39 +0530 Subject: [PATCH 1/4] Resolve dynamic flow steps from connectors/dropins --- ...get-ask-password-flow-builder-resources.ts | 10 ++- .../resource-properties.tsx | 7 ++ .../api/use-get-extension-executor-steps.ts | 38 +++++++++ .../execution-factory/extension-execution.tsx | 82 +++++++++++++++++++ .../execution/execution-factory/index.tsx | 18 ++++ .../hooks/use-extension-executor.ts | 49 +++++++++++ .../models/metadata.ts | 38 +++++++++ .../utils/build-extension-executor-steps.ts | 63 ++++++++++++++ ...assword-recovery-flow-builder-resources.ts | 10 ++- .../resource-properties.tsx | 9 +- ...get-registration-flow-builder-resources.ts | 12 ++- .../resource-properties.tsx | 7 ++ 12 files changed, 338 insertions(+), 5 deletions(-) create mode 100644 features/admin.flow-builder-core.v1/api/use-get-extension-executor-steps.ts create mode 100644 features/admin.flow-builder-core.v1/components/resources/steps/execution/execution-factory/extension-execution.tsx create mode 100644 features/admin.flow-builder-core.v1/hooks/use-extension-executor.ts create mode 100644 features/admin.flow-builder-core.v1/utils/build-extension-executor-steps.ts diff --git a/features/admin.ask-password-flow-builder.v1/api/use-get-ask-password-flow-builder-resources.ts b/features/admin.ask-password-flow-builder.v1/api/use-get-ask-password-flow-builder-resources.ts index 6e8fb0290ce..bb3229a9552 100644 --- a/features/admin.ask-password-flow-builder.v1/api/use-get-ask-password-flow-builder-resources.ts +++ b/features/admin.ask-password-flow-builder.v1/api/use-get-ask-password-flow-builder-resources.ts @@ -17,8 +17,12 @@ */ import { RequestErrorInterface, RequestResultInterface } from "@wso2is/admin.core.v1/hooks/use-request"; +import useGetExtensionExecutorSteps + from "@wso2is/admin.flow-builder-core.v1/api/use-get-extension-executor-steps"; import useGetFlowBuilderCoreResources from "@wso2is/admin.flow-builder-core.v1/api/use-get-flow-builder-core-resources"; import { Resources } from "@wso2is/admin.flow-builder-core.v1/models/resources"; +import { Step } from "@wso2is/admin.flow-builder-core.v1/models/steps"; +import { FlowTypes } from "@wso2is/admin.flows.v1/models/flows"; import elements from "../data/elements.json"; import steps from "../data/steps.json"; import templates from "../data/templates.json"; @@ -40,6 +44,9 @@ const useGetAskPasswordFlowBuilderResources = => { const { data: coreResources } = useGetFlowBuilderCoreResources(); + // Executors contributed by extensions deployed on the server. Appended to the palette at runtime. + const extensionExecutorSteps: Step[] = useGetExtensionExecutorSteps(FlowTypes.INVITED_USER_REGISTRATION); + return { data: ({ ...coreResources, @@ -49,7 +56,8 @@ const useGetAskPasswordFlowBuilderResources = = return resource?.variants?.find((_element: Element) => _element.variant === resource.variant); }, [ resource.variants, resource.variant ]); + const isConnectionlessExtensionExecutor: boolean = useIsConnectionlessExtensionExecutor( + resource?.data?.action?.executor?.name + ); + const renderElementId = (): ReactElement => { return ( = <> { renderElementId() } { + !isConnectionlessExtensionExecutor && !AskPasswordFlowBuilderConstants.FEDERATION_CONFIG_SKIPPED_EXECUTORS.includes( resource?.data?.action?.executor?.name) && ( { + const { data: metadata } = useGetMetadata(flowType, !!flowType); + + return useMemo( + () => buildExtensionExecutorSteps(metadata?.extensionExecutors), + [ metadata?.extensionExecutors ] + ); +}; + +export default useGetExtensionExecutorSteps; diff --git a/features/admin.flow-builder-core.v1/components/resources/steps/execution/execution-factory/extension-execution.tsx b/features/admin.flow-builder-core.v1/components/resources/steps/execution/execution-factory/extension-execution.tsx new file mode 100644 index 00000000000..12477fd66ea --- /dev/null +++ b/features/admin.flow-builder-core.v1/components/resources/steps/execution/execution-factory/extension-execution.tsx @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Box from "@oxygen-ui/react/Box"; +import Typography from "@oxygen-ui/react/Typography"; +import loadStaticResource from "@wso2is/admin.core.v1/utils/load-static-resource"; +import { IdentifiableComponentInterface } from "@wso2is/core/models"; +import React, { ReactElement, useMemo, useState } from "react"; +import useAuthenticationFlowBuilderCore from "../../../../../hooks/use-authentication-flow-builder-core-context"; +import { ExtensionExecutorInterface } from "../../../../../models/metadata"; +import { DEFAULT_EXTENSION_EXECUTOR_ICON } from "../../../../../utils/build-extension-executor-steps"; +import { ExecutionMinimalPropsInterface } from "../execution-minimal"; + +/** + * Props interface of ExtensionExecution. + */ +type ExtensionExecutionPropsInterface = ExecutionMinimalPropsInterface & IdentifiableComponentInterface; + +/** + * Canvas node for an executor contributed by an extension deployed on the server. + */ +const ExtensionExecution = ({ + resource, + "data-componentid": componentId = "extension-execution" +}: ExtensionExecutionPropsInterface): ReactElement => { + const { metadata } = useAuthenticationFlowBuilderCore(); + const [ hasIconFailed, setHasIconFailed ] = useState(false); + + const executorName: string = resource?.data?.action?.executor?.name; + + const executor: ExtensionExecutorInterface | null = useMemo(() => { + if (!executorName || !metadata?.extensionExecutors?.length) { + return null; + } + + return metadata.extensionExecutors.find( + (candidate: ExtensionExecutorInterface) => candidate.name === executorName + ) || null; + }, [ executorName, metadata?.extensionExecutors ]); + + const isDefaultIcon: boolean = hasIconFailed || !executor?.icon; + const iconSrc: string = isDefaultIcon + ? loadStaticResource(DEFAULT_EXTENSION_EXECUTOR_ICON) + : loadStaticResource(executor.icon); + + const displayName: string = executor?.displayName || resource?.display?.label || executorName; + + return ( + + { setHasIconFailed(true) } + /> + { displayName } + + ); +}; + +export default ExtensionExecution; diff --git a/features/admin.flow-builder-core.v1/components/resources/steps/execution/execution-factory/index.tsx b/features/admin.flow-builder-core.v1/components/resources/steps/execution/execution-factory/index.tsx index 2057cbd9cf9..a6a18d89caf 100644 --- a/features/admin.flow-builder-core.v1/components/resources/steps/execution/execution-factory/index.tsx +++ b/features/admin.flow-builder-core.v1/components/resources/steps/execution/execution-factory/index.tsx @@ -22,11 +22,14 @@ import { IdentifiableComponentInterface } from "@wso2is/core/models"; import React, { FC, ReactElement } from "react"; import { useTranslation } from "react-i18next"; import AppleExecution from "./apple-execution"; +import ExtensionExecution from "./extension-execution"; import FacebookExecution from "./facebook-execution"; import GithubExecution from "./github-execution"; import GoogleExecution from "./google-execution"; import FlowExtensionExecution from "./flow-extension-execution"; import MicrosoftExecution from "./microsoft-execution"; +import useAuthenticationFlowBuilderCore from "../../../../../hooks/use-authentication-flow-builder-core-context"; +import { ExtensionExecutorInterface } from "../../../../../models/metadata"; import { ExecutionTypes } from "../../../../../models/steps"; import "./execution-factory.scss"; import { ExecutionMinimalPropsInterface } from "../execution-minimal"; @@ -48,6 +51,7 @@ const ExecutionFactory: FC = ({ "data-componentid": componentId = "execution-factory" }: ExecutionFactoryPropsInterface): ReactElement => { const { t } = useTranslation(); + const { metadata } = useAuthenticationFlowBuilderCore(); if ((resource.data?.action as any)?.executor?.name === ExecutionTypes.GoogleFederation) { return ( @@ -128,6 +132,20 @@ const ExecutionFactory: FC = ({ ); } + /* + * Flow metadata describes how to render executors that are dynamically registered. + */ + const isContributedByExtension: boolean = !!metadata?.extensionExecutors?.some( + (executor: ExtensionExecutorInterface) => + executor.name === (resource.data?.action as any)?.executor?.name + ); + + if (isContributedByExtension) { + return ( + + ); + } + return ( { t("flows:core.executions.names.default") } diff --git a/features/admin.flow-builder-core.v1/hooks/use-extension-executor.ts b/features/admin.flow-builder-core.v1/hooks/use-extension-executor.ts new file mode 100644 index 00000000000..4b5aefa9cb6 --- /dev/null +++ b/features/admin.flow-builder-core.v1/hooks/use-extension-executor.ts @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { useMemo } from "react"; +import useAuthenticationFlowBuilderCore from "./use-authentication-flow-builder-core-context"; +import { ExtensionExecutorInterface } from "../models/metadata"; + +/** + * Hook that resolves the metadata of an executor contributed by an extension deployed on the server. + */ +export const useExtensionExecutor = (executorName: string): ExtensionExecutorInterface | null => { + const { metadata } = useAuthenticationFlowBuilderCore(); + + return useMemo(() => { + if (!executorName || !metadata?.extensionExecutors?.length) { + return null; + } + + return metadata.extensionExecutors.find( + (executor: ExtensionExecutorInterface) => executor.name === executorName + ) || null; + }, [ executorName, metadata?.extensionExecutors ]); +}; + +/** + * Hook that decides whether the connection picker applies to an dynamically registered executor. + */ +export const useIsConnectionlessExtensionExecutor = (executorName: string): boolean => { + const executor: ExtensionExecutorInterface | null = useExtensionExecutor(executorName); + + return !!executor && executor.requiresConnection === false; +}; + +export default useExtensionExecutor; diff --git a/features/admin.flow-builder-core.v1/models/metadata.ts b/features/admin.flow-builder-core.v1/models/metadata.ts index 114fad1caa6..379efe97836 100644 --- a/features/admin.flow-builder-core.v1/models/metadata.ts +++ b/features/admin.flow-builder-core.v1/models/metadata.ts @@ -54,6 +54,10 @@ export interface MetadataInterface { * List of active flow extension connections from metadata. */ flowExtensionConnections?: FlowExtensionConnectionInterface[]; + /** + * Executors registered by an connector deployed on the server. + */ + extensionExecutors?: ExtensionExecutorInterface[]; /** * Is a Workflow engagement enabled? */ @@ -114,6 +118,40 @@ export interface ExecutorConnectionInterface { connections: string[]; } +/** + * Interface for an executor registered dynamically by an extension deployed on the server. + */ +export interface ExtensionExecutorInterface { + /** + * Unique name of the executor, as registered with the flow execution engine. + */ + name: string; + /** + * Human readable name shown in the palette and on the canvas node. + */ + displayName: string; + /** + * Short explanation of what the step does, shown under the label in the palette. + */ + description?: string; + /** + * Reserved behavior flags declared by the executor. Interpreted in API layer. + */ + behaviorFlags?: string[]; + /** + * Icon for the executor. + */ + icon?: string; + /** + * Whether a connection has to be selected before a step using this executor is valid. + */ + requiresConnection?: boolean; + /** + * Name of the authenticator backing this executor. + */ + associatedAuthenticator?: string; +} + /** * Interface for flow extension connection info from flow metadata. */ diff --git a/features/admin.flow-builder-core.v1/utils/build-extension-executor-steps.ts b/features/admin.flow-builder-core.v1/utils/build-extension-executor-steps.ts new file mode 100644 index 00000000000..a30cbe0a112 --- /dev/null +++ b/features/admin.flow-builder-core.v1/utils/build-extension-executor-steps.ts @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ExtensionExecutorInterface } from "../models/metadata"; +import { ResourceTypes } from "../models/resources"; +import { Step, StepCategories, StepTypes } from "../models/steps"; + +/** + * Icon used when a contributed executor does not declare one, or declares one that fails to load. + */ +export const DEFAULT_EXTENSION_EXECUTOR_ICON: string = "assets/images/icons/step.svg"; + +/** + * Builds palette steps for the executors contributed by extensions deployed on the server. + */ +const buildExtensionExecutorSteps = (extensionExecutors: ExtensionExecutorInterface[]): Step[] => { + if (!extensionExecutors?.length) { + return []; + } + + return extensionExecutors + .filter((executor: ExtensionExecutorInterface) => !!executor?.name) + .map((executor: ExtensionExecutorInterface) => ({ + category: StepCategories.Workflow, + data: { + action: { + executor: { + name: executor.name, + ...(executor.requiresConnection ? { meta: { idpName: "" } } : {}) + }, + next: "", + type: "EXECUTOR" + } + }, + deprecated: false, + display: { + description: executor.description, + image: executor.icon || DEFAULT_EXTENSION_EXECUTOR_ICON, + label: executor.displayName, + showOnResourcePanel: true + }, + resourceType: ResourceTypes.Step, + type: StepTypes.Execution, + version: "0.1.0" + })) as unknown as Step[]; +}; + +export default buildExtensionExecutorSteps; diff --git a/features/admin.password-recovery-flow-builder.v1/api/use-get-password-recovery-flow-builder-resources.ts b/features/admin.password-recovery-flow-builder.v1/api/use-get-password-recovery-flow-builder-resources.ts index 02c1f94ed66..2d0a1972d31 100644 --- a/features/admin.password-recovery-flow-builder.v1/api/use-get-password-recovery-flow-builder-resources.ts +++ b/features/admin.password-recovery-flow-builder.v1/api/use-get-password-recovery-flow-builder-resources.ts @@ -17,8 +17,12 @@ */ import { RequestErrorInterface, RequestResultInterface } from "@wso2is/admin.core.v1/hooks/use-request"; +import useGetExtensionExecutorSteps + from "@wso2is/admin.flow-builder-core.v1/api/use-get-extension-executor-steps"; import useGetFlowBuilderCoreResources from "@wso2is/admin.flow-builder-core.v1/api/use-get-flow-builder-core-resources"; import { Resources } from "@wso2is/admin.flow-builder-core.v1/models/resources"; +import { Step } from "@wso2is/admin.flow-builder-core.v1/models/steps"; +import { FlowTypes } from "@wso2is/admin.flows.v1/models/flows"; import steps from "../data/steps.json"; import templates from "../data/templates.json"; import widgets from "../data/widgets.json"; @@ -39,12 +43,16 @@ const useGetPasswordRecoveryFlowBuilderResources = => { const { data: coreResources } = useGetFlowBuilderCoreResources(); + // Executors contributed by extensions deployed on the server. Appended to the palette at runtime. + const extensionExecutorSteps: Step[] = useGetExtensionExecutorSteps(FlowTypes.PASSWORD_RECOVERY); + return { data: ({ ...coreResources, steps: [ ...coreResources?.steps, - ...steps + ...steps, + ...extensionExecutorSteps ], templates: [ ...coreResources?.templates, diff --git a/features/admin.password-recovery-flow-builder.v1/components/resource-property-panel/resource-properties.tsx b/features/admin.password-recovery-flow-builder.v1/components/resource-property-panel/resource-properties.tsx index d62ccd3a96e..d2fb2c67cdd 100644 --- a/features/admin.password-recovery-flow-builder.v1/components/resource-property-panel/resource-properties.tsx +++ b/features/admin.password-recovery-flow-builder.v1/components/resource-property-panel/resource-properties.tsx @@ -24,7 +24,9 @@ import { import { FieldKey, FieldValue } from "@wso2is/admin.flow-builder-core.v1/models/base"; import { Element, ElementCategories, ElementTypes } from "@wso2is/admin.flow-builder-core.v1/models/elements"; import { Resource } from "@wso2is/admin.flow-builder-core.v1/models/resources"; -import { StepCategories, StepTypes } from "@wso2is/admin.flow-builder-core.v1/models/steps"; +import { ExecutionTypes, StepCategories, StepTypes } from "@wso2is/admin.flow-builder-core.v1/models/steps"; +import { useIsConnectionlessExtensionExecutor } + from "@wso2is/admin.flow-builder-core.v1/hooks/use-extension-executor"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; import isEmpty from "lodash-es/isEmpty"; import React, { ChangeEvent, FunctionComponent, ReactElement, useMemo } from "react"; @@ -57,6 +59,10 @@ const ResourceProperties: FunctionComponent = return resource?.variants?.find((_element: Element) => _element.variant === resource.variant); }, [ resource.variants, resource.variant ]); + const isConnectionlessExtensionExecutor: boolean = useIsConnectionlessExtensionExecutor( + resource?.data?.action?.executor?.name + ); + const renderElementId = (): ReactElement => { return ( = <> { renderElementId() } { + !isConnectionlessExtensionExecutor && !PasswordRecoveryFlowBuilderConstants.FEDERATION_CONFIG_SKIPPED_EXECUTORS.includes( resource?.data?.action?.executor?.name) && ( => { const { data: coreResources } = useGetFlowBuilderCoreResources(); + // Executors contributed by extensions deployed on the server. Appended to the palette at runtime. + const extensionExecutorSteps: Step[] = useGetExtensionExecutorSteps(FlowTypes.REGISTRATION); + const aiFeature: FeatureAccessConfigInterface = useSelector( (state: AppState) => state.config.ui.features?.ai ); @@ -66,7 +73,8 @@ const useGetRegistrationFlowBuilderResources = = return resource?.variants?.find((_element: Element) => _element.variant === resource.variant); }, [ resource.variants, resource.variant ]); + const isConnectionlessExtensionExecutor: boolean = useIsConnectionlessExtensionExecutor( + resource?.data?.action?.executor?.name + ); + const renderElementId = (): ReactElement => { return ( = <> { renderElementId() } { + !isConnectionlessExtensionExecutor && !RegistrationFlowBuilderConstants.FEDERATION_CONFIG_SKIPPED_EXECUTORS.includes( resource?.data?.action?.executor?.name) && ( Date: Thu, 13 Aug 2026 12:04:49 +0530 Subject: [PATCH 2/4] Add recovery factor behavior flag and enhance validation logic --- .../constants/validation-constants.ts | 5 +++ .../hooks/use-factor-validation.ts | 41 +++++++++++++++---- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/features/admin.flow-builder-core.v1/constants/validation-constants.ts b/features/admin.flow-builder-core.v1/constants/validation-constants.ts index 52ee67a1677..ed42d114832 100644 --- a/features/admin.flow-builder-core.v1/constants/validation-constants.ts +++ b/features/admin.flow-builder-core.v1/constants/validation-constants.ts @@ -23,6 +23,11 @@ class ValidationConstants { private constructor() {} public static readonly REQUIRED_FIELD_ERROR_CODE: string = "REQUIRED_FIELD_ERROR"; + + /** + * Behavior flag an executor declares to quality as a password recovery factor. + */ + public static readonly RECOVERY_FACTOR_BEHAVIOR_FLAG: string = "RECOVERY_FACTOR"; } export default ValidationConstants; diff --git a/features/admin.flow-builder-core.v1/hooks/use-factor-validation.ts b/features/admin.flow-builder-core.v1/hooks/use-factor-validation.ts index f713c005858..075b4071142 100644 --- a/features/admin.flow-builder-core.v1/hooks/use-factor-validation.ts +++ b/features/admin.flow-builder-core.v1/hooks/use-factor-validation.ts @@ -22,8 +22,11 @@ RegistrationFlowExecutorConstants import { Edge, Node, ReactFlowState, getIncomers, useStore } from "@xyflow/react"; import { useEffect, useMemo } from "react"; import { useTranslation } from "react-i18next"; +import useAuthenticationFlowBuilderCore from "./use-authentication-flow-builder-core-context"; import useValidationStatus from "./use-validation-status"; +import ValidationConstants from "../constants/validation-constants"; import { Action, Element } from "../models/elements"; +import { ExtensionExecutorInterface } from "../models/metadata"; import Notification, { NotificationType } from "../models/notification"; import { Resource } from "../public-api"; @@ -33,6 +36,7 @@ import { Resource } from "../public-api"; const useRecoveryFactorValidation = (node: Node): void => { const { t } = useTranslation(); const { addNotification, removeNotification, validationConfig } = useValidationStatus(); + const { metadata } = useAuthenticationFlowBuilderCore(); const nodes: Node[] = useStore((state: ReactFlowState) => state.nodes); const edges: Edge[] = useStore((state: ReactFlowState) => state.edges); @@ -76,19 +80,40 @@ const useRecoveryFactorValidation = (node: Node): void => { }; /** - * Finds factor views such as SMS OTP, Email OTP and Magic Link in a node's components. + * Executors that satisfy the recovery factor requirement. Covers the built-in factors and any + * executor contributed by a deployed extension that declares the RECOVERY_FACTOR behavior flag. + */ + const recoveryFactorExecutors: Set = useMemo(() => { + const factors: Set = new Set([ + RegistrationFlowExecutorConstants.EMAIL_OTP_EXECUTOR, + RegistrationFlowExecutorConstants.SMS_OTP_EXECUTOR, + RegistrationFlowExecutorConstants.MAGIC_LINK_EXECUTOR + ]); + + metadata?.extensionExecutors?.forEach((executor: ExtensionExecutorInterface) => { + if (executor?.name + && executor?.behaviorFlags?.includes(ValidationConstants.RECOVERY_FACTOR_BEHAVIOR_FLAG)) { + factors.add(executor.name); + } + }); + + return factors; + }, [ metadata?.extensionExecutors ]); + + /** + * Finds factor views such as SMS OTP, Email OTP, Magic Link or an extension contributed + * recovery factor in a node's components or in its own action. */ const FactorExistsInTheFlow = (node: Node): boolean => { + const isRecoveryFactor = (executorName: string): boolean => + !!executorName && recoveryFactorExecutors.has(executorName); + return (node?.data?.components as Element[])?.some((parent: Element) => parent?.components?.some( - (child: Element) => child?.action?.executor?.name === - RegistrationFlowExecutorConstants.EMAIL_OTP_EXECUTOR || - child?.action?.executor?.name === - RegistrationFlowExecutorConstants.SMS_OTP_EXECUTOR + (child: Element) => isRecoveryFactor(child?.action?.executor?.name) ) - ) || (node?.data?.action as Action)?.executor?.name === - RegistrationFlowExecutorConstants.MAGIC_LINK_EXECUTOR; + ) || isRecoveryFactor((node?.data?.action as Action)?.executor?.name); }; /** @@ -116,7 +141,7 @@ const useRecoveryFactorValidation = (node: Node): void => { } return false; - }, [ isRecoveryFactorValidationEnabled, containsResetPassword, node, ancestors ]); + }, [ isRecoveryFactorValidationEnabled, containsResetPassword, node, ancestors, recoveryFactorExecutors ]); const errorNotificationId: string = `recovery-factor-validation-${node?.id}`; From af825b681a3ab9bb58868d50272cab6ff1d8b7fd Mon Sep 17 00:00:00 2001 From: VIHANGAGIT Date: Thu, 13 Aug 2026 14:24:51 +0530 Subject: [PATCH 3/4] Refactor --- .../hooks/use-extension-executor.ts | 4 +--- .../hooks/use-factor-validation.ts | 7 +++---- .../i18n/src/translations/en-US/portals/applications.ts | 5 ++--- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/features/admin.flow-builder-core.v1/hooks/use-extension-executor.ts b/features/admin.flow-builder-core.v1/hooks/use-extension-executor.ts index 4b5aefa9cb6..3eb3a9d0579 100644 --- a/features/admin.flow-builder-core.v1/hooks/use-extension-executor.ts +++ b/features/admin.flow-builder-core.v1/hooks/use-extension-executor.ts @@ -23,7 +23,7 @@ import { ExtensionExecutorInterface } from "../models/metadata"; /** * Hook that resolves the metadata of an executor contributed by an extension deployed on the server. */ -export const useExtensionExecutor = (executorName: string): ExtensionExecutorInterface | null => { +const useExtensionExecutor = (executorName: string): ExtensionExecutorInterface | null => { const { metadata } = useAuthenticationFlowBuilderCore(); return useMemo(() => { @@ -45,5 +45,3 @@ export const useIsConnectionlessExtensionExecutor = (executorName: string): bool return !!executor && executor.requiresConnection === false; }; - -export default useExtensionExecutor; diff --git a/features/admin.flow-builder-core.v1/hooks/use-factor-validation.ts b/features/admin.flow-builder-core.v1/hooks/use-factor-validation.ts index 075b4071142..7897e7227ce 100644 --- a/features/admin.flow-builder-core.v1/hooks/use-factor-validation.ts +++ b/features/admin.flow-builder-core.v1/hooks/use-factor-validation.ts @@ -129,7 +129,7 @@ const useRecoveryFactorValidation = (node: Node): void => { const hasAncestors: boolean = ancestors?.length > 0; /** - * Checks if any ancestor nodes have factor views such as SMS OTP, Email OTP and Magic Link. + * Checks if any ancestor nodes have a recovery factor view. */ const checkIfAncestorsHaveFactors: boolean = useMemo((): boolean => { if (!isRecoveryFactorValidationEnabled || !containsResetPassword || !node) return false; @@ -170,9 +170,8 @@ const useRecoveryFactorValidation = (node: Node): void => { errorNotificationId, t( "flowBuilder:validations.passwordRecoveryRequiresFactors.message", - "Password recovery requires at least one of the following " + - "factors to be present in the flow: Email OTP, SMS OTP, " + - "or Magic Link." + "Password recovery requires at least one recovery factor, such as " + + "Email OTP, SMS OTP or Magic Link, to be present in the flow." ), NotificationType.ERROR ); diff --git a/modules/i18n/src/translations/en-US/portals/applications.ts b/modules/i18n/src/translations/en-US/portals/applications.ts index 3769424fe8b..663249de062 100644 --- a/modules/i18n/src/translations/en-US/portals/applications.ts +++ b/modules/i18n/src/translations/en-US/portals/applications.ts @@ -1197,9 +1197,8 @@ export const applications: ApplicationsNS = { message: "Email OTP requires at least one email field to be present in the flow." }, passwordRecoveryRequiresFactors: { - message: "Password recovery requires at least one of the following " + - "factors to be present in the flow: Email OTP, SMS OTP, " + - "or Magic Link." + message: "Password recovery requires at least one recovery factor, such as " + + "Email OTP, SMS OTP or Magic Link, to be present in the flow." }, passwordExecutorRequired: { message: "Forms with a Password field requires a 'Provision Password' Action to be configured for the button." From 0fdd131fac299f6e2c7e67de2cd4ecae09eafcde Mon Sep 17 00:00:00 2001 From: VIHANGAGIT Date: Mon, 17 Aug 2026 15:22:03 +0530 Subject: [PATCH 4/4] Add changeset --- .changeset/hot-llamas-burn.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/hot-llamas-burn.md diff --git a/.changeset/hot-llamas-burn.md b/.changeset/hot-llamas-burn.md new file mode 100644 index 00000000000..eacfb5dc2c8 --- /dev/null +++ b/.changeset/hot-llamas-burn.md @@ -0,0 +1,10 @@ +--- +"@wso2is/admin.password-recovery-flow-builder.v1": patch +"@wso2is/admin.ask-password-flow-builder.v1": patch +"@wso2is/admin.registration-flow-builder.v1": patch +"@wso2is/admin.flow-builder-core.v1": patch +"@wso2is/console": patch +"@wso2is/i18n": patch +--- + +Resolve steps in flow builder for dynamically registered executors from /dropins