Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions features/admin.actions.v1/models/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -616,3 +616,20 @@
*/
disabled?: boolean
}

/**
* Expose/modify entry in access config.
* Same structure is used for both expose and modify entries: a path plus an optional encryption flag.
*/
export interface ContextPathInterface {

Check failure on line 624 in features/admin.actions.v1/models/actions.ts

View workflow job for this annotation

GitHub Actions / ✂️ Knip (DEAD CODE) (20.x, 10.33.0)

✂️ Knip / Unused exported types

ContextPathInterface in features/admin.actions.v1/models/actions.ts

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems this interface is no longer used and can be removed. Please verify.

path: string;
encrypted: boolean;
}

/**
* Access config for Flow Extension actions.
*/
export interface AccessConfigInterface {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interface name feels too generic. Shall we rename it to something like FlowExtensionAccessConfigInterface?

Also, should this be defined inside actions.v1?

expose: ContextPathInterface[];
modify: ContextPathInterface[];
}
76 changes: 76 additions & 0 deletions features/admin.connections.v1/api/create-flow-extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* 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 { AsgardeoSPAClient, HttpClientInstance } from "@asgardeo/auth-react";
import { RequestConfigInterface } from "@wso2is/admin.core.v1/hooks/use-request";
import { store } from "@wso2is/admin.core.v1/store";
import { IdentityAppsApiException } from "@wso2is/core/exceptions";
import { HttpErrorResponseDataInterface, HttpMethods } from "@wso2is/core/models";
import { AxiosError, AxiosResponse } from "axios";
import {
FlowExtensionCreateRequestInterface,
FlowExtensionResponseInterface
} from "../models/flow-extension";
Comment on lines +25 to +28

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we keep this in a single line for compactness?


const httpClient: HttpClientInstance = AsgardeoSPAClient.getInstance()
.httpRequest.bind(AsgardeoSPAClient.getInstance());

/**
* Create a Flow Extension connection.
*
* @param body - Flow Extension create request body.
* @returns A promise that resolves with the created Flow Extension.
* @throws Throws an IdentityAppsApiException if the request fails.
*/
const createFlowExtension = (
body: FlowExtensionCreateRequestInterface
): Promise<FlowExtensionResponseInterface> => {
const requestConfig: RequestConfigInterface = {
data: body,
method: HttpMethods.POST,
url: store.getState().config.endpoints.flowExtension
};

return httpClient(requestConfig)
.then((response: AxiosResponse) => {
if (response.status !== 201) {
throw new IdentityAppsApiException(
"Failed to create the flow extension.",
null,
response.status,
response.request,
response,
response.config
);
}

return Promise.resolve(response.data as FlowExtensionResponseInterface);
})
.catch((error: AxiosError<HttpErrorResponseDataInterface>) => {
throw new IdentityAppsApiException(
error.message,
error.stack,
error.response?.data?.code,
error.request,
error.response,
error.config
);
});
};

export default createFlowExtension;
69 changes: 69 additions & 0 deletions features/admin.connections.v1/api/delete-flow-extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* 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 { AsgardeoSPAClient, HttpClientInstance } from "@asgardeo/auth-react";
import { RequestConfigInterface } from "@wso2is/admin.core.v1/hooks/use-request";
import { store } from "@wso2is/admin.core.v1/store";
import { IdentityAppsApiException } from "@wso2is/core/exceptions";
import { HttpErrorResponseDataInterface, HttpMethods } from "@wso2is/core/models";
import { AxiosError, AxiosResponse } from "axios";

const httpClient: HttpClientInstance = AsgardeoSPAClient.getInstance()
.httpRequest.bind(AsgardeoSPAClient.getInstance());

/**
* Delete a Flow Extension connection.
*
* @param id - ID of the Flow Extension to delete.
* @returns A promise resolving with the delete response.
* @throws Throws an IdentityAppsApiException if the request fails.
*/
const deleteFlowExtension = (id: string): Promise<AxiosResponse> => {
const requestConfig: RequestConfigInterface = {
method: HttpMethods.DELETE,
url: `${ store.getState().config.endpoints.flowExtension }/${ id }`
};

return httpClient(requestConfig)
.then((response: AxiosResponse) => {
if (response.status !== 204) {
throw new IdentityAppsApiException(
"Failed to delete the flow extension.",
null,
response.status,
response.request,
response,
response.config
);
}

return response;
})
.catch((error: AxiosError<HttpErrorResponseDataInterface>) => {
throw new IdentityAppsApiException(
error.message,
error.stack,
error.response?.data?.code,
error.request,
error.response,
error.config
);
});
};

export default deleteFlowExtension;
58 changes: 58 additions & 0 deletions features/admin.connections.v1/api/use-get-flow-extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* 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 useRequest, {
RequestConfigInterface,
RequestErrorInterface,
RequestResultInterface
} from "@wso2is/admin.core.v1/hooks/use-request";
import { store } from "@wso2is/admin.core.v1/store";
import { HttpMethods } from "@wso2is/core/models";
import { FlowExtensionBasicResponseInterface } from "../models/flow-extension";

