From 0f9415748880fde1fbabe6d657b2a68f397f9cb5 Mon Sep 17 00:00:00 2001 From: zheyanyu Date: Wed, 9 Feb 2022 14:27:48 -0500 Subject: [PATCH 1/5] feat: add rest-hook Lambda --- cloudformation/subscriptions.yaml | 17 +- package.json | 4 +- serverless.yaml | 11 + src/config.ts | 2 +- src/index.ts | 2 + .../allowList.ts | 12 +- subscriptions/index.ts | 3 + subscriptions/restHook.test.ts | 112 ++++++ subscriptions/restHook.ts | 113 ++++++ subscriptions/restHookMultiTenant.test.ts | 104 ++++++ subscriptions/types.ts | 14 + yarn.lock | 323 ++++++++++++------ 12 files changed, 607 insertions(+), 110 deletions(-) rename {src/subscriptions => subscriptions}/allowList.ts (70%) create mode 100644 subscriptions/index.ts create mode 100644 subscriptions/restHook.test.ts create mode 100644 subscriptions/restHook.ts create mode 100644 subscriptions/restHookMultiTenant.test.ts create mode 100644 subscriptions/types.ts diff --git a/cloudformation/subscriptions.yaml b/cloudformation/subscriptions.yaml index ddf62547..6f095e7e 100644 --- a/cloudformation/subscriptions.yaml +++ b/cloudformation/subscriptions.yaml @@ -96,4 +96,19 @@ Resources: Protocol: sqs FilterPolicy: channelType: - - 'rest-hook' \ No newline at end of file + - 'rest-hook' + + RestHookLambdaRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: 'Allow' + Principal: + Service: 'lambda.amazonaws.com' + Action: 'sts:AssumeRole' + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess + - arn:aws:iam::aws:policy/AdministratorAccess \ No newline at end of file diff --git a/package.json b/package.json index b73c4d13..4cf2e607 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "fhir-works-on-aws-persistence-ddb": "3.9.0", "fhir-works-on-aws-routing": "6.3.0", "fhir-works-on-aws-search-es": "3.9.2", + "lodash": "^4.17.21", "serverless-http": "^2.7.0", "yargs": "^16.2.0" }, @@ -62,10 +63,9 @@ "jest-circus": "^26.6.3", "jest-mock-extended": "^1.0.8", "jsonwebtoken": "^8.5.1", - "lodash": "^4.17.21", "prettier": "^2.4.1", "qs": "^6.10.1", - "serverless": "2.64.1", + "serverless": "2.72.2", "serverless-bundle": "^4.4.0", "serverless-offline": "^8.2.0", "serverless-step-functions": "^3.1.1", diff --git a/serverless.yaml b/serverless.yaml index 3d6c4200..519557c9 100644 --- a/serverless.yaml +++ b/serverless.yaml @@ -192,6 +192,17 @@ functions: ELASTICSEARCH_DOMAIN_ENDPOINT: !Join ['', ['https://', !GetAtt ElasticSearchDomain.DomainEndpoint]] NUMBER_OF_SHARDS: !If [isDev, 1, 3] # 133 indices, one per resource types + subscriptionsRestHook: + timeout: 700 + runtime: nodejs14.x + description: 'Send rest-hook notification for subscription' + role: RestHookLambdaRole + handler: subscriptions/index.handler + events: + - sqs: + arn: + !GetAtt RestHookQueue.Arn + stepFunctions: stateMachines: BulkExportStateMachine: ${file(bulkExport/state-machine-definition.yaml)} diff --git a/src/config.ts b/src/config.ts index 123ff9a8..97c6ff42 100644 --- a/src/config.ts +++ b/src/config.ts @@ -25,7 +25,7 @@ import HapiFhirLambdaValidator from 'fhir-works-on-aws-routing/lib/router/valida import SubscriptionValidator from 'fhir-works-on-aws-routing/lib/router/validation/subscriptionValidator'; import RBACRules from './RBACRules'; import { loadImplementationGuides } from './implementationGuides/loadCompiledIGs'; -import getAllowListedSubscriptionEndpoints from './subscriptions/allowList'; +import getAllowListedSubscriptionEndpoints from '../subscriptions/allowList'; const { IS_OFFLINE, ENABLE_MULTI_TENANCY, ENABLE_SUBSCRIPTIONS } = process.env; diff --git a/src/index.ts b/src/index.ts index 919f8085..c7178dbf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,3 +32,5 @@ exports.handler = async (event: any = {}, context: any = {}): Promise => { await ensureAsyncInit(serverlessHandler); return (await serverlessHandler)(event, context); }; + +export default ensureAsyncInit; diff --git a/src/subscriptions/allowList.ts b/subscriptions/allowList.ts similarity index 70% rename from src/subscriptions/allowList.ts rename to subscriptions/allowList.ts index 12a11038..d3d99aa3 100644 --- a/src/subscriptions/allowList.ts +++ b/subscriptions/allowList.ts @@ -6,7 +6,7 @@ import { SubscriptionEndpoint } from 'fhir-works-on-aws-routing/lib/router/validation/subscriptionValidator'; const getAllowListedSubscriptionEndpoints = async (): Promise => { - return []; + // return []; // Add here the endpoints that are allowed in Subscriptions // [ // { @@ -16,6 +16,16 @@ const getAllowListedSubscriptionEndpoints = async (): Promise ({ + __esModule: true, + default: async () => [ + { + endpoint: 'https://fake-end-point-1', + headers: ['header-name-1: header-value-1'], + }, + { + endpoint: new RegExp('^https://fake-end-point-2'), + headers: ['header-name-2: header-value-2'], + }, + ], +})); + +process.env.ENABLE_MULTI_TENANCY = 'false'; + +const getEvent = ({ + channelHeader = ['testKey:testValue'], + channelPayload = 'application/fhir+json', + endpoint = 'https://fake-end-point-1', + tenantId = null as any, +} = {}) => ({ + Records: [ + { + body: JSON.stringify({ + Message: JSON.stringify({ + subscriptionId: 123456, + channelType: 'rest-hook', + tenantId, + endpoint, + channelPayload, + channelHeader, + matchedResource: { + id: 1234567, + resourceType: 'Patient', + versionId: 2, + lastUpdated: 'some-time-stamp', + }, + }), + }), + }, + ], +}); + +describe('Single tenant: Rest hook notification', () => { + beforeEach(() => { + axios.post = jest.fn().mockResolvedValueOnce({ data: { message: 'POST Successful' } }); + axios.put = jest.fn().mockResolvedValueOnce({ data: { message: 'PUT Successful' } }); + }); + + test('Empty POST notification is sent when channelPayload is null', async () => { + await expect(sendRestHookNotification(getEvent({ channelPayload: null as any }))).resolves.toEqual([ + { message: 'POST Successful' }, + ]); + expect(axios.post).toHaveBeenCalledWith('https://fake-end-point-1', null, { + headers: { 'header-name-1': ' header-value-1', testKey: 'testValue' }, + }); + }); + + test('PUT notification with ID is sent when channelPayload is application/fhir+json', async () => { + await expect( + sendRestHookNotification(getEvent({ endpoint: 'https://fake-end-point-2-something' })), + ).resolves.toEqual([{ message: 'PUT Successful' }]); + expect(axios.put).toHaveBeenCalledWith('https://fake-end-point-2-something/Patient/1234567', null, { + headers: { 'header-name-2': ' header-value-2', testKey: 'testValue' }, + }); + }); + + test('Header in channelHeader overrides header in allow list when there is duplicated header name', async () => { + await expect( + sendRestHookNotification( + getEvent({ + channelHeader: ['header-name-2: header-value-2-something'], + endpoint: 'https://fake-end-point-2-something', + }), + ), + ).resolves.toEqual([{ message: 'PUT Successful' }]); + expect(axios.put).toHaveBeenCalledWith('https://fake-end-point-2-something/Patient/1234567', null, { + headers: { 'header-name-2': ' header-value-2-something' }, + }); + }); + + test('Header string without colon is sent as empty header', async () => { + await expect( + sendRestHookNotification( + getEvent({ endpoint: 'https://fake-end-point-2-something', channelHeader: ['testKey'] }), + ), + ).resolves.toEqual([{ message: 'PUT Successful' }]); + expect(axios.put).toHaveBeenCalledWith('https://fake-end-point-2-something/Patient/1234567', null, { + headers: { 'header-name-2': ' header-value-2', testKey: '' }, + }); + }); + + test('Error thrown when endpoint is not allow listed', async () => { + await expect(sendRestHookNotification(getEvent({ endpoint: 'https://fake-end-point-3' }))).rejects.toThrow( + new Error('Endpoint https://fake-end-point-3 is not allow listed.'), + ); + }); + + test('Error thrown when tenantID is passed in', async () => { + const event = getEvent({ tenantId: 'tenant1' }); + await expect(sendRestHookNotification(event)).rejects.toThrow( + new Error('This instance has multi-tenancy disabled, but the incoming request has a tenantId'), + ); + }); +}); diff --git a/subscriptions/restHook.ts b/subscriptions/restHook.ts new file mode 100644 index 00000000..e6691a78 --- /dev/null +++ b/subscriptions/restHook.ts @@ -0,0 +1,113 @@ +import axios from 'axios'; +import { SubscriptionEndpoint } from 'fhir-works-on-aws-routing/lib/router/validation/subscriptionValidator'; +import { groupBy } from 'lodash'; +import { makeLogger } from 'fhir-works-on-aws-interface'; +import { SubscriptionMatchMessage } from './types'; +import getAllowListedSubscriptionEndpoints from './allowList'; +import ensureAsyncInit from '../src/index'; + +const logger = makeLogger(); + +const SINGLE_TENANT_ALLOW_LIST_KEY = 'SINGLE_TENANT_ALLOW_LIST_KEY'; + +export interface AllowListInfo { + allowList: (string | RegExp)[]; + headerMap: { [key: string]: string[] }; +} + +const extractAllowListInfo = (subscriptionEndpoints: SubscriptionEndpoint[]): AllowListInfo => { + const allowList: (string | RegExp)[] = []; + const headerMap: { [key: string]: string[] } = {}; + subscriptionEndpoints.forEach((allowEndpoint: SubscriptionEndpoint) => { + allowList.push(allowEndpoint.endpoint); + headerMap[allowEndpoint.endpoint.toString()] = allowEndpoint.headers || []; + }); + return { allowList, headerMap }; +}; + +async function getAllowListInfo(): Promise<{ [key: string]: AllowListInfo }> { + const originalAllowList = await getAllowListedSubscriptionEndpoints(); + logger.debug(originalAllowList); + if (process.env.ENABLE_MULTI_TENANCY !== 'true') { + return { [SINGLE_TENANT_ALLOW_LIST_KEY]: extractAllowListInfo(originalAllowList) }; + } + const allowListInfo: { [key: string]: AllowListInfo } = {}; + const endpointsGroupByTenant: { [key: string]: SubscriptionEndpoint[] } = groupBy( + originalAllowList, + (allowEndpoint: SubscriptionEndpoint) => allowEndpoint.tenantId, + ); + Object.entries(endpointsGroupByTenant).forEach(([key, value]) => { + allowListInfo[key] = extractAllowListInfo(value); + }); + return allowListInfo; +} + +// Throw error if the URL is not allow listed +const getAllowListHeaders = ( + allowListInfo: { [key: string]: AllowListInfo }, + endpoint: string, + tenantId?: string, +): string[] => { + const getHeaders = ({ allowList, headerMap }: AllowListInfo): string[] => { + // eslint-disable-next-line no-restricted-syntax + for (const allowedEndpoint of allowList) { + if (allowedEndpoint instanceof RegExp && allowedEndpoint.test(endpoint)) { + return headerMap[allowedEndpoint.toString()]; + } + if (allowedEndpoint === endpoint) { + return headerMap[allowedEndpoint]; + } + } + throw new Error(`Endpoint ${endpoint} is not allow listed.`); + }; + + if (process.env.ENABLE_MULTI_TENANCY === 'true') { + if (tenantId) { + return getHeaders(allowListInfo[tenantId]); + } + throw new Error('This instance has multi-tenancy enabled, but the incoming request is missing tenantId'); + } + if (!tenantId) { + return getHeaders(allowListInfo[SINGLE_TENANT_ALLOW_LIST_KEY]); + } + throw new Error('This instance has multi-tenancy disabled, but the incoming request has a tenantId'); +}; + +const mergeRequestHeaders = (allowListHeader: string[], channelHeader: string[]): any => { + const mergedHeader: any = {}; + const mergeHeader = (header: string) => { + const colonIndex = header.indexOf(':') === -1 ? header.length : header.indexOf(':'); + const headerKey = header.substring(0, colonIndex); + const headerValue = header.substring(colonIndex + 1); + mergedHeader[headerKey] = headerValue; + }; + + allowListHeader.forEach((header) => mergeHeader(header)); + channelHeader.forEach((header) => mergeHeader(header)); + return mergedHeader; +}; + +const allowListPromise: Promise<{ [key: string]: AllowListInfo }> = getAllowListInfo(); + +export const sendRestHookNotification = async (event: any): Promise => { + await ensureAsyncInit(allowListPromise); + const allowList = await allowListPromise; + const notificationPromises = event.Records.map((record: any) => { + const body = JSON.parse(record.body); + logger.debug(body); + const message: SubscriptionMatchMessage = JSON.parse(body.Message); + const { endpoint, channelHeader, channelPayload, matchedResource, tenantId } = message; + const allowListHeaders = getAllowListHeaders(allowList, endpoint, tenantId); + const headers = mergeRequestHeaders(allowListHeaders, channelHeader); + if (channelPayload === 'application/fhir+json') { + return axios.put(`${endpoint}/${matchedResource.resourceType}/${matchedResource.id}`, null, { + headers, + }); + } + return axios.post(endpoint, null, { headers }); + }); + const responses = (await Promise.all(notificationPromises)).map((response: any) => response.data); + logger.info('Subscription notifications sent.'); + logger.debug(responses); + return responses; +}; diff --git a/subscriptions/restHookMultiTenant.test.ts b/subscriptions/restHookMultiTenant.test.ts new file mode 100644 index 00000000..98dc9c8e --- /dev/null +++ b/subscriptions/restHookMultiTenant.test.ts @@ -0,0 +1,104 @@ +import axios from 'axios'; +import { sendRestHookNotification } from './restHook'; + +jest.mock('axios'); +jest.mock('./allowList', () => ({ + __esModule: true, + default: async () => [ + { + endpoint: 'https://fake-end-point-tenant1', + headers: ['header-name-1: header-value-1'], + tenantId: 'tenant1', + }, + { + endpoint: new RegExp('^https://fake-end-point-tenant2'), + headers: ['header-name-2: header-value-2'], + tenantId: 'tenant2', + }, + ], +})); + +process.env.ENABLE_MULTI_TENANCY = 'true'; + +const getEvent = ({ + channelHeader = ['testKey:testValue'], + channelPayload = 'application/fhir+json', + endpoint = 'https://fake-end-point-tenant1', + tenantId = 'tenant1', +} = {}) => ({ + Records: [ + { + body: JSON.stringify({ + Message: JSON.stringify({ + subscriptionId: 123456, + channelType: 'rest-hook', + tenantId, + endpoint, + channelPayload, + channelHeader, + matchedResource: { + id: 1234567, + resourceType: 'Patient', + versionId: 2, + lastUpdated: 'some-time-stamp', + }, + }), + }), + }, + ], +}); + +describe('Multi-tenant: Rest hook notification', () => { + beforeEach(() => { + axios.post = jest.fn().mockResolvedValueOnce({ data: { message: 'POST Successful' } }); + axios.put = jest.fn().mockResolvedValueOnce({ data: { message: 'PUT Successful' } }); + }); + + test('Empty POST notification is sent when channelPayload is null', async () => { + await expect(sendRestHookNotification(getEvent({ channelPayload: null as any }))).resolves.toEqual([ + { message: 'POST Successful' }, + ]); + expect(axios.post).toHaveBeenCalledWith('https://fake-end-point-tenant1', null, { + headers: { 'header-name-1': ' header-value-1', testKey: 'testValue' }, + }); + }); + + test('PUT notification with ID is sent when channelPayload is application/fhir+json', async () => { + await expect( + sendRestHookNotification( + getEvent({ endpoint: 'https://fake-end-point-tenant2-something', tenantId: 'tenant2' }), + ), + ).resolves.toEqual([{ message: 'PUT Successful' }]); + expect(axios.put).toHaveBeenCalledWith('https://fake-end-point-tenant2-something/Patient/1234567', null, { + headers: { 'header-name-2': ' header-value-2', testKey: 'testValue' }, + }); + }); + + test('Header in channelHeader overrides header in allow list when there is duplicated header name', async () => { + await expect( + sendRestHookNotification( + getEvent({ + channelHeader: ['header-name-2: header-value-2-something'], + endpoint: 'https://fake-end-point-tenant2-something', + tenantId: 'tenant2', + }), + ), + ).resolves.toEqual([{ message: 'PUT Successful' }]); + expect(axios.put).toHaveBeenCalledWith('https://fake-end-point-tenant2-something/Patient/1234567', null, { + headers: { 'header-name-2': ' header-value-2-something' }, + }); + }); + + test('Error thrown when endpoint is not allow listed', async () => { + await expect( + sendRestHookNotification(getEvent({ endpoint: 'https://fake-end-point-tenant2-something' })), + ).rejects.toThrow(new Error('Endpoint https://fake-end-point-tenant2-something is not allow listed.')); + }); + + test('Error thrown when tenantID is not passed in', async () => { + const event = getEvent({ tenantId: null as any }); + await expect(sendRestHookNotification(event)).rejects.toThrow( + new Error('This instance has multi-tenancy enabled, but the incoming request is missing tenantId'), + ); + }); +}); diff --git a/subscriptions/types.ts b/subscriptions/types.ts new file mode 100644 index 00000000..8ee105b8 --- /dev/null +++ b/subscriptions/types.ts @@ -0,0 +1,14 @@ +export interface SubscriptionMatchMessage { + subscriptionId: string; + tenantId?: string; + channelType: string; + endpoint: string; + channelPayload?: string; + channelHeader: string[]; + matchedResource: { + id: string; + resourceType: string; + versionId: string; + lastUpdated: string; + }; +} diff --git a/yarn.lock b/yarn.lock index b71b631a..05736b68 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1578,10 +1578,10 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= -"@serverless/cli@^1.5.2": - version "1.5.3" - resolved "https://registry.yarnpkg.com/@serverless/cli/-/cli-1.5.3.tgz#61045cdc11c0f41f50ee1ce784dfa8f0f4f26416" - integrity sha512-ZJ0Y7CsYoE/i45XkIMl/XBZO4KIlt0XH1qwxxNE2/84bZlih5cgRV6MZ+rKt7GlrD0iDAgQGyGv5dpyt+SGhKw== +"@serverless/cli@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@serverless/cli/-/cli-1.6.0.tgz#d020f67748401ca209a4fc6407929581810573e2" + integrity sha512-1Muw/KhS4sZ6+ZrXBdmVY9zAwZh03lF7v1DKtaZ0cmxjqQBwPLoO40rOGXlxR97pyufe2NS6rD/p+ri8NGqeXg== dependencies: "@serverless/core" "^1.1.2" "@serverless/template" "^1.1.3" @@ -1603,10 +1603,10 @@ node-fetch "^2.6.0" shortid "^2.2.14" -"@serverless/components@^3.17.1": - version "3.17.1" - resolved "https://registry.yarnpkg.com/@serverless/components/-/components-3.17.1.tgz#824ae5c7f5b7a6d089ca407a8e6361416af8ff88" - integrity sha512-Ra0VVpivEWB816ZAca4UCNzOxQqxveEp4h+RzUX5vaAsZrxpotPUFZi96w9yZGQk3OTxxscRqrsBLxGDtOu8SA== +"@serverless/components@^3.18.2": + version "3.18.2" + resolved "https://registry.yarnpkg.com/@serverless/components/-/components-3.18.2.tgz#628d78f96c1ce5f8dc836587f566a404a38d65e7" + integrity sha512-jQSgd3unajU94R6vjzD0l+PS5lVcky0vrE1DOfb28VPgmaS48+I/niavWR7+SOt0mYjIkUlwBI73a2ZuqeYK6Q== dependencies: "@serverless/platform-client" "^4.2.2" "@serverless/platform-client-china" "^2.2.0" @@ -1649,17 +1649,17 @@ ramda "^0.26.1" semver "^6.1.1" -"@serverless/dashboard-plugin@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@serverless/dashboard-plugin/-/dashboard-plugin-5.5.0.tgz#d8b0407e277f22111a88cf0d6062d0e17de5b9ca" - integrity sha512-a3vWcAacJrUeUFcGXhm/tDxvZjqQI1KjRjFGrqbxoN0N5fCdkLtOn6578Iq4hdP8BF2XanS1xGGdhPjcYBdsUA== +"@serverless/dashboard-plugin@^5.5.4": + version "5.5.4" + resolved "https://registry.yarnpkg.com/@serverless/dashboard-plugin/-/dashboard-plugin-5.5.4.tgz#2bbe1d5e2e5fa79b0f95422fd5b7558c248ce689" + integrity sha512-qqZnT/RXhBcWXwYnpF+GMeNvSUi6mnyIqsAHj8+f7jJ7N9qa5Qrb14+dUh78sdgRBG5Peub3m3Mlx1n5V9yHDw== dependencies: "@serverless/event-mocks" "^1.1.1" "@serverless/platform-client" "^4.3.0" - "@serverless/utils" "^5.18.0" + "@serverless/utils" "^5.20.3" chalk "^4.1.2" child-process-ext "^2.1.1" - chokidar "^3.5.2" + chokidar "^3.5.3" cli-color "^2.0.1" flat "^5.0.2" fs-extra "^9.1.0" @@ -1669,10 +1669,10 @@ memoizee "^0.4.15" ncjsm "^4.2.0" node-dir "^0.1.17" - node-fetch "^2.6.5" + node-fetch "^2.6.7" open "^7.4.2" semver "^7.3.5" - simple-git "^2.46.0" + simple-git "^2.48.0" uuid "^8.3.2" yamljs "^0.3.0" @@ -1783,16 +1783,16 @@ uuid "^8.3.2" write-file-atomic "^3.0.3" -"@serverless/utils@^5.18.0", "@serverless/utils@^5.19.0": - version "5.19.0" - resolved "https://registry.yarnpkg.com/@serverless/utils/-/utils-5.19.0.tgz#3cbf280e299dd4747bbdd24b5afc3c79d3c3867f" - integrity sha512-bgQawVfBgxcZoS1wxukJfRYKkMOZncZfOSTCRUnYzwH78fAAE79vfu49LGx2EGEJa8BThmtzjinZ9SK9yS0kIw== +"@serverless/utils@^5.20.3": + version "5.20.3" + resolved "https://registry.yarnpkg.com/@serverless/utils/-/utils-5.20.3.tgz#5ab15cf8eceb81934946f2deeee028c6b9b6e270" + integrity sha512-MG3DQJdto+LaeVY9gh/z0xloAfT0h1Y3Pa4/yYcKe8Dy5HYtSujuav0MvTOH18+s2outjKKJDxTh6tZuyNqFDQ== dependencies: archive-type "^4.0.0" chalk "^4.1.2" - ci-info "^3.2.0" - cli-progress-footer "^2.1.1" - content-disposition "^0.5.3" + ci-info "^3.3.0" + cli-progress-footer "^2.3.0" + content-disposition "^0.5.4" d "^1.0.1" decompress "^4.2.1" event-emitter "^0.3.5" @@ -1801,13 +1801,13 @@ file-type "^16.5.3" filenamify "^4.3.0" get-stream "^6.0.1" - got "^11.8.2" + got "^11.8.3" inquirer "^7.3.3" js-yaml "^4.1.0" jwt-decode "^3.1.2" lodash "^4.17.21" log "^6.3.1" - log-node "^8.0.1" + log-node "^8.0.3" make-dir "^3.1.0" memoizee "^0.4.15" ncjsm "^4.2.0" @@ -2569,7 +2569,7 @@ ansi-escapes@^4.2.1, ansi-escapes@^4.3.1: dependencies: type-fest "^0.21.3" -ansi-regex@^2.0.0, ansi-regex@^2.1.1, ansi-regex@^3.0.0, ansi-regex@^4.1.0, ansi-regex@^5.0.0, ansi-regex@^5.0.1: +ansi-regex@^2.0.0, ansi-regex@^3.0.0, ansi-regex@^4.1.0, ansi-regex@^5.0.0, ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== @@ -2842,7 +2842,7 @@ aws-elasticsearch-connector@^8.2.0: aws4 "^1.10.0" lodash.get "^4.4.2" -aws-sdk@^2.1000.0, aws-sdk@^2.1011.0, aws-sdk@^2.834.0: +aws-sdk@^2.1000.0, aws-sdk@^2.834.0: version "2.1015.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1015.0.tgz#fc69c0620a34c1d77173123769fbc725d867b3cc" integrity sha512-jSM955n08r+kzCMMhOu1Dbua8SRZQKgGO1nAoUwBSlXjnLtN+F81P93h4yNBtWsxUg1mAMTP3DKJjXFFrRToPw== @@ -2857,6 +2857,21 @@ aws-sdk@^2.1000.0, aws-sdk@^2.1011.0, aws-sdk@^2.834.0: uuid "3.3.2" xml2js "0.4.19" +aws-sdk@^2.1062.0: + version "2.1069.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1069.0.tgz#85c8005a61b4a42b08dc1f41e312e9bac4e411ad" + integrity sha512-AF7/5JotrVd8g/D3WWHgQto+IryB1V7iudIYm+H+qxmkGOU3xvL63ChhEoLTY/CxuK/diayg0oWILEsXUn3dfw== + dependencies: + buffer "4.9.2" + events "1.1.1" + ieee754 "1.1.13" + jmespath "0.16.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + uuid "3.3.2" + xml2js "0.4.19" + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -3465,7 +3480,7 @@ cacheable-request@^6.0.0: normalize-url "^4.1.0" responselike "^1.0.2" -cacheable-request@^7.0.1: +cacheable-request@^7.0.1, cacheable-request@^7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.2.tgz#ea0d0b889364a25854757301ca12b2da77f91d27" integrity sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew== @@ -3584,7 +3599,7 @@ child-process-ext@^2.1.1: split2 "^3.1.1" stream-promise "^3.2.0" -"chokidar@>=3.0.0 <4.0.0", chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.5.1, chokidar@^3.5.2: +"chokidar@>=3.0.0 <4.0.0", chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.5.1: version "3.5.2" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== @@ -3618,6 +3633,21 @@ chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" +chokidar@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + chownr@^1.0.1, chownr@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" @@ -3643,6 +3673,11 @@ ci-info@^3.1.1, ci-info@^3.2.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A== +ci-info@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.0.tgz#b4ed1fb6818dea4803a55c623041f9165d2066b2" + integrity sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw== + cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" @@ -3676,19 +3711,7 @@ cli-boxes@^2.2.1: resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== -cli-color@^1.3: - version "1.4.0" - resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-1.4.0.tgz#7d10738f48526824f8fe7da51857cb0f572fe01f" - integrity sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w== - dependencies: - ansi-regex "^2.1.1" - d "1" - es5-ext "^0.10.46" - es6-iterator "^2.0.3" - memoizee "^0.4.14" - timers-ext "^0.1.5" - -cli-color@^2.0.0, cli-color@^2.0.1: +cli-color@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-2.0.1.tgz#93e3491308691f1e46beb78b63d0fb2585e42ba6" integrity sha512-eBbxZF6fqPUNnf7CLAFOersUnyYzv83tHFLSlts+OAHsNendaqv2tHCq+/MO+b3Y+9JeoUlIvobyxG/Z8GNeOg== @@ -3713,27 +3736,28 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -cli-progress-footer@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/cli-progress-footer/-/cli-progress-footer-2.1.1.tgz#ed45cccc22f95284f715a38ae3e4191ebe3bdf04" - integrity sha512-fBEAKLDp/CCMzQSeEbvz4POvomCekmT0LodI/mchzrjIPeLXQHJ9Gb28leAqEjdc9wyV40cjsB2aWpvO5MA7Pw== +cli-progress-footer@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cli-progress-footer/-/cli-progress-footer-2.3.0.tgz#ef8af382fa8e8c5c649dd3e7667d93b7494d3d3c" + integrity sha512-xJl+jqvdsE0Gjh5tKoLzZrQS4nPHC+yzeitgq2faAZiHl+/Peuwzoy5Sed6EBkm8JNrPk7W4U3YNVO/uxoqOFg== dependencies: - cli-color "^2.0.0" + cli-color "^2.0.1" d "^1.0.1" es5-ext "^0.10.53" + mute-stream "0.0.8" process-utils "^4.0.0" timers-ext "^0.1.7" type "^2.5.0" -cli-sprintf-format@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/cli-sprintf-format/-/cli-sprintf-format-1.1.0.tgz#7373b00cc8299285f29bc2a20e050a4522d4baff" - integrity sha512-t3LcCdPvrypZovStadWdRS4a186gsq9aoHJYTIer55VY20YdVjGVHDV4uPWcWCXTw1tPjfwlRGE7zKMWJ663Sw== +cli-sprintf-format@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/cli-sprintf-format/-/cli-sprintf-format-1.1.1.tgz#ec69955c89ef1c61243b52e68015b75c08fb9188" + integrity sha512-BbEjY9BEdA6wagVwTqPvmAwGB24U93rQPBFZUT8lNCDxXzre5LFHQUTJc70czjgUomVg8u8R5kW8oY9DYRFNeg== dependencies: - cli-color "^1.3" - es5-ext "^0.10.46" - sprintf-kit "2" - supports-color "^5.5" + cli-color "^2.0.1" + es5-ext "^0.10.53" + sprintf-kit "^2.0.1" + supports-color "^6.1.0" cli-width@^2.0.0: version "2.2.1" @@ -3985,13 +4009,20 @@ constants-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= -content-disposition@0.5.3, content-disposition@^0.5.3: +content-disposition@0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== dependencies: safe-buffer "5.1.2" +content-disposition@^0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + content-type@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" @@ -5177,10 +5208,12 @@ esrecurse@^4.1.0, esrecurse@^4.3.0: dependencies: estraverse "^5.2.0" -essentials@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/essentials/-/essentials-1.1.1.tgz#03befbfbee7078301741279b38a806b6ca624821" - integrity sha512-SmaxoAdVu86XkZQM/u6TYSu96ZlFGwhvSk1l9zAkznFuQkMb9mRDS2iq/XWDow7R8OwBwdYH8nLyDKznMD+GWw== +essentials@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/essentials/-/essentials-1.2.0.tgz#c6361fb648f5c8c0c51279707f6139e521a05807" + integrity sha512-kP/j7Iw7KeNE8b/o7+tr9uX2s1wegElGOoGZ2Xm35qBr4BbbEcH3/bxR2nfH9l9JANCq9AUrvKw+gRuHtZp0HQ== + dependencies: + uni-global "^1.0.0" estraverse@^4.1.1, estraverse@^4.2.0: version "4.3.0" @@ -5453,6 +5486,17 @@ fast-glob@^3.1.1, fast-glob@^3.2.4, fast-glob@^3.2.5, fast-glob@^3.2.7: merge2 "^1.3.0" micromatch "^4.0.4" +fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -5675,10 +5719,10 @@ filenamify@^4.3.0: strip-outer "^1.0.1" trim-repeated "^1.0.0" -filesize@^8.0.3: - version "8.0.5" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.5.tgz#3b8ffa1bd5e6b0e187530280ab4c06b941a7226a" - integrity sha512-nW6kfJQIL+FY63PWbyxyRKDSFqm3aomjcMfydPzlOn7HiLnY9jPnbRuawroqGGQHEceDxUQEaF0RYy94irvsEw== +filesize@^8.0.7: + version "8.0.7" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8" + integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ== fill-range@^4.0.0: version "4.0.0" @@ -6121,7 +6165,7 @@ globals@^13.6.0, globals@^13.9.0: dependencies: type-fest "^0.20.2" -globby@^11.0.1, globby@^11.0.3, globby@^11.0.4: +globby@^11.0.1, globby@^11.0.3: version "11.0.4" resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== @@ -6133,6 +6177,18 @@ globby@^11.0.1, globby@^11.0.3, globby@^11.0.4: merge2 "^1.3.0" slash "^3.0.0" +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + got@^11.8.2: version "11.8.2" resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599" @@ -6150,6 +6206,23 @@ got@^11.8.2: p-cancelable "^2.0.0" responselike "^2.0.0" +got@^11.8.3: + version "11.8.3" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.3.tgz#f496c8fdda5d729a90b4905d2b07dbd148170770" + integrity sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg== + dependencies: + "@sindresorhus/is" "^4.0.0" + "@szmarczak/http-timer" "^4.0.5" + "@types/cacheable-request" "^6.0.1" + "@types/responselike" "^1.0.0" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.2" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.5.2" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" + got@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" @@ -6167,11 +6240,16 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.8: +graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: version "4.2.8" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== +graceful-fs@^4.2.9: + version "4.2.9" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" + integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== + graphlib@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da" @@ -6488,6 +6566,11 @@ ignore@^5.1.4, ignore@^5.1.8: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== +ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" @@ -7523,6 +7606,11 @@ jmespath@0.15.0: resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc= +jmespath@0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" + integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw== + js-string-escape@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" @@ -8036,14 +8124,14 @@ lodash@4.17.x, lodash@4.x, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.12, lod resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -log-node@^8.0.1: - version "8.0.2" - resolved "https://registry.yarnpkg.com/log-node/-/log-node-8.0.2.tgz#0c067e6e7f1623f0c69be1d809603af3c03db053" - integrity sha512-H+3t002yGqZRJhVW3A/EksIB7i1M84cwcWzBPsnAmQWOA2ePAV2fXJpNPBnw6VEdhzPcedIxQwT5jhBVTlYQww== +log-node@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/log-node/-/log-node-8.0.3.tgz#441bf1a72f9f1c28b62f5bf42e9eb3765af74d73" + integrity sha512-1UBwzgYiCIDFs8A0rM2QdBFo8Wd8UQ0HrSTu/MNI+/2zN3NoHRj2fhplurAyuxTYUXu3Oohugq1jAn5s05u1MQ== dependencies: ansi-regex "^5.0.1" - cli-color "^2.0.0" - cli-sprintf-format "^1.1.0" + cli-color "^2.0.1" + cli-sprintf-format "^1.1.1" d "^1.0.1" es5-ext "^0.10.53" sprintf-kit "^2.0.1" @@ -8246,7 +8334,7 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.3.0: +merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== @@ -8546,6 +8634,20 @@ ncjsm@^4.1.0, ncjsm@^4.2.0: fs2 "^0.3.9" type "^2.5.0" +ncjsm@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ncjsm/-/ncjsm-4.3.0.tgz#ec2301ad67475f414a50de34fae00ebc31527e38" + integrity sha512-oah6YGwb4Ern2alojiMFcjPhE4wvQBw1Ur/kUr2P0ovKdzaF5pCIsGjs0f2y+iZeej0/5Y6OOhQ8j30cTDMEGw== + dependencies: + builtin-modules "^3.2.0" + deferred "^0.7.11" + es5-ext "^0.10.53" + es6-set "^0.1.5" + ext "^1.6.0" + find-requires "^1.0.0" + fs2 "^0.3.9" + type "^2.5.0" + nearley@^2.20.0: version "2.20.1" resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.20.1.tgz#246cd33eff0d012faf197ff6774d7ac78acdd474" @@ -8611,7 +8713,7 @@ node-dir@^0.1.17: dependencies: minimatch "^3.0.2" -node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.5: +node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7: version "2.6.5" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.5.tgz#42735537d7f080a7e5f78b6c549b7146be1742fd" integrity sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ== @@ -8773,7 +8875,7 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-hash@^2.0.3, object-hash@^2.2.0: +object-hash@^2.0.3: version "2.2.0" resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== @@ -10172,7 +10274,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -10490,59 +10592,58 @@ serverless-webpack@^5.3.1: optionalDependencies: ts-node ">= 8.3.0" -serverless@2.64.1: - version "2.64.1" - resolved "https://registry.yarnpkg.com/serverless/-/serverless-2.64.1.tgz#708f7000577437f5fec27efd996967d67dc66fb3" - integrity sha512-9DErsV4ACg/2UkRoX2EYRkcDyRi3NBld/gexCeVnkmUunadSwnmtSBeVU8spvg+zc61b1vbusTHE4woqcd2gvw== +serverless@2.72.2: + version "2.72.2" + resolved "https://registry.yarnpkg.com/serverless/-/serverless-2.72.2.tgz#bb09965818cfa0683018900717c393a464a1ee5d" + integrity sha512-xlxaWyq4b58DIX3prwIikBmXj0z4R+YI9zcDPYgQc78mKJ0qTgCK7BMoy6dlVuHXnfhBkhT3uT/EhFvjKIdk6g== dependencies: - "@serverless/cli" "^1.5.2" - "@serverless/components" "^3.17.1" - "@serverless/dashboard-plugin" "^5.5.0" + "@serverless/cli" "^1.6.0" + "@serverless/components" "^3.18.2" + "@serverless/dashboard-plugin" "^5.5.4" "@serverless/platform-client" "^4.3.0" - "@serverless/utils" "^5.19.0" + "@serverless/utils" "^5.20.3" ajv "^6.12.6" ajv-keywords "^3.5.2" archiver "^5.3.0" - aws-sdk "^2.1011.0" + aws-sdk "^2.1062.0" bluebird "^3.7.2" boxen "^5.1.2" cachedir "^2.3.0" chalk "^4.1.2" child-process-ext "^2.1.1" - ci-info "^3.2.0" - cli-progress-footer "^2.1.1" + ci-info "^3.3.0" + cli-progress-footer "^2.3.0" d "^1.0.1" dayjs "^1.10.7" decompress "^4.2.1" dotenv "^10.0.0" dotenv-expand "^5.1.0" - essentials "^1.1.1" + essentials "^1.2.0" ext "^1.6.0" fastest-levenshtein "^1.0.12" - filesize "^8.0.3" + filesize "^8.0.7" fs-extra "^9.1.0" get-stdin "^8.0.0" - globby "^11.0.4" - got "^11.8.2" - graceful-fs "^4.2.8" + globby "^11.1.0" + got "^11.8.3" + graceful-fs "^4.2.9" https-proxy-agent "^5.0.0" is-docker "^2.2.1" - is-wsl "^2.2.0" js-yaml "^4.1.0" json-cycle "^1.3.0" json-refs "^3.0.15" lodash "^4.17.21" memoizee "^0.4.15" micromatch "^4.0.4" - ncjsm "^4.2.0" - node-fetch "^2.6.5" - object-hash "^2.2.0" + ncjsm "^4.3.0" + node-fetch "^2.6.7" + open "^7.4.2" path2 "^0.1.0" process-utils "^4.0.0" promise-queue "^2.2.5" replaceall "^0.1.6" semver "^7.3.5" - signal-exit "^3.0.5" + signal-exit "^3.0.6" strip-ansi "^6.0.1" tabtab "^3.0.2" tar "^6.1.11" @@ -10643,11 +10744,16 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.5: +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.5" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f" integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ== +signal-exit@^3.0.6: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + simple-concat@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" @@ -10662,10 +10768,10 @@ simple-get@^2.7.0: once "^1.3.1" simple-concat "^1.0.0" -simple-git@^2.46.0: - version "2.47.0" - resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-2.47.0.tgz#9693b471de7911901f703b4c403fc3e8774cd9be" - integrity sha512-+HfCpqPBEZTPWiW9fPdbiPJDslM22MLqrktfzNKyI2pWaJa6DhfNVx4Mds04KZzVv5vjC9/ksw3y5gVf8ECWDg== +simple-git@^2.48.0: + version "2.48.0" + resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-2.48.0.tgz#87c262dba8f84d7b96bb3a713e9e34701c1f6e3b" + integrity sha512-z4qtrRuaAFJS4PUd0g+xy7aN4y+RvEt/QTJpR184lhJguBA1S/LsVlvE/CM95RsYMOFJG3NGGDjqFCzKU19S/A== dependencies: "@kwsites/file-exists" "^1.1.1" "@kwsites/promise-deferred" "^1.1.1" @@ -10901,7 +11007,7 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -sprintf-kit@2, sprintf-kit@^2.0.1: +sprintf-kit@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/sprintf-kit/-/sprintf-kit-2.0.1.tgz#bb837e8fa4b28f094531d8e33669120027236bb8" integrity sha512-2PNlcs3j5JflQKcg4wpdqpZ+AjhQJ2OZEo34NXDtlB0tIPG84xaaXhpA8XFacFiwjKA4m49UOYG83y3hbMn/gQ== @@ -11185,13 +11291,20 @@ supports-color@^2.0.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= -supports-color@^5.3.0, supports-color@^5.5: +supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== dependencies: has-flag "^3.0.0" +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -11382,7 +11495,7 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" -timers-ext@^0.1.5, timers-ext@^0.1.7: +timers-ext@^0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6" integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ== From 584ed892bb7f0d17cbb8b44ddefd8c4c22cd8f06 Mon Sep 17 00:00:00 2001 From: zheyanyu Date: Wed, 9 Feb 2022 14:41:57 -0500 Subject: [PATCH 2/5] clean up --- cloudformation/subscriptions.yaml | 2 +- subscriptions/allowList.ts | 12 +----------- subscriptions/restHook.ts | 16 +++++++++++++++- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/cloudformation/subscriptions.yaml b/cloudformation/subscriptions.yaml index 6f095e7e..ce3d9754 100644 --- a/cloudformation/subscriptions.yaml +++ b/cloudformation/subscriptions.yaml @@ -111,4 +111,4 @@ Resources: ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess - - arn:aws:iam::aws:policy/AdministratorAccess \ No newline at end of file + - arn:aws:iam::aws:policy/AdministratorAccess diff --git a/subscriptions/allowList.ts b/subscriptions/allowList.ts index d3d99aa3..12a11038 100644 --- a/subscriptions/allowList.ts +++ b/subscriptions/allowList.ts @@ -6,7 +6,7 @@ import { SubscriptionEndpoint } from 'fhir-works-on-aws-routing/lib/router/validation/subscriptionValidator'; const getAllowListedSubscriptionEndpoints = async (): Promise => { - // return []; + return []; // Add here the endpoints that are allowed in Subscriptions // [ // { @@ -16,16 +16,6 @@ const getAllowListedSubscriptionEndpoints = async (): Promise { return allowListInfo; } -// Throw error if the URL is not allow listed +/** + * Verify endpoint is allow listed + * Return allow list headers if endpoint is allow listed + * Throw error if endpoint is not allow listed + * @param allowListInfo + * @param endpoint + * @param tenantId + */ const getAllowListHeaders = ( allowListInfo: { [key: string]: AllowListInfo }, endpoint: string, @@ -73,6 +80,13 @@ const getAllowListHeaders = ( throw new Error('This instance has multi-tenancy disabled, but the incoming request has a tenantId'); }; +/** + * Merge headers from allow list and subscription resource + * If same header name is present from both sources, header value in subscription resource will be used + * If a header string does not have ':', the header will be sent with no value + * @param allowListHeader + * @param channelHeader + */ const mergeRequestHeaders = (allowListHeader: string[], channelHeader: string[]): any => { const mergedHeader: any = {}; const mergeHeader = (header: string) => { From 69128d7d736452ecd2e0b412379833ae0d76e1f3 Mon Sep 17 00:00:00 2001 From: zheyanyu Date: Wed, 9 Feb 2022 15:49:00 -0500 Subject: [PATCH 3/5] Update Lambda permission --- cloudformation/subscriptions.yaml | 32 +++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/cloudformation/subscriptions.yaml b/cloudformation/subscriptions.yaml index ce3d9754..cd9adf7a 100644 --- a/cloudformation/subscriptions.yaml +++ b/cloudformation/subscriptions.yaml @@ -108,7 +108,31 @@ Resources: Principal: Service: 'lambda.amazonaws.com' Action: 'sts:AssumeRole' - ManagedPolicyArns: - - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole - - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess - - arn:aws:iam::aws:policy/AdministratorAccess + Policies: + - PolicyName: 'restHookLambdaPolicy' + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - logs:CreateLogStream + - logs:CreateLogGroup + - logs:PutLogEvents + Resource: !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:*:*' + - Effect: Allow + Action: + - 'xray:PutTraceSegments' + - 'xray:PutTelemetryRecords' + Resource: + - '*' + - Effect: Allow + Action: + - 'kms:Decrypt' + Resource: + - !GetAtt SubscriptionsKey.Arn + - Effect: Allow + Action: + - 'sqs:DeleteMessage' + - 'sqs:ReceiveMessage' + - 'sqs:GetQueueAttributes' + Resource: !GetAtt RestHookQueue.Arn \ No newline at end of file From bbfe69faaa45ba5205038aadb37ed29730218703 Mon Sep 17 00:00:00 2001 From: zheyanyu Date: Thu, 10 Feb 2022 14:50:55 -0500 Subject: [PATCH 4/5] Address comments --- package.json | 3 +- serverless.yaml | 4 +- src/config.ts | 2 +- .../subscriptions}/allowList.ts | 12 ++- .../subscriptions/allowListUtil.ts | 67 +++----------- src/subscriptions/index.ts | 14 +++ .../subscriptions}/restHook.test.ts | 53 ++++++++--- src/subscriptions/restHook.ts | 66 +++++++++++++ .../restHookMultiTenant.test.ts | 44 ++++++--- {subscriptions => src/subscriptions}/types.ts | 0 subscriptions/index.ts | 3 - yarn.lock | 92 +++++++++---------- 12 files changed, 221 insertions(+), 139 deletions(-) rename {subscriptions => src/subscriptions}/allowList.ts (70%) rename subscriptions/restHook.ts => src/subscriptions/allowListUtil.ts (52%) create mode 100644 src/subscriptions/index.ts rename {subscriptions => src/subscriptions}/restHook.test.ts (65%) create mode 100644 src/subscriptions/restHook.ts rename {subscriptions => src/subscriptions}/restHookMultiTenant.test.ts (67%) rename {subscriptions => src/subscriptions}/types.ts (100%) delete mode 100644 subscriptions/index.ts diff --git a/package.json b/package.json index 4cf2e607..d4243a8c 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "serverless-info": "serverless info" }, "dependencies": { + "@types/aws-lambda": "^8.10.92", "aws-sdk": "^2.1000.0", "axios": "^0.21.4", "fhir-works-on-aws-authz-rbac": "5.0.0", @@ -65,7 +66,7 @@ "jsonwebtoken": "^8.5.1", "prettier": "^2.4.1", "qs": "^6.10.1", - "serverless": "2.72.2", + "serverless": "2.64.1", "serverless-bundle": "^4.4.0", "serverless-offline": "^8.2.0", "serverless-step-functions": "^3.1.1", diff --git a/serverless.yaml b/serverless.yaml index 519557c9..8a6c12ff 100644 --- a/serverless.yaml +++ b/serverless.yaml @@ -193,11 +193,11 @@ functions: NUMBER_OF_SHARDS: !If [isDev, 1, 3] # 133 indices, one per resource types subscriptionsRestHook: - timeout: 700 + timeout: 20 runtime: nodejs14.x description: 'Send rest-hook notification for subscription' role: RestHookLambdaRole - handler: subscriptions/index.handler + handler: src/subscriptions/index.handler events: - sqs: arn: diff --git a/src/config.ts b/src/config.ts index 97c6ff42..90554f54 100644 --- a/src/config.ts +++ b/src/config.ts @@ -23,9 +23,9 @@ import { import JsonSchemaValidator from 'fhir-works-on-aws-routing/lib/router/validation/jsonSchemaValidator'; import HapiFhirLambdaValidator from 'fhir-works-on-aws-routing/lib/router/validation/hapiFhirLambdaValidator'; import SubscriptionValidator from 'fhir-works-on-aws-routing/lib/router/validation/subscriptionValidator'; +import getAllowListedSubscriptionEndpoints from './subscriptions/allowList'; import RBACRules from './RBACRules'; import { loadImplementationGuides } from './implementationGuides/loadCompiledIGs'; -import getAllowListedSubscriptionEndpoints from '../subscriptions/allowList'; const { IS_OFFLINE, ENABLE_MULTI_TENANCY, ENABLE_SUBSCRIPTIONS } = process.env; diff --git a/subscriptions/allowList.ts b/src/subscriptions/allowList.ts similarity index 70% rename from subscriptions/allowList.ts rename to src/subscriptions/allowList.ts index 12a11038..d3d99aa3 100644 --- a/subscriptions/allowList.ts +++ b/src/subscriptions/allowList.ts @@ -6,7 +6,7 @@ import { SubscriptionEndpoint } from 'fhir-works-on-aws-routing/lib/router/validation/subscriptionValidator'; const getAllowListedSubscriptionEndpoints = async (): Promise => { - return []; + // return []; // Add here the endpoints that are allowed in Subscriptions // [ // { @@ -16,6 +16,16 @@ const getAllowListedSubscriptionEndpoints = async (): Promise { +export async function getAllowListInfo({ + enableMultitenancy = false, +}: { + enableMultitenancy: boolean; +}): Promise<{ [key: string]: AllowListInfo }> { const originalAllowList = await getAllowListedSubscriptionEndpoints(); logger.debug(originalAllowList); - if (process.env.ENABLE_MULTI_TENANCY !== 'true') { + if (!enableMultitenancy) { return { [SINGLE_TENANT_ALLOW_LIST_KEY]: extractAllowListInfo(originalAllowList) }; } const allowListInfo: { [key: string]: AllowListInfo } = {}; @@ -49,11 +49,12 @@ async function getAllowListInfo(): Promise<{ [key: string]: AllowListInfo }> { * @param allowListInfo * @param endpoint * @param tenantId + * @param enableMultitenancy */ -const getAllowListHeaders = ( +export const getAllowListHeaders = ( allowListInfo: { [key: string]: AllowListInfo }, endpoint: string, - tenantId?: string, + { enableMultitenancy = false, tenantId }: { enableMultitenancy: boolean; tenantId: string | undefined }, ): string[] => { const getHeaders = ({ allowList, headerMap }: AllowListInfo): string[] => { // eslint-disable-next-line no-restricted-syntax @@ -68,7 +69,7 @@ const getAllowListHeaders = ( throw new Error(`Endpoint ${endpoint} is not allow listed.`); }; - if (process.env.ENABLE_MULTI_TENANCY === 'true') { + if (enableMultitenancy) { if (tenantId) { return getHeaders(allowListInfo[tenantId]); } @@ -79,49 +80,3 @@ const getAllowListHeaders = ( } throw new Error('This instance has multi-tenancy disabled, but the incoming request has a tenantId'); }; - -/** - * Merge headers from allow list and subscription resource - * If same header name is present from both sources, header value in subscription resource will be used - * If a header string does not have ':', the header will be sent with no value - * @param allowListHeader - * @param channelHeader - */ -const mergeRequestHeaders = (allowListHeader: string[], channelHeader: string[]): any => { - const mergedHeader: any = {}; - const mergeHeader = (header: string) => { - const colonIndex = header.indexOf(':') === -1 ? header.length : header.indexOf(':'); - const headerKey = header.substring(0, colonIndex); - const headerValue = header.substring(colonIndex + 1); - mergedHeader[headerKey] = headerValue; - }; - - allowListHeader.forEach((header) => mergeHeader(header)); - channelHeader.forEach((header) => mergeHeader(header)); - return mergedHeader; -}; - -const allowListPromise: Promise<{ [key: string]: AllowListInfo }> = getAllowListInfo(); - -export const sendRestHookNotification = async (event: any): Promise => { - await ensureAsyncInit(allowListPromise); - const allowList = await allowListPromise; - const notificationPromises = event.Records.map((record: any) => { - const body = JSON.parse(record.body); - logger.debug(body); - const message: SubscriptionMatchMessage = JSON.parse(body.Message); - const { endpoint, channelHeader, channelPayload, matchedResource, tenantId } = message; - const allowListHeaders = getAllowListHeaders(allowList, endpoint, tenantId); - const headers = mergeRequestHeaders(allowListHeaders, channelHeader); - if (channelPayload === 'application/fhir+json') { - return axios.put(`${endpoint}/${matchedResource.resourceType}/${matchedResource.id}`, null, { - headers, - }); - } - return axios.post(endpoint, null, { headers }); - }); - const responses = (await Promise.all(notificationPromises)).map((response: any) => response.data); - logger.info('Subscription notifications sent.'); - logger.debug(responses); - return responses; -}; diff --git a/src/subscriptions/index.ts b/src/subscriptions/index.ts new file mode 100644 index 00000000..54ee60fb --- /dev/null +++ b/src/subscriptions/index.ts @@ -0,0 +1,14 @@ +import RestHookHandler from './restHook'; +import { AllowListInfo, getAllowListInfo } from './allowListUtil'; + +const enableMultitenancy = process.env.ENABLE_MULTI_TENANCY === 'true'; + +const allowListPromise: Promise<{ [key: string]: AllowListInfo }> = getAllowListInfo({ + enableMultitenancy, +}); + +const restHookHandler = new RestHookHandler({ enableMultitenancy }); + +exports.handler = async (event: any) => { + return restHookHandler.sendRestHookNotification(event, allowListPromise); +}; diff --git a/subscriptions/restHook.test.ts b/src/subscriptions/restHook.test.ts similarity index 65% rename from subscriptions/restHook.test.ts rename to src/subscriptions/restHook.test.ts index 3117a97e..8990cb3f 100644 --- a/subscriptions/restHook.test.ts +++ b/src/subscriptions/restHook.test.ts @@ -1,5 +1,6 @@ import axios from 'axios'; -import { sendRestHookNotification } from './restHook'; +import RestHookHandler from './restHook'; +import { AllowListInfo, getAllowListInfo } from './allowListUtil'; jest.mock('axios'); // This mock only works on the file level for once @@ -18,8 +19,6 @@ jest.mock('./allowList', () => ({ ], })); -process.env.ENABLE_MULTI_TENANCY = 'false'; - const getEvent = ({ channelHeader = ['testKey:testValue'], channelPayload = 'application/fhir+json', @@ -28,6 +27,8 @@ const getEvent = ({ } = {}) => ({ Records: [ { + messageId: 'fake-message-id', + receiptHandle: 'fake-receipt-Handle', body: JSON.stringify({ Message: JSON.stringify({ subscriptionId: 123456, @@ -44,20 +45,35 @@ const getEvent = ({ }, }), }), + attributes: { + ApproximateReceiveCount: '1', + SentTimestamp: '123456789', + SenderId: 'FAKESENDERID', + MessageDeduplicationId: '1', + ApproximateFirstReceiveTimestamp: '123456789', + }, + messageAttributes: {}, + md5OfBody: '123456789012', + eventSource: 'aws:sqs', + eventSourceARN: 'arn:aws:sqs:us-east-2:123456789012:fhir-service-dev-RestHookQueue', + awsRegion: 'us-east-2', }, ], }); describe('Single tenant: Rest hook notification', () => { + const restHookHandler = new RestHookHandler({ enableMultitenancy: false }); + const allowListPromise: Promise<{ [key: string]: AllowListInfo }> = getAllowListInfo({ enableMultitenancy: false }); + beforeEach(() => { axios.post = jest.fn().mockResolvedValueOnce({ data: { message: 'POST Successful' } }); axios.put = jest.fn().mockResolvedValueOnce({ data: { message: 'PUT Successful' } }); }); test('Empty POST notification is sent when channelPayload is null', async () => { - await expect(sendRestHookNotification(getEvent({ channelPayload: null as any }))).resolves.toEqual([ - { message: 'POST Successful' }, - ]); + await expect( + restHookHandler.sendRestHookNotification(getEvent({ channelPayload: null as any }), allowListPromise), + ).resolves.toEqual([{ message: 'POST Successful' }]); expect(axios.post).toHaveBeenCalledWith('https://fake-end-point-1', null, { headers: { 'header-name-1': ' header-value-1', testKey: 'testValue' }, }); @@ -65,7 +81,10 @@ describe('Single tenant: Rest hook notification', () => { test('PUT notification with ID is sent when channelPayload is application/fhir+json', async () => { await expect( - sendRestHookNotification(getEvent({ endpoint: 'https://fake-end-point-2-something' })), + restHookHandler.sendRestHookNotification( + getEvent({ endpoint: 'https://fake-end-point-2-something' }), + allowListPromise, + ), ).resolves.toEqual([{ message: 'PUT Successful' }]); expect(axios.put).toHaveBeenCalledWith('https://fake-end-point-2-something/Patient/1234567', null, { headers: { 'header-name-2': ' header-value-2', testKey: 'testValue' }, @@ -74,11 +93,12 @@ describe('Single tenant: Rest hook notification', () => { test('Header in channelHeader overrides header in allow list when there is duplicated header name', async () => { await expect( - sendRestHookNotification( + restHookHandler.sendRestHookNotification( getEvent({ channelHeader: ['header-name-2: header-value-2-something'], endpoint: 'https://fake-end-point-2-something', }), + allowListPromise, ), ).resolves.toEqual([{ message: 'PUT Successful' }]); expect(axios.put).toHaveBeenCalledWith('https://fake-end-point-2-something/Patient/1234567', null, { @@ -88,8 +108,9 @@ describe('Single tenant: Rest hook notification', () => { test('Header string without colon is sent as empty header', async () => { await expect( - sendRestHookNotification( + restHookHandler.sendRestHookNotification( getEvent({ endpoint: 'https://fake-end-point-2-something', channelHeader: ['testKey'] }), + allowListPromise, ), ).resolves.toEqual([{ message: 'PUT Successful' }]); expect(axios.put).toHaveBeenCalledWith('https://fake-end-point-2-something/Patient/1234567', null, { @@ -98,14 +119,18 @@ describe('Single tenant: Rest hook notification', () => { }); test('Error thrown when endpoint is not allow listed', async () => { - await expect(sendRestHookNotification(getEvent({ endpoint: 'https://fake-end-point-3' }))).rejects.toThrow( - new Error('Endpoint https://fake-end-point-3 is not allow listed.'), - ); + await expect( + restHookHandler.sendRestHookNotification( + getEvent({ endpoint: 'https://fake-end-point-3' }), + allowListPromise, + ), + ).rejects.toThrow(new Error('Endpoint https://fake-end-point-3 is not allow listed.')); }); test('Error thrown when tenantID is passed in', async () => { - const event = getEvent({ tenantId: 'tenant1' }); - await expect(sendRestHookNotification(event)).rejects.toThrow( + await expect( + restHookHandler.sendRestHookNotification(getEvent({ tenantId: 'tenant1' }), allowListPromise), + ).rejects.toThrow( new Error('This instance has multi-tenancy disabled, but the incoming request has a tenantId'), ); }); diff --git a/src/subscriptions/restHook.ts b/src/subscriptions/restHook.ts new file mode 100644 index 00000000..cfe38e1b --- /dev/null +++ b/src/subscriptions/restHook.ts @@ -0,0 +1,66 @@ +import axios from 'axios'; +import { makeLogger } from 'fhir-works-on-aws-interface'; +import { SQSEvent } from 'aws-lambda'; +import { SubscriptionMatchMessage } from './types'; +import ensureAsyncInit from '../index'; +import { AllowListInfo, getAllowListHeaders } from './allowListUtil'; + +const logger = makeLogger({ component: 'subscriptions' }); + +/** + * Merge headers from allow list and subscription resource + * If same header name is present from both sources, header value in subscription resource will be used + * If a header string does not have ':', the header will be sent with no value + * @param allowListHeader + * @param channelHeader + */ +const mergeRequestHeaders = (allowListHeader: string[], channelHeader: string[]): any => { + const mergedHeader: any = {}; + const mergeHeader = (header: string) => { + const colonIndex = header.indexOf(':') === -1 ? header.length : header.indexOf(':'); + const headerKey = header.substring(0, colonIndex); + const headerValue = header.substring(colonIndex + 1); + mergedHeader[headerKey] = headerValue; + }; + + allowListHeader.forEach((header) => mergeHeader(header)); + channelHeader.forEach((header) => mergeHeader(header)); + return mergedHeader; +}; + +export default class RestHookHandler { + readonly enableMultitenancy: boolean; + + constructor({ enableMultitenancy = false }: { enableMultitenancy: boolean }) { + this.enableMultitenancy = enableMultitenancy; + } + + async sendRestHookNotification( + event: SQSEvent, + allowListPromise: Promise<{ [key: string]: AllowListInfo }>, + ): Promise { + await ensureAsyncInit(allowListPromise); + const allowList = await allowListPromise; + const notificationPromises = event.Records.map((record: any) => { + const body = JSON.parse(record.body); + logger.debug(body); + const message: SubscriptionMatchMessage = JSON.parse(body.Message); + const { endpoint, channelHeader, channelPayload, matchedResource, tenantId } = message; + const allowListHeaders = getAllowListHeaders(allowList, endpoint, { + enableMultitenancy: this.enableMultitenancy, + tenantId, + }); + const headers = mergeRequestHeaders(allowListHeaders, channelHeader); + if (channelPayload === 'application/fhir+json') { + return axios.put(`${endpoint}/${matchedResource.resourceType}/${matchedResource.id}`, null, { + headers, + }); + } + return axios.post(endpoint, null, { headers }); + }); + const responses = (await Promise.all(notificationPromises)).map((response: any) => response.data); + logger.info('Subscription notifications sent.'); + logger.debug(responses); + return responses; + } +} diff --git a/subscriptions/restHookMultiTenant.test.ts b/src/subscriptions/restHookMultiTenant.test.ts similarity index 67% rename from subscriptions/restHookMultiTenant.test.ts rename to src/subscriptions/restHookMultiTenant.test.ts index 98dc9c8e..fb33ea45 100644 --- a/subscriptions/restHookMultiTenant.test.ts +++ b/src/subscriptions/restHookMultiTenant.test.ts @@ -1,5 +1,6 @@ import axios from 'axios'; -import { sendRestHookNotification } from './restHook'; +import RestHookHandler from './restHook'; +import { AllowListInfo, getAllowListInfo } from './allowListUtil'; jest.mock('axios'); jest.mock('./allowList', () => ({ @@ -18,8 +19,6 @@ jest.mock('./allowList', () => ({ ], })); -process.env.ENABLE_MULTI_TENANCY = 'true'; - const getEvent = ({ channelHeader = ['testKey:testValue'], channelPayload = 'application/fhir+json', @@ -28,6 +27,8 @@ const getEvent = ({ } = {}) => ({ Records: [ { + messageId: 'fake-message-id', + receiptHandle: 'fake-receipt-Handle', body: JSON.stringify({ Message: JSON.stringify({ subscriptionId: 123456, @@ -44,20 +45,35 @@ const getEvent = ({ }, }), }), + attributes: { + ApproximateReceiveCount: '1', + SentTimestamp: '123456789', + SenderId: 'FAKESENDERID', + MessageDeduplicationId: '1', + ApproximateFirstReceiveTimestamp: '123456789', + }, + messageAttributes: {}, + md5OfBody: '123456789012', + eventSource: 'aws:sqs', + eventSourceARN: 'arn:aws:sqs:us-east-2:123456789012:fhir-service-dev-RestHookQueue', + awsRegion: 'us-east-2', }, ], }); describe('Multi-tenant: Rest hook notification', () => { + const restHookHandler = new RestHookHandler({ enableMultitenancy: true }); + const allowListPromise: Promise<{ [key: string]: AllowListInfo }> = getAllowListInfo({ enableMultitenancy: true }); + beforeEach(() => { axios.post = jest.fn().mockResolvedValueOnce({ data: { message: 'POST Successful' } }); axios.put = jest.fn().mockResolvedValueOnce({ data: { message: 'PUT Successful' } }); }); test('Empty POST notification is sent when channelPayload is null', async () => { - await expect(sendRestHookNotification(getEvent({ channelPayload: null as any }))).resolves.toEqual([ - { message: 'POST Successful' }, - ]); + await expect( + restHookHandler.sendRestHookNotification(getEvent({ channelPayload: null as any }), allowListPromise), + ).resolves.toEqual([{ message: 'POST Successful' }]); expect(axios.post).toHaveBeenCalledWith('https://fake-end-point-tenant1', null, { headers: { 'header-name-1': ' header-value-1', testKey: 'testValue' }, }); @@ -65,8 +81,9 @@ describe('Multi-tenant: Rest hook notification', () => { test('PUT notification with ID is sent when channelPayload is application/fhir+json', async () => { await expect( - sendRestHookNotification( + restHookHandler.sendRestHookNotification( getEvent({ endpoint: 'https://fake-end-point-tenant2-something', tenantId: 'tenant2' }), + allowListPromise, ), ).resolves.toEqual([{ message: 'PUT Successful' }]); expect(axios.put).toHaveBeenCalledWith('https://fake-end-point-tenant2-something/Patient/1234567', null, { @@ -76,12 +93,13 @@ describe('Multi-tenant: Rest hook notification', () => { test('Header in channelHeader overrides header in allow list when there is duplicated header name', async () => { await expect( - sendRestHookNotification( + restHookHandler.sendRestHookNotification( getEvent({ channelHeader: ['header-name-2: header-value-2-something'], endpoint: 'https://fake-end-point-tenant2-something', tenantId: 'tenant2', }), + allowListPromise, ), ).resolves.toEqual([{ message: 'PUT Successful' }]); expect(axios.put).toHaveBeenCalledWith('https://fake-end-point-tenant2-something/Patient/1234567', null, { @@ -91,13 +109,17 @@ describe('Multi-tenant: Rest hook notification', () => { test('Error thrown when endpoint is not allow listed', async () => { await expect( - sendRestHookNotification(getEvent({ endpoint: 'https://fake-end-point-tenant2-something' })), + restHookHandler.sendRestHookNotification( + getEvent({ endpoint: 'https://fake-end-point-tenant2-something' }), + allowListPromise, + ), ).rejects.toThrow(new Error('Endpoint https://fake-end-point-tenant2-something is not allow listed.')); }); test('Error thrown when tenantID is not passed in', async () => { - const event = getEvent({ tenantId: null as any }); - await expect(sendRestHookNotification(event)).rejects.toThrow( + await expect( + restHookHandler.sendRestHookNotification(getEvent({ tenantId: null as any }), allowListPromise), + ).rejects.toThrow( new Error('This instance has multi-tenancy enabled, but the incoming request is missing tenantId'), ); }); diff --git a/subscriptions/types.ts b/src/subscriptions/types.ts similarity index 100% rename from subscriptions/types.ts rename to src/subscriptions/types.ts diff --git a/subscriptions/index.ts b/subscriptions/index.ts deleted file mode 100644 index 14c8c23a..00000000 --- a/subscriptions/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { sendRestHookNotification } from './restHook'; - -exports.handler = sendRestHookNotification; diff --git a/yarn.lock b/yarn.lock index 05736b68..a56b3cc6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1578,7 +1578,7 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= -"@serverless/cli@^1.6.0": +"@serverless/cli@^1.5.2": version "1.6.0" resolved "https://registry.yarnpkg.com/@serverless/cli/-/cli-1.6.0.tgz#d020f67748401ca209a4fc6407929581810573e2" integrity sha512-1Muw/KhS4sZ6+ZrXBdmVY9zAwZh03lF7v1DKtaZ0cmxjqQBwPLoO40rOGXlxR97pyufe2NS6rD/p+ri8NGqeXg== @@ -1603,7 +1603,7 @@ node-fetch "^2.6.0" shortid "^2.2.14" -"@serverless/components@^3.18.2": +"@serverless/components@^3.17.1": version "3.18.2" resolved "https://registry.yarnpkg.com/@serverless/components/-/components-3.18.2.tgz#628d78f96c1ce5f8dc836587f566a404a38d65e7" integrity sha512-jQSgd3unajU94R6vjzD0l+PS5lVcky0vrE1DOfb28VPgmaS48+I/niavWR7+SOt0mYjIkUlwBI73a2ZuqeYK6Q== @@ -1649,7 +1649,7 @@ ramda "^0.26.1" semver "^6.1.1" -"@serverless/dashboard-plugin@^5.5.4": +"@serverless/dashboard-plugin@^5.5.0": version "5.5.4" resolved "https://registry.yarnpkg.com/@serverless/dashboard-plugin/-/dashboard-plugin-5.5.4.tgz#2bbe1d5e2e5fa79b0f95422fd5b7558c248ce689" integrity sha512-qqZnT/RXhBcWXwYnpF+GMeNvSUi6mnyIqsAHj8+f7jJ7N9qa5Qrb14+dUh78sdgRBG5Peub3m3Mlx1n5V9yHDw== @@ -1783,7 +1783,7 @@ uuid "^8.3.2" write-file-atomic "^3.0.3" -"@serverless/utils@^5.20.3": +"@serverless/utils@^5.19.0", "@serverless/utils@^5.20.3": version "5.20.3" resolved "https://registry.yarnpkg.com/@serverless/utils/-/utils-5.20.3.tgz#5ab15cf8eceb81934946f2deeee028c6b9b6e270" integrity sha512-MG3DQJdto+LaeVY9gh/z0xloAfT0h1Y3Pa4/yYcKe8Dy5HYtSujuav0MvTOH18+s2outjKKJDxTh6tZuyNqFDQ== @@ -1915,6 +1915,11 @@ resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.84.tgz#b1f391ceeb6908b28d8416d93f27afe8d1348d4e" integrity sha512-5V78eLtmN0d4RA14hKDwcsMQRl3JotQJlhGFDBo/jdE2TyDFRaYwB/UmMUC4SzhSvRGn+YMkh7jGPnXi8COAng== +"@types/aws-lambda@^8.10.92": + version "8.10.92" + resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.92.tgz#645f769ff88b8eba1acd35542695ac322c7757c4" + integrity sha512-dB14TltT1SNq73z3MaZfKyyBZ37NAgAFl8jze59bisR4fJ6pB6AYGxItHFkooZbN7UcVJX/cFudM4p8wp1W4rA== + "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7": version "7.1.16" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.16.tgz#bc12c74b7d65e82d29876b5d0baf5c625ac58702" @@ -2857,10 +2862,10 @@ aws-sdk@^2.1000.0, aws-sdk@^2.834.0: uuid "3.3.2" xml2js "0.4.19" -aws-sdk@^2.1062.0: - version "2.1069.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1069.0.tgz#85c8005a61b4a42b08dc1f41e312e9bac4e411ad" - integrity sha512-AF7/5JotrVd8g/D3WWHgQto+IryB1V7iudIYm+H+qxmkGOU3xvL63ChhEoLTY/CxuK/diayg0oWILEsXUn3dfw== +aws-sdk@^2.1011.0: + version "2.1072.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1072.0.tgz#4fa844765ebb65b78c242b8edcf98a1669d4523b" + integrity sha512-b0gEHuC6xTGduPTS+ZCScurw9RTyOned9gf6H0rDagW8hdSMebsFQy84ZreeiZHHChyKyWrNUbUFugOYdw+WXw== dependencies: buffer "4.9.2" events "1.1.1" @@ -3736,7 +3741,7 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -cli-progress-footer@^2.3.0: +cli-progress-footer@^2.1.1, cli-progress-footer@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/cli-progress-footer/-/cli-progress-footer-2.3.0.tgz#ef8af382fa8e8c5c649dd3e7667d93b7494d3d3c" integrity sha512-xJl+jqvdsE0Gjh5tKoLzZrQS4nPHC+yzeitgq2faAZiHl+/Peuwzoy5Sed6EBkm8JNrPk7W4U3YNVO/uxoqOFg== @@ -5208,7 +5213,7 @@ esrecurse@^4.1.0, esrecurse@^4.3.0: dependencies: estraverse "^5.2.0" -essentials@^1.2.0: +essentials@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/essentials/-/essentials-1.2.0.tgz#c6361fb648f5c8c0c51279707f6139e521a05807" integrity sha512-kP/j7Iw7KeNE8b/o7+tr9uX2s1wegElGOoGZ2Xm35qBr4BbbEcH3/bxR2nfH9l9JANCq9AUrvKw+gRuHtZp0HQ== @@ -5719,7 +5724,7 @@ filenamify@^4.3.0: strip-outer "^1.0.1" trim-repeated "^1.0.0" -filesize@^8.0.7: +filesize@^8.0.3: version "8.0.7" resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8" integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ== @@ -6177,7 +6182,7 @@ globby@^11.0.1, globby@^11.0.3: merge2 "^1.3.0" slash "^3.0.0" -globby@^11.1.0: +globby@^11.0.4: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -6245,7 +6250,7 @@ graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1. resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== -graceful-fs@^4.2.9: +graceful-fs@^4.2.8: version "4.2.9" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== @@ -8634,20 +8639,6 @@ ncjsm@^4.1.0, ncjsm@^4.2.0: fs2 "^0.3.9" type "^2.5.0" -ncjsm@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ncjsm/-/ncjsm-4.3.0.tgz#ec2301ad67475f414a50de34fae00ebc31527e38" - integrity sha512-oah6YGwb4Ern2alojiMFcjPhE4wvQBw1Ur/kUr2P0ovKdzaF5pCIsGjs0f2y+iZeej0/5Y6OOhQ8j30cTDMEGw== - dependencies: - builtin-modules "^3.2.0" - deferred "^0.7.11" - es5-ext "^0.10.53" - es6-set "^0.1.5" - ext "^1.6.0" - find-requires "^1.0.0" - fs2 "^0.3.9" - type "^2.5.0" - nearley@^2.20.0: version "2.20.1" resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.20.1.tgz#246cd33eff0d012faf197ff6774d7ac78acdd474" @@ -8713,7 +8704,7 @@ node-dir@^0.1.17: dependencies: minimatch "^3.0.2" -node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7: +node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.5, node-fetch@^2.6.7: version "2.6.5" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.5.tgz#42735537d7f080a7e5f78b6c549b7146be1742fd" integrity sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ== @@ -8875,7 +8866,7 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-hash@^2.0.3: +object-hash@^2.0.3, object-hash@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== @@ -10592,58 +10583,59 @@ serverless-webpack@^5.3.1: optionalDependencies: ts-node ">= 8.3.0" -serverless@2.72.2: - version "2.72.2" - resolved "https://registry.yarnpkg.com/serverless/-/serverless-2.72.2.tgz#bb09965818cfa0683018900717c393a464a1ee5d" - integrity sha512-xlxaWyq4b58DIX3prwIikBmXj0z4R+YI9zcDPYgQc78mKJ0qTgCK7BMoy6dlVuHXnfhBkhT3uT/EhFvjKIdk6g== +serverless@2.64.1: + version "2.64.1" + resolved "https://registry.yarnpkg.com/serverless/-/serverless-2.64.1.tgz#708f7000577437f5fec27efd996967d67dc66fb3" + integrity sha512-9DErsV4ACg/2UkRoX2EYRkcDyRi3NBld/gexCeVnkmUunadSwnmtSBeVU8spvg+zc61b1vbusTHE4woqcd2gvw== dependencies: - "@serverless/cli" "^1.6.0" - "@serverless/components" "^3.18.2" - "@serverless/dashboard-plugin" "^5.5.4" + "@serverless/cli" "^1.5.2" + "@serverless/components" "^3.17.1" + "@serverless/dashboard-plugin" "^5.5.0" "@serverless/platform-client" "^4.3.0" - "@serverless/utils" "^5.20.3" + "@serverless/utils" "^5.19.0" ajv "^6.12.6" ajv-keywords "^3.5.2" archiver "^5.3.0" - aws-sdk "^2.1062.0" + aws-sdk "^2.1011.0" bluebird "^3.7.2" boxen "^5.1.2" cachedir "^2.3.0" chalk "^4.1.2" child-process-ext "^2.1.1" - ci-info "^3.3.0" - cli-progress-footer "^2.3.0" + ci-info "^3.2.0" + cli-progress-footer "^2.1.1" d "^1.0.1" dayjs "^1.10.7" decompress "^4.2.1" dotenv "^10.0.0" dotenv-expand "^5.1.0" - essentials "^1.2.0" + essentials "^1.1.1" ext "^1.6.0" fastest-levenshtein "^1.0.12" - filesize "^8.0.7" + filesize "^8.0.3" fs-extra "^9.1.0" get-stdin "^8.0.0" - globby "^11.1.0" - got "^11.8.3" - graceful-fs "^4.2.9" + globby "^11.0.4" + got "^11.8.2" + graceful-fs "^4.2.8" https-proxy-agent "^5.0.0" is-docker "^2.2.1" + is-wsl "^2.2.0" js-yaml "^4.1.0" json-cycle "^1.3.0" json-refs "^3.0.15" lodash "^4.17.21" memoizee "^0.4.15" micromatch "^4.0.4" - ncjsm "^4.3.0" - node-fetch "^2.6.7" - open "^7.4.2" + ncjsm "^4.2.0" + node-fetch "^2.6.5" + object-hash "^2.2.0" path2 "^0.1.0" process-utils "^4.0.0" promise-queue "^2.2.5" replaceall "^0.1.6" semver "^7.3.5" - signal-exit "^3.0.6" + signal-exit "^3.0.5" strip-ansi "^6.0.1" tabtab "^3.0.2" tar "^6.1.11" @@ -10749,7 +10741,7 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f" integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ== -signal-exit@^3.0.6: +signal-exit@^3.0.5: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== From d2b4d29649428205a8fe15824aa9e2bc9f77496f Mon Sep 17 00:00:00 2001 From: zheyanyu Date: Thu, 10 Feb 2022 16:03:06 -0500 Subject: [PATCH 5/5] clean up --- .eslintrc.js | 5 + src/subscriptions/allowList.ts | 12 +- src/subscriptions/allowListUtil.ts | 27 +- src/subscriptions/restHookMultiTenant.test.ts | 9 + yarn.lock | 256 ++++++------------ 5 files changed, 108 insertions(+), 201 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 7b2fa0d3..bbde2035 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -25,6 +25,11 @@ module.exports = { 'no-empty-function': 'off', '@typescript-eslint/no-empty-function': 'error', 'import/no-extraneous-dependencies': ['error', { devDependencies: ['**/*.test.ts', 'integration-tests/*'] }], + // @types/aws-lambda is special since aws-lambda is not the name of a package that we take as a dependency. + // Making eslint recognize it would require several additional plugins and it's not worth setting it up right now. + // See https://github.com/typescript-eslint/typescript-eslint/issues/1624 + // eslint-disable-next-line import/no-unresolved + 'import/no-unresolved': ['error', { ignore: ['aws-lambda'] }], 'no-shadow': 'off', // replaced by ts-eslint rule below '@typescript-eslint/no-shadow': 'error', }, diff --git a/src/subscriptions/allowList.ts b/src/subscriptions/allowList.ts index d3d99aa3..12a11038 100644 --- a/src/subscriptions/allowList.ts +++ b/src/subscriptions/allowList.ts @@ -6,7 +6,7 @@ import { SubscriptionEndpoint } from 'fhir-works-on-aws-routing/lib/router/validation/subscriptionValidator'; const getAllowListedSubscriptionEndpoints = async (): Promise => { - // return []; + return []; // Add here the endpoints that are allowed in Subscriptions // [ // { @@ -16,16 +16,6 @@ const getAllowListedSubscriptionEndpoints = async (): Promise { - const getHeaders = ({ allowList, headerMap }: AllowListInfo): string[] => { - // eslint-disable-next-line no-restricted-syntax - for (const allowedEndpoint of allowList) { - if (allowedEndpoint instanceof RegExp && allowedEndpoint.test(endpoint)) { - return headerMap[allowedEndpoint.toString()]; - } - if (allowedEndpoint === endpoint) { - return headerMap[allowedEndpoint]; + const getHeaders = (allowListInfo: AllowListInfo): string[] => { + if (allowListInfo) { + const { allowList, headerMap } = allowListInfo; + // eslint-disable-next-line no-restricted-syntax + for (const allowedEndpoint of allowList) { + if (allowedEndpoint instanceof RegExp && allowedEndpoint.test(endpoint)) { + return headerMap[allowedEndpoint.toString()]; + } + if (allowedEndpoint === endpoint) { + return headerMap[allowedEndpoint]; + } } } throw new Error(`Endpoint ${endpoint} is not allow listed.`); @@ -71,12 +74,12 @@ export const getAllowListHeaders = ( if (enableMultitenancy) { if (tenantId) { - return getHeaders(allowListInfo[tenantId]); + return getHeaders(allowListInfoMap[tenantId]); } throw new Error('This instance has multi-tenancy enabled, but the incoming request is missing tenantId'); } if (!tenantId) { - return getHeaders(allowListInfo[SINGLE_TENANT_ALLOW_LIST_KEY]); + return getHeaders(allowListInfoMap[SINGLE_TENANT_ALLOW_LIST_KEY]); } throw new Error('This instance has multi-tenancy disabled, but the incoming request has a tenantId'); }; diff --git a/src/subscriptions/restHookMultiTenant.test.ts b/src/subscriptions/restHookMultiTenant.test.ts index fb33ea45..152cd20b 100644 --- a/src/subscriptions/restHookMultiTenant.test.ts +++ b/src/subscriptions/restHookMultiTenant.test.ts @@ -116,6 +116,15 @@ describe('Multi-tenant: Rest hook notification', () => { ).rejects.toThrow(new Error('Endpoint https://fake-end-point-tenant2-something is not allow listed.')); }); + test('Error thrown when tenant has no allow list', async () => { + await expect( + restHookHandler.sendRestHookNotification( + getEvent({ endpoint: 'https://fake-end-point-tenant3-something', tenantId: 'tenant3' }), + allowListPromise, + ), + ).rejects.toThrow(new Error('Endpoint https://fake-end-point-tenant3-something is not allow listed.')); + }); + test('Error thrown when tenantID is not passed in', async () => { await expect( restHookHandler.sendRestHookNotification(getEvent({ tenantId: null as any }), allowListPromise), diff --git a/yarn.lock b/yarn.lock index a56b3cc6..f699033e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1579,9 +1579,9 @@ integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= "@serverless/cli@^1.5.2": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@serverless/cli/-/cli-1.6.0.tgz#d020f67748401ca209a4fc6407929581810573e2" - integrity sha512-1Muw/KhS4sZ6+ZrXBdmVY9zAwZh03lF7v1DKtaZ0cmxjqQBwPLoO40rOGXlxR97pyufe2NS6rD/p+ri8NGqeXg== + version "1.5.3" + resolved "https://registry.yarnpkg.com/@serverless/cli/-/cli-1.5.3.tgz#61045cdc11c0f41f50ee1ce784dfa8f0f4f26416" + integrity sha512-ZJ0Y7CsYoE/i45XkIMl/XBZO4KIlt0XH1qwxxNE2/84bZlih5cgRV6MZ+rKt7GlrD0iDAgQGyGv5dpyt+SGhKw== dependencies: "@serverless/core" "^1.1.2" "@serverless/template" "^1.1.3" @@ -1604,9 +1604,9 @@ shortid "^2.2.14" "@serverless/components@^3.17.1": - version "3.18.2" - resolved "https://registry.yarnpkg.com/@serverless/components/-/components-3.18.2.tgz#628d78f96c1ce5f8dc836587f566a404a38d65e7" - integrity sha512-jQSgd3unajU94R6vjzD0l+PS5lVcky0vrE1DOfb28VPgmaS48+I/niavWR7+SOt0mYjIkUlwBI73a2ZuqeYK6Q== + version "3.17.1" + resolved "https://registry.yarnpkg.com/@serverless/components/-/components-3.17.1.tgz#824ae5c7f5b7a6d089ca407a8e6361416af8ff88" + integrity sha512-Ra0VVpivEWB816ZAca4UCNzOxQqxveEp4h+RzUX5vaAsZrxpotPUFZi96w9yZGQk3OTxxscRqrsBLxGDtOu8SA== dependencies: "@serverless/platform-client" "^4.2.2" "@serverless/platform-client-china" "^2.2.0" @@ -1650,16 +1650,16 @@ semver "^6.1.1" "@serverless/dashboard-plugin@^5.5.0": - version "5.5.4" - resolved "https://registry.yarnpkg.com/@serverless/dashboard-plugin/-/dashboard-plugin-5.5.4.tgz#2bbe1d5e2e5fa79b0f95422fd5b7558c248ce689" - integrity sha512-qqZnT/RXhBcWXwYnpF+GMeNvSUi6mnyIqsAHj8+f7jJ7N9qa5Qrb14+dUh78sdgRBG5Peub3m3Mlx1n5V9yHDw== + version "5.5.0" + resolved "https://registry.yarnpkg.com/@serverless/dashboard-plugin/-/dashboard-plugin-5.5.0.tgz#d8b0407e277f22111a88cf0d6062d0e17de5b9ca" + integrity sha512-a3vWcAacJrUeUFcGXhm/tDxvZjqQI1KjRjFGrqbxoN0N5fCdkLtOn6578Iq4hdP8BF2XanS1xGGdhPjcYBdsUA== dependencies: "@serverless/event-mocks" "^1.1.1" "@serverless/platform-client" "^4.3.0" - "@serverless/utils" "^5.20.3" + "@serverless/utils" "^5.18.0" chalk "^4.1.2" child-process-ext "^2.1.1" - chokidar "^3.5.3" + chokidar "^3.5.2" cli-color "^2.0.1" flat "^5.0.2" fs-extra "^9.1.0" @@ -1669,10 +1669,10 @@ memoizee "^0.4.15" ncjsm "^4.2.0" node-dir "^0.1.17" - node-fetch "^2.6.7" + node-fetch "^2.6.5" open "^7.4.2" semver "^7.3.5" - simple-git "^2.48.0" + simple-git "^2.46.0" uuid "^8.3.2" yamljs "^0.3.0" @@ -1783,16 +1783,16 @@ uuid "^8.3.2" write-file-atomic "^3.0.3" -"@serverless/utils@^5.19.0", "@serverless/utils@^5.20.3": - version "5.20.3" - resolved "https://registry.yarnpkg.com/@serverless/utils/-/utils-5.20.3.tgz#5ab15cf8eceb81934946f2deeee028c6b9b6e270" - integrity sha512-MG3DQJdto+LaeVY9gh/z0xloAfT0h1Y3Pa4/yYcKe8Dy5HYtSujuav0MvTOH18+s2outjKKJDxTh6tZuyNqFDQ== +"@serverless/utils@^5.18.0", "@serverless/utils@^5.19.0": + version "5.19.0" + resolved "https://registry.yarnpkg.com/@serverless/utils/-/utils-5.19.0.tgz#3cbf280e299dd4747bbdd24b5afc3c79d3c3867f" + integrity sha512-bgQawVfBgxcZoS1wxukJfRYKkMOZncZfOSTCRUnYzwH78fAAE79vfu49LGx2EGEJa8BThmtzjinZ9SK9yS0kIw== dependencies: archive-type "^4.0.0" chalk "^4.1.2" - ci-info "^3.3.0" - cli-progress-footer "^2.3.0" - content-disposition "^0.5.4" + ci-info "^3.2.0" + cli-progress-footer "^2.1.1" + content-disposition "^0.5.3" d "^1.0.1" decompress "^4.2.1" event-emitter "^0.3.5" @@ -1801,13 +1801,13 @@ file-type "^16.5.3" filenamify "^4.3.0" get-stream "^6.0.1" - got "^11.8.3" + got "^11.8.2" inquirer "^7.3.3" js-yaml "^4.1.0" jwt-decode "^3.1.2" lodash "^4.17.21" log "^6.3.1" - log-node "^8.0.3" + log-node "^8.0.1" make-dir "^3.1.0" memoizee "^0.4.15" ncjsm "^4.2.0" @@ -2574,7 +2574,7 @@ ansi-escapes@^4.2.1, ansi-escapes@^4.3.1: dependencies: type-fest "^0.21.3" -ansi-regex@^2.0.0, ansi-regex@^3.0.0, ansi-regex@^4.1.0, ansi-regex@^5.0.0, ansi-regex@^5.0.1: +ansi-regex@^2.0.0, ansi-regex@^2.1.1, ansi-regex@^3.0.0, ansi-regex@^4.1.0, ansi-regex@^5.0.0, ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== @@ -2847,7 +2847,7 @@ aws-elasticsearch-connector@^8.2.0: aws4 "^1.10.0" lodash.get "^4.4.2" -aws-sdk@^2.1000.0, aws-sdk@^2.834.0: +aws-sdk@^2.1000.0, aws-sdk@^2.1011.0, aws-sdk@^2.834.0: version "2.1015.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1015.0.tgz#fc69c0620a34c1d77173123769fbc725d867b3cc" integrity sha512-jSM955n08r+kzCMMhOu1Dbua8SRZQKgGO1nAoUwBSlXjnLtN+F81P93h4yNBtWsxUg1mAMTP3DKJjXFFrRToPw== @@ -2862,21 +2862,6 @@ aws-sdk@^2.1000.0, aws-sdk@^2.834.0: uuid "3.3.2" xml2js "0.4.19" -aws-sdk@^2.1011.0: - version "2.1072.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1072.0.tgz#4fa844765ebb65b78c242b8edcf98a1669d4523b" - integrity sha512-b0gEHuC6xTGduPTS+ZCScurw9RTyOned9gf6H0rDagW8hdSMebsFQy84ZreeiZHHChyKyWrNUbUFugOYdw+WXw== - dependencies: - buffer "4.9.2" - events "1.1.1" - ieee754 "1.1.13" - jmespath "0.16.0" - querystring "0.2.0" - sax "1.2.1" - url "0.10.3" - uuid "3.3.2" - xml2js "0.4.19" - aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -3485,7 +3470,7 @@ cacheable-request@^6.0.0: normalize-url "^4.1.0" responselike "^1.0.2" -cacheable-request@^7.0.1, cacheable-request@^7.0.2: +cacheable-request@^7.0.1: version "7.0.2" resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.2.tgz#ea0d0b889364a25854757301ca12b2da77f91d27" integrity sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew== @@ -3604,7 +3589,7 @@ child-process-ext@^2.1.1: split2 "^3.1.1" stream-promise "^3.2.0" -"chokidar@>=3.0.0 <4.0.0", chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.5.1: +"chokidar@>=3.0.0 <4.0.0", chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.5.1, chokidar@^3.5.2: version "3.5.2" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== @@ -3638,21 +3623,6 @@ chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" -chokidar@^3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - chownr@^1.0.1, chownr@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" @@ -3678,11 +3648,6 @@ ci-info@^3.1.1, ci-info@^3.2.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A== -ci-info@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.0.tgz#b4ed1fb6818dea4803a55c623041f9165d2066b2" - integrity sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw== - cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" @@ -3716,7 +3681,19 @@ cli-boxes@^2.2.1: resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== -cli-color@^2.0.1: +cli-color@^1.3: + version "1.4.0" + resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-1.4.0.tgz#7d10738f48526824f8fe7da51857cb0f572fe01f" + integrity sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w== + dependencies: + ansi-regex "^2.1.1" + d "1" + es5-ext "^0.10.46" + es6-iterator "^2.0.3" + memoizee "^0.4.14" + timers-ext "^0.1.5" + +cli-color@^2.0.0, cli-color@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-2.0.1.tgz#93e3491308691f1e46beb78b63d0fb2585e42ba6" integrity sha512-eBbxZF6fqPUNnf7CLAFOersUnyYzv83tHFLSlts+OAHsNendaqv2tHCq+/MO+b3Y+9JeoUlIvobyxG/Z8GNeOg== @@ -3741,28 +3718,27 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -cli-progress-footer@^2.1.1, cli-progress-footer@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/cli-progress-footer/-/cli-progress-footer-2.3.0.tgz#ef8af382fa8e8c5c649dd3e7667d93b7494d3d3c" - integrity sha512-xJl+jqvdsE0Gjh5tKoLzZrQS4nPHC+yzeitgq2faAZiHl+/Peuwzoy5Sed6EBkm8JNrPk7W4U3YNVO/uxoqOFg== +cli-progress-footer@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/cli-progress-footer/-/cli-progress-footer-2.1.1.tgz#ed45cccc22f95284f715a38ae3e4191ebe3bdf04" + integrity sha512-fBEAKLDp/CCMzQSeEbvz4POvomCekmT0LodI/mchzrjIPeLXQHJ9Gb28leAqEjdc9wyV40cjsB2aWpvO5MA7Pw== dependencies: - cli-color "^2.0.1" + cli-color "^2.0.0" d "^1.0.1" es5-ext "^0.10.53" - mute-stream "0.0.8" process-utils "^4.0.0" timers-ext "^0.1.7" type "^2.5.0" -cli-sprintf-format@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/cli-sprintf-format/-/cli-sprintf-format-1.1.1.tgz#ec69955c89ef1c61243b52e68015b75c08fb9188" - integrity sha512-BbEjY9BEdA6wagVwTqPvmAwGB24U93rQPBFZUT8lNCDxXzre5LFHQUTJc70czjgUomVg8u8R5kW8oY9DYRFNeg== +cli-sprintf-format@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/cli-sprintf-format/-/cli-sprintf-format-1.1.0.tgz#7373b00cc8299285f29bc2a20e050a4522d4baff" + integrity sha512-t3LcCdPvrypZovStadWdRS4a186gsq9aoHJYTIer55VY20YdVjGVHDV4uPWcWCXTw1tPjfwlRGE7zKMWJ663Sw== dependencies: - cli-color "^2.0.1" - es5-ext "^0.10.53" - sprintf-kit "^2.0.1" - supports-color "^6.1.0" + cli-color "^1.3" + es5-ext "^0.10.46" + sprintf-kit "2" + supports-color "^5.5" cli-width@^2.0.0: version "2.2.1" @@ -4014,20 +3990,13 @@ constants-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= -content-disposition@0.5.3: +content-disposition@0.5.3, content-disposition@^0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== dependencies: safe-buffer "5.1.2" -content-disposition@^0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - content-type@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" @@ -5214,11 +5183,9 @@ esrecurse@^4.1.0, esrecurse@^4.3.0: estraverse "^5.2.0" essentials@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/essentials/-/essentials-1.2.0.tgz#c6361fb648f5c8c0c51279707f6139e521a05807" - integrity sha512-kP/j7Iw7KeNE8b/o7+tr9uX2s1wegElGOoGZ2Xm35qBr4BbbEcH3/bxR2nfH9l9JANCq9AUrvKw+gRuHtZp0HQ== - dependencies: - uni-global "^1.0.0" + version "1.1.1" + resolved "https://registry.yarnpkg.com/essentials/-/essentials-1.1.1.tgz#03befbfbee7078301741279b38a806b6ca624821" + integrity sha512-SmaxoAdVu86XkZQM/u6TYSu96ZlFGwhvSk1l9zAkznFuQkMb9mRDS2iq/XWDow7R8OwBwdYH8nLyDKznMD+GWw== estraverse@^4.1.1, estraverse@^4.2.0: version "4.3.0" @@ -5491,17 +5458,6 @@ fast-glob@^3.1.1, fast-glob@^3.2.4, fast-glob@^3.2.5, fast-glob@^3.2.7: merge2 "^1.3.0" micromatch "^4.0.4" -fast-glob@^3.2.9: - version "3.2.11" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" - integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -5725,9 +5681,9 @@ filenamify@^4.3.0: trim-repeated "^1.0.0" filesize@^8.0.3: - version "8.0.7" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8" - integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ== + version "8.0.5" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.5.tgz#3b8ffa1bd5e6b0e187530280ab4c06b941a7226a" + integrity sha512-nW6kfJQIL+FY63PWbyxyRKDSFqm3aomjcMfydPzlOn7HiLnY9jPnbRuawroqGGQHEceDxUQEaF0RYy94irvsEw== fill-range@^4.0.0: version "4.0.0" @@ -6170,7 +6126,7 @@ globals@^13.6.0, globals@^13.9.0: dependencies: type-fest "^0.20.2" -globby@^11.0.1, globby@^11.0.3: +globby@^11.0.1, globby@^11.0.3, globby@^11.0.4: version "11.0.4" resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== @@ -6182,18 +6138,6 @@ globby@^11.0.1, globby@^11.0.3: merge2 "^1.3.0" slash "^3.0.0" -globby@^11.0.4: - version "11.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.2.9" - ignore "^5.2.0" - merge2 "^1.4.1" - slash "^3.0.0" - got@^11.8.2: version "11.8.2" resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599" @@ -6211,23 +6155,6 @@ got@^11.8.2: p-cancelable "^2.0.0" responselike "^2.0.0" -got@^11.8.3: - version "11.8.3" - resolved "https://registry.yarnpkg.com/got/-/got-11.8.3.tgz#f496c8fdda5d729a90b4905d2b07dbd148170770" - integrity sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg== - dependencies: - "@sindresorhus/is" "^4.0.0" - "@szmarczak/http-timer" "^4.0.5" - "@types/cacheable-request" "^6.0.1" - "@types/responselike" "^1.0.0" - cacheable-lookup "^5.0.3" - cacheable-request "^7.0.2" - decompress-response "^6.0.0" - http2-wrapper "^1.0.0-beta.5.2" - lowercase-keys "^2.0.0" - p-cancelable "^2.0.0" - responselike "^2.0.0" - got@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" @@ -6245,16 +6172,11 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: +graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.8: version "4.2.8" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== -graceful-fs@^4.2.8: - version "4.2.9" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" - integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== - graphlib@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da" @@ -6571,11 +6493,6 @@ ignore@^5.1.4, ignore@^5.1.8: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== -ignore@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" - integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== - immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" @@ -7611,11 +7528,6 @@ jmespath@0.15.0: resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc= -jmespath@0.16.0: - version "0.16.0" - resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" - integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw== - js-string-escape@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" @@ -8129,14 +8041,14 @@ lodash@4.17.x, lodash@4.x, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.12, lod resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -log-node@^8.0.3: - version "8.0.3" - resolved "https://registry.yarnpkg.com/log-node/-/log-node-8.0.3.tgz#441bf1a72f9f1c28b62f5bf42e9eb3765af74d73" - integrity sha512-1UBwzgYiCIDFs8A0rM2QdBFo8Wd8UQ0HrSTu/MNI+/2zN3NoHRj2fhplurAyuxTYUXu3Oohugq1jAn5s05u1MQ== +log-node@^8.0.1: + version "8.0.2" + resolved "https://registry.yarnpkg.com/log-node/-/log-node-8.0.2.tgz#0c067e6e7f1623f0c69be1d809603af3c03db053" + integrity sha512-H+3t002yGqZRJhVW3A/EksIB7i1M84cwcWzBPsnAmQWOA2ePAV2fXJpNPBnw6VEdhzPcedIxQwT5jhBVTlYQww== dependencies: ansi-regex "^5.0.1" - cli-color "^2.0.1" - cli-sprintf-format "^1.1.1" + cli-color "^2.0.0" + cli-sprintf-format "^1.1.0" d "^1.0.1" es5-ext "^0.10.53" sprintf-kit "^2.0.1" @@ -8339,7 +8251,7 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.3.0, merge2@^1.4.1: +merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== @@ -8704,7 +8616,7 @@ node-dir@^0.1.17: dependencies: minimatch "^3.0.2" -node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.5, node-fetch@^2.6.7: +node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.5: version "2.6.5" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.5.tgz#42735537d7f080a7e5f78b6c549b7146be1742fd" integrity sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ== @@ -10265,7 +10177,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -10736,16 +10648,11 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f" integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ== -signal-exit@^3.0.5: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - simple-concat@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" @@ -10760,10 +10667,10 @@ simple-get@^2.7.0: once "^1.3.1" simple-concat "^1.0.0" -simple-git@^2.48.0: - version "2.48.0" - resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-2.48.0.tgz#87c262dba8f84d7b96bb3a713e9e34701c1f6e3b" - integrity sha512-z4qtrRuaAFJS4PUd0g+xy7aN4y+RvEt/QTJpR184lhJguBA1S/LsVlvE/CM95RsYMOFJG3NGGDjqFCzKU19S/A== +simple-git@^2.46.0: + version "2.47.0" + resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-2.47.0.tgz#9693b471de7911901f703b4c403fc3e8774cd9be" + integrity sha512-+HfCpqPBEZTPWiW9fPdbiPJDslM22MLqrktfzNKyI2pWaJa6DhfNVx4Mds04KZzVv5vjC9/ksw3y5gVf8ECWDg== dependencies: "@kwsites/file-exists" "^1.1.1" "@kwsites/promise-deferred" "^1.1.1" @@ -10999,7 +10906,7 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -sprintf-kit@^2.0.1: +sprintf-kit@2, sprintf-kit@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/sprintf-kit/-/sprintf-kit-2.0.1.tgz#bb837e8fa4b28f094531d8e33669120027236bb8" integrity sha512-2PNlcs3j5JflQKcg4wpdqpZ+AjhQJ2OZEo34NXDtlB0tIPG84xaaXhpA8XFacFiwjKA4m49UOYG83y3hbMn/gQ== @@ -11283,20 +11190,13 @@ supports-color@^2.0.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= -supports-color@^5.3.0: +supports-color@^5.3.0, supports-color@^5.5: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== dependencies: has-flag "^3.0.0" -supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== - dependencies: - has-flag "^3.0.0" - supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -11487,7 +11387,7 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" -timers-ext@^0.1.7: +timers-ext@^0.1.5, timers-ext@^0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6" integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==