• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

GoodDollar / GoodServer / 13050841606

14 Jan 2025 12:39PM UTC coverage: 49.666% (+0.09%) from 49.574%
13050841606

push

github

sirpy
fix: remove unused

584 of 1457 branches covered (40.08%)

Branch coverage included in aggregate %.

1867 of 3478 relevant lines covered (53.68%)

8.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.3
/src/server/verification/processor/provider/ZoomProvider.js
1
// @flow
2
import { get, once, omitBy, bindAll, values, isError } from 'lodash'
3

4
import initZoomAPI from '../../api/ZoomAPI'
5

6
import {
7
  ZoomAPIError,
8
  duplicateFoundMessage,
9
  successfullyEnrolledMessage,
10
  alreadyEnrolledMessage,
11
  ZoomLicenseType
12
} from '../../utils/constants'
13

14
import { faceSnapshotFields } from '../../utils/logger'
15
import logger from '../../../../imports/logger'
16
import ServerConfig from '../../../server.config'
17

18
import { type IEnrollmentProvider } from '../typings'
19
import { strcasecmp } from '../../../utils/string'
20
import GoodIDUtils from '../../../goodid/utils'
21

22
class ZoomProvider implements IEnrollmentProvider {
4✔
23
  api = null
4✔
24
  logger = null
25

26
  constructor(api, Config, logger) {
4✔
27
    const { skipFaceVerification, disableFaceVerification, minAgeGroup } = Config
28

4✔
29
    this.minAgeGroup = minAgeGroup
4✔
30
    this.api = api
4✔
31
    this.logger = logger
4✔
32
    this.storeRecords = !disableFaceVerification && !skipFaceVerification
33

4✔
34
    bindAll(this, ['_enrollmentOperation'])
35
  }
36

37
  isPayloadValid(payload: any): boolean {
50✔
38
    return !faceSnapshotFields.some(field => !payload[field])
39
  }
40

41
  isValidLicenseType(licenseType: string): boolean {
8✔
42
    return values(ZoomLicenseType).includes(licenseType)
43
  }
44

1✔
45
  async getLicenseKey(licenseType, customLogger = null): Promise<any> {
4✔
46
    const { api } = this
4✔
47
    const { key } = await api.getLicenseKey(licenseType, customLogger)
48

3✔
49
    return key
50
  }
51

1✔
52
  async issueToken(customLogger = null): Promise<string> {
3✔
53
    const { api } = this
3✔
54
    const { sessionToken } = await api.getSessionToken(customLogger)
55

2✔
56
    return sessionToken
57
  }
58

59
  async enroll(
60
    enrollmentIdentifier: string,
61
    payload: any,
62
    onEnrollmentProcessing: (payload: IEnrollmentEventPayload) => void | Promise<void>,
13✔
63
    skipAgeCheck = false,
64
    customLogger = null
27✔
65
  ): Promise<any> {
27✔
66
    const { api, logger, storeRecords } = this
27✔
67
    const log = customLogger || logger
27✔
68
    const { defaultMinimalMatchLevel, defaultSearchIndexName } = api
69
    const { LivenessCheckFailed, SecurityCheckFailed, FacemapDoesNotMatch } = ZoomAPIError
70

50✔
71
    // send event to onEnrollmentProcessing
58✔
72
    const notifyProcessor = async eventPayload => onEnrollmentProcessing(eventPayload)
27✔
73
    const isLargeTextField = field => ['Base64', 'Blob'].some(suffix => field.endsWith(suffix))
74
    const redactedFields = ['callData', 'additionalSessionData', 'serverInfo']
75

27✔
76
    let resultBlob
27✔
77
    let isLive = true
27✔
78
    let isNotMatch = false
79
    let isEnrolled = true
80

27✔
81
    // reads resultBLob from response
82
    const fetchResult = response => (resultBlob = get(response, 'scanResultBlob'))
83

84
    // throws custom exception related to the predefined verification cases
27✔
85
    // e.g. livenes wasn't passed, duplicate found etc
11✔
86
    const throwException = (errorOrMessage, customResponse, originalResponse = {}) => {
11!
87
      let exception = errorOrMessage
88
      let { response } = exception || {}
11!
89

11✔
90
      if (!isError(errorOrMessage)) {
11✔
91
        exception = new Error(errorOrMessage)
92
        response = originalResponse
93
      }
94

95
      // removing debug fields and all large data (e.g.
29✔
96
      // images, facemaps), keeping just 'scanResultBlob'
97
      const redactedResponse = omitBy(response, (_, field) => redactedFields.includes(field) || isLargeTextField(field))
11✔
98

99
      exception.response = {
100
        ...redactedResponse,
101
        ...customResponse,
102
        isVerified: false
103
      }
11✔
104

105
      throw exception
106
    }
107

108
    // 1. Determining which operation we need to perform
27✔
109
    // a) if face verification disabled we'll check liveness only
27✔
110
    let alreadyEnrolled = false
27✔
111
    let methodToInvoke = 'checkLiveness'
112
    let methodArgs = [payload, customLogger]
113

114
    // b) if face verification enabled, we're checking is facescan already uploaded & enrolled
115
    // if (storeRecords) { // we now always store enrollments even for dev env
116
    // refactored - using a separate method was added after initial implementation
27✔
117
    // instead of the direct API call
118
    alreadyEnrolled = await this.isEnrollmentExists(enrollmentIdentifier, customLogger)
119
    // if already enrolled, will call /match-3d
25✔
120
    // othwerise (if not enrolled/stored yet) - /enroll
121
    methodToInvoke = alreadyEnrolled ? 'updateEnrollment' : 'submitEnrollment'
25✔
122
    // match/enroll requires enromment identifier, pre-prepding it to the args list
123
    methodArgs.unshift(enrollmentIdentifier)
124
    // }
125

25✔
126
    // 2. performing liveness check and storing facescan / audit trail images (if need)
25✔
127
    try {
128
      const enrollResult = await api[methodToInvoke](...methodArgs)
21✔
129

130
      fetchResult(enrollResult)
21✔
131

132
      log.debug('liveness enrollment success:', {
133
        methodToInvoke,
134
        enrollmentIdentifier,
135
        alreadyEnrolled,
136
        enrollResult,
137
        skipAgeCheck
21✔
138
      })
2✔
139

2✔
140
      if (skipAgeCheck !== true && enrollResult.ageV2GroupEnumInt < this.minAgeGroup) {
2✔
141
        const { age } = await GoodIDUtils.ageGenderCheck(payload.auditTrailImage)
142
        if (age.min < 16) {
143
          log.debug('age check failed', { enrollmentIdentifier, ageGroup: enrollResult.ageV2GroupEnumInt, awsAge: age })
6✔
144
          const e = new Error('age check failed')
145
          e.name = 'AgeCheck'
6✔
146
          throw e
6✔
147
        } else {
2✔
148
          log.debug('age check recover:', {
2✔
149
            enrollmentIdentifier,
150
            ageGroup: enrollResult.ageV2GroupEnumInt,
151
            awsAge: age
4✔
152
          })
1✔
153
        }
154
      }
1✔
155
    } catch (exception) {
1✔
156
      const { name, message, response } = exception
157

158
      log.warn('enroll failed:', { enrollmentIdentifier, name, message })
159
      if ('AgeCheck' === name) {
160
        await notifyProcessor({ isUnderAge: true })
1✔
161
        throwException(message, { isUnderAge: true }, response)
162
      }
163
      // if facemap doesn't match we won't show retry screen
164
      if (FacemapDoesNotMatch === name) {
3✔
165
        isNotMatch = true
166

167
        await notifyProcessor({ isNotMatch })
3!
168
        log.warn(message, { enrollmentIdentifier })
3✔
169

170
        // so we'll reject with isNotMatch: true instead
171
        // to show the error screen on app side immediately
3✔
172
        // notifying about liveness check failed
3✔
173
        throwException(message, { isNotMatch }, response)
3✔
174
      }
175

176
      // fetching resultBlob
177
      fetchResult(response)
×
178

×
179
      // if liveness / security issues were detected
180
      if ([LivenessCheckFailed, SecurityCheckFailed].includes(name)) {
181
        isLive = false
182

×
183
        // notifying about liveness check failed
184
        await notifyProcessor({ isLive })
185
        log.warn(message, { enrollmentIdentifier })
186
        throwException(message, { isLive, resultBlob }, response)
19✔
187
      }
188

189
      // if had response and blob - re-throw with scanResultBlob
19✔
190
      if (response && resultBlob) {
191
        throwException(exception, { resultBlob })
19✔
192
      }
193

4✔
194
      // otherwise just re-throwing exception and stopping processing
195
      throw exception
196
    }
197

198
    // notifying about liveness / match passed or not
19✔
199
    await notifyProcessor({ isLive, isNotMatch })
19✔
200

201
    // if wasn't already enrolled -  this means it wasn't also indexed
202
    let alreadyIndexed = false
16✔
203

204
    if (alreadyEnrolled) {
205
      // if already enrolled - need to check was it already indexed or not
206
      alreadyIndexed = await this.isEnrollmentIndexed(enrollmentIdentifier, customLogger)
207
    }
208

209
    // next steps are performed only if face verification enabled
13✔
210
    // if already enrolled and already indexed then passed match-3d, no need to facesearch
211
    log.debug('Preparing enrollment to uniqueness index:', { enrollmentIdentifier, alreadyEnrolled, alreadyIndexed })
13✔
212
    if (storeRecords && !alreadyIndexed) {
213
      // for dev env settings will be not to store records
4✔
214
      // 3. checking for duplicates
215
      const { results, ...faceSearchResponse } = await api.faceSearch(
216
        enrollmentIdentifier,
217
        defaultMinimalMatchLevel,
13✔
218
        defaultSearchIndexName,
219
        customLogger
220
      )
13✔
221

222
      log.debug('duplicate search result:', { enrollmentIdentifier, results, faceSearchResponse })
13✔
223
      // excluding own enrollmentIdentifier
224
      const duplicate = results.find(
4✔
225
        ({ identifier: matchId, matchLevel }) =>
4✔
226
          strcasecmp(matchId, enrollmentIdentifier) && Number(matchLevel) >= defaultMinimalMatchLevel
227
      )
228

229
      // if there're at least one record left - we have a duplicate
9✔
230
      const isDuplicate = !!duplicate
231

232
      // notifying about duplicates found or not
9✔
233
      await notifyProcessor({ isDuplicate })
9✔
234

8!
235
      if (isDuplicate) {
236
        // if duplicate found - throwing corresponding error
1✔
237
        log.warn(duplicateFoundMessage, { duplicate, enrollmentIdentifier })
238
        throwException(duplicateFoundMessage, { isDuplicate, duplicate }, faceSearchResponse)
239
      }
240

1!
241
      // 4. indexing uploaded & stored face scan to the 3D Database
×
242
      log.debug('Preparing enrollment to index:', { enrollmentIdentifier, alreadyEnrolled, alreadyIndexed })
243

244
      // if not already enrolled or indexed - indexing
245
      try {
1✔
246
        await api.indexEnrollment(enrollmentIdentifier, defaultSearchIndexName, customLogger)
247
        log.debug((alreadyIndexed ? 'Updated' : 'New') + ' enrollment indexed:', { enrollmentIdentifier })
1✔
248
      } catch (exception) {
1✔
249
        const { response, message } = exception
250

251
        // if exception has no response (e.g. no conneciton or service error)
252
        // just rethrowing it and stopping enrollment
253
        if (!response) {
11✔
254
          throw exception
255
        }
256

11✔
257
        // otherwise notifying & throwing enrollment exception
258
        isEnrolled = false
259

11✔
260
        await notifyProcessor({ isEnrolled })
261
        throwException(message, { isEnrolled }, response)
262
      }
263
    }
2✔
264

31✔
265
    // preparing corresponding success message depinding of the alreadyEnrolled status
31✔
266
    const enrollmentStatus = alreadyEnrolled ? alreadyEnrolledMessage : successfullyEnrolledMessage
267

268
    // notifying about successfull enrollment
269
    await notifyProcessor({ isEnrolled })
×
270

1✔
271
    // returning successfull result
272
    return { isVerified: true, alreadyEnrolled, alreadyIndexed, resultBlob, message: enrollmentStatus }
273
  }
274

2✔
275
  // eslint-disable-next-line require-await
10✔
276
  async isEnrollmentExists(enrollmentIdentifier: string, customLogger = null): Promise<boolean> {
277
    return this._enrollmentOperation('Error checking enrollment', enrollmentIdentifier, customLogger, async () =>
10✔
278
      this.api.readEnrollment(enrollmentIdentifier, customLogger)
10✔
279
    )
280
  }
281

282
  async getEnrollment(enrollmentIdentifier: string, customLogger = null): Promise<any> {
1✔
283
    return this.api.readEnrollment(enrollmentIdentifier, customLogger)
8✔
284
  }
8✔
285

8✔
286
  // eslint-disable-next-line require-await
8✔
287
  async isEnrollmentIndexed(enrollmentIdentifier: string, customLogger = null): Promise<boolean> {
288
    const { api } = this
289

8✔
290
    return this._enrollmentOperation('Error checking enrollment', enrollmentIdentifier, customLogger, async () =>
8✔
291
      api.readEnrollmentIndex(enrollmentIdentifier, api.defaultSearchIndexName, customLogger)
4✔
292
    )
293
  }
294

295
  async dispose(enrollmentIdentifier: string, customLogger = null): Promise<void> {
6✔
296
    const { api, _enrollmentOperation, logger } = this
297
    const { defaultSearchIndexName } = api
6✔
298
    const log = customLogger || logger
6✔
299
    const logLabel = 'Error disposing enrollment'
1✔
300

301
    // eslint-disable-next-line require-await
302
    await _enrollmentOperation(logLabel, enrollmentIdentifier, customLogger, async () => {
5✔
303
      await api.removeEnrollmentFromIndex(enrollmentIdentifier, defaultSearchIndexName, customLogger)
304
      log.debug('Enrollment removed from the search index', { enrollmentIdentifier })
305
    })
306

307
    // trying to remove also facemap from the DB
5!
308
    try {
5✔
309
      // eslint-disable-next-line require-await
310
      await _enrollmentOperation(logLabel, enrollmentIdentifier, customLogger, async () => {
311
        await api.disposeEnrollment(enrollmentIdentifier, customLogger)
312
        log.debug('Enrollment removed physically from the DB', { enrollmentIdentifier })
313
      })
×
314
    } catch (exception) {
55✔
315
      const { message: errMessage } = exception
316

55✔
317
      // if delete enrollment isn't supported by the server it will try to enroll
55✔
318
      // it check is enrollment already exists then validates input payload
18✔
319
      // so one of 'already exists' or 'facescan not valid' will be thrown
320
      if (/(enrollment\s+already\s+exists|faceScan\s+.+?not\s+valid)/i.test(errMessage)) {
37✔
321
        log.warn("ZoOm server doesn't supports removing enrollments", { enrollmentIdentifier })
322
      }
37✔
323
    }
28✔
324
  }
325

326
  async _enrollmentOperation(logLabel, enrollmentIdentifier, customLogger = null, operation): Promise<boolean> {
9✔
327
    const log = customLogger || this.logger
9✔
328

329
    try {
330
      await operation()
331
      return true
332
    } catch (exception) {
4✔
333
      const { message: errMessage } = exception
334

335
      if (ZoomAPIError.FacemapNotFound === exception.name) {
336
        return false
337
      }
338

339
      log.warn(logLabel, { e: exception, errMessage, enrollmentIdentifier })
340
      throw exception
341
    }
342
  }
343
}
344

345
export default once(() => new ZoomProvider(initZoomAPI(), ServerConfig, logger.child({ from: 'ZoomProvider' })))
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc