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

GoodDollar / GoodServer / 13616546152

02 Mar 2025 02:49PM UTC coverage: 49.628% (-0.07%) from 49.697%
13616546152

push

github

sirpy
add: admin age verify

593 of 1473 branches covered (40.26%)

Branch coverage included in aggregate %.

3 of 8 new or added lines in 3 files covered. (37.5%)

1 existing line in 1 file now uncovered.

1873 of 3496 relevant lines covered (53.58%)

8.65 hits per line

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

6.48
/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
  log.info('deleting user from analytics', { userId, walletAddress })
×
27
  const amplitudePromise = fetch(`https://amplitude.com/api/2/deletions/users`, {
28
    headers: { Authorization: `Basic ${conf.amplitudeBasicAuth}`, 'Content-Type': 'application/json' },
29
    method: 'POST',
30

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

51
  return amplitudePromise
×
52
}
×
53

54
const adminAuthenticate = (req, res, next) => {
55
  const { password } = req.body || {}
×
56

57
  if (password !== conf.adminPassword) {
58
    return res.json({ ok: 0 })
2✔
59
  }
2✔
60

61
  next()
62
}
63

64
const setup = (app: Router, storage: StorageAPI) => {
×
65
  app.use(
×
66
    ['/user/*'],
×
67
    passport.authenticate('jwt', { session: false }),
68
    requestRateLimiter(20, 1, 'user'),
×
69
    wrapAsync(async (req, res, next) => {
70
      const { user, body, log } = req
×
71
      const { loggedInAs } = user
×
72
      const identifier = get(body, 'user.identifier', loggedInAs)
×
73

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

76
      if (loggedInAs !== identifier) {
77
        log.warn(`Trying to update other user data! ${loggedInAs}!==${identifier}`)
78
        throw new Error(`Trying to update other user data! ${loggedInAs}!==${identifier}`)
79
      } else next()
80
    })
81
  )
82

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

×
102
      try {
103
        logger.debug('new user request:', { data: userPayload, userRecord })
104
        let { email } = userPayload
105
        email = email.toLowerCase()
×
106

107
        const { mobile, inviteCode, fullName, regMethod, torusProvider } = userPayload
108

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

×
113
        //this modifies userRecord with smsValidated/isEmailConfirmed
×
114
        const { emailVerified, mobileVerified } = await verifier.verifySignInIdentifiers()
×
115

×
116
        const isEmailTorusVerified = email && emailVerified
117
        const isEmailManuallyVerified = email && userRecord.isEmailConfirmed && userRecord.email === sha3(email)
×
118
        const isEmailConfirmed = !!('development' === env || isEmailTorusVerified || isEmailManuallyVerified)
119
        const isMobileTorusVerified = mobile && mobileVerified
120
        const isMobileManuallyVerified = mobile && userRecord.smsValidated && userRecord.mobile === sha3(mobile)
121
        const isMobileConfirmed = !!('development' === env || isMobileTorusVerified || isMobileManuallyVerified)
122

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

×
141
          if (skipEmailVerification === false && isEmailConfirmed === false) {
×
142
            throw new Error('User email not verified!')
143
          }
×
144
        }
×
145

146
        userRecord.isEmailConfirmed = isEmailConfirmed
147
        userRecord.smsValidated = isMobileConfirmed
148

×
149
        if (userRecord.createdDate) {
150
          logger.warn('user already created', { userRecord, userPayload })
151
          // return res.json({ ok: 1 })
152
        }
×
153

×
154
        const toUpdateUser: UserRecord = {
155
          identifier: userRecord.loggedInAs,
156
          regMethod,
157
          torusProvider,
×
158
          email: email ? sha3(email) : userRecord.email,
159
          mobile: mobile ? sha3(mobile) : userRecord.mobile,
160
          fullName,
161
          profilePublickey: userRecord.profilePublickey,
162
          walletAddress: sha3(userRecord.gdAddress.toLowerCase()),
163
          isCompleted: userRecord.isCompleted
164
            ? userRecord.isCompleted
165
            : {
166
                whiteList: false,
167
                topWallet: false
×
168
              },
×
169
          isEmailConfirmed,
170
          smsValidated: isMobileConfirmed
×
171
        }
172

×
173
        const userRecordWithPII = { ...userRecord, ...toUpdateUser, inviteCode, email, mobile }
174
        const signUpPromises = []
×
175

×
176
        const p1 = storage
177
          .updateUser(toUpdateUser)
×
178
          .then(() => logger.debug('updated new user record', { toUpdateUser }))
179
          .catch(e => {
180
            logger.error('failed updating new user record', e.message, e, { toUpdateUser })
×
181
            throw e
×
182
          })
183
        signUpPromises.push(p1)
×
184

×
185
        // whitelisting user if FR is disabled
186
        if (disableFaceVerification) {
187
          const p2 = addUserToWhiteList(userRecord, logger)
×
188
            .then(isWhitelisted => {
×
189
              logger.debug('addUserToWhiteList result', { isWhitelisted })
190
              if (isWhitelisted === false) throw new Error('Failed whitelisting user')
×
191
            })
192
            .catch(e => {
193
              logger.warn('addUserToWhiteList failed', e.message, e, { userRecord })
×
194
              throw e
×
195
            })
×
196
          signUpPromises.push(p2)
197
        }
×
198

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

×
213
        const p5 = Promise.all([
214
          //TODO: generate email/mobile claims using ceramic
215
        ])
×
216
          .then(res => logger.info('created did claims: result', { res }))
217
          .catch(() => {
218
            logger.warn('create did claims: failed')
×
219
          })
220

×
221
        signUpPromises.push(p5)
×
222

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

238
        logger.debug('signup steps success. adding new user:', { toUpdateUser })
239

240
        await storage.updateUser({
×
241
          identifier: userRecord.loggedInAs,
242
          createdDate: userRecord.createdDate || new Date().toString(),
×
243
          otp: {} //delete trace of mobile,email
×
244
        })
245

246
        res.json({ ok: 1 })
247
      } catch (e) {
248
        logger.warn('user signup failed', e.message, e)
249
        throw e
250
      }
251
    })
252
  )
2✔
253

254
  /**
255
   * we had issues with mautic some users are not in the database
×
256
   * fix to make sure we have user data in CRM
×
257
   */
258
  app.post(
×
259
    '/user/verifyCRM',
×
260
    wrapAsync(async (req, res) => {
261
      const { body, log: logger, user: userRecord } = req
×
262
      const { user: userPayload = {} } = body
×
263

264
      try {
×
265
        logger.debug('verify crm:', { data: userPayload, userRecord })
266

×
267
        if (userRecord.crmId) {
×
268
          logger.debug('verifyCRM already has crmID', { crmId: userRecord.crmId })
269
        } else {
×
270
          let { email, mobile, fullName } = userPayload
×
271

272
          if (!email) {
273
            const error = 'verifyCRM missing user email'
×
274

275
            logger.warn(error, { userPayload, userRecord })
×
276
            return res.json({ ok: 0, error })
277
          }
278

279
          let emailLC = email.toLowerCase()
280

281
          const toCRM = {
282
            identifier: userRecord.loggedInAs,
283
            fullName,
284
            walletAddress: sha3(userRecord.gdAddress.toLowerCase()),
285
            email
286
          }
×
287

×
288
          // TODO: verify why this is happening on wallet
289
          // for some reason some emails were kept with capital letter while from user they arrive lower case
290
          // this line is a patch to handle that case
291

×
292
          if (
293
            (sha3(email) === userRecord.email ||
294
              sha3(emailLC) === userRecord.email ||
×
295
              sha3(emailLC.charAt(0).toUpperCase() + emailLC.slice(1)) === userRecord.email) === false
×
296
          ) {
297
            logger.error('unable to verify user email', { email, hash: sha3(email), recordHash: userRecord.email })
298
          }
×
299

300
          if (mobile && sha3(mobile) === userRecord.mobile) {
×
301
            toCRM.mobile = mobile
302
          }
303

304
          const crmId = await createCRMRecord(toCRM, '', logger)
305

306
          await storage.updateUser({
×
307
            email: sha3(email),
308
            identifier: userRecord.loggedInAs,
309
            crmId
×
310
          })
311

×
312
          logger.debug('verifyCRM success', { crmId, toCRM })
×
313
        }
314

315
        res.json({ ok: 1 })
316
      } catch (e) {
317
        logger.error('createCRMRecord failed', e.message, e)
318
        throw new Error('Failed adding user in verifyCRM')
319
      }
320
    })
321
  )
322

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

×
341
      if (existingUser.crmId) {
×
342
        return res.json({ ok: 1 })
343
      }
344

345
      if (!user.email) {
×
346
        logger.error('email missing', { user, existingUser })
×
347
        throw new Error('Email is missed')
348
      }
×
349

350
      // fire and forget, don't wait for success or failure
351
      createCRMRecord({ ...user, email: user.email.toLowerCase() }, utmString, logger)
