@abtnode/core 1.16.30 → 1.16.31-beta-4246ab25

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
@@ -282,7 +282,7 @@ class TeamAPI extends EventEmitter {
282
282
  const now = Date.now();
283
283
  let sessionTtl = SESSION_TTL;
284
284
  let blocklet;
285
- if (teamDid !== nodeInfo.did) {
285
+ if (teamDid !== nodeInfo.did && query?.includeUserSessions) {
286
286
  blocklet = await getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs });
287
287
  sessionTtl = blocklet.settings?.session?.ttl || SESSION_TTL;
288
288
  }
@@ -310,6 +310,8 @@ class TeamAPI extends EventEmitter {
310
310
  'locale',
311
311
  'tags',
312
312
  'url',
313
+ 'inviter',
314
+ 'generation',
313
315
  'userSessions',
314
316
  // oauth relate fields
315
317
  'sourceProvider',
@@ -2372,6 +2372,20 @@ class DiskBlockletManager extends BaseBlockletManager {
2372
2372
  return states.backup.getBlockletBackups({ did });
2373
2373
  }
2374
2374
 
2375
+ async updateInviteSettings({ did, invite }, context) {
2376
+ await states.blockletExtras.setSettings(did, { invite });
2377
+
2378
+ const newState = await this.getBlocklet(did);
2379
+ this.emit(BlockletInternalEvents.appSettingChanged, { appDid: did });
2380
+ this.emit(BlockletEvents.updated, { ...newState, context });
2381
+
2382
+ return newState;
2383
+ }
2384
+
2385
+ getInviteSettings({ did }) {
2386
+ return states.blockletExtras.getSettings(did, 'invite', { enabled: false });
2387
+ }
2388
+
2375
2389
  deleteCache(did) {
2376
2390
  const cache = this.cachedBlocklets.get(did);
2377
2391
  if (cache) {
@@ -4854,6 +4868,8 @@ class FederatedBlockletManager extends DiskBlockletManager {
4854
4868
  'disconnectedAccount',
4855
4869
  'action',
4856
4870
  'sourceAppPid',
4871
+ 'inviter',
4872
+ 'generation',
4857
4873
  ])
4858
4874
  );
4859
4875
  }
