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

GoodDollar / GoodServer / 12412034147

18 Dec 2024 10:00AM UTC coverage: 49.344% (-0.8%) from 50.167%
12412034147

push

github

sirpy
add: send captcha type

573 of 1443 branches covered (39.71%)

Branch coverage included in aggregate %.

0 of 2 new or added lines in 1 file covered. (0.0%)

611 existing lines in 11 files now uncovered.

1833 of 3433 relevant lines covered (53.39%)

8.59 hits per line

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

6.22
/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
24

2✔
25
const deleteFromAnalytics = (userId, walletAddress, log) => {
26
  const amplitudePromise = fetch(`https://amplitude.com/api/2/deletions/users`, {
2✔
UNCOV
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())
UNCOV
37
    .then(_ => {
×
38
      log.info('amplitude delete user result', { result: _ })
39
      return {
×
UNCOV
40
        amplitude: 'ok'
×
41
      }
42
    })
43
    .catch(() => ({ amplitude: 'failed' }))
UNCOV
44

×
45
  return [amplitudePromise]
UNCOV
46
}
×
47

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

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

55
  next()
UNCOV
56
}
×
57

58
const setup = (app: Router, storage: StorageAPI) => {
59
  app.use(
2✔
60
    ['/user/*'],
2✔
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)
×
UNCOV
67

×
68
      log.trace(`/user/* ${req.baseUrl} auth:`, { user, body })
UNCOV
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()
×
UNCOV
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(
88
    '/user/add',
2✔
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
×
UNCOV
95

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

×
101
        const { mobile, inviteCode, fullName, regMethod, torusProvider } = userPayload
UNCOV
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)
UNCOV
106

×
107
        //this modifies userRecord with smsValidated/isEmailConfirmed
108
        const { emailVerified, mobileVerified } = await verifier.verifySignInIdentifiers()
UNCOV
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)
×
UNCOV
116

×
117
        logger.debug('new user verification result:', {
UNCOV
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!')
×
UNCOV
133
          }
×
134

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

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

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

148
        const toUpdateUser: UserRecord = {
UNCOV
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 = []
×
UNCOV
169

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

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

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

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

215
        signUpPromises.push(p5)
UNCOV
216

×
217
        // don't await, if we failed to update its not critical for user.
218
        withTimeout(Promise.all(signUpPromises), 30000, 'signup promises timeout')
UNCOV
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 })
×
UNCOV
227
                })
×
228
            }
229
          })
230
          .catch(e => logger.error('signup promises failed', e.message, e))
UNCOV
231

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

×
234
        await storage.updateUser({
UNCOV
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 })
UNCOV
241
      } catch (e) {
×
242
        logger.warn('user signup failed', e.message, e)
243
        throw e
×
UNCOV
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(
253
    '/user/verifyCRM',
2✔
254
    wrapAsync(async (req, res) => {
255
      const { body, log: logger, user: userRecord } = req
256
      const { user: userPayload = {} } = body
×
UNCOV
257

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

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

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

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

273
          let emailLC = email.toLowerCase()
UNCOV
274

×
275
          const toCRM = {
UNCOV
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 (
UNCOV
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 })
UNCOV
292
          }
×
293

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

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

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

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

309
        res.json({ ok: 1 })
UNCOV
310
      } catch (e) {
×
311
        logger.error('createCRMRecord failed', e.message, e)
312
        throw new Error('Failed adding user in verifyCRM')
×
UNCOV
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(
328
    '/user/start',
2✔
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
×
UNCOV
334

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

339
      if (!user.email) {
340
        logger.error('email missing', { user, existingUser })
×
341
        throw new Error('Email is missed')
×
UNCOV
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'))
×
UNCOV
347
        .catch(e => {
×
348
          logger.error('/user/start createCRMRecord failed', e.message, e, { user })
UNCOV
349
        })
×
350

351
      res.json({ ok: 1 })
UNCOV
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
366
  const qLogger = Logger.child({ from: 'OnGageQueue' })
2✔
367
  const clearOnGageQueue = async () => {
2✔
368
    if (updatesQueue.length === 0) return
2✔
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 })
×
UNCOV
374
    } catch (e) {
×
375
      qLogger.error('/user/claim updateContacts failed', e.message, e, { oldQueue })
UNCOV
376
    }
×
377
  }
378
  if (conf.env !== 'test') setInterval(clearOnGageQueue, 60 * 1000)
379

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

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

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

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

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

403
      res.json({ ok: 1 })
UNCOV
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(
417
    '/user/delete',
2✔
418
    wrapAsync(async (req, res) => {
419
      const { user, log } = req
420
      log.info('delete user', { user })
×
UNCOV
421

×
UNCOV
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
UNCOV
427
          })
×
UNCOV
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
UNCOV
435
          ? Promise.resolve({ crm: 'okMultiNotDeleted' })
×
UNCOV
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 })
UNCOV
446
    })
×
UNCOV
447
  )
×
448

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

458
   * @ignore
459
   */
460
  app.get(
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
  )
2✔
468

469
  /**
UNCOV
470
   * @api {post} /userExists returns user registration method
×
471
   * @apiGroup Storage
UNCOV
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(
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
2✔
489
      email = email ? email.toLowerCase() : undefined
490

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

×
494
      const identityFilters = [
UNCOV
495
        // identifier is stored lowercase in the db. we lowercase addresses in the /auth/eth process
×
UNCOV
496
        { identifier: lowerCaseID },
×
497
        { email: email && emailHash },
UNCOV
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
×
UNCOV
506
      }
×
507

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

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

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

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

523
      const projections = {
UNCOV
524
        identifier: 1,
×
525
        email: 1,
UNCOV
526
        mobile: 1,
×
527
        createdDate: 1,
528
        torusProvider: 1,
529
        fullName: 1,
UNCOV
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]
UNCOV
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 }
UNCOV
551
      ])
×
552

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

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

559
      const bestExisting = first(existing)
UNCOV
560

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

575
  app.get(
×
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

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

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

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

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

×
606
  app.post(
UNCOV
607
    '/admin/user/get',
×
UNCOV
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)
2✔
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)
×
UNCOV
618

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

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

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

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

×
UNCOV
641
  app.post(
×
UNCOV
642
    '/admin/model/fish',
×
643
    adminAuthenticate,
UNCOV
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 })
2✔
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 }))
×
UNCOV
653

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

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

665
      try {
2✔
666
        let removeWhitelistedResult
667
        if (walletAddress) {
668
          removeWhitelistedResult = await AdminWallet.removeWhitelisted(walletAddress)
UNCOV
669
        }
×
UNCOV
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
UNCOV
678

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

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

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

695
      try {
2✔
696
        const record = await getDisposalTask(storage, enrollmentIdentifier)
697
        log.debug('get face disposal task result:', { enrollmentIdentifier, record })
698
        return res.json({ ok: 1, record })
UNCOV
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
×
UNCOV
704
      }
×
UNCOV
705
    })
×
706
  )
UNCOV
707

×
UNCOV
708
  app.post(
×
UNCOV
709
    '/admin/user/verifyAge',
×
UNCOV
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