Skip to content

Commit

Permalink
Add Azure AD support to Explorer (#641)
Browse files Browse the repository at this point in the history
  • Loading branch information
eputtone authored May 3, 2024
1 parent 67fe85f commit 4a51d59
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 23 deletions.
52 changes: 52 additions & 0 deletions nflow-explorer/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions nflow-explorer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"private": true,
"homepage": ".",
"dependencies": {
"@azure/msal-browser": "^3.13.0",
"@azure/msal-react": "^2.0.15",
"@fontsource/roboto": "^4.5.8",
"@material-ui/core": "^4.12.4",
"@material-ui/icons": "^4.11.3",
Expand Down
15 changes: 15 additions & 0 deletions nflow-explorer/public/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,19 @@ var Config = new function() {
* ]
*/
this.searchResultColumns = undefined;

/**
* Microsoft Authentication Library for JavaScript configuration. For more details, see:
* https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/master/lib/msal-browser/docs/configuration.md
* this.msalConfig = {
* auth: {
* authority: "https://login.microsoftonline.com/ENTER_YOUR_TENANT_ID",
* clientId: "ENTER_YOUR_APPLICATION_ID"
* },
* cache: {
* cacheLocation: "localStorage"
* }
* };
*/
this.msalConfig = undefined;
};
26 changes: 25 additions & 1 deletion nflow-explorer/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';
import './index.scss';
import {Config} from "./types";
import {PublicClientApplication, InteractionType} from "@azure/msal-browser";
import {
MsalAuthenticationTemplate,
MsalProvider,
} from "@azure/msal-react";

// see: https://github.com/gregnb/mui-datatables/issues/1893
const oldRender = (TableCell as any).render;
Expand All @@ -37,13 +43,31 @@ const theme = createTheme({
}
});

const wrapMsalIfNeeded = (app: any, config: Config): any => {
if (config.msalConfig) {
console.info('Initializing MSAL')
const authRequest = {
scopes: ["openid"]
};
config.msalClient = new PublicClientApplication(config.msalConfig);
return (
<MsalProvider instance={config.msalClient}>
<MsalAuthenticationTemplate interactionType={InteractionType.Redirect} authenticationRequest={authRequest}>
{app}
</MsalAuthenticationTemplate>
</MsalProvider>
)
}
return app;
}

