@abtnode/core 1.16.32-beta-4d47ae7f → 1.16.32-beta-0593a408

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/lib/api/team.js CHANGED
@@ -312,6 +312,8 @@ class TeamAPI extends EventEmitter {
312
312
  'url',
313
313
  'inviter',
314
314
  'generation',
315
+ 'emailVerified',
316
+ 'phoneVerified',
315
317
  'userSessions',
316
318
  // oauth relate fields
317
319
  'sourceProvider',
@@ -373,6 +375,16 @@ class TeamAPI extends EventEmitter {
373
375
  return state.getUser(user.did, options);
374
376
  }
375
377
 
378
+ async isEmailUsed({ teamDid, email, verified = false }) {
379
+ const state = await this.getUserState(teamDid);
380
+ return state.isEmailUsed(email, verified);
381
+ }
382
+
383
+ async isPhoneUsed({ teamDid, phone, verified = false }) {
384
+ const state = await this.getUserState(teamDid);
385
+ return state.isPhoneUsed(phone, verified);
386
+ }
387
+
376
388
  async getUserByDid({ teamDid, userDid }) {
377
389
  const state = await this.getUserState(teamDid);
378
390
  return state.getUserByDid(userDid);
@@ -412,6 +424,10 @@ class TeamAPI extends EventEmitter {
412
424
  const doc = await state.updateUser(user.did, user);
413
425
  logger.info('user updated successfully', { teamDid, userDid: user.did });
414
426
  this.emit(TeamEvents.userUpdated, { teamDid, user: doc });
427
+ if (user.kycChanged) {
428
+ logger.info('user updated with kycChanged', { teamDid, userDid: user.did });
429
+ this.emit(TeamEvents.userPermissionUpdated, { teamDid, user: doc });
430
+ }
415
431
  return doc;
416
432
  }
417
433
 
@@ -1437,6 +1453,10 @@ class TeamAPI extends EventEmitter {
1437
1453
  return this.teamManager.getUserState(did);
1438
1454
  }
1439
1455
 
1456
+ getVerifyCodeState(did) {
1457
+ return this.teamManager.getVerifyCodeState(did);
1458
+ }
1459
+
1440
1460
  getUserSessionState(did) {
1441
1461
  return this.teamManager.getUserSessionState(did);
1442
1462
  }
@@ -1732,6 +1752,37 @@ class TeamAPI extends EventEmitter {
1732
1752
  }
1733
1753
  return result;
1734
1754
  }
1755
+
1756
+ // KYC related
1757
+ async createVerifyCode({ teamDid, subject, scope = 'email' }) {
1758
+ const state = await this.getVerifyCodeState(teamDid);
1759
+ return state.create(subject, scope);
1760
+ }
1761
+
1762
+ async consumeVerifyCode({ teamDid, code }) {
1763
+ const state = await this.getVerifyCodeState(teamDid);
1764
+ return state.verify(code);
1765
+ }
1766
+
1767
+ async sendVerifyCode({ teamDid, code }) {
1768
+ const state = await this.getVerifyCodeState(teamDid);
1769
+ return state.send(code);
1770
+ }
1771
+
1772
+ async isSubjectVerified({ teamDid, subject }) {
1773
+ const state = await this.getVerifyCodeState(teamDid);
1774
+ return state.isVerified(subject);
1775
+ }
1776
+
1777
+ async isSubjectSent({ teamDid, subject }) {
1778
+ const state = await this.getVerifyCodeState(teamDid);
1779
+ return state.isSent(subject);
1780
+ }
1781
+
1782
+ async getVerifyCode({ teamDid, code }) {
1783
+ const state = await this.getVerifyCodeState(teamDid);
1784
+ return state.findOne({ code });
1785
+ }
1735
1786
  }
1736
1787
 
1737
1788
  module.exports = TeamAPI;
@@ -1888,6 +1888,18 @@ class DiskBlockletManager extends BaseBlockletManager {
1888
1888
  sessionConfig.ttl = validateConfig.ttl;
1889
1889
  }
1890
1890
 
