Skip to content

Commit

Permalink
Merge pull request #72 from pegasystems/bugfix/libs
Browse files Browse the repository at this point in the history
Add support for oauthConnect component
  • Loading branch information
ricmars authored Aug 28, 2024
2 parents 23ccf68 + cb455d3 commit f02b1e9
Show file tree
Hide file tree
Showing 15 changed files with 438 additions and 2 deletions.
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.
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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions .storybook/static/login-demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">

<head>
<title>Demo Login</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1.0, viewport-fit=cover" />
<style></style>
</head>
<body>
<img width="250" height="100" src="pega-logo.svg" alt="Constellation UI Gallery">
<p>This popup window simulates an external application login credentials</p>
<button onclick="window.close();">Click to simulate login and close this window</button>
</body>
</html>
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"pasteable",
"PHARMACODE",
"reactflow",
"SAOAUTH",
"Talia",
"timegrid",
"widthpx"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ export const StyledCustomTreeLeaf = styled.div(() => {

StyledCustomTreeLeaf.defaultProps = defaultThemeProp;

// FIXME: any is used since typeof StyledNodeInteraction not playing nicely.
export const StyledCustomTreeParent: any = styled(StyledNodeInteraction)(({ theme }) => {
const { ltr } = useDirection();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,6 @@ const CustomTreeWithNodes: FunctionComponent<CustomTreeProps & ForwardProps> = f
]
)}
>
{/* FIXME: Types are having issues when styled(Tree) is typeof Tree. */}
<StyledCustomTree {...restProps} ref={treeRef} nodes={nodes} nodeRenderer={NodeRenderer} />
</CustomTreeContext.Provider>
);
Expand Down
45 changes: 45 additions & 0 deletions src/components/Pega_Extensions_OAuthConnect/Docs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Meta, Primary, Controls, Story } from '@storybook/blocks';
import * as DemoStories from './demo.stories';

<Meta of={DemoStories} />

# Overview

The OAuth Connect component facilitates seamless third-party authentication using the OAuth 2.0 authorization code grant flow.
When a user is not yet authenticated, a 'Connect' button will appear in the widget. Clicking this button initiates a popup window, redirecting the user to the external identity provider for authorization.
Upon successful authorization, the widget displays a 'Disconnect' button, allowing users to revoke their access token.

While the 'Disconnect' button is visible by default, it can be hidden from the user interface if necessary.

Other limitations:

- Authentication is handled through external URL and does not support a custom authorization View
- There is no UI into the widget from your 3rd party application. The JWT token will be available to the component and can be used to call other APIs or to load a URL inside an iframe.

<Primary />

## Props

<Controls />

## Example

Here is how to configure this component in authoring UI:

![Data Page Configuration](OAuthConnect_Configuration_5.png)

To use the component, you will need to have the data page D_OAuthConnect available - This DP is provided as part of the Computerland demo application.

![Data Page Configuration](OAuthConnect_Configuration_1.png)

![Data Page Configuration](OAuthConnect_Configuration_2.png)

The DP will react to the following events to the server (AUTHENTICATE, AUTHORIZE, REVOKE). These events will be used to perform the connect / disconnect functionality.

When create the authentication profile, make sure that the redirect url is point to YOURHOST/prweb/PRRestService/oauth2/v1/redirect so that the redirect endpoint and the activity 'pzGetAccessToken' is called.

![Data Page Configuration](OAuthConnect_Configuration_3.png)

In 23.1, this activity does not call the Constellation Messaging server to notify that the widget should refresh. To workaround this issue, the pyCloseWindow has been extended in the Computerland application to call the activity on load of the HTML fragment.

