Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance OpenAPI integration with runtime auth handling #53

Merged
merged 3 commits into from
Nov 11, 2024
Merged
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
6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"printWidth": 120,
"trailingComma": "all",
"singleQuote": true,
"semi": true
}
19 changes: 10 additions & 9 deletions package-lock.json

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

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@comake/skl-js-engine",
"version": "0.23.0",
"version": "0.24.0",
"description": "Standard Knowledge Language Javascript Engine",
"keywords": [
"skl",
Expand Down Expand Up @@ -42,7 +42,7 @@
]
},
"dependencies": {
"@comake/openapi-operation-executor": "^0.11.1",
"@comake/openapi-operation-executor": "^0.12.1",
"@comake/rmlmapper-js": "^0.5.2",
"@comunica/query-sparql-rdfjs": "^2.10.0",
"@rdfjs/data-model": "^1.3.0",
Expand Down
107 changes: 94 additions & 13 deletions src/SklEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
import type {
OpenApi,
OpenApiClientConfiguration,
OperationWithPathInfo,
} from '@comake/openapi-operation-executor';
import { OpenApiOperationExecutor } from '@comake/openapi-operation-executor';
import { getIdFromNodeObjectIfDefined, type ReferenceNodeObject } from '@comake/rmlmapper-js';
import axios from 'axios';

Check failure on line 9 in src/SklEngine.ts

View workflow job for this annotation

GitHub Actions / lint

'/home/runner/work/skl-js-engine/skl-js-engine/node_modules/axios/index.js' imported multiple times
import type { AxiosError, AxiosResponse } from 'axios';

Check failure on line 10 in src/SklEngine.ts

View workflow job for this annotation

GitHub Actions / lint

`axios` import should occur before import of `axios`
import type { ContextDefinition, GraphObject, NodeObject } from 'jsonld';
import type { Frame } from 'jsonld/jsonld-spec';
import { JSONPath } from 'jsonpath-plus';
Expand Down Expand Up @@ -53,7 +54,8 @@
ensureArray,
} from './util/Util';
import { SKL, SHACL, RDFS, SKL_ENGINE, XSD, RDF } from './util/Vocabularies';
import { GroupByOptions, GroupByResponse } from './storage/GroupOptionTypes';

Check failure on line 57 in src/SklEngine.ts

View workflow job for this annotation

GitHub Actions / lint

All imports in the declaration are only used as types. Use `import type`

Check failure on line 57 in src/SklEngine.ts

View workflow job for this annotation

GitHub Actions / lint

`./storage/GroupOptionTypes` import should occur before import of `./storage/operator/Exists`
import { AxiosRequestConfig } from 'axios';

Check failure on line 58 in src/SklEngine.ts

View workflow job for this annotation

GitHub Actions / lint

All imports in the declaration are only used as types. Use `import type`

Check failure on line 58 in src/SklEngine.ts

View workflow job for this annotation

GitHub Actions / lint

`axios` import should occur before import of `jsonld`

Check failure on line 58 in src/SklEngine.ts

View workflow job for this annotation

GitHub Actions / lint

'/home/runner/work/skl-js-engine/skl-js-engine/node_modules/axios/index.js' imported multiple times

export type VerbHandler = <T extends OrArray<NodeObject> = OrArray<NodeObject>>(
params: JSONObject,
Expand Down Expand Up @@ -826,6 +828,32 @@
});
}

private async findgetOpenApiRuntimeAuthorizationVerbIfDefined(): Promise<Verb | undefined> {
return (await this.findByIfExists({
type: SKL.Verb,
[RDFS.label]: 'getOpenApiRuntimeAuthorization',
})) as Verb;
}

private async getRuntimeCredentialsWithSecurityCredentials(securityCredentials: Entity, integrationId: string, openApiOperationInformation: OperationWithPathInfo, operationArgs: JSONObject): Promise<JSONObject> {

Check failure on line 838 in src/SklEngine.ts

View workflow job for this annotation

GitHub Actions / lint

This line has a length of 214. Maximum allowed is 120
const getOpenApiRuntimeAuthorizationVerb = await this.findgetOpenApiRuntimeAuthorizationVerbIfDefined();
if (!getOpenApiRuntimeAuthorizationVerb) {
return {};
}
const mapping = await this.findVerbIntegrationMapping(getOpenApiRuntimeAuthorizationVerb['@id'], integrationId);
if (!mapping) {
return {};
}
const args = {
securityCredentials,
openApiExecutorOperationWithPathInfo: openApiOperationInformation,
operationArgs,
} as JSONObject;
const operationInfoJsonLd = await this.performParameterMappingOnArgsIfDefined(args, mapping, undefined, true);
const headers = getValueIfDefined<JSONObject>(operationInfoJsonLd[SKL.headers]);
return headers ?? {};
}

