@bitfinex/bfx-facs-auth-google 1.2.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -6,25 +6,14 @@ const assert = require('assert')
6
6
  const _ = require('lodash')
7
7
  const async = require('async')
8
8
  const crypto = require('crypto')
9
- const DbBase = require('bfx-facs-db-sqlite')
9
+ const DbBase = require('@bitfinex/bfx-facs-db-sqlite')
10
10
  const uuidv4 = require('uuid/v4')
11
+ const { cloneDeep } = require('@bitfinex/lib-js-util-base')
11
12
  const { google } = require('googleapis')
12
- const { UserError } = require('./errors')
13
13
  const migrations = require('./migrations')
14
- const { cloneDeep } = require('@bitfinexcom/lib-js-util-base')
15
-
16
- const FORMS_FIELD = 'forms'
17
-
18
- async function hash (password, salt = '') {
19
- return new Promise((resolve, reject) => {
20
- const computedSalt = salt || crypto.randomBytes(8).toString('hex')
21
-
22
- crypto.scrypt(password, computedSalt, 64, (err, derivedKey) => {
23
- if (err) reject(err)
24
- resolve(computedSalt + ':' + derivedKey.toString('hex'))
25
- })
26
- })
27
- }
14
+ const AdminUserRepository = require('./src/admin-user-repo')
15
+ const PrivilegeRepository = require('./src/privilege-repo')
16
+ const AdminPrivilegeRepository = require('./src/admin-privilege-repo')
28
17
 
