Skip to content

Commit

Permalink
Enhance OpenAPI integration with runtime auth handling
Browse files Browse the repository at this point in the history
Updated the `openapi-operation-executor` package to version 0.12.1. Implemented functionality to dynamically generate runtime credentials and incorporate authorization headers in API requests. Added methods for handling API key retrieval from runtime security credentials.
  • Loading branch information
jagzmz committed Nov 5, 2024
1 parent 11137bc commit 8907ca4
Show file tree
Hide file tree
Showing 9 changed files with 16,161 additions and 35 deletions.
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
}
15 changes: 8 additions & 7 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
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
105 changes: 92 additions & 13 deletions src/SklEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
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';
Expand Down Expand Up @@ -54,6 +55,7 @@ import {
} 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,31 @@ export class SKLEngine {
});
}

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): 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 187. 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,
} 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 +916,83 @@ export class SKLEngine {
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 919 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,
);
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;

Check failure on line 947 in src/SklEngine.ts

View workflow job for this annotation

GitHub Actions / lint

Operator '|' must be spaced
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

0 comments on commit 8907ca4

Please sign in to comment.