private async createOpenApiOperationExecutorWithSpec(openApiDescription: OpenApi): Promise<OpenApiOperationExecutor> {
const executor = new OpenApiOperationExecutor();
await executor.setOpenapiSpec(openApiDescription);
Expand Down Expand Up @@ -889,31 +917,84 @@
const integrationId = (account[SKL.integration] as ReferenceNodeObject)['@id'];
const openApiDescription = await this.getOpenApiDescriptionForIntegration(integrationId);
const openApiExecutor = await this.createOpenApiOperationExecutorWithSpec(openApiDescription);
const openApiOperationInformation = await openApiExecutor.getOperationWithPathInfoMatchingOperationId(operationId);

Check failure on line 920 in src/SklEngine.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected `await` of a non-Promise (non-"Thenable") value
const securityCredentials = await this.findSecurityCredentialsForAccountIfDefined(account['@id']);
Logger.getInstance().log('Security Credentials', securityCredentials);
let runtimeAuthorization: JSONObject = {};
if (securityCredentials) {
const generatedRuntimeCredentials = await this.getRuntimeCredentialsWithSecurityCredentials(
securityCredentials,
integrationId,
openApiOperationInformation,
operationArgs

Check failure on line 928 in src/SklEngine.ts

View workflow job for this annotation

GitHub Actions / lint

Missing trailing comma
);
if (generatedRuntimeCredentials && Object.keys(generatedRuntimeCredentials).length > 0) {
runtimeAuthorization = generatedRuntimeCredentials;
}
}
const apiKey = [
getValueIfDefined<string>(securityCredentials?.[SKL.apiKey]),
this.getAuthorizationHeaderFromRuntimeCredentials(runtimeAuthorization),
].find(Boolean);
const configuration = {
accessToken: getValueIfDefined<string>(securityCredentials?.[SKL.accessToken]),
bearerToken: getValueIfDefined<string>(securityCredentials?.[SKL.bearerToken]),
apiKey: getValueIfDefined<string>(securityCredentials?.[SKL.apiKey]),
apiKey,
basePath: getValueIfDefined<string>(account[SKL.overrideBasePath]),
username: getValueIfDefined<string>(securityCredentials?.[SKL.clientId]),
password: getValueIfDefined<string>(securityCredentials?.[SKL.clientSecret]),
};
const response = await openApiExecutor.executeOperation(operationId, configuration, operationArgs)
.catch(async(error: Error | AxiosError): Promise<any> => {
if (axios.isAxiosError(error) && await this.isInvalidTokenError(error, integrationId) && securityCredentials) {
const refreshedConfiguration = await this.refreshSecurityCredentials(
securityCredentials,
integrationId,
account,
);
return await openApiExecutor.executeOperation(operationId, refreshedConfiguration, operationArgs);
}
let response;
try {
const additionalHeaders = this.getHeadersFromRuntimeCredentials(runtimeAuthorization) as any;
let executeOperationOptions: AxiosRequestConfig| undefined;
if (
additionalHeaders &&
typeof additionalHeaders === 'object' &&
!Array.isArray(additionalHeaders) &&
Object.keys(additionalHeaders).length > 0
) {
executeOperationOptions = { headers: additionalHeaders };
}
response = await openApiExecutor.executeOperation(operationId, configuration, operationArgs, executeOperationOptions);
} catch (error) {
if (axios.isAxiosError(error) && (await this.isInvalidTokenError(error, integrationId)) && securityCredentials) {
const refreshedConfiguration = await this.refreshSecurityCredentials(
securityCredentials,
integrationId,
account,
);
response = await openApiExecutor.executeOperation(operationId, refreshedConfiguration, operationArgs);
} else {
throw error;
});
}
}
return response;
}

private getHeadersFromRuntimeCredentials(runtimeCredentials: JSONObject): JSONObject {
let returnValue: JSONObject = {};
if (
runtimeCredentials.headers &&
typeof runtimeCredentials.headers === 'object' &&
Object.keys(runtimeCredentials.headers).length > 0 &&
!Array.isArray(runtimeCredentials.headers)
) {
returnValue = runtimeCredentials.headers;
}
return returnValue;
}

private getAuthorizationHeaderFromRuntimeCredentials(runtimeCredentials: JSONObject): string | undefined {
const headers = this.getHeadersFromRuntimeCredentials(runtimeCredentials);
if (headers && 'Authorization' in headers) {
const authorizationHeader = headers['Authorization'];
if (typeof authorizationHeader === 'string') {
return authorizationHeader;
}
}
return undefined;
}

private async isInvalidTokenError(error: AxiosError, integrationId: string): Promise<boolean> {
const integration = await this.findBy({ id: integrationId });
const errorMatcher = integration[SKL.invalidTokenErrorMatcher] as NodeObject;
Expand Down
2 changes: 2 additions & 0 deletions src/util/Vocabularies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ export const SKL = createNamespace(SKL_NAMESPACE, [
'Dataview',
'Entity',
'query',
'getOpenApiRuntimeAuthorization',
'headers',
]);

export const SKL_ENGINE_NAMESPACE = 'https://standardknowledge.com/ontologies/skl-engine/';
Expand Down
Loading
Loading