1891
+ sessionConfig.email = validateConfig.email;
1892
+ sessionConfig.phone = validateConfig.phone;
1893
+
1894
+ // Do some final check
1895
+ if (
1896
+ sessionConfig.email?.enabled &&
1897
+ sessionConfig.email?.requireVerified &&
1898
+ !blocklet.settings.notification?.email?.enabled
1899
+ ) {
1900
+ throw new Error('Email verification is required but email notification is not enabled');
1901
+ }
1902
+
1891
1903
  await states.blockletExtras.setSettings(blocklet.meta.did, { session: sessionConfig });
1892
1904
  this.emit(BlockletInternalEvents.appSettingChanged, { appDid: did });
1893
1905
 
@@ -2383,10 +2395,6 @@ class DiskBlockletManager extends BaseBlockletManager {
2383
2395
  return newState;
2384
2396
  }
2385
2397
 
2386
- getInviteSettings({ did }) {
2387
- return states.blockletExtras.getSettings(did, 'invite', { enabled: false });
2388
- }
2389
-
2390
2398
  deleteCache(did) {
2391
2399
  const cache = this.cachedBlocklets.get(did);
2392
2400
  if (cache) {
@@ -4871,6 +4879,8 @@ class FederatedBlockletManager extends DiskBlockletManager {
4871
4879
  'sourceAppPid',
4872
4880
  'inviter',
4873
4881
  'generation',
4882
+ 'emailVerified',
4883
+ 'phoneVerified',
4874
4884
  ])
4875
4885
  );
4876
4886
  }