readConfig().then(config => {
console.info('Config read');
ReactDOM.render(
<React.StrictMode>
<MuiThemeProvider theme={theme}>
<ConfigContext.Provider value={config}>
<App />
{wrapMsalIfNeeded(<App />, config)}
</ConfigContext.Provider>
</MuiThemeProvider>
</React.StrictMode>,
Expand Down
55 changes: 33 additions & 22 deletions nflow-explorer/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,30 @@ const convertWorkflowInstance = (instance: any) => {
);
};

const authenticatedApiCall = (url: string, config: Config, body?: any): Promise<any> => {
const options: RequestInit = {
method: body ? "PUT" : "GET",
body: body
};
if (!config.msalClient) {
return fetch(url, options);
}
const request = {
scopes: ["openid"],
};
config.msalClient.setActiveAccount(config.msalClient.getAllAccounts()[0]) // required by acquireTokenSilent
return config.msalClient.acquireTokenSilent(request)
.then(tokenResponse => {
options["headers"] = new Headers({
"Authorization": "Bearer " + tokenResponse.accessToken,
"content-type": "application/json"
});
return fetch(url, options)
});
}

const listExecutors = (config: Config): Promise<Array<Executor>> => {
return fetch(serviceUrl(config, '/v1/workflow-executor'))
return authenticatedApiCall(serviceUrl(config, '/v1/workflow-executor'), config)
.then(response => response.json())
.then((items: any) => items.map(convertExecutor));
};
Expand All @@ -53,7 +75,7 @@ const listWorkflowDefinitions = (
if (cached) {
return Promise.resolve(cached);
}
return fetch(url)
return authenticatedApiCall(url, config)
.then(response => response.json())
.then((response: Array<WorkflowDefinition>) =>
cacheWD.setAndReturn(url, response)
Expand All @@ -66,7 +88,7 @@ const getWorkflowDefinition = (
): Promise<WorkflowDefinition> => {
const url = serviceUrl(config, '/v1/workflow-definition?type=' + type);
return (
fetch(url)
authenticatedApiCall(url, config)
.then(response => response.json())
// TODO how to handle Not found case?
.then(response => response[0])
Expand All @@ -79,7 +101,7 @@ const getWorkflowStatistics = (
type: string
): Promise<WorkflowStatistics> => {
const url = serviceUrl(config, '/v1/statistics/workflow/' + type);
return fetch(url)
return authenticatedApiCall(url, config)
.then(response => response.json())
.then(response => response.stateStatistics);
};
Expand Down Expand Up @@ -146,7 +168,7 @@ const listWorkflowInstances = (
query?: any
): Promise<WorkflowInstance[]> => {
const params = new URLSearchParams(query).toString();
return fetch(serviceUrl(config, '/v1/workflow-instance?' + params.toString()))
return authenticatedApiCall(serviceUrl(config, '/v1/workflow-instance?' + params.toString()), config)
.then(response => response.json())
.then((items: any) => items.map(convertWorkflowInstance));
};
Expand All @@ -159,7 +181,7 @@ const listChildWorkflowInstances = (
config,
'/v1/workflow-instance?parentWorkflowId=' + id
);
return fetch(url)
return authenticatedApiCall(url, config)
.then(response => response.json())
.then((items: any) => items.map(convertWorkflowInstance));
};
Expand All @@ -174,7 +196,7 @@ const getWorkflowInstance = (
id +
'?include=actions,currentStateVariables,actionStateVariables'
);
return fetch(url)
return authenticatedApiCall(url, config)
.then(response => response.json())
.then(convertWorkflowInstance);
// TODO how to handle Not found case?
Expand All @@ -185,11 +207,8 @@ const createWorkflowInstance = (
data: NewWorkflowInstance
): Promise<NewWorkflowInstanceResponse> => {
const url = serviceUrl(config, '/v1/workflow-instance');
return fetch(url, {
method: 'PUT',
headers: {'content-type': 'application/json'},
body: JSON.stringify(data)
}).then(response => response.json());
return authenticatedApiCall(url, config, JSON.stringify(data))
.then(response => response.json());
};

const updateWorkflowInstance = (
Expand All @@ -198,11 +217,7 @@ const updateWorkflowInstance = (
data: any
): Promise<any> => {
const url = serviceUrl(config, '/v1/workflow-instance/id/' + workflowId);
return fetch(url, {
method: 'PUT',
headers: {'content-type': 'application/json'},
body: JSON.stringify(data)
});
return authenticatedApiCall(url, config, JSON.stringify(data));
};

const sendWorkflowInstanceSignal = (
Expand All @@ -214,11 +229,7 @@ const sendWorkflowInstanceSignal = (
config,
'/v1/workflow-instance/' + workflowId + '/signal'
);
return fetch(url, {
method: 'PUT',
headers: {'content-type': 'application/json'},
body: JSON.stringify(data)
});
return authenticatedApiCall(url, config, JSON.stringify(data));
};

export {
Expand Down
4 changes: 4 additions & 0 deletions nflow-explorer/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {Configuration, PublicClientApplication} from "@azure/msal-browser";

interface Endpoint {
id: string;
title: string;
Expand Down Expand Up @@ -25,6 +27,8 @@ interface Config {
nflowLogoFile?: string;
nflowLogoTitle?: string;
searchResultColumns?: Array<ColumnConfig>;
msalConfig?: Configuration;
msalClient?: PublicClientApplication;
}

interface Executor {
Expand Down

0 comments on commit 4a51d59

Please sign in to comment.