@abtnode/core 1.8.15 → 1.8.16

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
@@ -1,13 +1,35 @@
1
1
  const { EventEmitter } = require('events');
2
2
  const pick = require('lodash/pick');
3
+ const joinUrl = require('url-join');
4
+
3
5
  const logger = require('@abtnode/logger')('@abtnode/core:api:team');
4
- const { ROLES, genPermissionName, EVENTS, WHO_CAN_ACCESS, PASSPORT_STATUS } = require('@abtnode/constant');
6
+ const {
7
+ ROLES,
8
+ genPermissionName,
9
+ EVENTS,
10
+ WHO_CAN_ACCESS,
11
+ PASSPORT_STATUS,
12
+ WELLKNOWN_SERVICE_PATH_PREFIX,
13
+ } = require('@abtnode/constant');
5
14
  const { isValid: isValidDid } = require('@arcblock/did');
6
15
  const { BlockletEvents } = require('@blocklet/meta/lib/constants');
16
+ const { sendToUser } = require('@blocklet/sdk/lib/util/send-notification');
17
+ const {
18
+ createPassportVC,
19
+ createPassport,
20
+ upsertToPassports,
21
+ createUserPassport,
22
+ } = require('@abtnode/auth/lib/passport');
23
+ const { getPassportStatusEndpoint } = require('@abtnode/auth/lib/auth');
24
+ const getBlockletInfo = require('@blocklet/meta/lib/info');
25
+ const { parseUserAvatar } = require('@abtnode/util/lib/user-avatar');
26
+
7
27
  const { validateTrustedPassportIssuers } = require('../validators/trusted-passport');
8
28
  const { validateCreateRole, validateUpdateRole } = require('../validators/role');
9
29
  const { validateCreatePermission, validateUpdatePermission } = require('../validators/permission');
10
30
 
31
+ const { getBlocklet } = require('../util/blocklet');
32
+
11
33
  const MAX_USER_PAGE_SIZE = 100;
12
34
 
