@abtnode/core 1.17.8-beta-20260109-075740-5f484e08 → 1.17.8-beta-20260113-015027-32a1cec4

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.
Files changed (65) hide show
  1. package/lib/api/team/access-key-manager.js +104 -0
  2. package/lib/api/team/invitation-manager.js +461 -0
  3. package/lib/api/team/notification-manager.js +189 -0
  4. package/lib/api/team/oauth-manager.js +60 -0
  5. package/lib/api/team/org-crud-manager.js +202 -0
  6. package/lib/api/team/org-manager.js +56 -0
  7. package/lib/api/team/org-member-manager.js +403 -0
  8. package/lib/api/team/org-query-manager.js +126 -0
  9. package/lib/api/team/org-resource-manager.js +186 -0
  10. package/lib/api/team/passport-manager.js +670 -0
  11. package/lib/api/team/rbac-manager.js +335 -0
  12. package/lib/api/team/session-manager.js +540 -0
  13. package/lib/api/team/store-manager.js +198 -0
  14. package/lib/api/team/tag-manager.js +230 -0
  15. package/lib/api/team/user-auth-manager.js +132 -0
  16. package/lib/api/team/user-manager.js +78 -0
  17. package/lib/api/team/user-query-manager.js +299 -0
  18. package/lib/api/team/user-social-manager.js +354 -0
  19. package/lib/api/team/user-update-manager.js +224 -0
  20. package/lib/api/team/verify-code-manager.js +161 -0
  21. package/lib/api/team.js +439 -3287
  22. package/lib/blocklet/manager/disk/auth-manager.js +68 -0
  23. package/lib/blocklet/manager/disk/backup-manager.js +288 -0
  24. package/lib/blocklet/manager/disk/cleanup-manager.js +157 -0
  25. package/lib/blocklet/manager/disk/component-manager.js +83 -0
  26. package/lib/blocklet/manager/disk/config-manager.js +191 -0
  27. package/lib/blocklet/manager/disk/controller-manager.js +64 -0
  28. package/lib/blocklet/manager/disk/delete-reset-manager.js +328 -0
  29. package/lib/blocklet/manager/disk/download-manager.js +96 -0
  30. package/lib/blocklet/manager/disk/env-config-manager.js +311 -0
  31. package/lib/blocklet/manager/disk/federated-manager.js +651 -0
  32. package/lib/blocklet/manager/disk/hook-manager.js +124 -0
  33. package/lib/blocklet/manager/disk/install-component-manager.js +95 -0
  34. package/lib/blocklet/manager/disk/install-core-manager.js +448 -0
  35. package/lib/blocklet/manager/disk/install-download-manager.js +313 -0
  36. package/lib/blocklet/manager/disk/install-manager.js +36 -0
  37. package/lib/blocklet/manager/disk/install-upgrade-manager.js +340 -0
  38. package/lib/blocklet/manager/disk/job-manager.js +467 -0
  39. package/lib/blocklet/manager/disk/lifecycle-manager.js +26 -0
  40. package/lib/blocklet/manager/disk/notification-manager.js +343 -0
  41. package/lib/blocklet/manager/disk/query-manager.js +562 -0
  42. package/lib/blocklet/manager/disk/settings-manager.js +507 -0
  43. package/lib/blocklet/manager/disk/start-manager.js +611 -0
  44. package/lib/blocklet/manager/disk/stop-restart-manager.js +292 -0
  45. package/lib/blocklet/manager/disk/update-manager.js +153 -0
  46. package/lib/blocklet/manager/disk.js +669 -5796
  47. package/lib/blocklet/manager/helper/blue-green-start-blocklet.js +5 -0
  48. package/lib/blocklet/manager/lock.js +18 -0
  49. package/lib/event/index.js +28 -24
  50. package/lib/util/blocklet/app-utils.js +192 -0
  51. package/lib/util/blocklet/blocklet-loader.js +258 -0
  52. package/lib/util/blocklet/config-manager.js +232 -0
  53. package/lib/util/blocklet/did-document.js +240 -0
  54. package/lib/util/blocklet/environment.js +555 -0
  55. package/lib/util/blocklet/health-check.js +449 -0
  56. package/lib/util/blocklet/install-utils.js +365 -0
  57. package/lib/util/blocklet/logo.js +57 -0
  58. package/lib/util/blocklet/meta-utils.js +269 -0
  59. package/lib/util/blocklet/port-manager.js +141 -0
  60. package/lib/util/blocklet/process-manager.js +504 -0
  61. package/lib/util/blocklet/runtime-info.js +105 -0
  62. package/lib/util/blocklet/validation.js +418 -0
  63. package/lib/util/blocklet.js +98 -3066
  64. package/lib/util/wallet-app-notification.js +40 -0
  65. package/package.json +22 -22
