@mojaloop/sdk-scheme-adapter 11.18.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +140 -0
- package/.eslintignore +2 -0
- package/.eslintrc.json +30 -0
- package/.nvmrc +1 -0
- package/.versionrc +15 -0
- package/CHANGELOG.md +118 -0
- package/InboundServer/api.yaml +3594 -0
- package/InboundServer/api_template.yaml +69 -0
- package/InboundServer/handlers.js +940 -0
- package/InboundServer/index.js +205 -0
- package/InboundServer/middlewares.js +426 -0
- package/OAuthTestServer/index.js +66 -0
- package/OAuthTestServer/model.js +70 -0
- package/OutboundServer/api.yaml +2732 -0
- package/OutboundServer/api_interfaces/index.d.ts +117 -0
- package/OutboundServer/api_interfaces/openapi.d.ts +1475 -0
- package/OutboundServer/api_template/components/parameters/bulkQuoteId.yaml +9 -0
- package/OutboundServer/api_template/components/parameters/bulkTransferId.yaml +9 -0
- package/OutboundServer/api_template/components/parameters/requestToPayTransactionId.yaml +9 -0
- package/OutboundServer/api_template/components/parameters/transferId.yaml +9 -0
- package/OutboundServer/api_template/components/responses/accountsCreationCompleted.yaml +5 -0
- package/OutboundServer/api_template/components/responses/accountsCreationError.yaml +5 -0
- package/OutboundServer/api_template/components/responses/accountsCreationTimeout.yaml +5 -0
- package/OutboundServer/api_template/components/responses/authorizationPostSuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/authorizationsServerError.yaml +5 -0
- package/OutboundServer/api_template/components/responses/bulkQuoteBadRequest.yaml +5 -0
- package/OutboundServer/api_template/components/responses/bulkQuoteServerError.yaml +5 -0
- package/OutboundServer/api_template/components/responses/bulkQuoteSuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/bulkQuoteTimeout.yaml +5 -0
- package/OutboundServer/api_template/components/responses/bulkTransferBadRequest.yaml +5 -0
- package/OutboundServer/api_template/components/responses/bulkTransferServerError.yaml +5 -0
- package/OutboundServer/api_template/components/responses/bulkTransferSuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/bulkTransferTimeout.yaml +5 -0
- package/OutboundServer/api_template/components/responses/partiesByIdError404.yaml +9 -0
- package/OutboundServer/api_template/components/responses/partiesByIdSuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/quotesPostSuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/quotesServerError.yaml +5 -0
- package/OutboundServer/api_template/components/responses/requestToPaySuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/requestToPayTransferBadRequest.yaml +5 -0
- package/OutboundServer/api_template/components/responses/requestToPayTransferSuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/simpleTransfersPostSuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/simpleTransfersServerError.yaml +5 -0
- package/OutboundServer/api_template/components/responses/transferBadRequest.yaml +5 -0
- package/OutboundServer/api_template/components/responses/transferServerError.yaml +5 -0
- package/OutboundServer/api_template/components/responses/transferSuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/transferTimeout.yaml +5 -0
- package/OutboundServer/api_template/components/schemas/accountCreationStatus.yaml +18 -0
- package/OutboundServer/api_template/components/schemas/accountsCreationState.yaml +4 -0
- package/OutboundServer/api_template/components/schemas/accountsRequest.yaml +20 -0
- package/OutboundServer/api_template/components/schemas/accountsResponse.yaml +15 -0
- package/OutboundServer/api_template/components/schemas/async2SyncCurrentState.yaml +5 -0
- package/OutboundServer/api_template/components/schemas/authorizationsPostRequest.yaml +15 -0
- package/OutboundServer/api_template/components/schemas/authorizationsPostResponse.yaml +19 -0
- package/OutboundServer/api_template/components/schemas/bulkQuoteErrorResponse.yaml +8 -0
- package/OutboundServer/api_template/components/schemas/bulkQuoteRequest.yaml +26 -0
- package/OutboundServer/api_template/components/schemas/bulkQuoteResponse.yaml +21 -0
- package/OutboundServer/api_template/components/schemas/bulkQuoteStatus.yaml +4 -0
- package/OutboundServer/api_template/components/schemas/bulkQuoteStatusResponse.yaml +17 -0
- package/OutboundServer/api_template/components/schemas/bulkTransferErrorResponse.yaml +8 -0
- package/OutboundServer/api_template/components/schemas/bulkTransferRequest.yaml +26 -0
- package/OutboundServer/api_template/components/schemas/bulkTransferResponse.yaml +16 -0
- package/OutboundServer/api_template/components/schemas/bulkTransferStatus.yaml +4 -0
- package/OutboundServer/api_template/components/schemas/bulkTransferStatusResponse.yaml +17 -0
- package/OutboundServer/api_template/components/schemas/errorAccountsResponse.yaml +8 -0
- package/OutboundServer/api_template/components/schemas/errorAuthorizationsResponse.yaml +3 -0
- package/OutboundServer/api_template/components/schemas/errorQuotesResponse.yaml +9 -0
- package/OutboundServer/api_template/components/schemas/errorResponse.yaml +8 -0
- package/OutboundServer/api_template/components/schemas/errorSimpleTransfersResponse.yaml +3 -0
- package/OutboundServer/api_template/components/schemas/errorTransferResponse.yaml +8 -0
- package/OutboundServer/api_template/components/schemas/extensionListEmptiable.yaml +6 -0
- package/OutboundServer/api_template/components/schemas/individualQuote.yaml +32 -0
- package/OutboundServer/api_template/components/schemas/individualQuoteResult.yaml +28 -0
- package/OutboundServer/api_template/components/schemas/individualTransfer.yaml +32 -0
- package/OutboundServer/api_template/components/schemas/individualTransferFulfilment.yaml +13 -0
- package/OutboundServer/api_template/components/schemas/individualTransferResult.yaml +41 -0
- package/OutboundServer/api_template/components/schemas/mojaloopError.yaml +5 -0
- package/OutboundServer/api_template/components/schemas/mojaloopTransactionRequestState.yaml +2 -0
- package/OutboundServer/api_template/components/schemas/partiesByIdResponse.yaml +13 -0
- package/OutboundServer/api_template/components/schemas/quote.yaml +3 -0
- package/OutboundServer/api_template/components/schemas/quoteError.yaml +16 -0
- package/OutboundServer/api_template/components/schemas/quotesPostRequest.yaml +13 -0
- package/OutboundServer/api_template/components/schemas/quotesPostResponse.yaml +48 -0
- package/OutboundServer/api_template/components/schemas/requestToPayRequest.yaml +39 -0
- package/OutboundServer/api_template/components/schemas/requestToPayResponse.yaml +41 -0
- package/OutboundServer/api_template/components/schemas/requestToPayTransferRequest.yaml +42 -0
- package/OutboundServer/api_template/components/schemas/requestToPayTransferResponse.yaml +58 -0
- package/OutboundServer/api_template/components/schemas/simpleTransferServerError.yaml +5 -0
- package/OutboundServer/api_template/components/schemas/simpleTransfersPostRequest.yaml +12 -0
- package/OutboundServer/api_template/components/schemas/simpleTransfersPostResponse.yaml +11 -0
- package/OutboundServer/api_template/components/schemas/transactionType.yaml +4 -0
- package/OutboundServer/api_template/components/schemas/transferContinuationAcceptOTP.yaml +9 -0
- package/OutboundServer/api_template/components/schemas/transferContinuationAcceptParty.yaml +8 -0
- package/OutboundServer/api_template/components/schemas/transferContinuationAcceptQuote.yaml +9 -0
- package/OutboundServer/api_template/components/schemas/transferError.yaml +16 -0
- package/OutboundServer/api_template/components/schemas/transferFulfilment.yaml +3 -0
- package/OutboundServer/api_template/components/schemas/transferParty.yaml +40 -0
- package/OutboundServer/api_template/components/schemas/transferRequest.yaml +37 -0
- package/OutboundServer/api_template/components/schemas/transferResponse.yaml +58 -0
- package/OutboundServer/api_template/components/schemas/transferStatus.yaml +6 -0
- package/OutboundServer/api_template/components/schemas/transferStatusResponse.yaml +13 -0
- package/OutboundServer/api_template/health.yaml +12 -0
- package/OutboundServer/api_template/openapi.yaml +55 -0
- package/OutboundServer/api_template/paths/accounts.yaml +26 -0
- package/OutboundServer/api_template/paths/authorizations.yaml +19 -0
- package/OutboundServer/api_template/paths/bulkQuotes.yaml +23 -0
- package/OutboundServer/api_template/paths/bulkQuotes_bulkQuoteId.yaml +24 -0
- package/OutboundServer/api_template/paths/bulkTransfers.yaml +23 -0
- package/OutboundServer/api_template/paths/bulkTransfers_bulkTransferId.yaml +24 -0
- package/OutboundServer/api_template/paths/parties_Type_ID.yaml +20 -0
- package/OutboundServer/api_template/paths/parties_Type_ID_SubId.yaml +22 -0
- package/OutboundServer/api_template/paths/quotes.yaml +20 -0
- package/OutboundServer/api_template/paths/requestToPay.yaml +22 -0
- package/OutboundServer/api_template/paths/requestToPayTransfer.yaml +57 -0
- package/OutboundServer/api_template/paths/requestToPayTransfer_requestToPayTransactionId.yaml +34 -0
- package/OutboundServer/api_template/paths/simpleTransfers.yaml +19 -0
- package/OutboundServer/api_template/paths/transfers.yaml +55 -0
- package/OutboundServer/api_template/paths/transfers_transferId.yaml +58 -0
- package/OutboundServer/handlers.js +622 -0
- package/OutboundServer/index.js +137 -0
- package/OutboundServer/middlewares.js +67 -0
- package/TestServer/api.yaml +62 -0
- package/TestServer/handlers.js +63 -0
- package/TestServer/index.js +215 -0
- package/audit-resolve.json +65 -0
- package/babel.config.js +3 -0
- package/config.js +158 -0
- package/index.d.ts +1 -0
- package/index.js +149 -0
- package/jest.config.js +15 -0
- package/lib/api/index.js +12 -0
- package/lib/cache.js +352 -0
- package/lib/check.js +25 -0
- package/lib/model/AccountsModel.js +396 -0
- package/lib/model/Async2SyncModel.js +283 -0
- package/lib/model/AuthorizationsModel.js +86 -0
- package/lib/model/InboundTransfersModel.js +730 -0
- package/lib/model/OutboundBulkQuotesModel.js +485 -0
- package/lib/model/OutboundBulkTransfersModel.js +479 -0
- package/lib/model/OutboundRequestToPayModel.js +517 -0
- package/lib/model/OutboundRequestToPayTransferModel.js +893 -0
- package/lib/model/OutboundTransfersModel.js +823 -0
- package/lib/model/PartiesModel.js +70 -0
- package/lib/model/ProxyModel/MatchRules/Expression.js +48 -0
- package/lib/model/ProxyModel/MatchRules/Headers.js +65 -0
- package/lib/model/ProxyModel/MatchRules/MatchRule.js +27 -0
- package/lib/model/ProxyModel/MatchRules/Path.js +36 -0
- package/lib/model/ProxyModel/MatchRules/Query.js +65 -0
- package/lib/model/ProxyModel/MatchRules/index.js +19 -0
- package/lib/model/ProxyModel/Route.js +82 -0
- package/lib/model/ProxyModel/configSchema.json +118 -0
- package/lib/model/ProxyModel/index.js +138 -0
- package/lib/model/QuotesModel.js +94 -0
- package/lib/model/TransfersModel.js +81 -0
- package/lib/model/common/BackendError.js +26 -0
- package/lib/model/common/PersistentStateMachine.js +93 -0
- package/lib/model/common/index.js +18 -0
- package/lib/model/index.js +43 -0
- package/lib/model/lib/deferredJob.js +113 -0
- package/lib/model/lib/index.js +9 -0
- package/lib/model/lib/requests/backendRequests.js +227 -0
- package/lib/model/lib/requests/common.js +76 -0
- package/lib/model/lib/requests/index.js +19 -0
- package/lib/model/lib/shared.js +468 -0
- package/lib/randomphrase/index.js +21 -0
- package/lib/randomphrase/words.json +3397 -0
- package/lib/router.js +28 -0
- package/lib/validate.js +205 -0
- package/package.json +102 -0
- package/test/__mocks__/@mojaloop/sdk-standard-components.js +152 -0
- package/test/__mocks__/javascript-state-machine.js +21 -0
- package/test/__mocks__/redis.js +49 -0
- package/test/__mocks__/uuidv4.js +16 -0
- package/test/config/integration.env +136 -0
- package/test/integration/lib/Outbound/authorizations.test.js +58 -0
- package/test/integration/lib/Outbound/data/authorizationsPostRequest.json +43 -0
- package/test/integration/lib/Outbound/data/quotesPostRequest.json +52 -0
- package/test/integration/lib/Outbound/data/transfersPostRequest.json +24 -0
- package/test/integration/lib/Outbound/parties.test.js +28 -0
- package/test/integration/lib/Outbound/quotes.test.js +58 -0
- package/test/integration/lib/Outbound/simpleTransfers.test.js +67 -0
- package/test/integration/lib/cache.test.js +80 -0
- package/test/integration/testEnv.js +7 -0
- package/test/unit/InboundServer.test.js +443 -0
- package/test/unit/TestServer.test.js +394 -0
- package/test/unit/api/accounts/accounts.test.js +128 -0
- package/test/unit/api/accounts/data/postAccountsBody.json +7 -0
- package/test/unit/api/accounts/data/postAccountsErrorMojaloopResponse.json +25 -0
- package/test/unit/api/accounts/data/postAccountsErrorTimeoutResponse.json +19 -0
- package/test/unit/api/accounts/data/postAccountsSuccessResponse.json +17 -0
- package/test/unit/api/accounts/data/postAccountsSuccessResponseWithError1.json +21 -0
- package/test/unit/api/accounts/data/postAccountsSuccessResponseWithError2.json +21 -0
- package/test/unit/api/accounts/utils.js +65 -0
- package/test/unit/api/proxy/data/proxyConfig.yaml +82 -0
- package/test/unit/api/proxy/data/requestBody.json +22 -0
- package/test/unit/api/proxy/data/requestHeaders.json +5 -0
- package/test/unit/api/proxy/data/requestQuery.json +6 -0
- package/test/unit/api/proxy/data/responseBody.json +21 -0
- package/test/unit/api/proxy/data/responseHeaders.json +5 -0
- package/test/unit/api/proxy/proxy.test.js +220 -0
- package/test/unit/api/proxy/utils.js +79 -0
- package/test/unit/api/transfers/data/getTransfersCommittedResponse.json +21 -0
- package/test/unit/api/transfers/data/getTransfersErrorNotFound.json +17 -0
- package/test/unit/api/transfers/data/postQuotesBody.json +52 -0
- package/test/unit/api/transfers/data/postTransfersBadBody.json +17 -0
- package/test/unit/api/transfers/data/postTransfersBody.json +24 -0
- package/test/unit/api/transfers/data/postTransfersErrorMojaloopResponse.json +53 -0
- package/test/unit/api/transfers/data/postTransfersErrorTimeoutResponse.json +47 -0
- package/test/unit/api/transfers/data/postTransfersSimpleBody.json +26 -0
- package/test/unit/api/transfers/data/postTransfersSuccessResponse.json +101 -0
- package/test/unit/api/transfers/data/putPartiesBody.json +20 -0
- package/test/unit/api/transfers/data/putQuotesBody.json +37 -0
- package/test/unit/api/transfers/data/putTransfersBody.json +17 -0
- package/test/unit/api/transfers/transfers.test.js +191 -0
- package/test/unit/api/transfers/utils.js +183 -0
- package/test/unit/api/utils.js +75 -0
- package/test/unit/config.test.js +119 -0
- package/test/unit/data/commonHttpHeaders.json +6 -0
- package/test/unit/data/defaultConfig.json +58 -0
- package/test/unit/data/postQuotesBody.json +52 -0
- package/test/unit/data/putParticipantsBody.json +12 -0
- package/test/unit/data/putPartiesBody.json +20 -0
- package/test/unit/data/testFile.json +29 -0
- package/test/unit/data/testFile.yaml +14 -0
- package/test/unit/inboundApi/data/mockArguments.json +117 -0
- package/test/unit/inboundApi/data/mockTransactionRequest.json +42 -0
- package/test/unit/inboundApi/handlers.test.js +799 -0
- package/test/unit/index.test.js +55 -0
- package/test/unit/lib/cache.test.js +146 -0
- package/test/unit/lib/model/AccountsModel.test.js +121 -0
- package/test/unit/lib/model/AuthorizationsModel.test.js +460 -0
- package/test/unit/lib/model/InboundTransfersModel.test.js +628 -0
- package/test/unit/lib/model/OutboundBulkQuotesModel.test.js +249 -0
- package/test/unit/lib/model/OutboundBulkTransfersModel.test.js +244 -0
- package/test/unit/lib/model/OutboundRequestToPayModel.test.js +166 -0
- package/test/unit/lib/model/OutboundRequestToPayTransferModel.test.js +245 -0
- package/test/unit/lib/model/OutboundTransfersModel.test.js +836 -0
- package/test/unit/lib/model/PartiesModel.test.js +468 -0
- package/test/unit/lib/model/QuotesModel.test.js +470 -0
- package/test/unit/lib/model/TransfersModel.test.js +474 -0
- package/test/unit/lib/model/common/PersistentStateMachine.test.js +179 -0
- package/test/unit/lib/model/data/authorizationsResponse.json +13 -0
- package/test/unit/lib/model/data/bulkQuoteRequest.json +27 -0
- package/test/unit/lib/model/data/bulkQuoteResponse.json +35 -0
- package/test/unit/lib/model/data/bulkTransferFulfil.json +13 -0
- package/test/unit/lib/model/data/bulkTransferRequest.json +29 -0
- package/test/unit/lib/model/data/defaultConfig.json +47 -0
- package/test/unit/lib/model/data/getBulkTransfersBackendResponse.json +42 -0
- package/test/unit/lib/model/data/getBulkTransfersMojaloopResponse.json +22 -0
- package/test/unit/lib/model/data/getTransfersBackendResponse.json +34 -0
- package/test/unit/lib/model/data/getTransfersMojaloopResponse.json +17 -0
- package/test/unit/lib/model/data/mockArguments.json +131 -0
- package/test/unit/lib/model/data/mockTxnRequestsArguments.json +63 -0
- package/test/unit/lib/model/data/notificationToPayee.json +10 -0
- package/test/unit/lib/model/data/payeeParty.json +16 -0
- package/test/unit/lib/model/data/putAuthorizationsResponse.json +10 -0
- package/test/unit/lib/model/data/putQuotesResponse.json +33 -0
- package/test/unit/lib/model/data/putTransfersResponse.json +5 -0
- package/test/unit/lib/model/data/quoteResponse.json +31 -0
- package/test/unit/lib/model/data/requestToPayRequest.json +20 -0
- package/test/unit/lib/model/data/requestToPayTransferRequest.json +27 -0
- package/test/unit/lib/model/data/transactionRequestResponse.json +18 -0
- package/test/unit/lib/model/data/transferFulfil.json +8 -0
- package/test/unit/lib/model/data/transferRequest.json +26 -0
- package/test/unit/lib/model/mockedLibRequests.js +74 -0
- package/test/unit/mockLogger.js +39 -0
- package/test/unit/outboundApi/data/bulkQuoteRequest.json +28 -0
- package/test/unit/outboundApi/data/bulkTransferRequest.json +28 -0
- package/test/unit/outboundApi/data/mockBulkQuoteError.json +45 -0
- package/test/unit/outboundApi/data/mockBulkTransferError.json +48 -0
- package/test/unit/outboundApi/data/mockError.json +41 -0
- package/test/unit/outboundApi/data/mockGetPartiesError.json +4 -0
- package/test/unit/outboundApi/data/mockRequestToPayError.json +32 -0
- package/test/unit/outboundApi/data/mockRequestToPayTransferError.json +39 -0
- package/test/unit/outboundApi/data/requestToPay.json +21 -0
- package/test/unit/outboundApi/data/requestToPayTransferRequest.json +20 -0
- package/test/unit/outboundApi/data/transferRequest.json +21 -0
- package/test/unit/outboundApi/handlers.test.js +986 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**************************************************************************
|
|
2
|
+
* (C) Copyright ModusBox Inc. 2019 - All rights reserved. *
|
|
3
|
+
* *
|
|
4
|
+
* This file is made available under the terms of the license agreement *
|
|
5
|
+
* specified in the corresponding source code repository. *
|
|
6
|
+
* *
|
|
7
|
+
* ORIGINAL AUTHOR: *
|
|
8
|
+
* James Bush - james.bush@modusbox.com *
|
|
9
|
+
**************************************************************************/
|
|
10
|
+
|
|
11
|
+
const Koa = require('koa');
|
|
12
|
+
|
|
13
|
+
const assert = require('assert').strict;
|
|
14
|
+
const https = require('https');
|
|
15
|
+
const http = require('http');
|
|
16
|
+
const yaml = require('js-yaml');
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const EventEmitter = require('events');
|
|
20
|
+
|
|
21
|
+
const { WSO2Auth } = require('@mojaloop/sdk-standard-components');
|
|
22
|
+
|
|
23
|
+
const Validate = require('../lib/validate');
|
|
24
|
+
const router = require('../lib/router');
|
|
25
|
+
const handlers = require('./handlers');
|
|
26
|
+
const middlewares = require('./middlewares');
|
|
27
|
+
|
|
28
|
+
class InboundApi extends EventEmitter {
|
|
29
|
+
constructor(conf, logger, cache, validator) {
|
|
30
|
+
super({ captureExceptions: true });
|
|
31
|
+
this._conf = conf;
|
|
32
|
+
this._cache = cache;
|
|
33
|
+
this._wso2 = {
|
|
34
|
+
auth: new WSO2Auth({
|
|
35
|
+
...conf.wso2.auth,
|
|
36
|
+
logger,
|
|
37
|
+
tlsCreds: conf.mutualTLS.outboundRequests.enabled && conf.mutualTLS.outboundRequests.creds,
|
|
38
|
+
}),
|
|
39
|
+
retryWso2AuthFailureTimes: conf.wso2.requestAuthFailureRetryTimes,
|
|
40
|
+
};
|
|
41
|
+
this._wso2.auth.on('error', (msg) => {
|
|
42
|
+
this.emit('error', 'WSO2 auth error in InboundApi', msg);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (conf.validateInboundJws) {
|
|
46
|
+
this._jwsVerificationKeys = InboundApi._GetJwsKeys(conf.jwsVerificationKeysDirectory);
|
|
47
|
+
}
|
|
48
|
+
this._api = InboundApi._SetupApi({
|
|
49
|
+
conf,
|
|
50
|
+
logger,
|
|
51
|
+
validator,
|
|
52
|
+
cache,
|
|
53
|
+
jwsVerificationKeys: this._jwsVerificationKeys,
|
|
54
|
+
wso2: this._wso2,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async start() {
|
|
59
|
+
this._startJwsWatcher();
|
|
60
|
+
if (!this._conf.testingDisableWSO2AuthStart) {
|
|
61
|
+
await this._wso2.auth.start();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
stop() {
|
|
66
|
+
this._wso2.auth.stop();
|
|
67
|
+
if (this._keyWatcher) {
|
|
68
|
+
this._keyWatcher.close();
|
|
69
|
+
this._keyWatcher = null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
callback() {
|
|
74
|
+
return this._api.callback();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_startJwsWatcher() {
|
|
78
|
+
const FS_EVENT_TYPES = {
|
|
79
|
+
CHANGE: 'change',
|
|
80
|
+
RENAME: 'rename'
|
|
81
|
+
};
|
|
82
|
+
const watchHandler = async (eventType, filename) => {
|
|
83
|
+
// On most platforms, 'rename' is emitted whenever a filename appears or disappears in the directory.
|
|
84
|
+
// From: https://nodejs.org/docs/latest/api/fs.html#fs_fs_watch_filename_options_listener
|
|
85
|
+
if (path.extname(filename) !== '.pem') {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const keyName = path.basename(filename, '.pem');
|
|
89
|
+
const keyPath = path.join(this._conf.jwsVerificationKeysDirectory, filename);
|
|
90
|
+
if (eventType === FS_EVENT_TYPES.RENAME) {
|
|
91
|
+
if (fs.existsSync(keyPath)) {
|
|
92
|
+
this._jwsVerificationKeys[keyName] = await fs.promises.readFile(keyPath);
|
|
93
|
+
} else {
|
|
94
|
+
delete this._jwsVerificationKeys[keyName];
|
|
95
|
+
}
|
|
96
|
+
} else if (eventType === FS_EVENT_TYPES.CHANGE) {
|
|
97
|
+
this._jwsVerificationKeys[keyName] = await fs.promises.readFile(keyPath);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
if (this._conf.jwsVerificationKeysDirectory) {
|
|
101
|
+
this._keyWatcher = fs.watch(this._conf.jwsVerificationKeysDirectory, watchHandler);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
static _SetupApi({ conf, logger, validator, cache, jwsVerificationKeys, wso2 }) {
|
|
106
|
+
const api = new Koa();
|
|
107
|
+
|
|
108
|
+
api.use(middlewares.createErrorHandler(logger));
|
|
109
|
+
api.use(middlewares.createRequestIdGenerator());
|
|
110
|
+
api.use(middlewares.createHeaderValidator(logger));
|
|
111
|
+
if (conf.validateInboundJws) {
|
|
112
|
+
const jwsExclusions = conf.validateInboundPutPartiesJws ? [] : ['putParties'];
|
|
113
|
+
api.use(middlewares.createJwsValidator(logger, jwsVerificationKeys, jwsExclusions));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
api.use(middlewares.applyState({ cache, wso2, conf }));
|
|
117
|
+
api.use(middlewares.createLogger(logger));
|
|
118
|
+
api.use(middlewares.createRequestValidator(validator));
|
|
119
|
+
api.use(middlewares.assignFspiopIdentifier());
|
|
120
|
+
if (conf.enableTestFeatures) {
|
|
121
|
+
api.use(middlewares.cacheRequest(cache));
|
|
122
|
+
}
|
|
123
|
+
api.use(router(handlers));
|
|
124
|
+
api.use(middlewares.createResponseBodyHandler());
|
|
125
|
+
|
|
126
|
+
api.context.resourceVersions = conf.resourceVersions;
|
|
127
|
+
|
|
128
|
+
return api;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
static _GetJwsKeys(fromDir) {
|
|
132
|
+
const keys = {};
|
|
133
|
+
if (fromDir) {
|
|
134
|
+
fs.readdirSync(fromDir)
|
|
135
|
+
.filter(f => f.endsWith('.pem'))
|
|
136
|
+
.forEach(f => {
|
|
137
|
+
const keyName = path.basename(f, '.pem');
|
|
138
|
+
const keyPath = path.join(fromDir, f);
|
|
139
|
+
keys[keyName] = fs.readFileSync(keyPath);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return keys;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
class InboundServer extends EventEmitter {
|
|
147
|
+
constructor(conf, logger, cache) {
|
|
148
|
+
super({ captureExceptions: true });
|
|
149
|
+
this._conf = conf;
|
|
150
|
+
this._validator = new Validate();
|
|
151
|
+
this._logger = logger;
|
|
152
|
+
this._api = new InboundApi(
|
|
153
|
+
conf,
|
|
154
|
+
this._logger.push({ component: 'api' }),
|
|
155
|
+
cache,
|
|
156
|
+
this._validator
|
|
157
|
+
);
|
|
158
|
+
this._api.on('error', (...args) => {
|
|
159
|
+
this.emit('error', ...args);
|
|
160
|
+
});
|
|
161
|
+
this._server = this._createServer(
|
|
162
|
+
conf.mutualTLS.inboundRequests.enabled,
|
|
163
|
+
conf.mutualTLS.inboundRequests.creds,
|
|
164
|
+
this._api.callback()
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async start() {
|
|
169
|
+
assert(!this._server.listening, 'Server already listening');
|
|
170
|
+
const specPath = path.join(__dirname, 'api.yaml');
|
|
171
|
+
const apiSpecs = yaml.load(fs.readFileSync(specPath));
|
|
172
|
+
await this._validator.initialise(apiSpecs);
|
|
173
|
+
await this._api.start();
|
|
174
|
+
await new Promise((resolve) => this._server.listen(this._conf.inboundServerPort, resolve));
|
|
175
|
+
this._logger.log(`Serving inbound API on port ${this._conf.inboundServerPort}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async stop() {
|
|
179
|
+
if (this._server) {
|
|
180
|
+
await new Promise(resolve => this._server.close(resolve));
|
|
181
|
+
this._server = null;
|
|
182
|
+
}
|
|
183
|
+
if (this._api) {
|
|
184
|
+
await this._api.stop();
|
|
185
|
+
this._api = null;
|
|
186
|
+
}
|
|
187
|
+
this._logger.log('inbound shut down complete');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
_createServer(tlsEnabled, tlsCreds, handler) {
|
|
191
|
+
if (!tlsEnabled) {
|
|
192
|
+
return http.createServer(handler);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const inboundHttpsOpts = {
|
|
196
|
+
...tlsCreds,
|
|
197
|
+
requestCert: true,
|
|
198
|
+
rejectUnauthorized: true // no effect if requestCert is not true
|
|
199
|
+
};
|
|
200
|
+
return https.createServer(inboundHttpsOpts, handler);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
module.exports = InboundServer;
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
/**************************************************************************
|
|
2
|
+
* (C) Copyright ModusBox Inc. 2019 - All rights reserved. *
|
|
3
|
+
* *
|
|
4
|
+
* This file is made available under the terms of the license agreement *
|
|
5
|
+
* specified in the corresponding source code repository. *
|
|
6
|
+
* *
|
|
7
|
+
* ORIGINAL AUTHOR: *
|
|
8
|
+
* James Bush - james.bush@modusbox.com *
|
|
9
|
+
**************************************************************************/
|
|
10
|
+
|
|
11
|
+
const coBody = require('co-body');
|
|
12
|
+
|
|
13
|
+
const randomPhrase = require('../lib/randomphrase');
|
|
14
|
+
const { Jws, Errors } = require('@mojaloop/sdk-standard-components');
|
|
15
|
+
const {
|
|
16
|
+
parseAcceptHeader,
|
|
17
|
+
parseContentTypeHeader,
|
|
18
|
+
protocolVersionsMap
|
|
19
|
+
} = require('@mojaloop/central-services-shared').Util.HeaderValidation;
|
|
20
|
+
const {
|
|
21
|
+
defaultProtocolResources,
|
|
22
|
+
defaultProtocolVersions,
|
|
23
|
+
errorMessages
|
|
24
|
+
} = require('@mojaloop/central-services-shared').Util.Hapi.FSPIOPHeaderValidation;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Log raw to console as a last resort
|
|
28
|
+
* @return {Function}
|
|
29
|
+
*/
|
|
30
|
+
const createErrorHandler = (logger) => async (ctx, next) => {
|
|
31
|
+
try {
|
|
32
|
+
await next();
|
|
33
|
+
} catch (err) {
|
|
34
|
+
// TODO: return a 500 here if the response has not already been sent?
|
|
35
|
+
logger.push({ err }).log('Error caught in catchall');
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* tag each incoming request with the FSPIOP identifier from it's path or body
|
|
42
|
+
* @return {Function}
|
|
43
|
+
*/
|
|
44
|
+
const assignFspiopIdentifier = () => async (ctx, next) => {
|
|
45
|
+
const getters = {
|
|
46
|
+
'/authorizations/{ID}': {
|
|
47
|
+
get: () => ctx.state.path.params.ID,
|
|
48
|
+
put: () => ctx.state.path.params.ID,
|
|
49
|
+
},
|
|
50
|
+
'/bulkQuotes': {
|
|
51
|
+
post: () => ctx.request.body.bulkQuoteId,
|
|
52
|
+
},
|
|
53
|
+
'/bulkQuotes/{ID}': {
|
|
54
|
+
get: () => ctx.state.path.params.ID,
|
|
55
|
+
put: () => ctx.state.path.params.ID,
|
|
56
|
+
},
|
|
57
|
+
'/bulkQuotes/{ID}/error': {
|
|
58
|
+
put: () => ctx.state.path.params.ID,
|
|
59
|
+
},
|
|
60
|
+
'/bulkTransfers': {
|
|
61
|
+
post: () => ctx.request.body.bulkTransferId,
|
|
62
|
+
},
|
|
63
|
+
'/bulkTransfers/{ID}': {
|
|
64
|
+
get: () => ctx.state.path.params.ID,
|
|
65
|
+
put: () => ctx.state.path.params.ID,
|
|
66
|
+
},
|
|
67
|
+
'/bulkTransfers/{ID}/error': {
|
|
68
|
+
put: () => ctx.state.path.params.ID,
|
|
69
|
+
},
|
|
70
|
+
'/participants/{ID}': {
|
|
71
|
+
put: () => ctx.state.path.params.ID,
|
|
72
|
+
},
|
|
73
|
+
'/participants/{Type}/{ID}': {
|
|
74
|
+
get: () => ctx.state.path.params.ID,
|
|
75
|
+
},
|
|
76
|
+
'/participants/{Type}/{ID}/{SubId}': {
|
|
77
|
+
get: () => ctx.state.path.params.ID,
|
|
78
|
+
put: () => ctx.state.path.params.ID,
|
|
79
|
+
},
|
|
80
|
+
'/participants/{Type}/{ID}/{SubId}/error': {
|
|
81
|
+
put: () => ctx.state.path.params.ID,
|
|
82
|
+
},
|
|
83
|
+
'/participants/{ID}/error': {
|
|
84
|
+
put: () => ctx.state.path.params.ID,
|
|
85
|
+
},
|
|
86
|
+
'/parties/{Type}/{ID}': {
|
|
87
|
+
get: () => ctx.state.path.params.ID,
|
|
88
|
+
put: () => ctx.state.path.params.ID,
|
|
89
|
+
},
|
|
90
|
+
'/parties/{Type}/{ID}/{SubId}': {
|
|
91
|
+
get: () => ctx.state.path.params.ID,
|
|
92
|
+
put: () => ctx.state.path.params.ID,
|
|
93
|
+
},
|
|
94
|
+
'/parties/{Type}/{ID}/error': {
|
|
95
|
+
put: () => ctx.state.path.params.ID,
|
|
96
|
+
},
|
|
97
|
+
'/parties/{Type}/{ID}/{SubId}/error': {
|
|
98
|
+
put: () => ctx.state.path.params.ID,
|
|
99
|
+
},
|
|
100
|
+
'/quotes': {
|
|
101
|
+
post: () => ctx.request.body.quoteId,
|
|
102
|
+
},
|
|
103
|
+
'/quotes/{ID}': {
|
|
104
|
+
put: () => ctx.state.path.params.ID,
|
|
105
|
+
},
|
|
106
|
+
'/quotes/{ID}/error': {
|
|
107
|
+
put: () => ctx.state.path.params.ID,
|
|
108
|
+
},
|
|
109
|
+
'/transfers': {
|
|
110
|
+
post: () => ctx.request.body.transferId,
|
|
111
|
+
},
|
|
112
|
+
'/transfers/{ID}': {
|
|
113
|
+
get: () => ctx.state.path.params.ID,
|
|
114
|
+
put: () => ctx.state.path.params.ID,
|
|
115
|
+
patch: () => ctx.state.path.params.ID,
|
|
116
|
+
},
|
|
117
|
+
'/transfers/{ID}/error': {
|
|
118
|
+
put: () => ctx.state.path.params.ID,
|
|
119
|
+
},
|
|
120
|
+
'/transactionRequests': {
|
|
121
|
+
post: () => ctx.request.body.transactionRequestId,
|
|
122
|
+
},
|
|
123
|
+
'/transactionRequests/{ID}': {
|
|
124
|
+
put: () => ctx.state.path.params.ID,
|
|
125
|
+
}
|
|
126
|
+
}[ctx.state.path.pattern];
|
|
127
|
+
if (getters) {
|
|
128
|
+
const getter = getters[ctx.method.toLowerCase()];
|
|
129
|
+
if (getter) {
|
|
130
|
+
ctx.state.fspiopId = getter(ctx.request);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
await next();
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* cache incoming requests and callbacks
|
|
139
|
+
* @return {Function}
|
|
140
|
+
*/
|
|
141
|
+
const cacheRequest = (cache) => async (ctx, next) => {
|
|
142
|
+
if (ctx.state.fspiopId) {
|
|
143
|
+
const req = {
|
|
144
|
+
headers: ctx.request.headers,
|
|
145
|
+
data: ctx.request.body,
|
|
146
|
+
};
|
|
147
|
+
const prefix = ctx.method.toLowerCase() === 'put' ? cache.CALLBACK_PREFIX : cache.REQUEST_PREFIX;
|
|
148
|
+
const res = await cache.set(`${prefix}${ctx.state.fspiopId}`, req);
|
|
149
|
+
ctx.state.logger.push({ res }).log('Caching request');
|
|
150
|
+
}
|
|
151
|
+
await next();
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* tag each incoming request with a unique identifier
|
|
157
|
+
* @return {Function}
|
|
158
|
+
*/
|
|
159
|
+
const createRequestIdGenerator = () => async (ctx, next) => {
|
|
160
|
+
ctx.request.id = randomPhrase();
|
|
161
|
+
await next();
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Deal with mojaloop API content type headers, treat as JSON
|
|
167
|
+
* This is based on the Hapi header validation plugin found in `central-services-shared`
|
|
168
|
+
* Since `sdk-scheme-adapter` uses Koa instead of Hapi we convert the plugin
|
|
169
|
+
* into middleware
|
|
170
|
+
* @param logger
|
|
171
|
+
* @return {Function}
|
|
172
|
+
*/
|
|
173
|
+
//
|
|
174
|
+
const createHeaderValidator = (logger) => async (
|
|
175
|
+
ctx,
|
|
176
|
+
next,
|
|
177
|
+
resources = defaultProtocolResources,
|
|
178
|
+
supportedProtocolVersions = defaultProtocolVersions
|
|
179
|
+
) => {
|
|
180
|
+
const request = ctx.request;
|
|
181
|
+
|
|
182
|
+
// First, extract the resource type from the path
|
|
183
|
+
const resource = request.path.replace(/^\//, '').split('/')[0];
|
|
184
|
+
|
|
185
|
+
// Only validate requests for the requested resources
|
|
186
|
+
if (!resources.includes(resource)) {
|
|
187
|
+
return await next();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Always validate the accept header for a get request, or optionally if it has been
|
|
191
|
+
// supplied
|
|
192
|
+
if (request.method.toLowerCase() === 'get' || request.headers.accept) {
|
|
193
|
+
if (request.headers.accept === undefined) {
|
|
194
|
+
ctx.response.status = Errors.MojaloopApiErrorCodes.MISSING_ELEMENT.httpStatusCode;
|
|
195
|
+
ctx.response.body = new Errors.MojaloopFSPIOPError(
|
|
196
|
+
Errors.MojaloopApiErrorObjectFromCode(Errors.MojaloopApiErrorCodes.MISSING_ELEMENT.httpStatusCode),
|
|
197
|
+
errorMessages.REQUIRE_ACCEPT_HEADER,
|
|
198
|
+
null,
|
|
199
|
+
Errors.MojaloopApiErrorCodes.MISSING_ELEMENT
|
|
200
|
+
).toApiErrorObject();
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const accept = parseAcceptHeader(resource, request.headers.accept);
|
|
204
|
+
if (!accept.valid) {
|
|
205
|
+
ctx.response.status = Errors.MojaloopApiErrorCodes.MALFORMED_SYNTAX.httpStatusCode;
|
|
206
|
+
ctx.response.body = new Errors.MojaloopFSPIOPError(
|
|
207
|
+
Errors.MojaloopApiErrorObjectFromCode(Errors.MojaloopApiErrorCodes.MALFORMED_SYNTAX.httpStatusCode),
|
|
208
|
+
errorMessages.INVALID_ACCEPT_HEADER,
|
|
209
|
+
null,
|
|
210
|
+
Errors.MojaloopApiErrorCodes.MALFORMED_SYNTAX
|
|
211
|
+
).toApiErrorObject();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (!supportedProtocolVersions.some(supportedVer => accept.versions.has(supportedVer))) {
|
|
215
|
+
ctx.response.status = Errors.MojaloopApiErrorCodes.UNACCEPTABLE_VERSION.httpStatusCode;
|
|
216
|
+
ctx.response.body = new Errors.MojaloopFSPIOPError(
|
|
217
|
+
Errors.MojaloopApiErrorObjectFromCode(Errors.MojaloopApiErrorCodes.UNACCEPTABLE_VERSION.httpStatusCode),
|
|
218
|
+
errorMessages.REQUESTED_VERSION_NOT_SUPPORTED,
|
|
219
|
+
null,
|
|
220
|
+
Errors.MojaloopApiErrorCodes.UNACCEPTABLE_VERSION,
|
|
221
|
+
protocolVersionsMap
|
|
222
|
+
).toApiErrorObject();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Always validate the content-type header
|
|
228
|
+
if (request.headers['content-type'] === undefined) {
|
|
229
|
+
ctx.response.status = Errors.MojaloopApiErrorCodes.MISSING_ELEMENT.httpStatusCode;
|
|
230
|
+
ctx.response.body = new Errors.MojaloopFSPIOPError(
|
|
231
|
+
Errors.MojaloopApiErrorObjectFromCode(Errors.MojaloopApiErrorCodes.MISSING_ELEMENT.httpStatusCode),
|
|
232
|
+
errorMessages.REQUIRE_CONTENT_TYPE_HEADER,
|
|
233
|
+
null,
|
|
234
|
+
Errors.MojaloopApiErrorCodes.MISSING_ELEMENT,
|
|
235
|
+
protocolVersionsMap
|
|
236
|
+
).toApiErrorObject();
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const contentType = parseContentTypeHeader(resource, request.headers['content-type']);
|
|
241
|
+
if (!contentType.valid) {
|
|
242
|
+
ctx.response.status = Errors.MojaloopApiErrorCodes.MALFORMED_SYNTAX.httpStatusCode;
|
|
243
|
+
ctx.response.body = new Errors.MojaloopFSPIOPError(
|
|
244
|
+
Errors.MojaloopApiErrorObjectFromCode(Errors.MojaloopApiErrorCodes.MALFORMED_SYNTAX.httpStatusCode),
|
|
245
|
+
errorMessages.INVALID_CONTENT_TYPE_HEADER,
|
|
246
|
+
null,
|
|
247
|
+
Errors.MojaloopApiErrorCodes.MALFORMED_SYNTAX
|
|
248
|
+
).toApiErrorObject();
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (!supportedProtocolVersions.includes(contentType.version)) {
|
|
252
|
+
ctx.response.status = Errors.MojaloopApiErrorCodes.UNACCEPTABLE_VERSION.httpStatusCode;
|
|
253
|
+
ctx.response.body = new Errors.MojaloopFSPIOPError(
|
|
254
|
+
Errors.MojaloopApiErrorObjectFromCode(Errors.MojaloopApiErrorCodes.UNACCEPTABLE_VERSION.httpStatusCode),
|
|
255
|
+
errorMessages.SUPPLIED_VERSION_NOT_SUPPORTED,
|
|
256
|
+
null,
|
|
257
|
+
Errors.MojaloopApiErrorCodes.UNACCEPTABLE_VERSION,
|
|
258
|
+
protocolVersionsMap
|
|
259
|
+
).toApiErrorObject();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
ctx.request.body = await coBody.json(ctx.req);
|
|
265
|
+
}
|
|
266
|
+
catch(err) {
|
|
267
|
+
// error parsing body
|
|
268
|
+
logger.push({ err }).log('Error parsing body');
|
|
269
|
+
ctx.response.status = Errors.MojaloopApiErrorCodes.MALFORMED_SYNTAX.httpStatusCode;
|
|
270
|
+
ctx.response.body = new Errors.MojaloopFSPIOPError(err, err.message, null,
|
|
271
|
+
Errors.MojaloopApiErrorCodes.MALFORMED_SYNTAX).toApiErrorObject();
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
await next();
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
*
|
|
280
|
+
* @param logger
|
|
281
|
+
* @param keys {Object} JWS verification keys
|
|
282
|
+
* @param exclusions {string[]} Requests to exclude from validation. Possible values: "putParties"
|
|
283
|
+
* @return {Function}
|
|
284
|
+
*/
|
|
285
|
+
const createJwsValidator = (logger, keys, exclusions) => {
|
|
286
|
+
const jwsValidator = new Jws.validator({
|
|
287
|
+
logger: logger,
|
|
288
|
+
validationKeys: keys,
|
|
289
|
+
});
|
|
290
|
+
// JWS validation for incoming requests
|
|
291
|
+
return async (ctx, next) => {
|
|
292
|
+
try {
|
|
293
|
+
// we skip inbound JWS validation on PUT /parties requests if our config flag
|
|
294
|
+
// is set to do so.
|
|
295
|
+
if (exclusions.includes('putParties')
|
|
296
|
+
&& ctx.request.method === 'PUT'
|
|
297
|
+
&& ctx.request.path.startsWith('/parties/')) {
|
|
298
|
+
logger.log('Skipping jws validation on put parties. config flag is set');
|
|
299
|
+
return await next();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// we dont check signatures on GET requests
|
|
303
|
+
// todo: validate this requirement. No state is mutated by GETs but
|
|
304
|
+
// there are potential security issues if message origin is used to
|
|
305
|
+
// determine permission sets i.e. what is "readable"
|
|
306
|
+
if(ctx.request.method !== 'GET') {
|
|
307
|
+
logger.push({ request: ctx.request, body: ctx.request.body }).log('Validating JWS');
|
|
308
|
+
jwsValidator.validate(ctx.request, logger);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
}
|
|
312
|
+
catch(err) {
|
|
313
|
+
logger.push({ err }).log('Inbound request failed JWS validation');
|
|
314
|
+
|
|
315
|
+
ctx.response.status = 400;
|
|
316
|
+
ctx.response.body = new Errors.MojaloopFSPIOPError(
|
|
317
|
+
err, err.message, null, Errors.MojaloopApiErrorCodes.INVALID_SIGNATURE
|
|
318
|
+
).toApiErrorObject();
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
await next();
|
|
322
|
+
};
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Add request state.
|
|
328
|
+
* TODO: this should probably be app context:
|
|
329
|
+
* https://github.com/koajs/koa/blob/master/docs/api/index.md#appcontext
|
|
330
|
+
* @param sharedState
|
|
331
|
+
* @return {Function}
|
|
332
|
+
*/
|
|
333
|
+
const applyState = (sharedState) => async (ctx, next) => {
|
|
334
|
+
Object.assign(ctx.state, {
|
|
335
|
+
...sharedState,
|
|
336
|
+
conf: {
|
|
337
|
+
...sharedState.conf,
|
|
338
|
+
tls: sharedState?.conf?.mutualTLS?.outboundRequests,
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
await next();
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Add a log context for each request, log the receipt and handling thereof
|
|
347
|
+
* @param logger
|
|
348
|
+
* @return {Function}
|
|
349
|
+
*/
|
|
350
|
+
const createLogger = (logger) => async (ctx, next) => {
|
|
351
|
+
ctx.state.logger = logger.push({ request: {
|
|
352
|
+
id: ctx.request.id,
|
|
353
|
+
path: ctx.path,
|
|
354
|
+
method: ctx.method
|
|
355
|
+
}});
|
|
356
|
+
ctx.state.logger.push({ body: ctx.request.body }).log('Request received');
|
|
357
|
+
try {
|
|
358
|
+
await next();
|
|
359
|
+
} catch (err) {
|
|
360
|
+
ctx.state.logger.push(err).log('Error');
|
|
361
|
+
}
|
|
362
|
+
await ctx.state.logger.log('Request processed');
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Add validation for each inbound request
|
|
368
|
+
* @param validator
|
|
369
|
+
* @return {Function}
|
|
370
|
+
*/
|
|
371
|
+
const createRequestValidator = (validator) => async (ctx, next) => {
|
|
372
|
+
ctx.state.logger.log('Validating request');
|
|
373
|
+
try {
|
|
374
|
+
ctx.state.path = validator.validateRequest(ctx, ctx.state.logger);
|
|
375
|
+
ctx.state.logger.log('Request passed validation');
|
|
376
|
+
await next();
|
|
377
|
+
} catch (err) {
|
|
378
|
+
ctx.state.logger.push({ err }).log('Request failed validation.');
|
|
379
|
+
// send a mojaloop spec error response
|
|
380
|
+
ctx.response.status = err.httpStatusCode || 400;
|
|
381
|
+
|
|
382
|
+
if(err instanceof Errors.MojaloopFSPIOPError) {
|
|
383
|
+
// this is a specific mojaloop spec error
|
|
384
|
+
ctx.response.body = err.toApiErrorObject();
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// generic mojaloop spec validation error
|
|
389
|
+
ctx.response.body = {
|
|
390
|
+
errorInformation: {
|
|
391
|
+
errorCode: '3100',
|
|
392
|
+
errorDescription: `${err.dataPath ? err.dataPath + ' ' : ''}${err.message}`
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Override Koa's default behaviour of returning the status code as text in the body. If we
|
|
401
|
+
* haven't defined the body, we want it empty. Note that if setting this to null, Koa appears
|
|
402
|
+
* to override the status code with a 204. This is correct behaviour in the sense that the
|
|
403
|
+
* status code correctly corresponds to the content (none) but unfortunately the Mojaloop API
|
|
404
|
+
* does not respect this convention and requires a 200.
|
|
405
|
+
* @return {Function}
|
|
406
|
+
*/
|
|
407
|
+
const createResponseBodyHandler = () => async (ctx, next) => {
|
|
408
|
+
if (ctx.response.body === undefined) {
|
|
409
|
+
ctx.response.body = '';
|
|
410
|
+
}
|
|
411
|
+
return await next();
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
module.exports = {
|
|
416
|
+
applyState,
|
|
417
|
+
assignFspiopIdentifier,
|
|
418
|
+
cacheRequest,
|
|
419
|
+
createErrorHandler,
|
|
420
|
+
createRequestIdGenerator,
|
|
421
|
+
createHeaderValidator,
|
|
422
|
+
createJwsValidator,
|
|
423
|
+
createLogger,
|
|
424
|
+
createRequestValidator,
|
|
425
|
+
createResponseBodyHandler,
|
|
426
|
+
};
|