×
352
        .then(() => logger.debug('/user/start createCRMRecord success'))
353
        .catch(e => {
354
          logger.error('/user/start createCRMRecord failed', e.message, e, { user })
355
        })
356

357
      res.json({ ok: 1 })
358
    })
359
  )
360

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

×
386
  app.post(
387
    '/user/claim',
×
388
    onlyInEnv('production', 'staging'),
×
389
    wrapAsync(async (req, res) => {
390
      let { last_claim, claim_counter } = req.body
×
391
      const { log: logger, user } = req
×
392

×
393
      if (!user.crmId) {
394
        const error = 'user/claim missing crmId'
395

396
        logger.warn(error, { user, body: req.body })
×
397
        res.json({ ok: 0, error })
398
        return
×
399
      }
×
400

×
401
      // format date according to OnGage date format
402
      last_claim = moment().format('YYYY/MM/DD')
403

×
404
      updatesQueue.push({ id: user.crmId, last_claim, claim_counter })
405
      if (updatesQueue.length > 100) {
406
        clearOnGageQueue()
407
      }
408

409
      res.json({ ok: 1 })
410
    })
411
  )
412

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

428
      //first get number of accounts using same crmId before we delete the account
429
      const crmCount = user.crmId
430
        ? await storage.getCountCRMId(user.crmId).catch(e => {
×
431
            log.warn('getCountCRMId failed:', e.message, e)
×
432
            return 1
×
433
          })
×
434
        : 0
×
435

436
      const results = await Promise.all([
×
437
        (user.identifier ? storage.deleteUser(user) : Promise.reject())
438
          .then(() => ({ mongodb: 'ok' }))
439
          .catch(() => ({ mongodb: 'failed' })),
×
440
        crmCount > 1
×
441
          ? Promise.resolve({ crm: 'okMultiNotDeleted' })
442
          : crmCount === 0
443
            ? Promise.resolve({ crm: 'missingId' })
444
            : OnGage.deleteContact(user.crmId, log)
×
445
                .then(() => ({ crm: 'ok' }))
×
446
                .catch(() => ({ crm: 'failed' })),
447
        deleteFromAnalytics(user.identifier, user.gdAddress, log)
448
          .then(() => ({ analytics: 'ok' }))
449
          .catch(() => ({ analytics: 'failed' }))
450
      ])
451
      log.info('delete user results', { user, results })
452
      res.json({ ok: 1, results })
453
    })
454
  )
455

456
  /**
457
   * @api {get} /user/exists return true  if user finished registration
458
   * @apiName Delete
459
   * @apiGroup Storage
460
   *
2✔
461
   * @apiSuccess {Number} ok
462
   * @apiSuccess {Boolean} exists
463
   * @apiSuccess {String} fullName
×
464

465
   * @ignore
×
466
   */
467
  app.get(
468
    '/user/exists',
469
    wrapAsync(async (req, res) => {
470
      const { user } = req
471

472
      res.json({ ok: 1, exists: user.createdDate != null, fullName: user.fullName })
473
    })
474
  )
475

476
  /**
477
   * @api {post} /userExists returns user registration method
478
   * @apiGroup Storage
479
   *
480
   * @apiSuccess {Number} ok
481
   * @apiSuccess {Boolean} exists
2✔
482
   * @apiSuccess {String} fullName
483
   * @apiSuccess {String} provider
484

×
485

×
486
   * @ignore
×
487
   */
