@abtnode/core 1.15.17 → 1.16.0-beta-b16cb035
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/node.js +67 -69
- package/lib/api/team.js +386 -55
- package/lib/blocklet/downloader/blocklet-downloader.js +226 -0
- package/lib/blocklet/downloader/bundle-downloader.js +272 -0
- package/lib/blocklet/downloader/constants.js +3 -0
- package/lib/blocklet/downloader/resolve-download.js +199 -0
- package/lib/blocklet/extras.js +83 -26
- package/lib/blocklet/hooks.js +18 -65
- package/lib/blocklet/manager/base.js +10 -16
- package/lib/blocklet/manager/disk.js +1679 -1566
- package/lib/blocklet/manager/helper/install-application-from-backup.js +177 -0
- package/lib/blocklet/manager/helper/install-application-from-dev.js +94 -0
- package/lib/blocklet/manager/helper/install-application-from-general.js +188 -0
- package/lib/blocklet/manager/helper/install-component-from-dev.js +84 -0
- package/lib/blocklet/manager/helper/install-component-from-upload.js +181 -0
- package/lib/blocklet/manager/helper/install-component-from-url.js +173 -0
- package/lib/blocklet/manager/helper/migrate-application-to-struct-v2.js +450 -0
- package/lib/blocklet/manager/helper/rollback-cache.js +41 -0
- package/lib/blocklet/manager/helper/upgrade-components.js +152 -0
- package/lib/blocklet/migration.js +30 -52
- package/lib/blocklet/storage/backup/audit-log.js +27 -0
- package/lib/blocklet/storage/backup/base.js +62 -0
- package/lib/blocklet/storage/backup/blocklet-extras.js +92 -0
- package/lib/blocklet/storage/backup/blocklet.js +70 -0
- package/lib/blocklet/storage/backup/blocklets.js +74 -0
- package/lib/blocklet/storage/backup/data.js +19 -0
- package/lib/blocklet/storage/backup/logs.js +24 -0
- package/lib/blocklet/storage/backup/routing-rule.js +19 -0
- package/lib/blocklet/storage/backup/spaces.js +240 -0
- package/lib/blocklet/storage/restore/base.js +67 -0
- package/lib/blocklet/storage/restore/blocklet-extras.js +86 -0
- package/lib/blocklet/storage/restore/blocklet.js +56 -0
- package/lib/blocklet/storage/restore/blocklets.js +43 -0
- package/lib/blocklet/storage/restore/logs.js +21 -0
- package/lib/blocklet/storage/restore/spaces.js +156 -0
- package/lib/blocklet/storage/utils/hash.js +51 -0
- package/lib/blocklet/storage/utils/zip.js +43 -0
- package/lib/cert.js +206 -0
- package/lib/event.js +237 -64
- package/lib/index.js +191 -83
- package/lib/migrations/1.0.21-update-config.js +1 -1
- package/lib/migrations/1.0.22-max-memory.js +1 -1
- package/lib/migrations/1.0.25.js +1 -1
- package/lib/migrations/1.0.32-update-config.js +1 -1
- package/lib/migrations/1.0.33-blocklets.js +1 -1
- package/lib/migrations/1.5.20-registry.js +15 -0
- package/lib/migrations/1.6.17-blocklet-children.js +48 -0
- package/lib/migrations/1.6.21-rename-ip-echo-domain.js +35 -0
- package/lib/migrations/1.6.4-security.js +59 -0
- package/lib/migrations/1.6.5-security.js +60 -0
- package/lib/migrations/1.6.9-update-node-info-and-certificate.js +38 -0
- package/lib/migrations/1.7.1-blocklet-setup.js +18 -0
- package/lib/migrations/1.7.12-blocklet-meta.js +51 -0
- package/lib/migrations/1.7.15-blocklet-bundle-source.js +42 -0
- package/lib/migrations/1.7.20-blocklet-component.js +41 -0
- package/lib/migrations/1.8.33-blocklet-mem-limit.js +20 -0
- package/lib/migrations/README.md +1 -1
- package/lib/migrations/index.js +6 -2
- package/lib/monitor/blocklet-runtime-monitor.js +200 -0
- package/lib/monitor/get-history-list.js +37 -0
- package/lib/monitor/node-runtime-monitor.js +228 -0
- package/lib/router/helper.js +572 -497
- package/lib/router/index.js +85 -21
- package/lib/router/manager.js +146 -187
- package/lib/states/README.md +36 -1
- package/lib/states/access-key.js +39 -17
- package/lib/states/audit-log.js +462 -0
- package/lib/states/base.js +4 -213
- package/lib/states/blocklet-extras.js +194 -138
- package/lib/states/blocklet.js +361 -104
- package/lib/states/cache.js +8 -6
- package/lib/states/challenge.js +5 -5
- package/lib/states/index.js +19 -36
- package/lib/states/migration.js +4 -4
- package/lib/states/node.js +135 -46
- package/lib/states/notification.js +22 -35
- package/lib/states/session.js +17 -9
- package/lib/states/site.js +50 -25
- package/lib/states/user.js +74 -20
- package/lib/states/webhook.js +10 -6
- package/lib/team/manager.js +124 -7
- package/lib/util/blocklet.js +1223 -246
- package/lib/util/chain.js +1 -1
- package/lib/util/default-node-config.js +5 -23
- package/lib/util/disk-monitor.js +13 -10
- package/lib/util/domain-status.js +84 -15
- package/lib/util/get-accessible-external-node-ip.js +2 -2
- package/lib/util/get-domain-for-blocklet.js +13 -0
- package/lib/util/get-meta-from-url.js +33 -0
- package/lib/util/index.js +207 -272
- package/lib/util/ip.js +6 -0
- package/lib/util/maintain.js +233 -0
- package/lib/util/public-to-store.js +85 -0
- package/lib/util/ready.js +1 -1
- package/lib/util/requirement.js +28 -9
- package/lib/util/reset-node.js +22 -7
- package/lib/util/router.js +13 -0
- package/lib/util/rpc.js +16 -0
- package/lib/util/store.js +179 -0
- package/lib/util/sysinfo.js +44 -0
- package/lib/util/ua.js +54 -0
- package/lib/validators/blocklet-extra.js +24 -0
- package/lib/validators/node.js +25 -12
- package/lib/validators/permission.js +16 -1
- package/lib/validators/role.js +17 -3
- package/lib/validators/router.js +40 -20
- package/lib/validators/trusted-passport.js +1 -0
- package/lib/validators/util.js +22 -5
- package/lib/webhook/index.js +45 -35
- package/lib/webhook/sender/index.js +5 -0
- package/lib/webhook/sender/slack/index.js +1 -1
- package/lib/webhook/sender/wallet/index.js +48 -0
- package/package.json +54 -36
- package/lib/blocklet/registry.js +0 -205
- package/lib/states/https-cert.js +0 -67
- package/lib/util/get-ip-dns-domain-for-blocklet.js +0 -19
- package/lib/util/service.js +0 -66
- package/lib/util/upgrade.js +0 -178
- /package/lib/{queue.js → util/queue.js} +0 -0
package/lib/api/team.js
CHANGED
|
@@ -1,13 +1,46 @@
|
|
|
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 {
|
|
6
|
+
const {
|
|
7
|
+
ROLES,
|
|
8
|
+
genPermissionName,
|
|
9
|
+
EVENTS,
|
|
10
|
+
WHO_CAN_ACCESS,
|
|
11
|
+
PASSPORT_STATUS,
|
|
12
|
+
WELLKNOWN_SERVICE_PATH_PREFIX,
|
|
13
|
+
MAX_USER_PAGE_SIZE,
|
|
14
|
+
STORE_DETAIL_PAGE_PATH_PREFIX,
|
|
15
|
+
} = require('@abtnode/constant');
|
|
5
16
|
const { isValid: isValidDid } = require('@arcblock/did');
|
|
6
|
-
const { BlockletEvents } = require('@blocklet/
|
|
17
|
+
const { BlockletEvents } = require('@blocklet/constant');
|
|
18
|
+
const { sendToUser } = require('@blocklet/sdk/lib/util/send-notification');
|
|
19
|
+
const {
|
|
20
|
+
createPassportVC,
|
|
21
|
+
createPassport,
|
|
22
|
+
upsertToPassports,
|
|
23
|
+
createUserPassport,
|
|
24
|
+
} = require('@abtnode/auth/lib/passport');
|
|
25
|
+
const { getPassportStatusEndpoint } = require('@abtnode/auth/lib/auth');
|
|
26
|
+
const getBlockletInfo = require('@blocklet/meta/lib/info');
|
|
27
|
+
const { parseUserAvatar } = require('@abtnode/util/lib/user-avatar');
|
|
28
|
+
|
|
7
29
|
const { validateTrustedPassportIssuers } = require('../validators/trusted-passport');
|
|
8
30
|
const { validateCreateRole, validateUpdateRole } = require('../validators/role');
|
|
9
31
|
const { validateCreatePermission, validateUpdatePermission } = require('../validators/permission');
|
|
10
32
|
|
|
33
|
+
const { getBlocklet } = require('../util/blocklet');
|
|
34
|
+
const StoreUtil = require('../util/store');
|
|
35
|
+
|
|
36
|
+
const sanitizeUrl = (url) => {
|
|
37
|
+
if (!url) {
|
|
38
|
+
throw new Error('Registry URL should not be empty');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return url.trim();
|
|
42
|
+
};
|
|
43
|
+
|
|
11
44
|
const validateReservedRole = (role) => {
|
|
12
45
|
if (Object.values(ROLES).includes(role)) {
|
|
13
46
|
throw new Error(`The role ${role} is reserved`);
|
|
@@ -15,18 +48,53 @@ const validateReservedRole = (role) => {
|
|
|
15
48
|
return true;
|
|
16
49
|
};
|
|
17
50
|
|
|
51
|
+
const sendPassportVcNotification = ({ userDid, appWallet: wallet, locale, vc }) => {
|
|
52
|
+
try {
|
|
53
|
+
const receiver = userDid;
|
|
54
|
+
const sender = { appDid: wallet.address, appSk: wallet.secretKey };
|
|
55
|
+
const message = {
|
|
56
|
+
title: {
|
|
57
|
+
en: 'Receive a Passport',
|
|
58
|
+
zh: '获得通行证',
|
|
59
|
+
}[locale],
|
|
60
|
+
body: {
|
|
61
|
+
en: 'You got a passport',
|
|
62
|
+
zh: '你获得了一张通行证',
|
|
63
|
+
}[locale],
|
|
64
|
+
attachments: [
|
|
65
|
+
{
|
|
66
|
+
type: 'vc',
|
|
67
|
+
data: {
|
|
68
|
+
credential: vc,
|
|
69
|
+
tag: vc.credentialSubject.passport.name,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
sendToUser(receiver, message, sender, process.env.ABT_NODE_SERVICE_PORT).catch((error) => {
|
|
76
|
+
logger.error('Failed send passport vc to wallet', { error });
|
|
77
|
+
});
|
|
78
|
+
} catch (error) {
|
|
79
|
+
logger.error('Failed send passport vc to wallet', { error });
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
18
83
|
class TeamAPI extends EventEmitter {
|
|
19
84
|
/**
|
|
20
85
|
*
|
|
21
86
|
* @param {object} states abtnode core StateDB
|
|
22
87
|
*/
|
|
23
|
-
constructor({ teamManager, states }) {
|
|
88
|
+
constructor({ teamManager, states, dataDirs }) {
|
|
24
89
|
super();
|
|
25
90
|
|
|
26
91
|
this.notification = states.notification;
|
|
27
92
|
this.node = states.node;
|
|
28
|
-
this.
|
|
93
|
+
this.states = states;
|
|
94
|
+
this.memberInviteExpireTime = 1000 * 3600 * 24 * 30; // 30 days
|
|
95
|
+
this.serverInviteExpireTime = 1000 * 3600; // 1 hour
|
|
29
96
|
this.teamManager = teamManager;
|
|
97
|
+
this.dataDirs = dataDirs;
|
|
30
98
|
}
|
|
31
99
|
|
|
32
100
|
// User && Invitation
|
|
@@ -54,41 +122,71 @@ class TeamAPI extends EventEmitter {
|
|
|
54
122
|
if (teamDid === nodeInfo.did && nodeInfo.nodeOwner && user.did !== nodeInfo.nodeOwner.did) {
|
|
55
123
|
await this.notification.create({
|
|
56
124
|
title: 'New member join',
|
|
57
|
-
description: `User with Name (${user.fullName}) and DID (${user.did}) has joined this
|
|
125
|
+
description: `User with Name (${user.fullName}) and DID (${user.did}) has joined this server`,
|
|
58
126
|
entityType: 'node',
|
|
59
|
-
action: '/
|
|
127
|
+
action: '/team/members',
|
|
60
128
|
entityId: user.did,
|
|
61
129
|
severity: 'success',
|
|
62
130
|
});
|
|
63
131
|
}
|
|
64
132
|
|
|
65
|
-
this.emit(
|
|
133
|
+
this.emit(EVENTS.USER_ADDED, { teamDid, user: doc });
|
|
66
134
|
|
|
67
135
|
return doc;
|
|
68
136
|
}
|
|
69
137
|
|
|
70
|
-
async getUsers({ teamDid }) {
|
|
138
|
+
async getUsers({ teamDid, query, paging: inputPaging, sort, dids }) {
|
|
71
139
|
const state = await this.getUserState(teamDid);
|
|
72
140
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
141
|
+
if (inputPaging?.pageSize > MAX_USER_PAGE_SIZE) {
|
|
142
|
+
throw new Error(`Length of users should not exceed ${MAX_USER_PAGE_SIZE} per page`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (dids && dids.length > MAX_USER_PAGE_SIZE) {
|
|
146
|
+
throw new Error(`Length of users should not exceed ${MAX_USER_PAGE_SIZE}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let list;
|
|
150
|
+
let paging;
|
|
151
|
+
|
|
152
|
+
if (dids) {
|
|
153
|
+
list = await state.getUsersByDids({ query, dids });
|
|
154
|
+
paging = {
|
|
155
|
+
total: list.length,
|
|
156
|
+
pageSize: dids.length,
|
|
157
|
+
pageCount: 1,
|
|
158
|
+
page: 1,
|
|
159
|
+
};
|
|
160
|
+
} else {
|
|
161
|
+
const doc = await state.getUsers({ query, sort, paging: { pageSize: 20, ...inputPaging } });
|
|
162
|
+
list = doc.list;
|
|
163
|
+
paging = doc.paging;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
users: list.map(
|
|
168
|
+
(d) =>
|
|
169
|
+
pick(d, [
|
|
170
|
+
'did',
|
|
171
|
+
'pk',
|
|
172
|
+
'role',
|
|
173
|
+
'email',
|
|
174
|
+
'fullName',
|
|
175
|
+
'approved',
|
|
176
|
+
'createdAt',
|
|
177
|
+
'updatedAt',
|
|
178
|
+
'passports',
|
|
179
|
+
'firstLoginAt',
|
|
180
|
+
'lastLoginAt',
|
|
181
|
+
'lastLoginIp',
|
|
182
|
+
'remark',
|
|
183
|
+
'avatar',
|
|
184
|
+
'locale',
|
|
185
|
+
])
|
|
186
|
+
// eslint-disable-next-line function-paren-newline
|
|
187
|
+
),
|
|
188
|
+
paging,
|
|
189
|
+
};
|
|
92
190
|
}
|
|
93
191
|
|
|
94
192
|
async getUsersCount({ teamDid }) {
|
|
@@ -97,19 +195,54 @@ class TeamAPI extends EventEmitter {
|
|
|
97
195
|
return state.count();
|
|
98
196
|
}
|
|
99
197
|
|
|
198
|
+
async getUsersCountPerRole({ teamDid }) {
|
|
199
|
+
const roles = await this.getRoles({ teamDid });
|
|
200
|
+
|
|
201
|
+
const state = await this.getUserState(teamDid);
|
|
202
|
+
|
|
203
|
+
const res = [];
|
|
204
|
+
|
|
205
|
+
const all = await state.count();
|
|
206
|
+
res.push({ key: '$all', value: all });
|
|
207
|
+
|
|
208
|
+
for (const { name } of roles) {
|
|
209
|
+
// eslint-disable-next-line no-await-in-loop
|
|
210
|
+
const count = await state.count({ passports: { $elemMatch: { name, status: PASSPORT_STATUS.VALID } } });
|
|
211
|
+
res.push({ key: name, value: count });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const none = await state.count({ passports: { $size: 0 } });
|
|
215
|
+
res.push({ key: '$none', value: none });
|
|
216
|
+
|
|
217
|
+
return res;
|
|
218
|
+
}
|
|
219
|
+
|
|
100
220
|
async getUser({ teamDid, user }) {
|
|
101
221
|
const state = await this.getUserState(teamDid);
|
|
102
222
|
|
|
103
223
|
return state.getUser(user.did);
|
|
104
224
|
}
|
|
105
225
|
|
|
226
|
+
async getOwner({ teamDid }) {
|
|
227
|
+
const owner = await this.teamManager.getOwner(teamDid);
|
|
228
|
+
|
|
229
|
+
if (!owner) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const state = await this.getUserState(teamDid);
|
|
234
|
+
|
|
235
|
+
const full = await state.getUser(owner.did);
|
|
236
|
+
return full || owner;
|
|
237
|
+
}
|
|
238
|
+
|
|
106
239
|
async updateUser({ teamDid, user }) {
|
|
107
240
|
const state = await this.getUserState(teamDid);
|
|
108
241
|
|
|
109
242
|
const doc = await state.update(user);
|
|
110
243
|
|
|
111
244
|
logger.info('user updated successfully', { teamDid, userDid: user.did });
|
|
112
|
-
this.emit(
|
|
245
|
+
this.emit(EVENTS.USER_UPDATED, { teamDid, user: doc });
|
|
113
246
|
|
|
114
247
|
return doc;
|
|
115
248
|
}
|
|
@@ -131,7 +264,7 @@ class TeamAPI extends EventEmitter {
|
|
|
131
264
|
|
|
132
265
|
logger.info('user removed successfully', { teamDid, userDid: did });
|
|
133
266
|
|
|
134
|
-
this.emit(
|
|
267
|
+
this.emit(EVENTS.USER_REMOVED, { teamDid, user: { did } });
|
|
135
268
|
|
|
136
269
|
return { did };
|
|
137
270
|
}
|
|
@@ -152,7 +285,7 @@ class TeamAPI extends EventEmitter {
|
|
|
152
285
|
|
|
153
286
|
logger.info('user approval updated successfully', { teamDid, userDid: user.did, approved: user.approved });
|
|
154
287
|
|
|
155
|
-
this.emit(
|
|
288
|
+
this.emit(EVENTS.USER_UPDATED, { teamDid, user: doc2 });
|
|
156
289
|
|
|
157
290
|
return doc2;
|
|
158
291
|
}
|
|
@@ -171,11 +304,77 @@ class TeamAPI extends EventEmitter {
|
|
|
171
304
|
|
|
172
305
|
logger.info('user role updated successfully', { teamDid, userDid: user.did, role: user.role });
|
|
173
306
|
|
|
174
|
-
this.emit(
|
|
307
|
+
this.emit(EVENTS.USER_UPDATED, { teamDid, user: doc });
|
|
175
308
|
|
|
176
309
|
return doc;
|
|
177
310
|
}
|
|
178
311
|
|
|
312
|
+
async issuePassportToUser({ teamDid, userDid, role: roleName, notify = true }, context = {}) {
|
|
313
|
+
const { locale = 'en' } = context;
|
|
314
|
+
|
|
315
|
+
const userState = await this.getUserState(teamDid);
|
|
316
|
+
const user = await userState.getUser(userDid);
|
|
317
|
+
|
|
318
|
+
if (!user) {
|
|
319
|
+
throw new Error(`user does not exist: ${userDid}`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (!user.approved) {
|
|
323
|
+
throw new Error(`the user is revoked: ${userDid}`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const rbac = await this.getRBAC(teamDid);
|
|
327
|
+
const role = await rbac.getRole(roleName);
|
|
328
|
+
|
|
329
|
+
if (!role) {
|
|
330
|
+
throw new Error(`passport does not exist: ${roleName}`);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// create vc
|
|
334
|
+
const blocklet = await getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs });
|
|
335
|
+
const nodeInfo = await this.node.read();
|
|
336
|
+
const blockletInfo = getBlockletInfo(blocklet, nodeInfo.sk);
|
|
337
|
+
const { wallet, passportColor, appUrl, name: issuerName } = blockletInfo;
|
|
338
|
+
|
|
339
|
+
const vc = createPassportVC({
|
|
340
|
+
issuerName,
|
|
341
|
+
issuerWallet: wallet,
|
|
342
|
+
ownerDid: userDid,
|
|
343
|
+
passport: await createPassport({
|
|
344
|
+
role,
|
|
345
|
+
}),
|
|
346
|
+
endpoint: getPassportStatusEndpoint({
|
|
347
|
+
baseUrl: joinUrl(appUrl, WELLKNOWN_SERVICE_PATH_PREFIX),
|
|
348
|
+
userDid,
|
|
349
|
+
teamDid,
|
|
350
|
+
}),
|
|
351
|
+
ownerProfile: {
|
|
352
|
+
...user,
|
|
353
|
+
avatar: await parseUserAvatar(user.avatar, { dataDir: blocklet.env.dataDir }),
|
|
354
|
+
},
|
|
355
|
+
preferredColor: passportColor,
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// write passport to db
|
|
359
|
+
const passport = createUserPassport(vc, { role: role.name });
|
|
360
|
+
user.passports = upsertToPassports(user.passports || [], passport);
|
|
361
|
+
await this.updateUser({
|
|
362
|
+
teamDid,
|
|
363
|
+
user: {
|
|
364
|
+
did: user.did,
|
|
365
|
+
pk: user.pk,
|
|
366
|
+
passports: user.passports,
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// send vc to wallet
|
|
371
|
+
if (notify) {
|
|
372
|
+
sendPassportVcNotification({ userDid, appWallet: wallet, locale, vc });
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return user;
|
|
376
|
+
}
|
|
377
|
+
|
|
179
378
|
async revokeUserPassport({ teamDid, userDid, passportId } = {}) {
|
|
180
379
|
if (!passportId) {
|
|
181
380
|
throw new Error('Revoked passport should not be empty');
|
|
@@ -187,7 +386,7 @@ class TeamAPI extends EventEmitter {
|
|
|
187
386
|
|
|
188
387
|
logger.info('user passport revoked successfully', { teamDid, userDid, passportId });
|
|
189
388
|
|
|
190
|
-
this.emit(
|
|
389
|
+
this.emit(EVENTS.USER_UPDATED, { teamDid, user: doc });
|
|
191
390
|
|
|
192
391
|
return doc;
|
|
193
392
|
}
|
|
@@ -203,16 +402,40 @@ class TeamAPI extends EventEmitter {
|
|
|
203
402
|
|
|
204
403
|
logger.info('user passport enabled successfully', { teamDid, userDid, passportId });
|
|
205
404
|
|
|
206
|
-
this.emit(
|
|
405
|
+
this.emit(EVENTS.USER_UPDATED, { teamDid, user: doc });
|
|
207
406
|
|
|
208
407
|
return doc;
|
|
209
408
|
}
|
|
210
409
|
|
|
211
410
|
// Invite member
|
|
212
411
|
|
|
213
|
-
|
|
412
|
+
/**
|
|
413
|
+
* @type InvitationSession {
|
|
414
|
+
* type: 'invite';
|
|
415
|
+
* role: string;
|
|
416
|
+
* remark: string;
|
|
417
|
+
* expireDate: number;
|
|
418
|
+
* inviter: {
|
|
419
|
+
* did: string;
|
|
420
|
+
* email?: string;
|
|
421
|
+
* fullName?: string;
|
|
422
|
+
* role?: string;
|
|
423
|
+
* };
|
|
424
|
+
* teamDid: string;
|
|
425
|
+
* status: '' | 'success';
|
|
426
|
+
* receiver: {
|
|
427
|
+
* did: string;
|
|
428
|
+
* };
|
|
429
|
+
* }
|
|
430
|
+
* @returns
|
|
431
|
+
*/
|
|
432
|
+
async createMemberInvitation({ teamDid, role, expireTime, remark }, context) {
|
|
214
433
|
await this.teamManager.checkEnablePassportIssuance(teamDid);
|
|
215
434
|
|
|
435
|
+
if (expireTime && expireTime <= 0) {
|
|
436
|
+
throw new Error('Expire time must be greater than 0');
|
|
437
|
+
}
|
|
438
|
+
|
|
216
439
|
if (!role) {
|
|
217
440
|
throw new Error('Role cannot be empty');
|
|
218
441
|
}
|
|
@@ -232,7 +455,7 @@ class TeamAPI extends EventEmitter {
|
|
|
232
455
|
throw new Error('Inviter does not exist');
|
|
233
456
|
}
|
|
234
457
|
|
|
235
|
-
const expireDate = Date.now() + this.
|
|
458
|
+
const expireDate = Date.now() + (expireTime || this.memberInviteExpireTime);
|
|
236
459
|
const state = await this.getSessionState(teamDid);
|
|
237
460
|
const { id: inviteId } = await state.start({
|
|
238
461
|
type: 'invite',
|
|
@@ -241,7 +464,6 @@ class TeamAPI extends EventEmitter {
|
|
|
241
464
|
expireDate,
|
|
242
465
|
inviter: user,
|
|
243
466
|
teamDid,
|
|
244
|
-
interfaceName,
|
|
245
467
|
});
|
|
246
468
|
|
|
247
469
|
logger.info('Create invite member', { role, user, inviteId });
|
|
@@ -253,16 +475,47 @@ class TeamAPI extends EventEmitter {
|
|
|
253
475
|
expireDate: new Date(expireDate).toString(),
|
|
254
476
|
inviter: user,
|
|
255
477
|
teamDid,
|
|
256
|
-
interfaceName,
|
|
257
478
|
};
|
|
258
479
|
}
|
|
259
480
|
|
|
260
|
-
async
|
|
481
|
+
async createTransferInvitation({ teamDid, remark }, context) {
|
|
482
|
+
return this.createMemberInvitation(
|
|
483
|
+
{ teamDid, expireTime: this.serverInviteExpireTime, remark, role: 'owner' },
|
|
484
|
+
context
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
async getInvitation({ teamDid, inviteId }) {
|
|
489
|
+
if (!teamDid || !inviteId) {
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const state = await this.getSessionState(teamDid);
|
|
494
|
+
|
|
495
|
+
const invitation = await state.findOne({ _id: inviteId, type: 'invite' });
|
|
496
|
+
if (!invitation) {
|
|
497
|
+
return null;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return {
|
|
501
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
502
|
+
inviteId: invitation._id,
|
|
503
|
+
role: invitation.role,
|
|
504
|
+
remark: invitation.remark,
|
|
505
|
+
expireDate: new Date(invitation.expireDate).toString(),
|
|
506
|
+
inviter: invitation.inviter,
|
|
507
|
+
teamDid: invitation.teamDid,
|
|
508
|
+
status: invitation.status,
|
|
509
|
+
receiver: invitation.receiver,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
async getInvitations({ teamDid, filter }) {
|
|
261
514
|
const state = await this.getSessionState(teamDid);
|
|
262
515
|
|
|
263
516
|
const invitations = await state.find({ type: 'invite' });
|
|
264
517
|
|
|
265
|
-
return invitations.map((d) => ({
|
|
518
|
+
return invitations.filter(filter || ((x) => x.status !== 'success')).map((d) => ({
|
|
266
519
|
// eslint-disable-next-line no-underscore-dangle
|
|
267
520
|
inviteId: d._id,
|
|
268
521
|
role: d.role,
|
|
@@ -270,7 +523,8 @@ class TeamAPI extends EventEmitter {
|
|
|
270
523
|
expireDate: new Date(d.expireDate).toString(),
|
|
271
524
|
inviter: d.inviter,
|
|
272
525
|
teamDid: d.teamDid,
|
|
273
|
-
|
|
526
|
+
status: d.status,
|
|
527
|
+
receiver: d.receiver,
|
|
274
528
|
}));
|
|
275
529
|
}
|
|
276
530
|
|
|
@@ -287,7 +541,7 @@ class TeamAPI extends EventEmitter {
|
|
|
287
541
|
return true;
|
|
288
542
|
}
|
|
289
543
|
|
|
290
|
-
async
|
|
544
|
+
async checkInvitation({ teamDid, inviteId }) {
|
|
291
545
|
const state = await this.getSessionState(teamDid);
|
|
292
546
|
|
|
293
547
|
const invitation = await state.read(inviteId);
|
|
@@ -296,7 +550,7 @@ class TeamAPI extends EventEmitter {
|
|
|
296
550
|
throw new Error(`The invitation does not exist: ${inviteId}`);
|
|
297
551
|
}
|
|
298
552
|
|
|
299
|
-
const { role, expireDate } = await state.read(inviteId);
|
|
553
|
+
const { role, expireDate, remark } = await state.read(inviteId);
|
|
300
554
|
|
|
301
555
|
if (Date.now() > expireDate) {
|
|
302
556
|
logger.error('Invite id has expired', { inviteId, expireAt: new Date(expireDate) });
|
|
@@ -309,15 +563,33 @@ class TeamAPI extends EventEmitter {
|
|
|
309
563
|
throw new Error(`Role does not exist: ${role}`);
|
|
310
564
|
}
|
|
311
565
|
|
|
312
|
-
await state.end(inviteId);
|
|
313
|
-
|
|
314
|
-
logger.info('Invitation session completed successfully', { inviteId, role });
|
|
315
|
-
|
|
316
566
|
return {
|
|
317
567
|
role,
|
|
568
|
+
remark,
|
|
318
569
|
};
|
|
319
570
|
}
|
|
320
571
|
|
|
572
|
+
async closeInvitation({ teamDid, inviteId, status, receiver, timeout = 30 * 1000 }) {
|
|
573
|
+
const state = await this.getSessionState(teamDid);
|
|
574
|
+
|
|
575
|
+
const invitation = await state.read(inviteId);
|
|
576
|
+
|
|
577
|
+
if (!invitation) {
|
|
578
|
+
throw new Error(`The invitation does not exist: ${inviteId}`);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
await state.update(inviteId, { status, receiver });
|
|
582
|
+
|
|
583
|
+
setTimeout(async () => {
|
|
584
|
+
try {
|
|
585
|
+
logger.info('Invitation session closed', { inviteId });
|
|
586
|
+
await state.end(inviteId);
|
|
587
|
+
} catch (error) {
|
|
588
|
+
logger.error('close invitation failed', { error });
|
|
589
|
+
}
|
|
590
|
+
}, timeout);
|
|
591
|
+
}
|
|
592
|
+
|
|
321
593
|
// Issue Passport
|
|
322
594
|
|
|
323
595
|
async createPassportIssuance({ teamDid, ownerDid, name }) {
|
|
@@ -338,7 +610,7 @@ class TeamAPI extends EventEmitter {
|
|
|
338
610
|
throw new Error(`Passport does not exist: ${name}`);
|
|
339
611
|
}
|
|
340
612
|
|
|
341
|
-
const expireDate = Date.now() + this.
|
|
613
|
+
const expireDate = Date.now() + this.memberInviteExpireTime;
|
|
342
614
|
|
|
343
615
|
const state = await this.getSessionState(teamDid);
|
|
344
616
|
const { id } = await state.start({
|
|
@@ -365,7 +637,13 @@ class TeamAPI extends EventEmitter {
|
|
|
365
637
|
async getPassportIssuances({ teamDid, ownerDid }) {
|
|
366
638
|
const state = await this.getSessionState(teamDid);
|
|
367
639
|
|
|
368
|
-
const
|
|
640
|
+
const query = { type: 'passport-issuance' };
|
|
641
|
+
|
|
642
|
+
if (ownerDid) {
|
|
643
|
+
query.ownerDid = ownerDid;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const list = await state.find(query);
|
|
369
647
|
|
|
370
648
|
return list.map((d) => ({
|
|
371
649
|
// eslint-disable-next-line no-underscore-dangle
|
|
@@ -452,14 +730,18 @@ class TeamAPI extends EventEmitter {
|
|
|
452
730
|
}
|
|
453
731
|
}
|
|
454
732
|
|
|
455
|
-
|
|
733
|
+
async configWhoCanAccess({ teamDid, value }) {
|
|
734
|
+
if (!Object.values(WHO_CAN_ACCESS).includes(value)) {
|
|
735
|
+
throw new Error('value is invalid');
|
|
736
|
+
}
|
|
456
737
|
|
|
457
|
-
|
|
458
|
-
|
|
738
|
+
await this.teamManager.configWhoCanAccess(teamDid, value);
|
|
739
|
+
}
|
|
459
740
|
|
|
460
|
-
|
|
741
|
+
// Access Control
|
|
461
742
|
|
|
462
|
-
|
|
743
|
+
async getRoles({ teamDid }) {
|
|
744
|
+
return this.teamManager.getRoles(teamDid);
|
|
463
745
|
}
|
|
464
746
|
|
|
465
747
|
async createRole({ teamDid, name, description, title, childName, permissions = [] }) {
|
|
@@ -486,7 +768,7 @@ class TeamAPI extends EventEmitter {
|
|
|
486
768
|
async updateRole({ teamDid, role: { name, title, description } = {} }) {
|
|
487
769
|
logger.info('update role', { teamDid, name, title, description });
|
|
488
770
|
|
|
489
|
-
validateUpdateRole({ name, title, description });
|
|
771
|
+
await validateUpdateRole({ name, title, description });
|
|
490
772
|
|
|
491
773
|
const rbac = await this.getRBAC(teamDid);
|
|
492
774
|
|
|
@@ -628,6 +910,55 @@ class TeamAPI extends EventEmitter {
|
|
|
628
910
|
);
|
|
629
911
|
}
|
|
630
912
|
|
|
913
|
+
// eslint-disable-next-line no-unused-vars
|
|
914
|
+
async addStore({ teamDid, url }, context) {
|
|
915
|
+
logger.info('add registry', { url });
|
|
916
|
+
|
|
917
|
+
const urlObj = new URL(url);
|
|
918
|
+
let newUrl = urlObj.origin;
|
|
919
|
+
|
|
920
|
+
// if the pathname is store blocklet list or blocklet detail
|
|
921
|
+
if (urlObj.pathname?.includes(STORE_DETAIL_PAGE_PATH_PREFIX)) {
|
|
922
|
+
const lastIndex = urlObj.pathname.lastIndexOf(STORE_DETAIL_PAGE_PATH_PREFIX);
|
|
923
|
+
const pathnamePrefix = urlObj.pathname.substring(0, lastIndex);
|
|
924
|
+
newUrl = `${newUrl}${pathnamePrefix || ''}`;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
const sanitized = sanitizeUrl(newUrl);
|
|
928
|
+
|
|
929
|
+
const storeList = await this.teamManager.getStoreList(teamDid);
|
|
930
|
+
|
|
931
|
+
const exist = storeList.find((x) => x.url === sanitized);
|
|
932
|
+
if (exist) {
|
|
933
|
+
throw new Error(`Blocklet registry already exist: ${sanitized}`);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
await StoreUtil.validateStoreUrl(sanitized);
|
|
937
|
+
|
|
938
|
+
const store = await StoreUtil.getStoreMeta(sanitized);
|
|
939
|
+
|
|
940
|
+
storeList.push({ ...store, url: sanitized, protected: false });
|
|
941
|
+
|
|
942
|
+
return this.teamManager.updateStoreList(teamDid, storeList);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// eslint-disable-next-line no-unused-vars
|
|
946
|
+
async deleteStore({ teamDid, url }, context) {
|
|
947
|
+
logger.info('delete registry', { url });
|
|
948
|
+
const sanitized = sanitizeUrl(url);
|
|
949
|
+
|
|
950
|
+
const storeList = await this.teamManager.getStoreList(teamDid);
|
|
951
|
+
const exist = storeList.find((x) => x.url === sanitized);
|
|
952
|
+
if (!exist) {
|
|
953
|
+
throw new Error(`Blocklet registry does not exist: ${sanitized}`);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
return this.teamManager.updateStoreList(
|
|
957
|
+
teamDid,
|
|
958
|
+
storeList.filter((x) => x.url !== sanitized)
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
|
|
631
962
|
// =======
|
|
632
963
|
// Private
|
|
633
964
|
// =======
|
|
@@ -648,7 +979,7 @@ class TeamAPI extends EventEmitter {
|
|
|
648
979
|
// Just for test
|
|
649
980
|
// =============
|
|
650
981
|
setInviteExpireTime(ms) {
|
|
651
|
-
this.
|
|
982
|
+
this.memberInviteExpireTime = ms;
|
|
652
983
|
}
|
|
653
984
|
}
|
|
654
985
|
|