@@ -0,0 +1,670 @@
1
+ const { joinURL } = require('ufo');
2
+ const isEmpty = require('lodash/isEmpty');
3
+ const logger = require('@abtnode/logger')('@abtnode/core:api:team:passport');
4
+ const {
5
+ WELLKNOWN_SERVICE_PATH_PREFIX,
6
+ PASSPORT_LOG_ACTION,
7
+ PASSPORT_SOURCE,
8
+ PASSPORT_ISSUE_ACTION,
9
+ NOTIFICATION_SEND_CHANNEL,
10
+ MAIN_CHAIN_ENDPOINT,
11
+ } = require('@abtnode/constant');
12
+ const { isValid: isValidDid } = require('@arcblock/did');
13
+ const { BlockletEvents, TeamEvents } = require('@blocklet/constant');
14
+ const { sendToUser } = require('@blocklet/sdk/lib/util/send-notification');
15
+ const {
16
+ createPassportVC,
17
+ createPassport,
18
+ upsertToPassports,
19
+ createUserPassport,
20
+ } = require('@abtnode/auth/lib/passport');
21
+ const { formatError } = require('@blocklet/error');
22
+ const { getPassportStatusEndpoint, getApplicationInfo } = require('@abtnode/auth/lib/auth');
23
+ const { getBlockletInfo } = require('@blocklet/meta/lib/info');
24
+ const { getUserAvatarUrl, getAppAvatarUrl } = require('@abtnode/util/lib/user');
25
+ const { getChainClient } = require('@abtnode/util/lib/get-chain-client');
26
+ const { getWalletDid } = require('@blocklet/meta/lib/did-utils');
27
+ const axios = require('@abtnode/util/lib/axios');
28
+
29
+ const { validateTrustedPassportIssuers } = require('../../validators/trusted-passport');
30
+ const { validateTrustedFactories } = require('../../validators/trusted-factory');
31
+ const { getBlocklet } = require('../../util/blocklet');
32
+ const { passportDisplaySchema } = require('../../validators/util');
33
+
34
+ /**
35
+ * Send passport VC notification to user wallet
36
+ * @param {Object} params
37
+ * @param {string} params.userDid - User DID
38
+ * @param {Object} params.appWallet - App wallet
39
+ * @param {string} params.locale - Locale
40
+ * @param {Object} params.vc - VC
41
+ * @param {Object} params.notification - Notification
42
+ */
43
+ const sendPassportVcNotification = ({ userDid, appWallet: wallet, locale, vc, notification }) => {
44
+ try {
45
+ const receiver = userDid;
46
+ const sender = { appDid: wallet.address, appSk: wallet.secretKey };
47
+ const message = { ...notification };
48
+ if (!message.title) {
49
+ message.title = {
50
+ en: 'Receive a Passport',
51
+ zh: '获得通行证',
52
+ }[locale];
53
+ }
54
+ if (!message.body) {
55
+ message.body = {
56
+ en: 'You got a passport',
57
+ zh: '你获得了一张通行证',
58
+ }[locale];
59
+ }
60
+ if (!message.attachments) {
61
+ message.attachments = [
62
+ {
63
+ type: 'vc',
64
+ data: {
65
+ credential: vc,
66
+ tag: vc.credentialSubject.passport.name,
67
+ },
68
+ },
69
+ ];
70
+ }
71
+
72
+ sendToUser(receiver, message, sender, { channels: [NOTIFICATION_SEND_CHANNEL.WALLET] }).catch((error) => {
73
+ logger.error('Failed send passport vc to wallet', { error });
74
+ });
75
+ } catch (error) {
76
+ logger.error('Failed send passport vc to wallet', { error });
77
+ }
78
+ };
79
+
80
+ /**
81
+ * Validate passport display
82
+ * @param {Object} role - Role
83
+ * @param {Object} display - Display
84
+ */
85
+ const validatePassportDisplay = async (role, display) => {
86
+ if (role.extra?.display === 'custom') {
87
+ if (!display) {
88
+ throw new Error(`display is required for custom passport: ${role.name}`);
89
+ }
90
+ const { error, value } = passportDisplaySchema.validate(display);
91
+ if (error) {
92
+ throw new Error(`display invalid for custom passport: ${formatError(error)}`);
93
+ }
94
+
95
+ if (value.type === 'url') {
96
+ try {
97
+ const res = await axios.head(value.content, {
98
+ timeout: 8000,
99
+ maxRedirects: 8,
100
+ validateStatus: null,
101
+ headers: {
102
+ Accept: 'image/avif,image/webp,image/png,image/svg+xml,image/*,*/*;q=0.8',
103
+ 'User-Agent':
104
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', // prettier-ignore
105
+ },
106
+ });
107
+ if (res.status >= 400) {
108
+ throw new Error(`Passport display is not accessible: ${res.statusText}`);
109
+ }
110
+ const contentType = res.headers['content-type'];
111
+ if (!contentType?.startsWith('image/')) {
112
+ throw new Error(`Passport display does not return valid image: ${contentType}`);
113
+ }
114
+ } catch (err) {
115
+ throw new Error(`Error validating passport display: ${err.message}`);
116
+ }
117
+ }
118
+ }
119
+ };
120
+
121
+ /**
122
+ * Issue passport to user
123
+ * @param {Object} api - TeamAPI instance
124
+ * @param {Object} params
125
+ * @param {string} params.teamDid - Team DID
126
+ * @param {string} params.userDid - User DID
127
+ * @param {string} params.role - Role name
128
+ * @param {Object} params.display - Display settings
129
+ * @param {boolean} params.notify - Notify user
130
+ * @param {Object} params.notification - Notification
131
+ * @param {string} params.action - Action
132
+ * @param {Object} context
133
+ * @returns {Promise<Object>}
134
+ */
135
+ async function issuePassportToUser(
136
+ api,
137
+ { teamDid, userDid, role: roleName, display = null, notify = true, notification, action = '' },
138
+ context = {}
139
+ ) {
140
+ try {
141
+ // eslint-disable-next-line no-param-reassign
142
+ notification = JSON.parse(notification);
143
+ } catch {
144
+ logger.error('Failed to parse notification, use notification as undefined', { notification });
145
+ }
146
+ const { locale = 'en' } = context;
147
+
148
+ const userState = await api.getUserState(teamDid);
149
+ // NOTICE: issuePassportToUser 必须传入 wallet did,无需查询 connectedAccount
150
+ const user = await userState.getUser(userDid);
151
+
152
+ if (!user) {
153
+ throw new Error(`user does not exist: ${userDid}`);
154
+ }
155
+
156
+ if (!user.approved) {
157
+ throw new Error(`the user is revoked: ${userDid}`);
158
+ }
159
+
160
+ const rbac = await api.getRBAC(teamDid);
161
+ const role = await rbac.getRole(roleName);
162
+
163
+ if (!role) {
164
+ throw new Error(`passport does not exist: ${roleName}`);
165
+ }
166
+
167
+ await validatePassportDisplay(role, display);
168
+
169
+ // create vc
170
+ const nodeInfo = await api.node.read();
171
+ const {
172
+ wallet,
173
+ passportColor,
174
+ appUrl,
175
+ name: issuerName,
176
+ } = await getApplicationInfo({
177
+ teamDid,
178
+ nodeInfo,
179
+ node: {
180
+ dataDirs: api.dataDirs,
181
+ getSessionSecret: api.nodeAPI?.getSessionSecret?.bind(api.nodeAPI) || (() => 'abc'),
182
+ getBlocklet: () => getBlocklet({ did: teamDid, states: api.states, dataDirs: api.dataDirs }),
183
+ },
184
+ });
185
+
186
+ const result = await createPassport({ role });
187
+ const vc = await createPassportVC({
188
+ issuerName,
189
+ issuerWallet: wallet,
190
+ issuerAvatarUrl: getAppAvatarUrl(appUrl),
191
+ ownerDid: userDid,
192
+ endpoint: getPassportStatusEndpoint({
193
+ baseUrl: joinURL(appUrl, WELLKNOWN_SERVICE_PATH_PREFIX),
194
+ userDid: user.did,
195
+ teamDid,
196
+ }),
197
+ ownerProfile: {
198
+ ...user,
199
+ avatar: getUserAvatarUrl(appUrl, user.avatar),
200
+ },
201
+ preferredColor: passportColor,
202
+ passport: result.passport,
203
+ types: result.types,
204
+ purpose: teamDid === nodeInfo.did || isEmpty(result.types) ? 'login' : 'verification',
205
+ display,
206
+ });
207
+
208
+ // write passport to db
209
+ const passport = createUserPassport(vc, { role: role.name, display, source: PASSPORT_SOURCE.ISSUE });
210
+
211
+ user.passports = upsertToPassports(user.passports || [], passport);
212
+ await api.updateUser({
213
+ teamDid,
214
+ user: {
215
+ did: user.did,
216
+ pk: user.pk,
217
+ passports: user.passports,
218
+ },
219
+ });
220
+
221
+ if (passport && api.passportAPI) {
222
+ await api.passportAPI.createPassportLog(teamDid, {
223
+ passportId: passport.id,
224
+ action: PASSPORT_LOG_ACTION.ISSUE,
225
+ operatorDid: userDid,
226
+ metadata: {
227
+ action: action || 'issue-passport-to-user',
228
+ },
229
+ operatorIp: context?.ip || '',
230
+ operatorUa: context?.ua || '',
231
+ });
232
+ }
233
+
234
+ // send vc to wallet
235
+ if (notify) {
236
+ sendPassportVcNotification({ userDid, appWallet: wallet, locale, vc, notification });
237
+ }
238
+
239
+ return user;
240
+ }
241
+
242
+ /**
243
+ * Revoke user passport
244
+ * @param {Object} api - TeamAPI instance
245
+ * @param {Object} params
246
+ * @param {string} params.teamDid - Team DID
247
+ * @param {string} params.userDid - User DID
248
+ * @param {string} params.passportId - Passport ID
249
+ * @param {Object} context
250
+ * @returns {Promise<Object>}
251
+ */
252
+ async function revokeUserPassport(api, { teamDid, userDid, passportId } = {}, context = {}) {
253
+ if (!passportId) {
254
+ throw new Error('Revoked passport should not be empty');
255
+ }
256
+
257
+ const state = await api.getUserState(teamDid);
258
+
259
+ const doc = await state.revokePassportById({ did: userDid, id: passportId });
260
+
261
+ // create passport log
262
+ if (api.passportAPI) {
263
+ await api.passportAPI.createPassportLog(teamDid, {
264
+ passportId,
265
+ action: PASSPORT_LOG_ACTION.REVOKE,
266
+ operatorDid: userDid,
267
+ metadata: {
268
+ operator: await state.getUser(userDid).catch(() => null),
269
+ },
270
+ operatorIp: context?.ip || '',
271
+ operatorUa: context?.ua || '',
272
+ });
273
+ }
274
+
275
+ logger.info('user passport revoked successfully', { teamDid, userDid, passportId });
276
+
277
+ api.emit(TeamEvents.userUpdated, { teamDid, user: doc });
278
+ api.emit(TeamEvents.userPermissionUpdated, { teamDid, user: doc });
279
+
280
+ return doc;
281
+ }
282
+
283
+ /**
284
+ * Enable user passport
285
+ * @param {Object} api - TeamAPI instance
286
+ * @param {Object} params
287
+ * @param {string} params.teamDid - Team DID
288
+ * @param {string} params.userDid - User DID
289
+ * @param {string} params.passportId - Passport ID
290
+ * @param {Object} context
291
+ * @returns {Promise<Object>}
292
+ */
293
+ async function enableUserPassport(api, { teamDid, userDid, passportId } = {}, context) {
294
+ if (!passportId) {
295
+ throw new Error('Passport should not be empty');
296
+ }
297
+
298
+ const state = await api.getUserState(teamDid);
299
+
300
+ const doc = await state.enablePassportById({ did: userDid, id: passportId });
301
+
302
+ if (api.passportAPI) {
303
+ await api.passportAPI.createPassportLog(teamDid, {
304
+ passportId,
305
+ action: PASSPORT_LOG_ACTION.APPROVE,
306
+ operatorDid: userDid,
307
+ metadata: {
308
+ operator: await state.getUser(userDid).catch(() => null),
309
+ },
310
+ operatorIp: context?.ip || '',
311
+ operatorUa: context?.ua || '',
312
+ });
313
+ }
314
+
315
+ logger.info('user passport enabled successfully', { teamDid, userDid, passportId });
316
+
317
+ api.emit(TeamEvents.userUpdated, { teamDid, user: doc });
318
+ api.emit(TeamEvents.userPermissionUpdated, { teamDid, user: doc });
319
+
320
+ return doc;
321
+ }
322
+
323
+ /**
324
+ * Remove user passport
325
+ * @param {Object} api - TeamAPI instance
326
+ * @param {Object} params
327
+ * @param {string} params.passportId - Passport ID
328
+ * @param {string} params.userDid - User DID
329
+ * @param {string} params.teamDid - Team DID
330
+ * @returns {Promise<Object>}
331
+ */
332
+ async function removeUserPassport(api, { passportId, userDid, teamDid }) {
333
+ if (!passportId) {
334
+ throw new Error('Revoked passport should not be empty');
335
+ }
336
+
337
+ const state = await api.getUserState(teamDid);
338
+
339
+ const doc = await state.removePassportById({ id: passportId });
340
+
341
+ logger.info('user passport remove successfully', { teamDid, userDid, passportId });
342
+
343
+ api.emit(TeamEvents.userUpdated, { teamDid, user: doc });
344
+ api.emit(TeamEvents.userPermissionUpdated, { teamDid, user: doc });
345
+
346
+ return doc;
347
+ }
348
+
349
+ /**
350
+ * Create passport issuance session
351
+ * @param {Object} api - TeamAPI instance
352
+ * @param {Object} params
353
+ * @param {string} params.teamDid - Team DID
354
+ * @param {string} params.ownerDid - Owner DID
355
+ * @param {string} params.name - Passport name
356
+ * @param {Object} params.display - Display settings
357
+ * @param {number} params.passportExpireTime - Passport expire time
358
+ * @param {Object} context
359
+ * @returns {Promise<Object>}
360
+ */
361
+ async function createPassportIssuance(
362
+ api,
363
+ { teamDid, ownerDid, name, display = null, passportExpireTime = null },
364
+ context = {}
365
+ ) {
366
+ if (!name) {
367
+ throw new Error('Passport cannot be empty');
368
+ }
369
+
370
+ if (!isValidDid(ownerDid)) {
371
+ throw new Error(`ownerDid is invalid: ${ownerDid}`);
372
+ }
373
+
374
+ await api.teamManager.checkEnablePassportIssuance(teamDid);
375
+
376
+ // passport name is same as role
377
+ const roles = await api.getRoles({ teamDid });
378
+ const role = roles.find((r) => r.name === name);
379
+ if (!role) {
380
+ throw new Error(`Passport does not exist: ${name}`);
381
+ }
382
+ if (role.extra?.display === 'custom') {
383
+ if (!display) {
384
+ throw new Error(`display is required for custom passport: ${name}`);
385
+ }
386
+ const { error } = passportDisplaySchema.validate(display);
387
+ if (error) {
388
+ throw new Error(`display invalid for custom passport: ${formatError(error)}`);
389
+ }
390
+ }
391
+
392
+ await validatePassportDisplay(role, display);
393
+
394
+ const expireDate = Date.now() + api.memberInviteExpireTime;
395
+
396
+ const state = await api.getSessionState(teamDid);
397
+
398
+ // 为指定用户颁发通行证,拿到的 did 是永久 did,无需查询 connectedAccount
399
+ const currentUser = await api.getUser({ teamDid, user: { did: ownerDid } });
400
+ const walletDid = getWalletDid(currentUser);
401
+ const userDid = walletDid || ownerDid;
402
+ // 这里可能是为一个不存在的用户创建一个 passport,所以需要额外判断一下
403
+ if (currentUser) {
404
+ // auth0 账户不需要手动领取
405
+ if (walletDid !== ownerDid) {
406
+ const blocklet = await getBlocklet({ did: teamDid, states: api.states, dataDirs: api.dataDirs });
407
+ const nodeInfo = await api.node.read();
408
+ const blockletInfo = getBlockletInfo(blocklet, nodeInfo.sk);
409
+ const { wallet, passportColor, appUrl, name: issuerName } = blockletInfo;
410
+ const result = await createPassport({ role });
411
+ const vc = await createPassportVC({
412
+ issuerName,
413
+ issuerWallet: wallet,
414
+ issuerAvatarUrl: getAppAvatarUrl(appUrl),
415
+ ownerDid: userDid,
416
+ endpoint: getPassportStatusEndpoint({
417
+ baseUrl: joinURL(appUrl, WELLKNOWN_SERVICE_PATH_PREFIX),
418
+ userDid: ownerDid,
419
+ teamDid,
420
+ }),
421
+ ownerProfile: {
422
+ ...currentUser,
423
+ avatar: getUserAvatarUrl(appUrl, currentUser.avatar),
424
+ },
425
+ preferredColor: passportColor,
426
+ display,
427
+ passport: result.passport,
428
+ types: result.types,
429
+ purpose: teamDid === nodeInfo.did || isEmpty(result.types) ? 'login' : 'verification',
430
+ });
431
+
432
+ if (walletDid) {
433
+ sendPassportVcNotification({ userDid, appWallet: wallet, locale: 'en', vc });
434
+ }
435
+
436
+ // write passport to db
437
+ const passport = createUserPassport(vc, { role: role.name, display, source: PASSPORT_SOURCE.ISSUE });
438
+
439
+ const passports = upsertToPassports(currentUser.passports || [], passport);
440
+ await api.updateUser({
441
+ teamDid,
442
+ user: {
443
+ did: currentUser.did,
444
+ pk: currentUser.pk,
445
+ passports,
446
+ },
447
+ });
448
+
449
+ if (passport && api.passportAPI) {
450
+ await api.passportAPI.createPassportLog(teamDid, {
451
+ passportId: passport.id,
452
+ action: PASSPORT_LOG_ACTION.ISSUE,
453
+ operatorIp: context?.ip || '',
454
+ operatorUa: context?.ua || '',
455
+ operatorDid: ownerDid,
456
+ metadata: {
457
+ action: PASSPORT_ISSUE_ACTION.ISSUE_ON_AUTH0,
458
+ },
459
+ });
460
+ }
461
+
462
+ return undefined;
463
+ }
464
+ }
465
+ const { id } = await state.start({
466
+ type: 'passport-issuance',
467
+ key: userDid,
468
+ expireDate, // session expireDate
469
+ name: role.name,
470
+ title: role.title,
471
+ ownerDid: userDid,
472
+ teamDid,
473
+ display,
474
+ inviter: context?.user,
475
+ passportExpireTime,
476
+ });
477
+
478
+ logger.info('Create issuing passport session', { id, passport: name, ownerDid, teamDid });
479
+
480
+ return {
481
+ id,
482
+ name: role.name,
483
+ title: role.title,
484
+ expireDate: new Date(expireDate).toString(),
485
+ ownerDid: userDid,
486
+ teamDid,
487
+ display,
488
+ };
489
+ }
490
+
491
+ /**
492
+ * Get passport issuances
493
+ * @param {Object} api - TeamAPI instance
494
+ * @param {Object} params
495
+ * @param {string} params.teamDid - Team DID
496
+ * @param {string} params.ownerDid - Owner DID
497
+ * @returns {Promise<Array>}
498
+ */
499
+ async function getPassportIssuances(api, { teamDid, ownerDid }) {
500
+ const state = await api.getSessionState(teamDid);
501
+
502
+ const query = { type: 'passport-issuance' };
503
+
504
+ if (ownerDid) {
505
+ query.key = ownerDid;
506
+ }
507
+
508
+ const list = await state.find(query);
509
+
510
+ return list.map((d) => ({
511
+ id: d.id,
512
+ name: d.name,
513
+ title: d.title,
514
+ expireDate: new Date(d.expireDate).toString(),
515
+ ownerDid: d.ownerDid,
516
+ teamDid: d.teamDid,
517
+ display: d.display,
518
+ }));
519
+ }
520
+
521
+ /**
522
+ * Get passport issuance
523
+ * @param {Object} api - TeamAPI instance
524
+ * @param {Object} params
525
+ * @param {string} params.teamDid - Team DID
526
+ * @param {string} params.sessionId - Session ID
527
+ * @returns {Promise<Object>}
528
+ */
529
+ async function getPassportIssuance(api, { teamDid, sessionId }) {
530
+ const state = await api.getSessionState(teamDid);
531
+ const doc = await state.read(sessionId);
532
+ // FIXME: @zhanghan 指定被邀请者的邀请需要查询被邀请者的详细信息
533
+ return doc;
534
+ }
535
+
536
+ /**
537
+ * Process passport issuance
538
+ * @param {Object} api - TeamAPI instance
539
+ * @param {Object} params
540
+ * @param {string} params.teamDid - Team DID
541
+ * @param {string} params.sessionId - Session ID
542
+ * @returns {Promise<Object>}
543
+ */
544
+ async function processPassportIssuance(api, { teamDid, sessionId }) {
545
+ const state = await api.getSessionState(teamDid);
546
+
547
+ const session = await state.read(sessionId);
548
+
549
+ if (!session) {
550
+ throw new Error(`The passport issuance session does not exist: ${sessionId}`);
551
+ }
552
+
553
+ const { name, ownerDid, expireDate } = session;
554
+
555
+ if (Date.now() > expireDate) {
556
+ logger.error('The passport issuance session has expired', { sessionId, expireAt: new Date(expireDate) });
557
+ throw new Error(`The passport issuance session has expired: ${sessionId}`);
558
+ }
559
+
560
+ const roles = await api.getRoles({ teamDid });
561
+ if (!roles.find((x) => x.name === name)) {
562
+ throw new Error(`Passport does not exist: ${name}`);
563
+ }
564
+
565
+ await state.end(sessionId);
566
+
567
+ logger.info('The passport issuance session completed successfully', { sessionId, name, ownerDid });
568
+
569
+ return {
570
+ name,
571
+ ownerDid,
572
+ };
573
+ }
574
+
575
+ /**
576
+ * Delete passport issuance
577
+ * @param {Object} api - TeamAPI instance
578
+ * @param {Object} params
579
+ * @param {string} params.teamDid - Team DID
580
+ * @param {string} params.sessionId - Session ID
581
+ * @returns {Promise<boolean>}
582
+ */
583
+ async function deletePassportIssuance(api, { teamDid, sessionId }) {
584
+ if (!sessionId) {
585
+ throw new Error('SessionId cannot be empty');
586
+ }
587
+
588
+ const state = await api.getSessionState(teamDid);
589
+ await state.end(sessionId);
590
+
591
+ logger.info('Delete passport issuance session', { sessionId });
592
+
593
+ return true;
594
+ }
595
+
596
+ /**
597
+ * Config trusted passports
598
+ * @param {Object} api - TeamAPI instance
599
+ * @param {Object} params
600
+ * @param {string} params.teamDid - Team DID
601
+ * @param {Array} params.trustedPassports - Trusted passports
602
+ * @returns {Promise<void>}
603
+ */
604
+ async function configTrustedPassports(api, { teamDid, trustedPassports }) {
605
+ const value = await validateTrustedPassportIssuers(trustedPassports);
606
+ await api.teamManager.configTrustedPassports(teamDid, value);
607
+ if (!api.teamManager.isNodeTeam(teamDid)) {
608
+ api.emit(BlockletEvents.updated, { meta: { did: teamDid } });
609
+ }
610
+ }
611
+
612
+ /**
613
+ * Config trusted factories
614
+ * @param {Object} api - TeamAPI instance
615
+ * @param {Object} params
616
+ * @param {string} params.teamDid - Team DID
617
+ * @param {Array} params.trustedFactories - Trusted factories
618
+ * @returns {Promise<void>}
619
+ */
620
+ async function configTrustedFactories(api, { teamDid, trustedFactories }) {
621
+ const value = await validateTrustedFactories(trustedFactories);
622
+
623
+ const client = getChainClient(MAIN_CHAIN_ENDPOINT);
624
+ await Promise.all(
625
+ value.map(async (x) => {
626
+ const { state } = await client.getFactoryState({ address: x.factoryAddress });
627
+ if (!state) {
628
+ throw new Error(`NFT Collection ${x.factoryAddress} not found on ${MAIN_CHAIN_ENDPOINT}`);
629
+ }
630
+ })
631
+ );
632
+
633
+ await api.teamManager.configTrustedFactories(teamDid, value);
634
+ if (!api.teamManager.isNodeTeam(teamDid)) {
635
+ api.emit(BlockletEvents.updated, { meta: { did: teamDid } });
636
+ }
637
+ }
638
+
639
+ /**
640
+ * Config passport issuance
641
+ * @param {Object} api - TeamAPI instance
642
+ * @param {Object} params
643
+ * @param {string} params.teamDid - Team DID
644
+ * @param {boolean} params.enable - Enable
645
+ * @returns {Promise<void>}
646
+ */
647
+ async function configPassportIssuance(api, { teamDid, enable }) {
648
+ await api.teamManager.configPassportIssuance(teamDid, !!enable);
649
+
650
+ if (!api.teamManager.isNodeTeam(teamDid)) {
651
+ api.emit(BlockletEvents.updated, { meta: { did: teamDid } });
652
+ }
653
+ }
654
+
655
+ module.exports = {
656
+ sendPassportVcNotification,
657
+ validatePassportDisplay,
658
+ issuePassportToUser,
659
+ revokeUserPassport,
660
+ enableUserPassport,
661
+ removeUserPassport,
662
+ createPassportIssuance,
663
+ getPassportIssuances,
664
+ getPassportIssuance,
665
+ processPassportIssuance,
666
+ deletePassportIssuance,
667
+ configTrustedPassports,
668
+ configTrustedFactories,
669
+ configPassportIssuance,
670
+ };