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.
Files changed (319) hide show
  1. package/.dockerignore +10 -0
  2. package/.grype.yaml +16 -0
  3. package/.ncurc.yaml +9 -0
  4. package/.nvmrc +1 -0
  5. package/.versionrc.js +16 -0
  6. package/CHANGELOG.md +504 -0
  7. package/CODEOWNERS +30 -0
  8. package/Dockerfile +42 -0
  9. package/Dockerfile-newman +13 -0
  10. package/LICENSE.md +9 -0
  11. package/README.md +119 -0
  12. package/assets/diagrams/architectural/architectural-diagram.svg +3 -0
  13. package/assets/diagrams/flow/flow-diagram.svg +3 -0
  14. package/assets/images/Sample-Response-Failure.png +0 -0
  15. package/assets/images/Screenshot 2020-04-16 at 22.58.04.png +0 -0
  16. package/assets/images/TLS-Enabled-on-Environment.png +0 -0
  17. package/assets/images/adapter-mutual-tls-enabled.png +0 -0
  18. package/assets/images/add-additional-input-values.png +0 -0
  19. package/assets/images/add-condition-button.png +0 -0
  20. package/assets/images/add-new-assertion.png +0 -0
  21. package/assets/images/add-new-input-value.png +0 -0
  22. package/assets/images/add-new-input-variable.png +0 -0
  23. package/assets/images/additional-transfers.png +0 -0
  24. package/assets/images/api-provisioning-add-new-api-confirmation.png +0 -0
  25. package/assets/images/api-provisioning-file-input-window.png +0 -0
  26. package/assets/images/api-provisioning-list-apis-view.png +0 -0
  27. package/assets/images/api-provisioning-menu-item.png +0 -0
  28. package/assets/images/apply_and_restart.jpg +0 -0
  29. package/assets/images/assess-request-or-response.png +0 -0
  30. package/assets/images/assess-response-equation-save.png +0 -0
  31. package/assets/images/assess-response-equation.png +0 -0
  32. package/assets/images/assess-response-status.png +0 -0
  33. package/assets/images/building-new-rules-file.png +0 -0
  34. package/assets/images/callback-rules-screen.png +0 -0
  35. package/assets/images/configurable-parameter-assertion.png +0 -0
  36. package/assets/images/configurable-parameter-currency.png +0 -0
  37. package/assets/images/configurable-parameter.png +0 -0
  38. package/assets/images/connection-manager-ui-opening.png +0 -0
  39. package/assets/images/create-inbound-user-simulator.png +0 -0
  40. package/assets/images/creating-new-rule-file.png +0 -0
  41. package/assets/images/dfsp-client-cacert.png +0 -0
  42. package/assets/images/dfsp-client-submit.png +0 -0
  43. package/assets/images/dfsp-client.png +0 -0
  44. package/assets/images/dfsp-p2p-happy-path.png +0 -0
  45. package/assets/images/dfsp-server-cacert.png +0 -0
  46. package/assets/images/dfsp-server-cert.png +0 -0
  47. package/assets/images/download-report.png +0 -0
  48. package/assets/images/drive_have_not_been_shared.jpg +0 -0
  49. package/assets/images/event-response-options.png +0 -0
  50. package/assets/images/expand-monitoring-messages.png +0 -0
  51. package/assets/images/fixed-response-sample.png +0 -0
  52. package/assets/images/header-selection.png +0 -0
  53. package/assets/images/heap_error_windows.jpg +0 -0
  54. package/assets/images/hosted-mode-docker-compose-intro.png +0 -0
  55. package/assets/images/hub-client-cert.png +0 -0
  56. package/assets/images/import-template.png +0 -0
  57. package/assets/images/inbound-requests-environment.png +0 -0
  58. package/assets/images/inbound-requests-scripts.png +0 -0
  59. package/assets/images/jws-certificate-submit.png +0 -0
  60. package/assets/images/jws-certificate.png +0 -0
  61. package/assets/images/jws-certs-keys.png +0 -0
  62. package/assets/images/jws-hub-certs-keys.png +0 -0
  63. package/assets/images/local-enable-jws-publickey.png +0 -0
  64. package/assets/images/local-mutual-tls-enabled.png +0 -0
  65. package/assets/images/local_drives_to_be_available.jpg +0 -0
  66. package/assets/images/mcm-environment-opening.png +0 -0
  67. package/assets/images/menu-items.png +0 -0
  68. package/assets/images/mock-response-sample.png +0 -0
  69. package/assets/images/monitoring-initial-state.png +0 -0
  70. package/assets/images/monitoring-messages.png +0 -0
  71. package/assets/images/new-empty-assertion.png +0 -0
  72. package/assets/images/opened-imported-template.png +0 -0
  73. package/assets/images/opening-default-settings.png +0 -0
  74. package/assets/images/opening-sync-response-rules.png +0 -0
  75. package/assets/images/opening-view.png +0 -0
  76. package/assets/images/outbound-display-opening-hub.png +0 -0
  77. package/assets/images/outbound-display-opening.png +0 -0
  78. package/assets/images/override-with-environment-variable.png +0 -0
  79. package/assets/images/populate-with-sample-body.png +0 -0
  80. package/assets/images/resource-selection.png +0 -0
  81. package/assets/images/rule-builder-select-api.png +0 -0
  82. package/assets/images/sample-condition-add-configurable-params.png +0 -0
  83. package/assets/images/sample-condition.png +0 -0
  84. package/assets/images/sample-editor.png +0 -0
  85. package/assets/images/sample-request.png +0 -0
  86. package/assets/images/sample-test-assertion.png +0 -0
  87. package/assets/images/send-transfer.png +0 -0
  88. package/assets/images/sending-single-test-case-1.png +0 -0
  89. package/assets/images/sending-single-test-case-2.png +0 -0
  90. package/assets/images/sending-test-cases.png +0 -0
  91. package/assets/images/server-certificates-submitted.png +0 -0
  92. package/assets/images/simulator-response.png +0 -0
  93. package/assets/images/simulator-scheme-adapter-endpoint.png +0 -0
  94. package/assets/images/summarized-view-of-rule.png +0 -0
  95. package/assets/images/template-window.png +0 -0
  96. package/assets/images/test-case-editor-console-log.png +0 -0
  97. package/assets/images/test-case-editor-environment-state.png +0 -0
  98. package/assets/images/test-case-editor-scripts.png +0 -0
  99. package/assets/images/test-case-editor.png +0 -0
  100. package/assets/images/testcase-definition-download.png +0 -0
  101. package/assets/images/testcase-definition-edit-meta-info.png +0 -0
  102. package/assets/images/testing-toolkit-mojaloop-testing-toolkit-endpoint.png +0 -0
  103. package/assets/images/tls-hub-certs-keys.png +0 -0
  104. package/assets/images/tls-jws-enabled-on-environment.png +0 -0
  105. package/assets/images/updated-sample-body-data.png +0 -0
  106. package/assets/images/using-configurable-parameter.png +0 -0
  107. package/assets/images/validation-rules-screen.png +0 -0
  108. package/assets/images/view-response.png +0 -0
  109. package/audit-ci.jsonc +7 -0
  110. package/connection-manager/docker-compose.yml +55 -0
  111. package/database/docker-compose.yml +16 -0
  112. package/docker/hosted-mode/docker-compose.yaml +107 -0
  113. package/docker/hosted-mode/keycloak/keycloak-realm.json +2298 -0
  114. package/docker/hosted-mode/mongo-init.sh +1 -0
  115. package/docker/hosted-mode-tls/docker-compose.yaml +171 -0
  116. package/docker/hosted-mode-tls/keycloak/keycloak-realm.json +2298 -0
  117. package/docker/hosted-mode-tls/mongo-init.sh +1 -0
  118. package/docker-compose.yml +62 -0
  119. package/documents/Mojaloop-Testing-Toolkit.md +296 -0
  120. package/documents/RULES_ENGINE.md +403 -0
  121. package/documents/User-Guide-API-Provisioning.md +121 -0
  122. package/documents/User-Guide-CLI.md +218 -0
  123. package/documents/User-Guide-Connection-Manager.md +282 -0
  124. package/documents/User-Guide-Frequently-Asked-Questions.md +39 -0
  125. package/documents/User-Guide-Hosted-Mode-Docker-Compose.md +110 -0
  126. package/documents/User-Guide-Installation.md +163 -0
  127. package/documents/User-Guide-Mojaloop-Testing-Toolkit.md +642 -0
  128. package/documents/User-Guide-OAuth-Server-Deployment.md +283 -0
  129. package/documents/User-Guide-Onboarding-DFSP.md +197 -0
  130. package/documents/User-Guide-Onboarding-HUB.md +191 -0
  131. package/documents/User-Guide.md +53 -0
  132. package/examples/collections/dfsp/p2p_failed_tests.json +7161 -0
  133. package/examples/collections/dfsp/p2p_fx_happy_path.json +502 -0
  134. package/examples/collections/dfsp/p2p_happy_path.json +350 -0
  135. package/examples/collections/dfsp/p2p_happy_path_extended.json +6106 -0
  136. package/examples/collections/dfsp/p2p_happy_path_jws.json +511 -0
  137. package/examples/collections/dfsp/p2p_payee_assertions_websocket.json +441 -0
  138. package/examples/collections/dfsp/sample.json +5029 -0
  139. package/examples/collections/dfsp/transaction_request_service.json +240 -0
  140. package/examples/collections/fxp/FXP.json +264 -0
  141. package/examples/collections/fxp/SDK_backend.json +98 -0
  142. package/examples/collections/fxp/SDK_outbound.json +163 -0
  143. package/examples/collections/hub/hub_01_p2p_happy_path/hub_p2p_receive_quote.json +400 -0
  144. package/examples/collections/hub/hub_01_p2p_happy_path/hub_p2p_send_quote.json +395 -0
  145. package/examples/collections/hub/hub_02_block_transfer/hub_block_transfer.json +393 -0
  146. package/examples/collections/hub/hub_03_funds_in_out/hub_funds_in.json +224 -0
  147. package/examples/collections/hub/hub_03_funds_in_out/hub_funds_out.json +780 -0
  148. package/examples/collections/hub/hub_04_settlements/hub_settlements.json +3138 -0
  149. package/examples/collections/hub/hub_05_transfer_negative_scenarios/hub_transfer_negative_payee_abort.json +475 -0
  150. package/examples/collections/hub/hub_05_transfer_negative_scenarios/hub_transfer_negative_payee_invalid_fulfillment.json +370 -0
  151. package/examples/collections/hub/hub_05_transfer_negative_scenarios/hub_transfer_negative_transfer_timeout.json +262 -0
  152. package/examples/collections/hub/hub_06_transaction_requests_service/hub_trs_authorizations.json +117 -0
  153. package/examples/collections/hub/hub_06_transaction_requests_service/hub_trs_error_framework.json +591 -0
  154. package/examples/collections/hub/hub_06_transaction_requests_service/hub_trs_received_state.json +379 -0
  155. package/examples/collections/hub/hub_06_transaction_requests_service/hub_trs_reject_state.json +361 -0
  156. package/examples/collections/hub/hub_07_quoting_service.json +525 -0
  157. package/examples/collections/hub/hub_08_participant_inactive_stop_transfers.json +706 -0
  158. package/examples/collections/hub/hub_09_duplicate_handling_transfers.json +1377 -0
  159. package/examples/collections/hub/hub_10_on_us_transfers.json +245 -0
  160. package/examples/collections/hub/hub_11_accented_and_spl_chars.json +629 -0
  161. package/examples/collections/hub/hub_12_fspiop_version_1.1.json +646 -0
  162. package/examples/collections/hub/hub_13_bulk_transfers.json +1857 -0
  163. package/examples/collections/iso20022/self_referencing_iso20022.json +926 -0
  164. package/examples/collections/provisioning/testingtoolkitdfsp.json +904 -0
  165. package/examples/environments/dfsp_local_environment.json +46 -0
  166. package/examples/environments/hub_local_environment.json +57 -0
  167. package/jest.config.js +17 -0
  168. package/package.json +199 -0
  169. package/sbom-v18.12.4.csv +1553 -0
  170. package/secrets/keygen.sh +5 -0
  171. package/secrets/privatekey.pem +27 -0
  172. package/secrets/publickey.cer +21 -0
  173. package/secrets/tls/01.pem +132 -0
  174. package/secrets/tls/createSecrets.sh +20 -0
  175. package/secrets/tls/hub_client.csr +32 -0
  176. package/secrets/tls/hub_client_cacert.pem +35 -0
  177. package/secrets/tls/hub_client_cakey.pem +52 -0
  178. package/secrets/tls/hub_client_key.key +52 -0
  179. package/secrets/tls/hub_server.csr +31 -0
  180. package/secrets/tls/hub_server_cacert.pem +35 -0
  181. package/secrets/tls/hub_server_cakey.pem +52 -0
  182. package/secrets/tls/hub_server_cert.pem +132 -0
  183. package/secrets/tls/hub_server_key.key +52 -0
  184. package/secrets/tls/index.txt +1 -0
  185. package/secrets/tls/index.txt.attr +1 -0
  186. package/secrets/tls/openssl-client.cnf +36 -0
  187. package/secrets/tls/openssl-clientca.cnf +71 -0
  188. package/secrets/tls/openssl-server.cnf +39 -0
  189. package/secrets/tls/openssl-serverca.cnf +71 -0
  190. package/secrets/tls/serial.txt +1 -0
  191. package/spec_files/api_definitions/als_admin_1.1/api_spec.yaml +804 -0
  192. package/spec_files/api_definitions/central_admin_1.0/api_spec.yaml +1850 -0
  193. package/spec_files/api_definitions/central_admin_1.0/response_map.json +96 -0
  194. package/spec_files/api_definitions/central_admin_old_9.3/api_spec.yaml +2467 -0
  195. package/spec_files/api_definitions/central_admin_old_9.3/response_map.json +96 -0
  196. package/spec_files/api_definitions/fspiop_1.0/api_spec.yaml +4187 -0
  197. package/spec_files/api_definitions/fspiop_1.0/callback_map.json +568 -0
  198. package/spec_files/api_definitions/fspiop_1.0/mockRef.json +79 -0
  199. package/spec_files/api_definitions/fspiop_1.0/trigger_templates/transaction_request_followup.json +126 -0
  200. package/spec_files/api_definitions/fspiop_1.0/trigger_templates/transaction_request_followup_quotes_only.json +97 -0
  201. package/spec_files/api_definitions/fspiop_1.1/api_spec.yaml +3778 -0
  202. package/spec_files/api_definitions/fspiop_1.1/callback_map.json +568 -0
  203. package/spec_files/api_definitions/fspiop_1.1/mockRef.json +79 -0
  204. package/spec_files/api_definitions/fspiop_1.1/trigger_templates/transaction_request_followup.json +125 -0
  205. package/spec_files/api_definitions/fspiop_2.0/api_spec.yaml +4839 -0
  206. package/spec_files/api_definitions/fspiop_2.0/callback_map.json +716 -0
  207. package/spec_files/api_definitions/fspiop_2.0/mockRef.json +79 -0
  208. package/spec_files/api_definitions/fspiop_2.0/trigger_templates/transaction_request_followup.json +125 -0
  209. package/spec_files/api_definitions/fspiop_2.0_iso20022/api_spec.yaml +8331 -0
  210. package/spec_files/api_definitions/fspiop_2.0_iso20022/callback_map.json +508 -0
  211. package/spec_files/api_definitions/fspiop_2.0_iso20022/mockRef.json +66 -0
  212. package/spec_files/api_definitions/fx-api_2.0/api_spec.yaml +1768 -0
  213. package/spec_files/api_definitions/fx-api_2.0/callback_map.json +188 -0
  214. package/spec_files/api_definitions/fx-api_2.0/mockRef.json +83 -0
  215. package/spec_files/api_definitions/mojaloop_sdk_outbound_scheme_adapter_1.0/api_spec.yaml +2612 -0
  216. package/spec_files/api_definitions/mojaloop_sdk_outbound_scheme_adapter_1.0/mockRef.json +22 -0
  217. package/spec_files/api_definitions/mojaloop_sdk_outbound_scheme_adapter_1.0/response_map.json +35 -0
  218. package/spec_files/api_definitions/mojaloop_simulator_0.1/api_spec.yaml +225 -0
  219. package/spec_files/api_definitions/mojaloop_simulator_sim_1.4/api_spec.yaml +1087 -0
  220. package/spec_files/api_definitions/mojaloop_simulator_sim_1.4/mockRef.json +75 -0
  221. package/spec_files/api_definitions/mojaloop_simulator_sim_1.4/response_map.json +55 -0
  222. package/spec_files/api_definitions/payment_manager_1.4/api_spec.yaml +1389 -0
  223. package/spec_files/api_definitions/sdk-scheme-adapter-backend-v2_1_0-openapi3-snippets_2.1/api_spec.yaml +2834 -0
  224. package/spec_files/api_definitions/sdk-scheme-adapter-outbound-v2_1_0-openapi3-snippets_2.1/api_spec.yaml +3449 -0
  225. package/spec_files/api_definitions/settlements_1.0/api_spec.yaml +983 -0
  226. package/spec_files/api_definitions/settlements_1.0/mockRef.json +38 -0
  227. package/spec_files/api_definitions/settlements_1.0/response_map.json +34 -0
  228. package/spec_files/api_definitions/settlements_2.0/api_spec.yaml +1001 -0
  229. package/spec_files/api_definitions/settlements_2.0/mockRef.json +38 -0
  230. package/spec_files/api_definitions/settlements_2.0/response_map.json +34 -0
  231. package/spec_files/api_definitions/thirdparty_sdk_outbound_0.1/api_spec.yaml +2139 -0
  232. package/spec_files/reports/templates/newman/html_template.html +1202 -0
  233. package/spec_files/reports/templates/newman/pdf_template.html +790 -0
  234. package/spec_files/reports/templates/testcase_definition/table_view.html +1602 -0
  235. package/spec_files/rules_callback/config.json +3 -0
  236. package/spec_files/rules_callback/default.json +2698 -0
  237. package/spec_files/rules_callback/p2p-limit.json +129 -0
  238. package/spec_files/rules_forward/config.json +3 -0
  239. package/spec_files/rules_forward/default.json +482 -0
  240. package/spec_files/rules_response/config.json +3 -0
  241. package/spec_files/rules_response/default.json +295 -0
  242. package/spec_files/rules_validation/config.json +3 -0
  243. package/spec_files/rules_validation/default.json +1 -0
  244. package/spec_files/rules_validation/p2p-limit.json +55 -0
  245. package/spec_files/system_config.json +175 -0
  246. package/spec_files/user_config.json +109 -0
  247. package/src/index.js +67 -0
  248. package/src/lib/MyEventEmitter.js +54 -0
  249. package/src/lib/api-management.js +143 -0
  250. package/src/lib/api-routes/config.js +83 -0
  251. package/src/lib/api-routes/history.js +139 -0
  252. package/src/lib/api-routes/keycloak.js +54 -0
  253. package/src/lib/api-routes/longpolling.js +70 -0
  254. package/src/lib/api-routes/oauth2.js +149 -0
  255. package/src/lib/api-routes/objectstore.js +53 -0
  256. package/src/lib/api-routes/openapi.js +224 -0
  257. package/src/lib/api-routes/outbound.js +134 -0
  258. package/src/lib/api-routes/reports.js +72 -0
  259. package/src/lib/api-routes/rules.js +356 -0
  260. package/src/lib/api-routes/samples.js +92 -0
  261. package/src/lib/api-routes/server-logs.js +44 -0
  262. package/src/lib/api-routes/settings.js +71 -0
  263. package/src/lib/api-server.js +135 -0
  264. package/src/lib/arrayStore.js +101 -0
  265. package/src/lib/callbackHandler.js +201 -0
  266. package/src/lib/config.js +177 -0
  267. package/src/lib/configuration-providers/mb-connection-manager.js +625 -0
  268. package/src/lib/db/adapters/dbAdapter.js +184 -0
  269. package/src/lib/db/dfspMockUsers.js +64 -0
  270. package/src/lib/db/models/mongoDBWrapper.js +78 -0
  271. package/src/lib/eventListenerClient/inboundEventListener.js +176 -0
  272. package/src/lib/fileAdapter.js +57 -0
  273. package/src/lib/httpAgentStore.js +135 -0
  274. package/src/lib/importExport.js +186 -0
  275. package/src/lib/jws/JwsSigning.js +141 -0
  276. package/src/lib/loadSamples.js +128 -0
  277. package/src/lib/logger.js +20 -0
  278. package/src/lib/longpollingEmitter.js +56 -0
  279. package/src/lib/metrics.js +51 -0
  280. package/src/lib/mocking/custom-functions/generic.js +57 -0
  281. package/src/lib/mocking/middleware-functions/ilpModel.js +238 -0
  282. package/src/lib/mocking/middleware-functions/quotesAssociation.js +75 -0
  283. package/src/lib/mocking/middleware-functions/transactionRequestsService.js +78 -0
  284. package/src/lib/mocking/openApiDefinitionsModel.js +64 -0
  285. package/src/lib/mocking/openApiMockHandler.js +466 -0
  286. package/src/lib/mocking/openApiRulesEngine.js +492 -0
  287. package/src/lib/mocking/openApiVersionTools.js +136 -0
  288. package/src/lib/mocking/transformers/fspiopToISO20022.js +230 -0
  289. package/src/lib/mocking/transformers/index.js +41 -0
  290. package/src/lib/notificationEmitter.js +64 -0
  291. package/src/lib/oauth/KeycloakHelper.js +220 -0
  292. package/src/lib/oauth/LoginService.js +133 -0
  293. package/src/lib/oauth/OAuthHelper.js +181 -0
  294. package/src/lib/oauth/OAuthValidator.js +118 -0
  295. package/src/lib/oauth/Wso2Client.js +64 -0
  296. package/src/lib/objectStore/inMemoryImpl.js +50 -0
  297. package/src/lib/objectStore/objectStoreInterface.js +51 -0
  298. package/src/lib/objectStore.js +122 -0
  299. package/src/lib/report-generator/generator.js +126 -0
  300. package/src/lib/report-generator/helpers.js +154 -0
  301. package/src/lib/requestLogger.js +190 -0
  302. package/src/lib/resources/wso2carbon-publickey.cert +20 -0
  303. package/src/lib/rulesEngine.js +95 -0
  304. package/src/lib/rulesEngineModel.js +463 -0
  305. package/src/lib/scripting-engines/postman-sandbox.js +142 -0
  306. package/src/lib/scripting-engines/vm-javascript-sandbox.js +294 -0
  307. package/src/lib/server-logs/adapters/elastic-search.js +102 -0
  308. package/src/lib/server-logs/adapters/grafana.js +0 -0
  309. package/src/lib/server-logs/index.js +75 -0
  310. package/src/lib/socket-server.js +55 -0
  311. package/src/lib/storageAdapter.js +109 -0
  312. package/src/lib/test-outbound/TestCaseRunner.js +173 -0
  313. package/src/lib/test-outbound/getTracing.js +19 -0
  314. package/src/lib/test-outbound/outbound-initiator.js +1107 -0
  315. package/src/lib/uniqueIdGenerator.js +35 -0
  316. package/src/lib/utils.js +89 -0
  317. package/src/lib/utilsInternal.js +56 -0
  318. package/src/lib/webSocketClient/WebSocketClientManager.js +197 -0
  319. 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
+ }