From 10ea3cb1838a61fe5b38d3cc5d625745d402cc21 Mon Sep 17 00:00:00 2001 From: aleserche Date: Tue, 30 Oct 2018 16:44:54 +0500 Subject: [PATCH 01/11] Update ShuftiPro provider. --- src/config.ts | 2 +- src/providers/kyc/shuftipro.provider.ts | 31 +++++++++++-------------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/config.ts b/src/config.ts index f9f5af1..33497da 100644 --- a/src/config.ts +++ b/src/config.ts @@ -187,7 +187,7 @@ export default { shuftipro: { clientId: KYC_SHUFTIPRO_CLIENT_ID, secretKey: KYC_SHUFTIPRO_SECRET_KEY, - baseUrl: 'https://api.shuftipro.com', + baseUrl: 'https://shuftipro.com/api', callbackUrl: KYC_SHUFTIPRO_CALLBACK_URL, redirectUrl: KYC_SHUFTIPRO_REDIRECT_URL, allowRecreateSession: (KYC_SHUFTIPRO_ALLOW_RECREATE_SESSION === 'true') || false diff --git a/src/providers/kyc/shuftipro.provider.ts b/src/providers/kyc/shuftipro.provider.ts index 0d1e969..e725d7c 100644 --- a/src/providers/kyc/shuftipro.provider.ts +++ b/src/providers/kyc/shuftipro.provider.ts @@ -48,9 +48,13 @@ export class ShuftiproProvider implements KycProviderInterface { await this.localInitKycProcess(investor, postData.reference); const options = { + 'auth': { + 'user': config.kyc.shuftipro.clientId, + 'pass': config.kyc.shuftipro.secretKey, + }, 'method': 'POST', 'headers': { - 'content-type': 'application/x-www-form-urlencoded' + 'content-type': 'application/json' }, 'path': '/', 'body': qs.stringify(postData) @@ -237,30 +241,23 @@ export class ShuftiproProvider implements KycProviderInterface { } private preparePostData(user: Investor): any { - const verificationServices = { - first_name: user.firstName, - last_name: user.lastName, - dob: user.dob, - background_check: '0' - }; const postData = { - client_id: this.clientId, reference: uuid.v4(), email: user.email, - phone_number: user.phone, country: user.country, - lang: 'en', + language: 'EN', + verification_mode: 'any', callback_url: config.kyc.shuftipro.callbackUrl, redirect_url: config.kyc.shuftipro.redirectUrl, - verification_services: JSON.stringify(verificationServices) + background_checks: { + name: { + first_name: user.firstName, + last_name: user.lastName + }, + dob: user.dob + } }; - let rawData: string = ''; - Object.keys(postData).sort().forEach(function(value) { - rawData += postData[value]; - }); - postData['signature'] = this.signature(rawData); - return postData; } From 0c13c37f3ff476a29246712303dae8c5df019e45 Mon Sep 17 00:00:00 2001 From: aleserche Date: Wed, 31 Oct 2018 21:25:19 +0500 Subject: [PATCH 02/11] Update shuftipro api --- src/config.ts | 2 +- src/entities/shuftipro.kyc.result.ts | 26 +++-- src/index.d.ts | 9 +- src/providers/kyc/shuftipro.provider.ts | 146 ++++++++++++------------ 4 files changed, 99 insertions(+), 84 deletions(-) diff --git a/src/config.ts b/src/config.ts index 33497da..89e76bc 100644 --- a/src/config.ts +++ b/src/config.ts @@ -187,7 +187,7 @@ export default { shuftipro: { clientId: KYC_SHUFTIPRO_CLIENT_ID, secretKey: KYC_SHUFTIPRO_SECRET_KEY, - baseUrl: 'https://shuftipro.com/api', + baseUrl: 'https://shuftipro.com', callbackUrl: KYC_SHUFTIPRO_CALLBACK_URL, redirectUrl: KYC_SHUFTIPRO_REDIRECT_URL, allowRecreateSession: (KYC_SHUFTIPRO_ALLOW_RECREATE_SESSION === 'true') || false diff --git a/src/entities/shuftipro.kyc.result.ts b/src/entities/shuftipro.kyc.result.ts index ba3abea..54b0a9d 100644 --- a/src/entities/shuftipro.kyc.result.ts +++ b/src/entities/shuftipro.kyc.result.ts @@ -6,16 +6,25 @@ export class ShuftiproKycResult { id: ObjectID; @Column() - statusCode?: string; + event?: string; @Column() - message: string; + reference?: string; @Column() - reference?: string; + token?: string; + + @Column() + verificationUrl?: string; + + @Column() + verificationResult?: string | null; + + @Column() + verificationData?: any; @Column() - signature?: string; + declinedReason?: any; @Column() error?: boolean; @@ -28,11 +37,14 @@ export class ShuftiproKycResult { static createShuftiproKycResult(data: ShuftiproInitResult): ShuftiproKycResult { const kycResult = new ShuftiproKycResult(); - kycResult.statusCode = data.status_code; - kycResult.message = data.message; + kycResult.event = data.event; + kycResult.token = data.token; kycResult.reference = data.reference; - kycResult.signature = data.signature; + kycResult.verificationData = data.verification_data; + kycResult.verificationResult = data.verification_result; + kycResult.verificationUrl = data.verification_url; kycResult.timestamp = data.timestamp; + kycResult.declinedReason = data.declined_reason; if (data.error) { kycResult.error = data.error; diff --git a/src/index.d.ts b/src/index.d.ts index 86d017f..2c0c40d 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -257,11 +257,14 @@ declare interface JumioInitResult extends KycInitResult { } declare interface ShuftiproInitResult extends KycInitResult { - status_code?: string; - message: string; + event?: string; reference?: string; - signature?: string; error?: boolean; + token?: string; + verification_url?: string; + verification_result?: string | null; + verification_data?: any; + declined_reason?: any; } declare interface KycScanStatus { diff --git a/src/providers/kyc/shuftipro.provider.ts b/src/providers/kyc/shuftipro.provider.ts index e725d7c..d4ff04c 100644 --- a/src/providers/kyc/shuftipro.provider.ts +++ b/src/providers/kyc/shuftipro.provider.ts @@ -6,7 +6,7 @@ import * as crypto from 'crypto'; import * as qs from 'querystring'; import * as uuid from 'node-uuid'; import { AuthorizedRequest } from '../../requests/authorized.request'; -import { KYC_STATUS_VERIFIED, KYC_STATUS_FAILED, KYC_STATUS_PENDING, Investor, KYC_STATUS_NOT_VERIFIED } from '../../entities/investor'; +import { KYC_STATUS_VERIFIED, KYC_STATUS_FAILED, KYC_STATUS_PENDING, Investor } from '../../entities/investor'; import { KycAlreadyVerifiedError, KycFailedError, KycPendingError, KycShuftiProInvalidSignature } from '../../exceptions/exceptions'; import { getConnection } from 'typeorm'; import { ShuftiproKycResult } from '../../entities/shuftipro.kyc.result'; @@ -14,6 +14,15 @@ import { Web3ClientInterface, Web3ClientType } from '../../services/web3.client' const mongo = require('mongodb'); +const LOCAL_INIT = 'local.init'; +const REQUEST_PENDING = 'request.pending'; +const REQUEST_INVALID = 'request.invalid'; +const REQUEST_CANCELLED = 'request.cancelled'; +const REQUEST_TIMEOUT = 'request.timeout'; +const REQUEST_UNAUTORIZED = 'request.unautorized'; +const VERIFICATION_ACCEPTED = 'verification.accepted'; +const VERIFICATION_DECLINED = 'verification.declined'; + @injectable() export class ShuftiproProvider implements KycProviderInterface { @@ -36,73 +45,60 @@ export class ShuftiproProvider implements KycProviderInterface { }); } - async init(investor: Investor): Promise { - const logger = this.logger.sub({ email: investor.email }).addPrefix('[init] '); + async init(investor: Investor): Promise { + const logger = this.logger.sub({ email: investor.email }).addPrefix('[init]'); - try { - logger.debug('Prepare investor for identification'); - - if (this.kycEnabled) { - const postData = this.preparePostData(investor); - - await this.localInitKycProcess(investor, postData.reference); - - const options = { - 'auth': { - 'user': config.kyc.shuftipro.clientId, - 'pass': config.kyc.shuftipro.secretKey, - }, - 'method': 'POST', - 'headers': { - 'content-type': 'application/json' - }, - 'path': '/', - 'body': qs.stringify(postData) - }; - - const kycInitResponse = await request.json(this.baseUrl, options); - - if (!kycInitResponse.error) { - const signature = this.signature(kycInitResponse.status_code + kycInitResponse.message + kycInitResponse.reference); - if (signature === kycInitResponse.signature) { - await this.saveKycInitResult(investor, kycInitResponse); - this.logger.info('Successful init'); - return { ...kycInitResponse, timestamp: (new Date()).toISOString() } as ShuftiproInitResult; - } - - this.logger.exception('Invalid signature'); - throw new KycShuftiProInvalidSignature('Invalid signature'); - } + if (this.kycEnabled) { + const postData = this.preparePostData(investor); + await this.localInitKycProcess(investor, postData.reference); - this.logger.exception(kycInitResponse.message); - throw new Error(kycInitResponse.message); - } else { - return { - message: 'KYC disabled', - status_code: 'SP1' - } as ShuftiproInitResult; + const options = { + headers: { + 'content-type': 'application/json', + 'autorization': this.authOption() + } + }; + + const response = await request.post(this.baseUrl + '/api', options, JSON.stringify(postData)); + const kycInitResponse = JSON.parse(response.content); + + if (!kycInitResponse.error) { + const signature = this.signature(kycInitResponse + config.kyc.shuftipro.secretKey); + if (signature === response.headers['sp_signature']) { + await this.saveKycInitResult(investor, kycInitResponse); + this.logger.info('Successful init'); + return { + event: kycInitResponse.event, + error: kycInitResponse.error, + verification_url: kycInitResponse.verification_url, + reference: postData.reference, + timestamp: (new Date()).toISOString() + } as ShuftiproInitResult; + } } - } catch (error) { - logger.exception({ error }); - - throw error; + throw new Error(kycInitResponse.error); } + + return { + event: VERIFICATION_ACCEPTED, + timestamp: (new Date()).toISOString() + } as ShuftiproInitResult; } async getInitStatus(req: AuthorizedRequest, res: any, next: any): Promise { res.json(await this.processKycStatus(req.user)); } - successUpload(req: any, res: any, next: any) { + public successUpload(req: any, res: any, next: any) { throw new Error('Method not supported.'); } async callback(req: any, res: any, next: any): Promise { - await this.processCallback(req.body); + await this.processCallback(req.body, req.headers['sp_signature']); res.status(200).send(); } - async processCallback(kycResultRequest: ShuftiproInitResult): Promise { + async processCallback(kycResultRequest: ShuftiproInitResult, spSignature: string): Promise { const kycResultRepo = getConnection().getMongoRepository(ShuftiproKycResult); const investorRepo = getConnection().getMongoRepository(Investor); const storedKycResult = await kycResultRepo.findOne({ where: {'reference': kycResultRequest.reference} }); @@ -119,15 +115,15 @@ export class ShuftiproProvider implements KycProviderInterface { return; } - const signature = this.signature(kycResult.statusCode + kycResult.message + kycResult.reference); - if (signature === kycResult.signature) { - switch (kycResult.statusCode) { - case 'SP1': + const signature = this.spSignature(kycResultRequest); + if (signature === spSignature) { + switch (kycResult.event) { + case VERIFICATION_ACCEPTED: investor.kycStatus = KYC_STATUS_VERIFIED; investor.kycInitResult = kycResult; await this.web3Client.addAddressToWhiteList(investor.ethWallet.address); break; - case 'SP0': + case VERIFICATION_DECLINED: investor.kycStatus = KYC_STATUS_FAILED; investor.kycInitResult = kycResult; break; @@ -153,7 +149,7 @@ export class ShuftiproProvider implements KycProviderInterface { throw new KycPendingError('Your account verification is pending. Please wait for status update'); } - if ((user.kycInitResult as ShuftiproKycResult).statusCode === 'SP1') { + if ((user.kycInitResult as ShuftiproKycResult).event === VERIFICATION_ACCEPTED) { return user.kycInitResult as ShuftiproInitResult; } @@ -168,9 +164,8 @@ export class ShuftiproProvider implements KycProviderInterface { try { const currentStatus = await this.getKycStatus(user); if (currentStatus.error - || (currentStatus.status_code !== 'SP2' - && currentStatus.status_code !== 'SP1' - && currentStatus.status_code !== 'SP26' + || (currentStatus.event !== REQUEST_PENDING + && currentStatus.event !== VERIFICATION_ACCEPTED )) { return await this.createNewKycProcess(user); } @@ -183,23 +178,21 @@ export class ShuftiproProvider implements KycProviderInterface { } } - private async getKycStatus(user: Investor): Promise { - const kycInitResult = user.kycInitResult as ShuftiproInitResult; + private async getKycStatus(investor: Investor): Promise { + this.logger.debug('get kyc status'); + const kycInitResult = investor.kycInitResult as ShuftiproInitResult; const postData = { - client_id: this.clientId, - reference: kycInitResult.reference, - signature: this.signature(this.clientId + kycInitResult.reference) + reference: kycInitResult.reference }; const options = { - 'method': 'POST', 'headers': { - 'content-type': 'application/x-www-form-urlencoded' - }, - 'body': qs.stringify(postData) + 'content-type': 'application/json', + 'Authorization': this.authOption() + } }; - const response = await request.post(this.baseUrl + '/status', options); + const response = await request.post(this.baseUrl + '/api/status', options, JSON.stringify(postData)); if (response.content.length > 0) { const result = JSON.parse(response.content); if (!result.error) { @@ -212,11 +205,14 @@ export class ShuftiproProvider implements KycProviderInterface { } return { - error: true, - message: 'Empty response' + error: true } as ShuftiproInitResult; } + private authOption() { + return 'Basic ' + Buffer.from(`${config.kyc.shuftipro.clientId}:${config.kyc.shuftipro.secretKey}`).toString('base64'); + } + private signature(data: string): string { return crypto.createHash('sha256').update(data + this.secretKey, 'utf8').digest('hex'); } @@ -225,7 +221,7 @@ export class ShuftiproProvider implements KycProviderInterface { const shuftiproKycResultRepo = getConnection().getMongoRepository(ShuftiproKycResult); const localInitKyc = new ShuftiproKycResult(); - localInitKyc.message = 'Local init'; + localInitKyc.event = LOCAL_INIT; localInitKyc.reference = reference; localInitKyc.timestamp = (new Date()).toISOString(); localInitKyc.user = user.id; @@ -268,4 +264,8 @@ export class ShuftiproProvider implements KycProviderInterface { await investorRepo.save(user); return kycInitResult; } + + private spSignature(data: any): string { + return crypto.createHash('sha256').update(data + config.kyc.shuftipro.secretKey).digest('hex'); + } } From 3f722d59a64774716f38f21552a57f125713e69c Mon Sep 17 00:00:00 2001 From: aleserche Date: Thu, 1 Nov 2018 00:48:03 +0500 Subject: [PATCH 03/11] Refactoring. --- src/index.d.ts | 41 +++++++- src/ioc.container.ts | 5 + src/providers/kyc/shuftipro.provider.ts | 118 ++++++++---------------- src/services/shuftipro.client.ts | 83 +++++++++++++++++ 4 files changed, 167 insertions(+), 80 deletions(-) create mode 100644 src/services/shuftipro.client.ts diff --git a/src/index.d.ts b/src/index.d.ts index 2c0c40d..f6c7953 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -262,11 +262,50 @@ declare interface ShuftiproInitResult extends KycInitResult { error?: boolean; token?: string; verification_url?: string; - verification_result?: string | null; + verification_result?: any; verification_data?: any; declined_reason?: any; } +declare interface ShuftiProName { + first_name: string; + last_name: string; +} + +declare interface ShuftiProDocument { + supported_types: string[]; + name: ShuftiProName; + dob: string; +} + +declare interface ShuftiProBackgroundChecks { + name: ShuftiProName; + dob: string; +} + +declare interface ShuftiProVerificationData { + reference: string; + country: string; + callback_url: string; + document: ShuftiProDocument; + background_checks: ShuftiProBackgroundChecks; +} + +declare interface ShuftiProVerificationResponse { + reference: string; + event: string; + error: any; + token: string; + verification_url: string; + verification_result: number | null; + verification_data: any; +} + +declare interface ShuftiProStatusResponse { + reference: string; + event: string; +} + declare interface KycScanStatus { timestamp: string; scanReference: string; diff --git a/src/ioc.container.ts b/src/ioc.container.ts index cecbf97..df1bcda 100644 --- a/src/ioc.container.ts +++ b/src/ioc.container.ts @@ -25,6 +25,7 @@ import { GatewayController } from './controllers/gateway.controller'; import { EmailTemplateService, EmailTemplateServiceType } from './services/email.template.service'; import { JumioProvider } from './providers/kyc/jumio.provider'; import { ShuftiproProvider } from './providers/kyc/shuftipro.provider'; +import { ShuftiProClientInterface, ShuftiProClientType, ShuftiProClient } from './services/shuftipro.client'; let container = new Container(); @@ -60,6 +61,10 @@ container.bind(CoinpaymentsClientType).to(Coinpayme container.bind(PaymentsServiceType).to(PaymentsService).inSingletonScope(); container.bind(IPNServiceType).to(IPNService).inSingletonScope(); container.bind(EmailTemplateServiceType).to(EmailTemplateService).inSingletonScope(); +container.bind(ShuftiProClientType).toConstantValue(new ShuftiProClient( + config.kyc.shuftipro.clientId, + config.kyc.shuftipro.secretKey +)); const auth = new Auth(container.get(AuthClientType)); // middlewares diff --git a/src/providers/kyc/shuftipro.provider.ts b/src/providers/kyc/shuftipro.provider.ts index d4ff04c..b2acdef 100644 --- a/src/providers/kyc/shuftipro.provider.ts +++ b/src/providers/kyc/shuftipro.provider.ts @@ -11,6 +11,7 @@ import { KycAlreadyVerifiedError, KycFailedError, KycPendingError, KycShuftiProI import { getConnection } from 'typeorm'; import { ShuftiproKycResult } from '../../entities/shuftipro.kyc.result'; import { Web3ClientInterface, Web3ClientType } from '../../services/web3.client'; +import { ShuftiProClientType, ShuftiProClientInterface } from '../../services/shuftipro.client'; const mongo = require('mongodb'); @@ -27,17 +28,12 @@ const VERIFICATION_DECLINED = 'verification.declined'; export class ShuftiproProvider implements KycProviderInterface { private logger = Logger.getInstance('SHUFTIPRO_KYC_CLIENT'); - clientId: string; - secretKey: string; - baseUrl: string; kycEnabled: boolean; constructor( - @inject(Web3ClientType) private web3Client: Web3ClientInterface + @inject(Web3ClientType) private web3Client: Web3ClientInterface, + @inject(ShuftiProClientType) private shuftiClient: ShuftiProClientInterface ) { - this.clientId = config.kyc.shuftipro.clientId; - this.secretKey = config.kyc.shuftipro.secretKey; - this.baseUrl = config.kyc.shuftipro.baseUrl; this.kycEnabled = config.kyc.enabled; request.defaults({ @@ -49,34 +45,18 @@ export class ShuftiproProvider implements KycProviderInterface { const logger = this.logger.sub({ email: investor.email }).addPrefix('[init]'); if (this.kycEnabled) { - const postData = this.preparePostData(investor); - await this.localInitKycProcess(investor, postData.reference); + const verificationData = this.preparePostData(investor); + await this.localInitKycProcess(investor, verificationData.reference); - const options = { - headers: { - 'content-type': 'application/json', - 'autorization': this.authOption() - } - }; - - const response = await request.post(this.baseUrl + '/api', options, JSON.stringify(postData)); - const kycInitResponse = JSON.parse(response.content); - - if (!kycInitResponse.error) { - const signature = this.signature(kycInitResponse + config.kyc.shuftipro.secretKey); - if (signature === response.headers['sp_signature']) { - await this.saveKycInitResult(investor, kycInitResponse); - this.logger.info('Successful init'); - return { - event: kycInitResponse.event, - error: kycInitResponse.error, - verification_url: kycInitResponse.verification_url, - reference: postData.reference, - timestamp: (new Date()).toISOString() - } as ShuftiproInitResult; - } + const verificationResponse = await this.shuftiClient.init(verificationData); + + if (!verificationResponse.error) { + await this.saveKycInitResult(investor, verificationResponse); + this.logger.info('Successful init'); + return verificationResponse; } - throw new Error(kycInitResponse.error); + logger.exception(verificationResponse.error); + throw new Error(verificationResponse.error); } return { @@ -181,40 +161,9 @@ export class ShuftiproProvider implements KycProviderInterface { private async getKycStatus(investor: Investor): Promise { this.logger.debug('get kyc status'); const kycInitResult = investor.kycInitResult as ShuftiproInitResult; - const postData = { - reference: kycInitResult.reference - }; - - const options = { - 'headers': { - 'content-type': 'application/json', - 'Authorization': this.authOption() - } - }; - - const response = await request.post(this.baseUrl + '/api/status', options, JSON.stringify(postData)); - if (response.content.length > 0) { - const result = JSON.parse(response.content); - if (!result.error) { - const signature = this.signature(result.status_code + result.message + result.reference); - if (signature === result.signature) { - return { ...result, timestamp: (new Date()).toISOString() } as ShuftiproInitResult; - } - throw new KycShuftiProInvalidSignature('Invalid signature'); - } - } - - return { - error: true - } as ShuftiproInitResult; - } - - private authOption() { - return 'Basic ' + Buffer.from(`${config.kyc.shuftipro.clientId}:${config.kyc.shuftipro.secretKey}`).toString('base64'); - } - private signature(data: string): string { - return crypto.createHash('sha256').update(data + this.secretKey, 'utf8').digest('hex'); + const statusResponse = await this.shuftiClient.status(kycInitResult.reference); + return { ...statusResponse, timestamp: (new Date()).toISOString() } as ShuftiproInitResult; } private async localInitKycProcess(user: Investor, reference: string): Promise { @@ -229,32 +178,43 @@ export class ShuftiproProvider implements KycProviderInterface { await shuftiproKycResultRepo.save(localInitKyc); } - private async saveKycInitResult(user: Investor, kycInitResponse: ShuftiproInitResult): Promise { + private async saveKycInitResult(user: Investor, kycInitResponse: ShuftiProVerificationResponse): Promise { const shuftiproKycResultRepo = getConnection().getMongoRepository(ShuftiproKycResult); - const kycInitResult = ShuftiproKycResult.createShuftiproKycResult({ ...kycInitResponse, timestamp: (new Date()).toISOString() }); + const kycInitResult = ShuftiproKycResult.createShuftiproKycResult({ + error: kycInitResponse.error, + event: kycInitResponse.event, + reference: kycInitResponse.reference, + verification_url: kycInitResponse.verification_url, + verification_result: kycInitResponse.verification_result, + timestamp: (new Date()).toISOString() + }); await shuftiproKycResultRepo.save(shuftiproKycResultRepo.create({ ...kycInitResult, user: user.id })); } - private preparePostData(user: Investor): any { - const postData = { + private preparePostData(user: Investor): ShuftiProVerificationData { + const verificationData = { + callback_url: config.kyc.shuftipro.callbackUrl, reference: uuid.v4(), - email: user.email, country: user.country, - language: 'EN', - verification_mode: 'any', - callback_url: config.kyc.shuftipro.callbackUrl, - redirect_url: config.kyc.shuftipro.redirectUrl, + document: { + supported_types: ['passport', 'id_card', 'driving_license'], + name: { + first_name: user.firstName, + last_name: user.lastName + }, + dob: user.dob + }, background_checks: { name: { - first_name: user.firstName, - last_name: user.lastName + first_name: user.firstName, + last_name: user.lastName }, dob: user.dob } - }; + } as ShuftiProVerificationData; - return postData; + return verificationData; } private async createNewKycProcess(user: Investor): Promise { diff --git a/src/services/shuftipro.client.ts b/src/services/shuftipro.client.ts new file mode 100644 index 0000000..3d71d21 --- /dev/null +++ b/src/services/shuftipro.client.ts @@ -0,0 +1,83 @@ +import * as request from 'web-request'; +import * as crypto from 'crypto'; +import { injectable } from 'inversify'; + +export class InvalidSignature extends Error {} +export class InvalidRequest extends Error {} + +export interface ShuftiProClientInterface { + init(verificationData: ShuftiProVerificationData): Promise; + status(reference: string): Promise; +} + +@injectable() +export class ShuftiProClient implements ShuftiProClientInterface { + private baseUrlApi: string; + private rootPath = '/'; + private statusPath = '/status'; + private clientId: string; + private secretKey: string; + + constructor(clientId: string, secretKey: string) { + this.baseUrlApi = 'https://shuftipro.com/api'; + this.clientId = clientId; + this.secretKey = secretKey; + + request.defaults({ throwResponseError: false }); + } + + public async init(verificationData: ShuftiProVerificationData): Promise { + const options = { + headers: { + 'Content-Type': 'application/json', + 'Authorization': this.basicAuth() + } + }; + + try { + const response = await request.post(this.baseUrlApi + this.rootPath, options, JSON.stringify(verificationData)); + + if (response.headers.sp_signature === this.spSignature(response.content)) { + return JSON.parse(response.content) as ShuftiProVerificationResponse; + } + throw new InvalidSignature('Invalid signature'); + } catch (error) { + throw new Error(error); + } + } + + public async status(reference: string): Promise { + const options = { + headers: { + 'Content-Type': 'application/json', + 'Authorization': this.basicAuth() + } + }; + + try { + const response = await request.post(this.baseUrlApi + this.statusPath, options, JSON.stringify({ reference })); + + if (response.statusCode === 404) { + throw new InvalidRequest('Invalid Request'); + } + + if (response.headers.sp_signature === this.spSignature(response.content)) { + return JSON.parse(response.content) as ShuftiProStatusResponse; + } + throw new InvalidSignature('Invalid signature'); + } catch (error) { + throw new Error(error); + } + } + + private basicAuth(): string { + return 'Basic ' + Buffer.from(`${this.clientId}:${this.secretKey}`).toString('base64'); + } + + private spSignature(data: any): string { + return crypto.createHash('sha256').update(data + this.secretKey).digest('hex'); + } +} + +const ShuftiProClientType = Symbol('ShuftiProClientInterface'); +export { ShuftiProClientType }; From c58cdde9571a2675a7e4c4dfdda5baafe10f1706 Mon Sep 17 00:00:00 2001 From: aleserche Date: Thu, 1 Nov 2018 00:52:54 +0500 Subject: [PATCH 04/11] Refactoring. --- src/providers/kyc/shuftipro.provider.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/providers/kyc/shuftipro.provider.ts b/src/providers/kyc/shuftipro.provider.ts index b2acdef..6f1346b 100644 --- a/src/providers/kyc/shuftipro.provider.ts +++ b/src/providers/kyc/shuftipro.provider.ts @@ -151,10 +151,7 @@ export class ShuftiproProvider implements KycProviderInterface { } return user.kycInitResult as ShuftiproInitResult; } catch (error) { - if (error.constructor === KycShuftiProInvalidSignature) { - return await this.createNewKycProcess(user); - } - throw error; + return await this.createNewKycProcess(user); } } From 17633cf1f6e5cb828ef8b94a1d573890e32e9247 Mon Sep 17 00:00:00 2001 From: aleserche Date: Thu, 1 Nov 2018 00:56:32 +0500 Subject: [PATCH 05/11] Fix. --- src/index.d.ts | 1 + src/providers/kyc/shuftipro.provider.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/index.d.ts b/src/index.d.ts index f6c7953..e02072d 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -292,6 +292,7 @@ declare interface ShuftiProVerificationData { } declare interface ShuftiProVerificationResponse { + message?: string; reference: string; event: string; error: any; diff --git a/src/providers/kyc/shuftipro.provider.ts b/src/providers/kyc/shuftipro.provider.ts index 6f1346b..fe81204 100644 --- a/src/providers/kyc/shuftipro.provider.ts +++ b/src/providers/kyc/shuftipro.provider.ts @@ -53,6 +53,7 @@ export class ShuftiproProvider implements KycProviderInterface { if (!verificationResponse.error) { await this.saveKycInitResult(investor, verificationResponse); this.logger.info('Successful init'); + verificationResponse.message = verificationResponse.verification_url; return verificationResponse; } logger.exception(verificationResponse.error); From 51deeefb664586c468dd310cb1341edbd6cad505 Mon Sep 17 00:00:00 2001 From: aleserche Date: Thu, 1 Nov 2018 00:58:37 +0500 Subject: [PATCH 06/11] Fix. --- src/index.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.d.ts b/src/index.d.ts index e02072d..84377ee 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -257,6 +257,7 @@ declare interface JumioInitResult extends KycInitResult { } declare interface ShuftiproInitResult extends KycInitResult { + message?: string; event?: string; reference?: string; error?: boolean; From 9b19d30457a28b5e6dc7b2470ff84a3f1fcb26a5 Mon Sep 17 00:00:00 2001 From: aleserche Date: Thu, 1 Nov 2018 01:00:31 +0500 Subject: [PATCH 07/11] Removed message from response. --- src/index.d.ts | 2 -- src/providers/kyc/shuftipro.provider.ts | 1 - 2 files changed, 3 deletions(-) diff --git a/src/index.d.ts b/src/index.d.ts index 84377ee..f6c7953 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -257,7 +257,6 @@ declare interface JumioInitResult extends KycInitResult { } declare interface ShuftiproInitResult extends KycInitResult { - message?: string; event?: string; reference?: string; error?: boolean; @@ -293,7 +292,6 @@ declare interface ShuftiProVerificationData { } declare interface ShuftiProVerificationResponse { - message?: string; reference: string; event: string; error: any; diff --git a/src/providers/kyc/shuftipro.provider.ts b/src/providers/kyc/shuftipro.provider.ts index fe81204..6f1346b 100644 --- a/src/providers/kyc/shuftipro.provider.ts +++ b/src/providers/kyc/shuftipro.provider.ts @@ -53,7 +53,6 @@ export class ShuftiproProvider implements KycProviderInterface { if (!verificationResponse.error) { await this.saveKycInitResult(investor, verificationResponse); this.logger.info('Successful init'); - verificationResponse.message = verificationResponse.verification_url; return verificationResponse; } logger.exception(verificationResponse.error); From 01cbf329f269bc7294d21fae6f5cfded79435e97 Mon Sep 17 00:00:00 2001 From: aleserche Date: Thu, 1 Nov 2018 01:32:28 +0500 Subject: [PATCH 08/11] Refactoring. --- src/providers/kyc/shuftipro.provider.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/providers/kyc/shuftipro.provider.ts b/src/providers/kyc/shuftipro.provider.ts index 6f1346b..4a54475 100644 --- a/src/providers/kyc/shuftipro.provider.ts +++ b/src/providers/kyc/shuftipro.provider.ts @@ -79,6 +79,10 @@ export class ShuftiproProvider implements KycProviderInterface { } async processCallback(kycResultRequest: ShuftiproInitResult, spSignature: string): Promise { + const logger = this.logger.sub({ reference: kycResultRequest.reference }).addPrefix('[callback]'); + + logger.debug('process callback request'); + const kycResultRepo = getConnection().getMongoRepository(ShuftiproKycResult); const investorRepo = getConnection().getMongoRepository(Investor); const storedKycResult = await kycResultRepo.findOne({ where: {'reference': kycResultRequest.reference} }); @@ -101,9 +105,12 @@ export class ShuftiproProvider implements KycProviderInterface { case VERIFICATION_ACCEPTED: investor.kycStatus = KYC_STATUS_VERIFIED; investor.kycInitResult = kycResult; + + logger.debug('add investor into whitelist', investor.email); await this.web3Client.addAddressToWhiteList(investor.ethWallet.address); break; case VERIFICATION_DECLINED: + logger.debug('verification declined', investor.email, kycResult); investor.kycStatus = KYC_STATUS_FAILED; investor.kycInitResult = kycResult; break; From 5556e409ed0b5f1b112d140762c089201e3871a0 Mon Sep 17 00:00:00 2001 From: aleserche Date: Thu, 1 Nov 2018 01:42:17 +0500 Subject: [PATCH 09/11] Disable Content-Type --- src/app.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/app.ts b/src/app.ts index ac6f624..cef0c63 100644 --- a/src/app.ts +++ b/src/app.ts @@ -43,15 +43,6 @@ app.use((req: Request, res: Response, next: NextFunction) => { }); app.post('*', (req: Request, res: Response, next: NextFunction) => { - if ( - !req.header('Content-Type') || - (req.header('Content-Type') !== 'application/json' && !req.header('Content-Type').includes('application/x-www-form-urlencoded')) - ) { - return res.status(406).json({ - error: 'Unsupported "Content-Type"' - }); - } - return next(); }); From 6689ce95f4b4adf447c4c12c8f12bc1168cf6db9 Mon Sep 17 00:00:00 2001 From: aleserche Date: Thu, 1 Nov 2018 01:45:14 +0500 Subject: [PATCH 10/11] Revert. --- src/app.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/app.ts b/src/app.ts index cef0c63..ac6f624 100644 --- a/src/app.ts +++ b/src/app.ts @@ -43,6 +43,15 @@ app.use((req: Request, res: Response, next: NextFunction) => { }); app.post('*', (req: Request, res: Response, next: NextFunction) => { + if ( + !req.header('Content-Type') || + (req.header('Content-Type') !== 'application/json' && !req.header('Content-Type').includes('application/x-www-form-urlencoded')) + ) { + return res.status(406).json({ + error: 'Unsupported "Content-Type"' + }); + } + return next(); }); From a5510d52c989ff99345debc2742d8994c1d91b70 Mon Sep 17 00:00:00 2001 From: aleserche Date: Thu, 1 Nov 2018 01:48:57 +0500 Subject: [PATCH 11/11] Fix signature. --- src/providers/kyc/shuftipro.provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/providers/kyc/shuftipro.provider.ts b/src/providers/kyc/shuftipro.provider.ts index 4a54475..3c7fa9b 100644 --- a/src/providers/kyc/shuftipro.provider.ts +++ b/src/providers/kyc/shuftipro.provider.ts @@ -99,7 +99,7 @@ export class ShuftiproProvider implements KycProviderInterface { return; } - const signature = this.spSignature(kycResultRequest); + const signature = this.spSignature(JSON.stringify(kycResultRequest)); if (signature === spSignature) { switch (kycResult.event) { case VERIFICATION_ACCEPTED: