diff --git a/src/audit-resolve.json b/src/audit-resolve.json index cea3d051..149476fb 100644 --- a/src/audit-resolve.json +++ b/src/audit-resolve.json @@ -7,33 +7,33 @@ }, "1589|sqlite>sqlite3>node-pre-gyp>rc>ini": { "decision": "ignore", - "madeAt": 1615383991700, - "expiresAt": 1617975978786 + "madeAt": 1617995554900, + "expiresAt": 1618600328607 }, "1589|00unidentified>sqlite>sqlite3>node-pre-gyp>rc>ini": { "decision": "ignore", - "madeAt": 1615383991701, - "expiresAt": 1617975978786 + "madeAt": 1617995554900, + "expiresAt": 1618600328607 }, "1589|00unidentified>00unidentified>sqlite>sqlite3>node-pre-gyp>rc>ini": { "decision": "ignore", - "madeAt": 1615383991701, - "expiresAt": 1617975978786 + "madeAt": 1617995554900, + "expiresAt": 1618600328607 }, "1589|ava>update-notifier>latest-version>package-json>registry-auth-token>rc>ini": { "decision": "ignore", - "madeAt": 1615383991701, - "expiresAt": 1617975978786 + "madeAt": 1617995554900, + "expiresAt": 1618600328607 }, "1589|ava>update-notifier>latest-version>package-json>registry-url>rc>ini": { "decision": "ignore", - "madeAt": 1615383991701, - "expiresAt": 1617975978786 + "madeAt": 1617995554900, + "expiresAt": 1618600328607 }, "1589|ava>update-notifier>is-installed-globally>global-dirs>ini": { "decision": "ignore", - "madeAt": 1615383991701, - "expiresAt": 1617975978786 + "madeAt": 1617995554900, + "expiresAt": 1618600328607 }, "1654|ava>yargs>y18n": { "decision": "fix", diff --git a/src/lib/objectStore/inMemoryImpl.js b/src/lib/objectStore/inMemoryImpl.js new file mode 100644 index 00000000..e64e3b7c --- /dev/null +++ b/src/lib/objectStore/inMemoryImpl.js @@ -0,0 +1,41 @@ + +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates 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 + http://www.apache.org/licenses/LICENSE-2.0 + 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. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + + * ModusBox + * Vijaya Kumar Guthi (Original Author) + -------------- + ******/ + +const storedObject = { +}; + +const set = async (key, value) => { + storedObject[key] = { ...value }; +}; + +const get = async (key) => ({ ...storedObject[key] }); + +const init = async () => null; + +module.exports = { + set, + get, + init, +}; diff --git a/src/lib/objectStore/objectStoreInterface.js b/src/lib/objectStore/objectStoreInterface.js new file mode 100644 index 00000000..f59fa5b1 --- /dev/null +++ b/src/lib/objectStore/objectStoreInterface.js @@ -0,0 +1,44 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates 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 + http://www.apache.org/licenses/LICENSE-2.0 + 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. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + + * ModusBox + * Vijaya Kumar Guthi (Original Author) + -------------- + ******/ + +const objectStoreImpl = require('./inMemoryImpl'); + +const set = async (key, value) => { + await objectStoreImpl.set(key, value); +}; + +const get = async (key) => { + const value = await objectStoreImpl.get(key); + return value; +}; + +const init = async () => { + await objectStoreImpl.init(); +}; + +module.exports = { + set, + get, + init, +}; diff --git a/src/simulator/api.yaml b/src/simulator/api.yaml index ce8f88fc..ffcd5d29 100644 --- a/src/simulator/api.yaml +++ b/src/simulator/api.yaml @@ -651,6 +651,154 @@ paths: application/json: schema: $ref: '#/components/schemas/errorResponse' + + /validateConsentRequests: + post: + operationId: PostValidateConsentRequests + summary: PostValidateConsentRequests + description: | + The HTTP request `POST /validateConsentRequests` is used to validate ConsentRequests + tags: + - ConsentRequests + requestBody: + description: An incoming consentRequest + content: + application/json: + schema: + $ref: '#/components/schemas/ConsentRequestsPostRequest' + responses: + 200: + description: Response containing validation details + content: + application/json: + schema: + $ref: '#/components/schemas/ConsentRequestsPostResponse' + 400: + description: Malformed or missing required headers or parameters + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + 500: + description: An error occured processing the request + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + /sendOTP: + post: + tags: + - DFSPSim + description: The HTTP request `POST /sendOTP` is used to send OTP to a DFSP user (most likely through SMS). + summary: SendOTP + operationId: SendOTP + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SendOTPRequest' + example: + userName: dfspa.user.name + consentRequestId: 3b346cec-47b3-4def-b870-edb255aaf6c3 + message: '9876' + responses: + 200: + description: Response containing validation details + content: + application/json: + schema: + $ref: '#/components/schemas/SendOTPResponse' + 400: + description: Malformed or missing required headers or parameters + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + 500: + description: An error occured processing the request + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + + /store/consentRequests/{ID}: + get: + tags: + - DFSPSim + operationId: GetConsentRequest + summary: GetConsentRequest + description: | + The HTTP request `GET /store/consentRequests/{ID}` is used by DFSP to load a specified consentRequest. + responses: + 200: + description: Response containing consentRequest details + content: + application/json: + schema: + $ref: '#/components/schemas/ScopesIdResponse' + example: + scopes: + - accountId: dfspa.username.1234 + actions: + - accounts.transfer + - accounts.getBalance + - accountId: dfspa.username.5678 + actions: + - accounts.transfer + - accounts.getBalance + 400: + description: Malformed or missing required headers or parameters + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + 500: + description: An error occured processing the request + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + post: + tags: + - DFSPSim + operationId: StoreConsentRequest + summary: StoreConsentRequest + description: | + The HTTP request `POST /store/consentRequests/{ID}` is used by a DFSP to store consentRequests. + requestBody: + description: The consentRequest to store + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ScopesIdResponse' + example: + scopes: + - accountId: dfspa.username.1234 + actions: + - accounts.transfer + - accounts.getBalance + - accountId: dfspa.username.5678 + actions: + - accounts.transfer + - accounts.getBalance + responses: + 200: + description: OK + 400: + description: Malformed or missing required headers or parameters + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + 500: + description: An error occured processing the request + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + components: schemas: @@ -1776,6 +1924,17 @@ components: - COMMITTED DFSP has successfully performed the transfer. - ABORTED DFSP has aborted the transfer due a rejection or failure to perform the transfer. + ConsentRequestChannelType: + title: ConsentRequestChannelType + type: string + enum: + - WEB + - OTP + description: | + The auth channel being used for the consentRequest. + - "WEB" - The Web auth channel. + - "OTP" - The OTP auth channel. + fulfilNotification: title: TransfersIDPatchResponse type: object @@ -1832,6 +1991,38 @@ components: - authenticationInfo - transactionRequestId + ConsentRequestsPostRequest: + title: ConsentRequestsPostRequest + type: object + description: The object sent in a `POST /consentRequests` request. + properties: + id: + $ref: '#/components/schemas/CorrelationId' + initiatorId: + type: string + description: >- + The id of the PISP who will initiate transactions on a user's + behalf. + scopes: + type: array + items: + $ref: '#/components/schemas/Scope' + authChannels: + type: array + items: + $ref: '#/components/schemas/ConsentRequestChannelType' + callbackUri: + type: string + description: >- + The callback uri that the user will be redirected to after + completing the WEB auth channel. + required: + - id + - initiatorId + - scopes + - authChannels + - callbackUri + AuthorizationResponse: title: AuthorizationResponse type: string @@ -1905,7 +2096,6 @@ components: - accountId - actions - ValidateOTPResponse: title: ValidateOTPResponse type: object @@ -1925,3 +2115,66 @@ components: type: array items: $ref: '#/components/schemas/Scope' + + ConsentRequestsPostResponse: + title: ConsentRequestsPostResponse + type: object + description: | + `POST /validateConsentRequests` response. + properties: + isValid: + type: boolean + authChannels: + type: array + items: + $ref: '#/components/schemas/ConsentRequestChannelType' + authUri: + type: string + description: >- + The callback uri that the pisp app redirects to for user to complete + their login. + UserName: + title: UserName + type: string + minLength: 1 + maxLength: 25 + Password: + title: Password + type: string + minLength: 1 + maxLength: 25 + Message: + title: Message + type: string + description: OTP value or error message + + SendOTPRequest: + title: SendOTPRequest + type: object + description: 'POST /sendOTP request' + properties: + userName: + $ref: '#/components/schemas/UserName' + consentRequestId: + $ref: '#/components/schemas/CorrelationId' + message: + $ref: '#/components/schemas/Message' + required: + - consentRequestId + SendOTPResponse: + title: SendOTPResponse + type: object + description: | + `POST /sendOTP` response. + properties: + otp: + type: string + StoreConsentRequest: + title: StoreConsentRequest + type: object + description: 'store consent request details' + properties: + scopes: + $ref: '#/components/schemas/ScopesIdResponse' + required: + - scopes \ No newline at end of file diff --git a/src/simulator/handlers.js b/src/simulator/handlers.js index b7cd182f..e0015a22 100644 --- a/src/simulator/handlers.js +++ b/src/simulator/handlers.js @@ -30,7 +30,7 @@ const crypto = require('crypto'); require('dotenv').config(); const { getStackOrInspect } = require('@internal/log'); const { ApiErrorCodes } = require('../models/errors.js'); - +const objectStore = require('../lib/objectStore/objectStoreInterface'); const getParticipantsByTypeAndId = async (ctx) => { try { @@ -124,7 +124,6 @@ const putTransfersById = async (ctx) => { } }; - const postQuotes = async (ctx) => { try { const res = await ctx.state.model.quote.create(ctx.request.body); @@ -236,21 +235,21 @@ const getScopesById = async (ctx) => { const res = { scopes: [ { - "accountId": "dfsp.blue.account.one", - "actions": [ - "accounts.getBalance", - "accounts.transfer" - ] + accountId: 'dfsp.blue.account.one', + actions: [ + 'accounts.getBalance', + 'accounts.transfer', + ], }, { - "accountId": "dfsp.blue.account.two", - "actions": [ - "accounts.getBalance", - "accounts.transfer" - ] + accountId: 'dfsp.blue.account.two', + actions: [ + 'accounts.getBalance', + 'accounts.transfer', + ], }, - ] - } + ], + }; ctx.response.body = res; ctx.response.status = 200; }; @@ -259,13 +258,64 @@ const postValidateOTP = async (ctx) => { // fake validation for testing purposes // even auth tokens validate true const res = { - isValid: ctx.request.body.authToken % 2 == 0 - } + isValid: ctx.request.body.authToken % 2 === 0, + }; ctx.state.logger.log(`postValidateOTP is returning body: ${util.inspect(res)}`); ctx.response.body = res; ctx.response.status = 200; }; +const validateConsentRequests = async (ctx) => { + const request = ctx.request.body; + ctx.state.logger.log(`validateConsentRequests request body: ${util.inspect(request)}`); + // default mock reponse, if rules not configured + const res = { + isValid: true, + authChannels: ['WEB'], + authUri: `dfspa.com/authorize?consentRequestId=${request.id}`, + }; + ctx.state.logger.log(`validateConsentRequests is returning body: ${util.inspect(res)}`); + ctx.response.body = res; + ctx.response.status = 200; +}; + +const sendOTP = async (ctx) => { + const request = ctx.request.body; + ctx.state.logger.log(`sendOTP request body: ${util.inspect(request)}`); + // default mock reponse, if rules not configured + const res = { + otp: Math.floor(Math.random() * 90000) + 10000, + }; + await objectStore.set(`${request.consentRequestId}-OTP`, res); + ctx.state.logger.log(`sendOTP is returning body: ${util.inspect(res)}`); + ctx.response.body = res; + ctx.response.status = 200; +}; + +const storeConsentRequest = async (ctx) => { + const { ID } = ctx.state.path.params; + const request = ctx.request.body; + ctx.state.logger.log(`storeConsentRequest request body: ${util.inspect(request)}`); + // default mock reponse, if rules not configured + const res = { + status: 'OK', + }; + await objectStore.set(`${ID}-CR`, request); + ctx.state.logger.log(`sendOTP is returning body: ${util.inspect(res)}`); + ctx.response.body = res; + ctx.response.status = 200; +}; + +const getConsentRequest = async (ctx) => { + const { ID } = ctx.state.path.params; + ctx.state.logger.log(`getConsentRequest : ${ID}`); + // default mock reponse, if rules not configured + const res = await objectStore.get(`${ID}-CR`); + ctx.state.logger.log(`getConsentRequest is returning body: ${util.inspect(res)}`); + ctx.response.body = res; + ctx.response.status = 200; +}; + const map = { '/': { get: healthCheck, @@ -320,10 +370,19 @@ const map = { }, '/validateOTP': { post: postValidateOTP, - } + }, + '/validateConsentRequests': { + post: validateConsentRequests, + }, + '/sendOTP': { + post: sendOTP, + }, + '/store/consentRequests/{ID}': { + get: getConsentRequest, + post: storeConsentRequest, + }, }; - module.exports = { map, }; diff --git a/src/test/simulator.js b/src/test/simulator.js index 084e6911..860ff887 100644 --- a/src/test/simulator.js +++ b/src/test/simulator.js @@ -73,13 +73,56 @@ test('get scopes by Id', async (t) => { t.is(t.context.response.status, 200); }); +test('post validateConsentRequests', async (t) => { + // eslint-disable-next-line no-param-reassign + t.context.request = { + body: { id: '123456' }, + }; + await map['/validateConsentRequests'].post(t.context); + t.truthy(t.context.response.body); + t.is(t.context.response.body.isValid, true); + t.is(t.context.response.status, 200); +}); + +test('post sendOTP', async (t) => { + // eslint-disable-next-line no-param-reassign + t.context.request = { + body: { consentRequestId: '123456' }, + }; + await map['/sendOTP'].post(t.context); + t.truthy(t.context.response.body); + t.is(t.context.response.status, 200); +}); + +test('post storeConsentRequest', async (t) => { + // eslint-disable-next-line no-param-reassign + t.context.state.path = { params: { ID: '123456' } }; + // eslint-disable-next-line no-param-reassign + t.context.request = { + body: { consentRequestId: '123456' }, + }; + await map['/store/consentRequests/{ID}'].post(t.context); + t.truthy(t.context.response.body); + t.is(t.context.response.body.status, 'OK'); + t.is(t.context.response.status, 200); +}); + +test('get consentRequest', async (t) => { + // eslint-disable-next-line no-param-reassign + t.context.state.path = { params: { ID: '123456' } }; + + await map['/store/consentRequests/{ID}'].get(t.context); + t.truthy(t.context.response.body); + t.is(t.context.response.status, 200); +}); + test('post validate otp valid', async (t) => { // eslint-disable-next-line no-param-reassign t.context.request = { body: { authToken: '123456', - consentRequestId: idValue - } + consentRequestId: idValue, + }, }; await map['/validateOTP'].post(t.context); t.truthy(t.context.response.body); @@ -93,8 +136,8 @@ test('post validate otp invalid', async (t) => { t.context.request = { body: { authToken: '123457', - consentRequestId: idValue - } + consentRequestId: idValue, + }, }; await map['/validateOTP'].post(t.context); t.truthy(t.context.response.body); diff --git a/src/test/unit/objectStore/objectStore.test.js b/src/test/unit/objectStore/objectStore.test.js new file mode 100644 index 00000000..4b93a5d4 --- /dev/null +++ b/src/test/unit/objectStore/objectStore.test.js @@ -0,0 +1,37 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates 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 + http://www.apache.org/licenses/LICENSE-2.0 + 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. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + - Sridhar Voruganti - sridhar.voruganti@modusbox.com + -------------- + ******/ +'use strict'; + +const test = require('ava'); +const objectStore = require('../../../lib/objectStore/objectStoreInterface'); + +test('test store and get', async (t) => { + const testObj = { + testKey: 'test=value', + }; + await objectStore.init(); + await objectStore.set('test', testObj); + const storedValue = await objectStore.get('test'); + t.deepEqual(storedValue, testObj, 'Response did not match expected'); +});