@abtnode/core 1.16.17-beta-3232a7af → 1.16.17-beta-703fb879

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,5 +1,7 @@
1
1
  const { EventEmitter } = require('events');
2
2
  const pick = require('lodash/pick');
3
+ const defaults = require('lodash/defaults');
4
+ const cloneDeep = require('lodash/cloneDeep');
3
5
  const joinUrl = require('url-join');
4
6
 
5
7
  const logger = require('@abtnode/logger')('@abtnode/core:api:team');
@@ -40,6 +42,7 @@ const { validateCreatePermission, validateUpdatePermission } = require('../valid
40
42
  const { getBlocklet } = require('../util/blocklet');
41
43
  const StoreUtil = require('../util/store');
42
44
  const { profileSchema } = require('../validators/user');
45
+ const { callFederated } = require('../util/federated');
43
46
 
44
47
  const sanitizeUrl = (url) => {
45
48
  if (!url) {
@@ -1000,6 +1003,7 @@ class TeamAPI extends EventEmitter {
1000
1003
  async getPassportIssuance({ teamDid, sessionId }) {
1001
1004
  const state = await this.getSessionState(teamDid);
1002
1005
  const doc = await state.read(sessionId);
1006
+ // FIXME: @zhanghan 指定被邀请者的邀请需要查询被邀请者的详细信息
1003
1007
  return doc;
1004
1008
  }
1005
1009
 
@@ -1096,10 +1100,28 @@ class TeamAPI extends EventEmitter {
1096
1100
  return this.teamManager.getRoles(teamDid);
1097
1101
  }
1098
1102
 
1099
- async createRole({ teamDid, name, description, title, childName, permissions = [] }) {
1100
- logger.info('create role', { teamDid, name, description, childName, permissions });
1103
+ async getRole({ teamDid, role: { name } = {} }) {
1104
+ if (!name) {
1105
+ throw new Error('role name is invalid');
1106
+ }
1107
+ const rbac = await this.getRBAC(teamDid);
1108
+ const role = await rbac.getRole(name);
1109
+ return role ? pick(role, ['name', 'grants', 'title', 'description', 'extra']) : null;
1110
+ }
1101
1111
 
1102
- await validateCreateRole({ name, title, description });
1112
+ async createRole({ teamDid, name, description, title, childName, permissions = [], extra: raw }) {
1113
+ logger.info('create role', { teamDid, name, description, childName, permissions, raw });
1114
+ const attrs = { name, title, description, childName, permissions };
1115
+
1116
+ if (raw) {
1117
+ try {
1118
+ attrs.extra = JSON.parse(raw);
1119
+ } catch (err) {
1120
+ throw new Error('extra should be a valid json string');
1121
+ }
1122
+ }
1123
+
1124
+ await validateCreateRole(pick(attrs, ['name', 'title', 'description', 'extra']));
1103
1125
 
1104
1126
  validateReservedRole(name);
1105
1127
 
@@ -1107,8 +1129,8 @@ class TeamAPI extends EventEmitter {
1107
1129
 
1108
1130
  let role;
1109
1131
  try {
1110
- role = await rbac.createRole({ name, title, description, childName, permissions });
1111
- return pick(role, ['name', 'grants', 'title', 'description']);
1132
+ role = await rbac.createRole(attrs);
1133
+ return pick(role, ['name', 'title', 'grants', 'description', 'extra']);
1112
1134
  } catch (err) {
1113
1135
  if (new RegExp(`Item ${name} already exists`).test(err.message)) {
1114
1136
  throw new Error(`Id ${name} already exists`);
@@ -1117,16 +1139,23 @@ class TeamAPI extends EventEmitter {
1117
1139
  }
1118
1140
  }
1119
1141
 
1120
- async updateRole({ teamDid, role: { name, title, description } = {} }) {
1121
- logger.info('update role', { teamDid, name, title, description });
1142
+ async updateRole({ teamDid, role: { name, title, description, extra: raw } = {} }) {
1143
+ logger.info('update role', { teamDid, name, title, description, raw });
1122
1144
 
1123
- await validateUpdateRole({ name, title, description });
1124
-
1125
- const rbac = await this.getRBAC(teamDid);
1145
+ const attrs = { name, title, description };
1126
1146
 
1127
- const state = await rbac.updateRole({ name, title, description });
1147
+ if (raw) {
1148
+ try {
1149
+ attrs.extra = JSON.parse(raw);
1150
+ } catch (err) {
1151
+ throw new Error('extra should be a valid json string');
1152
+ }
1153
+ }
1128
1154
 
1129
- return pick(state, ['name', 'title', 'grants', 'description']);
1155
+ await validateUpdateRole(attrs);
1156
+ const rbac = await this.getRBAC(teamDid);
1157
+ const state = await rbac.updateRole(attrs);
1158
+ return pick(state, ['name', 'title', 'grants', 'description', 'extra']);
1130
1159
  }
1131
1160
 
1132
1161
  async getPermissions({ teamDid }) {
@@ -1315,6 +1344,10 @@ class TeamAPI extends EventEmitter {
1315
1344
  return this.teamManager.getUserState(did);
1316
1345
  }
1317
1346
 
1347
+ getUserSessionState(did) {
1348
+ return this.teamManager.getUserSessionState(did);
1349
+ }
1350
+
1318
1351
  getTagState(did) {
1319
1352
  return this.teamManager.getTagState(did);
1320
1353
  }
@@ -1329,6 +1362,148 @@ class TeamAPI extends EventEmitter {
1329
1362
  setInviteExpireTime(ms) {
1330
1363
  this.memberInviteExpireTime = ms;
1331
1364
  }
1365
+
1366
+ // user-session management
1367
+ async getUserSession({ teamDid, userDid, visitorId }) {
1368
+ const state = await this.getUserSessionState(teamDid);
1369
+ const userState = await this.getUserState(teamDid);
1370
+
1371
+ const where = {
1372
+ status: 'online',
1373
+ };
1374
+ if (userDid) where.userDid = userDid;
1375
+ if (visitorId) where.visitorId = visitorId;
1376
+
1377
+ const userSessions = await state.model.findAll({
1378
+ where,
1379
+ attributes: ['id', 'appPid', 'userDid', 'visitorId', 'passportId'],
1380
+ include: {
1381
+ model: userState.model,
1382
+ as: 'user',
1383
+ attributes: ['did', 'sourceProvider', 'fullName', 'email', 'avatar', 'remark', 'sourceAppPid'],
1384
+ },
1385
+ });
1386
+ return userSessions.map((x) => x.toJSON());
1387
+ }
1388
+
1389
+ async getPassportById({ teamDid, passportId }) {
1390
+ const userState = await this.getUserState(teamDid);
1391
+ const passport = await userState.getPassportById(passportId);
1392
+ return passport;
1393
+ }
1394
+
1395
+ async getPassportFromFederated({ site, passportId, teamDid }) {
1396
+ const blocklet = await getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs });
1397
+ const nodeInfo = await this.node.read();
1398
+ const { permanentWallet } = getBlockletInfo(blocklet, nodeInfo.sk);
1399
+
1400
+ const result = await callFederated({
1401
+ action: 'getPassport',
1402
+ data: {
1403
+ passportId,
1404
+ },
1405
+ permanentWallet,
1406
+ site,
1407
+ });
1408
+
1409
+ return result;
1410
+ }
1411
+
1412
+ async upsertUserSession({ teamDid, appPid, userDid, visitorId, ua, lastLoginIp, sourceAppPid, passportId, status }) {
1413
+ if (!userDid) {
1414
+ throw new Error('userDid are required');
1415
+ }
1416
+
1417
+ const state = await this.getUserSessionState(teamDid);
1418
+ let data;
1419
+ if (!visitorId) {
1420
+ data = await state.insert({
1421
+ userDid,
1422
+ ua,
1423
+ lastLoginIp,
1424
+ appPid,
1425
+ passportId,
1426
+ });
1427
+ } else {
1428
+ const exist = await state.findOne({ userDid, visitorId, appPid });
1429
+ if (exist) {
1430
+ [, [data]] = await state.update(exist.id, { ua, lastLoginIp, passportId, status });
1431
+ } else {
1432
+ data = await state.insert({
1433
+ visitorId,
1434
+ userDid,
1435
+ ua,
1436
+ lastLoginIp,
1437
+ appPid,
1438
+ passportId,
1439
+ });
1440
+ }
1441
+ }
1442
+
1443
+ if (sourceAppPid) {
1444
+ const blocklet = await getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs });
1445
+ const nodeInfo = await this.node.read();
1446
+ const { permanentWallet } = getBlockletInfo(blocklet, nodeInfo.sk);
1447
+ const federated = defaults(cloneDeep(blocklet.settings.federated || {}), {
1448
+ config: {},
1449
+ sites: [],
1450
+ });
1451
+ const masterSite = federated.sites[0];
1452
+ if (masterSite && masterSite.isMaster !== false && masterSite.appPid === sourceAppPid) {
1453
+ await callFederated({
1454
+ action: 'sync',
1455
+ permanentWallet,
1456
+ site: masterSite,
1457
+ data: {
1458
+ userSessions: [
1459
+ {
1460
+ action: 'login',
1461
+ userDid,
1462
+ visitorId: data.visitorId,
1463
+ ua,
1464
+ lastLoginIp,
1465
+ appPid: teamDid,
1466
+ passportId,
1467
+ },
1468
+ ],
1469
+ },
1470
+ });
1471
+ }
1472
+ }
1473
+
1474
+ return data;
1475
+ }
1476
+
1477
+ async logoutUser({ teamDid, userDid, visitorId, appPid }) {
1478
+ const state = await this.getUserSessionState(teamDid);
1479
+ const data = await state.update({ userDid, visitorId, appPid }, { status: 'offline' });
1480
+ const nodeInfo = await this.node.read();
1481
+ const blocklet = await getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs });
1482
+ const { permanentWallet } = getBlockletInfo(blocklet, nodeInfo.sk);
1483
+ const federated = defaults(cloneDeep(blocklet.settings.federated || {}), {
1484
+ config: {},
1485
+ sites: [],
1486
+ });
1487
+ const masterSite = federated.sites[0];
1488
+ if (masterSite && masterSite.isMaster !== false && masterSite.appPid !== teamDid) {
1489
+ callFederated({
1490
+ action: 'sync',
1491
+ permanentWallet,
1492
+ site: masterSite,
1493
+ data: {
1494
+ userSessions: [
1495
+ {
1496
+ action: 'logout',
1497
+ userDid,
1498
+ visitorId,
1499
+ appPid: teamDid,
1500
+ },
1501
+ ],
1502
+ },
1503
+ });
1504
+ }
1505
+ return data;
1506
+ }
1332
1507
  }
1333
1508
 
1334
1509
  module.exports = TeamAPI;
@@ -1,5 +1,4 @@
1
1
  /* eslint-disable max-classes-per-file */
2
- /* eslint-disable no-underscore-dangle */
3
2
  /* eslint-disable no-await-in-loop */
4
3
  const fs = require('fs-extra');
5
4
  const path = require('path');
@@ -163,7 +162,12 @@ const UpgradeComponents = require('./helper/upgrade-components');
163
162
  const BlockletDownloader = require('../downloader/blocklet-downloader');
164
163
  const RollbackCache = require('./helper/rollback-cache');
165
164
  const { migrateApplicationToStructV2 } = require('./helper/migrate-application-to-struct-v2');
166
- const { getBackupFilesUrlFromEndpoint, getBackupEndpoint, getSpaceNameByEndpoint } = require('../../util/spaces');
165
+ const {
166
+ getBackupFilesUrlFromEndpoint,
167
+ getBackupEndpoint,
168
+ getSpaceNameByEndpoint,
169
+ getBackupJobId,
170
+ } = require('../../util/spaces');
167
171
  const { validateAddSpaceGateway, validateUpdateSpaceGateway } = require('../../validators/space-gateway');
168
172
  const { sessionConfigSchema } = require('../../validators/util');
169
173
 
@@ -182,6 +186,7 @@ const {
182
186
  getSelectedResources,
183
187
  updateSelectedResources,
184
188
  } = require('../project');
189
+ const { callFederated } = require('../../util/federated');
185
190
 
186
191
  const { formatEnvironments, getBlockletMeta, validateOwner, isCLI } = util;
187
192
 
@@ -221,12 +226,19 @@ class DiskBlockletManager extends BaseBlockletManager {
221
226
  this.teamManager = teamManager;
222
227
 
223
228
  if (isFunction(this.backupQueue.on)) {
224
- const handleBackupComplete = async ({ id, job }) => {
225
- await this.backupQueue.delete(id);
226
-
227
- const autoBackup = await this.getAutoBackup({ did: id });
229
+ /**
230
+ *
231
+ * @param {{
232
+ * id: string,
233
+ * job: { blocklet: import('@abtnode/client').BlockletState }
234
+ * }} param0
235
+ */
236
+ const handleBackupComplete = async ({ id: jobId, job }) => {
237
+ await this.backupQueue.delete(jobId);
238
+
239
+ const autoBackup = await this.getAutoBackup({ did: job.blocklet.meta.did });
228
240
  if (autoBackup?.enabled) {
229
- this.backupQueue.push(job, id, true, BACKUPS.JOB.INTERVAL);
241
+ this.backupQueue.push(job, jobId, true, BACKUPS.JOB.INTERVAL);
230
242
  }
231
243
  };
232
244
  this.backupQueue.on('finished', handleBackupComplete).on('failed', handleBackupComplete);
@@ -1898,12 +1910,14 @@ class DiskBlockletManager extends BaseBlockletManager {
1898
1910
  const value = { ...autoBackup };
1899
1911
  await states.blockletExtras.setSettings(did, { autoBackup: value });
1900
1912
 
1901
- await this.backupQueue.delete(did);
1913
+ const jobId = getBackupJobId(did);
1914
+ await this.backupQueue.delete(jobId);
1902
1915
 
1903
1916
  logger.info('updateAutoBackup.$value', value);
1904
1917
 
1905
1918
  if (value.enabled) {
1906
1919
  const blocklet = await states.blocklet.getBlocklet(did);
1920
+
1907
1921
  this.backupQueue.push(
1908
1922
  {
1909
1923
  entity: 'blocklet',
@@ -1911,7 +1925,7 @@ class DiskBlockletManager extends BaseBlockletManager {
1911
1925
  blocklet,
1912
1926
  context,
1913
1927
  },
1914
- did,
1928
+ jobId,
1915
1929
  true,
1916
1930
  BACKUPS.JOB.INTERVAL
1917
1931
  );
@@ -3568,7 +3582,8 @@ class DiskBlockletManager extends BaseBlockletManager {
3568
3582
  try {
3569
3583
  const { did } = blocklet.meta;
3570
3584
 
3571
- await this.backupQueue.delete(did);
3585
+ const jobId = getBackupJobId(did);
3586
+ await this.backupQueue.delete(jobId);
3572
3587
 
3573
3588
  this.backupQueue.push(
3574
3589
  {
@@ -3580,7 +3595,7 @@ class DiskBlockletManager extends BaseBlockletManager {
3580
3595
  strategy: BACKUPS.STRATEGY.MANUAL,
3581
3596
  },
3582
3597
  },
3583
- did
3598
+ jobId
3584
3599
  );
3585
3600
 
3586
3601
  return blocklet;
@@ -3744,7 +3759,7 @@ class FederatedBlockletManager extends DiskBlockletManager {
3744
3759
  * @param {string} options.appUrl - 申请加入统一登录的 master appUrl
3745
3760
  * @returns {Promise<any>} 更新后的 blocklet 数据
3746
3761
  */
3747
- async joinFederatedLogin({ appUrl, did }) {
3762
+ async joinFederatedLogin({ appUrl, did }, context) {
3748
3763
  const url = new URL(appUrl);
3749
3764
  // master service api 的地址
3750
3765
  url.pathname = joinUrl(WELLKNOWN_SERVICE_PATH_PREFIX, '/api/federated/join');
@@ -3818,7 +3833,7 @@ class FederatedBlockletManager extends DiskBlockletManager {
3818
3833
  * @param {string} options.did - blocklet pid
3819
3834
  * @returns {Promise<any>} 更新后的 blocklet 数据
3820
3835
  */
3821
- async quitFederatedLogin({ did }) {
3836
+ async quitFederatedLogin({ did }, context) {
3822
3837
  const blocklet = await this.getBlocklet(did);
3823
3838
  const federated = defaults(cloneDeep(blocklet.settings.federated || {}), {
3824
3839
  config: {},
@@ -3839,21 +3854,17 @@ class FederatedBlockletManager extends DiskBlockletManager {
3839
3854
  },
3840
3855
  masterAppUrl: masterSite.appUrl,
3841
3856
  });
3842
- const url = new URL(masterSite.appUrl);
3843
- url.pathname = joinUrl(WELLKNOWN_SERVICE_PATH_PREFIX, '/api/federated/quit');
3844
3857
  try {
3845
- await pRetry(
3846
- () =>
3847
- request.post(url.href, {
3848
- signer: permanentWallet.address,
3849
- data: signV2(permanentWallet.address, permanentWallet.secretKey, {
3850
- memberPid: blocklet.appPid,
3851
- }),
3852
- }),
3853
- { retries: 3 }
3854
- );
3858
+ await callFederated({
3859
+ action: 'quit',
3860
+ site: masterSite,
3861
+ permanentWallet,
3862
+ data: {
3863
+ memberPid: blocklet.appPid,
3864
+ },
3865
+ });
3855
3866
  } catch (error) {
3856
- logger.error('Failed to quit blocklet, will still quit federated by itself', { error, did, url: url.href });
3867
+ logger.error('Failed to quit blocklet, will still quit federated by itself', { error, did });
3857
3868
  }
3858
3869
  }
3859
3870
 
@@ -3872,7 +3883,7 @@ class FederatedBlockletManager extends DiskBlockletManager {
3872
3883
  * @param {string} options.did - blocklet pid
3873
3884
  * @returns {Promise<any>} 更新后的 blocklet 数据
3874
3885
  */
3875
- async disbandFederatedLogin({ did }) {
3886
+ async disbandFederatedLogin({ did }, context) {
3876
3887
  const blocklet = await this.getBlocklet(did);
3877
3888
  const federated = defaults(cloneDeep(blocklet.settings.federated || {}), {
3878
3889
  config: {},
@@ -3899,22 +3910,17 @@ class FederatedBlockletManager extends DiskBlockletManager {
3899
3910
  .filter((item) => item.appPid !== did)
3900
3911
  .map((item) => {
3901
3912
  return limitSync(async () => {
3902
- const url = new URL(item.appUrl);
3903
- url.pathname = joinUrl(WELLKNOWN_SERVICE_PATH_PREFIX, '/api/federated/disband');
3904
3913
  try {
3905
- await pRetry(
3906
- () =>
3907
- request.post(url.href, {
3908
- signer: permanentWallet.address,
3909
- data: signV2(permanentWallet.address, permanentWallet.secretKey, {}),
3910
- }),
3911
- { retries: 1 }
3912
- );
3914
+ await callFederated({
3915
+ action: 'disband',
3916
+ permanentWallet,
3917
+ site: item,
3918
+ data: {},
3919
+ });
3913
3920
  } catch (error) {
3914
3921
  logger.error('Failed to disband blocklet, will still disband federated by itself', {
3915
3922
  error,
3916
3923
  did,
3917
- url: url.href,
3918
3924
  });
3919
3925
  }
3920
3926
  });
@@ -3937,7 +3943,7 @@ class FederatedBlockletManager extends DiskBlockletManager {
3937
3943
  * @param {object} param.config federated 配置内容
3938
3944
  * @returns {Promise<any>} 更新后的 blocklet 数据
3939
3945
  */
3940
- async setFederated({ did, config }) {
3946
+ async setFederated({ did, config }, context) {
3941
3947
  await states.blockletExtras.setSettings(did, { federated: config });
3942
3948
 
3943
3949
  const newState = await this.getBlocklet(did);
@@ -3953,7 +3959,8 @@ class FederatedBlockletManager extends DiskBlockletManager {
3953
3959
  * @param {boolean} param.autoLogin 是否自动登录
3954
3960
  * @returns {Promise<any>} 更新后的 blocklet 数据
3955
3961
  */
3956
- async configFederated({ did, autoLogin = false }) {
3962
+ // FIXME: @zhanghan deprecated 不再需要配置自动登录
3963
+ async configFederated({ did, autoLogin = false }, context) {
3957
3964
  const blocklet = await this.getBlocklet(did);
3958
3965
  const federated = cloneDeep(blocklet.settings.federated || {});
3959
3966
  federated.config.autoLogin = autoLogin;
@@ -3970,7 +3977,6 @@ class FederatedBlockletManager extends DiskBlockletManager {
3970
3977
  * @param {object} param
3971
3978
  * @param {string} param.did master blocklet pid
3972
3979
  * @param {string} param.memberPid member blocklet pid
3973
- * @param {boolean} param.autoLogin 是否自动登录
3974
3980
  * @returns {Promise<any>} 更新后的 blocklet 数据
3975
3981
  */
3976
3982
  async auditFederatedLogin({ memberPid, did, status }) {
@@ -4028,31 +4034,24 @@ class FederatedBlockletManager extends DiskBlockletManager {
4028
4034
  roles = await this.teamManager.getRoles(blocklet.appPid);
4029
4035
  }
4030
4036
 
4031
- const postUrl = joinUrl(memberSite.appUrl, WELLKNOWN_SERVICE_PATH_PREFIX, '/api/federated/audit-res');
4032
-
4033
4037
  logger.info('Audit member join federated login', {
4034
4038
  status,
4035
- postUrl,
4036
4039
  });
4037
4040
  try {
4038
4041
  const roleList = roles.map((item) => pick(item, ['name', 'title', 'description']));
4039
- await pRetry(
4040
- () =>
4041
- request.post(postUrl, {
4042
- signer: permanentWallet.address,
4043
- data: signV2(permanentWallet.address, permanentWallet.secretKey, {
4044
- masterPid: blocklet.appPid,
4045
- status,
4046
- delegation,
4047
- roles: roleList,
4048
- }),
4049
- }),
4050
- {
4051
- retries: 3,
4052
- }
4053
- );
4042
+ await callFederated({
4043
+ action: 'audit-res',
4044
+ permanentWallet,
4045
+ site: memberSite,
4046
+ data: {
4047
+ masterPid: blocklet.appPid,
4048
+ status,
4049
+ delegation,
4050
+ roles: roleList,
4051
+ },
4052
+ });
4054
4053
  } catch (error) {
4055
- logger.error('Failed to post audit res to member-site', { error, did, url: postUrl });
4054
+ logger.error('Failed to post audit res to member-site', { error, did });
4056
4055
  throw error;
4057
4056
  }
4058
4057
  await this.syncFederated({
@@ -4119,24 +4118,34 @@ class FederatedBlockletManager extends DiskBlockletManager {
4119
4118
  .filter((item) => item.appId !== federated.config.appId)
4120
4119
  .map((item) => {
4121
4120
  return limitSync(async () => {
4122
- const url = joinUrl(item.appUrl, WELLKNOWN_SERVICE_PATH_PREFIX, '/api/federated/sync');
4123
4121
  try {
4124
4122
  // NOTICE: 即使通知某个站点失败了,也不影响其他站点接收同步结果
4125
- await pRetry(
4126
- () =>
4127
- request.post(url, {
4128
- signer: permanentWallet.address,
4129
- data: signV2(permanentWallet.address, permanentWallet.secretKey, safeData),
4130
- }),
4131
- { retries: 3 }
4132
- );
4123
+ await callFederated({
4124
+ action: 'sync',
4125
+ permanentWallet,
4126
+ site: item,
4127
+ data: safeData,
4128
+ });
4133
4129
  } catch (error) {
4134
- logger.warn('Failed to sync federated sites', { error, did, url, action: 'sync' });
4130
+ logger.warn('Failed to sync federated sites', { error, did, action: 'sync' });
4135
4131
  }
4136
4132
  });
4137
4133
  });
4138
4134
  await Promise.all(waitingList);
4139
4135
  }
4136
+
4137
+ async loginFederated({ did, site, data }) {
4138
+ const blocklet = await this.getBlocklet(did);
4139
+ const nodeInfo = await states.node.read();
4140
+ const { permanentWallet } = getBlockletInfo(blocklet, nodeInfo.sk);
4141
+ const result = await callFederated({
4142
+ action: 'loginByMaster',
4143
+ data,
4144
+ permanentWallet,
4145
+ site,
4146
+ });
4147
+ return result;
4148
+ }
4140
4149
  }
4141
4150
 
4142
4151
  class BlockletManager extends FederatedBlockletManager {}
@@ -1,5 +1,6 @@
1
1
  const debounce = require('lodash/debounce');
2
2
  const logger = require('@abtnode/logger')('@abtnode/core:event/auto-backup-handler');
3
+ const { getBackupJobId } = require('../util/spaces');
3
4
 
4
5
  /**
5
6
  * @description
@@ -15,7 +16,8 @@ async function autoBackupHandler(eventName, payload, blockletManager) {
15
16
  logger.info('autoBackupHandler.$Boolean(payload.context)', Boolean(payload.context));
16
17
 
17
18
  if (autoBackup.enabled && payload.context) {
18
- await blockletManager.backupQueue.delete(did);
19
+ const jobId = getBackupJobId(did);
20
+ await blockletManager.backupQueue.delete(jobId);
19
21
 
20
22
  blockletManager.backupQueue.push(
21
23
  {
@@ -24,7 +26,7 @@ async function autoBackupHandler(eventName, payload, blockletManager) {
24
26
  blocklet: payload,
25
27
  context: payload.context,
26
28
  },
27
- did
29
+ jobId
28
30
  );
29
31
  }
30
32
  }
package/lib/index.js CHANGED
@@ -269,6 +269,7 @@ function ABTNode(options) {
269
269
  configFederated: blockletManager.configFederated.bind(blockletManager),
270
270
  setFederated: blockletManager.setFederated.bind(blockletManager),
271
271
  syncFederated: blockletManager.syncFederated.bind(blockletManager),
272
+ loginFederated: blockletManager.loginFederated.bind(blockletManager),
272
273
  configNotification: blockletManager.configNotification.bind(blockletManager),
273
274
  updateWhoCanAccess: blockletManager.updateWhoCanAccess.bind(blockletManager),
274
275
  updateAppSessionConfig: blockletManager.updateAppSessionConfig.bind(blockletManager),
@@ -381,6 +382,13 @@ function ABTNode(options) {
381
382
  isConnectedAccount: teamAPI.isConnectedAccount.bind(teamAPI),
382
383
  switchProfile: teamAPI.switchProfile.bind(teamAPI),
383
384
 
385
+ // user-session
386
+ getUserSession: teamAPI.getUserSession.bind(teamAPI),
387
+ upsertUserSession: teamAPI.upsertUserSession.bind(teamAPI),
388
+ logoutUser: teamAPI.logoutUser.bind(teamAPI),
389
+ getPassportById: teamAPI.getPassportById.bind(teamAPI),
390
+ getPassportFromFederated: teamAPI.getPassportFromFederated.bind(teamAPI),
391
+
384
392
  // Tagging
385
393
  createTag: teamAPI.createTag.bind(teamAPI),
386
394
  updateTag: teamAPI.updateTag.bind(teamAPI),
@@ -391,6 +399,7 @@ function ABTNode(options) {
391
399
  getRBAC: (did = options.nodeDid) => teamManager.getRBAC(did),
392
400
 
393
401
  getRoles: teamAPI.getRoles.bind(teamAPI),
402
+ getRole: teamAPI.getRole.bind(teamAPI),
394
403
  createRole: teamAPI.createRole.bind(teamAPI),
395
404
  updateRole: teamAPI.updateRole.bind(teamAPI),
396
405
  deleteRole: teamAPI.deleteRole.bind(teamAPI),
@@ -234,7 +234,7 @@ class BlockletState extends BaseState {
234
234
  } = {}) {
235
235
  let doc = await this.getBlocklet(meta.did);
236
236
  if (doc) {
237
- throw new Error('Blocklet already added');
237
+ throw new Error(`Blocklet already added: ${meta.did}`);
238
238
  }
239
239
 
240
240
  try {
@@ -288,7 +288,7 @@ class BlockletState extends BaseState {
288
288
  async updateBlocklet(did, updates) {
289
289
  const doc = await this.getBlocklet(did);
290
290
  if (!doc) {
291
- throw new Error('Blocklet does not exist');
291
+ throw new Error(`Blocklet does not exist on update: ${did}`);
292
292
  }
293
293
 
294
294
  const formatted = formatBlocklet(cloneDeep(updates), 'onUpdate', this.config.dek);
@@ -302,7 +302,7 @@ class BlockletState extends BaseState {
302
302
  async upgradeBlocklet({ meta, source, deployedFrom = '', children } = {}) {
303
303
  const doc = await this.getBlocklet(meta.did);
304
304
  if (!doc) {
305
- throw new Error('Blocklet does not exist');
305
+ throw new Error(`Blocklet does not exist on upgrade: ${meta.did}`);
306
306
  }
307
307
 
308
308
  try {
@@ -427,7 +427,7 @@ class BlockletState extends BaseState {
427
427
  async refreshBlockletPorts(did, componentDids = []) {
428
428
  const blocklet = await this.getBlocklet(did);
429
429
  if (!blocklet) {
430
- throw new Error('Blocklet does not exist');
430
+ throw new Error(`Blocklet does not exist on refresh: ${did}`);
431
431
  }
432
432
 
433
433
  const { occupiedExternalPorts, occupiedInternalPorts } = await this._getOccupiedPorts();
@@ -626,7 +626,7 @@ class BlockletState extends BaseState {
626
626
  async addChildren(did, children) {
627
627
  const parent = await this.getBlocklet(did);
628
628
  if (!parent) {
629
- throw new Error('Blocklet does not exist');
629
+ throw new Error(`Blocklet does not exist on addChildren: ${did}`);
630
630
  }
631
631
 
632
632
  const oldChildren = parent.children || [];
@@ -0,0 +1,8 @@
1
+ const ExtendBase = require('./base');
2
+
3
+ /**
4
+ * @extends BaseState<import('@abtnode/models').UserSession>
5
+ */
6
+ class UserSession extends ExtendBase {}
7
+
8
+ module.exports = UserSession;
@@ -423,6 +423,16 @@ class User extends ExtendBase {
423
423
 
424
424
  return result;
425
425
  }
426
+
427
+ async getPassports(did) {
428
+ const passports = await this.passport.find({ userDid: did });
429
+ return passports;
430
+ }
431
+
432
+ async getPassportById(passportId) {
433
+ const passport = await this.passport.findOne({ id: passportId });
434
+ return passport;
435
+ }
426
436
  }
427
437
 
428
438
  module.exports = User;
@@ -28,6 +28,7 @@ const rbacCreationLock = new Lock('rbac-creation-lock');
28
28
 
29
29
  const States = {
30
30
  User: require('../states/user'),
31
+ UserSession: require('../states/user-session'),
31
32
  Passport: require('../states/passport'),
32
33
  ConnectedAccount: require('../states/connect-account'),
33
34
  Session: require('../states/session'),
@@ -96,6 +97,7 @@ class TeamManager extends EventEmitter {
96
97
  this.cache[this.nodeDid] = {
97
98
  rbac: await createRBAC({ storage: new MemoryStorage(), data: RBAC_CONFIG }),
98
99
  user: await this.createState(this.nodeDid, 'User'),
100
+ userSession: await this.createState(this.nodeDid, 'UserSession'),
99
101
  tag: await this.createState(this.nodeDid, 'Tag'),
100
102
  passport: await this.createState(this.nodeDid, 'Passport'),
101
103
  connectedAccount: await this.createState(this.nodeDid, 'ConnectedAccount'),
@@ -107,6 +109,10 @@ class TeamManager extends EventEmitter {
107
109
  return this.getState(teamDid, 'user');
108
110
  }
109
111
 
112
+ getUserSessionState(teamDid) {
113
+ return this.getState(teamDid, 'userSession');
114
+ }
115
+
110
116
  getTagState(teamDid) {
111
117
  return this.getState(teamDid, 'tag');
112
118
  }
@@ -320,7 +326,7 @@ class TeamManager extends EventEmitter {
320
326
  async getRoles(did) {
321
327
  const rbac = await this.getRBAC(did);
322
328
  const roles = await rbac.getRoles();
323
- return roles.map((d) => pick(d, ['name', 'grants', 'title', 'description']));
329
+ return roles.map((d) => pick(d, ['name', 'grants', 'title', 'description', 'extra']));
324
330
  }
325
331
 
326
332
  async initTeam(did) {
@@ -339,10 +345,12 @@ class TeamManager extends EventEmitter {
339
345
  const passport = await this.createState(did, 'Passport');
340
346
  const connectedAccount = await this.createState(did, 'ConnectedAccount');
341
347
  const session = await this.createState(did, 'Session');
348
+ const userSession = await this.createState(did, 'UserSession');
342
349
 
343
350
  this.cache[did] = {
344
351
  rbac,
345
352
  user,
353
+ userSession,
346
354
  tag,
347
355
  session,
348
356
  passport,
@@ -12,6 +12,7 @@ const toLower = require('lodash/toLower');
12
12
  const isEmpty = require('lodash/isEmpty');
13
13
  const streamToPromise = require('stream-to-promise');
14
14
  const { Throttle } = require('stream-throttle');
15
+ const { slugify } = require('transliteration');
15
16
  const ssri = require('ssri');
16
17
  const diff = require('deep-diff');
17
18
  const createArchive = require('archiver');
@@ -19,6 +20,7 @@ const isUrl = require('is-url');
19
20
  const semver = require('semver');
20
21
  const { chainInfo: chainInfoSchema } = require('@arcblock/did-auth/lib/schema');
21
22
 
23
+ const urlPathFriendly = require('@blocklet/meta/lib/url-path-friendly').default;
22
24
  const { fromSecretKey, fromPublicKey } = require('@ocap/wallet');
23
25
  const { toHex, isHex, toDid, toAddress, toBuffer } = require('@ocap/util');
24
26
  const { isValid: isValidDid, isEthereumDid } = require('@arcblock/did');
@@ -309,6 +311,7 @@ const getAppSystemEnvironments = (blocklet, nodeInfo, dataDirs) => {
309
311
  BLOCKLET_APP_PSK: appPsk, // permanent sk even the blocklet has been migrated
310
312
  BLOCKLET_APP_PID: appPid, // permanent did even the blocklet has been migrated
311
313
  BLOCKLET_APP_NAME: appName,
314
+ BLOCKLET_APP_NAME_SLUG: urlPathFriendly(slugify(appName)),
312
315
  BLOCKLET_APP_DESCRIPTION: appDescription,
313
316
  BLOCKLET_APP_URL: appUrl,
314
317
  BLOCKLET_APP_DATA_DIR: path.join(dataDirs.data, blocklet.meta.name),
@@ -0,0 +1,24 @@
1
+ const { WELLKNOWN_SERVICE_PATH_PREFIX } = require('@abtnode/constant');
2
+ const pRetry = require('p-retry');
3
+ const { signV2 } = require('@arcblock/jwt');
4
+ const joinUrl = require('url-join');
5
+
6
+ const request = require('./request');
7
+
8
+ async function callFederated({ site, permanentWallet, data, action }) {
9
+ const url = new URL(site.appUrl);
10
+ url.pathname = joinUrl(WELLKNOWN_SERVICE_PATH_PREFIX, `/api/federated/${action}`);
11
+ const result = await pRetry(
12
+ () =>
13
+ request.post(url.href, {
14
+ signer: permanentWallet.address,
15
+ data: signV2(permanentWallet.address, permanentWallet.secretKey, data),
16
+ }),
17
+ { retries: 3 }
18
+ );
19
+ return result.data;
20
+ }
21
+
22
+ module.exports = {
23
+ callFederated,
24
+ };
@@ -21,7 +21,7 @@ const updateNFTDomainRecord = async ({ name, value, blocklet, nodeInfo }) => {
21
21
  const { wallet } = getBlockletInfo(blocklet, nodeInfo.sk);
22
22
 
23
23
  const payload = {
24
- delegatee: wallet.address,
24
+ delegatee: blocklet.appPid,
25
25
  domain: name,
26
26
  record: {
27
27
  name,
@@ -43,7 +43,7 @@ const updateNFTDomainRecord = async ({ name, value, blocklet, nodeInfo }) => {
43
43
  error,
44
44
  name,
45
45
  value,
46
- delegatee: wallet.address,
46
+ delegatee: blocklet.appPid,
47
47
  appPid: blocklet.appPid,
48
48
  resp: error.response?.data,
49
49
  });
@@ -72,9 +72,19 @@ async function getSpaceNameByEndpoint(endpoint, defaultValue = '') {
72
72
  }
73
73
  }
74
74
 
75
+ /**
76
+ * @description
77
+ * @param {string} did
78
+ * @return {string}
79
+ */
80
+ function getBackupJobId(did) {
81
+ return `${did}.backupToSpaces`;
82
+ }
83
+
75
84
  module.exports = {
76
85
  getBackupEndpoint,
77
86
  getBackupFilesUrlFromEndpoint,
78
87
  getDIDSpacesUrlFromEndpoint,
79
88
  getSpaceNameByEndpoint,
89
+ getBackupJobId,
80
90
  };
@@ -1,8 +1,8 @@
1
1
  /* eslint-disable newline-per-chained-call */
2
- const JOI = require('joi');
2
+ const Joi = require('joi');
3
3
  const { getMultipleLangParams } = require('./util');
4
4
 
5
- const roleNameSchema = JOI.string()
5
+ const roleNameSchema = Joi.string()
6
6
  .trim()
7
7
  .max(64)
8
8
  .custom((value) => {
@@ -17,19 +17,41 @@ const roleNameSchema = JOI.string()
17
17
  return value;
18
18
  });
19
19
 
20
- const titleSchema = JOI.string().trim().max(25);
21
- const descriptionSchema = JOI.string().trim().max(600);
20
+ const roleAcquireSchema = Joi.object({
21
+ pay: Joi.string().optional().allow(''),
22
+ exchange: Joi.string().optional().allow(''),
23
+ invite: Joi.boolean().optional(),
24
+ transfer: Joi.boolean().optional(),
25
+ request: Joi.boolean().optional(),
26
+ }).default({
27
+ pay: '',
28
+ exchange: '',
29
+ invite: true,
30
+ transfer: false,
31
+ request: false,
32
+ });
33
+
34
+ const titleSchema = Joi.string().trim().max(25);
35
+ const descriptionSchema = Joi.string().trim().max(600);
22
36
 
23
- const createRoleSchema = JOI.object({
37
+ const createRoleSchema = Joi.object({
24
38
  name: roleNameSchema.required(),
25
39
  title: titleSchema.required(),
26
40
  description: descriptionSchema.required(),
41
+ extra: Joi.object({
42
+ acquire: roleAcquireSchema,
43
+ payment: Joi.any().optional(),
44
+ }).optional(),
27
45
  });
28
46
 
29
- const updateRoleSchema = JOI.object({
47
+ const updateRoleSchema = Joi.object({
30
48
  name: roleNameSchema.required(),
31
49
  title: titleSchema,
32
50
  description: descriptionSchema,
51
+ extra: Joi.object({
52
+ acquire: roleAcquireSchema,
53
+ payment: Joi.any().optional(),
54
+ }).optional(),
33
55
  });
34
56
 
35
57
  module.exports = {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.16.17-beta-3232a7af",
6
+ "version": "1.16.17-beta-703fb879",
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.17-beta-3232a7af",
23
- "@abtnode/auth": "1.16.17-beta-3232a7af",
24
- "@abtnode/certificate-manager": "1.16.17-beta-3232a7af",
25
- "@abtnode/constant": "1.16.17-beta-3232a7af",
26
- "@abtnode/cron": "1.16.17-beta-3232a7af",
27
- "@abtnode/logger": "1.16.17-beta-3232a7af",
28
- "@abtnode/models": "1.16.17-beta-3232a7af",
29
- "@abtnode/queue": "1.16.17-beta-3232a7af",
30
- "@abtnode/rbac": "1.16.17-beta-3232a7af",
31
- "@abtnode/router-provider": "1.16.17-beta-3232a7af",
32
- "@abtnode/static-server": "1.16.17-beta-3232a7af",
33
- "@abtnode/timemachine": "1.16.17-beta-3232a7af",
34
- "@abtnode/util": "1.16.17-beta-3232a7af",
22
+ "@abtnode/analytics": "1.16.17-beta-703fb879",
23
+ "@abtnode/auth": "1.16.17-beta-703fb879",
24
+ "@abtnode/certificate-manager": "1.16.17-beta-703fb879",
25
+ "@abtnode/constant": "1.16.17-beta-703fb879",
26
+ "@abtnode/cron": "1.16.17-beta-703fb879",
27
+ "@abtnode/logger": "1.16.17-beta-703fb879",
28
+ "@abtnode/models": "1.16.17-beta-703fb879",
29
+ "@abtnode/queue": "1.16.17-beta-703fb879",
30
+ "@abtnode/rbac": "1.16.17-beta-703fb879",
31
+ "@abtnode/router-provider": "1.16.17-beta-703fb879",
32
+ "@abtnode/static-server": "1.16.17-beta-703fb879",
33
+ "@abtnode/timemachine": "1.16.17-beta-703fb879",
34
+ "@abtnode/util": "1.16.17-beta-703fb879",
35
35
  "@arcblock/did": "1.18.92",
36
36
  "@arcblock/did-auth": "1.18.92",
37
37
  "@arcblock/did-ext": "^1.18.92",
@@ -42,11 +42,11 @@
42
42
  "@arcblock/pm2-events": "^0.0.5",
43
43
  "@arcblock/validator": "^1.18.92",
44
44
  "@arcblock/vc": "1.18.92",
45
- "@blocklet/constant": "1.16.17-beta-3232a7af",
46
- "@blocklet/env": "1.16.17-beta-3232a7af",
47
- "@blocklet/meta": "1.16.17-beta-3232a7af",
48
- "@blocklet/resolver": "1.16.17-beta-3232a7af",
49
- "@blocklet/sdk": "1.16.17-beta-3232a7af",
45
+ "@blocklet/constant": "1.16.17-beta-703fb879",
46
+ "@blocklet/env": "1.16.17-beta-703fb879",
47
+ "@blocklet/meta": "1.16.17-beta-703fb879",
48
+ "@blocklet/resolver": "1.16.17-beta-703fb879",
49
+ "@blocklet/sdk": "1.16.17-beta-703fb879",
50
50
  "@did-space/client": "^0.3.11",
51
51
  "@fidm/x509": "^1.2.1",
52
52
  "@ocap/mcrypto": "1.18.92",
@@ -101,5 +101,5 @@
101
101
  "jest": "^27.5.1",
102
102
  "unzipper": "^0.10.11"
103
103
  },
104
- "gitHead": "af4067dd2e418c51a0ed96b1ee242ca812904056"
104
+ "gitHead": "97d63c671d823076e58b994107e10866367edd5d"
105
105
  }