Skip to content

Commit

Permalink
updated ZoomAPI class for v9
Browse files Browse the repository at this point in the history
  • Loading branch information
serdiukov-o-nordwhale committed Oct 30, 2020
1 parent 6ac033d commit 3e3b4d5
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 102 deletions.
10 changes: 9 additions & 1 deletion src/server/server.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import dotenv from 'dotenv'
import getNetworks from './networks'
import ContractsAddress from '@gooddollar/goodcontracts/releases/deployment.json'

import { version } from '../../package.json'
import { version, description } from '../../package.json'

export const appName = description.replace(/\s*server\s*/i, '')

let dotenvPath = '.env'

Expand Down Expand Up @@ -276,6 +278,12 @@ const conf = convict({
env: 'ZOOM_MINIMAL_MATCHLEVEL',
default: 1
},
zoomSearchIndexName: {
doc: 'FaceTec 3d DB search index name',
format: '*',
env: 'ZOOM_SEARCH_INDEX_NAME',
default: appName
},
zoomServerBaseUrl: {
doc: 'FaceTec Managed Testing API URL',
format: '*',
Expand Down
222 changes: 121 additions & 101 deletions src/server/verification/api/ZoomAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,114 +7,138 @@ import { assign, merge, get, pick, omit, isPlainObject, isArray, mapValues, once
import Config from '../../server.config'
import logger from '../../../imports/logger'

const LIVENESS_PASSED = 0
export const ZoomAPIError = {
FacemapNotFound: 'facemapNotFound',
LivenessCheckFailed: 'livenessCheckFailed'
}

class ZoomAPI {
http = null
defaultMinimalMatchLevel = null
defaultSearchIndexName = null

constructor(Config, httpFactory, logger) {
const { zoomMinimalMatchLevel } = Config
const { zoomMinimalMatchLevel, zoomSearchIndexName } = Config
const httpClientOptions = this._configureClient(Config, logger)

this.logger = logger
this.http = httpFactory(httpClientOptions)
this.defaultMinimalMatchLevel = Number(zoomMinimalMatchLevel)
this.defaultSearchIndexName = zoomSearchIndexName

this._configureRequests()
this._configureResponses()
}

async getSessionToken(customLogger = null) {
let [response, exception] = await this._sendRequest('get', '/session-token', { customLogger })
const response = await this.http.get('/session-token', { customLogger })

if (!get(response, 'sessionToken')) {
const exception = new Error('No sessionToken in the FaceTec API response')

if (!exception && !get(response, 'sessionToken')) {
exception = new Error('No sessionToken in the FaceTec API response')
assign(exception, { response })
throw exception
}

if (exception) {
return response
}

async readEnrollment(enrollmentIdentifier, customLogger = null) {
let response

try {
response = await this.http.get('/enrollment-3d/:enrollmentIdentifier', {
customLogger,
params: { enrollmentIdentifier }
})
} catch (exception) {
const { message } = exception

if (/no\s+entry\s+found/i.test(message)) {
exception.name = ZoomAPIError.FacemapNotFound
}

throw exception
}

return response
}

async submitEnrollment(payload, customLogger = null) {
let [response, exception] = await this._sendRequest('post', '/enrollment', payload, { customLogger })
const { message, isEnrolled } = response
const [isLivenessPassed, reasonOfFailure] = this._checkLivenessStatus(response)
async submitEnrollment(enrollmentIdentifier, faceSnapshot, customLogger = null) {
const payload = {
...pick(faceSnapshot, 'sessionId', 'faceScan', 'auditTrailImage', 'lowQualityAuditTrailImage'),
externalDatabaseRefID: enrollmentIdentifier
}

if (exception || !isLivenessPassed || !isEnrolled) {
if (/enrollment\s+already\s+exists/i.test(message)) {
response.subCode = 'nameCollision'
const response = await this.http.post('post', '/enrollment-3d', payload, { customLogger })
const isLivenessPassed = this.isLivenessCheckPassed(response)
const { success, faceScanSecurityChecks } = response

const {
auditTrailVerificationCheckSucceeded,
replayCheckSucceeded,
sessionTokenCheckSucceeded
} = faceScanSecurityChecks

if (!success) {
let message = 'FaceMap was not enrolled'

if (!sessionTokenCheckSucceeded) {
message += ' because the session token is missing or was failed to be checked'
} else if (!replayCheckSucceeded) {
message += ' because the replay check was failed'
} else if (!isLivenessPassed) {
message = 'Liveness could not be determined'

if (!auditTrailVerificationCheckSucceeded) {
message += ' because the photoshoots evaluated to be of poor quality'
}
}

if (!exception) {
exception = new Error(reasonOfFailure)
const exception = new Error(message + '.')

if (!isLivenessPassed) {
exception.name = ZoomAPIError.LivenessCheckFailed
}

assign(exception, { response, message: reasonOfFailure })
assign(exception, { response })
throw exception
}

return response
}

// eslint-disable-line require-await
async indexEnrollment(enrollmentIdentifier, indexName = null, customLogger = null) {
return this._3dDbRequest('enroll', enrollmentIdentifier, indexName, null, customLogger)
}

// eslint-disable-next-line require-await
async readEnrollment(enrollmentIdentifier, customLogger = null) {
return this._faceMapRequest('get', enrollmentIdentifier, customLogger)
async readEnrollmentIndex(enrollmentIdentifier, indexName = null, customLogger = null) {
return this._3dDbIndexRequest('get', enrollmentIdentifier, indexName, customLogger)
}

// eslint-disable-next-line require-await
async disposeEnrollment(enrollmentIdentifier, customLogger = null) {
return this._faceMapRequest('delete', enrollmentIdentifier, customLogger)
async removeEnrollmentFromIndex(enrollmentIdentifier, indexName = null, customLogger = null) {
return this._3dDbIndexRequest('delete', enrollmentIdentifier, indexName, customLogger)
}

async faceSearch(payload, minimalMatchLevel: number = null, customLogger = null) {
// eslint-disable-next-line require-await
async faceSearch(enrollmentIdentifier, minimalMatchLevel: number = null, indexName = null, customLogger = null) {
let minMatchLevel = minimalMatchLevel
let [response, exception] = await this._sendRequest('post', '/search', payload, { customLogger })

if (exception) {
let livenessCheckFailed = false

if ('livenessStatus' in response) {
const [isLivenessPassed, reasonOfFailure] = this._checkLivenessStatus(response)

livenessCheckFailed = !isLivenessPassed

if (livenessCheckFailed) {
exception.message = reasonOfFailure
}
} else {
livenessCheckFailed = /must\s+have.+?liveness\s+proven/i.test(response.message)
}

if (livenessCheckFailed) {
response.subCode = 'livenessCheckFailed'
}

throw exception
}

if (null === minMatchLevel) {
minMatchLevel = this.defaultMinimalMatchLevel
}

if (minMatchLevel) {
const { results = [] } = response
minMatchLevel = Number(minMatchLevel)

response.results = results.filter(({ matchLevel }) => Number(matchLevel) >= minMatchLevel)
}

return response
return this._3dDbRequest('search', enrollmentIdentifier, indexName, { minMatchLevel }, customLogger)
}

isLivenessCheckPassed(response) {
const { livenessStatus } = response
const { faceScanSecurityChecks } = response || {}
const { faceScanLivenessCheckSucceeded } = faceScanSecurityChecks || {}

return LIVENESS_PASSED === livenessStatus
return true === faceScanLivenessCheckSucceeded
}

_configureClient(Config, logger) {
Expand Down Expand Up @@ -187,11 +211,11 @@ class ZoomAPI {

async _responseInterceptor(response) {
const zoomResponse = this._transformResponse(response)
const { success, errorMessage } = zoomResponse
const { error, errorMessage } = zoomResponse

this._logResponse('Received response from Zoom API:', response)

if (false === success) {
if (true === error) {
const exception = new Error(errorMessage || 'FaceTec API response is empty')

exception.response = zoomResponse
Expand Down Expand Up @@ -255,78 +279,74 @@ class ZoomAPI {
}
}

_createLoggingSafeCopy(payload) {
if (isArray(payload)) {
return payload.map(item => this._createLoggingSafeCopy(item))
}
_getDatabaseIndex(indexName = null) {
let databaseIndex = indexName

if (!isPlainObject(payload)) {
return payload
if (null === indexName) {
databaseIndex = this.defaultSearchIndexName
}

return mapValues(omit(payload, 'faceMap', 'auditTrailImage', 'lowQualityAuditTrailImage'), payloadField =>
this._createLoggingSafeCopy(payloadField)
)
return databaseIndex
}

async _sendRequest(method, endpoint, payloadOrOptions = null, options = null) {
async _3dDbRequest(operation, enrollmentIdentifier, indexName = null, additionalData = null, customLogger = null) {
let response
let exception
const databaseIndex = this._getDatabaseIndex(indexName)

const payload = {
externalDatabaseRefID: enrollmentIdentifier,
groupName: databaseIndex,
...(additionalData || {})
}

try {
response = await this.http[method](...filter([endpoint, payloadOrOptions, options]))
} catch (apiException) {
exception = apiException
response = apiException.response
response = await this.http.post(`/3d-db/${operation}`, payload, { customLogger })
} catch (exception) {
const { message } = exception

if (!response) {
throw apiException
if (/enrollment\s+does\s+not\s+exist/i.test(message)) {
exception.name = ZoomAPIError.FacemapNotFound
}

throw exception
}

return [response, exception]
return response
}

async _faceMapRequest(method, enrollmentIdentifier, customLogger = null) {
let [response, exception] = await this._sendRequest(method, '/enrollment/:enrollmentIdentifier', {
customLogger,
params: { enrollmentIdentifier }
})
async _3dDbIndexRequest(method, enrollmentIdentifier, indexName = null, customLogger = null) {
const databaseIndex = this._getDatabaseIndex(indexName)

const { message } = response
const payload = {
identifier: enrollmentIdentifier,
groupName: databaseIndex
}

if (/no\s+entry\s+found/i.test(message)) {
if (!exception) {
exception = new Error(message)
exception.response = response
}
const response = await this.http.post(`/3d-db/${method}`, payload, { customLogger })
const { success } = response

response.subCode = 'facemapNotFound'
}
if (false === success) {
const exception = new Error('An enrollment does not exist for this externalDatabaseRefID.')

if (exception) {
assign(exception, { response, name: ZoomAPIError.FacemapNotFound })
throw exception
}

return response
}

_checkLivenessStatus(response) {
const { glasses, message, isLowQuality } = response
const isLivenessPassed = this.isLivenessCheckPassed(response)

let errorMessage = null

if (!isLivenessPassed) {
errorMessage = message
_createLoggingSafeCopy(payload) {
if (isArray(payload)) {
return payload.map(item => this._createLoggingSafeCopy(item))
}

if (isLowQuality) {
errorMessage = 'Liveness could not be determined because '
errorMessage += 'the photoshoots evaluated to be of poor quality.'
}
if (!isPlainObject(payload)) {
return payload
}

return [isLivenessPassed, errorMessage]
return mapValues(omit(payload, 'faceMapBase64', 'auditTrailBase64'), payloadField =>
this._createLoggingSafeCopy(payloadField)
)
}
}

Expand Down

0 comments on commit 3e3b4d5

Please sign in to comment.