![Data Page Configuration](OAuthConnect_Configuration_4.png)
57 changes: 57 additions & 0 deletions src/components/Pega_Extensions_OAuthConnect/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"name": "Pega_Extensions_OAuthConnect",
"label": "OAuth Connect",
"description": "Allows to integrate external applications into Constellation UI using OAuth 2.0 authorization code grant flow",
"organization": "Pega",
"version": "1.0.0",
"library": "Extensions",
"allowedApplications": [],
"componentKey": "Pega_Extensions_OAuthConnect",
"type": "Widget",
"subtype": ["PAGE", "CASE"],
"properties": [
{
"name": "heading",
"label": "Heading",
"format": "TEXT",
"defaultValue": "Demo Application"
},
{
"name": "profileName",
"label": "Authentication profile",
"format": "TEXT",
"required": "true"
},
{
"name": "connectLabel",
"label": "Connect button text",
"format": "TEXT",
"defaultValue": "Connect",
"visibility": "(!customAuth = true)"
},
{
"name": "showDisconnect",
"label": "Show disconnect",
"format": "BOOLEAN",
"defaultValue": true
},
{
"name": "disconnectLabel",
"label": "Disconnect button text",
"format": "TEXT",
"defaultValue": "Disconnect",
"visibility": "showDisconnect = true"
},
{
"label": "Conditions",
"format": "GROUP",
"properties": [
{
"name": "visibility",
"label": "Visibility",
"format": "VISIBILITY"
}
]
}
]
}
141 changes: 141 additions & 0 deletions src/components/Pega_Extensions_OAuthConnect/demo.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import type { StoryObj } from '@storybook/react';
import { PegaExtensionsOAuthConnect, type OAuthConnectProps } from './index';

interface OAuthConnectStoryProps extends OAuthConnectProps {
isConnected: boolean;
}

export default {
title: 'Widgets/OAuth Connect',
argTypes: {
getPConnect: {
table: {
disable: true
}
}
},
component: PegaExtensionsOAuthConnect
};

const setPCore = (args: { isConnected: boolean; profileName: string }) => {
const { isConnected, profileName } = args;
(window as any).PCore = {
getConstants: () => {
return {
CASE_INFO: {
CASE_INFO_ID: 'ID'
}
};
},
getMessagingServiceManager: () => {
return {
subscribe: () => {
/* nothing */
},
unsubscribe: () => {
/* nothing */
}
};
},
getEnvironmentInfo: () => {
return {
getOperatorIdentifier: () => {
return 'operator';
}
};
},
getSemanticUrlUtils: () => {
return {
getResolvedSemanticURL: () => {
return '/case/case-1';
},
getActions: () => {
return {
ACTION_OPENWORKBYHANDLE: 'openWorkByHandle'
};
}
};
},
getDataPageUtils: () => {
return {
getPageDataAsync: (
data: string,
context: string,
parameters: { Event: string; ProfileName: string; gadgetId: string }
) => {
const { Event, ProfileName, gadgetId } = parameters;
switch (Event) {
case 'AUTHENTICATE':
if (isConnected) {
return Promise.resolve({
pyServiceType: 'AUTHENTICATE',
pyTaskStatus: true,
pyIsAuthenticated: true,
pyAccessToken: 'xxxx',
pyLabel: profileName,
pyExpiresAt: '20270828T181056.516 GMT'
});
} else {
return Promise.resolve({
pyServiceType: 'AUTHENTICATE',
pyTaskStatus: true,
pyIsAuthenticated: false
});
}
case 'AUTHORIZE':
return Promise.resolve({
pyServiceType: 'AUTHORIZE',
pyOauthURLRedirect: 'login-demo.html',
pyTaskStatus: true
});
case 'REVOKE':
return Promise.resolve({
pyServiceType: 'REVOKE',
pyTaskStatus: true
});
}
}
};
}
};
};

type Story = StoryObj<OAuthConnectStoryProps>;
export const Default: Story = {
render: (args: any) => {
setPCore(args);
const props = {
...args,
getPConnect: () => {
return {
getCaseInfo: () => {
return {
getKey: () => 'OPGO8L-CARINSUR-WORK A-5'
};
},
getActionsApi: () => {
return {
openWorkByHandle: () => {
/* nothing */
},
showCasePreview: () => {
/* nothing */
}
};
},
getContextName: () => '',
getValue: () => 'OPGO8L-CARINSUR-WORK A-5'
};
}
};
return <PegaExtensionsOAuthConnect {...props} />;
},
args: {
heading: 'Demo Application',
profileName: 'Demo application',
connectLabel: 'Connect',
showDisconnect: true,
isConnected: false,
disconnectLabel: 'Disconnect'
}
};
12 changes: 12 additions & 0 deletions src/components/Pega_Extensions_OAuthConnect/demo.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { render, screen } from '@testing-library/react';
import { composeStories } from '@storybook/react';
import * as DemoStories from './demo.stories';

const { Default } = composeStories(DemoStories);

test('renders OAuth Connect component with default args', async () => {
render(<Default />);
expect(await screen.findByText('Demo Application')).toBeVisible();
const buttonElement = screen.getByRole('button', { name: 'Connect' });
expect(buttonElement).not.toBeNull();
});
Loading

0 comments on commit f02b1e9

Please sign in to comment.