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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/lib/api/team/access-key-manager.js +104 -0
  2. package/lib/api/team/invitation-manager.js +461 -0
  3. package/lib/api/team/notification-manager.js +189 -0
  4. package/lib/api/team/oauth-manager.js +60 -0
  5. package/lib/api/team/org-crud-manager.js +202 -0
  6. package/lib/api/team/org-manager.js +56 -0
  7. package/lib/api/team/org-member-manager.js +403 -0
  8. package/lib/api/team/org-query-manager.js +126 -0
  9. package/lib/api/team/org-resource-manager.js +186 -0
  10. package/lib/api/team/passport-manager.js +670 -0
  11. package/lib/api/team/rbac-manager.js +335 -0
  12. package/lib/api/team/session-manager.js +540 -0
  13. package/lib/api/team/store-manager.js +198 -0
  14. package/lib/api/team/tag-manager.js +230 -0
  15. package/lib/api/team/user-auth-manager.js +132 -0
  16. package/lib/api/team/user-manager.js +78 -0
  17. package/lib/api/team/user-query-manager.js +299 -0
  18. package/lib/api/team/user-social-manager.js +354 -0
  19. package/lib/api/team/user-update-manager.js +224 -0
  20. package/lib/api/team/verify-code-manager.js +161 -0
  21. package/lib/api/team.js +439 -3287
  22. package/lib/blocklet/manager/disk/auth-manager.js +68 -0
  23. package/lib/blocklet/manager/disk/backup-manager.js +288 -0
  24. package/lib/blocklet/manager/disk/cleanup-manager.js +157 -0
  25. package/lib/blocklet/manager/disk/component-manager.js +83 -0
  26. package/lib/blocklet/manager/disk/config-manager.js +191 -0
  27. package/lib/blocklet/manager/disk/controller-manager.js +64 -0
  28. package/lib/blocklet/manager/disk/delete-reset-manager.js +328 -0
  29. package/lib/blocklet/manager/disk/download-manager.js +96 -0
  30. package/lib/blocklet/manager/disk/env-config-manager.js +311 -0
  31. package/lib/blocklet/manager/disk/federated-manager.js +651 -0
  32. package/lib/blocklet/manager/disk/hook-manager.js +124 -0
  33. package/lib/blocklet/manager/disk/install-component-manager.js +95 -0
  34. package/lib/blocklet/manager/disk/install-core-manager.js +448 -0
  35. package/lib/blocklet/manager/disk/install-download-manager.js +313 -0
  36. package/lib/blocklet/manager/disk/install-manager.js +36 -0
  37. package/lib/blocklet/manager/disk/install-upgrade-manager.js +340 -0
  38. package/lib/blocklet/manager/disk/job-manager.js +467 -0
  39. package/lib/blocklet/manager/disk/lifecycle-manager.js +26 -0
  40. package/lib/blocklet/manager/disk/notification-manager.js +343 -0
  41. package/lib/blocklet/manager/disk/query-manager.js +562 -0
  42. package/lib/blocklet/manager/disk/settings-manager.js +507 -0
  43. package/lib/blocklet/manager/disk/start-manager.js +611 -0
  44. package/lib/blocklet/manager/disk/stop-restart-manager.js +292 -0
  45. package/lib/blocklet/manager/disk/update-manager.js +153 -0
  46. package/lib/blocklet/manager/disk.js +669 -5796
  47. package/lib/blocklet/manager/helper/blue-green-start-blocklet.js +5 -0
  48. package/lib/blocklet/manager/lock.js +18 -0
  49. package/lib/event/index.js +28 -24
  50. package/lib/util/blocklet/app-utils.js +192 -0
  51. package/lib/util/blocklet/blocklet-loader.js +258 -0
  52. package/lib/util/blocklet/config-manager.js +232 -0
  53. package/lib/util/blocklet/did-document.js +240 -0
  54. package/lib/util/blocklet/environment.js +555 -0
  55. package/lib/util/blocklet/health-check.js +449 -0
  56. package/lib/util/blocklet/install-utils.js +365 -0
  57. package/lib/util/blocklet/logo.js +57 -0
  58. package/lib/util/blocklet/meta-utils.js +269 -0
  59. package/lib/util/blocklet/port-manager.js +141 -0
  60. package/lib/util/blocklet/process-manager.js +504 -0
  61. package/lib/util/blocklet/runtime-info.js +105 -0
  62. package/lib/util/blocklet/validation.js +418 -0
  63. package/lib/util/blocklet.js +98 -3066
  64. package/lib/util/wallet-app-notification.js +40 -0
  65. package/package.json +22 -22