29
18
  async function verify (password, hash) {
30
19
  return new Promise((resolve, reject) => {
@@ -36,26 +25,7 @@ async function verify (password, hash) {
36
25
  })
37
26
  }
38
27
 
39
- const tableName = 'admin_users'
40
28
  /**
41
- * @typedef {{
42
- * email: string,
43
- * level: number,
44
- * readOnly?: boolean,
45
- * blockPrivilege?: boolean,
46
- * analyticsPrivilege?: boolean,
47
- * manageAdminsPrivilege?: boolean,
48
- * fetchMotivationsPrivilege?: boolean,
49
- * passwordResetToken?: string,
50
- * passwordResetSentAt?: Date,
51
- * company?: string,
52
- * forms?: string[]
53
- * }} BaseAdminT
54
- * @typedef { BaseAdminT & { password: string }} AddAdminT
55
- * @typedef { BaseAdminT & {
56
- * active: boolean,
57
- * id: number
58
- * }} AddedAdminT
59
29
  * @typedef {{ username: string, password: string }} LoginUserT
60
30
  * @typedef {{
61
31
  * access_token: string | null;
@@ -70,7 +40,7 @@ const tableName = 'admin_users'
70
40
  * id_token?: string | null;
71
41
  * scope?: string;
72
42
  * }} TokenCredentials
73
- * @typedef { AddedAdminT & {
43
+ * @typedef { AdminUserRepository.AddedAdminT & {
74
44
  * username: string,
75
45
  * token: string,
76
46
  * expires_at: Date
@@ -80,23 +50,7 @@ class GoogleAuth extends DbBase {
80
50
  constructor (caller, opts = {}, ctx) {
81
51
  opts.name = 'auth-google'
82
52
  opts.runSqlAtStart = [
83
- `CREATE TABLE IF NOT EXISTS ${tableName} (
84
- id INTEGER PRIMARY KEY AUTOINCREMENT,
85
- email TEXT UNIQUE NOT NULL,
86
- password TEXT,
87
- level INTEGER NOT NULL,
88
- active TINYINTEGER DEFAULT 1,
89
- readOnly TINYINTEGER,
90
- blockPrivilege TINYINTEGER,
91
- analyticsPrivilege TINYINTEGER,
92
- manageAdminsPrivilege TINYINTEGER,
93
- passwordResetToken TEXT,
94
- passwordResetSentAt DATETIME,
95
- company TEXT,
96
- timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
97
- ${FORMS_FIELD} TEXT
98
- )`,
99
- `CREATE UNIQUE INDEX IF NOT EXISTS uidx_email ON ${tableName}(email ASC)`
53
+ ...AdminUserRepository.runSqlAtStart
100
54
  ]
101
55
  super(caller, opts, ctx)
102
56
 
@@ -218,6 +172,11 @@ class GoogleAuth extends DbBase {
218
172
  }
219
173
  },
220
174
  super._start.bind(this),
175
+ async () => {
176
+ this.adminUserRepo = new AdminUserRepository(this.db, this.conf)
177
+ this.privilegeRepo = new PrivilegeRepository(this.db, this.conf)
178
+ this.adminPrivilegeRepo = new AdminPrivilegeRepository(this.db, this.conf)
179
+ },
221
180
  cb => {
222
181
  this.runMigrations(migrations, cb)
223
182
  },
@@ -238,9 +197,9 @@ class GoogleAuth extends DbBase {
238
197
  /**
239
198
  * @param {{ user: LoginUserT, google: Credentials, ip: number }} args
240
199
  * @param { (err: null|Error, res: LoginResp) => void } cb
241
- * @returns { void }
200
+ * @returns { Promise<void> }
242
201
  */
243
- loginAdmin (args, cb) {
202
+ async loginAdmin (args, cb) {
244
203
  const { user, google, ip } = args
245
204
 
246
205
  if (!user && !google) {
@@ -292,7 +251,7 @@ class GoogleAuth extends DbBase {
292
251
  : cb(new Error('AUTH_FAC_ACCOUNT_IS_NOT_VALID'))
293
252
  } catch (e) {
294
253
  console.log(e)
295
- cb(new Error('AUTH_FAC_INCORRECT_GOOGLE_TOKEN'))
254
+ cb(new Error('AUTH_FAC_ACCOUNT_IS_NOT_VALID'))
296
255
  }
297
256
  }
298
257
 
@@ -304,7 +263,8 @@ class GoogleAuth extends DbBase {
304
263
  const query = { username, token, ip, level, expires_at: exp }
305
264
  try {
306
265
  await this._createUniqueAndExpireDbToken(query)
307
- return cb(null, { username, token, level, ...extra, expires_at: exp })
266
+ const privileges = await this.adminPrivilegeRepo.getAdminPrivileges(extra.id)
267
+ return cb(null, { username, token, level, privileges, ...extra, expires_at: exp })
308
268
  } catch (e) {
309
269
  return cb(new Error('AUTH_FAC_ADMIN_TOKEN_CREATE_ERROR'))
310
270
  }
@@ -464,7 +424,7 @@ class GoogleAuth extends DbBase {
464
424
  async _saveAdminsFromConfig () {
465
425
  const admins = this.conf.ADM_USERS
466
426
  if (!(admins && Array.isArray(admins))) return true
467
- const adminDbHasData = await this._checkIfAdminDbHasData()
427
+ const adminDbHasData = await this.adminUserRepo.doesAdminDbHaveData()
468
428
  if (adminDbHasData) return true
469
429
 
470
430
  const tasks = admins.map(async (admin) => {
@@ -479,236 +439,37 @@ class GoogleAuth extends DbBase {
479
439
  }
480
440
 
481
441
  /**
482
- * @param { AddAdminT } user
483
- * @returns { Promise<AddedAdminT> }
442
+ * @param { AdminUserRepository.AddAdminT } user
443
+ * @returns { Promise<AdminUserRepository.AddedAdminT> }
484
444
  */
485
445
  async addAdmin (user) {
486
446
  assert.ok(this.conf.useDB, 'Cannot add admins if DB is not available')
487
447
 
488
- const {
489
- email,
490
- password,
491
- level,
492
- readOnly,
493
- blockPrivilege,
494
- analyticsPrivilege,
495
- manageAdminsPrivilege,
496
- fetchMotivationsPrivilege,
497
- company
498
- } = user
499
-
500
- assert.ok(typeof email === 'string', 'Email is required')
501
- assert.ok(typeof level === 'number', 'Level must be a number')
502
-
503
- if (password) {
504
- assert.ok(typeof password === 'string', 'Password should be a string')
505
- }
506
-
507
- if (readOnly) {
508
- assert.ok(typeof readOnly === 'boolean', 'readOnly should be a boolean')
509
- }
510
-
511
- if (blockPrivilege) {
512
- assert.ok(typeof blockPrivilege === 'boolean', 'blockPrivilege should be a boolean')
513
- }
514
-
515
- if (analyticsPrivilege) {
516
- assert.ok(typeof analyticsPrivilege === 'boolean', 'analyticsPrivilege should be a boolean')
517
- }
518
-
519
- if (manageAdminsPrivilege) {
520
- assert.ok(typeof manageAdminsPrivilege === 'boolean', 'manageAdminsPrivilege should be a boolean')
521
- }
522
-
523
- if (fetchMotivationsPrivilege) {
524
- assert.ok(typeof fetchMotivationsPrivilege === 'boolean', 'fetchMotivationsPrivilege should be a boolean')
525
- }
526
-
527
- if (company) {
528
- assert.ok(typeof company === 'string', 'company should be a string')
529
- }
530
-
531
- const adm = await this._getAdmin(email, false)
532
- if (adm) throw new UserError('ADMIN_ACCOUNT_EXISTS')
533
-
534
- const hashedPassword = password
535
- ? await hash(password, this.conf.hashSalt)
536
- : null
537
-
538
- user.password = hashedPassword
539
-
540
- return new Promise((resolve, reject) => {
541
- const keys = Object.keys(user)
542
-
543
- this.db.run(
544
- `INSERT INTO ${tableName} (${keys.join(', ')}) VALUES (${Array(keys.length).fill('?').join(', ')})`,
545
- keys.map(key => key === FORMS_FIELD ? JSON.stringify(user[key]) : user[key]),
546
- function (err) {
547
- if (err) return reject(err)
548
-
549
- resolve({
550
- email,
551
- level,
552
- readOnly,
553
- blockPrivilege,
554
- analyticsPrivilege,
555
- manageAdminsPrivilege,
556
- fetchMotivationsPrivilege,
557
- company,
558
- active: true,
559
- id: this.lastID
560
- })
561
- }
562
- )
563
- })
448
+ return this.adminUserRepo.add(user)
564
449
  }
565
450
 
566
451
  async updateAdmin (email, user) {
567
- assert.ok(this.conf.useDB, 'Cannot add admins if DB is not available')
568
-
569
- const {
570
- password,
571
- level,
572
- readOnly,
573
- blockPrivilege,
574
- analyticsPrivilege,
575
- manageAdminsPrivilege,
576
- fetchMotivationsPrivilege,
577
- company,
578
- active
579
- } = user
580
-
581
- assert.ok(typeof email === 'string', 'Email is required')
582
-
583
- if (user.email) {
584
- throw new UserError('Email cannot be updated')
585
- }
586
-
587
- if (password) {
588
- throw new UserError('Use Change Password endpoint to update user password')
589
- }
590
-
591
- if (level) {
592
- assert.ok(typeof level === 'number', 'Level must be a number')
593
- }
594
-
595
- if (readOnly) {
596
- assert.ok(typeof readOnly === 'boolean', 'readOnly should be a boolean')
597
- }
598
-
599
- if (blockPrivilege) {
600
- assert.ok(typeof blockPrivilege === 'boolean', 'blockPrivilege should be a boolean')
601
- }
602
-
603
- if (analyticsPrivilege) {
604
- assert.ok(typeof analyticsPrivilege === 'boolean', 'analyticsPrivilege should be a boolean')
605
- }
606
-
607
- if (manageAdminsPrivilege) {
608
- assert.ok(typeof manageAdminsPrivilege === 'boolean', 'manageAdminsPrivilege should be a boolean')
609
- }
452
+ assert.ok(this.conf.useDB, 'Cannot update admins if DB is not available')
610
453
 
611
- if (fetchMotivationsPrivilege) {
612
- assert.ok(typeof fetchMotivationsPrivilege === 'boolean', 'fetchMotivationsPrivilege should be a boolean')
613
- }
614
-
615
- if (company) {
616
- assert.ok(typeof company === 'string', 'company should be a string')
617
- }
618
-
619
- if (active) {
620
- assert.ok(typeof active === 'boolean', 'active should be a boolean')
621
- }
622
-
623
- const adm = await this._getAdmin(email, !active)
624
- if (!adm) throw new UserError('ADMIN_ACCOUNT_DOES_NOT_EXIST_OR_IS_NOT_ACTIVE')
625
-
626
- return new Promise((resolve, reject) => {
627
- const keys = Object.keys(user)
628
-
629
- this.db.run(
630
- `UPDATE ${tableName} SET ${keys.join(' = ?, ')} = ? WHERE id = ?`,
631
- keys.map(key => user[key]).concat(adm.id),
632
- function (err) {
633
- if (err) return reject(err)
634
-
635
- resolve(user)
636
- }
637
- )
638
- })
454
+ return this.adminUserRepo.update(email, user)
639
455
  }
640
456
 
641
457
  async updateAdminPassword (email, newPassword, oldPassword) {
642
- assert.ok(this.conf.useDB, 'Cannot add admins if DB is not available')
643
-
644
- assert.ok(typeof email === 'string', 'Email is required')
645
- assert.ok(typeof newPassword === 'string', 'New Password is required')
646
- assert.ok(typeof oldPassword === 'string', 'Old Password is required')
647
-
648
- const adm = await this._getAdmin(email)
649
- if (!adm) throw new UserError('ADMIN_ACCOUNT_DOES_NOT_EXIST_OR_IS_NOT_ACTIVE')
650
-
651
- if (!(await verify(oldPassword, adm.password))) {
652
- throw new UserError('INVALID_PASSWORD')
653
- }
458
+ assert.ok(this.conf.useDB, 'Cannot update admins if DB is not available')
654
459
 
655
- const password = await hash(newPassword, this.conf.hashSalt)
656
-
657
- return new Promise((resolve, reject) => {
658
- this.db.run(
659
- `UPDATE ${tableName} SET password = ? WHERE id = ?`,
660
- [password, adm.id],
661
- function (err) {
662
- if (err) return reject(err)
663
-
664
- resolve(true)
665
- }
666
- )
667
- })
460
+ return this.adminUserRepo.updatePassword(email, newPassword, oldPassword)
668
461
  }
669
462
 
670
463
  async resetAdminPassword (email, newPassword, passwordResetToken) {
671
- assert.ok(this.conf.useDB, 'Cannot add admins if DB is not available')
672
-
673
- assert.ok(typeof email === 'string', 'Email is required')
674
- assert.ok(typeof newPassword === 'string', 'New Password is required')
675
-
676
- const admin = await this._getAdmin(email)
677
- if (!admin) throw new UserError('ADMIN_ACCOUNT_DOES_NOT_EXIST_OR_IS_NOT_ACTIVE')
678
- if (admin.passwordResetToken !== passwordResetToken) throw new UserError('INVALID_passwordResetToken')
679
- const expiryDate = new Date(admin.passwordResetSentAt)
680
- expiryDate.setDate(expiryDate.getDate() + 1)
681
- if (Date.now() > expiryDate) throw new UserError('RESET_LINK_EXPIRED')
682
-
683
- const password = await hash(newPassword, this.conf.hashSalt)
684
-
685
- return new Promise((resolve, reject) => {
686
- this.db.run(
687
- `UPDATE ${tableName} SET password = ? WHERE id = ?`,
688
- [password, admin.id],
689
- function (err) {
690
- if (err) return reject(err)
464
+ assert.ok(this.conf.useDB, 'Cannot update admins if DB is not available')
691
465
 
692
- resolve(true)
693
- }
694
- )
695
- })
466
+ return this.adminUserRepo.resetPassword(email, newPassword, passwordResetToken)
696
467
  }
697
468
 
698
469
  async removeAdmin (idOrEmail) {
699
470
  assert.ok(this.conf.useDB, 'Cannot remove admins if DB is not available')
700
471
 
701
- return new Promise((resolve, reject) => {
702
- this.db.serialize(() => {
703
- const statement = this.db.prepare(`DELETE FROM ${tableName} WHERE id = ? OR LOWER(email) = ?`)
704
- statement.run([idOrEmail, `${idOrEmail}`.toLowerCase()])
705
- statement.finalize(err => {
706
- if (err) return reject(err)
707
-
708
- resolve(idOrEmail)
709
- })
710
- })
711
- })
472
+ return this.adminUserRepo.remove(idOrEmail)
712
473
  }
713
474
 
714
475
  async basicAuthAdmLogCheck (sentEmail, sentPassword) {
@@ -769,36 +530,35 @@ class GoogleAuth extends DbBase {
769
530
  return !!(admin.level === 0 && admin.manageAdminsPrivilege)
770
531
  }
771
532
 
772
- async checkAdmHasFetchMotivationsPrivilege (adminEmail) {
533
+ async checkAdmHasCasesPrivilege (adminEmail) {
773
534
  const admin = await this._getAdmin(adminEmail)
774
535
  if (!admin) throw new Error('Searched admin was not found')
775
536
 
776
- return !!(admin.level === 0 || admin.fetchMotivationsPrivilege)
537
+ return !!(admin.level === 0 && admin.casesPrivilege)
777
538
  }
778
539
 
779
- _checkIfAdminDbHasData () {
780
- return new Promise((resolve, reject) => {
781
- const query = `SELECT EXISTS(SELECT 1 FROM ${tableName}) as exist`
782
- this.db.get(query, (err, row) => {
783
- if (err) return reject(err)
784
- resolve(row?.exist)
785
- })
786
- })
540
+ async checkAdmHasFetchMotivationsPrivilege (adminEmail) {
541
+ const admin = await this._getAdmin(adminEmail)
542
+ if (!admin) throw new Error('Searched admin was not found')
543
+
544
+ return !!(admin.level === 0 || admin.fetchMotivationsPrivilege)
787
545
  }
788
546
 
789
547
  /**
790
548
  * @param { string|number } emailOrId - Identifier used for searching the admin, it can be either the admin email address or its database id.
791
549
  * @param { boolean } [active=true] - Flag for considering only the active users, it is `true` by default. If `false`, it will search through inactive users too.
792
550
  * @param { boolean } [id=false] - Flag for searching as well by id criterion, it's `false` by default. If `true`, enables the mentioned behavior.
793
- * @returns { Promise<BaseAdminT & { timestamp: Date, active: boolean }> }
551
+ * @returns { Promise<AdminUserRepository.BaseAdminT & { timestamp: Date, active: boolean }> }
794
552
  */
795
553
  async getAdmin (emailOrId, active = true, id = false) {
796
554
  const admin = await this._getAdmin(emailOrId, active, id)
797
- const displayKeys = ['email', 'level', 'blockPrivilege', 'company',
798
- 'analyticsPrivilege', 'manageAdminsPrivilege', 'fetchMotivationsPrivilege', 'readOnly', 'active', 'timestamp', FORMS_FIELD]
555
+ const displayKeys = ['id', 'email', 'level', 'blockPrivilege', 'company',
556
+ 'analyticsPrivilege', 'manageAdminsPrivilege', 'casesPrivilege', 'fetchMotivationsPrivilege', 'readOnly', 'active', 'timestamp', AdminUserRepository.FORMS_FIELD, 'whitelistedIps']
799
557
 
800
- if (this.conf.useDB && admin && admin[FORMS_FIELD]) {
801
- admin[FORMS_FIELD] = JSON.parse(admin[FORMS_FIELD])
558
+ if (this.conf.useDB && admin) {
559
+ for (const field of AdminUserRepository.JSON_FIELDS) {
560
+ if (admin[field]) admin[field] = JSON.parse(admin[field])
561
+ }
802
562
  }
803
563
 
804
564
  return admin
@@ -810,32 +570,10 @@ class GoogleAuth extends DbBase {
810
570
  if (!emailOrId) return false
811
571
 
812
572
  return this.conf.useDB
813
- ? this._getAdminFromDB(emailOrId, active, id)
573
+ ? this.adminUserRepo.getAdmin(emailOrId, active, id)
814
574
  : this._getAdminFromConfig(emailOrId)
815
575
  }
816
576
 
817
- async _getAdminFromDB (emailOrId, active, id) {
818
- return new Promise((resolve, reject) => {
819
- const identifierCondition = id
820
- ? '(LOWER(email) = ? OR id = ?)'
821
- : 'LOWER(email) = ?'
822
-
823
- const query = active
824
- ? `SELECT * FROM ${tableName} WHERE ${identifierCondition} AND active = 1`
825
- : `SELECT * FROM ${tableName} WHERE ${identifierCondition}`
826
-
827
- const params = [String(emailOrId).toLowerCase()]
828
- if (id) {
829
- params.push(emailOrId)
830
- }
831
-
832
- this.db.get(query, params, (err, row) => {
833
- if (err) return reject(err)
834
- resolve(row)
835
- })
836
- })
837
- }
838
-
839
577
  _getAdminFromConfig (email) {
840
578
  const admins = this.conf.ADM_USERS || []
841
579
 
@@ -848,33 +586,10 @@ class GoogleAuth extends DbBase {
848
586
 
849
587
  async getAdminEmails (active = true, company) {
850
588
  return this.conf.useDB
851
- ? this._getAdminEmailsFromDB(active, company)
589
+ ? this.adminUserRepo.getAdminEmails(active, company)
852
590
  : this._getAdminEmailsFromConfig(company)
853
591
  }
854
592
 
855
- async _getAdminEmailsFromDB (active, company) {
856
- return new Promise((resolve, reject) => {
857
- const whereClause = [['active', active && 1], ['company', company && `'${company}'`]].reduce((query, prop) => {
858
- if (prop[1]) {
859
- if (query.length) {
860
- query += ' AND '
861
- }
862
- query += `${prop[0]} = ${prop[1]}`
863
- }
864
- return query
865
- }, '')
866
-
867
- const query = active || company
868
- ? `SELECT LOWER(email) AS email FROM ${tableName} WHERE ${whereClause} ORDER BY email ASC`
869
- : `SELECT LOWER(email) AS email FROM ${tableName} ORDER BY email ASC`
870
-
871
- this.db.all(query, [], (err, rows) => {
872
- if (err) return reject(err)
873
- resolve((rows || []).map(row => row.email))
874
- })
875
- })
876
- }
877
-
878
593
  async _getAdminEmailsFromConfig (company) {
879
594
  const admins = this.conf.ADM_USERS || []
880
595
  return admins
@@ -891,6 +606,77 @@ class GoogleAuth extends DbBase {
891
606
  const admin = await this._getAdmin(email)
892
607
  return !!admin?.password
893
608
  }
609
+
610
+ addPrivilege (name) {
611
+ return this.privilegeRepo.add({ name })
612
+ }
613
+
614
+ getAllPrivileges () {
615
+ return this.privilegeRepo.findAll()
616
+ }
617
+
618
+ /**
619
+ *
620
+ * @param {string} emailOrId
621
+ * @param {number} privilegeId
622
+ * @returns {Promise<{ admin: emailOrId, privilege: string }>}
623
+ */
624
+ async assignAdminPrivilege (emailOrId, privilegeId) {
625
+ const admin = await this.adminUserRepo.getAdmin(emailOrId, true, true)
626
+ if (!admin) throw new Error('INVALID_ADMIN')
627
+
628
+ const privilege = await this.privilegeRepo.findById(privilegeId)
629
+ if (!privilege) throw new Error('INVALID_PRIVILEGE_ID')
630
+
631
+ await this.adminPrivilegeRepo.add({ adminId: admin.id, privilegeId: privilege.id })
632
+ return { admin: emailOrId, privilege: privilege.name }
633
+ }
634
+
635
+ /**
636
+ *
637
+ * @param {string} emailOrId
638
+ * @param {number} privilegeId
639
+ * @returns {Promise<{ admin: emailOrId, privilege: string }>}
640
+ */
641
+ async unAssignAdminPrivilege (emailOrId, privilegeId) {
642
+ const admin = await this.adminUserRepo.getAdmin(emailOrId, true, true)
643
+ if (!admin) throw new Error('INVALID_ADMIN')
644
+
645
+ const privilege = await this.privilegeRepo.findById(privilegeId)
646
+ if (!privilege) throw new Error('INVALID_PRIVILEGE_ID')
647
+
648
+ await this.adminPrivilegeRepo.remove(admin.id, privilege.id)
649
+ return { admin: emailOrId, privilege: privilege.name }
650
+ }
651
+
652
+ /**
653
+ *
654
+ * @param {string} emailOrId
655
+ * @param {string} privilege
656
+ * @returns {Promise<Boolean>}
657
+ */
658
+ async checkAdminHasRequiredPrivilege (emailOrId, privilege) {
659
+ const admin = await this.adminUserRepo.getAdmin(emailOrId, true, true)
660
+ if (!admin) throw new Error('INVALID_ADMIN')
661
+
662
+ const adminPrivilege = await this.adminPrivilegeRepo.findAdminPrivilege(admin.id, privilege)
663
+ return Boolean(adminPrivilege)
664
+ }
665
+
666
+ /**
667
+ * @param { string } email
668
+ * @param { boolean } [active=true]
669
+ * @param { boolean } [id=false]
670
+ * @returns {Promise<AdminUserRepository.BaseAdminT & {timestamp: Date, active: boolean, privileges: Array<{id: number, name: string}>}>}
671
+ */
672
+ async getAdminWithPrivileges (email, active = true, id = false) {
673
+ const admin = await this.getAdmin(email, active, id)
674
+ if (!admin) return admin
675
+
676
+ const privileges = await this.adminPrivilegeRepo.getAdminPrivileges(admin.id)
677
+
678
+ return { ...admin, privileges }
679
+ }
894
680
  }
895
681
 
896
682
  module.exports = GoogleAuth
package/migrations.js CHANGED
@@ -1,6 +1,8 @@
1
1
  'use strict'
2
2
 
3
3
  const tableName = 'admin_users'
4
+ const privilegesTable = 'privileges'
5
+ const adminPrivilegeTable = 'admin_privileges'
4
6
 
5
7
  const migrations = [
6
8
  (_this, cb) => {
@@ -57,6 +59,52 @@ const migrations = [
57
59
  ADD fetchMotivationsPrivilege BOOLEAN
58
60
  `, cb)
59
61
  })
62
+ },
63
+ (_this, cb) => {
64
+ _this.db.all(`PRAGMA table_info(${tableName})`, [], (err, rows) => {
65
+ if (err) return cb(err)
66
+ if (rows?.find(row => row.name === 'casesPrivilege')) return cb()
67
+
68
+ _this.db.run(`
69
+ ALTER TABLE ${tableName}
70
+ ADD casesPrivilege TINYINTEGER
71
+ `, cb)
72
+ })
73
+ },
74
+ (_this, cb) => {
75
+ _this.db.all(`PRAGMA table_info(${tableName})`, [], (err, rows) => {
76
+ if (err) return cb(err)
77
+ if (rows?.find(row => row.name === 'whitelistedIps')) return cb()
78
+
79
+ _this.db.run(`
80
+ ALTER TABLE ${tableName}
81
+ ADD whitelistedIps TEXT
82
+ `, cb)
83
+ })
84
+ },
85
+ (_this, cb) => {
86
+ _this.db.exec('PRAGMA foreign_keys = ON;', cb)
87
+ },
88
+ (_this, cb) => {
89
+ _this.db.run(`
90
+ CREATE TABLE IF NOT EXISTS ${privilegesTable} (
91
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
92
+ name TEXT NOT NULL UNIQUE
93
+ );
94
+ `, cb)
95
+ },
96
+ (_this, cb) => {
97
+ _this.db.run(`
98
+ CREATE TABLE IF NOT EXISTS ${adminPrivilegeTable} (
99
+ admin_id INTEGER NOT NULL,
100
+ privilege_id INTEGER NOT NULL,
101
+
102
+ PRIMARY KEY (admin_id, privilege_id),
103
+
104
+ FOREIGN KEY (admin_id) REFERENCES admin_users(id) ON DELETE CASCADE,
105
+ FOREIGN KEY (privilege_id) REFERENCES privileges(id) ON DELETE CASCADE
106
+ );
107
+ `, cb)
60
108
  }
61
109
  ]
62
110
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitfinex/bfx-facs-auth-google",
3
- "version": "1.2.3",
3
+ "version": "2.0.0",
4
4
  "private": false,
5
5
  "description": "Bitfinex Google Oauth Facility",
6
6
  "author": {
@@ -14,7 +14,7 @@
14
14
  "dependencies": {
15
15
  "@bitfinex/bfx-facs-base": "git+https://github.com/bitfinexcom/bfx-facs-base.git",
16
16
  "@bitfinex/bfx-facs-db-sqlite": "git+https://github.com/bitfinexcom/bfx-facs-db-sqlite.git",
17
- "@bitfinexcom/lib-js-util-base": "git+https://github.com/bitfinexcom/lib-js-util-base.git",
17
+ "@bitfinex/lib-js-util-base": "git+https://github.com/bitfinexcom/lib-js-util-base.git",
18
18
  "async": "^2.6.4",
19
19
  "googleapis": "^68.0.0",
20
20
  "lodash": "^4.17.21",