@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
package/lib/api/team.js
CHANGED
|
@@ -1,233 +1,36 @@
|
|
|
1
1
|
const { EventEmitter } = require('events');
|
|
2
|
-
const axios = require('@abtnode/util/lib/axios');
|
|
3
|
-
const pick = require('lodash/pick');
|
|
4
|
-
const omit = require('lodash/omit');
|
|
5
|
-
const defaults = require('lodash/defaults');
|
|
6
|
-
const isEmpty = require('lodash/isEmpty');
|
|
7
2
|
const throttle = require('lodash/throttle');
|
|
8
|
-
const cloneDeep = require('@abtnode/util/lib/deep-clone');
|
|
9
|
-
const { joinURL, withoutTrailingSlash } = require('ufo');
|
|
10
3
|
const { Op } = require('sequelize');
|
|
11
4
|
const dayjs = require('@abtnode/util/lib/dayjs');
|
|
12
5
|
const logger = require('@abtnode/logger')('@abtnode/core:api:team');
|
|
13
6
|
const pAll = require('p-all');
|
|
14
|
-
const {
|
|
15
|
-
ROLES,
|
|
16
|
-
genPermissionName,
|
|
17
|
-
PASSPORT_STATUS,
|
|
18
|
-
WELLKNOWN_SERVICE_PATH_PREFIX,
|
|
19
|
-
MAX_USER_PAGE_SIZE,
|
|
20
|
-
MAIN_CHAIN_ENDPOINT,
|
|
21
|
-
USER_AVATAR_URL_PREFIX,
|
|
22
|
-
SESSION_TTL,
|
|
23
|
-
EVENTS,
|
|
24
|
-
NOTIFICATION_SEND_CHANNEL,
|
|
25
|
-
USER_SESSION_STATUS,
|
|
26
|
-
WEBHOOK_CONSECUTIVE_FAILURES_THRESHOLD,
|
|
27
|
-
} = require('@abtnode/constant');
|
|
28
|
-
const { isValid: isValidDid } = require('@arcblock/did');
|
|
29
|
-
const { BlockletEvents, TeamEvents, BlockletInternalEvents } = require('@blocklet/constant');
|
|
30
|
-
const { sendToUser } = require('@blocklet/sdk/lib/util/send-notification');
|
|
31
|
-
const {
|
|
32
|
-
createPassportVC,
|
|
33
|
-
createPassport,
|
|
34
|
-
upsertToPassports,
|
|
35
|
-
createUserPassport,
|
|
36
|
-
} = require('@abtnode/auth/lib/passport');
|
|
37
|
-
const { formatError } = require('@blocklet/error');
|
|
38
|
-
const { getPassportStatusEndpoint, getApplicationInfo } = require('@abtnode/auth/lib/auth');
|
|
39
|
-
const {
|
|
40
|
-
callFederated,
|
|
41
|
-
getFederatedMaster,
|
|
42
|
-
findFederatedSite,
|
|
43
|
-
getUserAvatarUrl: getFederatedUserAvatarUrl,
|
|
44
|
-
syncFederated,
|
|
45
|
-
} = require('@abtnode/auth/lib/util/federated');
|
|
46
|
-
const { hasActiveOwnerPassport } = require('@abtnode/util/lib/passport');
|
|
47
|
-
const { getBlockletInfo } = require('@blocklet/meta/lib/info');
|
|
48
|
-
const { getUserAvatarUrl, getAppAvatarUrl, extractUserAvatar, getAvatarByUrl } = require('@abtnode/util/lib/user');
|
|
49
|
-
const { getChainClient } = require('@abtnode/util/lib/get-chain-client');
|
|
50
|
-
const { getWalletDid } = require('@blocklet/meta/lib/did-utils');
|
|
51
|
-
const { generateRandomString } = require('@abtnode/models/lib/util');
|
|
52
|
-
const pMap = require('p-map');
|
|
7
|
+
const { PASSPORT_STATUS, USER_AVATAR_URL_PREFIX } = require('@abtnode/constant');
|
|
53
8
|
const isUrl = require('is-url');
|
|
54
|
-
const {
|
|
55
|
-
const
|
|
56
|
-
const { CustomError } = require('@blocklet/error');
|
|
57
|
-
const { getEmailServiceProvider } = require('@abtnode/auth/lib/email');
|
|
58
|
-
const md5 = require('@abtnode/util/lib/md5');
|
|
59
|
-
const { sanitizeTag } = require('@abtnode/util/lib/sanitize');
|
|
60
|
-
const { Joi } = require('@arcblock/validator');
|
|
61
|
-
const { getBlockletAppIdList } = require('@blocklet/meta/lib/util');
|
|
62
|
-
|
|
63
|
-
const { validateTrustedPassportIssuers } = require('../validators/trusted-passport');
|
|
64
|
-
const { validateTrustedFactories } = require('../validators/trusted-factory');
|
|
65
|
-
const { validateCreateRole, validateUpdateRole } = require('../validators/role');
|
|
66
|
-
const { validateCreatePermission, validateUpdatePermission } = require('../validators/permission');
|
|
9
|
+
const { extractUserAvatar, getAvatarByUrl } = require('@abtnode/util/lib/user');
|
|
10
|
+
const { TeamEvents } = require('@blocklet/constant');
|
|
67
11
|
const { BlockletRuntimeMonitor } = require('../monitor/blocklet-runtime-monitor');
|
|
68
12
|
const { getBlocklet } = require('../util/blocklet');
|
|
69
|
-
const StoreUtil = require('../util/store');
|
|
70
13
|
const { profileSchema } = require('../validators/user');
|
|
71
|
-
const { passportDisplaySchema } = require('../validators/util');
|
|
72
|
-
const { validateUserRolePassport } = require('../util/validate-user-role-passport');
|
|
73
|
-
const { getOrgInviteLink, createOrgValidators, isOrgOwner, isAdmingPath } = require('../util/org');
|
|
74
|
-
const { createOrgInputSchema, updateOrgInputSchema } = require('../validators/org');
|
|
75
|
-
const { checkPushChannelAvailable, getNotificationPushState } = require('../util/notification');
|
|
76
|
-
|
|
77
|
-
const sanitizeUrl = (url) => {
|
|
78
|
-
if (!url) {
|
|
79
|
-
throw new Error('Registry URL should not be empty');
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return url.trim();
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const validateReservedRole = (role) => {
|
|
86
|
-
if (Object.values(ROLES).includes(role)) {
|
|
87
|
-
throw new Error(`The role ${role} is reserved`);
|
|
88
|
-
}
|
|
89
|
-
return true;
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const sendPassportVcNotification = ({ userDid, appWallet: wallet, locale, vc, notification }) => {
|
|
93
|
-
try {
|
|
94
|
-
const receiver = userDid;
|
|
95
|
-
const sender = { appDid: wallet.address, appSk: wallet.secretKey };
|
|
96
|
-
const message = { ...notification };
|
|
97
|
-
if (!message.title) {
|
|
98
|
-
message.title = {
|
|
99
|
-
en: 'Receive a Passport',
|
|
100
|
-
zh: '获得通行证',
|
|
101
|
-
}[locale];
|
|
102
|
-
}
|
|
103
|
-
if (!message.body) {
|
|
104
|
-
message.body = {
|
|
105
|
-
en: 'You got a passport',
|
|
106
|
-
zh: '你获得了一张通行证',
|
|
107
|
-
}[locale];
|
|
108
|
-
}
|
|
109
|
-
if (!message.attachments) {
|
|
110
|
-
message.attachments = [
|
|
111
|
-
{
|
|
112
|
-
type: 'vc',
|
|
113
|
-
data: {
|
|
114
|
-
credential: vc,
|
|
115
|
-
tag: vc.credentialSubject.passport.name,
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
];
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
sendToUser(receiver, message, sender, { channels: [NOTIFICATION_SEND_CHANNEL.WALLET] }).catch((error) => {
|
|
122
|
-
logger.error('Failed send passport vc to wallet', { error });
|
|
123
|
-
});
|
|
124
|
-
} catch (error) {
|
|
125
|
-
logger.error('Failed send passport vc to wallet', { error });
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
const formatTransferData = (data) => ({
|
|
130
|
-
transferId: data.id,
|
|
131
|
-
remark: data.remark || '',
|
|
132
|
-
expireDate: new Date(data.expireDate).toString(),
|
|
133
|
-
appDid: data.appDid,
|
|
134
|
-
status: data.status || '',
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
const taggingSchema = Joi.object({
|
|
138
|
-
tagId: Joi.number().required(),
|
|
139
|
-
taggableIds: Joi.array().items(Joi.string()).min(1).required().messages({
|
|
140
|
-
'any.required': 'Must specify tagging.taggableIds to create',
|
|
141
|
-
'array.base': 'taggableIds must be an array',
|
|
142
|
-
'array.min': 'taggableIds must contain at least one element',
|
|
143
|
-
}),
|
|
144
|
-
taggableType: Joi.string().required().messages({
|
|
145
|
-
'any.required': 'Must specify tagging.taggableType to create',
|
|
146
|
-
'string.base': 'taggableType must be a string',
|
|
147
|
-
}),
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
const validatePassportDisplay = async (role, display) => {
|
|
151
|
-
if (role.extra?.display === 'custom') {
|
|
152
|
-
if (!display) {
|
|
153
|
-
throw new Error(`display is required for custom passport: ${role.name}`);
|
|
154
|
-
}
|
|
155
|
-
const { error, value } = passportDisplaySchema.validate(display);
|
|
156
|
-
if (error) {
|
|
157
|
-
throw new Error(`display invalid for custom passport: ${formatError(error)}`);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (value.type === 'url') {
|
|
161
|
-
try {
|
|
162
|
-
const res = await axios.head(value.content, {
|
|
163
|
-
timeout: 8000,
|
|
164
|
-
maxRedirects: 8,
|
|
165
|
-
validateStatus: null,
|
|
166
|
-
headers: {
|
|
167
|
-
Accept: 'image/avif,image/webp,image/png,image/svg+xml,image/*,*/*;q=0.8',
|
|
168
|
-
'User-Agent': '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
|
|
169
|
-
},
|
|
170
|
-
});
|
|
171
|
-
if (res.status >= 400) {
|
|
172
|
-
throw new Error(`Passport display is not accessible: ${res.statusText}`);
|
|
173
|
-
}
|
|
174
|
-
const contentType = res.headers['content-type'];
|
|
175
|
-
if (!contentType?.startsWith('image/')) {
|
|
176
|
-
throw new Error(`Passport display does not return valid image: ${contentType}`);
|
|
177
|
-
}
|
|
178
|
-
} catch (err) {
|
|
179
|
-
throw new Error(`Error validating passport display: ${err.message}`);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
14
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
[Op.lt]: new Date(now - sessionTtl * 1000),
|
|
202
|
-
},
|
|
203
|
-
status: {
|
|
204
|
-
[Op.ne]: USER_SESSION_STATUS.OFFLINE,
|
|
205
|
-
},
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
if (status === USER_SESSION_STATUS.OFFLINE) {
|
|
209
|
-
return {
|
|
210
|
-
status: USER_SESSION_STATUS.OFFLINE,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
return {};
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
// 关注取消关注相关接口
|
|
217
|
-
const USER_RELATION_ACTIONS = {
|
|
218
|
-
follow: (state, followerDid, userDid, options) => state.followUser(followerDid, userDid, options),
|
|
219
|
-
unfollow: (state, followerDid, userDid, options) => state.unfollowUser(followerDid, userDid, options),
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
// 获取用户相关接口
|
|
223
|
-
const USER_RELATION_QUERIES = {
|
|
224
|
-
followers: (state, userDid, options, context) => state.getFollowers(userDid, options, context),
|
|
225
|
-
following: (state, userDid, options, context) => state.getFollowing(userDid, options, context),
|
|
226
|
-
};
|
|
15
|
+
// Import split modules
|
|
16
|
+
const tagManager = require('./team/tag-manager');
|
|
17
|
+
const notificationManager = require('./team/notification-manager');
|
|
18
|
+
const rbacManager = require('./team/rbac-manager');
|
|
19
|
+
const accessKeyManager = require('./team/access-key-manager');
|
|
20
|
+
const oauthManager = require('./team/oauth-manager');
|
|
21
|
+
const verifyCodeManager = require('./team/verify-code-manager');
|
|
22
|
+
const storeManager = require('./team/store-manager');
|
|
23
|
+
const sessionManager = require('./team/session-manager');
|
|
24
|
+
const userManager = require('./team/user-manager');
|
|
25
|
+
const invitationManager = require('./team/invitation-manager');
|
|
26
|
+
const passportManager = require('./team/passport-manager');
|
|
27
|
+
const orgManager = require('./team/org-manager');
|
|
28
|
+
|
|
29
|
+
// Re-export helper functions
|
|
30
|
+
const { validatePassportDisplay, sendPassportVcNotification } = passportManager;
|
|
227
31
|
|
|
228
32
|
class TeamAPI extends EventEmitter {
|
|
229
33
|
/**
|
|
230
|
-
*
|
|
231
34
|
* @param {object} states abtnode core StateDB
|
|
232
35
|
*/
|
|
233
36
|
constructor({ teamManager, states, dataDirs, nodeAPI, passportAPI, passportIssueQueue }) {
|
|
@@ -369,3308 +172,657 @@ class TeamAPI extends EventEmitter {
|
|
|
369
172
|
};
|
|
370
173
|
}
|
|
371
174
|
|
|
372
|
-
//
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
const { error, value } = taggingSchema.validate(tagging, { stripUnknown: true });
|
|
377
|
-
if (error) {
|
|
378
|
-
throw new Error(error.details.map((d) => d.message).join('; '));
|
|
379
|
-
}
|
|
175
|
+
// ============ Tag Manager ============
|
|
176
|
+
createTagging({ teamDid, tagging }) {
|
|
177
|
+
return tagManager.createTagging(this, { teamDid, tagging });
|
|
178
|
+
}
|
|
380
179
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
180
|
+
deleteTagging({ teamDid, tagging }) {
|
|
181
|
+
return tagManager.deleteTagging(this, { teamDid, tagging });
|
|
182
|
+
}
|
|
384
183
|
|
|
385
|
-
|
|
386
|
-
});
|
|
184
|
+
getTag({ teamDid, tag }) {
|
|
185
|
+
return tagManager.getTag(this, { teamDid, tag });
|
|
186
|
+
}
|
|
387
187
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
return docs;
|
|
188
|
+
createTag({ teamDid, tag }, context = {}) {
|
|
189
|
+
return tagManager.createTag(this, { teamDid, tag }, context);
|
|
391
190
|
}
|
|
392
191
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
throw new Error(error.details.map((d) => d.message).join('; '));
|
|
397
|
-
}
|
|
192
|
+
updateTag({ teamDid, tag }, context = {}) {
|
|
193
|
+
return tagManager.updateTag(this, { teamDid, tag }, context);
|
|
194
|
+
}
|
|
398
195
|
|
|
399
|
-
|
|
196
|
+
deleteTag({ teamDid, tag, moveTo }) {
|
|
197
|
+
return tagManager.deleteTag(this, { teamDid, tag, moveTo });
|
|
198
|
+
}
|
|
400
199
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
});
|
|
200
|
+
getTags({ teamDid, paging }) {
|
|
201
|
+
return tagManager.getTags(this, { teamDid, paging });
|
|
202
|
+
}
|
|
405
203
|
|
|
406
|
-
|
|
407
|
-
|
|
204
|
+
// ============ Notification Manager ============
|
|
205
|
+
getNotification(params, context) {
|
|
206
|
+
return notificationManager.getNotification(this, params, context);
|
|
207
|
+
}
|
|
408
208
|
|
|
409
|
-
|
|
209
|
+
getNotificationById(params) {
|
|
210
|
+
return notificationManager.getNotificationById(this, params);
|
|
410
211
|
}
|
|
411
212
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const state = await this.getTagState(teamDid);
|
|
415
|
-
return state.findOne({ id: tag.id });
|
|
213
|
+
getNotificationsUnreadCount(params, context) {
|
|
214
|
+
return notificationManager.getNotificationsUnreadCount(this, params, context);
|
|
416
215
|
}
|
|
417
216
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
}
|
|
422
|
-
if (!tag.color || !tag.color.match(/^#[0-9a-f]{6}$/i)) {
|
|
423
|
-
throw new Error('Must specify hex encoded tag.color to create');
|
|
424
|
-
}
|
|
425
|
-
if (!tag.slug) {
|
|
426
|
-
throw new Error('Must specify tag.slug to create');
|
|
427
|
-
}
|
|
217
|
+
createNotificationReceiver(params) {
|
|
218
|
+
return notificationManager.createNotificationReceiver(this, params);
|
|
219
|
+
}
|
|
428
220
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
tagData.createdBy = context.user.did;
|
|
432
|
-
tagData.updatedBy = context.user.did;
|
|
433
|
-
const doc = await state.insert(tagData);
|
|
434
|
-
logger.info('tags created successfully', { teamDid, tag });
|
|
435
|
-
return doc;
|
|
221
|
+
markAllNotificationsAsRead(params, context) {
|
|
222
|
+
return notificationManager.markAllNotificationsAsRead(this, params, context);
|
|
436
223
|
}
|
|
437
224
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
}
|
|
225
|
+
updateNotificationStatus(params, context) {
|
|
226
|
+
return notificationManager.updateNotificationStatus(this, params, context);
|
|
227
|
+
}
|
|
442
228
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
229
|
+
readNotifications(params, context) {
|
|
230
|
+
return notificationManager.readNotifications(this, params, context);
|
|
231
|
+
}
|
|
446
232
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
logger.info('tags updated successfully', { teamDid, tag, result });
|
|
450
|
-
return state.findOne({ id: tag.id });
|
|
233
|
+
unreadNotifications(params, context) {
|
|
234
|
+
return notificationManager.unreadNotifications(this, params, context);
|
|
451
235
|
}
|
|
452
236
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
}
|
|
237
|
+
getNotificationSendLog(params) {
|
|
238
|
+
return notificationManager.getNotificationSendLog(this, params);
|
|
239
|
+
}
|
|
457
240
|
|
|
458
|
-
|
|
459
|
-
|
|
241
|
+
getNotificationComponents(params) {
|
|
242
|
+
return notificationManager.getNotificationComponents(this, params);
|
|
243
|
+
}
|
|
460
244
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
const existing = await taggingState.find({ tagId: moveTo, $or: checkPairs });
|
|
245
|
+
getReceivers(params) {
|
|
246
|
+
return notificationManager.getReceivers(this, params);
|
|
247
|
+
}
|
|
465
248
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
249
|
+
createNotification(payload) {
|
|
250
|
+
return this.teamManager.createNotification(payload);
|
|
251
|
+
}
|
|
469
252
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
logger.info('tag already at moveTo, will delete record', { teamDid, tag, record });
|
|
475
|
-
} else {
|
|
476
|
-
toUpdate.push(record);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
253
|
+
// ============ RBAC Manager ============
|
|
254
|
+
getRole({ teamDid, role }) {
|
|
255
|
+
return rbacManager.getRole(this, { teamDid, role });
|
|
256
|
+
}
|
|
479
257
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
taggingState
|
|
484
|
-
.update(
|
|
485
|
-
{ tagId: record.tagId, taggableId: record.taggableId, taggableType: record.taggableType },
|
|
486
|
-
{ $set: { tagId: moveTo } }
|
|
487
|
-
)
|
|
488
|
-
.then(() => {
|
|
489
|
-
logger.info('tag moved successfully', { teamDid, tag, record, moveTo });
|
|
490
|
-
})
|
|
491
|
-
)
|
|
492
|
-
);
|
|
493
|
-
}
|
|
258
|
+
createRole({ teamDid, name, description, title, childName, permissions = [], extra, orgId }) {
|
|
259
|
+
return rbacManager.createRole(this, { teamDid, name, description, title, childName, permissions, extra, orgId });
|
|
260
|
+
}
|
|
494
261
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
taggingState.remove(record).then(() => {
|
|
499
|
-
logger.info('tagging deleted successfully', { teamDid, tag, record });
|
|
500
|
-
})
|
|
501
|
-
)
|
|
502
|
-
);
|
|
503
|
-
}
|
|
504
|
-
}
|
|
262
|
+
updateRole({ teamDid, role, orgId }) {
|
|
263
|
+
return rbacManager.updateRole(this, { teamDid, role, orgId });
|
|
264
|
+
}
|
|
505
265
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
logger.info('tags deleted successfully', { teamDid, tag });
|
|
509
|
-
return doc;
|
|
266
|
+
getPermissions({ teamDid }) {
|
|
267
|
+
return rbacManager.getPermissions(this, { teamDid });
|
|
510
268
|
}
|
|
511
269
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
const result = await state.paginate({}, { createdAt: -1 }, { pageSize: 20, ...paging });
|
|
515
|
-
return { tags: result.list, paging: result.paging };
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
async sendNewMemberNotification(teamManager, user, nodeInfo) {
|
|
519
|
-
if (nodeInfo.nodeOwner && user.did !== nodeInfo.nodeOwner.did) {
|
|
520
|
-
const actionPath = '/team/members';
|
|
521
|
-
const action =
|
|
522
|
-
process.env.NODE_ENV === 'production' ? joinURL(nodeInfo.routing.adminPath, actionPath) : actionPath;
|
|
523
|
-
await teamManager.createNotification({
|
|
524
|
-
title: 'New member join',
|
|
525
|
-
description: `User with Name (${user.fullName}) and DID (${user.did}) has joined this server`,
|
|
526
|
-
entityType: 'node',
|
|
527
|
-
action,
|
|
528
|
-
entityId: user.did,
|
|
529
|
-
severity: 'success',
|
|
530
|
-
});
|
|
531
|
-
}
|
|
270
|
+
createPermission({ teamDid, name, description, extra }) {
|
|
271
|
+
return rbacManager.createPermission(this, { teamDid, name, description, extra });
|
|
532
272
|
}
|
|
533
273
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
const nodeInfo = await this.node.read();
|
|
274
|
+
updatePermission({ teamDid, permission }) {
|
|
275
|
+
return rbacManager.updatePermission(this, { teamDid, permission });
|
|
276
|
+
}
|
|
538
277
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
543
|
-
if (await state.count({ role: ROLES.OWNER })) {
|
|
544
|
-
throw new Error('The owner already exists');
|
|
545
|
-
}
|
|
546
|
-
}
|
|
278
|
+
grant({ teamDid, roleName, grantName }) {
|
|
279
|
+
return rbacManager.grant(this, { teamDid, roleName, grantName });
|
|
280
|
+
}
|
|
547
281
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
this.emit(TeamEvents.userUpdated, { teamDid, user: doc });
|
|
552
|
-
} else if (_action === 'add') {
|
|
553
|
-
this.createDefaultOrgForUser({ teamDid, user });
|
|
282
|
+
revoke({ teamDid, roleName, grantName }) {
|
|
283
|
+
return rbacManager.revoke(this, { teamDid, roleName, grantName });
|
|
284
|
+
}
|
|
554
285
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
286
|
+
updateGrants({ teamDid, roleName, grantNames }) {
|
|
287
|
+
return rbacManager.updateGrants(this, { teamDid, roleName, grantNames });
|
|
288
|
+
}
|
|
558
289
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
}
|
|
562
|
-
return doc;
|
|
290
|
+
deleteRole({ teamDid, name }) {
|
|
291
|
+
return rbacManager.deleteRole(this, { teamDid, name });
|
|
563
292
|
}
|
|
564
293
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
294
|
+
deletePermission({ teamDid, name }) {
|
|
295
|
+
return rbacManager.deletePermission(this, { teamDid, name });
|
|
296
|
+
}
|
|
568
297
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
connectedAccountDid: connectedAccount.did,
|
|
572
|
-
connectedAccountPk: connectedAccount.pk,
|
|
573
|
-
connectedAccountProvider: connectedAccount.provider,
|
|
574
|
-
});
|
|
575
|
-
return result;
|
|
298
|
+
getPermissionsByRole({ teamDid, role }) {
|
|
299
|
+
return rbacManager.getPermissionsByRole(this, { teamDid, role });
|
|
576
300
|
}
|
|
577
301
|
|
|
578
|
-
|
|
579
|
-
|
|
302
|
+
hasPermission({ teamDid, role, permission }) {
|
|
303
|
+
return rbacManager.hasPermission(this, { teamDid, role, permission });
|
|
304
|
+
}
|
|
580
305
|
|
|
581
|
-
|
|
582
|
-
|
|
306
|
+
refreshBlockletInterfacePermissions(blockletMeta) {
|
|
307
|
+
return rbacManager.refreshBlockletInterfacePermissions(this, blockletMeta);
|
|
308
|
+
}
|
|
583
309
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
310
|
+
// ============ Access Key Manager ============
|
|
311
|
+
getAccessKeys({ teamDid, paging }, context) {
|
|
312
|
+
return accessKeyManager.getAccessKeys(this, { teamDid, paging }, context);
|
|
313
|
+
}
|
|
587
314
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
}
|
|
315
|
+
getAccessKey({ teamDid, accessKeyId }, context) {
|
|
316
|
+
return accessKeyManager.getAccessKey(this, { teamDid, accessKeyId }, context);
|
|
317
|
+
}
|
|
592
318
|
|
|
593
|
-
|
|
319
|
+
createAccessKey({ teamDid, ...data }, context) {
|
|
320
|
+
return accessKeyManager.createAccessKey(this, { teamDid, ...data }, context);
|
|
321
|
+
}
|
|
594
322
|
|
|
595
|
-
|
|
323
|
+
updateAccessKey({ teamDid, ...data }, context) {
|
|
324
|
+
return accessKeyManager.updateAccessKey(this, { teamDid, ...data }, context);
|
|
325
|
+
}
|
|
596
326
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
}
|
|
327
|
+
deleteAccessKey({ teamDid, accessKeyId }, context) {
|
|
328
|
+
return accessKeyManager.deleteAccessKey(this, { teamDid, accessKeyId }, context);
|
|
329
|
+
}
|
|
601
330
|
|
|
602
|
-
|
|
331
|
+
refreshLastUsed({ teamDid, accessKeyId }, context) {
|
|
332
|
+
return accessKeyManager.refreshLastUsed(this, { teamDid, accessKeyId }, context);
|
|
333
|
+
}
|
|
603
334
|
|
|
604
|
-
|
|
335
|
+
verifyAccessKey({ teamDid, accessKey }) {
|
|
336
|
+
return accessKeyManager.verifyAccessKey(this, { teamDid, accessKey });
|
|
605
337
|
}
|
|
606
338
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
339
|
+
// ============ OAuth Manager ============
|
|
340
|
+
getOAuthClients({ teamDid, paging }) {
|
|
341
|
+
return oauthManager.getOAuthClients(this, { teamDid, paging });
|
|
342
|
+
}
|
|
610
343
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
delete query.includeUserSessions;
|
|
615
|
-
}
|
|
616
|
-
}
|
|
344
|
+
deleteOAuthClient({ teamDid, clientId }) {
|
|
345
|
+
return oauthManager.deleteOAuthClient(this, { teamDid, clientId });
|
|
346
|
+
}
|
|
617
347
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
348
|
+
createOAuthClient({ teamDid, input }, context) {
|
|
349
|
+
return oauthManager.createOAuthClient(this, { teamDid, input }, context);
|
|
350
|
+
}
|
|
621
351
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
352
|
+
updateOAuthClient({ teamDid, input }, context) {
|
|
353
|
+
return oauthManager.updateOAuthClient(this, { teamDid, input }, context);
|
|
354
|
+
}
|
|
625
355
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
list = await state.getUsersByDids({ query, dids }, context);
|
|
631
|
-
paging = {
|
|
632
|
-
total: list.length,
|
|
633
|
-
pageSize: dids.length,
|
|
634
|
-
pageCount: 1,
|
|
635
|
-
page: 1,
|
|
636
|
-
};
|
|
637
|
-
} else {
|
|
638
|
-
const doc = await state.getUsers({ query, sort, paging: { pageSize: 20, ...inputPaging } }, context);
|
|
639
|
-
list = doc.list;
|
|
640
|
-
paging = doc.paging;
|
|
641
|
-
}
|
|
356
|
+
// ============ Verify Code Manager ============
|
|
357
|
+
createVerifyCode({ teamDid, subject, scope, purpose }) {
|
|
358
|
+
return verifyCodeManager.createVerifyCode(this, { teamDid, subject, scope, purpose });
|
|
359
|
+
}
|
|
642
360
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
list,
|
|
647
|
-
async (x) => {
|
|
648
|
-
const { count: userSessionsCount } = await this.getUserSessionsCount({
|
|
649
|
-
teamDid,
|
|
650
|
-
query: {
|
|
651
|
-
userDid: x.did,
|
|
652
|
-
appPid: teamDid,
|
|
653
|
-
},
|
|
654
|
-
});
|
|
655
|
-
return userSessionsCount;
|
|
656
|
-
},
|
|
657
|
-
{ concurrency: 10 }
|
|
658
|
-
);
|
|
659
|
-
}
|
|
660
|
-
const users = list.map(
|
|
661
|
-
(d, index) => {
|
|
662
|
-
const pickData = pick(d, [
|
|
663
|
-
'did',
|
|
664
|
-
'pk',
|
|
665
|
-
'role',
|
|
666
|
-
'email',
|
|
667
|
-
'phone',
|
|
668
|
-
'fullName',
|
|
669
|
-
'approved',
|
|
670
|
-
'createdAt',
|
|
671
|
-
'updatedAt',
|
|
672
|
-
'passports',
|
|
673
|
-
'firstLoginAt',
|
|
674
|
-
'lastLoginAt',
|
|
675
|
-
'lastLoginIp',
|
|
676
|
-
'remark',
|
|
677
|
-
'avatar',
|
|
678
|
-
'locale',
|
|
679
|
-
'tags',
|
|
680
|
-
'url',
|
|
681
|
-
'inviter',
|
|
682
|
-
'generation',
|
|
683
|
-
'emailVerified',
|
|
684
|
-
'phoneVerified',
|
|
685
|
-
// oauth relate fields
|
|
686
|
-
'sourceProvider',
|
|
687
|
-
'sourceAppPid',
|
|
688
|
-
'connectedAccounts',
|
|
689
|
-
|
|
690
|
-
'metadata',
|
|
691
|
-
'isFollowing',
|
|
692
|
-
]);
|
|
361
|
+
consumeVerifyCode({ teamDid, code }) {
|
|
362
|
+
return verifyCodeManager.consumeVerifyCode(this, { teamDid, code });
|
|
363
|
+
}
|
|
693
364
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
userSessionsCount: userSessionsCountList[index] || 0,
|
|
698
|
-
};
|
|
699
|
-
}
|
|
365
|
+
issueVerifyCode({ teamDid, code }) {
|
|
366
|
+
return verifyCodeManager.issueVerifyCode(this, { teamDid, code });
|
|
367
|
+
}
|
|
700
368
|
|
|
701
|
-
|
|
702
|
-
);
|
|
703
|
-
return {
|
|
704
|
-
// FIXME: @zhanghan 这里做字段过滤的目的是?gql 本身已经对字段做了过滤了
|
|
705
|
-
users,
|
|
706
|
-
paging,
|
|
707
|
-
};
|
|
369
|
+
sendVerifyCode({ teamDid, code }) {
|
|
370
|
+
return verifyCodeManager.sendVerifyCode(this, { teamDid, code });
|
|
708
371
|
}
|
|
709
372
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
return state.count();
|
|
373
|
+
isSubjectVerified({ teamDid, subject }) {
|
|
374
|
+
return verifyCodeManager.isSubjectVerified(this, { teamDid, subject });
|
|
713
375
|
}
|
|
714
376
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
const state = await this.getUserState(teamDid);
|
|
718
|
-
const names = ['$all', ...roles.filter((x) => !x.orgId).map((x) => x.name), '$none', '$blocked'];
|
|
719
|
-
return Promise.all(
|
|
720
|
-
names.map(async (name) => {
|
|
721
|
-
const count = await state.countByPassport({ name, status: PASSPORT_STATUS.VALID });
|
|
722
|
-
return { key: name, value: count };
|
|
723
|
-
})
|
|
724
|
-
);
|
|
377
|
+
isSubjectIssued({ teamDid, userDid, subject }) {
|
|
378
|
+
return verifyCodeManager.isSubjectIssued(this, { teamDid, userDid, subject });
|
|
725
379
|
}
|
|
726
380
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
* @param {{
|
|
730
|
-
* teamDid: string,
|
|
731
|
-
* user: {
|
|
732
|
-
* did: string,
|
|
733
|
-
* },
|
|
734
|
-
* options: Record<string, any>
|
|
735
|
-
* }} { teamDid, user, options = {} }
|
|
736
|
-
* @return {Promise<import('@abtnode/models').UserState>}
|
|
737
|
-
* @memberof TeamAPI
|
|
738
|
-
*/
|
|
739
|
-
async getUser({ teamDid, user, options = {} }) {
|
|
740
|
-
const state = await this.getUserState(teamDid);
|
|
741
|
-
return state.getUser(user.did, options);
|
|
381
|
+
isSubjectSent({ teamDid, subject }) {
|
|
382
|
+
return verifyCodeManager.isSubjectSent(this, { teamDid, subject });
|
|
742
383
|
}
|
|
743
384
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
return state.getConnectedAccount(id);
|
|
385
|
+
getVerifyCode({ teamDid, code, id }) {
|
|
386
|
+
return verifyCodeManager.getVerifyCode(this, { teamDid, code, id });
|
|
747
387
|
}
|
|
748
388
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
return state.isEmailUsed(email, verified, sourceProvider);
|
|
389
|
+
rotateSessionKey({ teamDid }) {
|
|
390
|
+
return verifyCodeManager.rotateSessionKey(this, { teamDid });
|
|
752
391
|
}
|
|
753
392
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
return
|
|
393
|
+
// ============ Store Manager ============
|
|
394
|
+
addStore({ teamDid, url, scope }, context) {
|
|
395
|
+
return storeManager.addStore(this, { teamDid, url, scope }, context);
|
|
757
396
|
}
|
|
758
397
|
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
return state.getUserByDid(userDid, attributes);
|
|
398
|
+
addEndpoint({ teamDid, url }, context) {
|
|
399
|
+
return storeManager.addEndpoint(this, { teamDid, url }, context);
|
|
762
400
|
}
|
|
763
401
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
return state.isUserValid(userDid);
|
|
402
|
+
deleteEndpoint({ teamDid, did, projectId }, context) {
|
|
403
|
+
return storeManager.deleteEndpoint(this, { teamDid, did, projectId }, context);
|
|
767
404
|
}
|
|
768
405
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
return state.isPassportValid(passportId);
|
|
406
|
+
existsStore({ teamDid, url, scope }, context) {
|
|
407
|
+
return storeManager.existsStore(this, { teamDid, url, scope }, context);
|
|
772
408
|
}
|
|
773
409
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
return state.isConnectedAccount(did);
|
|
410
|
+
deleteStore({ teamDid, url, projectId, scope }, context) {
|
|
411
|
+
return storeManager.deleteStore(this, { teamDid, url, projectId, scope }, context);
|
|
777
412
|
}
|
|
778
413
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
const ownerDids = await state.getOwnerDids();
|
|
784
|
-
if (ownerDids.length > 0) {
|
|
785
|
-
owner = { did: ownerDids[0] };
|
|
786
|
-
}
|
|
787
|
-
}
|
|
414
|
+
// ============ Session Manager ============
|
|
415
|
+
getUserSession(params) {
|
|
416
|
+
return sessionManager.getUserSession(this, params);
|
|
417
|
+
}
|
|
788
418
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
419
|
+
getUserSessions(params) {
|
|
420
|
+
return sessionManager.getUserSessions(this, params);
|
|
421
|
+
}
|
|
792
422
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
return full || owner;
|
|
423
|
+
getUserSessionsCount(params) {
|
|
424
|
+
return sessionManager.getUserSessionsCount(this, params);
|
|
796
425
|
}
|
|
797
426
|
|
|
798
|
-
|
|
799
|
-
|
|
427
|
+
getPassportById(params) {
|
|
428
|
+
return sessionManager.getPassportById(this, params);
|
|
429
|
+
}
|
|
800
430
|
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
`Invalid action: ${action}. Supported: ${Object.keys(USER_RELATION_ACTIONS).join(', ')}`
|
|
805
|
-
);
|
|
806
|
-
}
|
|
807
|
-
let result;
|
|
808
|
-
try {
|
|
809
|
-
const state = await this.getUserState(teamDid);
|
|
810
|
-
result = await USER_RELATION_ACTIONS[action](state, targetDid, userDid, options);
|
|
811
|
-
} catch (error) {
|
|
812
|
-
logger.error(`Failed to ${action} user`, {
|
|
813
|
-
followerDid: targetDid,
|
|
814
|
-
userDid,
|
|
815
|
-
error,
|
|
816
|
-
});
|
|
817
|
-
throw error;
|
|
818
|
-
}
|
|
431
|
+
getPassportFromFederated(params) {
|
|
432
|
+
return sessionManager.getPassportFromFederated(this, params);
|
|
433
|
+
}
|
|
819
434
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
await this.createNotification({
|
|
824
|
-
teamDid,
|
|
825
|
-
receiver: userDid,
|
|
826
|
-
title: 'Followed',
|
|
827
|
-
description: `<${currentUser.fullName}(did:abt:${currentUser.did})> followed you`,
|
|
828
|
-
activity: {
|
|
829
|
-
type: 'follow',
|
|
830
|
-
actor: followerDid,
|
|
831
|
-
target: {
|
|
832
|
-
type: 'user',
|
|
833
|
-
id: followerDid,
|
|
834
|
-
},
|
|
835
|
-
},
|
|
836
|
-
entityId: teamDid,
|
|
837
|
-
source: 'system',
|
|
838
|
-
severity: 'success',
|
|
839
|
-
});
|
|
840
|
-
}
|
|
841
|
-
} catch (error) {
|
|
842
|
-
// 消息创建失败,不影响 follow 操作
|
|
843
|
-
logger.error('Failed to create notification', { error });
|
|
844
|
-
}
|
|
435
|
+
upsertUserSession(params) {
|
|
436
|
+
return sessionManager.upsertUserSession(this, params);
|
|
437
|
+
}
|
|
845
438
|
|
|
846
|
-
|
|
439
|
+
syncUserSession(params) {
|
|
440
|
+
return sessionManager.syncUserSession(this, params);
|
|
847
441
|
}
|
|
848
442
|
|
|
849
|
-
|
|
850
|
-
|
|
443
|
+
logoutUser(params) {
|
|
444
|
+
return sessionManager.logoutUser(this, params);
|
|
445
|
+
}
|
|
851
446
|
|
|
852
|
-
|
|
447
|
+
// ============ User Manager ============
|
|
448
|
+
loginUser(params, context) {
|
|
449
|
+
return userManager.loginUser(this, params, context);
|
|
450
|
+
}
|
|
853
451
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
452
|
+
disconnectUserAccount(params, context) {
|
|
453
|
+
return userManager.disconnectUserAccount(this, params, context);
|
|
454
|
+
}
|
|
857
455
|
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
456
|
+
addUser(params, context) {
|
|
457
|
+
return userManager.addUser(this, params, context);
|
|
458
|
+
}
|
|
861
459
|
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
state,
|
|
865
|
-
userDid,
|
|
866
|
-
{
|
|
867
|
-
paging,
|
|
868
|
-
sort,
|
|
869
|
-
...options,
|
|
870
|
-
},
|
|
871
|
-
context
|
|
872
|
-
);
|
|
873
|
-
|
|
874
|
-
list.forEach((item) => {
|
|
875
|
-
const { user } = item;
|
|
876
|
-
if (user && user?.avatar && user?.avatar?.startsWith(USER_AVATAR_URL_PREFIX)) {
|
|
877
|
-
user.avatar = `${getFederatedUserAvatarUrl(user.avatar, blocklet)}?imageFilter=resize&w=48&h=48`;
|
|
878
|
-
}
|
|
879
|
-
});
|
|
880
|
-
|
|
881
|
-
return {
|
|
882
|
-
data: list,
|
|
883
|
-
paging: resultPaging,
|
|
884
|
-
};
|
|
885
|
-
} catch (error) {
|
|
886
|
-
logger.error('Failed to get user follows', { error });
|
|
887
|
-
throw error;
|
|
888
|
-
}
|
|
460
|
+
getUsers(params, context) {
|
|
461
|
+
return userManager.getUsers(this, params, context);
|
|
889
462
|
}
|
|
890
463
|
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
const info = await this.node.read();
|
|
894
|
-
const prefix = process.env.NODE_ENV === 'production' ? info.routing.adminPath : '';
|
|
895
|
-
return state.getFollowStats({ userDids, teamDid, prefix, options }, context);
|
|
464
|
+
getUsersCount(params) {
|
|
465
|
+
return userManager.getUsersCount(this, params);
|
|
896
466
|
}
|
|
897
467
|
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
throw new CustomError(400, 'Follower DID is required');
|
|
901
|
-
}
|
|
902
|
-
const state = await this.getUserState(teamDid);
|
|
903
|
-
return state.isFollowing(followerDid, userDids);
|
|
468
|
+
getUsersCountPerRole(params) {
|
|
469
|
+
return userManager.getUsersCountPerRole(this, params);
|
|
904
470
|
}
|
|
905
471
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
if (paging?.pageSize > MAX_USER_PAGE_SIZE) {
|
|
909
|
-
throw new CustomError(400, `Length of users should not exceed ${MAX_USER_PAGE_SIZE} per page`);
|
|
910
|
-
}
|
|
911
|
-
const state = await this.getUserState(teamDid);
|
|
912
|
-
const { users, paging: resultPaging } = await state.getInvitees(userDid, { paging, sort, ...options }, context);
|
|
913
|
-
// 处理头像
|
|
914
|
-
const blocklet = await getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs });
|
|
915
|
-
users.forEach((user) => {
|
|
916
|
-
if (user && user?.avatar && user?.avatar?.startsWith(USER_AVATAR_URL_PREFIX)) {
|
|
917
|
-
user.avatar = `${getFederatedUserAvatarUrl(user.avatar, blocklet)}?imageFilter=resize&w=48&h=48`;
|
|
918
|
-
}
|
|
919
|
-
});
|
|
920
|
-
return {
|
|
921
|
-
users,
|
|
922
|
-
paging: resultPaging,
|
|
923
|
-
};
|
|
924
|
-
} catch (error) {
|
|
925
|
-
logger.error('Failed to get user invites', { teamDid, userDid, error });
|
|
926
|
-
throw error;
|
|
927
|
-
}
|
|
472
|
+
getUser(params, context) {
|
|
473
|
+
return userManager.getUser(this, params, context);
|
|
928
474
|
}
|
|
929
475
|
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
title: 'Your webhook has been temporarily disabled',
|
|
934
|
-
description: `Your Webhook has been temporarily disabled after ${WEBHOOK_CONSECUTIVE_FAILURES_THRESHOLD} consecutive failed message delivery attempts. Please check its availability and you can click the button below to reactivate it if needed.`,
|
|
935
|
-
failureCount: 'Number of failures',
|
|
936
|
-
failureUrl: 'Failed Webhook URL',
|
|
937
|
-
},
|
|
938
|
-
zh: {
|
|
939
|
-
title: '您的 webhook 已被暂时禁用',
|
|
940
|
-
description: `您的 Webhook 在连续 ${WEBHOOK_CONSECUTIVE_FAILURES_THRESHOLD} 次消息投递失败后已被暂时禁用。请检查其可用性,如果需要重新激活,请点击下方按钮。`,
|
|
941
|
-
failureCount: '失败次数',
|
|
942
|
-
failureUrl: '失败的 Webhook URL',
|
|
943
|
-
},
|
|
944
|
-
};
|
|
476
|
+
getConnectedAccount(params, context) {
|
|
477
|
+
return userManager.getConnectedAccount(this, params, context);
|
|
478
|
+
}
|
|
945
479
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
// server 的 webhook 页面地址
|
|
949
|
-
const actionPath = '/settings/integration';
|
|
950
|
-
let _action =
|
|
951
|
-
process.env.NODE_ENV === 'production' ? joinURL(nodeInfo.routing.adminPath, actionPath) : actionPath;
|
|
952
|
-
const channels = [NOTIFICATION_SEND_CHANNEL.WALLET, NOTIFICATION_SEND_CHANNEL.PUSH];
|
|
953
|
-
if (teamDid && !this.teamManager.isNodeTeam(teamDid)) {
|
|
954
|
-
channels.push(NOTIFICATION_SEND_CHANNEL.EMAIL);
|
|
955
|
-
_action = action || `${WELLKNOWN_SERVICE_PATH_PREFIX}/user/settings`; // 默认返回用户中心页面设置页面
|
|
956
|
-
}
|
|
957
|
-
await this.teamManager.createNotification({
|
|
958
|
-
title: notification[locale].title,
|
|
959
|
-
description: notification[locale].description,
|
|
960
|
-
attachments: [
|
|
961
|
-
{
|
|
962
|
-
type: 'section',
|
|
963
|
-
fields: [
|
|
964
|
-
{
|
|
965
|
-
type: 'text',
|
|
966
|
-
data: {
|
|
967
|
-
type: 'plain',
|
|
968
|
-
color: '#9397A1',
|
|
969
|
-
text: notification[locale].failureUrl,
|
|
970
|
-
},
|
|
971
|
-
},
|
|
972
|
-
{
|
|
973
|
-
type: 'text',
|
|
974
|
-
data: {
|
|
975
|
-
type: 'plain',
|
|
976
|
-
color: '#9397A1',
|
|
977
|
-
text: webhook.url,
|
|
978
|
-
},
|
|
979
|
-
},
|
|
980
|
-
{
|
|
981
|
-
type: 'text',
|
|
982
|
-
data: {
|
|
983
|
-
type: 'plain',
|
|
984
|
-
color: '#9397A1',
|
|
985
|
-
text: notification[locale].failureCount,
|
|
986
|
-
},
|
|
987
|
-
},
|
|
988
|
-
{
|
|
989
|
-
type: 'text',
|
|
990
|
-
data: {
|
|
991
|
-
type: 'plain',
|
|
992
|
-
color: '#9397A1',
|
|
993
|
-
text: `${WEBHOOK_CONSECUTIVE_FAILURES_THRESHOLD}`,
|
|
994
|
-
},
|
|
995
|
-
},
|
|
996
|
-
],
|
|
997
|
-
},
|
|
998
|
-
],
|
|
999
|
-
source: 'system',
|
|
1000
|
-
severity: 'warning',
|
|
1001
|
-
action: _action,
|
|
1002
|
-
channels,
|
|
1003
|
-
...(teamDid ? { teamDid } : {}),
|
|
1004
|
-
...(userDid ? { receiver: userDid } : {}),
|
|
1005
|
-
});
|
|
1006
|
-
} catch (error) {
|
|
1007
|
-
logger.error('Failed to create webhook disabled notification', { teamDid, userDid, webhook, error });
|
|
1008
|
-
}
|
|
480
|
+
isEmailUsed(params) {
|
|
481
|
+
return userManager.isEmailUsed(this, params);
|
|
1009
482
|
}
|
|
1010
483
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
return Promise.all(
|
|
1014
|
-
userDids.map(async (userDid) => {
|
|
1015
|
-
const webhookParams =
|
|
1016
|
-
consecutiveFailures === undefined
|
|
1017
|
-
? omit(
|
|
1018
|
-
{
|
|
1019
|
-
...webhook,
|
|
1020
|
-
enabled: enabled ?? webhook.enabled,
|
|
1021
|
-
},
|
|
1022
|
-
['consecutiveFailures']
|
|
1023
|
-
)
|
|
1024
|
-
: {
|
|
1025
|
-
...webhook,
|
|
1026
|
-
enabled: enabled ?? webhook.enabled,
|
|
1027
|
-
consecutiveFailures,
|
|
1028
|
-
};
|
|
1029
|
-
await state.updateWebhook(userDid, webhookParams, (updated) => {
|
|
1030
|
-
this.createWebhookDisabledNotification({ teamDid, userDid, webhook: updated });
|
|
1031
|
-
});
|
|
1032
|
-
})
|
|
1033
|
-
);
|
|
484
|
+
isPhoneUsed(params) {
|
|
485
|
+
return userManager.isPhoneUsed(this, params);
|
|
1034
486
|
}
|
|
1035
487
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
const updated = await state.updateUser(user.did, user);
|
|
1040
|
-
logger.info('user updated successfully', { teamDid, userDid: user.did });
|
|
1041
|
-
|
|
1042
|
-
this.emit(TeamEvents.userUpdated, { teamDid, user: updated });
|
|
1043
|
-
const kycChanged =
|
|
1044
|
-
(typeof user.emailVerified === 'boolean' && user.emailVerified !== exist.emailVerified) ||
|
|
1045
|
-
(typeof user.phoneVerified === 'boolean' && user.phoneVerified !== exist.phoneVerified);
|
|
1046
|
-
if (kycChanged) {
|
|
1047
|
-
logger.info('user updated with kycChanged', { teamDid, userDid: user.did });
|
|
1048
|
-
this.emit(TeamEvents.userPermissionUpdated, { teamDid, user: updated });
|
|
1049
|
-
}
|
|
488
|
+
getUserByDid(params) {
|
|
489
|
+
return userManager.getUserByDid(this, params);
|
|
490
|
+
}
|
|
1050
491
|
|
|
1051
|
-
|
|
492
|
+
isUserValid(params) {
|
|
493
|
+
return userManager.isUserValid(this, params);
|
|
1052
494
|
}
|
|
1053
495
|
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
}
|
|
496
|
+
isPassportValid(params) {
|
|
497
|
+
return userManager.isPassportValid(this, params);
|
|
498
|
+
}
|
|
1058
499
|
|
|
1059
|
-
|
|
500
|
+
isConnectedAccount(params) {
|
|
501
|
+
return userManager.isConnectedAccount(this, params);
|
|
1060
502
|
}
|
|
1061
503
|
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
const doc = await state.updateTags(did, tags);
|
|
1065
|
-
logger.info('user tags updated successfully', { teamDid, userDid: did, tags });
|
|
1066
|
-
this.emit(TeamEvents.userUpdated, { teamDid, user: doc });
|
|
1067
|
-
return doc;
|
|
504
|
+
getOwner(params) {
|
|
505
|
+
return userManager.getOwner(this, params);
|
|
1068
506
|
}
|
|
1069
507
|
|
|
1070
|
-
|
|
1071
|
-
|
|
508
|
+
userFollowAction(params, context) {
|
|
509
|
+
return userManager.userFollowAction(this, params, context);
|
|
510
|
+
}
|
|
1072
511
|
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
512
|
+
getUserFollows(params, context) {
|
|
513
|
+
return userManager.getUserFollows(this, params, context);
|
|
514
|
+
}
|
|
1076
515
|
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
516
|
+
getUserFollowStats(params, context) {
|
|
517
|
+
return userManager.getUserFollowStats(this, params, context);
|
|
518
|
+
}
|
|
1080
519
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
this.emit(TeamEvents.userRemoved, { teamDid, user: { did } });
|
|
520
|
+
checkFollowing(params) {
|
|
521
|
+
return userManager.checkFollowing(this, params);
|
|
522
|
+
}
|
|
1085
523
|
|
|
1086
|
-
|
|
524
|
+
getUserInvites(params) {
|
|
525
|
+
return userManager.getUserInvites(this, params);
|
|
1087
526
|
}
|
|
1088
527
|
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
528
|
+
updateUser(params, context) {
|
|
529
|
+
return userManager.updateUser(this, params, context);
|
|
530
|
+
}
|
|
1092
531
|
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
532
|
+
updateUserAddress(params, context) {
|
|
533
|
+
return userManager.updateUserAddress(this, params, context);
|
|
534
|
+
}
|
|
1096
535
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
536
|
+
updateUserTags(params, context) {
|
|
537
|
+
return userManager.updateUserTags(this, params, context);
|
|
538
|
+
}
|
|
1100
539
|
|
|
1101
|
-
|
|
540
|
+
removeUser(params, context) {
|
|
541
|
+
return userManager.removeUser(this, params, context);
|
|
542
|
+
}
|
|
1102
543
|
|
|
1103
|
-
|
|
544
|
+
updateUserApproval(params, context) {
|
|
545
|
+
return userManager.updateUserApproval(this, params, context);
|
|
546
|
+
}
|
|
1104
547
|
|
|
1105
|
-
|
|
548
|
+
updateWebHookState(params) {
|
|
549
|
+
return userManager.updateWebHookState(this, params);
|
|
550
|
+
}
|
|
1106
551
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
552
|
+
createWebhookDisabledNotification(params) {
|
|
553
|
+
return userManager.createWebhookDisabledNotification(this, params);
|
|
554
|
+
}
|
|
1110
555
|
|
|
1111
|
-
|
|
1112
|
-
|
|
556
|
+
async switchProfile({ teamDid, userDid, profile }) {
|
|
557
|
+
const state = await this.getUserState(teamDid);
|
|
558
|
+
// NOTICE: 这个 schema 没有对数据做任何 default 操作,所以没必要用 validate 之后的值
|
|
559
|
+
const { error } = profileSchema.validate({ ...profile, did: userDid });
|
|
560
|
+
if (error) {
|
|
561
|
+
throw new Error(error);
|
|
1113
562
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
563
|
+
const oldUser = await state.getUser(userDid);
|
|
564
|
+
if (!oldUser) {
|
|
565
|
+
throw new Error('User is not exist', { userDid, teamDid });
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const mergeData = { ...profile };
|
|
569
|
+
|
|
570
|
+
if (mergeData.avatar) {
|
|
571
|
+
if (mergeData.avatar.startsWith(USER_AVATAR_URL_PREFIX)) {
|
|
572
|
+
// pass
|
|
573
|
+
} else if (isUrl(mergeData.avatar)) {
|
|
574
|
+
try {
|
|
575
|
+
mergeData.avatar = await getAvatarByUrl(mergeData.avatar);
|
|
576
|
+
const blocklet = await getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs });
|
|
577
|
+
mergeData.avatar = await extractUserAvatar(mergeData.avatar, { dataDir: blocklet.env.dataDir });
|
|
578
|
+
} catch {
|
|
579
|
+
logger.error('Failed to convert external avatar', { teamDid, userDid, avatar: mergeData.avatar });
|
|
580
|
+
throw new Error('Failed to convert external avatar');
|
|
581
|
+
}
|
|
582
|
+
} else {
|
|
583
|
+
// 仅当 avatar 是 base64 时,对 avatar 进行处理
|
|
584
|
+
const regex = /^data:image\/(\w+);base64,/;
|
|
585
|
+
const match = regex.exec(mergeData.avatar);
|
|
586
|
+
if (match) {
|
|
587
|
+
const blocklet = await getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs });
|
|
588
|
+
mergeData.avatar = await extractUserAvatar(mergeData.avatar, { dataDir: blocklet.env.dataDir });
|
|
589
|
+
} else {
|
|
590
|
+
logger.error('Profile avatar is invalid', { teamDid, userDid, profile });
|
|
591
|
+
throw new Error('Profile avatar is invalid');
|
|
592
|
+
}
|
|
1137
593
|
}
|
|
1138
594
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
595
|
+
const doc = await state.updateUser(userDid, mergeData);
|
|
596
|
+
logger.info('User switch-profile successfully', { teamDid, userDid });
|
|
597
|
+
this.emit(TeamEvents.userUpdated, { teamDid, user: doc });
|
|
598
|
+
this.emit(TeamEvents.userProfileUpdated, { teamDid, user: doc });
|
|
599
|
+
return doc;
|
|
600
|
+
}
|
|
1141
601
|
|
|
1142
|
-
|
|
602
|
+
// ============ Invitation Manager ============
|
|
603
|
+
createMemberInvitation(params, context) {
|
|
604
|
+
return invitationManager.createMemberInvitation(this, params, context, { validatePassportDisplay });
|
|
1143
605
|
}
|
|
1144
606
|
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
* @param {string} options.userDid
|
|
1149
|
-
* @param {string} options.role
|
|
1150
|
-
* @param {object} [options.display]
|
|
1151
|
-
* @param {string} options.display.type
|
|
1152
|
-
* @param {string} options.display.content
|
|
1153
|
-
* @param {boolean} [options.notify=true]
|
|
1154
|
-
* @param {object} [options.notification]
|
|
1155
|
-
*/
|
|
1156
|
-
// 前端用上了,目前是在后端使用
|
|
1157
|
-
async issuePassportToUser(
|
|
1158
|
-
{ teamDid, userDid, role: roleName, display = null, notify = true, notification, action = '' },
|
|
1159
|
-
context = {}
|
|
1160
|
-
) {
|
|
1161
|
-
try {
|
|
1162
|
-
// eslint-disable-next-line no-param-reassign
|
|
1163
|
-
notification = JSON.parse(notification);
|
|
1164
|
-
} catch {
|
|
1165
|
-
logger.error('Failed to parse notification, use notification as undefined', { notification });
|
|
1166
|
-
}
|
|
1167
|
-
const { locale = 'en' } = context;
|
|
607
|
+
getInvitation({ teamDid, inviteId }) {
|
|
608
|
+
return invitationManager.getInvitation(this, { teamDid, inviteId });
|
|
609
|
+
}
|
|
1168
610
|
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
611
|
+
getInvitations({ teamDid, filter, orgId }, context) {
|
|
612
|
+
return invitationManager.getInvitations(this, { teamDid, filter, orgId }, context);
|
|
613
|
+
}
|
|
1172
614
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
615
|
+
deleteInvitation({ teamDid, inviteId }) {
|
|
616
|
+
return invitationManager.deleteInvitation(this, { teamDid, inviteId });
|
|
617
|
+
}
|
|
1176
618
|
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
619
|
+
checkInvitation({ teamDid, inviteId }) {
|
|
620
|
+
return invitationManager.checkInvitation(this, { teamDid, inviteId });
|
|
621
|
+
}
|
|
1180
622
|
|
|
1181
|
-
|
|
1182
|
-
|
|
623
|
+
closeInvitation(params) {
|
|
624
|
+
return invitationManager.closeInvitation(this, params);
|
|
625
|
+
}
|
|
1183
626
|
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
627
|
+
createTransferInvitation({ teamDid, remark }, context = {}) {
|
|
628
|
+
return invitationManager.createTransferInvitation(this, { teamDid, remark }, context, { validatePassportDisplay });
|
|
629
|
+
}
|
|
1187
630
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
const nodeInfo = await this.node.read();
|
|
1192
|
-
const {
|
|
1193
|
-
wallet,
|
|
1194
|
-
passportColor,
|
|
1195
|
-
appUrl,
|
|
1196
|
-
name: issuerName,
|
|
1197
|
-
} = await getApplicationInfo({
|
|
1198
|
-
teamDid,
|
|
1199
|
-
nodeInfo,
|
|
1200
|
-
node: {
|
|
1201
|
-
dataDirs: this.dataDirs,
|
|
1202
|
-
getSessionSecret: this.nodeAPI?.getSessionSecret?.bind(this.nodeAPI) || (() => 'abc'),
|
|
1203
|
-
getBlocklet: () => getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs }),
|
|
1204
|
-
},
|
|
1205
|
-
});
|
|
631
|
+
createTransferAppOwnerSession({ appDid, remark }) {
|
|
632
|
+
return invitationManager.createTransferAppOwnerSession(this, { appDid, remark });
|
|
633
|
+
}
|
|
1206
634
|
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
issuerName,
|
|
1210
|
-
issuerWallet: wallet,
|
|
1211
|
-
issuerAvatarUrl: getAppAvatarUrl(appUrl),
|
|
1212
|
-
ownerDid: userDid,
|
|
1213
|
-
endpoint: getPassportStatusEndpoint({
|
|
1214
|
-
baseUrl: joinURL(appUrl, WELLKNOWN_SERVICE_PATH_PREFIX),
|
|
1215
|
-
userDid: user.did,
|
|
1216
|
-
teamDid,
|
|
1217
|
-
}),
|
|
1218
|
-
ownerProfile: {
|
|
1219
|
-
...user,
|
|
1220
|
-
avatar: getUserAvatarUrl(appUrl, user.avatar),
|
|
1221
|
-
},
|
|
1222
|
-
preferredColor: passportColor,
|
|
1223
|
-
passport: result.passport,
|
|
1224
|
-
types: result.types,
|
|
1225
|
-
purpose: teamDid === nodeInfo.did || isEmpty(result.types) ? 'login' : 'verification',
|
|
1226
|
-
display,
|
|
1227
|
-
});
|
|
1228
|
-
|
|
1229
|
-
// write passport to db
|
|
1230
|
-
const passport = createUserPassport(vc, { role: role.name, display, source: PASSPORT_SOURCE.ISSUE });
|
|
1231
|
-
|
|
1232
|
-
user.passports = upsertToPassports(user.passports || [], passport);
|
|
1233
|
-
await this.updateUser({
|
|
1234
|
-
teamDid,
|
|
1235
|
-
user: {
|
|
1236
|
-
did: user.did,
|
|
1237
|
-
pk: user.pk,
|
|
1238
|
-
passports: user.passports,
|
|
1239
|
-
},
|
|
1240
|
-
});
|
|
1241
|
-
|
|
1242
|
-
if (passport && this.passportAPI) {
|
|
1243
|
-
await this.passportAPI.createPassportLog(teamDid, {
|
|
1244
|
-
passportId: passport.id,
|
|
1245
|
-
action: PASSPORT_LOG_ACTION.ISSUE,
|
|
1246
|
-
operatorDid: userDid,
|
|
1247
|
-
metadata: {
|
|
1248
|
-
action: action || 'issue-passport-to-user',
|
|
1249
|
-
},
|
|
1250
|
-
operatorIp: context?.ip || '',
|
|
1251
|
-
operatorUa: context?.ua || '',
|
|
1252
|
-
});
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
// send vc to wallet
|
|
1256
|
-
if (notify) {
|
|
1257
|
-
sendPassportVcNotification({ userDid, appWallet: wallet, locale, vc, notification });
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
return user;
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
async revokeUserPassport({ teamDid, userDid, passportId } = {}, context = {}) {
|
|
1264
|
-
if (!passportId) {
|
|
1265
|
-
throw new Error('Revoked passport should not be empty');
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
const state = await this.getUserState(teamDid);
|
|
1269
|
-
|
|
1270
|
-
const doc = await state.revokePassportById({ did: userDid, id: passportId });
|
|
1271
|
-
|
|
1272
|
-
// create passport log
|
|
1273
|
-
if (this.passportAPI) {
|
|
1274
|
-
await this.passportAPI.createPassportLog(teamDid, {
|
|
1275
|
-
passportId,
|
|
1276
|
-
action: PASSPORT_LOG_ACTION.REVOKE,
|
|
1277
|
-
operatorDid: userDid,
|
|
1278
|
-
metadata: {
|
|
1279
|
-
operator: await state.getUser(userDid).catch(() => null),
|
|
1280
|
-
},
|
|
1281
|
-
operatorIp: context?.ip || '',
|
|
1282
|
-
operatorUa: context?.ua || '',
|
|
1283
|
-
});
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
logger.info('user passport revoked successfully', { teamDid, userDid, passportId });
|
|
1287
|
-
|
|
1288
|
-
this.emit(TeamEvents.userUpdated, { teamDid, user: doc });
|
|
1289
|
-
this.emit(TeamEvents.userPermissionUpdated, { teamDid, user: doc });
|
|
1290
|
-
|
|
1291
|
-
return doc;
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
async enableUserPassport({ teamDid, userDid, passportId } = {}, context) {
|
|
1295
|
-
if (!passportId) {
|
|
1296
|
-
throw new Error('Passport should not be empty');
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
const state = await this.getUserState(teamDid);
|
|
1300
|
-
|
|
1301
|
-
const doc = await state.enablePassportById({ did: userDid, id: passportId });
|
|
1302
|
-
|
|
1303
|
-
if (this.passportAPI) {
|
|
1304
|
-
await this.passportAPI.createPassportLog(teamDid, {
|
|
1305
|
-
passportId,
|
|
1306
|
-
action: PASSPORT_LOG_ACTION.APPROVE,
|
|
1307
|
-
operatorDid: userDid,
|
|
1308
|
-
metadata: {
|
|
1309
|
-
operator: await state.getUser(userDid).catch(() => null),
|
|
1310
|
-
},
|
|
1311
|
-
operatorIp: context?.ip || '',
|
|
1312
|
-
operatorUa: context?.ua || '',
|
|
1313
|
-
});
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
logger.info('user passport enabled successfully', { teamDid, userDid, passportId });
|
|
1317
|
-
|
|
1318
|
-
this.emit(TeamEvents.userUpdated, { teamDid, user: doc });
|
|
1319
|
-
this.emit(TeamEvents.userPermissionUpdated, { teamDid, user: doc });
|
|
1320
|
-
|
|
1321
|
-
return doc;
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
async removeUserPassport({ passportId, userDid, teamDid }) {
|
|
1325
|
-
if (!passportId) {
|
|
1326
|
-
throw new Error('Revoked passport should not be empty');
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
const state = await this.getUserState(teamDid);
|
|
1330
|
-
|
|
1331
|
-
const doc = await state.removePassportById({ id: passportId });
|
|
1332
|
-
|
|
1333
|
-
logger.info('user passport remove successfully', { teamDid, userDid, passportId });
|
|
1334
|
-
|
|
1335
|
-
this.emit(TeamEvents.userUpdated, { teamDid, user: doc });
|
|
1336
|
-
this.emit(TeamEvents.userPermissionUpdated, { teamDid, user: doc });
|
|
1337
|
-
|
|
1338
|
-
return doc;
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
async switchProfile({ teamDid, userDid, profile }) {
|
|
1342
|
-
const state = await this.getUserState(teamDid);
|
|
1343
|
-
// NOTICE: 这个 schema 没有对数据做任何 default 操作,所以没必要用 validate 之后的值
|
|
1344
|
-
const { error } = profileSchema.validate({ ...profile, did: userDid });
|
|
1345
|
-
if (error) {
|
|
1346
|
-
throw new Error(error);
|
|
1347
|
-
}
|
|
1348
|
-
const oldUser = await state.getUser(userDid);
|
|
1349
|
-
if (!oldUser) {
|
|
1350
|
-
throw new Error('User is not exist', { userDid, teamDid });
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
const mergeData = { ...profile };
|
|
1354
|
-
|
|
1355
|
-
if (mergeData.avatar) {
|
|
1356
|
-
if (mergeData.avatar.startsWith(USER_AVATAR_URL_PREFIX)) {
|
|
1357
|
-
// pass
|
|
1358
|
-
} else if (isUrl(mergeData.avatar)) {
|
|
1359
|
-
try {
|
|
1360
|
-
mergeData.avatar = await getAvatarByUrl(mergeData.avatar);
|
|
1361
|
-
const blocklet = await getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs });
|
|
1362
|
-
mergeData.avatar = await extractUserAvatar(mergeData.avatar, { dataDir: blocklet.env.dataDir });
|
|
1363
|
-
} catch {
|
|
1364
|
-
logger.error('Failed to convert external avatar', { teamDid, userDid, avatar: mergeData.avatar });
|
|
1365
|
-
throw new Error('Failed to convert external avatar');
|
|
1366
|
-
}
|
|
1367
|
-
} else {
|
|
1368
|
-
// 仅当 avatar 是 base64 时,对 avatar 进行处理
|
|
1369
|
-
const regex = /^data:image\/(\w+);base64,/;
|
|
1370
|
-
const match = regex.exec(mergeData.avatar);
|
|
1371
|
-
if (match) {
|
|
1372
|
-
const blocklet = await getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs });
|
|
1373
|
-
mergeData.avatar = await extractUserAvatar(mergeData.avatar, { dataDir: blocklet.env.dataDir });
|
|
1374
|
-
} else {
|
|
1375
|
-
logger.error('Profile avatar is invalid', { teamDid, userDid, profile });
|
|
1376
|
-
throw new Error('Profile avatar is invalid');
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
const doc = await state.updateUser(userDid, mergeData);
|
|
1381
|
-
logger.info('User switch-profile successfully', { teamDid, userDid });
|
|
1382
|
-
this.emit(TeamEvents.userUpdated, { teamDid, user: doc });
|
|
1383
|
-
this.emit(TeamEvents.userProfileUpdated, { teamDid, user: doc });
|
|
1384
|
-
return doc;
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
|
-
// Invite member
|
|
1388
|
-
|
|
1389
|
-
/**
|
|
1390
|
-
* @type InvitationSession {
|
|
1391
|
-
* type: 'invite';
|
|
1392
|
-
* role: string;
|
|
1393
|
-
* remark: string;
|
|
1394
|
-
* expireDate: number;
|
|
1395
|
-
* inviter: {
|
|
1396
|
-
* did: string;
|
|
1397
|
-
* email?: string;
|
|
1398
|
-
* fullName?: string;
|
|
1399
|
-
* role?: string;
|
|
1400
|
-
* };
|
|
1401
|
-
* teamDid: string;
|
|
1402
|
-
* status: '' | 'success';
|
|
1403
|
-
* receiver: {
|
|
1404
|
-
* did: string;
|
|
1405
|
-
* };
|
|
1406
|
-
* sourceAppPid: string;
|
|
1407
|
-
* display: {
|
|
1408
|
-
* type: string;
|
|
1409
|
-
* tag: string;
|
|
1410
|
-
* };
|
|
1411
|
-
* }
|
|
1412
|
-
* @returns
|
|
1413
|
-
*/
|
|
1414
|
-
async createMemberInvitation(
|
|
1415
|
-
{
|
|
1416
|
-
teamDid,
|
|
1417
|
-
role: roleName,
|
|
1418
|
-
expireTime,
|
|
1419
|
-
remark,
|
|
1420
|
-
sourceAppPid,
|
|
1421
|
-
display = null,
|
|
1422
|
-
passportExpireTime = null,
|
|
1423
|
-
orgId = '', // 是否是邀请加入组织
|
|
1424
|
-
inviteUserDids = [], // 邀请用户列表
|
|
1425
|
-
},
|
|
1426
|
-
context
|
|
1427
|
-
) {
|
|
1428
|
-
await this.teamManager.checkEnablePassportIssuance(teamDid);
|
|
1429
|
-
|
|
1430
|
-
if (expireTime && expireTime <= 0) {
|
|
1431
|
-
throw new Error('Expire time must be greater than 0');
|
|
1432
|
-
}
|
|
1433
|
-
if (!roleName) {
|
|
1434
|
-
throw new Error('Role cannot be empty');
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
if (passportExpireTime && dayjs(passportExpireTime).isBefore(dayjs())) {
|
|
1438
|
-
throw new Error('Passport expire time must be greater than current time');
|
|
1439
|
-
}
|
|
1440
|
-
|
|
1441
|
-
const roles = await this.getRoles({ teamDid, orgId });
|
|
1442
|
-
const role = roles.find((r) => r.name === roleName);
|
|
1443
|
-
if (!role) {
|
|
1444
|
-
throw new Error(`Role does not exist: ${roleName}`);
|
|
1445
|
-
}
|
|
1446
|
-
await validatePassportDisplay(role, display);
|
|
1447
|
-
|
|
1448
|
-
if (remark && remark.length > 50) {
|
|
1449
|
-
throw new Error('Remark length should NOT be more than 50 characters');
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
const { user } = context;
|
|
1453
|
-
if (!user || !user.did) {
|
|
1454
|
-
throw new Error('Inviter does not exist');
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
// 如果邀请加入组织,则不验证 role 和 passport
|
|
1458
|
-
if (!orgId && (user?.role || '').startsWith('blocklet-')) {
|
|
1459
|
-
const userInfo = await this.getUser({ teamDid, user });
|
|
1460
|
-
validateUserRolePassport({
|
|
1461
|
-
role: (user?.role || '').replace('blocklet-', ''),
|
|
1462
|
-
passports: userInfo?.passports || [],
|
|
1463
|
-
});
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
const expireDate = Date.now() + (expireTime || this.memberInviteExpireTime);
|
|
1467
|
-
const state = await this.getSessionState(teamDid);
|
|
1468
|
-
const { id: inviteId } = await state.start({
|
|
1469
|
-
type: 'invite',
|
|
1470
|
-
role: roleName,
|
|
1471
|
-
remark,
|
|
1472
|
-
expireDate,
|
|
1473
|
-
inviter: user,
|
|
1474
|
-
teamDid,
|
|
1475
|
-
orgId,
|
|
1476
|
-
inviteUserDids,
|
|
1477
|
-
sourceAppPid,
|
|
1478
|
-
display,
|
|
1479
|
-
passportExpireTime,
|
|
1480
|
-
});
|
|
1481
|
-
|
|
1482
|
-
logger.info('Create invite member', { role: roleName, user, inviteId, display });
|
|
1483
|
-
|
|
1484
|
-
return {
|
|
1485
|
-
inviteId,
|
|
1486
|
-
role: roleName,
|
|
1487
|
-
remark,
|
|
1488
|
-
expireDate: new Date(expireDate).toString(),
|
|
1489
|
-
inviter: user,
|
|
1490
|
-
teamDid,
|
|
1491
|
-
sourceAppPid,
|
|
1492
|
-
display,
|
|
1493
|
-
};
|
|
1494
|
-
}
|
|
1495
|
-
|
|
1496
|
-
async getInvitation({ teamDid, inviteId }) {
|
|
1497
|
-
if (!teamDid || !inviteId) {
|
|
1498
|
-
return null;
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
const state = await this.getSessionState(teamDid);
|
|
1502
|
-
const invitation = await state.read(inviteId);
|
|
1503
|
-
if (!invitation) {
|
|
1504
|
-
return null;
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
return {
|
|
1508
|
-
// eslint-disable-next-line no-underscore-dangle
|
|
1509
|
-
inviteId: invitation.id,
|
|
1510
|
-
role: invitation.role,
|
|
1511
|
-
remark: invitation.remark,
|
|
1512
|
-
expireDate: new Date(invitation.expireDate).toString(),
|
|
1513
|
-
inviter: invitation.inviter,
|
|
1514
|
-
teamDid: invitation.teamDid,
|
|
1515
|
-
status: invitation.status,
|
|
1516
|
-
receiver: invitation.receiver,
|
|
1517
|
-
sourceAppPid: invitation.sourceAppPid || null,
|
|
1518
|
-
display: invitation.display || null,
|
|
1519
|
-
orgId: invitation.orgId || null,
|
|
1520
|
-
inviteUserDids: invitation.inviteUserDids || [],
|
|
1521
|
-
passportExpireTime: invitation.passportExpireTime || null,
|
|
1522
|
-
};
|
|
1523
|
-
}
|
|
1524
|
-
|
|
1525
|
-
async getInvitations({ teamDid, filter, orgId = '' }, context = {}) {
|
|
1526
|
-
const state = await this.getSessionState(teamDid);
|
|
1527
|
-
let invitations = [];
|
|
1528
|
-
if (orgId) {
|
|
1529
|
-
const org = await this.getOrg({ teamDid, id: orgId }, context);
|
|
1530
|
-
const { user } = context;
|
|
1531
|
-
if (!org) {
|
|
1532
|
-
throw new CustomError(404, `Org does not exist: ${orgId}`);
|
|
1533
|
-
}
|
|
1534
|
-
if (org.ownerDid !== user?.did) {
|
|
1535
|
-
throw new CustomError(403, `You are not the owner of the org: ${orgId}`);
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
1538
|
-
const query = { type: 'invite' };
|
|
1539
|
-
if (orgId) {
|
|
1540
|
-
query['__data.orgId'] = orgId;
|
|
1541
|
-
} else {
|
|
1542
|
-
query.$or = [{ '__data.orgId': { $exists: false } }, { '__data.orgId': '' }];
|
|
1543
|
-
}
|
|
1544
|
-
invitations = await state.find(query);
|
|
1545
|
-
|
|
1546
|
-
return invitations.filter(filter || ((x) => x.status !== 'success')).map((d) => ({
|
|
1547
|
-
// eslint-disable-next-line no-underscore-dangle
|
|
1548
|
-
inviteId: d.id,
|
|
1549
|
-
role: d.role,
|
|
1550
|
-
remark: d.remark,
|
|
1551
|
-
expireDate: new Date(d.expireDate).toString(),
|
|
1552
|
-
inviter: d.inviter,
|
|
1553
|
-
teamDid: d.teamDid,
|
|
1554
|
-
status: d.status,
|
|
1555
|
-
receiver: d.receiver,
|
|
1556
|
-
sourceAppPid: d.sourceAppPid || null,
|
|
1557
|
-
display: d.display || null,
|
|
1558
|
-
orgId: d.orgId || null,
|
|
1559
|
-
inviteUserDids: d.inviteUserDids || [],
|
|
1560
|
-
passportExpireTime: d.passportExpireTime || null,
|
|
1561
|
-
}));
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
async deleteInvitation({ teamDid, inviteId }) {
|
|
1565
|
-
if (!inviteId) {
|
|
1566
|
-
throw new Error('InviteId cannot be empty');
|
|
1567
|
-
}
|
|
1568
|
-
|
|
1569
|
-
const state = await this.getSessionState(teamDid);
|
|
1570
|
-
await state.end(inviteId);
|
|
1571
|
-
|
|
1572
|
-
logger.info('Delete invite session', { inviteId });
|
|
1573
|
-
|
|
1574
|
-
return true;
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
async checkInvitation({ teamDid, inviteId }) {
|
|
1578
|
-
const state = await this.getSessionState(teamDid);
|
|
1579
|
-
|
|
1580
|
-
const invitation = await state.read(inviteId);
|
|
1581
|
-
|
|
1582
|
-
if (!invitation) {
|
|
1583
|
-
throw new Error(`The invitation does not exist: ${inviteId}`);
|
|
1584
|
-
}
|
|
1585
|
-
|
|
1586
|
-
const { role, expireDate, remark } = invitation;
|
|
1587
|
-
|
|
1588
|
-
if (Date.now() > expireDate) {
|
|
1589
|
-
logger.error('Invite id has expired', { inviteId, expireAt: new Date(expireDate) });
|
|
1590
|
-
|
|
1591
|
-
throw new Error(`The invitation has expired: ${inviteId}`);
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
|
-
const roles = await this.getRoles({ teamDid });
|
|
1595
|
-
const orgRoles = await this.getRoles({ teamDid, orgId: invitation.orgId });
|
|
1596
|
-
const allRoles = [...roles, ...orgRoles];
|
|
1597
|
-
if (!allRoles.some((r) => r.name === role)) {
|
|
1598
|
-
throw new Error(`Role does not exist: ${role}`);
|
|
1599
|
-
}
|
|
1600
|
-
|
|
1601
|
-
return {
|
|
1602
|
-
role,
|
|
1603
|
-
remark,
|
|
1604
|
-
};
|
|
1605
|
-
}
|
|
1606
|
-
|
|
1607
|
-
async closeInvitation({ teamDid, inviteId, status, receiver, isOrgInvite, timeout = 30 * 1000 }) {
|
|
1608
|
-
const state = await this.getSessionState(teamDid);
|
|
1609
|
-
|
|
1610
|
-
const invitation = await state.read(inviteId);
|
|
1611
|
-
|
|
1612
|
-
if (!invitation) {
|
|
1613
|
-
throw new Error(`The invitation does not exist: ${inviteId}`);
|
|
1614
|
-
}
|
|
1615
|
-
let shouldRemoveInviteSession = false;
|
|
1616
|
-
if (isOrgInvite && invitation?.inviteUserDids?.length > 0) {
|
|
1617
|
-
const unReceiverUserDids = invitation.inviteUserDids.filter((did) => did !== receiver.did);
|
|
1618
|
-
shouldRemoveInviteSession = unReceiverUserDids.length === 0;
|
|
1619
|
-
await state.update(inviteId, {
|
|
1620
|
-
receiver,
|
|
1621
|
-
inviteUserDids: unReceiverUserDids,
|
|
1622
|
-
...(shouldRemoveInviteSession ? { status } : {}),
|
|
1623
|
-
});
|
|
1624
|
-
} else {
|
|
1625
|
-
await state.update(inviteId, { status, receiver });
|
|
1626
|
-
shouldRemoveInviteSession = true;
|
|
1627
|
-
}
|
|
1628
|
-
|
|
1629
|
-
if (shouldRemoveInviteSession) {
|
|
1630
|
-
setTimeout(async () => {
|
|
1631
|
-
try {
|
|
1632
|
-
logger.info('Invitation session closed', { inviteId });
|
|
1633
|
-
await state.end(inviteId);
|
|
1634
|
-
} catch (error) {
|
|
1635
|
-
logger.error('close invitation failed', { error });
|
|
1636
|
-
}
|
|
1637
|
-
}, timeout);
|
|
1638
|
-
}
|
|
1639
|
-
}
|
|
1640
|
-
|
|
1641
|
-
// transfer
|
|
1642
|
-
|
|
1643
|
-
/**
|
|
1644
|
-
* this function is only used to create server owner transfer invitation
|
|
1645
|
-
*/
|
|
1646
|
-
createTransferInvitation({ teamDid, remark }, context = {}) {
|
|
1647
|
-
return this.createMemberInvitation(
|
|
1648
|
-
{ teamDid, expireTime: this.transferOwnerExpireTime, remark, role: 'owner' },
|
|
1649
|
-
context
|
|
1650
|
-
);
|
|
1651
|
-
}
|
|
1652
|
-
|
|
1653
|
-
/**
|
|
1654
|
-
* this function is only used to create application owner transfer
|
|
1655
|
-
*/
|
|
1656
|
-
async createTransferAppOwnerSession({ appDid, remark = '' }) {
|
|
1657
|
-
const expireTime = this.transferOwnerExpireTime;
|
|
1658
|
-
|
|
1659
|
-
await this.teamManager.checkEnableTransferAppOwner(appDid);
|
|
1660
|
-
|
|
1661
|
-
if (remark && remark.length > 50) {
|
|
1662
|
-
throw new Error('Remark length should NOT be more than 50 characters');
|
|
1663
|
-
}
|
|
1664
|
-
|
|
1665
|
-
const expireDate = Date.now() + expireTime;
|
|
1666
|
-
const state = await this.getSessionState(appDid);
|
|
1667
|
-
const { id: transferId } = await state.start({
|
|
1668
|
-
type: 'transfer-app-owner',
|
|
1669
|
-
remark,
|
|
1670
|
-
expireDate,
|
|
1671
|
-
appDid,
|
|
1672
|
-
});
|
|
1673
|
-
|
|
1674
|
-
logger.info('createTransferAppOwnerSession', { appDid, transferId });
|
|
1675
|
-
|
|
1676
|
-
return {
|
|
1677
|
-
transferId,
|
|
1678
|
-
remark,
|
|
1679
|
-
expireDate: new Date(expireDate).toString(),
|
|
1680
|
-
appDid,
|
|
1681
|
-
status: 'created',
|
|
1682
|
-
};
|
|
1683
|
-
}
|
|
1684
|
-
|
|
1685
|
-
async getTransferAppOwnerSession({ appDid, transferId }) {
|
|
1686
|
-
if (!appDid || !transferId) {
|
|
1687
|
-
return null;
|
|
1688
|
-
}
|
|
1689
|
-
|
|
1690
|
-
const state = await this.getSessionState(appDid);
|
|
1691
|
-
|
|
1692
|
-
const transfer = await state.read(transferId);
|
|
1693
|
-
if (!transfer) {
|
|
1694
|
-
return null;
|
|
1695
|
-
}
|
|
1696
|
-
|
|
1697
|
-
return formatTransferData(transfer);
|
|
1698
|
-
}
|
|
1699
|
-
|
|
1700
|
-
async checkTransferAppOwnerSession({ appDid, transferId }) {
|
|
1701
|
-
const state = await this.getSessionState(appDid);
|
|
1702
|
-
|
|
1703
|
-
const transfer = await state.read(transferId);
|
|
1704
|
-
|
|
1705
|
-
if (!transfer) {
|
|
1706
|
-
throw new Error(`The transfer session does not exist: ${transferId}`);
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
const { expireDate } = transfer;
|
|
1710
|
-
|
|
1711
|
-
if (Date.now() > expireDate) {
|
|
1712
|
-
logger.error('Transfer session has expired', { transferId, expireAt: new Date(expireDate) });
|
|
1713
|
-
|
|
1714
|
-
throw new Error(`The transfer session has expired: ${transferId}`);
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
|
-
return formatTransferData(transfer);
|
|
1718
|
-
}
|
|
1719
|
-
|
|
1720
|
-
async closeTransferAppOwnerSession({ appPid, transferId, status, timeout = 30 * 1000 }) {
|
|
1721
|
-
const state = await this.getSessionState(appPid);
|
|
1722
|
-
|
|
1723
|
-
const session = await state.read(transferId);
|
|
1724
|
-
|
|
1725
|
-
if (!session) {
|
|
1726
|
-
throw new Error(`The transfer app owner session does not exist: ${transferId}`);
|
|
1727
|
-
}
|
|
1728
|
-
|
|
1729
|
-
await state.update(transferId, { status });
|
|
1730
|
-
|
|
1731
|
-
setTimeout(async () => {
|
|
1732
|
-
try {
|
|
1733
|
-
await state.end(transferId);
|
|
1734
|
-
logger.info('Transfer session closed', { transferId });
|
|
1735
|
-
} catch (error) {
|
|
1736
|
-
logger.error('close transfer session failed', { transferId, error });
|
|
1737
|
-
}
|
|
1738
|
-
}, timeout);
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
// Issue Passport
|
|
1742
|
-
/**
|
|
1743
|
-
* 为指定用户颁发 passport
|
|
1744
|
-
* @param {string} param.teamDid - 应用的 did
|
|
1745
|
-
* @param {string} param.ownerDid - 指定用户的 did
|
|
1746
|
-
* @param {string} param.name - passport 的 role
|
|
1747
|
-
* @returns {object} passport-info
|
|
1748
|
-
*/
|
|
1749
|
-
async createPassportIssuance({ teamDid, ownerDid, name, display = null, passportExpireTime = null }, context = {}) {
|
|
1750
|
-
if (!name) {
|
|
1751
|
-
throw new Error('Passport cannot be empty');
|
|
1752
|
-
}
|
|
1753
|
-
|
|
1754
|
-
if (!isValidDid(ownerDid)) {
|
|
1755
|
-
throw new Error(`ownerDid is invalid: ${ownerDid}`);
|
|
1756
|
-
}
|
|
1757
|
-
|
|
1758
|
-
await this.teamManager.checkEnablePassportIssuance(teamDid);
|
|
1759
|
-
|
|
1760
|
-
// passport name is same as role
|
|
1761
|
-
const roles = await this.getRoles({ teamDid });
|
|
1762
|
-
const role = roles.find((r) => r.name === name);
|
|
1763
|
-
if (!role) {
|
|
1764
|
-
throw new Error(`Passport does not exist: ${name}`);
|
|
1765
|
-
}
|
|
1766
|
-
if (role.extra?.display === 'custom') {
|
|
1767
|
-
if (!display) {
|
|
1768
|
-
throw new Error(`display is required for custom passport: ${name}`);
|
|
1769
|
-
}
|
|
1770
|
-
const { error } = passportDisplaySchema.validate(display);
|
|
1771
|
-
if (error) {
|
|
1772
|
-
throw new Error(`display invalid for custom passport: ${formatError(error)}`);
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
|
|
1776
|
-
await validatePassportDisplay(role, display);
|
|
1777
|
-
|
|
1778
|
-
const expireDate = Date.now() + this.memberInviteExpireTime;
|
|
1779
|
-
|
|
1780
|
-
const state = await this.getSessionState(teamDid);
|
|
1781
|
-
|
|
1782
|
-
// 为指定用户颁发通行证,拿到的 did 是永久 did,无需查询 connectedAccount
|
|
1783
|
-
const currentUser = await this.getUser({ teamDid, user: { did: ownerDid } });
|
|
1784
|
-
const walletDid = getWalletDid(currentUser);
|
|
1785
|
-
const userDid = walletDid || ownerDid;
|
|
1786
|
-
// 这里可能是为一个不存在的用户创建一个 passport,所以需要额外判断一下
|
|
1787
|
-
if (currentUser) {
|
|
1788
|
-
// auth0 账户不需要手动领取
|
|
1789
|
-
if (walletDid !== ownerDid) {
|
|
1790
|
-
const blocklet = await getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs });
|
|
1791
|
-
const nodeInfo = await this.node.read();
|
|
1792
|
-
const blockletInfo = getBlockletInfo(blocklet, nodeInfo.sk);
|
|
1793
|
-
const { wallet, passportColor, appUrl, name: issuerName } = blockletInfo;
|
|
1794
|
-
const result = await createPassport({ role });
|
|
1795
|
-
const vc = await createPassportVC({
|
|
1796
|
-
issuerName,
|
|
1797
|
-
issuerWallet: wallet,
|
|
1798
|
-
issuerAvatarUrl: getAppAvatarUrl(appUrl),
|
|
1799
|
-
ownerDid: userDid,
|
|
1800
|
-
endpoint: getPassportStatusEndpoint({
|
|
1801
|
-
baseUrl: joinURL(appUrl, WELLKNOWN_SERVICE_PATH_PREFIX),
|
|
1802
|
-
userDid: ownerDid,
|
|
1803
|
-
teamDid,
|
|
1804
|
-
}),
|
|
1805
|
-
ownerProfile: {
|
|
1806
|
-
...currentUser,
|
|
1807
|
-
avatar: getUserAvatarUrl(appUrl, currentUser.avatar),
|
|
1808
|
-
},
|
|
1809
|
-
preferredColor: passportColor,
|
|
1810
|
-
display,
|
|
1811
|
-
passport: result.passport,
|
|
1812
|
-
types: result.types,
|
|
1813
|
-
purpose: teamDid === nodeInfo.did || isEmpty(result.types) ? 'login' : 'verification',
|
|
1814
|
-
});
|
|
1815
|
-
|
|
1816
|
-
if (walletDid) {
|
|
1817
|
-
sendPassportVcNotification({ userDid, appWallet: wallet, locale: 'en', vc });
|
|
1818
|
-
}
|
|
1819
|
-
|
|
1820
|
-
// write passport to db
|
|
1821
|
-
const passport = createUserPassport(vc, { role: role.name, display, source: PASSPORT_SOURCE.ISSUE });
|
|
1822
|
-
|
|
1823
|
-
const passports = upsertToPassports(currentUser.passports || [], passport);
|
|
1824
|
-
await this.updateUser({
|
|
1825
|
-
teamDid,
|
|
1826
|
-
user: {
|
|
1827
|
-
did: currentUser.did,
|
|
1828
|
-
pk: currentUser.pk,
|
|
1829
|
-
passports,
|
|
1830
|
-
},
|
|
1831
|
-
});
|
|
1832
|
-
|
|
1833
|
-
if (passport && this.passportAPI) {
|
|
1834
|
-
await this.passportAPI.createPassportLog(teamDid, {
|
|
1835
|
-
passportId: passport.id,
|
|
1836
|
-
action: PASSPORT_LOG_ACTION.ISSUE,
|
|
1837
|
-
operatorIp: context?.ip || '',
|
|
1838
|
-
operatorUa: context?.ua || '',
|
|
1839
|
-
operatorDid: ownerDid,
|
|
1840
|
-
metadata: {
|
|
1841
|
-
action: PASSPORT_ISSUE_ACTION.ISSUE_ON_AUTH0,
|
|
1842
|
-
},
|
|
1843
|
-
});
|
|
1844
|
-
}
|
|
1845
|
-
|
|
1846
|
-
return undefined;
|
|
1847
|
-
}
|
|
1848
|
-
}
|
|
1849
|
-
const { id } = await state.start({
|
|
1850
|
-
type: 'passport-issuance',
|
|
1851
|
-
key: userDid,
|
|
1852
|
-
expireDate, // session expireDate
|
|
1853
|
-
name: role.name,
|
|
1854
|
-
title: role.title,
|
|
1855
|
-
ownerDid: userDid,
|
|
1856
|
-
teamDid,
|
|
1857
|
-
display,
|
|
1858
|
-
inviter: context?.user,
|
|
1859
|
-
passportExpireTime,
|
|
1860
|
-
});
|
|
1861
|
-
|
|
1862
|
-
logger.info('Create issuing passport session', { id, passport: name, ownerDid, teamDid });
|
|
1863
|
-
|
|
1864
|
-
return {
|
|
1865
|
-
id,
|
|
1866
|
-
name: role.name,
|
|
1867
|
-
title: role.title,
|
|
1868
|
-
expireDate: new Date(expireDate).toString(),
|
|
1869
|
-
ownerDid: userDid,
|
|
1870
|
-
teamDid,
|
|
1871
|
-
display,
|
|
1872
|
-
};
|
|
1873
|
-
}
|
|
1874
|
-
|
|
1875
|
-
async getPassportIssuances({ teamDid, ownerDid }) {
|
|
1876
|
-
const state = await this.getSessionState(teamDid);
|
|
1877
|
-
|
|
1878
|
-
const query = { type: 'passport-issuance' };
|
|
1879
|
-
|
|
1880
|
-
if (ownerDid) {
|
|
1881
|
-
query.key = ownerDid;
|
|
1882
|
-
}
|
|
1883
|
-
|
|
1884
|
-
const list = await state.find(query);
|
|
1885
|
-
|
|
1886
|
-
return list.map((d) => ({
|
|
1887
|
-
// eslint-disable-next-line no-underscore-dangle
|
|
1888
|
-
id: d.id,
|
|
1889
|
-
name: d.name,
|
|
1890
|
-
title: d.title,
|
|
1891
|
-
expireDate: new Date(d.expireDate).toString(),
|
|
1892
|
-
ownerDid: d.ownerDid,
|
|
1893
|
-
teamDid: d.teamDid,
|
|
1894
|
-
display: d.display,
|
|
1895
|
-
}));
|
|
1896
|
-
}
|
|
1897
|
-
|
|
1898
|
-
async getPassportIssuance({ teamDid, sessionId }) {
|
|
1899
|
-
const state = await this.getSessionState(teamDid);
|
|
1900
|
-
const doc = await state.read(sessionId);
|
|
1901
|
-
// FIXME: @zhanghan 指定被邀请者的邀请需要查询被邀请者的详细信息
|
|
1902
|
-
return doc;
|
|
1903
|
-
}
|
|
1904
|
-
|
|
1905
|
-
async processPassportIssuance({ teamDid, sessionId }) {
|
|
1906
|
-
const state = await this.getSessionState(teamDid);
|
|
1907
|
-
|
|
1908
|
-
const session = await state.read(sessionId);
|
|
1909
|
-
|
|
1910
|
-
if (!session) {
|
|
1911
|
-
throw new Error(`The passport issuance session does not exist: ${sessionId}`);
|
|
1912
|
-
}
|
|
1913
|
-
|
|
1914
|
-
const { name, ownerDid, expireDate } = session;
|
|
1915
|
-
|
|
1916
|
-
if (Date.now() > expireDate) {
|
|
1917
|
-
logger.error('The passport issuance session has expired', { sessionId, expireAt: new Date(expireDate) });
|
|
1918
|
-
throw new Error(`The passport issuance session has expired: ${sessionId}`);
|
|
1919
|
-
}
|
|
1920
|
-
|
|
1921
|
-
const roles = await this.getRoles({ teamDid });
|
|
1922
|
-
if (!roles.find((x) => x.name === name)) {
|
|
1923
|
-
throw new Error(`Passport does not exist: ${name}`);
|
|
1924
|
-
}
|
|
1925
|
-
|
|
1926
|
-
await state.end(sessionId);
|
|
1927
|
-
|
|
1928
|
-
logger.info('The passport issuance session completed successfully', { sessionId, name, ownerDid });
|
|
1929
|
-
|
|
1930
|
-
return {
|
|
1931
|
-
name,
|
|
1932
|
-
ownerDid,
|
|
1933
|
-
};
|
|
1934
|
-
}
|
|
1935
|
-
|
|
1936
|
-
async deletePassportIssuance({ teamDid, sessionId }) {
|
|
1937
|
-
if (!sessionId) {
|
|
1938
|
-
throw new Error('SessionId cannot be empty');
|
|
1939
|
-
}
|
|
1940
|
-
|
|
1941
|
-
const state = await this.getSessionState(teamDid);
|
|
1942
|
-
await state.end(sessionId);
|
|
1943
|
-
|
|
1944
|
-
logger.info('Delete passport issuance session', { sessionId });
|
|
1945
|
-
|
|
1946
|
-
return true;
|
|
1947
|
-
}
|
|
1948
|
-
|
|
1949
|
-
async configTrustedPassports({ teamDid, trustedPassports }) {
|
|
1950
|
-
const value = await validateTrustedPassportIssuers(trustedPassports);
|
|
1951
|
-
await this.teamManager.configTrustedPassports(teamDid, value);
|
|
1952
|
-
if (!this.teamManager.isNodeTeam(teamDid)) {
|
|
1953
|
-
this.emit(BlockletEvents.updated, { meta: { did: teamDid } });
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
|
|
1957
|
-
async configTrustedFactories({ teamDid, trustedFactories }) {
|
|
1958
|
-
const value = await validateTrustedFactories(trustedFactories);
|
|
1959
|
-
|
|
1960
|
-
const client = getChainClient(MAIN_CHAIN_ENDPOINT);
|
|
1961
|
-
await Promise.all(
|
|
1962
|
-
value.map(async (x) => {
|
|
1963
|
-
const { state } = await client.getFactoryState({ address: x.factoryAddress });
|
|
1964
|
-
if (!state) {
|
|
1965
|
-
throw new Error(`NFT Collection ${x.factoryAddress} not found on ${MAIN_CHAIN_ENDPOINT}`);
|
|
1966
|
-
}
|
|
1967
|
-
})
|
|
1968
|
-
);
|
|
1969
|
-
|
|
1970
|
-
await this.teamManager.configTrustedFactories(teamDid, value);
|
|
1971
|
-
if (!this.teamManager.isNodeTeam(teamDid)) {
|
|
1972
|
-
this.emit(BlockletEvents.updated, { meta: { did: teamDid } });
|
|
1973
|
-
}
|
|
1974
|
-
}
|
|
1975
|
-
|
|
1976
|
-
async configPassportIssuance({ teamDid, enable }) {
|
|
1977
|
-
await this.teamManager.configPassportIssuance(teamDid, !!enable);
|
|
1978
|
-
|
|
1979
|
-
if (!this.teamManager.isNodeTeam(teamDid)) {
|
|
1980
|
-
this.emit(BlockletEvents.updated, { meta: { did: teamDid } });
|
|
1981
|
-
}
|
|
1982
|
-
}
|
|
1983
|
-
|
|
1984
|
-
// Access Control
|
|
1985
|
-
|
|
1986
|
-
getRoles({ teamDid, orgId }) {
|
|
1987
|
-
return this.teamManager.getRoles(teamDid, orgId);
|
|
1988
|
-
}
|
|
1989
|
-
|
|
1990
|
-
async getRole({ teamDid, role: { name } = {} }) {
|
|
1991
|
-
if (!name) {
|
|
1992
|
-
throw new Error('role name is invalid');
|
|
1993
|
-
}
|
|
1994
|
-
const rbac = await this.getRBAC(teamDid);
|
|
1995
|
-
const role = await rbac.getRole(name);
|
|
1996
|
-
return role ? pick(role, ['name', 'grants', 'title', 'description', 'extra', 'orgId']) : null;
|
|
1997
|
-
}
|
|
1998
|
-
|
|
1999
|
-
async createRole({ teamDid, name, description, title, childName, permissions = [], extra: raw, orgId }) {
|
|
2000
|
-
logger.info('create role', { teamDid, name, description, childName, permissions, raw });
|
|
2001
|
-
const attrs = { name, title, description, childName, permissions, orgId };
|
|
2002
|
-
|
|
2003
|
-
if (raw) {
|
|
2004
|
-
try {
|
|
2005
|
-
attrs.extra = JSON.parse(raw);
|
|
2006
|
-
} catch (err) {
|
|
2007
|
-
throw new Error('extra should be a valid json string');
|
|
2008
|
-
}
|
|
2009
|
-
}
|
|
2010
|
-
|
|
2011
|
-
await validateCreateRole(pick(attrs, ['name', 'title', 'description', 'extra']));
|
|
2012
|
-
|
|
2013
|
-
validateReservedRole(name);
|
|
2014
|
-
|
|
2015
|
-
const rbac = await this.getRBAC(teamDid);
|
|
2016
|
-
|
|
2017
|
-
let role;
|
|
2018
|
-
try {
|
|
2019
|
-
role = await rbac.createRole(attrs);
|
|
2020
|
-
return pick(role, ['name', 'title', 'grants', 'description', 'extra', 'orgId']);
|
|
2021
|
-
} catch (err) {
|
|
2022
|
-
if (new RegExp(`Item ${name} already exists`).test(err.message)) {
|
|
2023
|
-
throw new Error(`Id ${name} already exists`);
|
|
2024
|
-
}
|
|
2025
|
-
throw err;
|
|
2026
|
-
}
|
|
2027
|
-
}
|
|
2028
|
-
|
|
2029
|
-
async updateRole({ teamDid, role: { name, title, description, extra: raw } = {}, orgId }) {
|
|
2030
|
-
logger.info('update role', { teamDid, name, title, description, raw });
|
|
2031
|
-
|
|
2032
|
-
const attrs = { name, title, description, orgId };
|
|
2033
|
-
|
|
2034
|
-
if (raw) {
|
|
2035
|
-
try {
|
|
2036
|
-
attrs.extra = JSON.parse(raw);
|
|
2037
|
-
} catch (err) {
|
|
2038
|
-
throw new Error('extra should be a valid json string');
|
|
2039
|
-
}
|
|
2040
|
-
}
|
|
2041
|
-
|
|
2042
|
-
await validateUpdateRole(pick(attrs, ['name', 'title', 'description', 'extra']));
|
|
2043
|
-
const rbac = await this.getRBAC(teamDid);
|
|
2044
|
-
const state = await rbac.updateRole(attrs);
|
|
2045
|
-
return pick(state, ['name', 'title', 'grants', 'description', 'extra', 'orgId']);
|
|
2046
|
-
}
|
|
2047
|
-
|
|
2048
|
-
async getPermissions({ teamDid }) {
|
|
2049
|
-
const rbac = await this.getRBAC(teamDid);
|
|
2050
|
-
const permissions = await rbac.getPermissions();
|
|
2051
|
-
return permissions.map((d) => {
|
|
2052
|
-
d.isProtected = !!(d.extra && d.extra.isProtected);
|
|
2053
|
-
return pick(d, ['name', 'description', 'isProtected']);
|
|
2054
|
-
});
|
|
2055
|
-
}
|
|
2056
|
-
|
|
2057
|
-
async createPermission({ teamDid, name, description }) {
|
|
2058
|
-
logger.info('create permissions', { teamDid, name });
|
|
2059
|
-
|
|
2060
|
-
await validateCreatePermission({ name, description });
|
|
2061
|
-
|
|
2062
|
-
const rbac = await this.getRBAC(teamDid);
|
|
2063
|
-
const added = await rbac.createPermission({ name, description });
|
|
2064
|
-
|
|
2065
|
-
return pick(added, ['name', 'description']);
|
|
2066
|
-
}
|
|
2067
|
-
|
|
2068
|
-
async updatePermission({ teamDid, permission: { name, description } = {} }) {
|
|
2069
|
-
logger.info('update permission', { teamDid, name, description });
|
|
2070
|
-
|
|
2071
|
-
await validateUpdatePermission({ name, description });
|
|
2072
|
-
|
|
2073
|
-
const rbac = await this.getRBAC(teamDid);
|
|
2074
|
-
const state = await rbac.updatePermission({ name, description });
|
|
2075
|
-
|
|
2076
|
-
return pick(state, ['name', 'description']);
|
|
2077
|
-
}
|
|
2078
|
-
|
|
2079
|
-
async grant({ teamDid, roleName, grantName }) {
|
|
2080
|
-
logger.info('grant', { teamDid, roleName, grantName });
|
|
2081
|
-
|
|
2082
|
-
const rbac = await this.getRBAC(teamDid);
|
|
2083
|
-
await rbac.grant(roleName, grantName);
|
|
2084
|
-
|
|
2085
|
-
return true;
|
|
2086
|
-
}
|
|
2087
|
-
|
|
2088
|
-
async revoke({ teamDid, roleName, grantName }) {
|
|
2089
|
-
logger.info('revoke', { teamDid, roleName, grantName });
|
|
2090
|
-
|
|
2091
|
-
const rbac = await this.getRBAC(teamDid);
|
|
2092
|
-
await rbac.revoke(roleName, grantName);
|
|
2093
|
-
|
|
2094
|
-
return true;
|
|
2095
|
-
}
|
|
2096
|
-
|
|
2097
|
-
async updateGrants({ teamDid, roleName, grantNames }) {
|
|
2098
|
-
logger.info('update grants', { teamDid, roleName, grantNames });
|
|
2099
|
-
|
|
2100
|
-
const rbac = await this.getRBAC(teamDid);
|
|
2101
|
-
const role = await rbac.updateGrants(roleName, grantNames);
|
|
2102
|
-
|
|
2103
|
-
return pick(role, ['name', 'grants', 'title', 'description']);
|
|
2104
|
-
}
|
|
2105
|
-
|
|
2106
|
-
async deleteRole({ teamDid, name }) {
|
|
2107
|
-
logger.info('delete role', { teamDid, name });
|
|
2108
|
-
|
|
2109
|
-
validateReservedRole(name);
|
|
2110
|
-
|
|
2111
|
-
const rbac = await this.getRBAC(teamDid);
|
|
2112
|
-
await rbac.removeRole(name);
|
|
2113
|
-
|
|
2114
|
-
return true;
|
|
2115
|
-
}
|
|
2116
|
-
|
|
2117
|
-
async deletePermission({ teamDid, name }) {
|
|
2118
|
-
logger.info('delete permission', { teamDid, name });
|
|
2119
|
-
|
|
2120
|
-
const rbac = await this.getRBAC(teamDid);
|
|
2121
|
-
|
|
2122
|
-
const permission = await rbac.getPermission(name);
|
|
2123
|
-
if (permission.extra && permission.extra.isProtected) {
|
|
2124
|
-
throw new Error(`The permission ${name} is reserved`);
|
|
2125
|
-
}
|
|
2126
|
-
|
|
2127
|
-
await rbac.removePermission(name);
|
|
2128
|
-
|
|
2129
|
-
return true;
|
|
2130
|
-
}
|
|
2131
|
-
|
|
2132
|
-
async getPermissionsByRole({ teamDid, role }) {
|
|
2133
|
-
const rbac = await this.getRBAC(teamDid);
|
|
2134
|
-
|
|
2135
|
-
const permissions = await rbac.getScope(role.name, true);
|
|
2136
|
-
|
|
2137
|
-
return permissions.map((d) => pick(d, ['name', 'description']));
|
|
2138
|
-
}
|
|
2139
|
-
|
|
2140
|
-
async hasPermission({ teamDid, role, permission }) {
|
|
2141
|
-
const rbac = await this.getRBAC(teamDid);
|
|
2142
|
-
|
|
2143
|
-
const has = await rbac.can(role, ...permission.split('_'));
|
|
2144
|
-
|
|
2145
|
-
return has;
|
|
2146
|
-
}
|
|
2147
|
-
|
|
2148
|
-
async refreshBlockletInterfacePermissions(blockletMeta) {
|
|
2149
|
-
const { did, interfaces } = blockletMeta;
|
|
2150
|
-
|
|
2151
|
-
const rbac = await this.getRBAC(did);
|
|
2152
|
-
|
|
2153
|
-
const oldPermissions = await this.getPermissions({ teamDid: did });
|
|
2154
|
-
|
|
2155
|
-
await Promise.all(
|
|
2156
|
-
(interfaces || []).map(async ({ name, type }) => {
|
|
2157
|
-
const permissionName = genPermissionName(name);
|
|
2158
|
-
if (type === 'web') {
|
|
2159
|
-
if (!oldPermissions.some((x) => x.name === permissionName)) {
|
|
2160
|
-
await rbac.createPermission({
|
|
2161
|
-
name: permissionName,
|
|
2162
|
-
description: `Access resources under the ${name} interface`,
|
|
2163
|
-
extra: {
|
|
2164
|
-
isProtected: true,
|
|
2165
|
-
},
|
|
2166
|
-
});
|
|
2167
|
-
}
|
|
2168
|
-
}
|
|
2169
|
-
})
|
|
2170
|
-
);
|
|
2171
|
-
}
|
|
2172
|
-
|
|
2173
|
-
// eslint-disable-next-line no-unused-vars
|
|
2174
|
-
async addStore({ teamDid, url, scope = '' }, context) {
|
|
2175
|
-
logger.info('add store', { teamDid, url, scope });
|
|
2176
|
-
|
|
2177
|
-
const sanitized = withoutTrailingSlash(await StoreUtil.getStoreUrl(url));
|
|
2178
|
-
const storeList = await this.teamManager.getStoreList(teamDid);
|
|
2179
|
-
const exists = storeList.some((x) => {
|
|
2180
|
-
if (x.url === sanitized) {
|
|
2181
|
-
if (!x.scope) {
|
|
2182
|
-
return true;
|
|
2183
|
-
}
|
|
2184
|
-
|
|
2185
|
-
return x.scope === scope;
|
|
2186
|
-
}
|
|
2187
|
-
|
|
2188
|
-
return false;
|
|
2189
|
-
});
|
|
2190
|
-
|
|
2191
|
-
if (exists) {
|
|
2192
|
-
throw new Error(`Blocklet Store already exist: ${sanitized}`);
|
|
2193
|
-
}
|
|
2194
|
-
|
|
2195
|
-
const store = await StoreUtil.getStoreMeta(sanitized);
|
|
2196
|
-
|
|
2197
|
-
storeList.push({ ...store, scope, url: sanitized, protected: false });
|
|
2198
|
-
|
|
2199
|
-
return this.teamManager.updateStoreList(teamDid, storeList);
|
|
2200
|
-
}
|
|
2201
|
-
|
|
2202
|
-
// 获取更多信息
|
|
2203
|
-
async addEndpoint({ teamDid, url }, context) {
|
|
2204
|
-
const userDid = context?.user?.did;
|
|
2205
|
-
const { endpoint, appPid, appName, appDescription } = await ensureServerEndpoint(url);
|
|
2206
|
-
const endpointList = await this.teamManager.getEndpointList(teamDid);
|
|
2207
|
-
|
|
2208
|
-
if (!appPid) {
|
|
2209
|
-
throw new Error('current url not blocklet url');
|
|
2210
|
-
}
|
|
2211
|
-
|
|
2212
|
-
if (!endpoint) {
|
|
2213
|
-
throw new Error('current url not blocklet url');
|
|
2214
|
-
}
|
|
2215
|
-
|
|
2216
|
-
const index = endpointList.findIndex((x) => x.id === appPid);
|
|
2217
|
-
if (index !== -1) {
|
|
2218
|
-
endpointList[index].url = url;
|
|
2219
|
-
} else {
|
|
2220
|
-
endpointList.push({ id: appPid, appName, appDescription, scope: userDid, url, endpoint, protected: false });
|
|
2221
|
-
}
|
|
2222
|
-
|
|
2223
|
-
return this.teamManager.updateEndpointList(teamDid, endpointList);
|
|
2224
|
-
}
|
|
2225
|
-
|
|
2226
|
-
async deleteEndpoint({ teamDid, did, projectId }, context) {
|
|
2227
|
-
const endpointList = await this.teamManager.getEndpointList(teamDid);
|
|
2228
|
-
|
|
2229
|
-
const index = endpointList.findIndex((x) => x.id === did);
|
|
2230
|
-
if (index === -1) {
|
|
2231
|
-
throw new Error(`Blocklet Endpoint does not exist: ${did}`);
|
|
2232
|
-
}
|
|
2233
|
-
endpointList.splice(index, 1);
|
|
2234
|
-
|
|
2235
|
-
if (projectId) {
|
|
2236
|
-
const { projectState } = await this.teamManager.getProjectState(teamDid);
|
|
2237
|
-
await projectState.deleteConnectedEndpoint({ projectId, endpointId: did, createdBy: context?.user?.did });
|
|
2238
|
-
}
|
|
2239
|
-
|
|
2240
|
-
return this.teamManager.updateEndpointList(teamDid, endpointList);
|
|
2241
|
-
}
|
|
2242
|
-
|
|
2243
|
-
async existsStore({ teamDid, url, scope }, context) {
|
|
2244
|
-
const sanitized = withoutTrailingSlash(await StoreUtil.getStoreUrl(url));
|
|
2245
|
-
const storeList = await this.teamManager.getStoreList(teamDid);
|
|
2246
|
-
const did = context?.user?.did;
|
|
2247
|
-
const exists = storeList.some((x) => {
|
|
2248
|
-
if (x.url !== sanitized) {
|
|
2249
|
-
return false;
|
|
2250
|
-
}
|
|
2251
|
-
if (x.scope && x.scope === did) {
|
|
2252
|
-
return true;
|
|
2253
|
-
}
|
|
2254
|
-
if (!x.scope || x.scope === 'studio') {
|
|
2255
|
-
return true;
|
|
2256
|
-
}
|
|
2257
|
-
return x.scope === scope;
|
|
2258
|
-
});
|
|
2259
|
-
|
|
2260
|
-
return exists;
|
|
2261
|
-
}
|
|
2262
|
-
|
|
2263
|
-
// eslint-disable-next-line no-unused-vars
|
|
2264
|
-
async deleteStore({ teamDid, url, projectId, scope }, context) {
|
|
2265
|
-
logger.info('delete registry', { url });
|
|
2266
|
-
const sanitized = sanitizeUrl(url);
|
|
2267
|
-
|
|
2268
|
-
const storeList = await this.teamManager.getStoreList(teamDid);
|
|
2269
|
-
let storeIndex;
|
|
2270
|
-
if (scope) {
|
|
2271
|
-
storeIndex = storeList.findIndex((x) => {
|
|
2272
|
-
if (x.protected) {
|
|
2273
|
-
return false;
|
|
2274
|
-
}
|
|
2275
|
-
return x.url === sanitized && x.scope === scope;
|
|
2276
|
-
});
|
|
2277
|
-
if (storeIndex === -1) {
|
|
2278
|
-
throw new Error(`No permission to delete the Store registry: ${sanitized}`);
|
|
2279
|
-
}
|
|
2280
|
-
} else {
|
|
2281
|
-
storeIndex = storeList.findIndex((x) => x.url === sanitized && !x.scope);
|
|
2282
|
-
if (storeIndex === -1) {
|
|
2283
|
-
throw new Error(`Store registry does not exist: ${sanitized}`);
|
|
2284
|
-
}
|
|
2285
|
-
}
|
|
2286
|
-
|
|
2287
|
-
if (projectId && scope) {
|
|
2288
|
-
const { projectState } = await this.teamManager.getProjectState(teamDid);
|
|
2289
|
-
const store = await StoreUtil.getStoreMeta(sanitized);
|
|
2290
|
-
await projectState.deleteConnectedStore({
|
|
2291
|
-
projectId,
|
|
2292
|
-
storeId: store.id,
|
|
2293
|
-
createdBy: scope === 'studio' ? null : context?.user?.did,
|
|
2294
|
-
});
|
|
2295
|
-
}
|
|
2296
|
-
storeList.splice(storeIndex, 1);
|
|
2297
|
-
return this.teamManager.updateStoreList(teamDid, storeList);
|
|
2298
|
-
}
|
|
2299
|
-
|
|
2300
|
-
createNotification = (payload) => {
|
|
2301
|
-
return this.teamManager.createNotification(payload);
|
|
2302
|
-
};
|
|
2303
|
-
|
|
2304
|
-
async getNotification({ teamDid, ...rest }, context) {
|
|
2305
|
-
const notificationState = await this.getNotificationState(teamDid);
|
|
2306
|
-
return notificationState.findPaginated({ teamDid, ...rest }, context);
|
|
2307
|
-
}
|
|
2308
|
-
|
|
2309
|
-
async getNotificationById({ teamDid, id }) {
|
|
2310
|
-
const notificationState = await this.getNotificationState(teamDid);
|
|
2311
|
-
const notification = await notificationState.findNotification({ id });
|
|
2312
|
-
return notification;
|
|
2313
|
-
}
|
|
2314
|
-
|
|
2315
|
-
async getNotificationsUnreadCount({ teamDid, receiver }, context) {
|
|
2316
|
-
const notificationState = await this.getNotificationState(teamDid);
|
|
2317
|
-
return notificationState.getUnreadCount({ receiver }, context);
|
|
2318
|
-
}
|
|
2319
|
-
|
|
2320
|
-
async createNotificationReceiver({ teamDid, receiverInstance, ...rest }, context) {
|
|
2321
|
-
const notificationState = await this.getNotificationState(teamDid);
|
|
2322
|
-
return notificationState.createNotificationReceiver({ receiverInstance, ...rest }, context);
|
|
2323
|
-
}
|
|
2324
|
-
|
|
2325
|
-
async markAllNotificationsAsRead({ teamDid, ...rest }, context) {
|
|
2326
|
-
const notificationState = await this.getNotificationState(teamDid);
|
|
2327
|
-
const result = await notificationState.makeAllAsRead(rest, context);
|
|
2328
|
-
const { numAffected, notificationIds, effectRows } = result;
|
|
2329
|
-
this.teamManager.emitReadNotification(teamDid, {
|
|
2330
|
-
readCount: numAffected,
|
|
2331
|
-
notificationIds,
|
|
2332
|
-
receiver: rest.receiver,
|
|
2333
|
-
effectRows,
|
|
2334
|
-
});
|
|
2335
|
-
return result;
|
|
2336
|
-
}
|
|
2337
|
-
|
|
2338
|
-
async updateNotificationStatus({ teamDid, receivers, notificationId, updates }) {
|
|
2339
|
-
const notificationState = await this.getNotificationState(teamDid);
|
|
2340
|
-
const result = await notificationState.updateStatus({ receivers, notificationId, updates, teamDid });
|
|
2341
|
-
// 发送状态更新的通知给前端,使用通用节流函数
|
|
2342
|
-
if (result > 0 && teamDid && !this.teamManager.isNodeTeam(teamDid)) {
|
|
2343
|
-
const throttledEmit = this.getThrottledEmit(teamDid);
|
|
2344
|
-
if (throttledEmit) {
|
|
2345
|
-
throttledEmit(EVENTS.NOTIFICATION_BLOCKLET_UPDATE, {
|
|
2346
|
-
teamDid,
|
|
2347
|
-
notificationId,
|
|
2348
|
-
});
|
|
2349
|
-
}
|
|
2350
|
-
}
|
|
2351
|
-
return result;
|
|
635
|
+
getTransferAppOwnerSession({ appDid, transferId }) {
|
|
636
|
+
return invitationManager.getTransferAppOwnerSession(this, { appDid, transferId });
|
|
2352
637
|
}
|
|
2353
638
|
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
const { numAffected, effectRows } = await notificationState.read({ teamDid, ...rest }, context);
|
|
2357
|
-
this.teamManager.emitReadNotification(teamDid, {
|
|
2358
|
-
readCount: numAffected,
|
|
2359
|
-
receiver: rest.receiver,
|
|
2360
|
-
notificationIds: rest.notificationIds ?? [],
|
|
2361
|
-
effectRows,
|
|
2362
|
-
});
|
|
2363
|
-
return numAffected;
|
|
2364
|
-
}
|
|
2365
|
-
|
|
2366
|
-
async unreadNotifications({ teamDid, ...rest }, context) {
|
|
2367
|
-
const notificationState = await this.getNotificationState(teamDid);
|
|
2368
|
-
return notificationState.unread({ teamDid, ...rest }, context);
|
|
2369
|
-
}
|
|
2370
|
-
|
|
2371
|
-
async getNotificationSendLog({ teamDid, ...rest }, context) {
|
|
2372
|
-
const notificationState = await this.getNotificationState(teamDid);
|
|
2373
|
-
return notificationState.getNotificationSendLog({ ...rest }, context);
|
|
2374
|
-
}
|
|
2375
|
-
|
|
2376
|
-
async getNotificationComponents({ teamDid, ...rest }, context) {
|
|
2377
|
-
const notificationState = await this.getNotificationState(teamDid);
|
|
2378
|
-
return notificationState.getNotificationComponents({ ...rest }, context);
|
|
2379
|
-
}
|
|
2380
|
-
|
|
2381
|
-
async getReceivers({ teamDid, ...rest }, context) {
|
|
2382
|
-
const notificationState = await this.getNotificationState(teamDid);
|
|
2383
|
-
return notificationState.getNotificationReceivers({ ...rest }, context);
|
|
2384
|
-
}
|
|
2385
|
-
|
|
2386
|
-
// =======
|
|
2387
|
-
// Private
|
|
2388
|
-
// =======
|
|
2389
|
-
|
|
2390
|
-
getRBAC(did) {
|
|
2391
|
-
return this.teamManager.getRBAC(did);
|
|
2392
|
-
}
|
|
2393
|
-
|
|
2394
|
-
getUserState(did) {
|
|
2395
|
-
return this.teamManager.getUserState(did);
|
|
2396
|
-
}
|
|
2397
|
-
|
|
2398
|
-
getVerifyCodeState(did) {
|
|
2399
|
-
return this.teamManager.getVerifyCodeState(did);
|
|
2400
|
-
}
|
|
2401
|
-
|
|
2402
|
-
getUserSessionState(did) {
|
|
2403
|
-
return this.teamManager.getUserSessionState(did);
|
|
2404
|
-
}
|
|
2405
|
-
|
|
2406
|
-
getAccessKeyState(did) {
|
|
2407
|
-
return this.teamManager.getAccessKeyState(did);
|
|
2408
|
-
}
|
|
2409
|
-
|
|
2410
|
-
getTagState(did) {
|
|
2411
|
-
return this.teamManager.getTagState(did);
|
|
2412
|
-
}
|
|
2413
|
-
|
|
2414
|
-
getTaggingState(did) {
|
|
2415
|
-
return this.teamManager.getTaggingState(did);
|
|
2416
|
-
}
|
|
2417
|
-
|
|
2418
|
-
getSessionState(did) {
|
|
2419
|
-
return this.teamManager.getSessionState(did);
|
|
2420
|
-
}
|
|
2421
|
-
|
|
2422
|
-
getNotificationState(did) {
|
|
2423
|
-
return this.teamManager.getNotificationState(did);
|
|
2424
|
-
}
|
|
2425
|
-
|
|
2426
|
-
getOrgState(did) {
|
|
2427
|
-
return this.teamManager.getOrgState(did);
|
|
2428
|
-
}
|
|
2429
|
-
|
|
2430
|
-
// =============
|
|
2431
|
-
// Just for test
|
|
2432
|
-
// =============
|
|
2433
|
-
setInviteExpireTime(ms) {
|
|
2434
|
-
this.memberInviteExpireTime = ms;
|
|
2435
|
-
}
|
|
2436
|
-
|
|
2437
|
-
// user-session management
|
|
2438
|
-
async getUserSession({ teamDid, id, includeUser = false }) {
|
|
2439
|
-
const state = await this.getUserSessionState(teamDid);
|
|
2440
|
-
const userState = await this.getUserState(teamDid);
|
|
2441
|
-
|
|
2442
|
-
const where = {
|
|
2443
|
-
status: USER_SESSION_STATUS.ONLINE,
|
|
2444
|
-
id,
|
|
2445
|
-
};
|
|
2446
|
-
const include = [];
|
|
2447
|
-
if (includeUser) {
|
|
2448
|
-
include.push({
|
|
2449
|
-
model: userState.model,
|
|
2450
|
-
as: 'user',
|
|
2451
|
-
attributes: [
|
|
2452
|
-
'did',
|
|
2453
|
-
'pk',
|
|
2454
|
-
'sourceProvider',
|
|
2455
|
-
'fullName',
|
|
2456
|
-
'email',
|
|
2457
|
-
'avatar',
|
|
2458
|
-
'remark',
|
|
2459
|
-
'sourceAppPid',
|
|
2460
|
-
'approved',
|
|
2461
|
-
],
|
|
2462
|
-
});
|
|
2463
|
-
}
|
|
2464
|
-
|
|
2465
|
-
const userSession = await state.model.findOne({
|
|
2466
|
-
where,
|
|
2467
|
-
attributes: [
|
|
2468
|
-
'id',
|
|
2469
|
-
'appPid',
|
|
2470
|
-
'userDid',
|
|
2471
|
-
'visitorId',
|
|
2472
|
-
'passportId',
|
|
2473
|
-
'createdAt',
|
|
2474
|
-
'updatedAt',
|
|
2475
|
-
'extra',
|
|
2476
|
-
'ua',
|
|
2477
|
-
'lastLoginIp',
|
|
2478
|
-
'status',
|
|
2479
|
-
],
|
|
2480
|
-
include,
|
|
2481
|
-
});
|
|
2482
|
-
return userSession?.toJSON?.();
|
|
2483
|
-
}
|
|
2484
|
-
|
|
2485
|
-
// query, paging: inputPaging, sort
|
|
2486
|
-
async getUserSessions({ teamDid, query = {}, paging, sort }) {
|
|
2487
|
-
const nodeInfo = await this.node.read({ useCache: true });
|
|
2488
|
-
if (teamDid === nodeInfo.did) {
|
|
2489
|
-
return {
|
|
2490
|
-
list: [],
|
|
2491
|
-
paging: {
|
|
2492
|
-
page: 1,
|
|
2493
|
-
pageCount: 0,
|
|
2494
|
-
pageSize: 10,
|
|
2495
|
-
total: 0,
|
|
2496
|
-
},
|
|
2497
|
-
};
|
|
2498
|
-
}
|
|
2499
|
-
|
|
2500
|
-
const { userDid, visitorId, appPid, includeUser = false, status = USER_SESSION_STATUS.ONLINE } = query;
|
|
2501
|
-
const state = await this.getUserSessionState(teamDid);
|
|
2502
|
-
const userState = await this.getUserState(teamDid);
|
|
2503
|
-
const where = {};
|
|
2504
|
-
if (userDid) where.userDid = userDid;
|
|
2505
|
-
if (visitorId) where.visitorId = visitorId;
|
|
2506
|
-
if (appPid) where.appPid = appPid;
|
|
2507
|
-
|
|
2508
|
-
const blocklet = await getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs, useCache: true });
|
|
2509
|
-
const whereStatus = getUserSessionWhere({ status, blocklet });
|
|
2510
|
-
Object.assign(where, whereStatus);
|
|
2511
|
-
const include = [];
|
|
2512
|
-
if (includeUser) {
|
|
2513
|
-
include.push({
|
|
2514
|
-
model: userState.model,
|
|
2515
|
-
as: 'user',
|
|
2516
|
-
attributes: [
|
|
2517
|
-
'did',
|
|
2518
|
-
'pk',
|
|
2519
|
-
'sourceProvider',
|
|
2520
|
-
'fullName',
|
|
2521
|
-
'email',
|
|
2522
|
-
'avatar',
|
|
2523
|
-
'remark',
|
|
2524
|
-
'sourceAppPid',
|
|
2525
|
-
'approved',
|
|
2526
|
-
],
|
|
2527
|
-
});
|
|
2528
|
-
}
|
|
2529
|
-
|
|
2530
|
-
const result = await state.paginate(
|
|
2531
|
-
{
|
|
2532
|
-
where,
|
|
2533
|
-
attributes: [
|
|
2534
|
-
'id',
|
|
2535
|
-
'appPid',
|
|
2536
|
-
'userDid',
|
|
2537
|
-
'visitorId',
|
|
2538
|
-
'passportId',
|
|
2539
|
-
'createdAt',
|
|
2540
|
-
'updatedAt',
|
|
2541
|
-
'extra',
|
|
2542
|
-
'ua',
|
|
2543
|
-
'lastLoginIp',
|
|
2544
|
-
'status',
|
|
2545
|
-
],
|
|
2546
|
-
include,
|
|
2547
|
-
},
|
|
2548
|
-
{ updatedAt: -1, ...sort },
|
|
2549
|
-
{ pageSize: 10, ...paging }
|
|
2550
|
-
);
|
|
2551
|
-
|
|
2552
|
-
// NOTICE: 保留结构,方便理解
|
|
2553
|
-
return {
|
|
2554
|
-
list: result.list,
|
|
2555
|
-
paging: result.paging,
|
|
2556
|
-
};
|
|
639
|
+
checkTransferAppOwnerSession({ appDid, transferId }) {
|
|
640
|
+
return invitationManager.checkTransferAppOwnerSession(this, { appDid, transferId });
|
|
2557
641
|
}
|
|
2558
642
|
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
if (teamDid === nodeInfo.did) {
|
|
2562
|
-
return {
|
|
2563
|
-
count: 0,
|
|
2564
|
-
};
|
|
2565
|
-
}
|
|
2566
|
-
const { userDid, visitorId, appPid, status = USER_SESSION_STATUS.ONLINE } = query;
|
|
2567
|
-
const state = await this.getUserSessionState(teamDid);
|
|
2568
|
-
const where = {};
|
|
2569
|
-
if (userDid) where.userDid = userDid;
|
|
2570
|
-
if (visitorId) where.visitorId = visitorId;
|
|
2571
|
-
if (appPid) where.appPid = appPid;
|
|
2572
|
-
|
|
2573
|
-
const blocklet = await getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs, useCache: true });
|
|
2574
|
-
const whereStatus = getUserSessionWhere({ status, blocklet });
|
|
2575
|
-
Object.assign(where, whereStatus);
|
|
2576
|
-
|
|
2577
|
-
// HACK: 使用 state.count 并不能按预期工作,先改为使用 state.model.count 来实现
|
|
2578
|
-
const result = await state.model.count({ where });
|
|
2579
|
-
return {
|
|
2580
|
-
count: result,
|
|
2581
|
-
};
|
|
643
|
+
closeTransferAppOwnerSession(params) {
|
|
644
|
+
return invitationManager.closeTransferAppOwnerSession(this, params);
|
|
2582
645
|
}
|
|
2583
646
|
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
return passport;
|
|
2588
|
-
}
|
|
2589
|
-
|
|
2590
|
-
async getPassportFromFederated({ site, passportId, teamDid }) {
|
|
2591
|
-
try {
|
|
2592
|
-
const blocklet = await getBlocklet({
|
|
2593
|
-
did: teamDid,
|
|
2594
|
-
states: this.states,
|
|
2595
|
-
dataDirs: this.dataDirs,
|
|
2596
|
-
useCache: true,
|
|
2597
|
-
});
|
|
2598
|
-
const nodeInfo = await this.node.read();
|
|
2599
|
-
const { permanentWallet } = getBlockletInfo(blocklet, nodeInfo.sk);
|
|
2600
|
-
|
|
2601
|
-
const result = await callFederated({
|
|
2602
|
-
action: 'getPassport',
|
|
2603
|
-
data: {
|
|
2604
|
-
passportId,
|
|
2605
|
-
},
|
|
2606
|
-
permanentWallet,
|
|
2607
|
-
site,
|
|
2608
|
-
requestOptions: {
|
|
2609
|
-
// 缩短查询通行证的请求时间,这个请求不会很复杂
|
|
2610
|
-
timeout: 3 * 1000,
|
|
2611
|
-
},
|
|
2612
|
-
});
|
|
2613
|
-
|
|
2614
|
-
return result;
|
|
2615
|
-
} catch (error) {
|
|
2616
|
-
// 吞没错误,查询失败也不会影响整个快捷登录流程
|
|
2617
|
-
logger.error('Failed to getPassportFromFederated', { site, passportId, teamDid, error });
|
|
2618
|
-
return null;
|
|
2619
|
-
}
|
|
647
|
+
// ============ Passport Manager ============
|
|
648
|
+
issuePassportToUser(params, context) {
|
|
649
|
+
return passportManager.issuePassportToUser(this, params, context);
|
|
2620
650
|
}
|
|
2621
651
|
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
* @param {string} params.teamDid - 当前更新的这个应用的 teamDid
|
|
2625
|
-
* @param {string} params.appPid - 记录的 userSession 中应用的 appPid
|
|
2626
|
-
* @param {string} params.userDid - 记录的 userSession 中当前用户的 did
|
|
2627
|
-
* @param {string} params.visitorId - 记录的 userSession 中当前用户所使用设备的 visitorId
|
|
2628
|
-
* @param {string} params.ua - 记录的 userSession 中当前用户所使用设备的 ua
|
|
2629
|
-
* @param {string} params.lastLoginIp - 记录的 userSession 中当前用户的最后登录 ip
|
|
2630
|
-
* @param {string} params.passportId - 记录的 userSession 中当前用户登录所使用的 passportId
|
|
2631
|
-
* @param {string} params.status - 记录的 userSession 的状态
|
|
2632
|
-
* @param {Object} params.extra - 记录的 userSession 额外的数据
|
|
2633
|
-
* @returns
|
|
2634
|
-
*/
|
|
2635
|
-
|
|
2636
|
-
async upsertUserSession(
|
|
2637
|
-
{ teamDid, appPid, userDid, visitorId, ua, lastLoginIp, passportId, status, extra, locale, origin },
|
|
2638
|
-
{ skipNotification = false } = {}
|
|
2639
|
-
) {
|
|
2640
|
-
if (!userDid) {
|
|
2641
|
-
throw new Error('userDid is required');
|
|
2642
|
-
}
|
|
2643
|
-
if (visitorId) {
|
|
2644
|
-
if (visitorId.length > 80) {
|
|
2645
|
-
throw new Error('visitorId should be less than 80 characters');
|
|
2646
|
-
}
|
|
2647
|
-
}
|
|
2648
|
-
|
|
2649
|
-
const state = await this.getUserSessionState(teamDid);
|
|
2650
|
-
let data;
|
|
2651
|
-
if (!visitorId) {
|
|
2652
|
-
data = await state.insert({
|
|
2653
|
-
userDid,
|
|
2654
|
-
ua,
|
|
2655
|
-
lastLoginIp,
|
|
2656
|
-
appPid,
|
|
2657
|
-
passportId,
|
|
2658
|
-
extra,
|
|
2659
|
-
});
|
|
2660
|
-
logger.info('insert userSession successfully', { userDid, ua, lastLoginIp, appPid, passportId, extra });
|
|
2661
|
-
this.emit(BlockletEvents.addUserSession, {
|
|
2662
|
-
userSession: data,
|
|
2663
|
-
teamDid,
|
|
2664
|
-
userDid,
|
|
2665
|
-
locale,
|
|
2666
|
-
skipNotification,
|
|
2667
|
-
origin,
|
|
2668
|
-
});
|
|
2669
|
-
} else {
|
|
2670
|
-
const exist = await state.findOne({ userDid, visitorId, appPid });
|
|
2671
|
-
if (exist) {
|
|
2672
|
-
const mergeExtra = defaults({}, extra || {}, exist.extra || {});
|
|
2673
|
-
[, [data]] = await state.update(exist.id, {
|
|
2674
|
-
passportId,
|
|
2675
|
-
lastLoginIp,
|
|
2676
|
-
ua,
|
|
2677
|
-
status,
|
|
2678
|
-
extra: mergeExtra,
|
|
2679
|
-
});
|
|
2680
|
-
logger.info('update userSession successfully', {
|
|
2681
|
-
id: exist.id,
|
|
2682
|
-
ua,
|
|
2683
|
-
lastLoginIp,
|
|
2684
|
-
passportId,
|
|
2685
|
-
status,
|
|
2686
|
-
extra: mergeExtra,
|
|
2687
|
-
});
|
|
2688
|
-
if (Date.now() - new Date(exist.createdAt).getTime() < 1000 * 10) {
|
|
2689
|
-
// HACK: 此处是为了矫正首次创建 userSession 没有 ua 的情况,会通过 /api/did/session 接口来更新 ua 信息,这个时候才能当成新增 userSession 来发送通知
|
|
2690
|
-
this.emit(BlockletEvents.addUserSession, {
|
|
2691
|
-
userSession: data,
|
|
2692
|
-
teamDid,
|
|
2693
|
-
userDid,
|
|
2694
|
-
locale,
|
|
2695
|
-
skipNotification,
|
|
2696
|
-
origin,
|
|
2697
|
-
});
|
|
2698
|
-
} else {
|
|
2699
|
-
this.emit(BlockletEvents.updateUserSession, {
|
|
2700
|
-
userSession: data,
|
|
2701
|
-
teamDid,
|
|
2702
|
-
userDid,
|
|
2703
|
-
skipNotification,
|
|
2704
|
-
origin,
|
|
2705
|
-
});
|
|
2706
|
-
}
|
|
2707
|
-
} else {
|
|
2708
|
-
data = await state.insert({
|
|
2709
|
-
visitorId,
|
|
2710
|
-
userDid,
|
|
2711
|
-
ua,
|
|
2712
|
-
lastLoginIp,
|
|
2713
|
-
appPid,
|
|
2714
|
-
passportId,
|
|
2715
|
-
extra,
|
|
2716
|
-
});
|
|
2717
|
-
logger.info('insert userSession successfully', {
|
|
2718
|
-
visitorId,
|
|
2719
|
-
userDid,
|
|
2720
|
-
ua,
|
|
2721
|
-
lastLoginIp,
|
|
2722
|
-
appPid,
|
|
2723
|
-
passportId,
|
|
2724
|
-
extra,
|
|
2725
|
-
});
|
|
2726
|
-
this.emit(BlockletEvents.addUserSession, {
|
|
2727
|
-
userSession: data,
|
|
2728
|
-
teamDid,
|
|
2729
|
-
userDid,
|
|
2730
|
-
locale,
|
|
2731
|
-
skipNotification,
|
|
2732
|
-
origin,
|
|
2733
|
-
});
|
|
2734
|
-
}
|
|
2735
|
-
}
|
|
2736
|
-
|
|
2737
|
-
return data;
|
|
652
|
+
revokeUserPassport(params, context) {
|
|
653
|
+
return passportManager.revokeUserPassport(this, params, context);
|
|
2738
654
|
}
|
|
2739
655
|
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
* @param {string} params.teamDid - 当前更新的这个应用的 teamDid
|
|
2743
|
-
* @param {string} params.targetAppPid - 同步的 userSession 的目标站点 appPid
|
|
2744
|
-
* @param {string} params.userDid - 同步的 userSession 中当前用户的 did
|
|
2745
|
-
* @param {string} params.visitorId - 同步的 userSession 中当前用户所使用设备的 visitorId
|
|
2746
|
-
* @param {string} params.ua - 同步的 userSession 中当前用户所使用设备的 ua
|
|
2747
|
-
* @param {string} params.lastLoginIp - 同步的 userSession 中当前用户的最后登录 ip
|
|
2748
|
-
* @param {string} params.passportId - 同步的 userSession 中当前用户登录所使用的 passportId
|
|
2749
|
-
* @param {object} params.extra - 同步的 userSession 中当前用户登录额外的数据
|
|
2750
|
-
* @returns
|
|
2751
|
-
*/
|
|
2752
|
-
async syncUserSession({ teamDid, targetAppPid, userDid, visitorId, ua, lastLoginIp, passportId, extra }) {
|
|
2753
|
-
if (!targetAppPid) return;
|
|
2754
|
-
|
|
2755
|
-
const blocklet = await getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs, useCache: true });
|
|
2756
|
-
const nodeInfo = await this.node.read();
|
|
2757
|
-
const { permanentWallet } = getBlockletInfo(blocklet, nodeInfo.sk);
|
|
2758
|
-
|
|
2759
|
-
const targetSite = findFederatedSite(blocklet, targetAppPid);
|
|
2760
|
-
const masterSite = getFederatedMaster(blocklet);
|
|
2761
|
-
|
|
2762
|
-
let syncSite = null;
|
|
2763
|
-
let appPid;
|
|
2764
|
-
if (masterSite && targetSite) {
|
|
2765
|
-
if (targetSite.appPid === masterSite.appPid) {
|
|
2766
|
-
if (teamDid !== masterSite.appPid) {
|
|
2767
|
-
syncSite = masterSite;
|
|
2768
|
-
appPid = teamDid;
|
|
2769
|
-
}
|
|
2770
|
-
} else if (teamDid === masterSite.appPid) {
|
|
2771
|
-
syncSite = targetSite;
|
|
2772
|
-
appPid = targetAppPid;
|
|
2773
|
-
}
|
|
2774
|
-
|
|
2775
|
-
if (syncSite) {
|
|
2776
|
-
try {
|
|
2777
|
-
const userSession = {
|
|
2778
|
-
action: 'login',
|
|
2779
|
-
userDid,
|
|
2780
|
-
visitorId,
|
|
2781
|
-
ua,
|
|
2782
|
-
lastLoginIp,
|
|
2783
|
-
appPid,
|
|
2784
|
-
passportId,
|
|
2785
|
-
extra,
|
|
2786
|
-
};
|
|
2787
|
-
await callFederated({
|
|
2788
|
-
action: 'sync',
|
|
2789
|
-
permanentWallet,
|
|
2790
|
-
site: targetSite,
|
|
2791
|
-
data: {
|
|
2792
|
-
userSessions: [userSession],
|
|
2793
|
-
},
|
|
2794
|
-
});
|
|
2795
|
-
logger.debug('Sync userSession to federated site successfully', {
|
|
2796
|
-
userDid,
|
|
2797
|
-
visitorId,
|
|
2798
|
-
ua,
|
|
2799
|
-
lastLoginIp,
|
|
2800
|
-
appPid,
|
|
2801
|
-
passportId,
|
|
2802
|
-
targetSite,
|
|
2803
|
-
extra,
|
|
2804
|
-
});
|
|
2805
|
-
} catch (err) {
|
|
2806
|
-
logger.error(
|
|
2807
|
-
'Sync userSession to federated site failed',
|
|
2808
|
-
{
|
|
2809
|
-
userDid,
|
|
2810
|
-
visitorId,
|
|
2811
|
-
ua,
|
|
2812
|
-
lastLoginIp,
|
|
2813
|
-
appPid,
|
|
2814
|
-
passportId,
|
|
2815
|
-
targetSite,
|
|
2816
|
-
extra,
|
|
2817
|
-
},
|
|
2818
|
-
err
|
|
2819
|
-
);
|
|
2820
|
-
}
|
|
2821
|
-
}
|
|
2822
|
-
}
|
|
656
|
+
enableUserPassport(params, context) {
|
|
657
|
+
return passportManager.enableUserPassport(this, params, context);
|
|
2823
658
|
}
|
|
2824
659
|
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
throw new Error('userDid is required');
|
|
2828
|
-
}
|
|
2829
|
-
if (!teamDid) {
|
|
2830
|
-
throw new Error('teamDid is required');
|
|
2831
|
-
}
|
|
2832
|
-
|
|
2833
|
-
const nodeInfo = await this.node.read();
|
|
2834
|
-
if (nodeInfo.did === teamDid) {
|
|
2835
|
-
return 0;
|
|
2836
|
-
}
|
|
2837
|
-
const userSessionState = await this.getUserSessionState(teamDid);
|
|
2838
|
-
const where = { userDid };
|
|
2839
|
-
if (visitorId) where.visitorId = visitorId;
|
|
2840
|
-
if (appPid) where.appPid = appPid;
|
|
2841
|
-
|
|
2842
|
-
const blocklet = await getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs, useCache: true });
|
|
2843
|
-
const whereStatus = getUserSessionWhere({ status, blocklet });
|
|
2844
|
-
Object.assign(where, whereStatus);
|
|
2845
|
-
let result;
|
|
2846
|
-
if (remove) {
|
|
2847
|
-
result = await userSessionState.remove(where);
|
|
2848
|
-
} else {
|
|
2849
|
-
result = await userSessionState.update(where, { $set: { status: USER_SESSION_STATUS.OFFLINE } });
|
|
2850
|
-
}
|
|
2851
|
-
const { permanentWallet } = getBlockletInfo(blocklet, nodeInfo.sk);
|
|
2852
|
-
const masterSite = getFederatedMaster(blocklet);
|
|
2853
|
-
if (masterSite && masterSite.isMaster !== false && masterSite.appPid !== teamDid) {
|
|
2854
|
-
callFederated({
|
|
2855
|
-
action: 'sync',
|
|
2856
|
-
permanentWallet,
|
|
2857
|
-
// FIXME: @zhanghan 是否还需要通知其他 Member 站点,执行退出登录的操作
|
|
2858
|
-
site: masterSite,
|
|
2859
|
-
data: {
|
|
2860
|
-
userSessions: [
|
|
2861
|
-
{
|
|
2862
|
-
action: 'logout',
|
|
2863
|
-
userDid,
|
|
2864
|
-
visitorId,
|
|
2865
|
-
// HACK: 如果未传入 appPid,代表要注销当前用户在 master 中所有登录状态,所以传入 undefined;否则就只注销 master 中记录的当前 member 的登录状态
|
|
2866
|
-
appPid: appPid ? teamDid : undefined,
|
|
2867
|
-
remove,
|
|
2868
|
-
},
|
|
2869
|
-
],
|
|
2870
|
-
},
|
|
2871
|
-
});
|
|
2872
|
-
}
|
|
2873
|
-
return result;
|
|
660
|
+
removeUserPassport(params) {
|
|
661
|
+
return passportManager.removeUserPassport(this, params);
|
|
2874
662
|
}
|
|
2875
663
|
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
const state = await this.getVerifyCodeState(teamDid);
|
|
2879
|
-
return state.create(subject, scope, purpose);
|
|
664
|
+
createPassportIssuance(params, context) {
|
|
665
|
+
return passportManager.createPassportIssuance(this, params, context);
|
|
2880
666
|
}
|
|
2881
667
|
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
return state.verify(code);
|
|
668
|
+
getPassportIssuances({ teamDid, ownerDid }) {
|
|
669
|
+
return passportManager.getPassportIssuances(this, { teamDid, ownerDid });
|
|
2885
670
|
}
|
|
2886
671
|
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
return state.issue(code);
|
|
672
|
+
getPassportIssuance({ teamDid, sessionId }) {
|
|
673
|
+
return passportManager.getPassportIssuance(this, { teamDid, sessionId });
|
|
2890
674
|
}
|
|
2891
675
|
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
return state.send(code);
|
|
676
|
+
processPassportIssuance({ teamDid, sessionId }) {
|
|
677
|
+
return passportManager.processPassportIssuance(this, { teamDid, sessionId });
|
|
2895
678
|
}
|
|
2896
679
|
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
return state.isVerified(subject);
|
|
680
|
+
deletePassportIssuance({ teamDid, sessionId }) {
|
|
681
|
+
return passportManager.deletePassportIssuance(this, { teamDid, sessionId });
|
|
2900
682
|
}
|
|
2901
683
|
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
return state.isSubjectIssued(userDid, subject);
|
|
684
|
+
configTrustedPassports({ teamDid, trustedPassports }) {
|
|
685
|
+
return passportManager.configTrustedPassports(this, { teamDid, trustedPassports });
|
|
2905
686
|
}
|
|
2906
687
|
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
return state.isSent(subject);
|
|
688
|
+
configTrustedFactories({ teamDid, trustedFactories }) {
|
|
689
|
+
return passportManager.configTrustedFactories(this, { teamDid, trustedFactories });
|
|
2910
690
|
}
|
|
2911
691
|
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
const where = {};
|
|
2915
|
-
if (code) where.code = code;
|
|
2916
|
-
if (id) where.id = id;
|
|
2917
|
-
return state.findOne(where);
|
|
692
|
+
configPassportIssuance({ teamDid, enable }) {
|
|
693
|
+
return passportManager.configPassportIssuance(this, { teamDid, enable });
|
|
2918
694
|
}
|
|
2919
695
|
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
}
|
|
2924
|
-
|
|
2925
|
-
const info = await this.node.read();
|
|
2926
|
-
if (info.did === teamDid) {
|
|
2927
|
-
await this.node.updateNodeInfo({ sessionSalt: generateRandomString(16) });
|
|
2928
|
-
return 0;
|
|
2929
|
-
}
|
|
2930
|
-
|
|
2931
|
-
const blocklet = await getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs, useCache: true });
|
|
2932
|
-
const sessionConfig = cloneDeep(blocklet.settings.session || {});
|
|
2933
|
-
sessionConfig.salt = generateRandomString(16);
|
|
2934
|
-
await this.states.blockletExtras.setSettings(blocklet.meta.did, { session: sessionConfig });
|
|
2935
|
-
this.emit(BlockletEvents.updated, { meta: { did: teamDid } });
|
|
2936
|
-
|
|
2937
|
-
// NOTE: we need emit appConfigChanged event to update sessionSalt in blocklet-sdk
|
|
2938
|
-
this.emit(BlockletInternalEvents.appConfigChanged, {
|
|
2939
|
-
appDid: teamDid,
|
|
2940
|
-
configs: [{ key: 'BLOCKLET_APP_SALT', value: sessionConfig.salt }],
|
|
2941
|
-
});
|
|
2942
|
-
|
|
2943
|
-
return 1;
|
|
696
|
+
// ============ Org Manager ============
|
|
697
|
+
getOrgs(params, context) {
|
|
698
|
+
return orgManager.getOrgs(this, params, context);
|
|
2944
699
|
}
|
|
2945
700
|
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
const state = await this.getAccessKeyState(teamDid);
|
|
2949
|
-
return state.findPaginated(data, context);
|
|
701
|
+
getOrg({ teamDid, id }, context) {
|
|
702
|
+
return orgManager.getOrg(this, { teamDid, id }, context);
|
|
2950
703
|
}
|
|
2951
704
|
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
return state.detail({ accessKeyId }, context);
|
|
705
|
+
createDefaultOrgForUser({ teamDid, user }) {
|
|
706
|
+
return orgManager.createDefaultOrgForUser(this, { teamDid, user });
|
|
2955
707
|
}
|
|
2956
708
|
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
return state.create(Object.assign({ componentDid: teamDid }, data), context);
|
|
709
|
+
issueOrgOwnerPassport({ teamDid, org }) {
|
|
710
|
+
return orgManager.issueOrgOwnerPassport(this, { teamDid, org });
|
|
2960
711
|
}
|
|
2961
712
|
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
return state.update(data, context);
|
|
713
|
+
createOrg(params, context) {
|
|
714
|
+
return orgManager.createOrg(this, params, context);
|
|
2965
715
|
}
|
|
2966
716
|
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
return state.remove({ accessKeyId }, context);
|
|
717
|
+
updateOrg({ teamDid, org }, context) {
|
|
718
|
+
return orgManager.updateOrg(this, { teamDid, org }, context);
|
|
2970
719
|
}
|
|
2971
720
|
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
return state.refreshLastUsed(accessKeyId, context);
|
|
721
|
+
deleteOrg({ teamDid, id }, context) {
|
|
722
|
+
return orgManager.deleteOrg(this, { teamDid, id }, context);
|
|
2975
723
|
}
|
|
2976
724
|
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
const accessKey = await state.verify(data, context);
|
|
2980
|
-
return accessKey;
|
|
725
|
+
addOrgMember({ teamDid, orgId, userDid }, context) {
|
|
726
|
+
return orgManager.addOrgMember(this, { teamDid, orgId, userDid }, context);
|
|
2981
727
|
}
|
|
2982
728
|
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
return oauthClientState.list(paging);
|
|
729
|
+
updateOrgMember({ teamDid, orgId, userDid, status }, context) {
|
|
730
|
+
return orgManager.updateOrgMember(this, { teamDid, orgId, userDid, status }, context);
|
|
2986
731
|
}
|
|
2987
732
|
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
return oauthClientState.remove({ clientId });
|
|
733
|
+
sendInvitationNotification(params) {
|
|
734
|
+
return orgManager.sendInvitationNotification(this, params);
|
|
2991
735
|
}
|
|
2992
736
|
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
return oauthClientState.create(input, context);
|
|
737
|
+
getFederatedMasterBlockletInfo({ blocklet }) {
|
|
738
|
+
return orgManager.getFederatedMasterBlockletInfo({ blocklet });
|
|
2996
739
|
}
|
|
2997
740
|
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
return oauthClientState.update(input, context);
|
|
741
|
+
inviteMembersToOrg(params, context) {
|
|
742
|
+
return orgManager.inviteMembersToOrg(this, params, context);
|
|
3001
743
|
}
|
|
3002
744
|
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
const state = await this.getOrgState(teamDid);
|
|
3006
|
-
const { passports, orgs, ...rest } = await state.list(payload, context);
|
|
3007
|
-
const { includePassports = true } = payload.options || {};
|
|
3008
|
-
if (includePassports) {
|
|
3009
|
-
// 获取每个组织的 passports
|
|
3010
|
-
const orgPassports = await Promise.all(orgs.map((o) => this.getRoles({ teamDid, orgId: o.id })));
|
|
3011
|
-
|
|
3012
|
-
orgs.forEach((o, index) => {
|
|
3013
|
-
const roles = orgPassports[index]; // 获取每个组织的角色
|
|
3014
|
-
// 过滤 passports
|
|
3015
|
-
o.passports = passports.filter((p) => roles.some((r) => r.name === p.name));
|
|
3016
|
-
});
|
|
3017
|
-
} else {
|
|
3018
|
-
orgs.forEach((o) => {
|
|
3019
|
-
o.passports = [];
|
|
3020
|
-
});
|
|
3021
|
-
}
|
|
3022
|
-
|
|
3023
|
-
return {
|
|
3024
|
-
...rest,
|
|
3025
|
-
orgs,
|
|
3026
|
-
};
|
|
3027
|
-
} catch (err) {
|
|
3028
|
-
logger.error('Failed to get orgs', { err, teamDid });
|
|
3029
|
-
throw err;
|
|
3030
|
-
}
|
|
745
|
+
removeOrgMember({ teamDid, orgId, userDid }, context) {
|
|
746
|
+
return orgManager.removeOrgMember(this, { teamDid, orgId, userDid }, context);
|
|
3031
747
|
}
|
|
3032
748
|
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
const state = await this.getOrgState(teamDid);
|
|
3036
|
-
return state.get({ id }, context);
|
|
3037
|
-
} catch (err) {
|
|
3038
|
-
logger.error('Failed to get org', { err, teamDid, id });
|
|
3039
|
-
throw err;
|
|
3040
|
-
}
|
|
749
|
+
getOrgMembers({ teamDid, orgId, paging }, context) {
|
|
750
|
+
return orgManager.getOrgMembers(this, { teamDid, orgId, paging }, context);
|
|
3041
751
|
}
|
|
3042
752
|
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
// 创建失败不要影响主流程
|
|
3046
|
-
await this.createOrg(
|
|
3047
|
-
{
|
|
3048
|
-
teamDid,
|
|
3049
|
-
name: user.fullName,
|
|
3050
|
-
description: `this is a default org for ${user.fullName}`,
|
|
3051
|
-
throwOnValidationError: false,
|
|
3052
|
-
},
|
|
3053
|
-
{ user }
|
|
3054
|
-
);
|
|
3055
|
-
} catch (err) {
|
|
3056
|
-
logger.error('Failed to create default org for user', { err, teamDid, user });
|
|
3057
|
-
}
|
|
753
|
+
getOrgInvitableUsers({ teamDid, id, query, paging }, context) {
|
|
754
|
+
return orgManager.getOrgInvitableUsers(this, { teamDid, id, query, paging }, context);
|
|
3058
755
|
}
|
|
3059
756
|
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
// 创建 org 的 owner passport, 并赋值给 owner
|
|
3063
|
-
const roleName = md5(`${org.id}-owner`); // 避免 name 重复
|
|
3064
|
-
await this.createRole({ teamDid, name: roleName, title: org.name, description: 'Owner', orgId: org.id });
|
|
3065
|
-
await this.issuePassportToUser({
|
|
3066
|
-
teamDid,
|
|
3067
|
-
userDid: org.ownerDid,
|
|
3068
|
-
role: roleName,
|
|
3069
|
-
notification: {},
|
|
3070
|
-
});
|
|
3071
|
-
} catch (err) {
|
|
3072
|
-
logger.error('Failed to create passport to org owner', { err, teamDid, org });
|
|
3073
|
-
}
|
|
757
|
+
getOrgResource({ teamDid, orgId, resourceId }, context) {
|
|
758
|
+
return orgManager.getOrgResource(this, { teamDid, orgId, resourceId }, context);
|
|
3074
759
|
}
|
|
3075
760
|
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
// 1. 对输入进行转义
|
|
3079
|
-
const sanitizedOrg = {
|
|
3080
|
-
...rest,
|
|
3081
|
-
description: sanitizeTag(rest.description || ''),
|
|
3082
|
-
name: sanitizeTag(rest.name || ''),
|
|
3083
|
-
};
|
|
3084
|
-
|
|
3085
|
-
const { error } = createOrgInputSchema.validate(sanitizedOrg);
|
|
3086
|
-
if (error) {
|
|
3087
|
-
throw new CustomError(400, error.message);
|
|
3088
|
-
}
|
|
3089
|
-
|
|
3090
|
-
const state = await this.getOrgState(teamDid);
|
|
3091
|
-
const blocklet = await getBlocklet({
|
|
3092
|
-
did: teamDid,
|
|
3093
|
-
states: this.states,
|
|
3094
|
-
dataDirs: this.dataDirs,
|
|
3095
|
-
useCache: true,
|
|
3096
|
-
});
|
|
3097
|
-
const checkedUserDid = rest.ownerDid || context.user.did || '';
|
|
3098
|
-
const orgCount = await state.getOrgCountByUser(checkedUserDid);
|
|
3099
|
-
|
|
3100
|
-
const { veriftMaxOrgPerUser } = createOrgValidators(blocklet);
|
|
3101
|
-
|
|
3102
|
-
try {
|
|
3103
|
-
veriftMaxOrgPerUser(orgCount); // 验证用户创建的 org 数量是否超过最大限制, 内部已经验证 org 是否开启
|
|
3104
|
-
} catch (_error) {
|
|
3105
|
-
if (throwOnValidationError) {
|
|
3106
|
-
throw _error;
|
|
3107
|
-
}
|
|
3108
|
-
logger.warn('Failed to validate org creation', { error: _error, teamDid, orgCount });
|
|
3109
|
-
return undefined;
|
|
3110
|
-
}
|
|
3111
|
-
|
|
3112
|
-
const result = await state.create({ ...sanitizedOrg }, context);
|
|
3113
|
-
|
|
3114
|
-
// 创建 org 的 owner passport, 并赋值给 owner
|
|
3115
|
-
if (!deferPassport) {
|
|
3116
|
-
await this.issueOrgOwnerPassport({ teamDid, org: result });
|
|
3117
|
-
} else {
|
|
3118
|
-
const jobId = md5(`${teamDid}-${result.id}-${checkedUserDid}`);
|
|
3119
|
-
this.passportIssueQueue.push(
|
|
3120
|
-
{
|
|
3121
|
-
action: 'issueOrgOwnerPassport',
|
|
3122
|
-
entity: 'blocklet',
|
|
3123
|
-
entityId: teamDid,
|
|
3124
|
-
params: {
|
|
3125
|
-
teamDid,
|
|
3126
|
-
org: result,
|
|
3127
|
-
},
|
|
3128
|
-
},
|
|
3129
|
-
jobId,
|
|
3130
|
-
true
|
|
3131
|
-
);
|
|
3132
|
-
}
|
|
3133
|
-
|
|
3134
|
-
return result;
|
|
3135
|
-
} catch (err) {
|
|
3136
|
-
logger.error('Failed to create org', err, {
|
|
3137
|
-
teamDid,
|
|
3138
|
-
name: rest.name,
|
|
3139
|
-
userDid: rest.ownerDid || context.user.did || '',
|
|
3140
|
-
});
|
|
3141
|
-
throw err;
|
|
3142
|
-
}
|
|
761
|
+
addOrgResource({ teamDid, orgId, resourceIds, type, metadata }, context) {
|
|
762
|
+
return orgManager.addOrgResource(this, { teamDid, orgId, resourceIds, type, metadata }, context);
|
|
3143
763
|
}
|
|
3144
764
|
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
const sanitizedOrg = {
|
|
3148
|
-
...org,
|
|
3149
|
-
description: sanitizeTag(org.description),
|
|
3150
|
-
name: sanitizeTag(org.name),
|
|
3151
|
-
};
|
|
3152
|
-
|
|
3153
|
-
const { error } = updateOrgInputSchema.validate(sanitizedOrg);
|
|
3154
|
-
if (error) {
|
|
3155
|
-
throw new CustomError(400, error.message);
|
|
3156
|
-
}
|
|
3157
|
-
|
|
3158
|
-
const state = await this.getOrgState(teamDid);
|
|
3159
|
-
return state.updateOrg({ org: sanitizedOrg }, context);
|
|
3160
|
-
} catch (err) {
|
|
3161
|
-
logger.error('Failed to update org', { err, teamDid });
|
|
3162
|
-
throw err;
|
|
3163
|
-
}
|
|
765
|
+
removeOrgResource({ teamDid, orgId, resourceIds }, context) {
|
|
766
|
+
return orgManager.removeOrgResource(this, { teamDid, orgId, resourceIds }, context);
|
|
3164
767
|
}
|
|
3165
768
|
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
const state = await this.getOrgState(teamDid);
|
|
3169
|
-
// 要同时删除与 org 相关的 passport 和 邀请链接
|
|
3170
|
-
const roles = await this.getRoles({ teamDid, orgId: id });
|
|
3171
|
-
const result = await state.deleteOrg({ id }, context);
|
|
3172
|
-
try {
|
|
3173
|
-
await state.removeOrgRelatedData({ roles, orgId: id });
|
|
3174
|
-
} catch (err) {
|
|
3175
|
-
logger.error('Failed to remove org related data', { err, teamDid, roles, id });
|
|
3176
|
-
}
|
|
3177
|
-
return result;
|
|
3178
|
-
} catch (err) {
|
|
3179
|
-
logger.error('Failed to delete org', { err, teamDid, id });
|
|
3180
|
-
throw err;
|
|
3181
|
-
}
|
|
769
|
+
migrateOrgResource({ teamDid, from, to, resourceIds }, context) {
|
|
770
|
+
return orgManager.migrateOrgResource(this, { teamDid, from, to, resourceIds }, context);
|
|
3182
771
|
}
|
|
3183
772
|
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
const state = await this.getOrgState(teamDid);
|
|
3187
|
-
return state.addOrgMember({ orgId, userDid }, context);
|
|
3188
|
-
} catch (err) {
|
|
3189
|
-
logger.error('Add member to org failed', { err, teamDid, orgId, userDid });
|
|
3190
|
-
throw err;
|
|
3191
|
-
}
|
|
773
|
+
getNotificationStats({ teamDid, since }) {
|
|
774
|
+
return orgManager.getNotificationStats(this, { teamDid, since });
|
|
3192
775
|
}
|
|
3193
776
|
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
return state.updateOrgMember({ orgId, userDid, status }, context);
|
|
3198
|
-
} catch (err) {
|
|
3199
|
-
logger.error('Update member in org failed', { err, teamDid, orgId, userDid, status });
|
|
3200
|
-
throw err;
|
|
3201
|
-
}
|
|
777
|
+
// ============ Delegated Methods (from teamManager) ============
|
|
778
|
+
getRoles({ teamDid, orgId }) {
|
|
779
|
+
return this.teamManager.getRoles(teamDid, orgId);
|
|
3202
780
|
}
|
|
3203
781
|
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
* @param {*} param0
|
|
3207
|
-
*/
|
|
3208
|
-
async sendInvitationNotification({
|
|
3209
|
-
teamDid,
|
|
3210
|
-
invitor,
|
|
3211
|
-
org,
|
|
3212
|
-
role,
|
|
3213
|
-
successUserDids,
|
|
3214
|
-
inviteLink,
|
|
3215
|
-
email,
|
|
3216
|
-
inviteType,
|
|
3217
|
-
blocklet,
|
|
3218
|
-
}) {
|
|
3219
|
-
try {
|
|
3220
|
-
const userInfo = await this.getUser({
|
|
3221
|
-
teamDid,
|
|
3222
|
-
user: { did: invitor.did },
|
|
3223
|
-
options: { enableConnectedAccount: true },
|
|
3224
|
-
});
|
|
3225
|
-
|
|
3226
|
-
// 检测是否开启了 email 服务
|
|
3227
|
-
const provider = getEmailServiceProvider(blocklet);
|
|
3228
|
-
|
|
3229
|
-
const translate = {
|
|
3230
|
-
en: {
|
|
3231
|
-
title: 'Inviting you to join the organization',
|
|
3232
|
-
description: `<${userInfo.fullName}(did:abt:${userInfo.did})> invites you to join the ${org.name} organization, role is ${role}.<br/>After accepting, you will be able to access the resources and collaboration content of the organization.<br/><br/>Please click the button below to handle the invitation.<br/><br/>If you don't want to join, you can ignore this notification.`,
|
|
3233
|
-
accept: 'Accept',
|
|
3234
|
-
},
|
|
3235
|
-
zh: {
|
|
3236
|
-
title: '邀请您加入组织',
|
|
3237
|
-
description: `<${userInfo.fullName}(did:abt:${userInfo.did})> 邀请您加入 ${org.name} 组织,角色为 ${role}。<br/>接受后,你将能够访问该组织的资源和协作内容。<br/><br/>请点击下方按钮处理邀请。<br/><br/>如果你不想加入,可以忽略此通知。`,
|
|
3238
|
-
accept: '接受',
|
|
3239
|
-
},
|
|
3240
|
-
};
|
|
3241
|
-
const content = translate[userInfo.locale || 'en'] || translate.en;
|
|
3242
|
-
|
|
3243
|
-
const message = {
|
|
3244
|
-
title: content.title,
|
|
3245
|
-
body: content.description,
|
|
3246
|
-
actions: [
|
|
3247
|
-
{
|
|
3248
|
-
name: content.accept,
|
|
3249
|
-
title: content.accept,
|
|
3250
|
-
link: inviteLink,
|
|
3251
|
-
},
|
|
3252
|
-
],
|
|
3253
|
-
};
|
|
3254
|
-
|
|
3255
|
-
if (inviteType === 'internal') {
|
|
3256
|
-
await this.createNotification({
|
|
3257
|
-
teamDid,
|
|
3258
|
-
receiver: successUserDids,
|
|
3259
|
-
entityId: teamDid,
|
|
3260
|
-
source: 'system',
|
|
3261
|
-
severity: 'info',
|
|
3262
|
-
...message,
|
|
3263
|
-
});
|
|
3264
|
-
logger.info('Invite notification sent successfully', {
|
|
3265
|
-
teamDid,
|
|
3266
|
-
orgId: org.id,
|
|
3267
|
-
sentToUsers: successUserDids,
|
|
3268
|
-
sentCount: successUserDids.length,
|
|
3269
|
-
});
|
|
3270
|
-
} else if (inviteType === 'external' && provider && email) {
|
|
3271
|
-
// 当 service 开启 email 服务时才会发送邮件通知
|
|
3272
|
-
const emailInputSchema = Joi.string().email().required();
|
|
3273
|
-
const { error } = emailInputSchema.validate(email);
|
|
3274
|
-
if (error) {
|
|
3275
|
-
throw new CustomError(400, error.message);
|
|
3276
|
-
}
|
|
3277
|
-
const nodeInfo = await this.node.read();
|
|
3278
|
-
const blockletInfo = getBlockletInfo(blocklet, nodeInfo.sk);
|
|
3279
|
-
const sender = {
|
|
3280
|
-
appDid: blockletInfo.wallet.address,
|
|
3281
|
-
appSk: blockletInfo.wallet.secretKey,
|
|
3282
|
-
};
|
|
3283
|
-
|
|
3284
|
-
await sendToUser(email, message, sender, undefined, 'send-to-mail');
|
|
3285
|
-
logger.info('Send invitation notification to email completed', {
|
|
3286
|
-
teamDid,
|
|
3287
|
-
orgId: org.id,
|
|
3288
|
-
email,
|
|
3289
|
-
});
|
|
3290
|
-
}
|
|
3291
|
-
} catch (notificationErr) {
|
|
3292
|
-
// 通知发送失败不影响邀请的成功,只记录警告
|
|
3293
|
-
logger.warn('Failed to send invitation notification, but invitations were created successfully', {
|
|
3294
|
-
notificationErr,
|
|
3295
|
-
teamDid,
|
|
3296
|
-
orgId: org.id,
|
|
3297
|
-
successUserDids,
|
|
3298
|
-
});
|
|
3299
|
-
}
|
|
782
|
+
getRBAC(teamDid) {
|
|
783
|
+
return this.teamManager.getRBAC(teamDid);
|
|
3300
784
|
}
|
|
3301
785
|
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
const federatedCurrent = sites.find((item) => item.appId === blocklet.appDid);
|
|
3306
|
-
const isFederated = !!federatedMaster || !!federatedCurrent;
|
|
3307
|
-
if (!isFederated) {
|
|
3308
|
-
return undefined;
|
|
3309
|
-
}
|
|
3310
|
-
|
|
3311
|
-
const formatBlockletInfo = (federateBlocklet) => ({
|
|
3312
|
-
appId: federateBlocklet?.appId,
|
|
3313
|
-
appName: federateBlocklet?.appName,
|
|
3314
|
-
appDescription: federateBlocklet?.appDescription,
|
|
3315
|
-
appLogo: federateBlocklet?.appLogo,
|
|
3316
|
-
appPid: federateBlocklet?.appPid,
|
|
3317
|
-
appUrl: federateBlocklet?.appUrl,
|
|
3318
|
-
version: federateBlocklet?.version,
|
|
3319
|
-
sourceAppPid: federateBlocklet?.appPid,
|
|
3320
|
-
provider: 'wallet',
|
|
3321
|
-
});
|
|
3322
|
-
|
|
3323
|
-
const blocklets = [];
|
|
3324
|
-
if (federatedCurrent?.status === 'approved') {
|
|
3325
|
-
blocklets.push(formatBlockletInfo(federatedMaster));
|
|
3326
|
-
}
|
|
3327
|
-
if (federatedCurrent) {
|
|
3328
|
-
blocklets.push({
|
|
3329
|
-
...formatBlockletInfo(federatedCurrent),
|
|
3330
|
-
sourceAppPid: null,
|
|
3331
|
-
});
|
|
3332
|
-
} else {
|
|
3333
|
-
const blockletInfo = getBlockletInfo(blocklet, undefined, { returnWallet: false });
|
|
3334
|
-
blocklets.push({
|
|
3335
|
-
...formatBlockletInfo(blockletInfo),
|
|
3336
|
-
appPid: blocklet?.appPid,
|
|
3337
|
-
appLogo: joinURL(blockletInfo.appUrl, WELLKNOWN_SERVICE_PATH_PREFIX, '/blocklet/logo'),
|
|
3338
|
-
sourceAppPid: null,
|
|
3339
|
-
});
|
|
3340
|
-
}
|
|
3341
|
-
|
|
3342
|
-
return blocklets[0];
|
|
786
|
+
// State getters (delegated to teamManager)
|
|
787
|
+
getUserState(teamDid) {
|
|
788
|
+
return this.teamManager.getUserState(teamDid);
|
|
3343
789
|
}
|
|
3344
790
|
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
* 1. 批量添加成员到组织 - 记录添加成功和添加失败的用户 DID
|
|
3348
|
-
* 2. 创建邀请链接,只创建添加成功的用户邀请链接
|
|
3349
|
-
* 3. 发送邀请通知,只发送添加成功的用户邀请通知
|
|
3350
|
-
* @param {*} param0
|
|
3351
|
-
* @param {*} param0.inviteType 邀请类型,internal 内部邀请,external 外部邀请
|
|
3352
|
-
* @param {*} context
|
|
3353
|
-
* @returns 返回邀请成功的用户和失败的用户 did 列表
|
|
3354
|
-
*/
|
|
3355
|
-
async inviteMembersToOrg({ teamDid, orgId, userDids, role, inviteType = 'internal', email }, context) {
|
|
3356
|
-
try {
|
|
3357
|
-
const state = await this.getOrgState(teamDid);
|
|
3358
|
-
const { user } = context || {};
|
|
3359
|
-
if (!user) {
|
|
3360
|
-
throw new CustomError(400, 'User is required');
|
|
3361
|
-
}
|
|
3362
|
-
const org = await this.getOrg({ teamDid, id: orgId }, context);
|
|
3363
|
-
if (!org) {
|
|
3364
|
-
throw new CustomError(400, 'Org not found');
|
|
3365
|
-
}
|
|
3366
|
-
|
|
3367
|
-
// dashboard 或者非 org owner 无法邀请成员
|
|
3368
|
-
if (isAdmingPath(context) || !isOrgOwner(user, org)) {
|
|
3369
|
-
throw new CustomError(403, "You cannot invite members to other users' org");
|
|
3370
|
-
}
|
|
3371
|
-
|
|
3372
|
-
if (inviteType === 'internal' && userDids.length === 0) {
|
|
3373
|
-
throw new CustomError(400, 'You must invite at least one user');
|
|
3374
|
-
}
|
|
3375
|
-
|
|
3376
|
-
// Step 1: 批量添加成员到组织 - 记录添加成功和添加失败的用户 DID
|
|
3377
|
-
const successUserDids = [];
|
|
3378
|
-
const failedUserDids = [];
|
|
3379
|
-
const skipInviteUserDids = [];
|
|
3380
|
-
|
|
3381
|
-
// 内部邀请
|
|
3382
|
-
if (inviteType === 'internal') {
|
|
3383
|
-
for (const userDid of userDids) {
|
|
3384
|
-
try {
|
|
3385
|
-
// eslint-disable-next-line no-await-in-loop
|
|
3386
|
-
const currentUser = await this.getUser({
|
|
3387
|
-
teamDid,
|
|
3388
|
-
user: { did: userDid },
|
|
3389
|
-
});
|
|
3390
|
-
const walletDid = getWalletDid(currentUser);
|
|
3391
|
-
// 如果当前用户不是钱包用户,则跳过邀请 (参考颁发通行证逻辑)
|
|
3392
|
-
const skipInvite = walletDid !== userDid;
|
|
3393
|
-
if (skipInvite) {
|
|
3394
|
-
skipInviteUserDids.push(userDid);
|
|
3395
|
-
}
|
|
3396
|
-
const status = skipInvite ? 'active' : 'inviting';
|
|
3397
|
-
// eslint-disable-next-line no-await-in-loop
|
|
3398
|
-
await state.addOrgMember({ orgId, userDid, status }, context);
|
|
3399
|
-
successUserDids.push(userDid);
|
|
3400
|
-
} catch (addErr) {
|
|
3401
|
-
failedUserDids.push(userDid);
|
|
3402
|
-
logger.warn('Failed to add user to org', { userDid, orgId, error: addErr.message });
|
|
3403
|
-
}
|
|
3404
|
-
}
|
|
3405
|
-
|
|
3406
|
-
logger.info('Batch add members to org completed', {
|
|
3407
|
-
teamDid,
|
|
3408
|
-
orgId,
|
|
3409
|
-
totalUsers: userDids.length,
|
|
3410
|
-
successCount: successUserDids.length,
|
|
3411
|
-
failedCount: failedUserDids.length,
|
|
3412
|
-
});
|
|
3413
|
-
|
|
3414
|
-
// 如果没有成功添加的用户,直接返回结果
|
|
3415
|
-
if (successUserDids.length === 0) {
|
|
3416
|
-
logger.warn('No users were successfully added to org', { teamDid, orgId, failedUserDids });
|
|
3417
|
-
throw new CustomError(500, 'No users were successfully added to org');
|
|
3418
|
-
}
|
|
3419
|
-
}
|
|
3420
|
-
|
|
3421
|
-
// 要排除 OAuth 用户
|
|
3422
|
-
const inviteUserDids = successUserDids.filter((did) => !skipInviteUserDids.includes(did));
|
|
3423
|
-
|
|
3424
|
-
if (skipInviteUserDids.length > 0) {
|
|
3425
|
-
logger.info('OAuth users were successfully added to org', { teamDid, orgId, skipInviteUserDids });
|
|
3426
|
-
// 向 OAuth 用户颁发通行证
|
|
3427
|
-
for (const userDid of skipInviteUserDids) {
|
|
3428
|
-
// eslint-disable-next-line no-await-in-loop
|
|
3429
|
-
await this.issuePassportToUser({
|
|
3430
|
-
teamDid,
|
|
3431
|
-
userDid,
|
|
3432
|
-
role,
|
|
3433
|
-
notification: {},
|
|
3434
|
-
});
|
|
3435
|
-
}
|
|
3436
|
-
}
|
|
3437
|
-
|
|
3438
|
-
// 如果没有其他用户,那么直接返回即可
|
|
3439
|
-
if (inviteType === 'internal' && inviteUserDids.length === 0) {
|
|
3440
|
-
return {
|
|
3441
|
-
successDids: successUserDids,
|
|
3442
|
-
failedDids: failedUserDids,
|
|
3443
|
-
};
|
|
3444
|
-
}
|
|
3445
|
-
|
|
3446
|
-
// Previous Step 2: 要判断站点是否是站点群内,如果是站点群中,需要使用 master 的 appPid 作为 sourceAppPid 创建邀请链接
|
|
3447
|
-
|
|
3448
|
-
const blocklet = await getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs });
|
|
3449
|
-
const masterBlockletInfo = this.getFederatedMasterBlockletInfo({ blocklet });
|
|
3450
|
-
|
|
3451
|
-
// Step 2: 创建邀请链接,只创建添加成功的用户邀请链接
|
|
3452
|
-
|
|
3453
|
-
const inviteInfo = await this.createMemberInvitation(
|
|
3454
|
-
{
|
|
3455
|
-
teamDid,
|
|
3456
|
-
role,
|
|
3457
|
-
expireTime: this.memberInvitationExpireTime,
|
|
3458
|
-
orgId,
|
|
3459
|
-
inviteUserDids: inviteType === 'internal' ? inviteUserDids : [],
|
|
3460
|
-
sourceAppPid: masterBlockletInfo?.appPid,
|
|
3461
|
-
},
|
|
3462
|
-
context
|
|
3463
|
-
);
|
|
3464
|
-
|
|
3465
|
-
const inviteLink = getOrgInviteLink(inviteInfo, blocklet);
|
|
3466
|
-
|
|
3467
|
-
logger.info('Invite link created for successful users', {
|
|
3468
|
-
teamDid,
|
|
3469
|
-
orgId,
|
|
3470
|
-
inviteId: inviteInfo.inviteId,
|
|
3471
|
-
userCount: successUserDids.length,
|
|
3472
|
-
});
|
|
3473
|
-
|
|
3474
|
-
// Step 3: 发送邀请通知,只发送添加成功的用户邀请通知
|
|
3475
|
-
|
|
3476
|
-
this.sendInvitationNotification({
|
|
3477
|
-
teamDid,
|
|
3478
|
-
invitor: user,
|
|
3479
|
-
org,
|
|
3480
|
-
role,
|
|
3481
|
-
successUserDids,
|
|
3482
|
-
email,
|
|
3483
|
-
inviteType,
|
|
3484
|
-
inviteLink,
|
|
3485
|
-
blocklet,
|
|
3486
|
-
});
|
|
3487
|
-
|
|
3488
|
-
// 返回成功和失败的用户列表
|
|
3489
|
-
return {
|
|
3490
|
-
successDids: successUserDids,
|
|
3491
|
-
failedDids: failedUserDids,
|
|
3492
|
-
inviteLink,
|
|
3493
|
-
};
|
|
3494
|
-
} catch (err) {
|
|
3495
|
-
logger.error('Invite users to org failed', { err, teamDid, orgId, userDids, role });
|
|
3496
|
-
throw err;
|
|
3497
|
-
}
|
|
791
|
+
getSessionState(teamDid) {
|
|
792
|
+
return this.teamManager.getSessionState(teamDid);
|
|
3498
793
|
}
|
|
3499
794
|
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
const state = await this.getOrgState(teamDid);
|
|
3503
|
-
const roles = await this.getRoles({ teamDid, orgId });
|
|
3504
|
-
const result = await state.removeOrgMember({ orgId, userDid }, context);
|
|
3505
|
-
try {
|
|
3506
|
-
await state.removeOrgRelatedData({ roles, orgId, userDid });
|
|
3507
|
-
} catch (err) {
|
|
3508
|
-
logger.error('Failed to remove user related passports', { err, teamDid, roles, userDid });
|
|
3509
|
-
}
|
|
3510
|
-
return result;
|
|
3511
|
-
} catch (err) {
|
|
3512
|
-
logger.error('Remove member from org failed', { err, teamDid, orgId, userDid });
|
|
3513
|
-
throw err;
|
|
3514
|
-
}
|
|
795
|
+
getNotificationState(teamDid) {
|
|
796
|
+
return this.teamManager.getNotificationState(teamDid);
|
|
3515
797
|
}
|
|
3516
798
|
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
const state = await this.getOrgState(teamDid);
|
|
3520
|
-
const result = await state.getOrgMembers({ orgId, paging, options: { includePassport: true } }, context);
|
|
3521
|
-
const info = await this.node.read();
|
|
3522
|
-
const isServer = this.teamManager.isNodeTeam(teamDid);
|
|
3523
|
-
const blocklet = await getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs });
|
|
3524
|
-
const baseUrl = blocklet.environmentObj.BLOCKLET_APP_URL;
|
|
3525
|
-
const roles = await this.getRoles({ teamDid, orgId });
|
|
3526
|
-
result.users.forEach((item) => {
|
|
3527
|
-
if (item?.user?.avatar) {
|
|
3528
|
-
item.user.avatar = getUserAvatarUrl(baseUrl, item.user.avatar, info, isServer);
|
|
3529
|
-
}
|
|
3530
|
-
if (item?.user?.passports?.length > 0) {
|
|
3531
|
-
item.user.passports = item.user.passports.filter((passport) =>
|
|
3532
|
-
roles.some((role) => role.name === passport.name)
|
|
3533
|
-
);
|
|
3534
|
-
}
|
|
3535
|
-
});
|
|
3536
|
-
|
|
3537
|
-
return result;
|
|
3538
|
-
} catch (err) {
|
|
3539
|
-
logger.error('Get org members failed', { err, teamDid, orgId });
|
|
3540
|
-
throw err;
|
|
3541
|
-
}
|
|
799
|
+
getTagState(teamDid) {
|
|
800
|
+
return this.teamManager.getTagState(teamDid);
|
|
3542
801
|
}
|
|
3543
802
|
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
const state = await this.getOrgState(teamDid);
|
|
3547
|
-
return state.getOrgInvitableUsers({ id, query, paging }, context);
|
|
3548
|
-
} catch (err) {
|
|
3549
|
-
logger.error('Get org invitable users failed', { err, teamDid, id });
|
|
3550
|
-
throw err;
|
|
3551
|
-
}
|
|
803
|
+
getTaggingState(teamDid) {
|
|
804
|
+
return this.teamManager.getTaggingState(teamDid);
|
|
3552
805
|
}
|
|
3553
806
|
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
const state = await this.getOrgState(teamDid);
|
|
3557
|
-
return state.getOrgResource({ orgId, resourceId }, context);
|
|
3558
|
-
} catch (err) {
|
|
3559
|
-
logger.error('Get org resource failed', { err, teamDid, orgId, resourceId });
|
|
3560
|
-
throw err;
|
|
3561
|
-
}
|
|
807
|
+
getVerifyCodeState(teamDid) {
|
|
808
|
+
return this.teamManager.getVerifyCodeState(teamDid);
|
|
3562
809
|
}
|
|
3563
810
|
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
const state = await this.getOrgState(teamDid);
|
|
3567
|
-
return state.addOrgResource({ orgId, resourceIds, type, metadata }, context);
|
|
3568
|
-
} catch (err) {
|
|
3569
|
-
logger.error('Add org resource failed', { err, teamDid, orgId, resourceIds, type, metadata });
|
|
3570
|
-
throw err;
|
|
3571
|
-
}
|
|
811
|
+
getAccessKeyState(teamDid) {
|
|
812
|
+
return this.teamManager.getAccessKeyState(teamDid);
|
|
3572
813
|
}
|
|
3573
814
|
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
const state = await this.getOrgState(teamDid);
|
|
3577
|
-
return state.removeOrgResource({ orgId, resourceIds }, context);
|
|
3578
|
-
} catch (err) {
|
|
3579
|
-
logger.error('Remove org resource failed', { err, teamDid, orgId, resourceIds });
|
|
3580
|
-
throw err;
|
|
3581
|
-
}
|
|
815
|
+
getOrgState(teamDid) {
|
|
816
|
+
return this.teamManager.getOrgState(teamDid);
|
|
3582
817
|
}
|
|
3583
818
|
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
const state = await this.getOrgState(teamDid);
|
|
3587
|
-
return state.migrateOrgResource({ from, to, resourceIds }, context);
|
|
3588
|
-
} catch (err) {
|
|
3589
|
-
logger.error('Migrate org resource failed', { err, teamDid, from, to, resourceIds });
|
|
3590
|
-
throw err;
|
|
3591
|
-
}
|
|
819
|
+
getUserSessionState(teamDid) {
|
|
820
|
+
return this.teamManager.getUserSessionState(teamDid);
|
|
3592
821
|
}
|
|
3593
822
|
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
if (since && typeof since === 'string') {
|
|
3598
|
-
const sinceMatch = since.match(/^(\d+)h$/);
|
|
3599
|
-
if (sinceMatch) {
|
|
3600
|
-
let hours = parseInt(sinceMatch[1], 10);
|
|
3601
|
-
if (hours < 1 || hours > 24) {
|
|
3602
|
-
hours = Math.min(Math.max(hours, 1), 24);
|
|
3603
|
-
}
|
|
3604
|
-
startTime = dayjs().subtract(hours, 'hours').toDate();
|
|
3605
|
-
}
|
|
3606
|
-
}
|
|
3607
|
-
|
|
3608
|
-
try {
|
|
3609
|
-
const state = await this.getNotificationState(teamDid);
|
|
3610
|
-
const isServer = this.teamManager.isNodeTeam(teamDid);
|
|
3611
|
-
const blocklet = isServer
|
|
3612
|
-
? {}
|
|
3613
|
-
: await getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs });
|
|
3614
|
-
const channelsAvailable = checkPushChannelAvailable(blocklet, isServer);
|
|
3615
|
-
const results = await state.getNotificationsBySince({ since });
|
|
3616
|
-
|
|
3617
|
-
if (results.length === 0) {
|
|
3618
|
-
return {
|
|
3619
|
-
healthy: true,
|
|
3620
|
-
message: `There have been no push records since ${startTime}. Please choose another time range`,
|
|
3621
|
-
since: startTime,
|
|
3622
|
-
channels: channelsAvailable,
|
|
3623
|
-
};
|
|
3624
|
-
}
|
|
3625
|
-
|
|
3626
|
-
const pushState = getNotificationPushState(results, channelsAvailable, isServer);
|
|
3627
|
-
|
|
3628
|
-
let teamDids = [teamDid];
|
|
3629
|
-
if (isServer) {
|
|
3630
|
-
const nodeInfo = await this.node.read();
|
|
3631
|
-
teamDids = [nodeInfo.did];
|
|
3632
|
-
} else {
|
|
3633
|
-
teamDids = getBlockletAppIdList(blocklet);
|
|
3634
|
-
}
|
|
3635
|
-
|
|
3636
|
-
const pendingResult = await this.states.job.getPendingNotifications({
|
|
3637
|
-
teamDids,
|
|
3638
|
-
isServer,
|
|
3639
|
-
channels: Object.keys(channelsAvailable),
|
|
3640
|
-
createdAt: startTime,
|
|
3641
|
-
});
|
|
3642
|
-
|
|
3643
|
-
// pushState 的 key 与 pendingResult 的 key 映射关系
|
|
3644
|
-
const channelKeyMap = {
|
|
3645
|
-
pushKit: NOTIFICATION_SEND_CHANNEL.PUSH,
|
|
3646
|
-
wallet: NOTIFICATION_SEND_CHANNEL.WALLET,
|
|
3647
|
-
email: NOTIFICATION_SEND_CHANNEL.EMAIL,
|
|
3648
|
-
webhook: NOTIFICATION_SEND_CHANNEL.WEBHOOK,
|
|
3649
|
-
};
|
|
3650
|
-
|
|
3651
|
-
// 合并 pending 数量到对应的 channel
|
|
3652
|
-
const channels = Object.entries(pushState).reduce((acc, [key, value]) => {
|
|
3653
|
-
const pendingKey = channelKeyMap[key] || key;
|
|
3654
|
-
acc[key] = {
|
|
3655
|
-
...value,
|
|
3656
|
-
pending: pendingResult[pendingKey] || 0,
|
|
3657
|
-
};
|
|
3658
|
-
return acc;
|
|
3659
|
-
}, {});
|
|
3660
|
-
|
|
3661
|
-
return {
|
|
3662
|
-
healthy: true,
|
|
3663
|
-
since: startTime,
|
|
3664
|
-
channels,
|
|
3665
|
-
};
|
|
3666
|
-
} catch (err) {
|
|
3667
|
-
logger.error('Get notification service health failed', err, { teamDid });
|
|
3668
|
-
return {
|
|
3669
|
-
healthy: false,
|
|
3670
|
-
error: err.message,
|
|
3671
|
-
since: startTime,
|
|
3672
|
-
};
|
|
3673
|
-
}
|
|
823
|
+
// ============ Just for test ============
|
|
824
|
+
setInviteExpireTime(ms) {
|
|
825
|
+
this.memberInviteExpireTime = ms;
|
|
3674
826
|
}
|
|
3675
827
|
}
|
|
3676
828
|
|