package/lib/index.js CHANGED
@@ -445,6 +445,8 @@ function ABTNode(options) {
445
445
  getUserByDid: teamAPI.getUserByDid.bind(teamAPI),
446
446
  isPassportValid: teamAPI.isPassportValid.bind(teamAPI),
447
447
  isConnectedAccount: teamAPI.isConnectedAccount.bind(teamAPI),
448
+ isEmailUsed: teamAPI.isEmailUsed.bind(teamAPI),
449
+ isPhoneUsed: teamAPI.isPhoneUsed.bind(teamAPI),
448
450
  switchProfile: teamAPI.switchProfile.bind(teamAPI),
449
451
 
450
452
  // user-session
@@ -496,6 +498,14 @@ function ABTNode(options) {
496
498
  configTrustedFactories: teamAPI.configTrustedFactories.bind(teamAPI),
497
499
  configPassportIssuance: teamAPI.configPassportIssuance.bind(teamAPI),
498
500
 
501
+ // KYC
502
+ createVerifyCode: teamAPI.createVerifyCode.bind(teamAPI),
503
+ sendVerifyCode: teamAPI.sendVerifyCode.bind(teamAPI),
504
+ consumeVerifyCode: teamAPI.consumeVerifyCode.bind(teamAPI),
505
+ getVerifyCode: teamAPI.getVerifyCode.bind(teamAPI),
506
+ isSubjectSent: teamAPI.isSubjectSent.bind(teamAPI),
507
+ isSubjectVerified: teamAPI.isSubjectVerified.bind(teamAPI),
508
+
499
509
  // Team Settings
500
510
  addBlockletStore: teamAPI.addStore.bind(teamAPI),
501
511
  deleteBlockletStore: teamAPI.deleteStore.bind(teamAPI),
@@ -520,7 +520,7 @@ const getScope = (args = {}) => {
520
520
  };
521
521
 
522
522
  const fixActor = (actor) => {
523
- if ([NODE_SERVICES.AUTH_SERVICE, 'blocklet'].includes(actor?.role)) {
523
+ if ([NODE_SERVICES.AUTH, 'blocklet'].includes(actor?.role)) {
524
524
  actor.role = '';
525
525
  }
526
526
  };
@@ -496,6 +496,8 @@ SELECT did,inviter,generation FROM UserTree`.trim();
496
496
  'remark',
497
497
  'inviter',
498
498
  'generation',
499
+ 'emailVerified',
500
+ 'phoneVerified',
499
501
  'sourceAppPid',
500
502
  ]),
501
503
  lastLoginAt: now,
@@ -587,7 +589,21 @@ SELECT did,inviter,generation FROM UserTree`.trim();
587
589
 
588
590
  // eslint-disable-next-line require-await
589
591
  async getUserByDid(did) {
590
- return this.findOne({ did }, { did: 1, pk: 1, fullName: 1, email: 1, role: 1, approved: 1 });
592
+ return this.findOne(
593
+ { did },
594
+ {
595
+ did: 1,
596
+ pk: 1,
597
+ fullName: 1,
598
+ email: 1,
599
+ role: 1,
600
+ approved: 1,
601
+ inviter: 1,
602
+ generation: 1,
603
+ emailVerified: 1,
604
+ phoneVerified: 1,
605
+ }
606
+ );
591
607
  }
592
608
 
593
609
  async isUserValid(did) {
@@ -641,6 +657,24 @@ SELECT did,inviter,generation FROM UserTree`.trim();
641
657
  const passport = await this.passport.findOne({ id: passportId });
642
658
  return passport;
643
659
  }
660
+
661
+ async isEmailUsed(email, verified = false) {
662
+ const condition = { email };
663
+ if (verified) {
664
+ condition.emailVerified = true;
665
+ }
666
+ const doc = await this.findOne(condition);
667
+ return email && !!doc;
668
+ }
669
+
670
+ async isPhoneUsed(phone, verified = false) {
671
+ const condition = { phone };
672
+ if (verified) {
673
+ condition.phoneVerified = true;
674
+ }
675
+ const doc = await this.findOne(condition);
676
+ return phone && !!doc;
677
+ }
644
678
  }
645
679
 
646
680
  module.exports = User;
@@ -0,0 +1,120 @@
1
+ const logger = require('@abtnode/logger')('@abtnode/core:states:verify-code');
2
+
3
+ const { Hasher } = require('@ocap/mcrypto');
4
+ const { Joi } = require('@arcblock/validator');
5
+ const { VERIFY_CODE_LENGTH, VERIFY_CODE_TTL, VERIFY_SEND_TTL } = require('@abtnode/constant');
6
+
7
+ const BaseState = require('./base');
8
+
9
+ const schema = Joi.string().email().required();
10
+
11
+ /**
12
+ * @extends BaseState<import('@abtnode/models').VerifyCodeState>
13
+ */
14
+ class VerifyCodeState extends BaseState {
15
+ async create(subject, scope = 'email') {
16
+ if (scope === 'email') {
17
+ const { error } = schema.validate(subject);
18
+ if (error) {
19
+ throw new Error('Invalid email specified when create verify code');
20
+ }
21
+ }
22
+
23
+ const count = await super.remove({ subject, verified: false });
24
+ if (count > 0) {
25
+ logger.info('remove old verify code', { subject });
26
+ }
27
+
28
+ return this.insert({
29
+ code: await this.getVerifyCode(),
30
+ subject,
31
+ digest: Hasher.SHA3.hash256(subject),
32
+ scope,
33
+ verified: false,
34
+ expired: false,
35
+ sent: false,
36
+ });
37
+ }
38
+
39
+ async verify(code) {
40
+ if (!code || code.length !== VERIFY_CODE_LENGTH) {
41
+ throw new Error('verify code invalid');
42
+ }
43
+
44
+ const doc = await super.findOne({ code });
45
+ if (!doc) {
46
+ throw new Error('verify code not found');
47
+ }
48
+
49
+ if (doc.expired) {
50
+ throw new Error('verify code has expired');
51
+ }
52
+
53
+ if (+new Date() > +doc.createdAt + VERIFY_CODE_TTL) {
54
+ logger.info('expire verify token', { code, subject: doc.subject });
55
+ await super.update({ code }, { expired: true, expiredAt: Date.now() });
56
+ throw new Error('verify code has expired');
57
+ }
58
+
59
+ if (doc.verified) {
60
+ throw new Error('verify code has been consumed');
61
+ }
62
+
63
+ logger.info('consume verify code', { code, subject: doc.subject });
64
+ const [, [updated]] = await super.update({ code }, { verified: true, verifiedAt: Date.now() });
65
+ // await super.remove({ subject: doc.subject, verified: false });
66
+
67
+ return updated;
68
+ }
69
+
70
+ async send(code) {
71
+ if (!code || code.length !== VERIFY_CODE_LENGTH) {
72
+ throw new Error('verify code invalid');
73
+ }
74
+
75
+ const doc = await super.findOne({ code });
76
+ if (!doc) {
77
+ throw new Error('verify code not found');
78
+ }
79
+
80
+ if (doc.sent) {
81
+ throw new Error('verify code has sent');
82
+ }
83
+
84
+ logger.info('send verify code', { code, subject: doc.subject });
85
+ const [, [updated]] = await super.update({ code }, { sent: true, sentAt: Date.now() });
86
+ return updated;
87
+ }
88
+
89
+ async isVerified(subject) {
90
+ const doc = await this.findOne({ subject, verified: true });
91
+ return !!doc;
92
+ }
93
+
94
+ async isSent(subject) {
95
+ const doc = await this.findOne({ subject, sent: true });
96
+ if (!doc) {
97
+ return false;
98
+ }
99
+
100
+ return doc.sentAt && +doc.sentAt + VERIFY_SEND_TTL > +new Date();
101
+ }
102
+
103
+ async getVerifyCode() {
104
+ let code;
105
+ let exist;
106
+ do {
107
+ code = this.generate();
108
+ // eslint-disable-next-line no-await-in-loop
109
+ exist = await super.findOne({ code });
110
+ } while (exist);
111
+
112
+ return code;
113
+ }
114
+
115
+ generate() {
116
+ return Array.from({ length: VERIFY_CODE_LENGTH }, () => Math.floor(Math.random() * 10)).join('');
117
+ }
118
+ }
119
+
120
+ module.exports = VerifyCodeState;
@@ -35,6 +35,7 @@ const States = {
35
35
  Project: require('../states/project'),
36
36
  Release: require('../states/release'),
37
37
  Notification: require('../states/notification'),
38
+ VerifyCode: require('../states/verify-code'),
38
39
  };
39
40
 
40
41
  const getDefaultTeamState = () => ({
@@ -47,6 +48,7 @@ const getDefaultTeamState = () => ({
47
48
  project: null,
48
49
  release: null,
49
50
  notification: null,
51
+ verifyCode: null,
50
52
  });
51
53
 
52
54
  class TeamManager extends EventEmitter {
@@ -104,6 +106,7 @@ class TeamManager extends EventEmitter {
104
106
  connectedAccount: await this.createState(this.nodeDid, 'ConnectedAccount'),
105
107
  session: await this.createState(this.nodeDid, 'Session'),
106
108
  notification: await this.createState(this.nodeDid, 'Notification'),
109
+ verifyCode: await this.createState(this.nodeDid, 'VerifyCode'),
107
110
  };
108
111
  }
109
112
 
@@ -135,6 +138,10 @@ class TeamManager extends EventEmitter {
135
138
  return this.getState(teamDid, 'notification');
136
139
  }
137
140
 
141
+ getVerifyCodeState(teamDid) {
142
+ return this.getState(teamDid, 'verifyCode');
143
+ }
144
+
138
145
  async createNotification({ teamDid, receiver, ...payload }) {
139
146
  const notification = await this.getState(teamDid, 'notification');
140
147
  const doc = await notification.create({ ...payload, teamDid });
@@ -28,6 +28,39 @@ const sessionConfigSchema = Joi.object({
28
28
  ttl: Joi.number()
29
29
  .min(86400) // 1d
30
30
  .max(86400 * 30), // 30d
31
+ email: Joi.object({
32
+ enabled: Joi.boolean().default(false),
33
+ requireVerified: Joi.boolean().default(false),
34
+ requireUnique: Joi.boolean().default(false),
35
+ trustOauthProviders: Joi.boolean().default(false),
36
+ domainBlackList: Joi.array().items(Joi.string()).optional().default([]),
37
+ trustedIssuers: Joi.array()
38
+ .items(
39
+ Joi.object({
40
+ id: Joi.DID().required(),
41
+ name: Joi.string().required(),
42
+ pk: Joi.string().optional(),
43
+ })
44
+ )
45
+ .optional()
46
+ .default([]),
47
+ }),
48
+ phone: Joi.object({
49
+ enabled: Joi.boolean().default(false),
50
+ requireVerified: Joi.boolean().default(false),
51
+ requireUnique: Joi.boolean().default(false),
52
+ regionBlackList: Joi.array().items(Joi.string()).optional().default([]),
53
+ trustedIssuers: Joi.array()
54
+ .items(
55
+ Joi.object({
56
+ id: Joi.DID().required(),
57
+ name: Joi.string().required(),
58
+ pk: Joi.string().optional(),
59
+ })
60
+ )
61
+ .optional()
62
+ .default([]),
63
+ }),
31
64
  });
32
65
 
33
66
  module.exports = {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.16.32-beta-4d47ae7f",
6
+ "version": "1.16.32-beta-0593a408",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -19,19 +19,19 @@
19
19
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
20
20
  "license": "Apache-2.0",
21
21
  "dependencies": {
22
- "@abtnode/analytics": "1.16.32-beta-4d47ae7f",
23
- "@abtnode/auth": "1.16.32-beta-4d47ae7f",
24
- "@abtnode/certificate-manager": "1.16.32-beta-4d47ae7f",
25
- "@abtnode/constant": "1.16.32-beta-4d47ae7f",
26
- "@abtnode/cron": "1.16.32-beta-4d47ae7f",
27
- "@abtnode/logger": "1.16.32-beta-4d47ae7f",
28
- "@abtnode/models": "1.16.32-beta-4d47ae7f",
29
- "@abtnode/queue": "1.16.32-beta-4d47ae7f",
30
- "@abtnode/rbac": "1.16.32-beta-4d47ae7f",
31
- "@abtnode/router-provider": "1.16.32-beta-4d47ae7f",
32
- "@abtnode/static-server": "1.16.32-beta-4d47ae7f",
33
- "@abtnode/timemachine": "1.16.32-beta-4d47ae7f",
34
- "@abtnode/util": "1.16.32-beta-4d47ae7f",
22
+ "@abtnode/analytics": "1.16.32-beta-0593a408",
23
+ "@abtnode/auth": "1.16.32-beta-0593a408",
24
+ "@abtnode/certificate-manager": "1.16.32-beta-0593a408",
25
+ "@abtnode/constant": "1.16.32-beta-0593a408",
26
+ "@abtnode/cron": "1.16.32-beta-0593a408",
27
+ "@abtnode/logger": "1.16.32-beta-0593a408",
28
+ "@abtnode/models": "1.16.32-beta-0593a408",
29
+ "@abtnode/queue": "1.16.32-beta-0593a408",
30
+ "@abtnode/rbac": "1.16.32-beta-0593a408",
31
+ "@abtnode/router-provider": "1.16.32-beta-0593a408",
32
+ "@abtnode/static-server": "1.16.32-beta-0593a408",
33
+ "@abtnode/timemachine": "1.16.32-beta-0593a408",
34
+ "@abtnode/util": "1.16.32-beta-0593a408",
35
35
  "@arcblock/did": "1.18.135",
36
36
  "@arcblock/did-auth": "1.18.135",
37
37
  "@arcblock/did-ext": "^1.18.135",
@@ -42,13 +42,13 @@
42
42
  "@arcblock/pm2-events": "^0.0.5",
43
43
  "@arcblock/validator": "^1.18.135",
44
44
  "@arcblock/vc": "1.18.135",
45
- "@blocklet/constant": "1.16.32-beta-4d47ae7f",
46
- "@blocklet/env": "1.16.32-beta-4d47ae7f",
47
- "@blocklet/meta": "1.16.32-beta-4d47ae7f",
48
- "@blocklet/resolver": "1.16.32-beta-4d47ae7f",
49
- "@blocklet/sdk": "1.16.32-beta-4d47ae7f",
50
- "@blocklet/store": "1.16.32-beta-4d47ae7f",
51
- "@did-space/client": "^0.5.32",
45
+ "@blocklet/constant": "1.16.32-beta-0593a408",
46
+ "@blocklet/env": "1.16.32-beta-0593a408",
47
+ "@blocklet/meta": "1.16.32-beta-0593a408",
48
+ "@blocklet/resolver": "1.16.32-beta-0593a408",
49
+ "@blocklet/sdk": "1.16.32-beta-0593a408",
50
+ "@blocklet/store": "1.16.32-beta-0593a408",
51
+ "@did-space/client": "^0.5.33",
52
52
  "@fidm/x509": "^1.2.1",
53
53
  "@ocap/mcrypto": "1.18.135",
54
54
  "@ocap/util": "1.18.135",
@@ -105,5 +105,5 @@
105
105
  "jest": "^29.7.0",
106
106
  "unzipper": "^0.10.11"
107
107
  },
108
- "gitHead": "740b8032aa02e19b888bf6a257840089696613ef"
108
+ "gitHead": "5cca981049ad04018d8d361d9cdd33748f47d7d7"
109
109
  }