diff --git a/config/services.js b/config/services.js index d131c2475..f76152026 100755 --- a/config/services.js +++ b/config/services.js @@ -599,6 +599,7 @@ const getConfigObject = (sourceConfig) => ({ }, KYC: { + ENABLE_HOOK: configParser(sourceConfig, 'bool', 'SERVICES_KYC_ENABLE_HOOK', false), INTERVAL_CHECK: configParser(sourceConfig, 'number', 'SERVICES_KYC_INTERVAL_CHECK', 6 * 60 * 60 * 1000), // 6 hour RE_SYNC_DELAY: configParser(sourceConfig, 'number', 'SERVICES_KYC_RE_SYNC_DELAY', 12), // 12 hour S3: { diff --git a/package.json b/package.json index e4311bbc4..69fc018b3 100755 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "service:exchange": "node runners/exchange", "service:investment": "node runners/investment", "service:kyc": "node runners/kyc", + "service:kyc-hook": "node runners/kycHook", "service:fixer": "node runners/fixer", "db:create": "node src/models/pg/utils/create", "db:migrate": "node src/models/pg/utils/migrate", diff --git a/runners/kycHook.js b/runners/kycHook.js new file mode 100644 index 000000000..4fb699a7b --- /dev/null +++ b/runners/kycHook.js @@ -0,0 +1,5 @@ +const Runner = require('../src/modules/runner'); + +const API = require('../src/services/kycHook'); + +Runner(API); diff --git a/src/services/api/middlewares/kycHook.js b/src/services/api/middlewares/kycHook.js new file mode 100644 index 000000000..63dc5d0cc --- /dev/null +++ b/src/services/api/middlewares/kycHook.js @@ -0,0 +1,7 @@ +module.exports = () => (ctx, next) => { + if (ctx.path === '/kyc/hookReview') { + ctx.headers['content-type'] = 'application/json'; + } + + return next(); +}; diff --git a/src/services/api/routers/kyc.js b/src/services/api/routers/kyc.js index 5b0a12b37..474d889cb 100755 --- a/src/services/api/routers/kyc.js +++ b/src/services/api/routers/kyc.js @@ -4,6 +4,7 @@ const Models = require('../../../models/pg'); const kycController = require('../controllers/kyc'); const AuthMiddleware = require('../middlewares/auth'); const { validateParams } = require('../../../helpers/validation'); +const config = require('../../../../config'); const KycRouter = { schemaUpdateQuestionnaire: Joi.object({ @@ -72,16 +73,18 @@ const KycRouter = { const router = Router(); const authed = AuthMiddleware.authAssert({ active: true, verifyEmail: true }); - /** - * @api {post} /kyc hook Review - * @apiName kyc_post_hook_review - * @apiGroup KYC - * @apiDescription KYC hook Review - * - * @apiSampleRequest /kyc/hookReview - * - */ - router.post('/hookReview', AuthMiddleware.kycVerifyWebHook, KycRouter.hookReview); + if (config.SERVICES.KYC.ENABLE_HOOK) { + /** + * @api {post} /kyc hook Review + * @apiName kyc_post_hook_review + * @apiGroup KYC + * @apiDescription KYC hook Review + * + * @apiSampleRequest /kyc/hookReview + * + */ + router.post('/hookReview', AuthMiddleware.kycVerifyWebHook, KycRouter.hookReview); + } /** * @api {post} /kyc/upload Upload new document diff --git a/src/services/kycHook/Procfile b/src/services/kycHook/Procfile new file mode 100644 index 000000000..0125144de --- /dev/null +++ b/src/services/kycHook/Procfile @@ -0,0 +1 @@ +web: npm run service:kyc-hook diff --git a/src/services/kycHook/app.js b/src/services/kycHook/app.js new file mode 100644 index 000000000..7ad66c07d --- /dev/null +++ b/src/services/kycHook/app.js @@ -0,0 +1,73 @@ +const Koa = require('koa'); +const Router = require('@koa/router'); +const compose = require('koa-compose'); +const bodyParser = require('koa-bodyparser'); + +const utilsMiddleware = require('../api/middlewares/util'); +const securityMiddleware = require('../api/middlewares/security'); +const loggerMiddleware = require('../api/middlewares/logger'); +const errorMiddleware = require('../api/middlewares/error'); +const kycHookMiddleware = require('../api/middlewares/kycHook'); +const AuthMiddleware = require('../api/middlewares/auth'); +const KycRouter = require('../api/routers/kyc'); + +const logger = require('../../modules/logger'); +const CONFIG = require('../../../config'); +const llo = logger.logMeta.bind(null, { service: 'kyc-hook-api' }); + +const mainRouter = new Router(); + +mainRouter.post('/kyc/hookReview', AuthMiddleware.kycVerifyWebHook, KycRouter.hookReview); + +const MainMiddleware = compose([ + loggerMiddleware(), + errorMiddleware(), + + kycHookMiddleware(), + + bodyParser({ + enableTypes: ['json', 'form', 'text'], + jsonLimit: '4mb', + textLimit: '4mb', + formLimit: '4mb', + onerror: utilsMiddleware.onBodyParserError, + }), + + securityMiddleware(), + + mainRouter.routes(), + mainRouter.allowedMethods(), +]); + +const API = () => + new Promise((resolve, reject) => { + const app = new Koa(); + + app.proxy = CONFIG.REMOTE_EXECUTION; + + app.on('error', (error) => { + logger.error('Unexpected API error', { error }); + }); + + require('koa-ctx-cache-control')(app); + + app.use((ctx, next) => { + ctx.cacheControl(false); + return next(); + }); + + app.use(MainMiddleware); + + const server = app.listen(CONFIG.SERVICES.API.PORT, (err) => { + if (err) { + return reject(err); + } + + logger.info('Listening', llo({ port: CONFIG.SERVICES.API.PORT })); + resolve(app); + }); + + server.setTimeout(CONFIG.SERVICES.API.TIMEOUT * 1000); + }); + +module.exports = API; diff --git a/src/services/kycHook/index.js b/src/services/kycHook/index.js new file mode 100644 index 000000000..ba9961ae7 --- /dev/null +++ b/src/services/kycHook/index.js @@ -0,0 +1,14 @@ +const app = require('./app'); +const Utils = require('../../helpers/utils'); + +const KycHookService = { + NEED_CONNECTIONS: ['postgre', 'redis'], + + start() { + return app(); + }, + + stop: Utils.noop, +}; + +module.exports = KycHookService; diff --git a/test/unit/services/api/middlewares/kycHook.spec.js b/test/unit/services/api/middlewares/kycHook.spec.js new file mode 100644 index 000000000..c7700d54c --- /dev/null +++ b/test/unit/services/api/middlewares/kycHook.spec.js @@ -0,0 +1,48 @@ +const path = require('path'); +const sinon = require('sinon'); + +const kycHookMiddleware = require(path.join(srcDir, '/services/api/middlewares/kycHook')); + +describe('Middleware: kyc hook', () => { + let sandbox = null; + + beforeEach(async () => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox && sandbox.restore(); + }); + + it('Should add header for kyc route', async () => { + const middleware = kycHookMiddleware(); + + const ctx = { + path: '/kyc/hookReview', + headers: {}, + }; + + const next = sandbox.stub().returns('data'); + + const res = await middleware(ctx, next); + + expect(res).to.eq('data'); + expect(ctx.headers['content-type']).to.be.eq('application/json'); + }); + + it('Should not add header for other route', async () => { + const middleware = kycHookMiddleware(); + + const ctx = { + path: '/kyc/others', + headers: {}, + }; + + const next = sandbox.stub().returns('data'); + + const res = await middleware(ctx, next); + + expect(res).to.eq('data'); + expect(ctx.headers['content-type']).not.to.exist; + }); +}); diff --git a/test/unit/services/api/routers/kyc.spec.js b/test/unit/services/api/routers/kyc.spec.js index ec0769cc6..fad503122 100644 --- a/test/unit/services/api/routers/kyc.spec.js +++ b/test/unit/services/api/routers/kyc.spec.js @@ -3,6 +3,7 @@ const sinon = require('sinon'); const Router = require('@koa/router'); const sequelizeMockingMocha = require('sequelize-mocking').sequelizeMockingMocha; +const config = require(path.join(srcDir, '/../config')); const authMiddleware = require(path.join(srcDir, '/services/api/middlewares/auth')); const Models = require(path.join(srcDir, '/models/pg')); const KycRouter = require(path.join(srcDir, '/services/api/routers/kyc')); @@ -43,19 +44,35 @@ describe('Router: Kyc', () => { it('Should get router', async () => { const authed = (ctx, next) => next(); const authAssert = sandbox.stub(authMiddleware, 'authAssert').returns(authed); - const kycVerifyWebHook = sandbox.stub(authMiddleware, 'kycVerifyWebHook').returns(authed); const router = await KycRouter.router(); expect(authAssert.calledWith({ verifyEmail: true, active: true })); expect(router instanceof Router).to.be.true; - expect(router.post.calledWith('/hookReview', kycVerifyWebHook, KycRouter.hookReview)).to.be.true; expect(router.post.calledWith('/questionnaire', authed, KycRouter.updateQuestionnaire)).to.be.true; expect(router.post.calledWith('/upload', authed, KycRouter.upload)).to.be.true; expect(router.get.calledWith('/', authed, KycRouter.getStatus)).to.be.true; }); + it('Should get router when is hook enable', async () => { + const oldConfigKycEnableHook = config.SERVICES.KYC.ENABLE_HOOK; + config.SERVICES.KYC.ENABLE_HOOK = true; + + const authed = (ctx, next) => next(); + const authAssert = sandbox.stub(authMiddleware, 'authAssert').returns(authed); + const kycVerifyWebHook = sandbox.stub(authMiddleware, 'kycVerifyWebHook').returns(authed); + + const router = await KycRouter.router(); + + expect(authAssert.calledWith({ verifyEmail: true, active: true })); + + expect(router instanceof Router).to.be.true; + expect(router.post.calledWith('/hookReview', kycVerifyWebHook, KycRouter.hookReview)).to.be.true; + + config.SERVICES.KYC.ENABLE_HOOK = oldConfigKycEnableHook; + }); + it('Should upload with padding', async () => { const ctx = { state: { @@ -166,8 +183,8 @@ describe('Router: Kyc', () => { }; const stubHookReview = sandbox.stub(KycController, 'hookReview').resolves('ok'); - - await KycRouter.hookReview(ctx); + const next = sandbox.stub(); + await KycRouter.hookReview(ctx, next); expect(ctx.body).to.eq('ok'); expect(