ml-testing-toolkit 18.13.0
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/.dockerignore +10 -0
- package/.grype.yaml +16 -0
- package/.ncurc.yaml +9 -0
- package/.nvmrc +1 -0
- package/.versionrc.js +16 -0
- package/CHANGELOG.md +504 -0
- package/CODEOWNERS +30 -0
- package/Dockerfile +42 -0
- package/Dockerfile-newman +13 -0
- package/LICENSE.md +9 -0
- package/README.md +119 -0
- package/assets/diagrams/architectural/architectural-diagram.svg +3 -0
- package/assets/diagrams/flow/flow-diagram.svg +3 -0
- package/assets/images/Sample-Response-Failure.png +0 -0
- package/assets/images/Screenshot 2020-04-16 at 22.58.04.png +0 -0
- package/assets/images/TLS-Enabled-on-Environment.png +0 -0
- package/assets/images/adapter-mutual-tls-enabled.png +0 -0
- package/assets/images/add-additional-input-values.png +0 -0
- package/assets/images/add-condition-button.png +0 -0
- package/assets/images/add-new-assertion.png +0 -0
- package/assets/images/add-new-input-value.png +0 -0
- package/assets/images/add-new-input-variable.png +0 -0
- package/assets/images/additional-transfers.png +0 -0
- package/assets/images/api-provisioning-add-new-api-confirmation.png +0 -0
- package/assets/images/api-provisioning-file-input-window.png +0 -0
- package/assets/images/api-provisioning-list-apis-view.png +0 -0
- package/assets/images/api-provisioning-menu-item.png +0 -0
- package/assets/images/apply_and_restart.jpg +0 -0
- package/assets/images/assess-request-or-response.png +0 -0
- package/assets/images/assess-response-equation-save.png +0 -0
- package/assets/images/assess-response-equation.png +0 -0
- package/assets/images/assess-response-status.png +0 -0
- package/assets/images/building-new-rules-file.png +0 -0
- package/assets/images/callback-rules-screen.png +0 -0
- package/assets/images/configurable-parameter-assertion.png +0 -0
- package/assets/images/configurable-parameter-currency.png +0 -0
- package/assets/images/configurable-parameter.png +0 -0
- package/assets/images/connection-manager-ui-opening.png +0 -0
- package/assets/images/create-inbound-user-simulator.png +0 -0
- package/assets/images/creating-new-rule-file.png +0 -0
- package/assets/images/dfsp-client-cacert.png +0 -0
- package/assets/images/dfsp-client-submit.png +0 -0
- package/assets/images/dfsp-client.png +0 -0
- package/assets/images/dfsp-p2p-happy-path.png +0 -0
- package/assets/images/dfsp-server-cacert.png +0 -0
- package/assets/images/dfsp-server-cert.png +0 -0
- package/assets/images/download-report.png +0 -0
- package/assets/images/drive_have_not_been_shared.jpg +0 -0
- package/assets/images/event-response-options.png +0 -0
- package/assets/images/expand-monitoring-messages.png +0 -0
- package/assets/images/fixed-response-sample.png +0 -0
- package/assets/images/header-selection.png +0 -0
- package/assets/images/heap_error_windows.jpg +0 -0
- package/assets/images/hosted-mode-docker-compose-intro.png +0 -0
- package/assets/images/hub-client-cert.png +0 -0
- package/assets/images/import-template.png +0 -0
- package/assets/images/inbound-requests-environment.png +0 -0
- package/assets/images/inbound-requests-scripts.png +0 -0
- package/assets/images/jws-certificate-submit.png +0 -0
- package/assets/images/jws-certificate.png +0 -0
- package/assets/images/jws-certs-keys.png +0 -0
- package/assets/images/jws-hub-certs-keys.png +0 -0
- package/assets/images/local-enable-jws-publickey.png +0 -0
- package/assets/images/local-mutual-tls-enabled.png +0 -0
- package/assets/images/local_drives_to_be_available.jpg +0 -0
- package/assets/images/mcm-environment-opening.png +0 -0
- package/assets/images/menu-items.png +0 -0
- package/assets/images/mock-response-sample.png +0 -0
- package/assets/images/monitoring-initial-state.png +0 -0
- package/assets/images/monitoring-messages.png +0 -0
- package/assets/images/new-empty-assertion.png +0 -0
- package/assets/images/opened-imported-template.png +0 -0
- package/assets/images/opening-default-settings.png +0 -0
- package/assets/images/opening-sync-response-rules.png +0 -0
- package/assets/images/opening-view.png +0 -0
- package/assets/images/outbound-display-opening-hub.png +0 -0
- package/assets/images/outbound-display-opening.png +0 -0
- package/assets/images/override-with-environment-variable.png +0 -0
- package/assets/images/populate-with-sample-body.png +0 -0
- package/assets/images/resource-selection.png +0 -0
- package/assets/images/rule-builder-select-api.png +0 -0
- package/assets/images/sample-condition-add-configurable-params.png +0 -0
- package/assets/images/sample-condition.png +0 -0
- package/assets/images/sample-editor.png +0 -0
- package/assets/images/sample-request.png +0 -0
- package/assets/images/sample-test-assertion.png +0 -0
- package/assets/images/send-transfer.png +0 -0
- package/assets/images/sending-single-test-case-1.png +0 -0
- package/assets/images/sending-single-test-case-2.png +0 -0
- package/assets/images/sending-test-cases.png +0 -0
- package/assets/images/server-certificates-submitted.png +0 -0
- package/assets/images/simulator-response.png +0 -0
- package/assets/images/simulator-scheme-adapter-endpoint.png +0 -0
- package/assets/images/summarized-view-of-rule.png +0 -0
- package/assets/images/template-window.png +0 -0
- package/assets/images/test-case-editor-console-log.png +0 -0
- package/assets/images/test-case-editor-environment-state.png +0 -0
- package/assets/images/test-case-editor-scripts.png +0 -0
- package/assets/images/test-case-editor.png +0 -0
- package/assets/images/testcase-definition-download.png +0 -0
- package/assets/images/testcase-definition-edit-meta-info.png +0 -0
- package/assets/images/testing-toolkit-mojaloop-testing-toolkit-endpoint.png +0 -0
- package/assets/images/tls-hub-certs-keys.png +0 -0
- package/assets/images/tls-jws-enabled-on-environment.png +0 -0
- package/assets/images/updated-sample-body-data.png +0 -0
- package/assets/images/using-configurable-parameter.png +0 -0
- package/assets/images/validation-rules-screen.png +0 -0
- package/assets/images/view-response.png +0 -0
- package/audit-ci.jsonc +7 -0
- package/connection-manager/docker-compose.yml +55 -0
- package/database/docker-compose.yml +16 -0
- package/docker/hosted-mode/docker-compose.yaml +107 -0
- package/docker/hosted-mode/keycloak/keycloak-realm.json +2298 -0
- package/docker/hosted-mode/mongo-init.sh +1 -0
- package/docker/hosted-mode-tls/docker-compose.yaml +171 -0
- package/docker/hosted-mode-tls/keycloak/keycloak-realm.json +2298 -0
- package/docker/hosted-mode-tls/mongo-init.sh +1 -0
- package/docker-compose.yml +62 -0
- package/documents/Mojaloop-Testing-Toolkit.md +296 -0
- package/documents/RULES_ENGINE.md +403 -0
- package/documents/User-Guide-API-Provisioning.md +121 -0
- package/documents/User-Guide-CLI.md +218 -0
- package/documents/User-Guide-Connection-Manager.md +282 -0
- package/documents/User-Guide-Frequently-Asked-Questions.md +39 -0
- package/documents/User-Guide-Hosted-Mode-Docker-Compose.md +110 -0
- package/documents/User-Guide-Installation.md +163 -0
- package/documents/User-Guide-Mojaloop-Testing-Toolkit.md +642 -0
- package/documents/User-Guide-OAuth-Server-Deployment.md +283 -0
- package/documents/User-Guide-Onboarding-DFSP.md +197 -0
- package/documents/User-Guide-Onboarding-HUB.md +191 -0
- package/documents/User-Guide.md +53 -0
- package/examples/collections/dfsp/p2p_failed_tests.json +7161 -0
- package/examples/collections/dfsp/p2p_fx_happy_path.json +502 -0
- package/examples/collections/dfsp/p2p_happy_path.json +350 -0
- package/examples/collections/dfsp/p2p_happy_path_extended.json +6106 -0
- package/examples/collections/dfsp/p2p_happy_path_jws.json +511 -0
- package/examples/collections/dfsp/p2p_payee_assertions_websocket.json +441 -0
- package/examples/collections/dfsp/sample.json +5029 -0
- package/examples/collections/dfsp/transaction_request_service.json +240 -0
- package/examples/collections/fxp/FXP.json +264 -0
- package/examples/collections/fxp/SDK_backend.json +98 -0
- package/examples/collections/fxp/SDK_outbound.json +163 -0
- package/examples/collections/hub/hub_01_p2p_happy_path/hub_p2p_receive_quote.json +400 -0
- package/examples/collections/hub/hub_01_p2p_happy_path/hub_p2p_send_quote.json +395 -0
- package/examples/collections/hub/hub_02_block_transfer/hub_block_transfer.json +393 -0
- package/examples/collections/hub/hub_03_funds_in_out/hub_funds_in.json +224 -0
- package/examples/collections/hub/hub_03_funds_in_out/hub_funds_out.json +780 -0
- package/examples/collections/hub/hub_04_settlements/hub_settlements.json +3138 -0
- package/examples/collections/hub/hub_05_transfer_negative_scenarios/hub_transfer_negative_payee_abort.json +475 -0
- package/examples/collections/hub/hub_05_transfer_negative_scenarios/hub_transfer_negative_payee_invalid_fulfillment.json +370 -0
- package/examples/collections/hub/hub_05_transfer_negative_scenarios/hub_transfer_negative_transfer_timeout.json +262 -0
- package/examples/collections/hub/hub_06_transaction_requests_service/hub_trs_authorizations.json +117 -0
- package/examples/collections/hub/hub_06_transaction_requests_service/hub_trs_error_framework.json +591 -0
- package/examples/collections/hub/hub_06_transaction_requests_service/hub_trs_received_state.json +379 -0
- package/examples/collections/hub/hub_06_transaction_requests_service/hub_trs_reject_state.json +361 -0
- package/examples/collections/hub/hub_07_quoting_service.json +525 -0
- package/examples/collections/hub/hub_08_participant_inactive_stop_transfers.json +706 -0
- package/examples/collections/hub/hub_09_duplicate_handling_transfers.json +1377 -0
- package/examples/collections/hub/hub_10_on_us_transfers.json +245 -0
- package/examples/collections/hub/hub_11_accented_and_spl_chars.json +629 -0
- package/examples/collections/hub/hub_12_fspiop_version_1.1.json +646 -0
- package/examples/collections/hub/hub_13_bulk_transfers.json +1857 -0
- package/examples/collections/iso20022/self_referencing_iso20022.json +926 -0
- package/examples/collections/provisioning/testingtoolkitdfsp.json +904 -0
- package/examples/environments/dfsp_local_environment.json +46 -0
- package/examples/environments/hub_local_environment.json +57 -0
- package/jest.config.js +17 -0
- package/package.json +199 -0
- package/sbom-v18.12.4.csv +1553 -0
- package/secrets/keygen.sh +5 -0
- package/secrets/privatekey.pem +27 -0
- package/secrets/publickey.cer +21 -0
- package/secrets/tls/01.pem +132 -0
- package/secrets/tls/createSecrets.sh +20 -0
- package/secrets/tls/hub_client.csr +32 -0
- package/secrets/tls/hub_client_cacert.pem +35 -0
- package/secrets/tls/hub_client_cakey.pem +52 -0
- package/secrets/tls/hub_client_key.key +52 -0
- package/secrets/tls/hub_server.csr +31 -0
- package/secrets/tls/hub_server_cacert.pem +35 -0
- package/secrets/tls/hub_server_cakey.pem +52 -0
- package/secrets/tls/hub_server_cert.pem +132 -0
- package/secrets/tls/hub_server_key.key +52 -0
- package/secrets/tls/index.txt +1 -0
- package/secrets/tls/index.txt.attr +1 -0
- package/secrets/tls/openssl-client.cnf +36 -0
- package/secrets/tls/openssl-clientca.cnf +71 -0
- package/secrets/tls/openssl-server.cnf +39 -0
- package/secrets/tls/openssl-serverca.cnf +71 -0
- package/secrets/tls/serial.txt +1 -0
- package/spec_files/api_definitions/als_admin_1.1/api_spec.yaml +804 -0
- package/spec_files/api_definitions/central_admin_1.0/api_spec.yaml +1850 -0
- package/spec_files/api_definitions/central_admin_1.0/response_map.json +96 -0
- package/spec_files/api_definitions/central_admin_old_9.3/api_spec.yaml +2467 -0
- package/spec_files/api_definitions/central_admin_old_9.3/response_map.json +96 -0
- package/spec_files/api_definitions/fspiop_1.0/api_spec.yaml +4187 -0
- package/spec_files/api_definitions/fspiop_1.0/callback_map.json +568 -0
- package/spec_files/api_definitions/fspiop_1.0/mockRef.json +79 -0
- package/spec_files/api_definitions/fspiop_1.0/trigger_templates/transaction_request_followup.json +126 -0
- package/spec_files/api_definitions/fspiop_1.0/trigger_templates/transaction_request_followup_quotes_only.json +97 -0
- package/spec_files/api_definitions/fspiop_1.1/api_spec.yaml +3778 -0
- package/spec_files/api_definitions/fspiop_1.1/callback_map.json +568 -0
- package/spec_files/api_definitions/fspiop_1.1/mockRef.json +79 -0
- package/spec_files/api_definitions/fspiop_1.1/trigger_templates/transaction_request_followup.json +125 -0
- package/spec_files/api_definitions/fspiop_2.0/api_spec.yaml +4839 -0
- package/spec_files/api_definitions/fspiop_2.0/callback_map.json +716 -0
- package/spec_files/api_definitions/fspiop_2.0/mockRef.json +79 -0
- package/spec_files/api_definitions/fspiop_2.0/trigger_templates/transaction_request_followup.json +125 -0
- package/spec_files/api_definitions/fspiop_2.0_iso20022/api_spec.yaml +8331 -0
- package/spec_files/api_definitions/fspiop_2.0_iso20022/callback_map.json +508 -0
- package/spec_files/api_definitions/fspiop_2.0_iso20022/mockRef.json +66 -0
- package/spec_files/api_definitions/fx-api_2.0/api_spec.yaml +1768 -0
- package/spec_files/api_definitions/fx-api_2.0/callback_map.json +188 -0
- package/spec_files/api_definitions/fx-api_2.0/mockRef.json +83 -0
- package/spec_files/api_definitions/mojaloop_sdk_outbound_scheme_adapter_1.0/api_spec.yaml +2612 -0
- package/spec_files/api_definitions/mojaloop_sdk_outbound_scheme_adapter_1.0/mockRef.json +22 -0
- package/spec_files/api_definitions/mojaloop_sdk_outbound_scheme_adapter_1.0/response_map.json +35 -0
- package/spec_files/api_definitions/mojaloop_simulator_0.1/api_spec.yaml +225 -0
- package/spec_files/api_definitions/mojaloop_simulator_sim_1.4/api_spec.yaml +1087 -0
- package/spec_files/api_definitions/mojaloop_simulator_sim_1.4/mockRef.json +75 -0
- package/spec_files/api_definitions/mojaloop_simulator_sim_1.4/response_map.json +55 -0
- package/spec_files/api_definitions/payment_manager_1.4/api_spec.yaml +1389 -0
- package/spec_files/api_definitions/sdk-scheme-adapter-backend-v2_1_0-openapi3-snippets_2.1/api_spec.yaml +2834 -0
- package/spec_files/api_definitions/sdk-scheme-adapter-outbound-v2_1_0-openapi3-snippets_2.1/api_spec.yaml +3449 -0
- package/spec_files/api_definitions/settlements_1.0/api_spec.yaml +983 -0
- package/spec_files/api_definitions/settlements_1.0/mockRef.json +38 -0
- package/spec_files/api_definitions/settlements_1.0/response_map.json +34 -0
- package/spec_files/api_definitions/settlements_2.0/api_spec.yaml +1001 -0
- package/spec_files/api_definitions/settlements_2.0/mockRef.json +38 -0
- package/spec_files/api_definitions/settlements_2.0/response_map.json +34 -0
- package/spec_files/api_definitions/thirdparty_sdk_outbound_0.1/api_spec.yaml +2139 -0
- package/spec_files/reports/templates/newman/html_template.html +1202 -0
- package/spec_files/reports/templates/newman/pdf_template.html +790 -0
- package/spec_files/reports/templates/testcase_definition/table_view.html +1602 -0
- package/spec_files/rules_callback/config.json +3 -0
- package/spec_files/rules_callback/default.json +2698 -0
- package/spec_files/rules_callback/p2p-limit.json +129 -0
- package/spec_files/rules_forward/config.json +3 -0
- package/spec_files/rules_forward/default.json +482 -0
- package/spec_files/rules_response/config.json +3 -0
- package/spec_files/rules_response/default.json +295 -0
- package/spec_files/rules_validation/config.json +3 -0
- package/spec_files/rules_validation/default.json +1 -0
- package/spec_files/rules_validation/p2p-limit.json +55 -0
- package/spec_files/system_config.json +175 -0
- package/spec_files/user_config.json +109 -0
- package/src/index.js +67 -0
- package/src/lib/MyEventEmitter.js +54 -0
- package/src/lib/api-management.js +143 -0
- package/src/lib/api-routes/config.js +83 -0
- package/src/lib/api-routes/history.js +139 -0
- package/src/lib/api-routes/keycloak.js +54 -0
- package/src/lib/api-routes/longpolling.js +70 -0
- package/src/lib/api-routes/oauth2.js +149 -0
- package/src/lib/api-routes/objectstore.js +53 -0
- package/src/lib/api-routes/openapi.js +224 -0
- package/src/lib/api-routes/outbound.js +134 -0
- package/src/lib/api-routes/reports.js +72 -0
- package/src/lib/api-routes/rules.js +356 -0
- package/src/lib/api-routes/samples.js +92 -0
- package/src/lib/api-routes/server-logs.js +44 -0
- package/src/lib/api-routes/settings.js +71 -0
- package/src/lib/api-server.js +135 -0
- package/src/lib/arrayStore.js +101 -0
- package/src/lib/callbackHandler.js +201 -0
- package/src/lib/config.js +177 -0
- package/src/lib/configuration-providers/mb-connection-manager.js +625 -0
- package/src/lib/db/adapters/dbAdapter.js +184 -0
- package/src/lib/db/dfspMockUsers.js +64 -0
- package/src/lib/db/models/mongoDBWrapper.js +78 -0
- package/src/lib/eventListenerClient/inboundEventListener.js +176 -0
- package/src/lib/fileAdapter.js +57 -0
- package/src/lib/httpAgentStore.js +135 -0
- package/src/lib/importExport.js +186 -0
- package/src/lib/jws/JwsSigning.js +141 -0
- package/src/lib/loadSamples.js +128 -0
- package/src/lib/logger.js +20 -0
- package/src/lib/longpollingEmitter.js +56 -0
- package/src/lib/metrics.js +51 -0
- package/src/lib/mocking/custom-functions/generic.js +57 -0
- package/src/lib/mocking/middleware-functions/ilpModel.js +238 -0
- package/src/lib/mocking/middleware-functions/quotesAssociation.js +75 -0
- package/src/lib/mocking/middleware-functions/transactionRequestsService.js +78 -0
- package/src/lib/mocking/openApiDefinitionsModel.js +64 -0
- package/src/lib/mocking/openApiMockHandler.js +466 -0
- package/src/lib/mocking/openApiRulesEngine.js +492 -0
- package/src/lib/mocking/openApiVersionTools.js +136 -0
- package/src/lib/mocking/transformers/fspiopToISO20022.js +230 -0
- package/src/lib/mocking/transformers/index.js +41 -0
- package/src/lib/notificationEmitter.js +64 -0
- package/src/lib/oauth/KeycloakHelper.js +220 -0
- package/src/lib/oauth/LoginService.js +133 -0
- package/src/lib/oauth/OAuthHelper.js +181 -0
- package/src/lib/oauth/OAuthValidator.js +118 -0
- package/src/lib/oauth/Wso2Client.js +64 -0
- package/src/lib/objectStore/inMemoryImpl.js +50 -0
- package/src/lib/objectStore/objectStoreInterface.js +51 -0
- package/src/lib/objectStore.js +122 -0
- package/src/lib/report-generator/generator.js +126 -0
- package/src/lib/report-generator/helpers.js +154 -0
- package/src/lib/requestLogger.js +190 -0
- package/src/lib/resources/wso2carbon-publickey.cert +20 -0
- package/src/lib/rulesEngine.js +95 -0
- package/src/lib/rulesEngineModel.js +463 -0
- package/src/lib/scripting-engines/postman-sandbox.js +142 -0
- package/src/lib/scripting-engines/vm-javascript-sandbox.js +294 -0
- package/src/lib/server-logs/adapters/elastic-search.js +102 -0
- package/src/lib/server-logs/adapters/grafana.js +0 -0
- package/src/lib/server-logs/index.js +75 -0
- package/src/lib/socket-server.js +55 -0
- package/src/lib/storageAdapter.js +109 -0
- package/src/lib/test-outbound/TestCaseRunner.js +173 -0
- package/src/lib/test-outbound/getTracing.js +19 -0
- package/src/lib/test-outbound/outbound-initiator.js +1107 -0
- package/src/lib/uniqueIdGenerator.js +35 -0
- package/src/lib/utils.js +89 -0
- package/src/lib/utilsInternal.js +56 -0
- package/src/lib/webSocketClient/WebSocketClientManager.js +197 -0
- package/src/server.js +218 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/*****
|
|
2
|
+
License
|
|
3
|
+
--------------
|
|
4
|
+
Copyright © 2020-2025 Mojaloop Foundation
|
|
5
|
+
The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
10
|
+
|
|
11
|
+
Contributors
|
|
12
|
+
--------------
|
|
13
|
+
This is the official list of the Mojaloop project contributors for this file.
|
|
14
|
+
Names of the original copyright holders (individuals or organizations)
|
|
15
|
+
should be listed with a '*' in the first column. People who have
|
|
16
|
+
contributed from an organization can be listed under the organization
|
|
17
|
+
that actually holds the copyright for their contributions (see the
|
|
18
|
+
Mojaloop Foundation for an example). Those individuals should have
|
|
19
|
+
their names indented and be marked with a '-'. Email address can be added
|
|
20
|
+
optionally within square brackets <email>.
|
|
21
|
+
|
|
22
|
+
* Mojaloop Foundation
|
|
23
|
+
- Name Surname <name.surname@mojaloop.io>
|
|
24
|
+
|
|
25
|
+
* Infitx
|
|
26
|
+
* Vijaya Kumar Guthi <vijaya.guthi@infitx.com> (Original Author)
|
|
27
|
+
--------------
|
|
28
|
+
******/
|
|
29
|
+
|
|
30
|
+
const { TransformFacades } = require('@mojaloop/ml-schema-transformer-lib')
|
|
31
|
+
const { getHeader, headersToLowerCase } = require('../../utils')
|
|
32
|
+
|
|
33
|
+
const _replaceAcceptOrContentTypeHeader = (inputStr, isReverse) => {
|
|
34
|
+
if (isReverse) {
|
|
35
|
+
return inputStr.replace('application/vnd.interoperability.iso20022.', 'application/vnd.interoperability.')
|
|
36
|
+
} else {
|
|
37
|
+
return inputStr.replace('application/vnd.interoperability.', 'application/vnd.interoperability.iso20022.')
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const _replaceHeaders = (headers, isReverse) => {
|
|
41
|
+
// Replace headers considering the case sensitivity
|
|
42
|
+
const newHeaders = {
|
|
43
|
+
...headers
|
|
44
|
+
}
|
|
45
|
+
Object.keys(headers).forEach((key) => {
|
|
46
|
+
if (key.toLowerCase() === 'content-type' || key.toLowerCase() === 'accept') {
|
|
47
|
+
newHeaders[key] = _replaceAcceptOrContentTypeHeader(headers[key], isReverse)
|
|
48
|
+
} else {
|
|
49
|
+
newHeaders[key] = headers[key]
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
return newHeaders
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const _transformGetResource = (_resource, options, isReverse) => {
|
|
56
|
+
const headers = _replaceHeaders(options.headers, isReverse)
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
...options,
|
|
60
|
+
headers
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const _transformPostResource = async (resource, options, isReverse) => {
|
|
65
|
+
const headers = _replaceHeaders(options.headers, isReverse)
|
|
66
|
+
let result
|
|
67
|
+
if (isReverse) {
|
|
68
|
+
TransformFacades.FSPIOPISO20022.configure({ isTestingMode: true })
|
|
69
|
+
result = await TransformFacades.FSPIOPISO20022[resource].post({ body: options.body, headers: options.headers })
|
|
70
|
+
} else {
|
|
71
|
+
TransformFacades.FSPIOP.configure({ isTestingMode: true })
|
|
72
|
+
result = await TransformFacades.FSPIOP[resource].post({ body: options.body, headers: options.headers })
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
...options,
|
|
77
|
+
headers,
|
|
78
|
+
body: result?.body
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const _transformPutResource = async (resource, options, isError, isReverse) => {
|
|
83
|
+
const headers = _replaceHeaders(options.headers, isReverse)
|
|
84
|
+
let result
|
|
85
|
+
if (isReverse) {
|
|
86
|
+
TransformFacades.FSPIOPISO20022.configure({ isTestingMode: true })
|
|
87
|
+
result = await TransformFacades.FSPIOPISO20022[resource][isError ? 'putError' : 'put']({
|
|
88
|
+
body: options.body,
|
|
89
|
+
headers: headersToLowerCase(headers),
|
|
90
|
+
params: options.params
|
|
91
|
+
})
|
|
92
|
+
} else {
|
|
93
|
+
TransformFacades.FSPIOP.configure({ isTestingMode: true })
|
|
94
|
+
result = await TransformFacades.FSPIOP[resource][isError ? 'putError' : 'put']({
|
|
95
|
+
body: options.body,
|
|
96
|
+
headers: headersToLowerCase(headers),
|
|
97
|
+
params: options.params
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
...options,
|
|
103
|
+
headers,
|
|
104
|
+
body: result?.body
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const _transformPatchResource = async (resource, options, isReverse) => {
|
|
109
|
+
const headers = _replaceHeaders(options.headers, isReverse)
|
|
110
|
+
let result
|
|
111
|
+
if (isReverse) {
|
|
112
|
+
TransformFacades.FSPIOPISO20022.configure({ isTestingMode: true })
|
|
113
|
+
result = await TransformFacades.FSPIOPISO20022[resource].patch({
|
|
114
|
+
body: options.body,
|
|
115
|
+
headers: headersToLowerCase(headers),
|
|
116
|
+
params: options.params
|
|
117
|
+
})
|
|
118
|
+
} else {
|
|
119
|
+
TransformFacades.FSPIOP.configure({ isTestingMode: true })
|
|
120
|
+
result = await TransformFacades.FSPIOP[resource].patch({
|
|
121
|
+
body: options.body,
|
|
122
|
+
headers: headersToLowerCase(headers),
|
|
123
|
+
params: options.params
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
...options,
|
|
129
|
+
headers,
|
|
130
|
+
body: result?.body
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const _transform = async (options, isReverse = false) => {
|
|
135
|
+
if (isReverse) {
|
|
136
|
+
if (!getHeader(options.headers, 'content-type')?.startsWith('application/vnd.interoperability.iso20022.')) {
|
|
137
|
+
return options
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
if (!getHeader(options.headers, 'content-type')?.startsWith('application/vnd.interoperability.')) {
|
|
141
|
+
return options
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
switch (options.method) {
|
|
146
|
+
case 'get':
|
|
147
|
+
if (options.path.startsWith('/parties')) {
|
|
148
|
+
return _transformGetResource('parties', options, isReverse)
|
|
149
|
+
} else if (options.path.startsWith('/quotes')) {
|
|
150
|
+
return _transformGetResource('quotes', options, isReverse)
|
|
151
|
+
} else if (options.path.startsWith('/transfers')) {
|
|
152
|
+
return _transformGetResource('transfers', options, isReverse)
|
|
153
|
+
} else if (options.path.startsWith('/fxQuotes')) {
|
|
154
|
+
return _transformGetResource('fxQuotes', options, isReverse)
|
|
155
|
+
} else if (options.path.startsWith('/fxTransfers')) {
|
|
156
|
+
return _transformGetResource('fxTransfers', options, isReverse)
|
|
157
|
+
} else if (options.path.startsWith('/participants')) {
|
|
158
|
+
return _transformGetResource('participants', options, isReverse)
|
|
159
|
+
}
|
|
160
|
+
break
|
|
161
|
+
case 'post':
|
|
162
|
+
if (options.path.startsWith('/quotes')) {
|
|
163
|
+
return await _transformPostResource('quotes', options, isReverse)
|
|
164
|
+
} else if (options.path.startsWith('/transfers')) {
|
|
165
|
+
return await _transformPostResource('transfers', options, isReverse)
|
|
166
|
+
} else if (options.path.startsWith('/fxQuotes')) {
|
|
167
|
+
return await _transformPostResource('fxQuotes', options, isReverse)
|
|
168
|
+
} else if (options.path.startsWith('/fxTransfers')) {
|
|
169
|
+
return await _transformPostResource('fxTransfers', options, isReverse)
|
|
170
|
+
} else if (options.path.startsWith('/participants')) {
|
|
171
|
+
// POST /participants - Only the headers need to be transformed
|
|
172
|
+
const headers = _replaceHeaders(options.headers, isReverse)
|
|
173
|
+
return {
|
|
174
|
+
...options,
|
|
175
|
+
headers
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
break
|
|
179
|
+
case 'put': {
|
|
180
|
+
let isError = false
|
|
181
|
+
if (options.path.endsWith('/error')) {
|
|
182
|
+
isError = true
|
|
183
|
+
}
|
|
184
|
+
if (options.path.startsWith('/parties')) {
|
|
185
|
+
return await _transformPutResource('parties', options, isError, isReverse)
|
|
186
|
+
} else if (options.path.startsWith('/quotes')) {
|
|
187
|
+
return await _transformPutResource('quotes', options, isError, isReverse)
|
|
188
|
+
} else if (options.path.startsWith('/transfers')) {
|
|
189
|
+
return await _transformPutResource('transfers', options, isError, isReverse)
|
|
190
|
+
} else if (options.path.startsWith('/fxQuotes')) {
|
|
191
|
+
return await _transformPutResource('fxQuotes', options, isError, isReverse)
|
|
192
|
+
} else if (options.path.startsWith('/fxTransfers')) {
|
|
193
|
+
return await _transformPutResource('fxTransfers', options, isError, isReverse)
|
|
194
|
+
} else if (options.path.startsWith('/participants')) {
|
|
195
|
+
// PUT /participants - Only the headers need to be transformed
|
|
196
|
+
const headers = _replaceHeaders(options.headers, isReverse)
|
|
197
|
+
return {
|
|
198
|
+
...options,
|
|
199
|
+
headers
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
break
|
|
203
|
+
}
|
|
204
|
+
case 'patch': {
|
|
205
|
+
if (options.path.startsWith('/transfers')) {
|
|
206
|
+
return await _transformPatchResource('transfers', options, isReverse)
|
|
207
|
+
} else if (options.path.startsWith('/fxTransfers')) {
|
|
208
|
+
return await _transformPatchResource('fxTransfers', options, isReverse)
|
|
209
|
+
}
|
|
210
|
+
break
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} catch (err) {
|
|
214
|
+
console.log('Error transforming request', err)
|
|
215
|
+
}
|
|
216
|
+
return options
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const forwardTransform = async (options) => {
|
|
220
|
+
return _transform(options)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const reverseTransform = async (options) => {
|
|
224
|
+
return _transform(options, true)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
module.exports = {
|
|
228
|
+
forwardTransform,
|
|
229
|
+
reverseTransform
|
|
230
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/*****
|
|
2
|
+
License
|
|
3
|
+
--------------
|
|
4
|
+
Copyright © 2020-2025 Mojaloop Foundation
|
|
5
|
+
The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
10
|
+
|
|
11
|
+
Contributors
|
|
12
|
+
--------------
|
|
13
|
+
This is the official list of the Mojaloop project contributors for this file.
|
|
14
|
+
Names of the original copyright holders (individuals or organizations)
|
|
15
|
+
should be listed with a '*' in the first column. People who have
|
|
16
|
+
contributed from an organization can be listed under the organization
|
|
17
|
+
that actually holds the copyright for their contributions (see the
|
|
18
|
+
Mojaloop Foundation for an example). Those individuals should have
|
|
19
|
+
their names indented and be marked with a '-'. Email address can be added
|
|
20
|
+
optionally within square brackets <email>.
|
|
21
|
+
|
|
22
|
+
* Mojaloop Foundation
|
|
23
|
+
- Name Surname <name.surname@mojaloop.io>
|
|
24
|
+
|
|
25
|
+
* Infitx
|
|
26
|
+
* Vijaya Kumar Guthi <vijaya.guthi@infitx.com> (Original Author)
|
|
27
|
+
--------------
|
|
28
|
+
******/
|
|
29
|
+
|
|
30
|
+
const getTransformer = (transformerName) => {
|
|
31
|
+
if (transformerName === 'none' || transformerName === 'NONE' || transformerName === 'no' || transformerName === 'NO') {
|
|
32
|
+
return null
|
|
33
|
+
} else {
|
|
34
|
+
const transformerModule = require(`./${transformerName}`)
|
|
35
|
+
return transformerModule
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
getTransformer
|
|
41
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/*****
|
|
2
|
+
License
|
|
3
|
+
--------------
|
|
4
|
+
Copyright © 2020-2025 Mojaloop Foundation
|
|
5
|
+
The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
10
|
+
|
|
11
|
+
Contributors
|
|
12
|
+
--------------
|
|
13
|
+
This is the official list of the Mojaloop project contributors for this file.
|
|
14
|
+
Names of the original copyright holders (individuals or organizations)
|
|
15
|
+
should be listed with a '*' in the first column. People who have
|
|
16
|
+
contributed from an organization can be listed under the organization
|
|
17
|
+
that actually holds the copyright for their contributions (see the
|
|
18
|
+
Mojaloop Foundation for an example). Those individuals should have
|
|
19
|
+
their names indented and be marked with a '-'. Email address can be added
|
|
20
|
+
optionally within square brackets <email>.
|
|
21
|
+
|
|
22
|
+
* Mojaloop Foundation
|
|
23
|
+
- Name Surname <name.surname@mojaloop.io>
|
|
24
|
+
|
|
25
|
+
* ModusBox
|
|
26
|
+
* Vijaya Kumar Guthi <vijaya.guthi@modusbox.com> (Original Author)
|
|
27
|
+
--------------
|
|
28
|
+
******/
|
|
29
|
+
|
|
30
|
+
const SocketServer = require('./socket-server')
|
|
31
|
+
|
|
32
|
+
const broadcast = (log, sessionID = null, type) => {
|
|
33
|
+
const io = SocketServer.getIO()
|
|
34
|
+
if (io) {
|
|
35
|
+
io.emit(type, log)
|
|
36
|
+
if (sessionID) {
|
|
37
|
+
io.emit(`${type}/` + sessionID, log)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const broadcastLog = (log, sessionID = null) => {
|
|
43
|
+
broadcast(log, sessionID, 'newLog')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const sendMessage = (message, sessionID = null) => {
|
|
47
|
+
broadcast(message, sessionID, 'pushMessage')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const broadcastOutboundLog = (log, sessionID = null) => {
|
|
51
|
+
broadcast(log, sessionID, 'newOutboundLog')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const broadcastOutboundProgress = (status, sessionID = null) => {
|
|
55
|
+
status.reportTime = new Date()
|
|
56
|
+
broadcast(status, sessionID, 'outboundProgress')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = {
|
|
60
|
+
broadcastLog,
|
|
61
|
+
broadcastOutboundLog,
|
|
62
|
+
broadcastOutboundProgress,
|
|
63
|
+
sendMessage
|
|
64
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/*****
|
|
2
|
+
License
|
|
3
|
+
--------------
|
|
4
|
+
Copyright © 2020-2025 Mojaloop Foundation
|
|
5
|
+
The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
10
|
+
|
|
11
|
+
Contributors
|
|
12
|
+
--------------
|
|
13
|
+
This is the official list of the Mojaloop project contributors for this file.
|
|
14
|
+
Names of the original copyright holders (individuals or organizations)
|
|
15
|
+
should be listed with a '*' in the first column. People who have
|
|
16
|
+
contributed from an organization can be listed under the organization
|
|
17
|
+
that actually holds the copyright for their contributions (see the
|
|
18
|
+
Mojaloop Foundation for an example). Those individuals should have
|
|
19
|
+
their names indented and be marked with a '-'. Email address can be added
|
|
20
|
+
optionally within square brackets <email>.
|
|
21
|
+
|
|
22
|
+
* Mojaloop Foundation
|
|
23
|
+
- Name Surname <name.surname@mojaloop.io>
|
|
24
|
+
|
|
25
|
+
* ModusBox
|
|
26
|
+
* Vijay Kumar Guthi <vijaya.guthi@modusbox.com> (Original Author)
|
|
27
|
+
--------------
|
|
28
|
+
******/
|
|
29
|
+
|
|
30
|
+
const Config = require('../config')
|
|
31
|
+
const querystring = require('querystring')
|
|
32
|
+
const axios = require('axios').default
|
|
33
|
+
const objectStore = require('../objectStore/objectStoreInterface')
|
|
34
|
+
const customLogger = require('../requestLogger')
|
|
35
|
+
|
|
36
|
+
const getKeyCloakUsers = async (keycloakToken) => {
|
|
37
|
+
const systemConfig = Config.getSystemConfig()
|
|
38
|
+
const getUsersResp = await axios.get(systemConfig.KEYCLOAK.API_URL + `/auth/admin/realms/${systemConfig.KEYCLOAK.REALM}/users`, { headers: { Authorization: `Bearer ${keycloakToken.accessToken}` } })
|
|
39
|
+
if (getUsersResp.status === 200) {
|
|
40
|
+
const users = []
|
|
41
|
+
getUsersResp.data.forEach(user => {
|
|
42
|
+
if (user.username !== systemConfig.KEYCLOAK.USERNAME) {
|
|
43
|
+
let userId = user.attributes.dfspId
|
|
44
|
+
if (Array.isArray(user.attributes.dfspId)) {
|
|
45
|
+
userId = user.attributes.dfspId[0]
|
|
46
|
+
}
|
|
47
|
+
users.push({
|
|
48
|
+
id: userId,
|
|
49
|
+
name: `${user.firstName} ${user.lastName}`
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
return users
|
|
54
|
+
} else {
|
|
55
|
+
throw new Error(`Some error while getting users from as ${systemConfig.KEYCLOAK.USERNAME}`)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const getTokenInfo = async (authInfo) => {
|
|
60
|
+
const formData = {
|
|
61
|
+
client_id: authInfo.clientId,
|
|
62
|
+
client_secret: authInfo.clientSecret,
|
|
63
|
+
grant_type: 'client_credentials'
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const tokenResponse = await axios.post(authInfo.tokenEndpoint, querystring.stringify(formData), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } })
|
|
67
|
+
if (tokenResponse.status === 200) {
|
|
68
|
+
return tokenResponse.data
|
|
69
|
+
} else {
|
|
70
|
+
throw new Error('Some error while generating token')
|
|
71
|
+
}
|
|
72
|
+
} catch (error) {
|
|
73
|
+
throw new Error('Token generation failed', error.error)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const keycloakAuth = async () => {
|
|
78
|
+
let keycloakToken = await objectStore.get('KEYCLOAK_TOKEN')
|
|
79
|
+
// if token not set or expires in 1 min, then generate a new one
|
|
80
|
+
if (Object.keys(keycloakToken).length === 0 || Date.now() >= (keycloakToken.expiresAt - (60 * 1000))) {
|
|
81
|
+
const systemConfig = Config.getSystemConfig()
|
|
82
|
+
const loginFormData = {
|
|
83
|
+
grant_type: 'password',
|
|
84
|
+
client_id: systemConfig.KEYCLOAK.ADMIN_CLIENT_ID,
|
|
85
|
+
// client_secret: systemConfig.OAUTH.APP_OAUTH_CLIENT_SECRET,
|
|
86
|
+
username: systemConfig.KEYCLOAK.ADMIN_USERNAME,
|
|
87
|
+
password: systemConfig.KEYCLOAK.ADMIN_PASSWORD
|
|
88
|
+
}
|
|
89
|
+
const loginResp = await axios.post(systemConfig.KEYCLOAK.API_URL + `/auth/realms/${systemConfig.KEYCLOAK.ADMIN_REALM}/protocol/openid-connect/token`, querystring.stringify(loginFormData), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } })
|
|
90
|
+
if (loginResp.status === 200) {
|
|
91
|
+
await objectStore.set('KEYCLOAK_TOKEN', {
|
|
92
|
+
accessToken: loginResp.data.access_token,
|
|
93
|
+
expiresAt: Date.now() + (loginResp.data.expires_in * 1000)
|
|
94
|
+
})
|
|
95
|
+
keycloakToken = await objectStore.get('KEYCLOAK_TOKEN')
|
|
96
|
+
} else {
|
|
97
|
+
throw new Error(`Some error while login to the Keycloak as ${systemConfig.KEYCLOAK.ADMIN_USERNAME}`)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return keycloakToken
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const getClientAuthInfo = async (user) => {
|
|
104
|
+
try {
|
|
105
|
+
const systemConfig = Config.getSystemConfig()
|
|
106
|
+
const keycloakToken = await keycloakAuth()
|
|
107
|
+
const clientId = user.dfspId
|
|
108
|
+
let clientInfo = await _findClient(clientId, keycloakToken)
|
|
109
|
+
if (!clientInfo) {
|
|
110
|
+
// No clients with the client ID, so create one
|
|
111
|
+
// TODO: Create a new client with the clientId
|
|
112
|
+
await _createClient(clientId, keycloakToken)
|
|
113
|
+
clientInfo = await _findClient(clientId, keycloakToken)
|
|
114
|
+
}
|
|
115
|
+
// Get the client secret
|
|
116
|
+
const clientKeycloakID = clientInfo.id
|
|
117
|
+
const clientSecretResp = await axios.get(systemConfig.KEYCLOAK.API_URL + `/auth/admin/realms/${systemConfig.KEYCLOAK.REALM}/clients/${clientKeycloakID}/client-secret`, { params: { clientId }, headers: { Authorization: `Bearer ${keycloakToken.accessToken}` } })
|
|
118
|
+
if (clientSecretResp.status === 200 && clientSecretResp.data.type && clientSecretResp.data.type === 'secret') {
|
|
119
|
+
return {
|
|
120
|
+
clientId,
|
|
121
|
+
clientSecret: clientSecretResp.data.value,
|
|
122
|
+
tokenEndpoint: systemConfig.OAUTH.OAUTH2_ISSUER
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
throw new Error('Client secret not found: Contact Hub operator')
|
|
126
|
+
}
|
|
127
|
+
} catch (error) {
|
|
128
|
+
customLogger.logMessage('error', error.stack, error, { notification: false })
|
|
129
|
+
if (error && error.statusCode === 400) {
|
|
130
|
+
throw new Error('Authentication failed', error.error)
|
|
131
|
+
}
|
|
132
|
+
throw error
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const _findClient = async (clientId, keycloakToken) => {
|
|
137
|
+
try {
|
|
138
|
+
const systemConfig = Config.getSystemConfig()
|
|
139
|
+
const clientResp = await axios.get(systemConfig.KEYCLOAK.API_URL + `/auth/admin/realms/${systemConfig.KEYCLOAK.REALM}/clients`, { params: { clientId }, headers: { Authorization: `Bearer ${keycloakToken.accessToken}` } })
|
|
140
|
+
if (clientResp.status === 200) {
|
|
141
|
+
const clientsFound = clientResp.data
|
|
142
|
+
if (clientsFound.length > 0) {
|
|
143
|
+
// Pick the first client
|
|
144
|
+
return clientsFound[0]
|
|
145
|
+
} else {
|
|
146
|
+
return null
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
throw new Error('Can not get client list from keycloak')
|
|
150
|
+
} catch (error) {
|
|
151
|
+
customLogger.logMessage('error', error.stack, error, { notification: false })
|
|
152
|
+
if (error && error.statusCode === 400) {
|
|
153
|
+
throw new Error('Authentication failed', error.error)
|
|
154
|
+
}
|
|
155
|
+
throw error
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const _createClient = async (clientId, keycloakToken) => {
|
|
160
|
+
const clientRepresentationObj = {
|
|
161
|
+
clientId,
|
|
162
|
+
enabled: true,
|
|
163
|
+
protocol: 'openid-connect',
|
|
164
|
+
rootUrl: '',
|
|
165
|
+
clientAuthenticatorType: 'client-secret',
|
|
166
|
+
surrogateAuthRequired: false,
|
|
167
|
+
alwaysDisplayInConsole: false,
|
|
168
|
+
redirectUris: ['http://ml-testing-toolkit-keycloak.local'],
|
|
169
|
+
bearerOnly: false,
|
|
170
|
+
consentRequired: false,
|
|
171
|
+
standardFlowEnabled: true,
|
|
172
|
+
implicitFlowEnabled: false,
|
|
173
|
+
directAccessGrantsEnabled: true,
|
|
174
|
+
serviceAccountsEnabled: true,
|
|
175
|
+
authorizationServicesEnabled: true,
|
|
176
|
+
publicClient: false,
|
|
177
|
+
frontchannelLogout: false,
|
|
178
|
+
attributes: {
|
|
179
|
+
'oauth2.device.authorization.grant.enabled': 'false',
|
|
180
|
+
'backchannel.logout.revoke.offline.tokens': 'false',
|
|
181
|
+
'use.refresh.tokens': 'true',
|
|
182
|
+
'oidc.ciba.grant.enabled': 'false',
|
|
183
|
+
'id.token.signed.response.alg': 'RS256',
|
|
184
|
+
'backchannel.logout.session.required': 'true',
|
|
185
|
+
'client_credentials.use_refresh_token': 'false',
|
|
186
|
+
'require.pushed.authorization.requests': 'false',
|
|
187
|
+
'id.token.as.detached.signature': 'false',
|
|
188
|
+
'access.token.signed.response.alg': 'RS256',
|
|
189
|
+
'exclude.session.state.from.auth.response': 'false',
|
|
190
|
+
'tls.client.certificate.bound.access.tokens': 'false',
|
|
191
|
+
'display.on.consent.screen': 'false'
|
|
192
|
+
},
|
|
193
|
+
fullScopeAllowed: true,
|
|
194
|
+
access: { view: true, configure: true, manage: true }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
const systemConfig = Config.getSystemConfig()
|
|
199
|
+
const clientCreateResp = await axios.post(systemConfig.KEYCLOAK.API_URL + `/auth/admin/realms/${systemConfig.KEYCLOAK.REALM}/clients`, clientRepresentationObj, { headers: { Authorization: `Bearer ${keycloakToken.accessToken}` } })
|
|
200
|
+
if (clientCreateResp.status === 201) {
|
|
201
|
+
// const clientsFound = clientResp.data
|
|
202
|
+
console.log(clientCreateResp.data)
|
|
203
|
+
} else {
|
|
204
|
+
throw new Error(`Can not create client in keycloak: ${clientCreateResp.status} ${clientCreateResp.statusText}`)
|
|
205
|
+
}
|
|
206
|
+
} catch (error) {
|
|
207
|
+
customLogger.logMessage('error', error.stack, error, { notification: false })
|
|
208
|
+
if (error && error.statusCode === 400) {
|
|
209
|
+
throw new Error('Authentication failed', error.error)
|
|
210
|
+
}
|
|
211
|
+
throw error
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
module.exports = {
|
|
216
|
+
getKeyCloakUsers,
|
|
217
|
+
getClientAuthInfo,
|
|
218
|
+
getTokenInfo,
|
|
219
|
+
keycloakAuth
|
|
220
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/*****
|
|
2
|
+
License
|
|
3
|
+
--------------
|
|
4
|
+
Copyright © 2020-2025 Mojaloop Foundation
|
|
5
|
+
The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
10
|
+
|
|
11
|
+
Contributors
|
|
12
|
+
--------------
|
|
13
|
+
This is the official list of the Mojaloop project contributors for this file.
|
|
14
|
+
Names of the original copyright holders (individuals or organizations)
|
|
15
|
+
should be listed with a '*' in the first column. People who have
|
|
16
|
+
contributed from an organization can be listed under the organization
|
|
17
|
+
that actually holds the copyright for their contributions (see the
|
|
18
|
+
Mojaloop Foundation for an example). Those individuals should have
|
|
19
|
+
their names indented and be marked with a '-'. Email address can be added
|
|
20
|
+
optionally within square brackets <email>.
|
|
21
|
+
|
|
22
|
+
* Mojaloop Foundation
|
|
23
|
+
- Name Surname <name.surname@mojaloop.io>
|
|
24
|
+
|
|
25
|
+
* ModusBox
|
|
26
|
+
* Georgi Logodazhki <georgi.logodazhki@modusbox.com> (Original Author)
|
|
27
|
+
--------------
|
|
28
|
+
******/
|
|
29
|
+
|
|
30
|
+
'use strict'
|
|
31
|
+
const Config = require('../config')
|
|
32
|
+
const Cookies = require('cookies')
|
|
33
|
+
const jwt = require('jsonwebtoken')
|
|
34
|
+
const wso2Client = require('./Wso2Client')
|
|
35
|
+
const customLogger = require('../requestLogger')
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Logs the user in.
|
|
39
|
+
* If successful, sets the JWT token in a cookie and returns the token payload
|
|
40
|
+
*/
|
|
41
|
+
exports.loginUser = async function (username, password, req, res) {
|
|
42
|
+
const tokenObj = await this.getTokenInfo(username, password)
|
|
43
|
+
if (tokenObj === null) {
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
token: {
|
|
47
|
+
payload: {
|
|
48
|
+
at_hash: null,
|
|
49
|
+
aud: null,
|
|
50
|
+
sub: null,
|
|
51
|
+
nbf: 0,
|
|
52
|
+
azp: null,
|
|
53
|
+
amr: [],
|
|
54
|
+
iss: null,
|
|
55
|
+
groups: [],
|
|
56
|
+
exp: 0,
|
|
57
|
+
iat: 0,
|
|
58
|
+
dfspId: null
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const response = buildJWTResponse(tokenObj.decodedIdToken, tokenObj.access_token, tokenObj.expires_in, req, res)
|
|
65
|
+
return response
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Gets the access token and decoded token
|
|
70
|
+
* If successful, sets the JWT token in a cookie and returns the token payload
|
|
71
|
+
*/
|
|
72
|
+
exports.getTokenInfo = async function (username, password) {
|
|
73
|
+
if (!Config.getSystemConfig().OAUTH.AUTH_ENABLED) {
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const loginResponseObj = await wso2Client.getToken(username, password)
|
|
78
|
+
const decodedIdToken = jwt.decode(loginResponseObj.id_token)
|
|
79
|
+
return {
|
|
80
|
+
...loginResponseObj,
|
|
81
|
+
decodedIdToken
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
customLogger.logMessage('error', 'login user failed', error, { notification: false })
|
|
85
|
+
if (error && error.statusCode === 400 && error.message.includes('Authentication failed')) {
|
|
86
|
+
throw new Error(`Authentication failed for user ${username}`, error.error)
|
|
87
|
+
}
|
|
88
|
+
throw error
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const buildJWTResponse = (decodedIdToken, accessToken, expiresIn, req, res) => {
|
|
93
|
+
// If the user is a DFSP admin, set the dfspId so the UI can send it
|
|
94
|
+
let dfspId = null
|
|
95
|
+
if (decodedIdToken.dfspId != null) {
|
|
96
|
+
dfspId = decodedIdToken.dfspId
|
|
97
|
+
} else {
|
|
98
|
+
// Get the DFSP id from the Application/DFSP: group
|
|
99
|
+
const groups = decodedIdToken.groups
|
|
100
|
+
for (const group of groups) {
|
|
101
|
+
const groupMatchResult = group.match(/^Application\/DFSP:(.*)$/)
|
|
102
|
+
if (groupMatchResult == null || !Array.isArray(groupMatchResult)) {
|
|
103
|
+
continue
|
|
104
|
+
}
|
|
105
|
+
dfspId = groupMatchResult[1]
|
|
106
|
+
customLogger.logMessage('info', `${dfspId} found in ${group} group`, { notification: false })
|
|
107
|
+
break // FIXME only returns the first ( there should be only one ). May report an error if there's more than one Application/DFSP group ?
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
decodedIdToken.dfspId = dfspId
|
|
112
|
+
|
|
113
|
+
const cookies = new Cookies(req, res)
|
|
114
|
+
const cookieOptions = { maxAge: expiresIn * 1000, httpOnly: true, sameSite: 'strict' } // secure is automatic based on HTTP or HTTPS used
|
|
115
|
+
cookies.set('TTK-API_ACCESS_TOKEN', accessToken, cookieOptions)
|
|
116
|
+
return {
|
|
117
|
+
ok: true,
|
|
118
|
+
token: {
|
|
119
|
+
payload: {
|
|
120
|
+
maxAge: expiresIn,
|
|
121
|
+
...decodedIdToken
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Logs the user out.
|
|
129
|
+
*/
|
|
130
|
+
exports.logoutUser = async function (req, res) {
|
|
131
|
+
const cookies = new Cookies(req, res)
|
|
132
|
+
cookies.set('TTK-API_ACCESS_TOKEN')
|
|
133
|
+
}
|