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

GoodDollar / GoodServer / 16730210565

15 May 2025 06:46AM UTC coverage: 49.341% (-0.3%) from 49.666%
16730210565

push

github

sirpy
fix: remove defualt gas fees

597 of 1485 branches covered (40.2%)

Branch coverage included in aggregate %.

1875 of 3525 relevant lines covered (53.19%)

7.34 hits per line

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

6.44
/src/server/storage/storageAPI.js
1
// @flow
2
import moment from 'moment'
3
import { Router } from 'express'
4
import passport from 'passport'
5
import fetch from 'cross-fetch'
6
import { first, get, toLower, values } from 'lodash'
7
import { sha3, toChecksumAddress } from 'web3-utils'
8

9
import { type StorageAPI, UserRecord } from '../../imports/types'
10
import { wrapAsync, onlyInEnv } from '../utils/helpers'
11
import { withTimeout } from '../utils/async'
12
import OnGage from '../crm/ongage'
13
import conf from '../server.config'
14
import { addUserToWhiteList, createCRMRecord } from './addUserSteps'
15
import createUserVerifier from './verifier'
16
import stakingModelTasks from '../blockchain/stakingModelTasks'
17
import { cancelDisposalTask, getDisposalTask } from '../verification/cron/taskUtil'
18
import createEnrollmentProcessor from '../verification/processor/EnrollmentProcessor'
19
import requestRateLimiter from '../utils/requestRateLimiter'
20
import { default as AdminWallet } from '../blockchain/MultiWallet'
21
import Logger from '../../imports/logger'
22

23
const { fishManager } = stakingModelTasks
2✔
24

25
const deleteFromAnalytics = (userId, walletAddress, log) => {
2✔
26
  const amplitudePromise = fetch(`https://amplitude.com/api/2/deletions/users`, {
×
27
    headers: { Authorization: `Basic ${conf.amplitudeBasicAuth}`, 'Content-Type': 'application/json' },
28
    method: 'POST',
29

30
    body: JSON.stringify({
31
      user_ids: [toChecksumAddress(userId.toLowerCase())], //amplitude id is case sensitive and is the original address form from user wallet
32
      delete_from_org: 'True',
33
      ignore_invalid_id: 'True'
34
    })
35
  })
36
    .then(_ => _.text())
×
37
    .then(_ => {
38
      log.info('amplitude delete user result', { result: _ })
×
39
      return {
×
40
        amplitude: 'ok'
41
      }
42
    })
43
    .catch(() => ({ amplitude: 'failed' }))
×
44

45
  return [amplitudePromise]
×
46
}
47

48
const adminAuthenticate = (req, res, next) => {
2✔
49
  const { password } = req.body || {}
×
50

51
  if (password !== conf.adminPassword) {
×
52
    return res.json({ ok: 0 })
×
53
  }
54

55
  next()
×
56
}
57