package/lib/index.js CHANGED
@@ -377,15 +377,15 @@ function ABTNode(options) {
377
377
  updateBlockletSpaceGateway: blockletManager.updateBlockletSpaceGateway.bind(blockletManager),
378
378
  getBlockletSpaceGateways: blockletManager.getBlockletSpaceGateways.bind(blockletManager),
379
379
 
380
- // auto backup
380
+ // auto backup related
381
381
  updateAutoBackup: blockletManager.updateAutoBackup.bind(blockletManager),
382
-
383
- // blocklet backup record
384
382
  getBlockletBackups: blockletManager.getBlockletBackups.bind(blockletManager),
385
383
 
386
384
  // check update
387
385
  updateAutoCheckUpdate: blockletManager.updateAutoCheckUpdate.bind(blockletManager),
388
386
 
387
+ updateInviteSettings: blockletManager.updateInviteSettings.bind(blockletManager),
388
+
389
389
  // Store
390
390
  getBlockletMeta: StoreUtil.getBlockletMeta,
391
391
  getStoreMeta: StoreUtil.getStoreMeta,
@@ -3,11 +3,12 @@ const get = require('lodash/get');
3
3
  const pick = require('lodash/pick');
4
4
  const uniq = require('lodash/uniq');
5
5
  const { isValid, toAddress } = require('@arcblock/did');
6
- const { PASSPORT_STATUS } = require('@abtnode/constant');
6
+ const { PASSPORT_STATUS, USER_MAX_INVITE_DEPTH } = require('@abtnode/constant');
7
7
  const { BaseState } = require('@abtnode/models');
8
8
  const { Sequelize, Op } = require('sequelize');
9
9
  const { updateConnectedAccount } = require('@abtnode/util/lib/user');
10
10
  const { LOGIN_PROVIDER } = require('@blocklet/constant');
11
+ const logger = require('@abtnode/logger')('@abtnode/core:states:user');
11
12
 
12
13
  const { validateOwner } = require('../util');
13
14
  const { loginSchema, disconnectAccountSchema } = require('../validators/user');
@@ -72,6 +73,7 @@ class User extends ExtendBase {
72
73
  // create user
73
74
  await this.insert({
74
75
  ...user,
76
+ ...(await this._prepareInviteInfo(user)),
75
77
  sourceProvider: user.sourceProvider || LOGIN_PROVIDER.WALLET,
76
78
  approved: !!user.approved,
77
79
  });
@@ -188,7 +190,18 @@ class User extends ExtendBase {
188
190
  // eslint-disable-next-line require-await
189
191
  async getUsers({ query, sort, paging } = {}) {
190
192
  const where = {};
191
- const { approved, role, search, tags, includeTags, includePassports, includeUserSessions } = query || {};
193
+ const {
194
+ approved,
195
+ role,
196
+ search,
197
+ tags,
198
+ invitee,
199
+ inviter,
200
+ generation, // 0 - unlimited, 1 - invited by inviter, 2 - invited by another
201
+ includeTags,
202
+ includePassports,
203
+ includeUserSessions,
204
+ } = query || {};
192
205
  const shouldIncludeTag = (tags && tags.length) || includeTags;
193
206
 
194
207
  if (isNullOrUndefined(approved) === false) {
@@ -206,17 +219,81 @@ class User extends ExtendBase {
206
219
  }
207
220
  }
208
221
 
222
+ if (inviter && invitee) {
223
+ throw new Error('You can not query by inviter and invitee at the same time');
224
+ }
225
+
226
+ // handle descendant query
227
+ if (inviter) {
228
+ if (isValid(inviter) === false) {
229
+ throw new Error('inviter did invalid');
230
+ }
231
+ const exist = await this.model.findByPk(toAddress(inviter), { attributes: ['did', 'generation'] });
232
+ if (!exist) {
233
+ throw new Error(`inviter not found: ${inviter}`);
234
+ }
235
+
236
+ try {
237
+ const subQuery = `
238
+ WITH RECURSIVE UserTree(did,inviter,generation) AS (
239
+ SELECT did,inviter,generation FROM users WHERE inviter="${exist.did}"
240
+ UNION ALL
241
+ SELECT child.did,child.inviter,child.generation FROM users AS child INNER JOIN UserTree AS parent ON (child.inviter=parent.did)
242
+ )
243
+ SELECT did,inviter,generation FROM UserTree ${generation > 0 ? `WHERE generation=${(exist.generation > 0 ? exist.generation : 0) + generation}` : ''} LIMIT ${USER_MAX_INVITE_DEPTH}`.trim();
244
+ const children = await this.query(subQuery);
245
+ where.did = children.map((x) => x.did);
246
+ } catch (err) {
247
+ console.error('Failed to get descendants', err);
248
+ where.did = [];
249
+ }
250
+ }
251
+
252
+ // handle ancestor query
253
+ if (invitee) {
254
+ if (isValid(invitee) === false) {
255
+ throw new Error('invitee did invalid');
256
+ }
257
+ const exist = await this.model.findByPk(toAddress(invitee), { attributes: ['did', 'generation'] });
258
+ if (!exist) {
259
+ throw new Error(`invitee not found: ${invitee}`);
260
+ }
261
+
262
+ try {
263
+ const subQuery = `
264
+ WITH RECURSIVE UserTree(did,inviter,generation) AS (
265
+ SELECT did,inviter,generation FROM users WHERE did="${exist.did}"
266
+ UNION ALL
267
+ SELECT
268
+ inviter,
269
+ (SELECT inviter FROM users AS parent WHERE parent.did=child.inviter),
270
+ (SELECT generation FROM users AS parent WHERE parent.did=child.inviter)
271
+ FROM UserTree AS child
272
+ WHERE inviter IS NOT NULL
273
+ LIMIT ${USER_MAX_INVITE_DEPTH}
274
+ )
275
+ SELECT did,inviter,generation FROM UserTree`.trim();
276
+ const children = await this.query(subQuery);
277
+ where.did = children.map((x) => x.did).filter((x) => x !== exist.did);
278
+ } catch (err) {
279
+ console.error('Failed to get ancestors', err);
280
+ where.did = [];
281
+ }
282
+ }
283
+
284
+ const replacements = {};
285
+
209
286
  if (role && role !== '$all' && !where.did) {
287
+ replacements.status = PASSPORT_STATUS.VALID;
210
288
  if (role === '$none') {
211
289
  where.did = {
212
- [Op.notIn]: Sequelize.literal(
213
- `(SELECT DISTINCT userDid FROM passports WHERE status = '${PASSPORT_STATUS.VALID}')`
214
- ),
290
+ [Op.notIn]: Sequelize.literal('(SELECT DISTINCT userDid FROM passports WHERE status = :status)'),
215
291
  };
216
292
  } else {
293
+ replacements.role = role;
217
294
  where.did = {
218
295
  [Op.in]: Sequelize.literal(
219
- `(SELECT DISTINCT userDid FROM passports WHERE name = '${role}' AND status = '${PASSPORT_STATUS.VALID}')`
296
+ '(SELECT DISTINCT userDid FROM passports WHERE name = :role AND status = :status)'
220
297
  ),
221
298
  };
222
299
  }
@@ -246,7 +323,8 @@ class User extends ExtendBase {
246
323
  required: false,
247
324
  });
248
325
  }
249
- const result = await this.paginate({ where, include }, sorting, paging);
326
+
327
+ const result = await this.paginate({ where, include, replacements }, sorting, paging);
250
328
  return result;
251
329
  }
252
330
 
@@ -287,11 +365,10 @@ class User extends ExtendBase {
287
365
  return this.count({
288
366
  where: {
289
367
  did: {
290
- [Op.notIn]: Sequelize.literal(
291
- `(SELECT DISTINCT userDid FROM passports WHERE status = '${PASSPORT_STATUS.VALID}')`
292
- ),
368
+ [Op.notIn]: Sequelize.literal('(SELECT DISTINCT userDid FROM passports WHERE status = :status)'),
293
369
  },
294
370
  },
371
+ replacements: { status: PASSPORT_STATUS.VALID },
295
372
  });
296
373
  }
297
374
 
@@ -305,10 +382,11 @@ class User extends ExtendBase {
305
382
  where: {
306
383
  did: {
307
384
  [Op.in]: Sequelize.literal(
308
- `(SELECT DISTINCT userDid FROM passports WHERE name = '${name}' AND status = '${status}')`
385
+ '(SELECT DISTINCT userDid FROM passports WHERE name = :name AND status = :status)'
309
386
  ),
310
387
  },
311
388
  },
389
+ replacements: { name, status },
312
390
  });
313
391
  }
