Skip to content
Open
1 change: 1 addition & 0 deletions config/services.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
5 changes: 5 additions & 0 deletions runners/kycHook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const Runner = require('../src/modules/runner');

const API = require('../src/services/kycHook');

Runner(API);
7 changes: 7 additions & 0 deletions src/services/api/middlewares/kycHook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = () => (ctx, next) => {
if (ctx.path === '/kyc/hookReview') {
ctx.headers['content-type'] = 'application/json';
}

return next();
};
23 changes: 13 additions & 10 deletions src/services/api/routers/kyc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/services/kycHook/Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: npm run service:kyc-hook
73 changes: 73 additions & 0 deletions src/services/kycHook/app.js
Original file line number Diff line number Diff line change
@@ -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;
14 changes: 14 additions & 0 deletions src/services/kycHook/index.js
Original file line number Diff line number Diff line change
@@ -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;
48 changes: 48 additions & 0 deletions test/unit/services/api/middlewares/kycHook.spec.js
Original file line number Diff line number Diff line change
@@ -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;
});
});
25 changes: 21 additions & 4 deletions test/unit/services/api/routers/kyc.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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(
Expand Down