/**
* Hook to get the list of Flow Extensions.
*
* @param shouldFetch - Whether the request should be sent.
* @returns The list of Flow Extensions as an SWR response.
*/
const useGetFlowExtension = <
Data = FlowExtensionBasicResponseInterface[],
Error = RequestErrorInterface
>(
shouldFetch: boolean = true
): RequestResultInterface<Data, Error> => {
Comment on lines +37 to +39

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shall we improve formatting here?


const requestConfig: RequestConfigInterface = {
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
},
method: HttpMethods.GET,
url: store.getState().config.endpoints.flowExtension
};

const { data, error, isLoading, isValidating, mutate } = useRequest<Data, Error>(
shouldFetch ? requestConfig : null,
{ shouldRetryOnError: false }
);

return { data, error, isLoading, isValidating, mutate };
};

export default useGetFlowExtension;
22 changes: 20 additions & 2 deletions features/admin.connections.v1/components/authenticator-grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import {
getConnectedApps,
getConnectedAppsOfAuthenticator
} from "../api/connections";
import deleteFlowExtension from "../api/delete-flow-extension";
import { getConnectionIcons } from "../configs/ui";
import { AuthenticatorMeta } from "../meta/authenticator-meta";
import {
Expand Down Expand Up @@ -212,6 +213,11 @@ export const AuthenticatorGrid: FunctionComponent<AuthenticatorGridPropsInterfac

break;

case ConnectionTypes.FLOW_EXTENSION:
history.push(AppConstants.getPaths().get("FLOW_EXTENSION_EDIT").replace(":id", id));

break;

case AuthenticatorCategories.LOCAL:
if (isCustom) {
history.push(AppConstants.getPaths().get("AUTH_EDIT").replace(":id", id));
Expand Down Expand Up @@ -247,8 +253,8 @@ export const AuthenticatorGrid: FunctionComponent<AuthenticatorGridPropsInterfac
const handleAuthenticatorDelete = async (idpId: string, connectionType: string,
isCustomLocalAuthenticator?: boolean): Promise<void> => {

// If the connection is an Identity Verification Provider, then skip checking for connected apps.
if (connectionType === ConnectionTypes.IDVP) {
// Identity Verification Providers and Flow Extensions have no connected apps to check.
if (connectionType === ConnectionTypes.IDVP || connectionType === ConnectionTypes.FLOW_EXTENSION) {
setDeletingIDP(authenticators.find(
(idp: ConnectionInterface | AuthenticatorInterface) => idp.id === idpId)
);
Expand Down Expand Up @@ -383,6 +389,8 @@ export const AuthenticatorGrid: FunctionComponent<AuthenticatorGridPropsInterfac

if(connectionType === ConnectionTypes.IDVP) {
deleteAuthenticator = deleteIdentityVerificationProvider;
} else if (connectionType === ConnectionTypes.FLOW_EXTENSION) {
deleteAuthenticator = deleteFlowExtension;
} else if (ConnectionsManagementUtils.IsCustomLocalAuthenticator(deletingIDP)) {
deleteAuthenticator = deleteCustomAuthenticator;
} else {
Expand Down Expand Up @@ -497,6 +505,10 @@ export const AuthenticatorGrid: FunctionComponent<AuthenticatorGridPropsInterfac
return false;
}

if (authenticator.type === ConnectionTypes.FLOW_EXTENSION) {
return true;
}

return ConnectionsManagementUtils.isConnectorIdentityProvider(authenticator) ||
(authenticator as ConnectionInterface).type === AuthenticatorTypes.FEDERATED ||
ConnectionsManagementUtils.IsCustomAuthenticator(authenticator) ;
Expand All @@ -521,6 +533,12 @@ export const AuthenticatorGrid: FunctionComponent<AuthenticatorGridPropsInterfac
: getConnectionIcons().default;
}

if (connection.type === ConnectionTypes.FLOW_EXTENSION) {
return connection?.image
? ConnectionsManagementUtils.resolveConnectionResourcePath(connectionResourcesUrl, connection.image)
: AuthenticatorMeta.getFlowExtensionIcon();
}

if ((connection?.type === AuthenticatorTypes.FEDERATED || isIdP) && !isOrganizationSSOIDP) {
return connection?.image
? ConnectionsManagementUtils.resolveConnectionResourcePath(connectionResourcesUrl, connection.image)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,16 @@ export const AuthenticatorCreateWizardFactory: FC<AuthenticatorCreateWizardFacto
true
);

// The Flow Extension template has no protocol template to fetch — it is handled by a
// dedicated wizard in ConnectionCreateWizardFactory, so skip the template fetch for it.
const shouldFetchTemplate: boolean = type !== null
&& type !== CommonAuthenticatorConstants.CONNECTION_TEMPLATE_IDS.FLOW_EXTENSION;

const {
data: connectionTemplate,
isLoading: isConnectionTemplateFetchRequestLoading,
error: connectionTemplateFetchRequestError
} = useGetConnectionTemplate(type === "enterprise-protocols" ? "enterprise-idp" : type, type !== null);
} = useGetConnectionTemplate(type === "enterprise-protocols" ? "enterprise-idp" : type, shouldFetchTemplate);

useEffect(() => {
if (connectionsFetchRequestError) {
Expand Down
Loading
Loading