314
392
 
@@ -368,10 +446,10 @@ class User extends ExtendBase {
368
446
  * @param {string} user.did
369
447
  * @param {string} user.pk
370
448
  * @param {ConnectedAccount} user.connectedAccount
371
- * @param {passport} user.passport
372
449
  * @param {string} user.fullName - user profile's name
373
450
  * @param {string} user.avatar - url of user's avatar, eg: bn://avatar/7f8848569405f8cdf8b1b2788ebf7d0f.jpg
374
451
  * @param {string} user.locale - locale
452
+ * @param {string} user.inviter - inviter
375
453
  * @param {Object} [user.extra] - extra data of user
376
454
  * @param {string} user.lastLoginIp - lastLoginIp
377
455
  * @param {('owner'|'admin'|'member'|'guest'|string)} user.role - deprecated user's role
@@ -399,6 +477,8 @@ class User extends ExtendBase {
399
477
  'extra',
400
478
  'lastLoginIp',
401
479
  'remark',
480
+ 'inviter',
481
+ 'generation',
402
482
  'sourceAppPid',
403
483
  ]),
404
484
  lastLoginAt: now,
@@ -410,11 +490,17 @@ class User extends ExtendBase {
410
490
  updates.sourceAppPid = null;
411
491
  }
412
492
 