488
  app.post(
×
489
    '/userExists',
×
490
    wrapAsync(async (req, res) => {
491
      const { log, body } = req
×
492
      const toHash = value => (value ? sha3(value) : null)
×
493
      const sendNotExists = () => res.json({ ok: 1, exists: false })
494

×
495
      let { identifier = '', email, mobile } = body
496
      email = email ? email.toLowerCase() : undefined
497

×
498
      const lowerCaseID = identifier ? identifier.toLowerCase() : undefined
×
499
      const [emailHash, mobileHash] = [email, mobile].map(toHash)
×
500

501
      const identityFilters = [
×
502
        // identifier is stored lowercase in the db. we lowercase addresses in the /auth/eth process
×
503
        { identifier: lowerCaseID },
×
504
        { email: email && emailHash },
505
        { mobile: mobile && mobileHash }
×
506
      ].filter(or => !!first(values(or)))
507

508
      if (identityFilters.length === 0) {
×
509
        log.warn('empty data for /userExists', { body })
510
        sendNotExists()
511

512
        return
×
513
      }
514

515
      const dateFilters = {
516
        createdDate: { $exists: true }
517
      }
×
518

519
      const providerFilters = [
×
520
        { regMethod: { $type: 'string', $ne: 'torus' } },
521
        { regMethod: 'torus', torusProvider: { $type: 'string', $ne: '' } }
522
      ]
523

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

526
      const allFilters = {
527
        $and: [dateFilters, ...searchFilters]
528
      }
529

530
      const projections = {
531
        identifier: 1,
532
        email: 1,
533
        mobile: 1,
534
        createdDate: 1,
535
        torusProvider: 1,
×
536
        fullName: 1,
537
        regMethod: 1,
×
538
        crmId: 1
539
      }
540

×
541
      // sort by importance, prefer oldest verified account
542
      let ordering = { isVerified: -1, createdDate: 1 }
543

544
      if (lowerCaseID) {
×
545
        // sortBy sorts in ascending order (and keeps existing sort)
546
        // so non-matched by id results would be moved to the end
547
        projections.identifierMatches = {
×
548
          $eq: ['$identifier', lowerCaseID]
549
        }
550

551
        ordering = { identifierMatches: -1, ...ordering }
552
      }
553

×
554
      const existing = await storage.model.aggregate([
555
        { $match: allFilters },
×
556
        { $project: projections },
×
557
        { $sort: ordering }
558
      ])
559

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

×
562
      if (!existing.length) {
563
        return sendNotExists()
564
      }
565

566
      const bestExisting = first(existing)
567

568
      return res.json({
×
569
        ok: 1,
×
570
        exists: true,
571
        found: existing.length,
572
        fullName: bestExisting.fullName,
573
        provider: bestExisting.torusProvider,
574
        identifier: bestExisting.identifierMatches,
575
        email: email && emailHash === bestExisting.email,
2✔
576
        mobile: mobile && mobileHash === bestExisting.mobile,
577
        regMethod: bestExisting.regMethod
578
      })
579
    })
×
580
  )
×
581

×
582
  app.get(
583
    '/userWhitelisted/:account',
×
584
    requestRateLimiter(10, 1),
585
    wrapAsync(async (req, res) => {
586
      const { params } = req
587
      const { account } = params
2✔
588
      const isWhitelisted = await AdminWallet.isVerified(account)
589

590
      res.json({ ok: 1, isWhitelisted })
591
    })
×
592
  )
×
593

594
  app.get(
×
595
    '/syncWhitelist/:account',
×
596
    requestRateLimiter(3, 1),
597
    wrapAsync(async (req, res) => {
×
598
      const { params, log } = req
×
599
      const { account } = params
600

×
601
      try {
×
602
        const whitelisted = await AdminWallet.syncWhitelist(account, log)
603

604
        log.debug('syncWhitelist success', { account, whitelisted })
605
        res.json({ ok: 1, whitelisted })
606
      } catch (e) {
2✔
607
        log.error('failed syncWhitelist', e.message, e, { account })
608
        res.json({ ok: 0, error: e.message })
609
      }
610
    })
×
611
  )
×
612

×
613
  app.post(
×
614
    '/admin/user/get',
×
615
    adminAuthenticate,
×
616
    wrapAsync(async (req, res) => {
×
617
      const { body } = req
×
618
      let user = {}
619
      if (body.email)
×
620
        user = await storage.getUsersByEmail(body.email.startsWith('0x') === false ? sha3(body.email) : body.email)
621
      if (body.mobile)
622
        user = await storage.getUsersByMobile(body.mobile.startsWith('0x') === false ? sha3(body.mobile) : body.mobile)
623
      if (body.identifier) user = await storage.getUser(body.identifier)
2✔
624
      if (body.identifierHash) user = await storage.getByIdentifierHash(body.identifierHash)
625

626
      res.json({ ok: 1, user })
×
627
    })
628
  )
629

2✔
630
  app.post(
631
    '/admin/user/list',
632
    adminAuthenticate,
633
    wrapAsync(async (_, res) => storage.listUsers(list => res.json(list)))
×
634
  )
×
635

