@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.
- package/lib/api/team/access-key-manager.js +104 -0
- package/lib/api/team/invitation-manager.js +461 -0
- package/lib/api/team/notification-manager.js +189 -0
- package/lib/api/team/oauth-manager.js +60 -0
- package/lib/api/team/org-crud-manager.js +202 -0
- package/lib/api/team/org-manager.js +56 -0
- package/lib/api/team/org-member-manager.js +403 -0
- package/lib/api/team/org-query-manager.js +126 -0
- package/lib/api/team/org-resource-manager.js +186 -0
- package/lib/api/team/passport-manager.js +670 -0
- package/lib/api/team/rbac-manager.js +335 -0
- package/lib/api/team/session-manager.js +540 -0
- package/lib/api/team/store-manager.js +198 -0
- package/lib/api/team/tag-manager.js +230 -0
- package/lib/api/team/user-auth-manager.js +132 -0
- package/lib/api/team/user-manager.js +78 -0
- package/lib/api/team/user-query-manager.js +299 -0
- package/lib/api/team/user-social-manager.js +354 -0
- package/lib/api/team/user-update-manager.js +224 -0
- package/lib/api/team/verify-code-manager.js +161 -0
- package/lib/api/team.js +439 -3287
- package/lib/blocklet/manager/disk/auth-manager.js +68 -0
- package/lib/blocklet/manager/disk/backup-manager.js +288 -0
- package/lib/blocklet/manager/disk/cleanup-manager.js +157 -0
- package/lib/blocklet/manager/disk/component-manager.js +83 -0
- package/lib/blocklet/manager/disk/config-manager.js +191 -0
- package/lib/blocklet/manager/disk/controller-manager.js +64 -0
- package/lib/blocklet/manager/disk/delete-reset-manager.js +328 -0
- package/lib/blocklet/manager/disk/download-manager.js +96 -0
- package/lib/blocklet/manager/disk/env-config-manager.js +311 -0
- package/lib/blocklet/manager/disk/federated-manager.js +651 -0
- package/lib/blocklet/manager/disk/hook-manager.js +124 -0
- package/lib/blocklet/manager/disk/install-component-manager.js +95 -0
- package/lib/blocklet/manager/disk/install-core-manager.js +448 -0
- package/lib/blocklet/manager/disk/install-download-manager.js +313 -0
- package/lib/blocklet/manager/disk/install-manager.js +36 -0
- package/lib/blocklet/manager/disk/install-upgrade-manager.js +340 -0
- package/lib/blocklet/manager/disk/job-manager.js +467 -0
- package/lib/blocklet/manager/disk/lifecycle-manager.js +26 -0
- package/lib/blocklet/manager/disk/notification-manager.js +343 -0
- package/lib/blocklet/manager/disk/query-manager.js +562 -0
- package/lib/blocklet/manager/disk/settings-manager.js +507 -0
- package/lib/blocklet/manager/disk/start-manager.js +611 -0
- package/lib/blocklet/manager/disk/stop-restart-manager.js +292 -0
- package/lib/blocklet/manager/disk/update-manager.js +153 -0
- package/lib/blocklet/manager/disk.js +669 -5796
- package/lib/blocklet/manager/helper/blue-green-start-blocklet.js +5 -0
- package/lib/blocklet/manager/lock.js +18 -0
- package/lib/event/index.js +28 -24
- package/lib/util/blocklet/app-utils.js +192 -0
- package/lib/util/blocklet/blocklet-loader.js +258 -0
- package/lib/util/blocklet/config-manager.js +232 -0
- package/lib/util/blocklet/did-document.js +240 -0
- package/lib/util/blocklet/environment.js +555 -0
- package/lib/util/blocklet/health-check.js +449 -0
- package/lib/util/blocklet/install-utils.js +365 -0
- package/lib/util/blocklet/logo.js +57 -0
- package/lib/util/blocklet/meta-utils.js +269 -0
- package/lib/util/blocklet/port-manager.js +141 -0
- package/lib/util/blocklet/process-manager.js +504 -0
- package/lib/util/blocklet/runtime-info.js +105 -0
- package/lib/util/blocklet/validation.js +418 -0
- package/lib/util/blocklet.js +98 -3066
- package/lib/util/wallet-app-notification.js +40 -0
- 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
|
+
};
|