493
+ Object.assign(updates, await this._prepareInviteInfo(raw));
494
+
413
495
  if (exist) {
414
- // HACK: sourceAppPid 不能更新
496
+ // immutable
415
497
  if (updates.sourceAppPid) {
416
498
  delete updates.sourceAppPid;
417
499
  }
500
+ if (updates.inviter) {
501
+ delete updates.inviter;
502
+ delete updates.generation;
503
+ }
418
504
  // 登录不再更新 locale
419
505
  delete updates.locale;
420
506
  // update user, connectedAccount, passport
@@ -434,6 +520,42 @@ class User extends ExtendBase {
434
520
  return { ...updated, _action: exist ? 'update' : 'add' };
435
521
  }
436
522
 
523
+ async _prepareInviteInfo(raw) {
524
+ const info = {};
525
+
526
+ // set inviter and generation
527
+ if (raw.inviter) {
528
+ // sybil-attack
529
+ if (isValid(raw.inviter)) {
530
+ const inviterId = toAddress(raw.inviter);
531
+ const inviter = await this.model.findByPk(inviterId, { attributes: ['did', 'generation'] });
532
+ if (inviter) {
533
+ info.inviter = inviterId;
534
+ info.generation = inviter.generation + 1;
535
+ } else {
536
+ logger.warn('Set inviter to non-exist user is not allowed', raw);
537
+ }
538
+ } else {
539
+ logger.warn('Set inviter to invalid did is not allowed', raw);
540
+ info.inviter = null;
541
+ }
542
+
543
+ // anti-land-attack
544
+ if (info.inviter === raw.did) {
545
+ logger.warn('Set inviter to self is not allowed', raw);
546
+ info.inviter = null;
547
+ }
548
+ }
549
+ if (!info.inviter) {
550
+ info.generation = 0;
551
+ }
552
+ if (info.generation > USER_MAX_INVITE_DEPTH) {
553
+ throw new Error('You have exceeded max user invite chain length');
554
+ }
555
+
556
+ return info;
557
+ }
558
+
437
559
  async disconnectUserAccount(raw) {
438
560
  const { error, value: connectedAccount } = disconnectAccountSchema.validate(raw);
439
561
  if (error) {
package/lib/util/index.js CHANGED
@@ -354,7 +354,7 @@ const getStateCrons = (states) => [
354
354
  options: { runOnInit: false },
355
355
  fn: async () => {
356
356
  const removed = await states.auditLog.remove({
357
- createdAt: { $lt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 90) },
357
+ createdAt: { $lt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 365) },
358
358
  });
359
359
  logger.info(`Removed ${removed} audit logs`);
360
360
  },
@@ -311,7 +311,7 @@ const setupAppOwner = async ({ node, sessionId, justCreate = false, context }) =
311
311
  logger.info('Create owner for blocklet', { appDid, ownerDid, sessionId });
312
312
 
313
313
  // create owner login token that can be used to login on blocklet site
314
- const appSecret = Hasher.SHA3.hash256(Buffer.concat([wallet.secretKey, wallet.address].map(Buffer.from)));
314
+ const appSecret = Hasher.SHA3.hash256(Buffer.concat([wallet.secretKey, wallet.address].map((v) => Buffer.from(v))));
315
315
  const setupToken = createAuthToken({
316
316
  did: ownerDid,
317
317
  passport,
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.16.30",
6
+ "version": "1.16.31-beta-4246ab25",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -19,43 +19,43 @@
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.30",
23
- "@abtnode/auth": "1.16.30",
24
- "@abtnode/certificate-manager": "1.16.30",
25
- "@abtnode/constant": "1.16.30",
26
- "@abtnode/cron": "1.16.30",
27
- "@abtnode/logger": "1.16.30",
28
- "@abtnode/models": "1.16.30",
29
- "@abtnode/queue": "1.16.30",
30
- "@abtnode/rbac": "1.16.30",
31
- "@abtnode/router-provider": "1.16.30",
32
- "@abtnode/static-server": "1.16.30",
33
- "@abtnode/timemachine": "1.16.30",
34
- "@abtnode/util": "1.16.30",
35
- "@arcblock/did": "1.18.132",
36
- "@arcblock/did-auth": "1.18.132",
37
- "@arcblock/did-ext": "^1.18.132",
22
+ "@abtnode/analytics": "1.16.31-beta-4246ab25",
23
+ "@abtnode/auth": "1.16.31-beta-4246ab25",
24
+ "@abtnode/certificate-manager": "1.16.31-beta-4246ab25",
25
+ "@abtnode/constant": "1.16.31-beta-4246ab25",
26
+ "@abtnode/cron": "1.16.31-beta-4246ab25",
27
+ "@abtnode/logger": "1.16.31-beta-4246ab25",
28
+ "@abtnode/models": "1.16.31-beta-4246ab25",
29
+ "@abtnode/queue": "1.16.31-beta-4246ab25",
30
+ "@abtnode/rbac": "1.16.31-beta-4246ab25",
31
+ "@abtnode/router-provider": "1.16.31-beta-4246ab25",
32
+ "@abtnode/static-server": "1.16.31-beta-4246ab25",
33
+ "@abtnode/timemachine": "1.16.31-beta-4246ab25",
34
+ "@abtnode/util": "1.16.31-beta-4246ab25",
35
+ "@arcblock/did": "1.18.135",
36
+ "@arcblock/did-auth": "1.18.135",
37
+ "@arcblock/did-ext": "^1.18.135",
38
38
  "@arcblock/did-motif": "^1.1.13",
39
- "@arcblock/did-util": "1.18.132",
40
- "@arcblock/event-hub": "1.18.132",
41
- "@arcblock/jwt": "^1.18.132",
39
+ "@arcblock/did-util": "1.18.135",
40
+ "@arcblock/event-hub": "1.18.135",
41
+ "@arcblock/jwt": "^1.18.135",
42
42
  "@arcblock/pm2-events": "^0.0.5",
43
- "@arcblock/validator": "^1.18.132",
44
- "@arcblock/vc": "1.18.132",
45
- "@blocklet/constant": "1.16.30",
46
- "@blocklet/env": "1.16.30",
47
- "@blocklet/meta": "1.16.30",
48
- "@blocklet/resolver": "1.16.30",
49
- "@blocklet/sdk": "1.16.30",
50
- "@blocklet/store": "1.16.30",
51
- "@did-space/client": "^0.5.28",
43
+ "@arcblock/validator": "^1.18.135",
44
+ "@arcblock/vc": "1.18.135",
45
+ "@blocklet/constant": "1.16.31-beta-4246ab25",
46
+ "@blocklet/env": "1.16.31-beta-4246ab25",
47
+ "@blocklet/meta": "1.16.31-beta-4246ab25",
48
+ "@blocklet/resolver": "1.16.31-beta-4246ab25",
49
+ "@blocklet/sdk": "1.16.31-beta-4246ab25",
50
+ "@blocklet/store": "1.16.31-beta-4246ab25",
51
+ "@did-space/client": "^0.5.31",
52
52
  "@fidm/x509": "^1.2.1",
53
- "@ocap/mcrypto": "1.18.132",
54
- "@ocap/util": "1.18.132",
55
- "@ocap/wallet": "1.18.132",
53
+ "@ocap/mcrypto": "1.18.135",
54
+ "@ocap/util": "1.18.135",
55
+ "@ocap/wallet": "1.18.135",
56
56
  "@slack/webhook": "^5.0.4",
57
57
  "archiver": "^7.0.1",
58
- "axios": "^1.7.2",
58
+ "axios": "^1.7.5",
59
59
  "axon": "^2.0.3",
60
60
  "chalk": "^4.1.2",
61
61
  "cross-spawn": "^7.0.3",
@@ -81,7 +81,7 @@
81
81
  "p-limit": "^3.1.0",
82
82
  "p-retry": "4.6.1",
83
83
  "read-last-lines": "^1.8.0",
84
- "semver": "^7.3.8",
84
+ "semver": "^7.6.3",
85
85
  "sequelize": "^6.35.0",
86
86
  "shelljs": "^0.8.5",
87
87
  "ssri": "^8.0.1",
@@ -103,5 +103,5 @@
103
103
  "jest": "^29.7.0",
104
104
  "unzipper": "^0.10.11"
105
105
  },
106
- "gitHead": "18420639259c736016b19152229183d9fc1c1656"
106
+ "gitHead": "ef93705b89033e4cd5fed458b64521d0a994a1d0"
107
107
  }