@@ -0,0 +1,540 @@
1
+ const defaults = require('lodash/defaults');
2
+ const logger = require('@abtnode/logger')('@abtnode/core:api:team:session');
3
+ const { Op } = require('sequelize');
4
+ const { USER_SESSION_STATUS, SESSION_TTL } = require('@abtnode/constant');
5
+ const { BlockletEvents } = require('@blocklet/constant');
6
+ const { getBlockletInfo } = require('@blocklet/meta/lib/info');
7
+ const { callFederated, getFederatedMaster, findFederatedSite } = require('@abtnode/auth/lib/util/federated');
8
+
9
+ const { getBlocklet } = require('../../util/blocklet');
10
+
11
+ /**
12
+ * Get user session where clause
13
+ * @param {Object} params
14
+ * @param {string} params.status - Session status
15
+ * @param {Object} params.blocklet - Blocklet
16
+ * @returns {Object}
17
+ */
18
+ const getUserSessionWhere = ({ status, blocklet }) => {
19
+ const now = Date.now();
20
+ const sessionTtl = blocklet.settings?.session?.ttl || SESSION_TTL;
21
+ if (status === USER_SESSION_STATUS.ONLINE) {
22
+ return {
23
+ updatedAt: {
24
+ [Op.gt]: new Date(now - sessionTtl * 1000),
25
+ },
26
+ status: {
27
+ [Op.ne]: USER_SESSION_STATUS.OFFLINE,
28
+ },
29
+ };
30
+ }
31
+ if (status === USER_SESSION_STATUS.EXPIRED) {
32
+ return {
33
+ updatedAt: {
34
+ [Op.lt]: new Date(now - sessionTtl * 1000),
35
+ },
36
+ status: {
37
+ [Op.ne]: USER_SESSION_STATUS.OFFLINE,
38
+ },
39
+ };
40
+ }
41
+ if (status === USER_SESSION_STATUS.OFFLINE) {
42
+ return {
43
+ status: USER_SESSION_STATUS.OFFLINE,
44
+ };
45
+ }
46
+ return {};
47
+ };
48
+
49
+ /**
50
+ * Get user session
51
+ * @param {Object} api - TeamAPI instance
52
+ * @param {Object} params
53
+ * @param {string} params.teamDid - Team DID
54
+ * @param {string} params.id - Session ID
55
+ * @param {boolean} params.includeUser - Include user data
56
+ * @returns {Promise<Object>}
57
+ */
58
+ async function getUserSession(api, { teamDid, id, includeUser = false }) {
59
+ const state = await api.getUserSessionState(teamDid);
60
+ const userState = await api.getUserState(teamDid);
61
+
62
+ const where = {
63
+ status: USER_SESSION_STATUS.ONLINE,
64
+ id,
65
+ };
66
+ const include = [];
67
+ if (includeUser) {
68
+ include.push({
69
+ model: userState.model,
70
+ as: 'user',
71
+ attributes: ['did', 'pk', 'sourceProvider', 'fullName', 'email', 'avatar', 'remark', 'sourceAppPid', 'approved'],
72
+ });
73
+ }
74
+
75
+ const userSession = await state.model.findOne({
76
+ where,
77
+ attributes: [
78
+ 'id',
79
+ 'appPid',
80
+ 'userDid',
81
+ 'visitorId',
82
+ 'passportId',
83
+ 'createdAt',
84
+ 'updatedAt',
85
+ 'extra',
86
+ 'ua',
87
+ 'lastLoginIp',
88
+ 'status',
89
+ ],
90
+ include,
91
+ });
92
+ return userSession?.toJSON?.();
93
+ }
94
+
95
+ /**
96
+ * Get user sessions
97
+ * @param {Object} api - TeamAPI instance
98
+ * @param {Object} params
99
+ * @param {string} params.teamDid - Team DID
100
+ * @param {Object} params.query - Query options
101
+ * @param {Object} params.paging - Paging options
102
+ * @param {Object} params.sort - Sort options
103
+ * @returns {Promise<Object>}
104
+ */
105
+ async function getUserSessions(api, { teamDid, query = {}, paging, sort }) {
106
+ const nodeInfo = await api.node.read({ useCache: true });
107
+ if (teamDid === nodeInfo.did) {
108
+ return {
109
+ list: [],
110
+ paging: {
111
+ page: 1,
112
+ pageCount: 0,
113
+ pageSize: 10,
114
+ total: 0,
115
+ },
116
+ };
117
+ }
118
+
119
+ const { userDid, visitorId, appPid, includeUser = false, status = USER_SESSION_STATUS.ONLINE } = query;
120
+ const state = await api.getUserSessionState(teamDid);
121
+ const userState = await api.getUserState(teamDid);
122
+ const where = {};
123
+ if (userDid) where.userDid = userDid;
124
+ if (visitorId) where.visitorId = visitorId;
125
+ if (appPid) where.appPid = appPid;
126
+
127
+ const blocklet = await getBlocklet({ did: teamDid, states: api.states, dataDirs: api.dataDirs, useCache: true });
128
+ const whereStatus = getUserSessionWhere({ status, blocklet });
129
+ Object.assign(where, whereStatus);
130
+ const include = [];
131
+ if (includeUser) {
132
+ include.push({
133
+ model: userState.model,
134
+ as: 'user',
135
+ attributes: ['did', 'pk', 'sourceProvider', 'fullName', 'email', 'avatar', 'remark', 'sourceAppPid', 'approved'],
136
+ });
137
+ }
138
+
139
+ const result = await state.paginate(
140
+ {
141
+ where,
142
+ attributes: [
143
+ 'id',
144
+ 'appPid',
145
+ 'userDid',
146
+ 'visitorId',
147
+ 'passportId',
148
+ 'createdAt',
149
+ 'updatedAt',
150
+ 'extra',
151
+ 'ua',
152
+ 'lastLoginIp',
153
+ 'status',
154
+ ],
155
+ include,
156
+ },
157
+ { updatedAt: -1, ...sort },
158
+ { pageSize: 10, ...paging }
159
+ );
160
+
161
+ // NOTICE: 保留结构,方便理解
162
+ return {
163
+ list: result.list,
164
+ paging: result.paging,
165
+ };
166
+ }
167
+
168
+ /**
169
+ * Get user sessions count
170
+ * @param {Object} api - TeamAPI instance
171
+ * @param {Object} params
172
+ * @param {string} params.teamDid - Team DID
173
+ * @param {Object} params.query - Query options
174
+ * @returns {Promise<Object>}
175
+ */
176
+ async function getUserSessionsCount(api, { teamDid, query = {} }) {
177
+ const nodeInfo = await api.node.read();
178
+ if (teamDid === nodeInfo.did) {
179
+ return {
180
+ count: 0,
181
+ };
182
+ }
183
+ const { userDid, visitorId, appPid, status = USER_SESSION_STATUS.ONLINE } = query;
184
+ const state = await api.getUserSessionState(teamDid);
185
+ const where = {};
186
+ if (userDid) where.userDid = userDid;
187
+ if (visitorId) where.visitorId = visitorId;
188
+ if (appPid) where.appPid = appPid;
189
+
190
+ const blocklet = await getBlocklet({ did: teamDid, states: api.states, dataDirs: api.dataDirs, useCache: true });
191
+ const whereStatus = getUserSessionWhere({ status, blocklet });
192
+ Object.assign(where, whereStatus);
193
+
194
+ // HACK: 使用 state.count 并不能按预期工作,先改为使用 state.model.count 来实现
195
+ const result = await state.model.count({ where });
196
+ return {
197
+ count: result,
198
+ };
199
+ }
200
+
201
+ /**
202
+ * Get passport by ID
203
+ * @param {Object} api - TeamAPI instance
204
+ * @param {Object} params
205
+ * @param {string} params.teamDid - Team DID
206
+ * @param {string} params.passportId - Passport ID
207
+ * @returns {Promise<Object>}
208
+ */
209
+ async function getPassportById(api, { teamDid, passportId }) {
210
+ const userState = await api.getUserState(teamDid);
211
+ const passport = await userState.getPassportById(passportId);
212
+ return passport;
213
+ }
214
+
215
+ /**
216
+ * Get passport from federated
217
+ * @param {Object} api - TeamAPI instance
218
+ * @param {Object} params
219
+ * @param {Object} params.site - Site
220
+ * @param {string} params.passportId - Passport ID
221
+ * @param {string} params.teamDid - Team DID
222
+ * @returns {Promise<Object>}
223
+ */
224
+ async function getPassportFromFederated(api, { site, passportId, teamDid }) {
225
+ try {
226
+ const blocklet = await getBlocklet({
227
+ did: teamDid,
228
+ states: api.states,
229
+ dataDirs: api.dataDirs,
230
+ useCache: true,
231
+ });
232
+ const nodeInfo = await api.node.read();
233
+ const { permanentWallet } = getBlockletInfo(blocklet, nodeInfo.sk);
234
+
235
+ const result = await callFederated({
236
+ action: 'getPassport',
237
+ data: {
238
+ passportId,
239
+ },
240
+ permanentWallet,
241
+ site,
242
+ requestOptions: {
243
+ // 缩短查询通行证的请求时间,这个请求不会很复杂
244
+ timeout: 3 * 1000,
245
+ },
246
+ });
247
+
248
+ return result;
249
+ } catch (error) {
250
+ // 吞没错误,查询失败也不会影响整个快捷登录流程
251
+ logger.error('Failed to getPassportFromFederated', { site, passportId, teamDid, error });
252
+ return null;
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Upsert user session
258
+ * @param {Object} api - TeamAPI instance
259
+ * @param {Object} params
260
+ * @param {string} params.teamDid - Team DID
261
+ * @param {string} params.appPid - App PID
262
+ * @param {string} params.userDid - User DID
263
+ * @param {string} params.visitorId - Visitor ID
264
+ * @param {string} params.ua - User agent
265
+ * @param {string} params.lastLoginIp - Last login IP
266
+ * @param {string} params.passportId - Passport ID
267
+ * @param {string} params.status - Status
268
+ * @param {Object} params.extra - Extra data
269
+ * @param {string} params.locale - Locale
270
+ * @param {string} params.origin - Origin
271
+ * @param {Object} options
272
+ * @param {boolean} options.skipNotification - Skip notification
273
+ * @returns {Promise<Object>}
274
+ */
275
+ async function upsertUserSession(
276
+ api,
277
+ { teamDid, appPid, userDid, visitorId, ua, lastLoginIp, passportId, status, extra, locale, origin },
278
+ { skipNotification = false } = {}
279
+ ) {
280
+ if (!userDid) {
281
+ throw new Error('userDid is required');
282
+ }
283
+ if (visitorId) {
284
+ if (visitorId.length > 80) {
285
+ throw new Error('visitorId should be less than 80 characters');
286
+ }
287
+ }
288
+
289
+ const state = await api.getUserSessionState(teamDid);
290
+ let data;
291
+ if (!visitorId) {
292
+ data = await state.insert({
293
+ userDid,
294
+ ua,
295
+ lastLoginIp,
296
+ appPid,
297
+ passportId,
298
+ extra,
299
+ });
300
+ logger.info('insert userSession successfully', { userDid, ua, lastLoginIp, appPid, passportId, extra });
301
+ api.emit(BlockletEvents.addUserSession, {
302
+ userSession: data,
303
+ teamDid,
304
+ userDid,
305
+ locale,
306
+ skipNotification,
307
+ origin,
308
+ });
309
+ } else {
310
+ const exist = await state.findOne({ userDid, visitorId, appPid });
311
+ if (exist) {
312
+ const mergeExtra = defaults({}, extra || {}, exist.extra || {});
313
+ [, [data]] = await state.update(exist.id, {
314
+ passportId,
315
+ lastLoginIp,
316
+ ua,
317
+ status,
318
+ extra: mergeExtra,
319
+ });
320
+ logger.info('update userSession successfully', {
321
+ id: exist.id,
322
+ ua,
323
+ lastLoginIp,
324
+ passportId,
325
+ status,
326
+ extra: mergeExtra,
327
+ });
328
+ if (Date.now() - new Date(exist.createdAt).getTime() < 1000 * 10) {
329
+ // HACK: 此处是为了矫正首次创建 userSession 没有 ua 的情况,会通过 /api/did/session 接口来更新 ua 信息,这个时候才能当成新增 userSession 来发送通知
330
+ api.emit(BlockletEvents.addUserSession, {
331
+ userSession: data,
332
+ teamDid,
333
+ userDid,
334
+ locale,
335
+ skipNotification,
336
+ origin,
337
+ });
338
+ } else {
339
+ api.emit(BlockletEvents.updateUserSession, {
340
+ userSession: data,
341
+ teamDid,
342
+ userDid,
343
+ skipNotification,
344
+ origin,
345
+ });
346
+ }
347
+ } else {
348
+ data = await state.insert({
349
+ visitorId,
350
+ userDid,
351
+ ua,
352
+ lastLoginIp,
353
+ appPid,
354
+ passportId,
355
+ extra,
356
+ });
357
+ logger.info('insert userSession successfully', {
358
+ visitorId,
359
+ userDid,
360
+ ua,
361
+ lastLoginIp,
362
+ appPid,
363
+ passportId,
364
+ extra,
365
+ });
366
+ api.emit(BlockletEvents.addUserSession, {
367
+ userSession: data,
368
+ teamDid,
369
+ userDid,
370
+ locale,
371
+ skipNotification,
372
+ origin,
373
+ });
374
+ }
375
+ }
376
+
377
+ return data;
378
+ }
379
+
380
+ /**
381
+ * Sync user session
382
+ * @param {Object} api - TeamAPI instance
383
+ * @param {Object} params
384
+ * @param {string} params.teamDid - Team DID
385
+ * @param {string} params.targetAppPid - Target app PID
386
+ * @param {string} params.userDid - User DID
387
+ * @param {string} params.visitorId - Visitor ID
388
+ * @param {string} params.ua - User agent
389
+ * @param {string} params.lastLoginIp - Last login IP
390
+ * @param {string} params.passportId - Passport ID
391
+ * @param {Object} params.extra - Extra data
392
+ * @returns {Promise<void>}
393
+ */
394
+ async function syncUserSession(api, { teamDid, targetAppPid, userDid, visitorId, ua, lastLoginIp, passportId, extra }) {
395
+ if (!targetAppPid) return;
396
+
397
+ const blocklet = await getBlocklet({ did: teamDid, states: api.states, dataDirs: api.dataDirs, useCache: true });
398
+ const nodeInfo = await api.node.read();
399
+ const { permanentWallet } = getBlockletInfo(blocklet, nodeInfo.sk);
400
+
401
+ const targetSite = findFederatedSite(blocklet, targetAppPid);
402
+ const masterSite = getFederatedMaster(blocklet);
403
+
404
+ let syncSite = null;
405
+ let appPid;
406
+ if (masterSite && targetSite) {
407
+ if (targetSite.appPid === masterSite.appPid) {
408
+ if (teamDid !== masterSite.appPid) {
409
+ syncSite = masterSite;
410
+ appPid = teamDid;
411
+ }
412
+ } else if (teamDid === masterSite.appPid) {
413
+ syncSite = targetSite;
414
+ appPid = targetAppPid;
415
+ }
416
+
417
+ if (syncSite) {
418
+ try {
419
+ const userSession = {
420
+ action: 'login',
421
+ userDid,
422
+ visitorId,
423
+ ua,
424
+ lastLoginIp,
425
+ appPid,
426
+ passportId,
427
+ extra,
428
+ };
429
+ await callFederated({
430
+ action: 'sync',
431
+ permanentWallet,
432
+ site: targetSite,
433
+ data: {
434
+ userSessions: [userSession],
435
+ },
436
+ });
437
+ logger.debug('Sync userSession to federated site successfully', {
438
+ userDid,
439
+ visitorId,
440
+ ua,
441
+ lastLoginIp,
442
+ appPid,
443
+ passportId,
444
+ targetSite,
445
+ extra,
446
+ });
447
+ } catch (err) {
448
+ logger.error(
449
+ 'Sync userSession to federated site failed',
450
+ {
451
+ userDid,
452
+ visitorId,
453
+ ua,
454
+ lastLoginIp,
455
+ appPid,
456
+ passportId,
457
+ targetSite,
458
+ extra,
459
+ },
460
+ err
461
+ );
462
+ }
463
+ }
464
+ }
465
+ }
466
+
467
+ /**
468
+ * Logout user
469
+ * @param {Object} api - TeamAPI instance
470
+ * @param {Object} params
471
+ * @param {string} params.teamDid - Team DID
472
+ * @param {string} params.userDid - User DID
473
+ * @param {string} params.visitorId - Visitor ID
474
+ * @param {string} params.appPid - App PID
475
+ * @param {string} params.status - Status
476
+ * @param {boolean} params.remove - Remove session
477
+ * @returns {Promise<number>}
478
+ */
479
+ async function logoutUser(api, { teamDid, userDid, visitorId = '', appPid = '', status = '', remove = false }) {
480
+ if (!userDid) {
481
+ throw new Error('userDid is required');
482
+ }
483
+ if (!teamDid) {
484
+ throw new Error('teamDid is required');
485
+ }
486
+
487
+ const nodeInfo = await api.node.read();
488
+ if (nodeInfo.did === teamDid) {
489
+ return 0;
490
+ }
491
+ const userSessionState = await api.getUserSessionState(teamDid);
492
+ const where = { userDid };
493
+ if (visitorId) where.visitorId = visitorId;
494
+ if (appPid) where.appPid = appPid;
495
+
496
+ const blocklet = await getBlocklet({ did: teamDid, states: api.states, dataDirs: api.dataDirs, useCache: true });
497
+ const whereStatus = getUserSessionWhere({ status, blocklet });
498
+ Object.assign(where, whereStatus);
499
+ let result;
500
+ if (remove) {
501
+ result = await userSessionState.remove(where);
502
+ } else {
503
+ result = await userSessionState.update(where, { $set: { status: USER_SESSION_STATUS.OFFLINE } });
504
+ }
505
+ const { permanentWallet } = getBlockletInfo(blocklet, nodeInfo.sk);
506
+ const masterSite = getFederatedMaster(blocklet);
507
+ if (masterSite && masterSite.isMaster !== false && masterSite.appPid !== teamDid) {
508
+ callFederated({
509
+ action: 'sync',
510
+ permanentWallet,
511
+ // FIXME: @zhanghan 是否还需要通知其他 Member 站点,执行退出登录的操作
512
+ site: masterSite,
513
+ data: {
514
+ userSessions: [
515
+ {
516
+ action: 'logout',
517
+ userDid,
518
+ visitorId,
519
+ // HACK: 如果未传入 appPid,代表要注销当前用户在 master 中所有登录状态,所以传入 undefined;否则就只注销 master 中记录的当前 member 的登录状态
520
+ appPid: appPid ? teamDid : undefined,
521
+ remove,
522
+ },
523
+ ],
524
+ },
525
+ });
526
+ }
527
+ return result;
528
+ }
529
+
530
+ module.exports = {
531
+ getUserSessionWhere,
532
+ getUserSession,
533
+ getUserSessions,
534
+ getUserSessionsCount,
535
+ getPassportById,
536
+ getPassportFromFederated,
537
+ upsertUserSession,
538
+ syncUserSession,
539
+ logoutUser,
540
+ };