@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 +115 -329
- package/migrations.js +48 -0
- package/package.json +2 -2
- package/src/admin-privilege-repo.js +86 -0
- package/src/admin-user-repo.js +428 -0
- package/src/privilege-repo.js +31 -0
- package/src/repository.js +81 -0
- package/test/privileges.test.js +160 -0
- package/test/unit.js +291 -12
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
|
|
15
|
-
|
|
16
|
-
const
|
|
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
|
-
|
|
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('
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
537
|
+
return !!(admin.level === 0 && admin.casesPrivilege)
|
|
777
538
|
}
|
|
778
539
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
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
|
|
801
|
-
|
|
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.
|
|
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.
|
|
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": "
|
|
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
|
-
"@
|
|
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",
|