13
35
  const validateReservedRole = (role) => {
@@ -17,19 +39,53 @@ const validateReservedRole = (role) => {
17
39
  return true;
18
40
  };
19
41
 
42
+ const sendPassportVcNotification = ({ userDid, appWallet: wallet, locale, vc }) => {
43
+ try {
44
+ const receiver = userDid;
45
+ const sender = { appDid: wallet.address, appSk: wallet.secretKey };
46
+ const message = {
47
+ title: {
48
+ en: 'Receive a Passport',
49
+ zh: '获得通行证',
50
+ }[locale],
51
+ body: {
52
+ en: 'You got a passport',
53
+ zh: '你获得了一张通行证',
54
+ }[locale],
55
+ attachments: [
56
+ {
57
+ type: 'vc',
58
+ data: {
59
+ credential: vc,
60
+ tag: vc.credentialSubject.passport.name,
61
+ },
62
+ },
63
+ ],
64
+ };
65
+
66
+ sendToUser(receiver, message, sender, process.env.ABT_NODE_SERVICE_PORT).catch((error) => {
67
+ logger.error('Failed send passport vc to wallet', { error });
68
+ });
69
+ } catch (error) {
70
+ logger.error('Failed send passport vc to wallet', { error });
71
+ }
72
+ };
73
+
20
74
  class TeamAPI extends EventEmitter {
21
75
  /**
22
76
  *
23
77
  * @param {object} states abtnode core StateDB
24
78
  */
25
- constructor({ teamManager, states }) {
79
+ constructor({ teamManager, states, dataDirs }) {
26
80
  super();
27
81
 
28
82
  this.notification = states.notification;
29
83
  this.node = states.node;
84
+ this.states = states;
30
85
  this.memberInviteExpireTime = 1000 * 3600 * 24 * 30; // 30 days
31
86
  this.serverInviteExpireTime = 1000 * 3600; // 1 hour
32
87
  this.teamManager = teamManager;
88
+ this.dataDirs = dataDirs;
33
89
  }
34
90
 
35
91
  // User && Invitation
@@ -223,6 +279,72 @@ class TeamAPI extends EventEmitter {
223
279
  return doc;
224
280
  }
225
281
 
282
+ async issuePassportToUser({ teamDid, userDid, role: roleName, notify = true }, context = {}) {
283
+ const { locale = 'en' } = context;
284
+
285
+ const userState = await this.getUserState(teamDid);
286
+ const user = await userState.getUser(userDid);
287
+
288
+ if (!user) {
289
+ throw new Error(`user does not exist: ${userDid}`);
290
+ }
291
+
292
+ if (!user.approved) {
293
+ throw new Error(`the user is revoked: ${userDid}`);
294
+ }
295
+
296
+ const rbac = await this.getRBAC(teamDid);
297
+ const role = await rbac.getRole(roleName);
298
+
299
+ if (!role) {
300
+ throw new Error(`passport does not exist: ${roleName}`);
301
+ }
302
+
303
+ // create vc
304
+ const blocklet = await getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs });
305
+ const nodeInfo = await this.node.read();
306
+ const blockletInfo = getBlockletInfo(blocklet, nodeInfo.sk);
307
+ const { wallet, passportColor, appUrl, name: issuerName } = blockletInfo;
308
+
309
+ const vc = createPassportVC({
310
+ issuerName,
311
+ issuerWallet: wallet,
312
+ ownerDid: userDid,
313
+ passport: await createPassport({
314
+ role,
315
+ }),
316
+ endpoint: getPassportStatusEndpoint({
317
+ baseUrl: joinUrl(appUrl, WELLKNOWN_SERVICE_PATH_PREFIX),
318
+ userDid,
319
+ teamDid,
320
+ }),
321
+ ownerProfile: {
322
+ ...user,
323
+ avatar: await parseUserAvatar(user.avatar, { dataDir: blocklet.env.dataDir }),
324
+ },
325
+ preferredColor: passportColor,
326
+ });
327
+
328
+ // write passport to db
329
+ const passport = createUserPassport(vc, { role: role.name });
330
+ user.passports = upsertToPassports(user.passports || [], passport);
331
+ await this.updateUser({
332
+ teamDid,
333
+ user: {
334
+ did: user.did,
335
+ pk: user.pk,
336
+ passports: user.passports,
337
+ },
338
+ });
339
+
340
+ // send vc to wallet
341
+ if (notify) {
342
+ sendPassportVcNotification({ userDid, appWallet: wallet, locale, vc });
343
+ }
344
+
345
+ return user;
346
+ }
347
+
226
348
  async revokeUserPassport({ teamDid, userDid, passportId } = {}) {
227
349
  if (!passportId) {
228
350
  throw new Error('Revoked passport should not be empty');
@@ -36,7 +36,6 @@ const {
36
36
  forEachBlocklet,
37
37
  forEachChild,
38
38
  getComponentId,
39
- getComponentName,
40
39
  getComponentBundleId,
41
40
  } = require('@blocklet/meta/lib/util');
42
41
  const getComponentProcessId = require('@blocklet/meta/lib/get-component-process-id');
@@ -73,9 +72,7 @@ const {
73
72
  getFromCache: getAccessibleExternalNodeIp,
74
73
  } = require('../../util/get-accessible-external-node-ip');
75
74
  const {
76
- getComponentDirs,
77
75
  getBlockletMetaFromUrl,
78
- fillBlockletConfigs,
79
76
  ensureBlockletExpanded,
80
77
  getAppSystemEnvironments,
81
78
  getComponentSystemEnvironments,
@@ -104,6 +101,7 @@ const {
104
101
  needBlockletDownload,
105
102
  findAvailableDid,
106
103
  ensureMeta,
104
+ getBlocklet,
107
105
  } = require('../../util/blocklet');
108
106
  const { parseSourceUrl } = require('../../util/registry');
109
107
  const states = require('../../states');
@@ -1294,49 +1292,8 @@ class BlockletManager extends BaseBlockletManager {
1294
1292
  return rootBlocklet;
1295
1293
  }
1296
1294
 
1297
- async ensureBlocklet(did, { e2eMode = false, validateEnv = true, throwOnNotExist = true } = {}) {
1298
- if (!isValidDid(did)) {
1299
- throw new Error(`Blocklet did is invalid: ${did}`);
1300
- }
1301
-
1302
- const blocklet = await states.blocklet.getBlocklet(did);
1303
- if (!blocklet) {
1304
- if (throwOnNotExist) {
1305
- throw new Error(`Can not find blocklet in database by did ${did}`);
1306
- }
1307
- return null;
1308
- }
1309
-
1310
- // app settings
1311
- const settings = await states.blockletExtras.getSettings(blocklet.meta.did);
1312
- blocklet.trustedPassports = get(settings, 'trustedPassports') || [];
1313
- blocklet.enablePassportIssuance = get(settings, 'enablePassportIssuance', true);
1314
- blocklet.settings = settings || {};
1315
-
1316
- // app site
1317
- blocklet.site = await this.getSiteByDid(blocklet.meta.did);
1318
-
1319
- await forEachBlocklet(blocklet, async (component, { id, level, ancestors }) => {
1320
- // component env
1321
- component.env = {
1322
- id,
1323
- name: getComponentName(component, ancestors),
1324
- processId: getComponentProcessId(component, ancestors),
1325
- ...getComponentDirs(component, {
1326
- dataDirs: this.dataDirs,
1327
- ensure: true,
1328
- validate: validateEnv,
1329
- ancestors,
1330
- e2eMode: level === 0 ? e2eMode : false,
1331
- }),
1332
- };
1333
-
1334
- // component config
1335
- const configs = await states.blockletExtras.getConfigs([...ancestors.map((x) => x.meta.did), component.meta.did]);
1336
- fillBlockletConfigs(component, configs);
1337
- });
1338
-
1339
- return blocklet;
1295
+ async ensureBlocklet(did, opts = {}) {
1296
+ return getBlocklet({ ...opts, states, dataDirs: this.dataDirs, did });
1340
1297
  }
1341
1298
 
1342
1299
  async hasBlocklet({ did }) {
package/lib/index.js CHANGED
@@ -163,7 +163,7 @@ function ABTNode(options) {
163
163
  } = getRouterHelpers({ dataDirs, routingSnapshot, routerManager, blockletManager, certManager });
164
164
 
165
165
  const nodeAPI = new NodeAPI(states.node);
166
- const teamAPI = new TeamAPI({ states, teamManager });
166
+ const teamAPI = new TeamAPI({ states, teamManager, dataDirs });
167
167
 
168
168
  blockletManager.getRoutingRulesByDid = getRoutingRulesByDid;
169
169
  blockletManager.getSiteByDid = getSiteByDid;
@@ -298,6 +298,8 @@ function ABTNode(options) {
298
298
  // Passport
299
299
  revokeUserPassport: teamAPI.revokeUserPassport.bind(teamAPI),
300
300
  enableUserPassport: teamAPI.enableUserPassport.bind(teamAPI),
301
+ issuePassportToUser: teamAPI.issuePassportToUser.bind(teamAPI),
302
+
301
303
  createPassportIssuance: teamAPI.createPassportIssuance.bind(teamAPI),
302
304
  getPassportIssuances: teamAPI.getPassportIssuances.bind(teamAPI),
303
305
  getPassportIssuance: teamAPI.getPassportIssuance.bind(teamAPI),
@@ -3,7 +3,7 @@ const pick = require('lodash/pick');
3
3
  const get = require('lodash/get');
4
4
  const joinUrl = require('url-join');
5
5
  const { getDisplayName } = require('@blocklet/meta/lib/util');
6
- const { BLOCKLET_SITE_GROUP_SUFFIX } = require('@abtnode/constant');
6
+ const { BLOCKLET_SITE_GROUP_SUFFIX, NODE_SERVICES } = require('@abtnode/constant');
7
7
  const logger = require('@abtnode/logger')('@abtnode/core:states:audit-log');
8
8
 
9
9
  const BaseState = require('./base');
@@ -177,6 +177,8 @@ const getLogContent = async (action, args, context, result, info, node) => {
177
177
  return `updated trusted passport issuers to following for ${team}: \n${args.trustedPassports.map(x => `- ${x.remark}: ${x.issuerDid}`).join('\n')}`; // prettier-ignore
178
178
  case 'delegateTransferNFT':
179
179
  return `${args.owner} ${args.reason}`;
180
+ case 'issuePassportToUser':
181
+ return `issued **${args.role}** passport to ${user}`;
180
182
 
181
183
  // accessKeys
182
184
  case 'createAccessKey':
@@ -344,6 +346,12 @@ const getScope = (args = {}) => {
344
346
  return null;
345
347
  };
346
348
 
349
+ const fixActor = (actor) => {
350
+ if ([NODE_SERVICES.AUTH_SERVICE, 'blocklet'].includes(actor?.role)) {
351
+ actor.role = '';
352
+ }
353
+ };
354
+
347
355
  class AuditLogState extends BaseState {
348
356
  constructor(baseDir, options = {}) {
349
357
  super(baseDir, { filename: 'audit-log.db', ...options });
@@ -399,6 +407,8 @@ class AuditLogState extends BaseState {
399
407
  const { ip, ua, user } = context;
400
408
  const [info, uaInfo] = await Promise.all([node.states.node.read(), parse(ua)]);
401
409
 
410
+ fixActor(user);
411
+
402
412
  const data = await this.asyncDB.insert({
403
413
  scope: getScope(args) || info.did, // server or blocklet did
404
414
  action,
@@ -41,6 +41,7 @@ const formatBlocklet = (blocklet, phase, dek) => {
41
41
 
42
42
  if (phase === 'onRead') {
43
43
  b.children = b.children || [];
44
+ b.environments = b.environments || [];
44
45
  }
45
46
 
46
47
  if (!b.environments || !b.meta || !dek) {
@@ -11,6 +11,7 @@ const ssri = require('ssri');
11
11
  const diff = require('deep-diff');
12
12
 
13
13
  const { toHex } = require('@ocap/util');
14
+ const { isValid: isValidDid } = require('@arcblock/did');
14
15
  const logger = require('@abtnode/logger')('@abtnode/core:util:blocklet');
15
16
  const pm2 = require('@abtnode/util/lib/async-pm2');
16
17
  const sleep = require('@abtnode/util/lib/sleep');
@@ -58,11 +59,13 @@ const {
58
59
  getBlockletMetaFromUrls,
59
60
  getBlockletMetaFromUrl,
60
61
  } = require('@blocklet/meta/lib/util-meta');
62
+ const getComponentProcessId = require('@blocklet/meta/lib/get-component-process-id');
61
63
 
62
64
  const { validate: validateEngine, get: getEngine } = require('../blocklet/manager/engine');
63
65
 
64
66
  const isRequirementsSatisfied = require('./requirement');
65
67
  const { getDidDomainForBlocklet } = require('./get-domain-for-blocklet');
68
+ const { getBlockletDomainGroupName } = require('./router');
66
69
  const { isBeforeInstalled, expandBundle, findInterfacePortByName, validateBlockletMeta } = require('./index');
67
70
 
68
71
  /**
@@ -208,7 +211,8 @@ const fillBlockletConfigs = (blocklet, configs) => {
208
211
  acc[x.key] = x.value;
209
212
  return acc;
210
213
  }, {});
211
- blocklet.environmentObj = (blocklet.environments || []).reduce((acc, x) => {
214
+ blocklet.environments = blocklet.environments || [];
215
+ blocklet.environmentObj = blocklet.environments.reduce((acc, x) => {
212
216
  acc[x.key] = x.value;
213
217
  return acc;
214
218
  }, {});
@@ -1223,6 +1227,72 @@ const ensureMeta = (meta, { name, did } = {}) => {
1223
1227
  return newMeta;
1224
1228
  };
1225
1229
 
1230
+ const getBlocklet = async ({
1231
+ did,
1232
+ dataDirs,
1233
+ states,
1234
+ e2eMode = false,
1235
+ validateEnv = true,
1236
+ throwOnNotExist = true,
1237
+ ensureDirs = true,
1238
+ } = {}) => {
1239
+ if (!did) {
1240
+ throw new Error('Blocklet did does not exist');
1241
+ }
1242
+ if (!isValidDid(did)) {
1243
+ throw new Error(`Blocklet did is invalid: ${did}`);
1244
+ }
1245
+
1246
+ if (!dataDirs) {
1247
+ throw new Error('dataDirs does not exist');
1248
+ }
1249
+
1250
+ if (!states) {
1251
+ throw new Error('states does not exist');
1252
+ }
1253
+
1254
+ const blocklet = await states.blocklet.getBlocklet(did);
1255
+ if (!blocklet) {
1256
+ if (throwOnNotExist) {
1257
+ throw new Error(`can not find blocklet in database by did ${did}`);
1258
+ }
1259
+ return null;
1260
+ }
1261
+
1262
+ // app settings
1263
+ const settings = await states.blockletExtras.getSettings(blocklet.meta.did);
1264
+ blocklet.trustedPassports = get(settings, 'trustedPassports') || [];
1265
+ blocklet.enablePassportIssuance = get(settings, 'enablePassportIssuance', true);
1266
+ blocklet.settings = settings || {};
1267
+
1268
+ // app site
1269
+ const sites = await states.site.getSites();
1270
+ const domain = getBlockletDomainGroupName(blocklet.meta.did);
1271
+ blocklet.site = (sites || []).find((x) => x.domain === domain);
1272
+
1273
+ await forEachBlocklet(blocklet, async (component, { id, level, ancestors }) => {
1274
+ // component env
1275
+ component.env = {
1276
+ id,
1277
+ name: getComponentName(component, ancestors),
1278
+ processId: getComponentProcessId(component, ancestors),
1279
+ ...getComponentDirs(component, {
1280
+ dataDirs,
1281
+ ensure: ensureDirs,
1282
+ validate: validateEnv,
1283
+ ancestors,
1284
+ e2eMode: level === 0 ? e2eMode : false,
1285
+ }),
1286
+ };
1287
+
1288
+ // component config
1289
+ const configs = await states.blockletExtras.getConfigs([...ancestors.map((x) => x.meta.did), component.meta.did]);
1290
+ fillBlockletConfigs(component, configs);
1291
+ });
1292
+
1293
+ return blocklet;
1294
+ };
1295
+
1226
1296
  module.exports = {
1227
1297
  forEachBlocklet,
1228
1298
  getBlockletMetaFromUrl: (url) => getBlockletMetaFromUrl(url, { logger }),
@@ -1261,4 +1331,5 @@ module.exports = {
1261
1331
  needBlockletDownload,
1262
1332
  findAvailableDid,
1263
1333
  ensureMeta,
1334
+ getBlocklet,
1264
1335
  };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.8.15",
6
+ "version": "1.8.16",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -19,17 +19,18 @@
19
19
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
- "@abtnode/certificate-manager": "1.8.15",
23
- "@abtnode/constant": "1.8.15",
24
- "@abtnode/cron": "1.8.15",
25
- "@abtnode/db": "1.8.15",
26
- "@abtnode/logger": "1.8.15",
27
- "@abtnode/queue": "1.8.15",
28
- "@abtnode/rbac": "1.8.15",
29
- "@abtnode/router-provider": "1.8.15",
30
- "@abtnode/static-server": "1.8.15",
31
- "@abtnode/timemachine": "1.8.15",
32
- "@abtnode/util": "1.8.15",
22
+ "@abtnode/auth": "1.8.16",
23
+ "@abtnode/certificate-manager": "1.8.16",
24
+ "@abtnode/constant": "1.8.16",
25
+ "@abtnode/cron": "1.8.16",
26
+ "@abtnode/db": "1.8.16",
27
+ "@abtnode/logger": "1.8.16",
28
+ "@abtnode/queue": "1.8.16",
29
+ "@abtnode/rbac": "1.8.16",
30
+ "@abtnode/router-provider": "1.8.16",
31
+ "@abtnode/static-server": "1.8.16",
32
+ "@abtnode/timemachine": "1.8.16",
33
+ "@abtnode/util": "1.8.16",
33
34
  "@arcblock/did": "1.17.17",
34
35
  "@arcblock/did-motif": "^1.1.10",
35
36
  "@arcblock/did-util": "1.17.17",
@@ -37,8 +38,8 @@
37
38
  "@arcblock/jwt": "^1.17.17",
38
39
  "@arcblock/pm2-events": "^0.0.5",
39
40
  "@arcblock/vc": "1.17.17",
40
- "@blocklet/meta": "1.8.15",
41
- "@blocklet/sdk": "1.8.15",
41
+ "@blocklet/meta": "1.8.16",
42
+ "@blocklet/sdk": "1.8.16",
42
43
  "@fidm/x509": "^1.2.1",
43
44
  "@nedb/core": "^1.3.4",
44
45
  "@nedb/multi": "^1.3.4",
@@ -81,5 +82,5 @@
81
82
  "express": "^4.18.1",
82
83
  "jest": "^27.5.1"
83
84
  },
84
- "gitHead": "57106db1b559b906734df5b8eac7ce87cae72980"
85
+ "gitHead": "2605cf6ebf2a0c3bb5524cb0f1385ad565fd85d6"
85
86
  }