×
636
  app.post(
637
    '/admin/user/delete',
×
638
    adminAuthenticate,
639
    wrapAsync(async (req, res) => {
640
      const { body, log } = req
641
      let user = {}
2✔
642
      if (body.email)
643
        user = await storage.getUsersByEmail(body.email.startsWith('0x') === false ? sha3(body.email) : body.email)
644
      if (body.mobile)
645
        user = await storage.getUsersByMobile(body.mobile.startsWith('0x') === false ? sha3(body.mobile) : body.mobile)
×
646
      if (body.identifier) user = await storage.getUser(body.identifier)
×
647
      if (body.identifierHash) user = await storage.getByIdentifierHash(body.identifierHash)
×
648
      if (!user?.length) return res.json({ ok: 0, error: 'User not found' })
×
649
      user = user[0]
×
650
      const crmCount = user.crmId
651
        ? await storage.getCountCRMId(user.crmId).catch(e => {
×
652
            log.warn('getCountCRMId failed:', e.message, e)
×
653
            return 1
654
          })
×
655
        : 0
656

657
      log.info('delete user', { user, crmCount })
658

2✔
659
      const results = await Promise.all([
660
        (user.identifier ? storage.deleteUser(user) : Promise.reject())
661
          .then(() => ({ mongodb: 'ok' }))
662
          .catch(() => ({ mongodb: 'failed' })),
×
663
        crmCount === 0
×
664
          ? Promise.resolve({ crm: 'missingId' })
665
          : OnGage.deleteContact(user.crmId, log)
×
666
              .then(() => ({ crm: 'ok' }))
667
              .catch(() => ({ crm: 'failed' })),
×
668
        deleteFromAnalytics(user.identifier, user.gdAddress, log)
×
669
          .then(() => ({ analytics: 'ok' }))
670
          .catch(() => ({ analytics: 'failed' }))
671
      ])
×
672

×
673
      log.info('delete user results', { user, results })
674
      res.json({ ok: 1, results })
×
675
    })
×
676
  )
677

×
678
  app.post(
679
    '/admin/model/fish',
×
680
    adminAuthenticate,
×
681
    wrapAsync(async (req, res) => {
×
682
      const { body, log } = req
683
      const { daysAgo } = body
684
      if (!daysAgo) return res.json({ ok: 0, error: 'missing daysAgo' })
×
685
      log.debug('fishing request', { daysAgo })
686
      fishManager
687
        .run(daysAgo)
688
        .then(fishResult => log.info('fishing request result:', { fishResult }))
2✔
689
        .catch(e => log.error('fish request failed', e.message, e, { daysAgo }))
690

691
      res.json({ ok: 1 })
692
    })
×
693
  )
×
694

695
  app.post(
×
696
    '/admin/verify/face/delete',
×
697
    adminAuthenticate,
×
698
    wrapAsync(async (req, res) => {
×
699
      const { body, log } = req
700
      const { enrollmentIdentifier, walletAddress } = body
×
701

×
702
      try {
×
703
        let removeWhitelistedResult
×
704
        if (walletAddress) {
705
          removeWhitelistedResult = await AdminWallet.removeWhitelisted(walletAddress)
706
        }
707

708
        log.info('admin delete faceid', { enrollmentIdentifier, walletAddress, removeWhitelistedResult })
2✔
709
        const processor = createEnrollmentProcessor(storage, log)
710

711
        await processor.dispose(toLower(enrollmentIdentifier), log)
NEW
712
        await cancelDisposalTask(storage, enrollmentIdentifier)
×
NEW
713
      } catch (exception) {
×
NEW
714
        const { message } = exception
×
NEW
715

×
NEW
716
        log.error('delete face record failed:', message, exception, { enrollmentIdentifier })
×
717
        res.status(400).json({ ok: 0, error: message })
718
        return
719
      }
720

721
      res.json({ ok: 1 })
722
    })
723
  )
724

725
  app.post(
726
    '/admin/verify/face/disposal',
727
    adminAuthenticate,
728
    wrapAsync(async (req, res) => {
729
      const { body, log } = req
730
      const { enrollmentIdentifier } = body
731

732
      try {
733
        const record = await getDisposalTask(storage, enrollmentIdentifier)
734
        log.debug('get face disposal task result:', { enrollmentIdentifier, record })
735
        return res.json({ ok: 1, record })
736
      } catch (exception) {
737
        const { message } = exception
738
        log.error('get face disposal task failed:', message, exception, { enrollmentIdentifier })
739
        res.status(400).json({ ok: 0, error: message })
740
        return
741
      }
742
    })
743
  )
744

745
  app.post(
746
    '/admin/user/verifyAge',
747
    adminAuthenticate,
748
    wrapAsync(async (req, res) => {
749
      const { body, log } = req
750
      let result = {}
751
      const identifier = body.identifier || sha3(toChecksumAddress(body.account))
752
      if (identifier) result = await storage.updateUser({ identifier, ageVerified: true })
753
      log.info('admin age verify', body, result)
754
      res.json({ ok: 1, result })
755
    })
756
  )
757
}
758

759
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