58
const setup = (app: Router, storage: StorageAPI) => {
2✔
59
  app.use(
2✔
60
    ['/user/*'],
61
    passport.authenticate('jwt', { session: false }),
62
    requestRateLimiter(20, 1, 'user'),
63
    wrapAsync(async (req, res, next) => {
64
      const { user, body, log } = req
×
65
      const { loggedInAs } = user
×
66
      const identifier = get(body, 'user.identifier', loggedInAs)
×
67

68
      log.trace(`/user/* ${req.baseUrl} auth:`, { user, body })
×
69

70
      if (loggedInAs !== identifier) {
×
71
        log.warn(`Trying to update other user data! ${loggedInAs}!==${identifier}`)
×
72
        throw new Error(`Trying to update other user data! ${loggedInAs}!==${identifier}`)
×
73
      } else next()
×
74
    })
75
  )
76

77
  /**
78
   * @api {post} /user/add Add user account
79
   * @apiName Add
80
   * @apiGroup Storage
81
   *
82
   * @apiParam {Object} user
83
   *
84
   * @apiSuccess {Number} ok
85
   * @ignore
86
   */
87
  app.post(
2✔
88
    '/user/add',
89
    wrapAsync(async (req, res) => {
90
      const { env, skipEmailVerification, disableFaceVerification, optionalMobile } = conf
×
91
      const isNonDevelopMode = env !== 'development'
×
92
      const { cookies, body, log: logger, user: userRecord } = req
×
93
      const { user: userPayload = {} } = body
×
94
      const { __utmzz: utmString = '' } = cookies
×
95

96
      try {
×
97
        logger.debug('new user request:', { data: userPayload, userRecord })
×
98
        let { email } = userPayload
×
99
        email = email.toLowerCase()
×
100

101
        const { mobile, inviteCode, fullName, regMethod, torusProvider } = userPayload
×
102

103
        // if torus, then we first verify the user mobile/email by verifying it matches the torus public key
104
        // (torus maps identifier such as email and mobile to private/public key pairs)
105
        const verifier = createUserVerifier(userRecord, userPayload, logger)
×
106

107
        //this modifies userRecord with smsValidated/isEmailConfirmed
108
        const { emailVerified, mobileVerified } = await verifier.verifySignInIdentifiers()
×
109

110
        const isEmailTorusVerified = email && emailVerified
×
111
        const isEmailManuallyVerified = email && userRecord.isEmailConfirmed && userRecord.email === sha3(email)
×
112
        const isEmailConfirmed = !!('development' === env || isEmailTorusVerified || isEmailManuallyVerified)
×
113
        const isMobileTorusVerified = mobile && mobileVerified
×
114
        const isMobileManuallyVerified = mobile && userRecord.smsValidated && userRecord.mobile === sha3(mobile)
×
115
        const isMobileConfirmed = !!('development' === env || isMobileTorusVerified || isMobileManuallyVerified)
×
116

117
        logger.debug('new user verification result:', {
×
118
          env,
119
          mobile,
120
          email,
121
          isEmailTorusVerified,
122
          isEmailManuallyVerified,
123
          isMobileTorusVerified,
124
          isMobileManuallyVerified,
125
          isEmailConfirmed,
126
          isMobileConfirmed
127
        })
128
        // check that user email/mobile sent is the same as the ones verified
129
        //in case email/mobile was verified using torus userRecord.mobile/email will be empty
130
        if (['production', 'staging'].includes(env)) {
×
131
          if (optionalMobile === false && isMobileConfirmed === false) {
×
132
            throw new Error('User mobile not verified!')
×
133
          }
134

135
          if (skipEmailVerification === false && isEmailConfirmed === false) {
×
136
            throw new Error('User email not verified!')
×
137
          }
138
        }
139

140
        userRecord.isEmailConfirmed = isEmailConfirmed
×
141
        userRecord.smsValidated = isMobileConfirmed
×
142

143
        if (userRecord.createdDate) {
×
144
          logger.warn('user already created', { userRecord, userPayload })
×
145
          // return res.json({ ok: 1 })
146
        }
147

148
        const toUpdateUser: UserRecord = {
×
149
          identifier: userRecord.loggedInAs,
150
          regMethod,
151
          torusProvider,
152
          email: email ? sha3(email) : userRecord.email,
×
153
          mobile: mobile ? sha3(mobile) : userRecord.mobile,
×
154
          fullName,
155
          profilePublickey: userRecord.profilePublickey,
156
          walletAddress: sha3(userRecord.gdAddress.toLowerCase()),
157
          isCompleted: userRecord.isCompleted
×
158
            ? userRecord.isCompleted
159
            : {
160
                whiteList: false,
161
                topWallet: false
162
              },
163
          isEmailConfirmed,
164
          smsValidated: isMobileConfirmed
165
        }
166

167
        const userRecordWithPII = { ...userRecord, ...toUpdateUser, inviteCode, email, mobile }
×
168
        const signUpPromises = []
×
169

170
        const p1 = storage
×
171
          .updateUser(toUpdateUser)
172
          .then(() => logger.debug('updated new user record', { toUpdateUser }))
×
173
          .catch(e => {
174
            logger.error('failed updating new user record', e.message, e, { toUpdateUser })
×
175
            throw e
×
176
          })
177
        signUpPromises.push(p1)
×
178

179
        // whitelisting user if FR is disabled
180
        if (disableFaceVerification) {
×
181
          const p2 = addUserToWhiteList(userRecord, logger)
×
182
            .then(isWhitelisted => {
183
              logger.debug('addUserToWhiteList result', { isWhitelisted })
×
184
              if (isWhitelisted === false) throw new Error('Failed whitelisting user')
×
185
            })
186
            .catch(e => {
187
              logger.warn('addUserToWhiteList failed', e.message, e, { userRecord })
×
188
              throw e
×
189
            })
190
          signUpPromises.push(p2)
×
191
        }
192

193
        let p3 = Promise.resolve()
×
194
        if (isNonDevelopMode) {
×
195
          p3 = createCRMRecord(userRecordWithPII, utmString, logger)
×
196
            .then(r => {
197
              logger.debug('createCRMRecord success')
×
198
              return r
×
199
            })
200
            .catch(e => {
201
              logger.error('createCRMRecord failed', e.message, e, { userRecordWithPII })
×
202
              throw new Error('Failed adding user to CRM')
×
203
            })
204
          signUpPromises.push(p3)
×
205
        }
206

207
        const p5 = Promise.all([
×
208
          //TODO: generate email/mobile claims using ceramic
209
        ])
210
          .then(res => logger.info('created did claims: result', { res }))
×
211
          .catch(() => {
212
            logger.warn('create did claims: failed')
×
213
          })
214

215
        signUpPromises.push(p5)
×
216

217
        // don't await, if we failed to update its not critical for user.
218
        withTimeout(Promise.all(signUpPromises), 30000, 'signup promises timeout')
×
219
          .then(async () => {
220
            logger.info('signup promises success')
×
221
            if (isNonDevelopMode) {
×
222
              const crmId = await p3
×
223
              if (crmId)
×
224
                await OnGage.updateContact(email, crmId, { signup_completed: true }, logger).catch(exception => {
×
225
                  const { message } = exception
×
226
                  logger.error('Failed CRM tagging user completed signup', message, exception, { crmId })
×
227
                })
228
            }
229
          })
230
          .catch(e => logger.error('signup promises failed', e.message, e))
×
231

232
        logger.debug('signup steps success. adding new user:', { toUpdateUser })
×
233

234
        await storage.updateUser({
×
235
          identifier: userRecord.loggedInAs,
236
          createdDate: userRecord.createdDate || new Date().toString(),
×
237
          otp: {} //delete trace of mobile,email
238
        })
239

240
        res.json({ ok: 1 })
×
241
      } catch (e) {
242
        logger.warn('user signup failed', e.message, e)
×
243
        throw e
×
244
      }
245
    })
246
  )
247

248
  /**
249
   * we had issues with mautic some users are not in the database
250
   * fix to make sure we have user data in CRM
251
   */
252
  app.post(
2✔
253
    '/user/verifyCRM',
254
    wrapAsync(async (req, res) => {
255
      const { body, log: logger, user: userRecord } = req
×
256
      const { user: userPayload = {} } = body
×
257

258
      try {
×
259
        logger.debug('verify crm:', { data: userPayload, userRecord })
×
260

261
        if (userRecord.crmId) {
×
262
          logger.debug('verifyCRM already has crmID', { crmId: userRecord.crmId })
×
263
        } else {
264
          let { email, mobile, fullName } = userPayload
×
265

266
          if (!email) {
×
267
            const error = 'verifyCRM missing user email'
×
268

269
            logger.warn(error, { userPayload, userRecord })
×
270
            return res.json({ ok: 0, error })
×
271
          }
272

273
          let emailLC = email.toLowerCase()
×
274

275
          const toCRM = {
×
276
            identifier: userRecord.loggedInAs,
277
            fullName,
278
            walletAddress: sha3(userRecord.gdAddress.toLowerCase()),
279
            email
280
          }
281

282
          // TODO: verify why this is happening on wallet
283
          // for some reason some emails were kept with capital letter while from user they arrive lower case
284
          // this line is a patch to handle that case
285

286
          if (
×
287
            (sha3(email) === userRecord.email ||
×
288
              sha3(emailLC) === userRecord.email ||
289
              sha3(emailLC.charAt(0).toUpperCase() + emailLC.slice(1)) === userRecord.email) === false
290
          ) {
291
            logger.error('unable to verify user email', { email, hash: sha3(email), recordHash: userRecord.email })
×
292
          }
293

294
          if (mobile && sha3(mobile) === userRecord.mobile) {
×
295
            toCRM.mobile = mobile
×
296
          }
297

298
          const crmId = await createCRMRecord(toCRM, '', logger)
×
299

300
          await storage.updateUser({
×
301
            email: sha3(email),
302
            identifier: userRecord.loggedInAs,
303
            crmId
304
          })
305

306
          logger.debug('verifyCRM success', { crmId, toCRM })
×
307
        }
308

309
        res.json({ ok: 1 })
×
310
      } catch (e) {
311
        logger.error('createCRMRecord failed', e.message, e)
×
312
        throw new Error('Failed adding user in verifyCRM')
×
313
      }
314
    })
315
  )
316

317
  /**
318
   * @api {post} /user/start user starts registration and we have his email
319
   * @apiName Add
320
   * @apiGroup Storage
321
   *
322
   * @apiParam {Object} user
323
   *
324
   * @apiSuccess {Number} ok
325
   * @ignore
326
   */
327
  app.post(
2✔
328
    '/user/start',
329
    onlyInEnv('production', 'staging', 'test'),
330
    wrapAsync(async (req, res) => {
331
      const { user } = req.body
×
332
      const { log: logger, user: existingUser } = req
×
333
      const { __utmzz: utmString = '' } = req.cookies
×
334

335
      if (existingUser.crmId) {
×
336
        return res.json({ ok: 1 })
×
337
      }
338

339
      if (!user.email) {
×
340
        logger.error('email missing', { user, existingUser })
×
341
        throw new Error('Email is missed')
×
342
      }
343

344
      // fire and forget, don't wait for success or failure
345
      createCRMRecord({ ...user, email: user.email.toLowerCase() }, utmString, logger)
×
346
        .then(() => logger.debug('/user/start createCRMRecord success'))
×
347
        .catch(e => {
348
          logger.error('/user/start createCRMRecord failed', e.message, e, { user })
×
349
        })
350

351
      res.json({ ok: 1 })
×
352
    })
353
  )
354

355
  /**
356
   * @api {post} /user/claim collect claim status to crm
357
   * @apiName Add
358
   * @apiGroup Storage
359
   *
360
   * @apiParam {Object} user
361
   *
362
   * @apiSuccess {Number} ok
363
   * @ignore
364
   */
365
  let updatesQueue = [] // we send updates to ongange in bulk
2✔
366
  const qLogger = Logger.child({ from: 'OnGageQueue' })
2✔
367
  const clearOnGageQueue = async () => {
2✔
368
    if (updatesQueue.length === 0) return
×
369
    const oldQueue = updatesQueue
×
370
    updatesQueue = []
×
371
    try {
×
372
      const result = await OnGage.updateContacts(oldQueue, qLogger)
×
373
      qLogger.debug('/user/claim updateContacts result:', { result, total: oldQueue.length })
×
374
    } catch (e) {
375
      qLogger.error('/user/claim updateContacts failed', e.message, e, { oldQueue })
×
376
    }
377
  }
378
  if (conf.env !== 'test') setInterval(clearOnGageQueue, 60 * 1000)
2!
379

380
  app.post(
2✔
381
    '/user/claim',
382
    onlyInEnv('production', 'staging'),
383
    wrapAsync(async (req, res) => {
384
      let { last_claim, claim_counter } = req.body
×
385
      const { log: logger, user } = req
×
386

387
      if (!user.crmId) {
×
388
        const error = 'user/claim missing crmId'
×
389

390
        logger.warn(error, { user, body: req.body })
×
391
        res.json({ ok: 0, error })
×
392
        return
×
393
      }
394

395
      // format date according to OnGage date format
396
      last_claim = moment().format('YYYY/MM/DD')
×
397

398
      updatesQueue.push({ id: user.crmId, last_claim, claim_counter })
×
399
      if (updatesQueue.length > 100) {
×
400
        clearOnGageQueue()
×
401
      }
402

403
      res.json({ ok: 1 })
×
404
    })
405
  )
406

407
  /**
408
   * @api {post} /user/delete Delete user account
409
   * @apiName Delete
410
   * @apiGroup Storage
411
   *
412
   * @apiSuccess {Number} ok
413
   * @apiSuccess {[Object]} results
414
   * @ignore
415
   */
416
  app.post(
2✔
417
    '/user/delete',
418
    wrapAsync(async (req, res) => {
419
      const { user, log } = req
×
420
      log.info('delete user', { user })
×
421

422
      //first get number of accounts using same crmId before we delete the account
423
      const crmCount = user.crmId
×
424
        ? await storage.getCountCRMId(user.crmId).catch(e => {
425
            log.warn('getCountCRMId failed:', e.message, e)
×
426
            return 1
×
427
          })
428
        : 0
429

430
      const results = await Promise.all([
×
431
        (user.identifier ? storage.deleteUser(user) : Promise.reject())
×
432
          .then(() => ({ mongodb: 'ok' }))
×
433
          .catch(() => ({ mongodb: 'failed' })),
×
434
        crmCount > 1
×
435
          ? Promise.resolve({ crm: 'okMultiNotDeleted' })
436
          : crmCount === 0
×
437
            ? Promise.resolve({ crm: 'missingId' })
438
            : OnGage.deleteContact(user.crmId, log)
439
                .then(() => ({ crm: 'ok' }))
×
440
                .catch(() => ({ crm: 'failed' })),
×
441
        ...deleteFromAnalytics(user.identifier, user.gdAddress)
442
      ])
443

444
      log.info('delete user results', { user, results })
×
445
      res.json({ ok: 1, results })
×
446
    })
447
  )
448

449
  /**
450
   * @api {get} /user/exists return true  if user finished registration
451
   * @apiName Delete
452
   * @apiGroup Storage
453
   *
454
   * @apiSuccess {Number} ok
455
   * @apiSuccess {Boolean} exists
456
   * @apiSuccess {String} fullName
457

458
   * @ignore
459
   */
460
  app.get(
2✔
461
    '/user/exists',
462
    wrapAsync(async (req, res) => {
463
      const { user } = req
×
464

465
      res.json({ ok: 1, exists: user.createdDate != null, fullName: user.fullName })
×
466
    })
467
  )
468

469
  /**
470
   * @api {post} /userExists returns user registration method
471
   * @apiGroup Storage
472
   *
473
   * @apiSuccess {Number} ok
474
   * @apiSuccess {Boolean} exists
475
   * @apiSuccess {String} fullName
476
   * @apiSuccess {String} provider
477

478

479
   * @ignore
480
   */
481
  app.post(
2✔
482
    '/userExists',
483
    wrapAsync(async (req, res) => {
484
      const { log, body } = req
×
485
      const toHash = value => (value ? sha3(value) : null)
×
486
      const sendNotExists = () => res.json({ ok: 1, exists: false })
×
487

488
      let { identifier = '', email, mobile } = body
×
489
      email = email ? email.toLowerCase() : undefined
×
490

491
      const lowerCaseID = identifier ? identifier.toLowerCase() : undefined
×
492
      const [emailHash, mobileHash] = [email, mobile].map(toHash)
×
493

494
      const identityFilters = [
×
495
        // identifier is stored lowercase in the db. we lowercase addresses in the /auth/eth process
496
        { identifier: lowerCaseID },
497
        { email: email && emailHash },
×
498
        { mobile: mobile && mobileHash }
×
499
      ].filter(or => !!first(values(or)))
×
500

501
      if (identityFilters.length === 0) {
×
502
        log.warn('empty data for /userExists', { body })
×
503
        sendNotExists()
×
504

505
        return
×
506
      }
507

508
      const dateFilters = {
×
509
        createdDate: { $exists: true }
510
      }
511

512
      const providerFilters = [
×
513
        { regMethod: { $type: 'string', $ne: 'torus' } },
514
        { regMethod: 'torus', torusProvider: { $type: 'string', $ne: '' } }
515
      ]
516

517
      const searchFilters = [identityFilters, providerFilters].map(filters => ({ $or: filters }))
×
518

519
      const allFilters = {
×
520
        $and: [dateFilters, ...searchFilters]
521
      }
522

523
      const projections = {
×
524
        identifier: 1,
525
        email: 1,
526
        mobile: 1,
527
        createdDate: 1,
528
        torusProvider: 1,
529
        fullName: 1,
530
        regMethod: 1,
531
        crmId: 1
532
      }
533

534
      // sort by importance, prefer oldest verified account
535
      let ordering = { isVerified: -1, createdDate: 1 }
×
536

537
      if (lowerCaseID) {
×
538
        // sortBy sorts in ascending order (and keeps existing sort)
539
        // so non-matched by id results would be moved to the end
540
        projections.identifierMatches = {
×
541
          $eq: ['$identifier', lowerCaseID]
542
        }
543

544
        ordering = { identifierMatches: -1, ...ordering }
×
545
      }
546

547
      const existing = await storage.model.aggregate([
×
548
        { $match: allFilters },
549
        { $project: projections },
550
        { $sort: ordering }
551
      ])
552

553
      log.debug('userExists:', { existing, identifier, identifierLC: lowerCaseID, email, mobile })
×
554

555
      if (!existing.length) {
×
556
        return sendNotExists()
×
557
      }
558

559
      const bestExisting = first(existing)
×
560

561
      return res.json({
×
562
        ok: 1,
563
        exists: true,
564
        found: existing.length,
565
        fullName: bestExisting.fullName,
566
        provider: bestExisting.torusProvider,
567
        identifier: bestExisting.identifierMatches,
568
        email: email && emailHash === bestExisting.email,
×
569
        mobile: mobile && mobileHash === bestExisting.mobile,
×
570
        regMethod: bestExisting.regMethod
571
      })
572
    })
573
  )
574

575
  app.get(
2✔
576
    '/userWhitelisted/:account',
577
    requestRateLimiter(10, 1),
578
    wrapAsync(async (req, res) => {
579
      const { params } = req
×
580
      const { account } = params
×
581
      const isWhitelisted = await AdminWallet.isVerified(account)
×
582

583
      res.json({ ok: 1, isWhitelisted })
×
584
    })
585
  )
586

587
  app.get(
2✔
588
    '/syncWhitelist/:account',
589
    requestRateLimiter(3, 1),
590
    wrapAsync(async (req, res) => {
591
      const { params, log } = req
×
592
      const { account } = params
×
593

594
      try {
×
595
        const whitelisted = await AdminWallet.syncWhitelist(account, log)
×
596

597
        log.debug('syncWhitelist success', { account, whitelisted })
×
598
        res.json({ ok: 1, whitelisted })
×
599
      } catch (e) {
600
        log.error('failed syncWhitelist', e.message, e, { account })
×
601
        res.json({ ok: 0, error: e.message })
×
602
      }
603
    })
604
  )
605

606
  app.post(
2✔
607
    '/admin/user/get',
608
    adminAuthenticate,
609
    wrapAsync(async (req, res) => {
610
      const { body } = req
×
611
      let user = {}
×
612
      if (body.email)
×
613
        user = await storage.getUsersByEmail(body.email.startsWith('0x') === false ? sha3(body.email) : body.email)
×
614
      if (body.mobile)
×
615
        user = await storage.getUsersByMobile(body.mobile.startsWith('0x') === false ? sha3(body.mobile) : body.mobile)
×
616
      if (body.identifier) user = await storage.getUser(body.identifier)
×
617
      if (body.identifierHash) user = await storage.getByIdentifierHash(body.identifierHash)
×
618

619
      res.json({ ok: 1, user })
×
620
    })
621
  )
622

623
  app.post(
2✔
624
    '/admin/user/list',
625
    adminAuthenticate,
626
    wrapAsync(async (_, res) => storage.listUsers(list => res.json(list)))
×
627
  )
628

629
  app.post(
2✔
630
    '/admin/user/delete',
631
    adminAuthenticate,
632
    wrapAsync(async (req, res) => {
633
      const { body } = req
×
634
      let result = {}
×
635
      if (body.identifier) result = await storage.deleteUser(body)
×
636

637
      res.json({ ok: 1, result })
×
638
    })
639
  )
640

641
  app.post(
2✔
642
    '/admin/model/fish',
643
    adminAuthenticate,
644
    wrapAsync(async (req, res) => {
645
      const { body, log } = req
×
646
      const { daysAgo } = body
×
647
      if (!daysAgo) return res.json({ ok: 0, error: 'missing daysAgo' })
×
648
      log.debug('fishing request', { daysAgo })
×
649
      fishManager
×
650
        .run(daysAgo)
651
        .then(fishResult => log.info('fishing request result:', { fishResult }))
×
652
        .catch(e => log.error('fish request failed', e.message, e, { daysAgo }))
×
653

654
      res.json({ ok: 1 })
×
655
    })
656
  )
657

658
  app.post(
2✔
659
    '/admin/verify/face/delete',
660
    adminAuthenticate,
661
    wrapAsync(async (req, res) => {
662
      const { body, log } = req
×
663
      const { enrollmentIdentifier, walletAddress } = body
×
664

665
      try {
×
666
        let removeWhitelistedResult
667
        if (walletAddress) {
×
668
          removeWhitelistedResult = await AdminWallet.removeWhitelisted(walletAddress)
×
669
        }
670

671
        log.info('admin delete faceid', { enrollmentIdentifier, walletAddress, removeWhitelistedResult })
×
672
        const processor = createEnrollmentProcessor(storage, log)
×
673

674
        await processor.dispose(toLower(enrollmentIdentifier), log)
×
675
        await cancelDisposalTask(storage, enrollmentIdentifier)
×
676
      } catch (exception) {
677
        const { message } = exception
×
678

679
        log.error('delete face record failed:', message, exception, { enrollmentIdentifier })
×
680
        res.status(400).json({ ok: 0, error: message })
×
681
        return
×
682
      }
683

684
      res.json({ ok: 1 })
×
685
    })
686
  )
687

688
  app.post(
2✔
689
    '/admin/verify/face/disposal',
690
    adminAuthenticate,
691
    wrapAsync(async (req, res) => {
692
      const { body, log } = req
×
693
      const { enrollmentIdentifier } = body
×
694

695
      try {
×
696
        const record = await getDisposalTask(storage, enrollmentIdentifier)
×
697
        log.debug('get face disposal task result:', { enrollmentIdentifier, record })
×
698
        return res.json({ ok: 1, record })
×
699
      } catch (exception) {
700
        const { message } = exception
×
701
        log.error('get face disposal task failed:', message, exception, { enrollmentIdentifier })
×
702
        res.status(400).json({ ok: 0, error: message })
×
703
        return
×
704
      }
705
    })
706
  )
707

708
  app.post(
2✔
709
    '/admin/user/verifyAge',
710
    adminAuthenticate,
711
    wrapAsync(async (req, res) => {
712
      const { body, log } = req
×
713
      let result = {}
×
714
      const identifier = body.identifier || sha3(toChecksumAddress(body.account))
×
715
      if (identifier) result = await storage.updateUser({ identifier, ageVerified: true })
×
716
      log.info('admin age verify', body, result)
×
717
      res.json({ ok: 1, result })
×
718
    })
719
  )
720
}
721

722
export default setup
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