@abtnode/blocklet-services 1.16.13 → 1.16.14-beta-0c29907f

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 (101) hide show
  1. package/api/index.js +5 -0
  2. package/api/libs/auth/utils.js +6 -8
  3. package/api/libs/connect/session.js +75 -23
  4. package/api/libs/connect/shared.js +12 -1
  5. package/api/libs/connect/v1.js +32 -3
  6. package/api/libs/jwt.js +8 -3
  7. package/api/middlewares/check-federated-cors-call.js +15 -0
  8. package/api/middlewares/ensure-blocklet.js +13 -0
  9. package/api/middlewares/verify-federated-call.js +35 -0
  10. package/api/routes/blocklet.js +19 -6
  11. package/api/routes/federated.js +365 -0
  12. package/api/routes/oauth.js +8 -7
  13. package/api/services/auth/connect/invite.js +2 -1
  14. package/api/services/auth/connect/issue-passport.js +2 -8
  15. package/api/services/auth/connect/login.js +9 -1
  16. package/api/services/auth/connect/receive-transfer-app-owner.js +3 -1
  17. package/api/services/auth/session.js +3 -1
  18. package/api/services/notification/blocklet-events-notifier.js +79 -28
  19. package/api/util/blocklet-utils.js +27 -0
  20. package/api/util/federated.js +41 -0
  21. package/build/asset-manifest.json +76 -73
  22. package/build/index.html +1 -1
  23. package/build/service-worker.js +1 -1
  24. package/build/service-worker.js.map +1 -1
  25. package/build/static/css/{8730.ecc708b7.chunk.css → 2130.b9437a64.chunk.css} +1 -1
  26. package/build/static/css/4603.dce369d5.chunk.css +2 -0
  27. package/build/static/js/1162.ff3136ae.chunk.js +2 -0
  28. package/build/static/js/1237.1d0b8f1e.chunk.js +3 -0
  29. package/build/static/js/2130.59fef4ad.chunk.js +3 -0
  30. package/build/static/js/3242.5dea77f3.chunk.js +2 -0
  31. package/build/static/js/3464.12806bfe.chunk.js +2 -0
  32. package/build/static/js/{3800.a4ffc6c2.chunk.js → 3800.faac00ca.chunk.js} +2 -2
  33. package/build/static/js/{3953.b81b810f.chunk.js → 3953.e3e51520.chunk.js} +2 -2
  34. package/build/static/js/3963.2e06f9bc.chunk.js +3 -0
  35. package/build/static/js/4319.e99b5af9.chunk.js +2 -0
  36. package/build/static/js/{445.4ee5d0c9.chunk.js → 445.6da52f3e.chunk.js} +3 -3
  37. package/build/static/js/4492.97b0d4f6.chunk.js +2 -0
  38. package/build/static/js/4547.b26ceabb.chunk.js +2 -0
  39. package/build/static/js/4603.f6b8d272.chunk.js +2 -0
  40. package/build/static/js/4651.0a58cbc3.chunk.js +3 -0
  41. package/build/static/js/5050.538edf89.chunk.js +3 -0
  42. package/build/static/js/5052.91c69c75.chunk.js +2 -0
  43. package/build/static/js/5176.68fd3fde.chunk.js +2 -0
  44. package/build/static/js/5233.faabaae9.chunk.js +2 -0
  45. package/build/static/js/5430.8114614c.chunk.js +2 -0
  46. package/build/static/js/5645.a17cd258.chunk.js +2 -0
  47. package/build/static/js/5711.aa17ef28.chunk.js +2 -0
  48. package/build/static/js/6315.59e078f6.chunk.js +2 -0
  49. package/build/static/js/652.5fc00cd3.chunk.js +2 -0
  50. package/build/static/js/6737.4f37dd4a.chunk.js +2 -0
  51. package/build/static/js/6891.6c7018ab.chunk.js +2 -0
  52. package/build/static/js/7345.4b4f8941.chunk.js +3 -0
  53. package/build/static/js/7371.da3c5cc3.chunk.js +2 -0
  54. package/build/static/js/{766.724b9aea.chunk.js → 766.5dd16d47.chunk.js} +2 -2
  55. package/build/static/js/8031.d9af3d84.chunk.js +2 -0
  56. package/build/static/js/8395.e5aa519c.chunk.js +2 -0
  57. package/build/static/js/868.aa2a1c4d.chunk.js +2 -0
  58. package/build/static/js/9476.bc480cfc.chunk.js +2 -0
  59. package/build/static/js/982.11dc355f.chunk.js +3 -0
  60. package/build/static/js/main.efa52345.js +3 -0
  61. package/build/static/media/index.20414a3fa1e6c5498a67.cjs +1 -0
  62. package/package.json +36 -35
  63. package/build/static/js/1162.c3a07ee0.chunk.js +0 -2
  64. package/build/static/js/1237.03f3a68a.chunk.js +0 -3
  65. package/build/static/js/1610.13323d09.chunk.js +0 -2
  66. package/build/static/js/2793.00991f08.chunk.js +0 -2
  67. package/build/static/js/2961.8237c11f.chunk.js +0 -3
  68. package/build/static/js/3242.86fc9bc6.chunk.js +0 -2
  69. package/build/static/js/338.2f0211d7.chunk.js +0 -2
  70. package/build/static/js/4319.5363b467.chunk.js +0 -2
  71. package/build/static/js/4547.b013b965.chunk.js +0 -2
  72. package/build/static/js/4651.ea828d09.chunk.js +0 -3
  73. package/build/static/js/5050.af9b1e9c.chunk.js +0 -3
  74. package/build/static/js/5052.da35270f.chunk.js +0 -2
  75. package/build/static/js/5176.36610ff7.chunk.js +0 -2
  76. package/build/static/js/5233.1f42910b.chunk.js +0 -2
  77. package/build/static/js/5430.f101b648.chunk.js +0 -2
  78. package/build/static/js/5645.48cf12d4.chunk.js +0 -2
  79. package/build/static/js/5711.13090883.chunk.js +0 -2
  80. package/build/static/js/5782.cf7e6574.chunk.js +0 -2
  81. package/build/static/js/6315.dec40b9b.chunk.js +0 -2
  82. package/build/static/js/652.0bde51bd.chunk.js +0 -2
  83. package/build/static/js/6737.719ec631.chunk.js +0 -2
  84. package/build/static/js/7240.6bbf13e8.chunk.js +0 -3
  85. package/build/static/js/7345.fb7fa7cf.chunk.js +0 -3
  86. package/build/static/js/7371.04b43294.chunk.js +0 -2
  87. package/build/static/js/8395.c43afdf4.chunk.js +0 -2
  88. package/build/static/js/868.cd5abe55.chunk.js +0 -2
  89. package/build/static/js/8730.0ac0b4df.chunk.js +0 -3
  90. package/build/static/js/9476.302a7b02.chunk.js +0 -2
  91. package/build/static/js/9645.822c1d69.chunk.js +0 -2
  92. package/build/static/js/main.c3544e79.js +0 -3
  93. /package/build/static/js/{1237.03f3a68a.chunk.js.LICENSE.txt → 1237.1d0b8f1e.chunk.js.LICENSE.txt} +0 -0
  94. /package/build/static/js/{8730.0ac0b4df.chunk.js.LICENSE.txt → 2130.59fef4ad.chunk.js.LICENSE.txt} +0 -0
  95. /package/build/static/js/{7240.6bbf13e8.chunk.js.LICENSE.txt → 3963.2e06f9bc.chunk.js.LICENSE.txt} +0 -0
  96. /package/build/static/js/{445.4ee5d0c9.chunk.js.LICENSE.txt → 445.6da52f3e.chunk.js.LICENSE.txt} +0 -0
  97. /package/build/static/js/{4651.ea828d09.chunk.js.LICENSE.txt → 4651.0a58cbc3.chunk.js.LICENSE.txt} +0 -0
  98. /package/build/static/js/{5050.af9b1e9c.chunk.js.LICENSE.txt → 5050.538edf89.chunk.js.LICENSE.txt} +0 -0
  99. /package/build/static/js/{7345.fb7fa7cf.chunk.js.LICENSE.txt → 7345.4b4f8941.chunk.js.LICENSE.txt} +0 -0
  100. /package/build/static/js/{2961.8237c11f.chunk.js.LICENSE.txt → 982.11dc355f.chunk.js.LICENSE.txt} +0 -0
  101. /package/build/static/js/{main.c3544e79.js.LICENSE.txt → main.efa52345.js.LICENSE.txt} +0 -0
package/api/index.js CHANGED
@@ -35,6 +35,7 @@ const StudioService = require('./services/studio');
35
35
  const AnalyticService = require('./services/analytics');
36
36
  const createEnvRoutes = require('./routes/env');
37
37
  const createOAuthRoutes = require('./routes/oauth');
38
+ const createFederatedRoutes = require('./routes/federated');
38
39
  const createUserRoutes = require('./routes/user');
39
40
  const createBlockletRoutes = require('./routes/blocklet');
40
41
  const createConnectRelayRoutes = require('./routes/connect/relay');
@@ -188,6 +189,9 @@ module.exports = function createServer(node, serverOptions = {}) {
188
189
  })
189
190
  );
190
191
 
192
+ // HACK: 必须放在 `server.use(cors());` 前面,否则会影响其原来的作用
193
+ createFederatedRoutes.preInit(server, node, options);
194
+
191
195
  // NOTE: will be overwrite by Blocklet Server in production
192
196
  server.use(cors());
193
197
 
@@ -244,6 +248,7 @@ module.exports = function createServer(node, serverOptions = {}) {
244
248
 
245
249
  // API: auth
246
250
  createOAuthRoutes.init(server, node, options);
251
+ createFederatedRoutes.init(server, node, options);
247
252
  createUserRoutes.init(server, node, options);
248
253
  createEnvRoutes.init(server, node, options);
249
254
  createBlockletRoutes.init(server, node);
@@ -1,7 +1,7 @@
1
1
  const { getPassportStatusEndpoint, getApplicationInfo } = require('@abtnode/auth/lib/auth');
2
2
  const { createPassportVC } = require('@abtnode/auth/lib/passport');
3
3
  const { VC_TYPE_NODE_PASSPORT, PASSPORT_STATUS } = require('@abtnode/constant');
4
- const { parseUserAvatar, getAvatarByEmail, getAvatarByUrl } = require('@abtnode/util/lib/user');
4
+ const { getAvatarByEmail, getAvatarByUrl, getUserAvatarUrl } = require('@abtnode/util/lib/user');
5
5
  const { getBlockletAppIdList } = require('@blocklet/meta/lib/util');
6
6
  const pick = require('lodash/pick');
7
7
  const uniq = require('lodash/uniq');
@@ -18,7 +18,6 @@ async function transferPassport(fromUser, toUser, { req, teamDid, node, nodeInfo
18
18
  name: issuerName,
19
19
  wallet: issuerWallet,
20
20
  passportColor,
21
- dataDir,
22
21
  } = await getApplicationInfo({ node, nodeInfo, teamDid });
23
22
 
24
23
  const blocklet = await req.getBlockletInfo();
@@ -43,15 +42,14 @@ async function transferPassport(fromUser, toUser, { req, teamDid, node, nodeInfo
43
42
  }
44
43
 
45
44
  const attachments = await Promise.all(
46
- waitPassportList.map(async (item) => {
47
- const avatar = await parseUserAvatar(toUser.avatar, { dataDir });
45
+ waitPassportList.map((x) => {
48
46
  const vcParams = {
49
47
  issuerName,
50
48
  issuerWallet,
51
49
  ownerDid: toUser.did,
52
- passport: pick(item, ['name', 'title', 'endpoint', 'specVersion']),
50
+ passport: pick(x, ['name', 'title', 'endpoint', 'specVersion']),
53
51
  endpoint: getPassportStatusEndpoint({
54
- baseUrl: item.endpoint,
52
+ baseUrl: x.endpoint,
55
53
  userDid: toUser.did,
56
54
  teamDid,
57
55
  }),
@@ -59,7 +57,7 @@ async function transferPassport(fromUser, toUser, { req, teamDid, node, nodeInfo
59
57
  ownerProfile: {
60
58
  email: toUser.email,
61
59
  fullName: toUser.fullName,
62
- avatar,
60
+ avatar: getUserAvatarUrl(x.endpoint, toUser.avatar),
63
61
  },
64
62
  preferredColor: passportColor,
65
63
  };
@@ -69,7 +67,7 @@ async function transferPassport(fromUser, toUser, { req, teamDid, node, nodeInfo
69
67
  type: 'vc',
70
68
  data: {
71
69
  credential: vc,
72
- tag: item.name,
70
+ tag: x.name,
73
71
  },
74
72
  };
75
73
  })
@@ -2,7 +2,7 @@
2
2
  const get = require('lodash/get');
3
3
  const joinUrl = require('url-join');
4
4
  const formatContext = require('@abtnode/util/lib/format-context');
5
- const { extractUserAvatar } = require('@abtnode/util/lib/user');
5
+ const { extractUserAvatar, getAppAvatarUrl } = require('@abtnode/util/lib/user');
6
6
  const {
7
7
  messages,
8
8
  getVCFromClaims,
@@ -35,6 +35,9 @@ const {
35
35
  const { getKeyPairClaim, getAuthPrincipalForMigrateAppToV2 } = require('@abtnode/auth/lib/server');
36
36
  const merge = require('lodash/merge');
37
37
  const { fromAppDid } = require('@arcblock/did-ext');
38
+ const { LOGIN_PROVIDER } = require('@blocklet/constant');
39
+ const { signV2 } = require('@arcblock/jwt');
40
+ const pick = require('lodash/pick');
38
41
 
39
42
  const { getRolesFromAuthConfig, getBlockletAppIdList } = require('@blocklet/meta/lib/util');
40
43
 
@@ -44,6 +47,9 @@ const { isInvitedUserOnly, createTokenFn, getDidConnectVersion } = require('../.
44
47
  const { transferPassport } = require('../auth/utils');
45
48
  const { generateTranslate } = require('../translate');
46
49
  const { migrateAccount, declareAccount } = require('../../services/oauth');
50
+ const { getTrustedIssuers, getLoginProvider } = require('../../util/blocklet-utils');
51
+ const { getFederatedMaster, getUserAvatarUrl } = require('../../util/federated');
52
+ const { api } = require('../api');
47
53
 
48
54
  const vcTypes = [VC_TYPE_GENERAL_PASSPORT, VC_TYPE_NODE_PASSPORT];
49
55
 
@@ -136,6 +142,7 @@ module.exports = {
136
142
  const blocklet = await request.getBlocklet();
137
143
  const config = await request.getServiceConfig(NODE_SERVICES.AUTH, { componentId });
138
144
  const { did: teamDid, wallet: blockletWallet } = await request.getBlockletInfo();
145
+ const provider = getLoginProvider(request);
139
146
 
140
147
  const profileFields = get(config, 'profileFields');
141
148
 
@@ -148,8 +155,8 @@ module.exports = {
148
155
  };
149
156
  if (action === 'login') {
150
157
  const [invitedUserOnly] = config ? await isInvitedUserOnly(config, node, teamDid) : [false];
151
- const trustedPassports = (blocklet.trustedPassports || []).map((x) => x.issuerDid);
152
- const trustedIssuers = [...getBlockletAppIdList(blocklet), ...trustedPassports].filter(Boolean);
158
+
159
+ const trustedIssuers = getTrustedIssuers(blocklet, { provider });
153
160
  claims.verifiableCredential = {
154
161
  type: 'verifiableCredential',
155
162
  description: messages.requestPassport[locale],
@@ -283,6 +290,7 @@ module.exports = {
283
290
  vc = createPassportVC({
284
291
  issuerName: name,
285
292
  issuerWallet: wallet,
293
+ issuerAvatarUrl: getAppAvatarUrl(baseUrl),
286
294
  ownerDid: realDid,
287
295
  passport: await createPassport({
288
296
  name: defaultRole,
@@ -325,9 +333,11 @@ module.exports = {
325
333
  let fullName = user?.fullName;
326
334
  // Update profile
327
335
  const passportForLog = passport || { name: 'Guest', role: 'guest' };
336
+ const provider = getLoginProvider(request);
337
+ let updatedUser;
328
338
  if (user) {
329
339
  // Update user
330
- const doc = await node.loginUser({
340
+ updatedUser = await node.loginUser({
331
341
  teamDid,
332
342
  user: {
333
343
  did: user.did,
@@ -335,15 +345,15 @@ module.exports = {
335
345
  locale,
336
346
  passport,
337
347
  lastLoginIp: get(request, 'headers[x-real-ip]') || '',
338
- connectedAccount: [{ provider: 'wallet', did: realDid }, connectedNft],
348
+ connectedAccount: [{ provider, did: realDid }, connectedNft],
339
349
  },
340
350
  });
341
351
  await node.createAuditLog(
342
352
  {
343
353
  action,
344
- args: { teamDid, userDid: realDid, passport: passportForLog, provider: 'wallet' },
345
- context: formatContext(Object.assign(request, { user: doc })),
346
- result: doc,
354
+ args: { teamDid, userDid: realDid, passport: passportForLog, provider },
355
+ context: formatContext(Object.assign(request, { user: updatedUser })),
356
+ result: updatedUser,
347
357
  },
348
358
  node
349
359
  );
@@ -352,7 +362,7 @@ module.exports = {
352
362
  const profile = claims.find((x) => x.type === 'profile');
353
363
  fullName = profile.fullName;
354
364
 
355
- const doc = await node.loginUser({
365
+ updatedUser = await node.loginUser({
356
366
  teamDid,
357
367
  user: {
358
368
  ...profile,
@@ -366,7 +376,7 @@ module.exports = {
366
376
  lastLoginIp: get(request, 'headers[x-real-ip]') || '',
367
377
  connectedAccount: [
368
378
  {
369
- provider: 'wallet',
379
+ provider,
370
380
  did: realDid,
371
381
  pk: realPk,
372
382
  },
@@ -378,8 +388,8 @@ module.exports = {
378
388
  {
379
389
  action: 'addUser',
380
390
  args: { teamDid, userDid: realDid, reason: `first login as ${passportForLog.role}` },
381
- context: formatContext(Object.assign(request, { user: doc })),
382
- result: doc,
391
+ context: formatContext(Object.assign(request, { user: updatedUser })),
392
+ result: updatedUser,
383
393
  },
384
394
  node
385
395
  );
@@ -390,7 +400,14 @@ module.exports = {
390
400
  const sessionConfig = blocklet.settings?.session || {};
391
401
  const { sessionToken, refreshToken } = createToken(
392
402
  realDid,
393
- { secret, passport, role, fullName },
403
+ {
404
+ secret,
405
+ passport,
406
+ role,
407
+ fullName,
408
+ // NOTE: token 中存储当前的 login provider
409
+ provider,
410
+ },
394
411
  { ...sessionConfig, didConnectVersion: getDidConnectVersion(request) }
395
412
  );
396
413
  logger.info(`${action}.success`, { userDid: realDid, role });
@@ -405,6 +422,37 @@ module.exports = {
405
422
  await node.setBlockletInitialized({ did: teamDid, owner: { did: realDid, pk: realPk } });
406
423
  }
407
424
 
425
+ const { permanentWallet } = await request.getBlockletInfo();
426
+
427
+ const masterSite = getFederatedMaster(blocklet);
428
+ let federatedSessionToken;
429
+ let federatedRefreshToken;
430
+ if (masterSite) {
431
+ const postUser = pick(updatedUser, ['did', 'pk', 'fullName', 'locale']);
432
+ postUser.lastLoginAt = get(request, 'headers[x-real-ip]') || '';
433
+
434
+ if (updatedUser.email) {
435
+ postUser.email = updatedUser.email;
436
+ }
437
+ if (updatedUser.avatar) {
438
+ postUser.avatar = getUserAvatarUrl(updatedUser.avatar, blocklet);
439
+ }
440
+
441
+ const { data } = await api.post(
442
+ joinUrl(masterSite.appUrl, WELLKNOWN_SERVICE_PATH_PREFIX, '/api/federated/loginByMember'),
443
+ {
444
+ signer: permanentWallet.address,
445
+ data: signV2(permanentWallet.address, permanentWallet.secretKey, {
446
+ user: postUser,
447
+ appId: blocklet.did,
448
+ passport,
449
+ }),
450
+ }
451
+ );
452
+ federatedSessionToken = data.sessionToken;
453
+ federatedRefreshToken = data.refreshToken;
454
+ }
455
+
408
456
  // issue passport for the first login user in a invite-only team
409
457
  if (issuePassport) {
410
458
  return {
@@ -413,6 +461,8 @@ module.exports = {
413
461
  data: vc,
414
462
  sessionToken,
415
463
  refreshToken,
464
+ federatedSessionToken,
465
+ federatedRefreshToken,
416
466
  nextWorkflowData: {
417
467
  userDid: realDid,
418
468
  },
@@ -422,6 +472,8 @@ module.exports = {
422
472
  return {
423
473
  sessionToken,
424
474
  refreshToken,
475
+ federatedSessionToken,
476
+ federatedRefreshToken,
425
477
  nextWorkflowData: {
426
478
  userDid: realDid,
427
479
  },
@@ -462,7 +514,7 @@ module.exports = {
462
514
  profile: {
463
515
  type: 'profile',
464
516
  description: messages.description[locale],
465
- items: get(config, 'profileFields') || ['fullName', 'avatar'],
517
+ items: get(config, 'profileFields') || ['fullName', 'avatar', 'email'],
466
518
  },
467
519
  };
468
520
  },
@@ -540,8 +592,8 @@ module.exports = {
540
592
  const [invitedUserOnly] = await isInvitedUserOnly(config, node, teamDid);
541
593
 
542
594
  const blocklet = await request.getBlocklet();
543
- const trustedPassports = (blocklet.trustedPassports || []).map((x) => x.issuerDid);
544
- const trustedIssuers = [...getBlockletAppIdList(blocklet), ...trustedPassports].filter(Boolean);
595
+ const provider = getLoginProvider(request);
596
+ const trustedIssuers = getTrustedIssuers(blocklet, { provider });
545
597
 
546
598
  return {
547
599
  verifiableCredential: {
@@ -633,7 +685,7 @@ module.exports = {
633
685
  await node.createAuditLog(
634
686
  {
635
687
  action: 'switchPassport',
636
- args: { teamDid, userDid, passport: passportForLog, provider: 'wallet' },
688
+ args: { teamDid, userDid, passport: passportForLog, provider: LOGIN_PROVIDER.WALLET },
637
689
  context: formatContext(Object.assign(request, { user })),
638
690
  result: {},
639
691
  },
@@ -702,8 +754,8 @@ module.exports = {
702
754
  throw new Error(t('notFound', locale));
703
755
  }
704
756
  const oauthConnectedAccounts = oauthUser.connectedAccounts || [];
705
- const sourceProvider = oauthUser.sourceProvider || 'wallet';
706
- if (oauthConnectedAccounts.find((item) => item.provider === 'wallet')) {
757
+ const sourceProvider = oauthUser.sourceProvider || LOGIN_PROVIDER.WALLET;
758
+ if (oauthConnectedAccounts.find((item) => item.provider === LOGIN_PROVIDER.WALLET)) {
707
759
  throw new Error(t('alreadyBindWallet', locale));
708
760
  }
709
761
 
@@ -743,7 +795,7 @@ module.exports = {
743
795
 
744
796
  return [];
745
797
  },
746
- onApprove: async ({ node, request, locale, userDid, userPk, claims, previousUserDid }) => {
798
+ onApprove: async ({ node, request, locale, userDid, userPk, claims, previousUserDid, baseUrl }) => {
747
799
  const blocklet = await request.getBlocklet();
748
800
  const { did: teamDid, wallet: blockletWallet } = await request.getBlockletInfo();
749
801
 
@@ -786,7 +838,7 @@ module.exports = {
786
838
  const currentTime = new Date().toISOString();
787
839
 
788
840
  const connectedAccount = {
789
- provider: 'wallet',
841
+ provider: LOGIN_PROVIDER.WALLET,
790
842
  did: userDid,
791
843
  pk: userPk,
792
844
  lastLoginAt: currentTime,
@@ -815,7 +867,7 @@ module.exports = {
815
867
  };
816
868
  }
817
869
 
818
- await transferPassport(oauthUser, bindUser, { req: request, node, nodeInfo, teamDid });
870
+ await transferPassport(oauthUser, bindUser, { req: request, node, nodeInfo, teamDid, baseUrl });
819
871
 
820
872
  const connectedAccounts = oauthUser?.connectedAccounts || [];
821
873
  const sourceProvider = oauthUser?.sourceProvider;
@@ -826,7 +878,7 @@ module.exports = {
826
878
  await node.createAuditLog(
827
879
  {
828
880
  action: 'connectAccount',
829
- args: { teamDid, connectedAccount, provider: 'wallet', userDid: oauthUser.did },
881
+ args: { teamDid, connectedAccount, provider: LOGIN_PROVIDER.WALLET, userDid: oauthUser.did },
830
882
  context: formatContext(Object.assign(request, { user: oauthUser })),
831
883
  result: bindUser,
832
884
  },
@@ -2,9 +2,11 @@ const get = require('lodash/get');
2
2
  const joinUrl = require('url-join');
3
3
  const { getConnectAppUrl, getChainInfo } = require('@blocklet/meta/lib/util');
4
4
  const { WELLKNOWN_SERVICE_PATH_PREFIX } = require('@abtnode/constant');
5
+ const { LOGIN_PROVIDER } = require('@blocklet/constant');
6
+ const { getProvider } = require('../../util/federated');
5
7
 
6
8
  module.exports = {
7
- appInfo: async ({ request, baseUrl }) => {
9
+ appInfo: async ({ request, baseUrl, extraParams }) => {
8
10
  const groupPathPrefix = request.headers['x-group-path-prefix'] || '/';
9
11
 
10
12
  const [blocklet, meta, info] = await Promise.all([
@@ -12,6 +14,13 @@ module.exports = {
12
14
  request.getBlockletInfo(),
13
15
  request.getNodeInfo(),
14
16
  ]);
17
+ // NOTE: 这里应该使用当前 blocklet 的 did,而不是 pid
18
+ let agentDid;
19
+ const provider = getProvider(request, extraParams);
20
+ // federated 登录模式下,需要告知原有的 blocklet-did
21
+ if (provider === LOGIN_PROVIDER.FEDERATED) {
22
+ agentDid = blocklet.appDid;
23
+ }
15
24
 
16
25
  const version = get(blocklet, 'meta.version', '');
17
26
 
@@ -23,6 +32,8 @@ module.exports = {
23
32
  updateSubEndpoint: true,
24
33
  subscriptionEndpoint: joinUrl(groupPathPrefix, WELLKNOWN_SERVICE_PATH_PREFIX, 'websocket'),
25
34
  nodeDid: info.did,
35
+ // NOTE: publisher 是 WalletAuthenticator 中自动添加的
36
+ agentDid,
26
37
  };
27
38
  },
28
39
 
@@ -2,15 +2,19 @@ const path = require('path');
2
2
  const get = require('lodash/get');
3
3
  const { toAddress } = require('@ocap/util');
4
4
  const { WalletAuthenticator } = require('@arcblock/did-auth');
5
+ const { types } = require('@ocap/mcrypto');
5
6
  const getBlockletInfo = require('@blocklet/meta/lib/info');
6
7
  const WalletHandlers = require('@blocklet/sdk/lib/wallet-handler');
7
8
  const { getDelegation } = require('@blocklet/sdk/lib/connect/shared');
8
9
  const { sendToUser, sendToRelay } = require('@blocklet/sdk/lib/util/send-notification');
9
10
  const { WELLKNOWN_SERVICE_PATH_PREFIX, NODE_SERVICES_PREFIX } = require('@abtnode/constant');
10
11
  const DynamicStorage = require('@abtnode/connect-storage');
12
+ const { fromPublicKey } = require('@ocap/wallet');
13
+ const { LOGIN_PROVIDER } = require('@blocklet/constant');
11
14
 
12
15
  const cache = require('../../cache');
13
16
  const { appInfo, chainInfo } = require('./shared');
17
+ const { getProvider } = require('../../util/federated');
14
18
 
15
19
  module.exports = (node, opts) => {
16
20
  const authenticator = new WalletAuthenticator({
@@ -20,15 +24,40 @@ module.exports = (node, opts) => {
20
24
  const { wallet } = await request.getBlockletInfo();
21
25
  return wallet.toJSON();
22
26
  },
23
- delegator: async ({ request }) => {
27
+ delegator: async ({ request, extraParams }) => {
28
+ const provider = getProvider(request, extraParams);
29
+
30
+ const blocklet = await request.getBlocklet();
31
+ if (provider === LOGIN_PROVIDER.FEDERATED) {
32
+ const pk = get(blocklet, 'settings.federated.sites[0].pk');
33
+
34
+ // NOTE: 这里的 type 参数必须保持跟生成 blocklet 时使用的是一致的
35
+ const type = {
36
+ role: types.RoleType.ROLE_APPLICATION,
37
+ pk: types.KeyType.ED25519,
38
+ hash: types.HashType.SHA3,
39
+ address: types.EncodingType.BASE58,
40
+ };
41
+ const delegator = fromPublicKey(pk, type);
42
+ delegator.provider = LOGIN_PROVIDER.FEDERATED;
43
+ return delegator;
44
+ }
45
+
24
46
  const { wallet: delegatee, permanentWallet: delegator } = await request.getBlockletInfo();
25
47
  if (delegatee.address !== delegator.address) {
26
48
  return delegator;
27
49
  }
28
50
  return null;
29
51
  },
30
- delegation: async ({ request }) => {
52
+ delegation: async ({ request, extraParams }) => {
31
53
  const { wallet: delegatee, permanentWallet: delegator } = await request.getBlockletInfo();
54
+ const provider = getProvider(request, extraParams);
55
+ if (provider === LOGIN_PROVIDER.FEDERATED) {
56
+ const blocklet = await request.getBlocklet();
57
+ const delegation = get(blocklet, 'settings.federated.config.delegation');
58
+ return delegation;
59
+ }
60
+
32
61
  if (delegatee.address !== delegator.address) {
33
62
  return getDelegation(delegator, delegatee);
34
63
  }
@@ -55,7 +84,7 @@ module.exports = (node, opts) => {
55
84
 
56
85
  // Only handles did-connect updates, because we need to determine the actual app
57
86
  sendToRelayFn: async (topic, event, data) => {
58
- const publisher = get(data, 'appInfo.publisher', '');
87
+ const publisher = get(data, 'appInfo.agentDid', '') || get(data, 'appInfo.publisher', '');
59
88
  if (!publisher) {
60
89
  return null;
61
90
  }
package/api/libs/jwt.js CHANGED
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable no-underscore-dangle */
2
2
  const jwt = require('jsonwebtoken');
3
3
  const { createAuthToken } = require('@abtnode/auth/lib/auth');
4
+ const { LOGIN_PROVIDER } = require('@blocklet/constant');
4
5
 
5
6
  const getUser = async (node, teamDid, userDid) => {
6
7
  const user = await node.getUserByDid({ teamDid, userDid });
@@ -15,7 +16,10 @@ const initJwt = (node, options) => {
15
16
  // 保持默认有效期为 1 天
16
17
  const ttl = options.sessionTtl || '1d';
17
18
 
18
- const createSessionToken = (did, { role, secret, passport, expiresIn, tokenType, fullName }) =>
19
+ const createSessionToken = (
20
+ did,
21
+ { role, secret, passport, expiresIn, tokenType, fullName, provider = LOGIN_PROVIDER.WALLET }
22
+ ) =>
19
23
  createAuthToken({
20
24
  did,
21
25
  passport,
@@ -24,6 +28,7 @@ const initJwt = (node, options) => {
24
28
  expiresIn: expiresIn || ttl,
25
29
  tokenType,
26
30
  fullName,
31
+ provider,
27
32
  });
28
33
 
29
34
  const verifySessionToken = (token, secret, { checkFromDb, teamDid, checkToken } = {}) =>
@@ -42,7 +47,7 @@ const initJwt = (node, options) => {
42
47
  }
43
48
  }
44
49
 
45
- const { did, role, passport, fullName } = decoded;
50
+ const { did, role, passport, fullName, provider = LOGIN_PROVIDER.WALLET } = decoded;
46
51
  let user;
47
52
  if (!did) {
48
53
  return reject(new Error('Invalid jwt token: invalid did'));
@@ -70,7 +75,7 @@ const initJwt = (node, options) => {
70
75
  user.role = role;
71
76
  user.passport = passport;
72
77
  } else {
73
- user = { did, role, passport, fullName };
78
+ user = { did, role, passport, fullName, provider };
74
79
  }
75
80
 
76
81
  return resolve(user);
@@ -0,0 +1,15 @@
1
+ function checkFederatedCorsCall() {
2
+ return (req, res, next) => {
3
+ const caller = req.headers.origin;
4
+ const { blocklet } = req;
5
+ const federated = blocklet.settings.federated || {};
6
+ const sites = federated?.sites || [];
7
+ if (sites.find((item) => item.appUrl === caller && item.status === 'approved')) {
8
+ next();
9
+ return;
10
+ }
11
+ res.status(403).send('Unauthorized');
12
+ };
13
+ }
14
+
15
+ module.exports = checkFederatedCorsCall;
@@ -0,0 +1,13 @@
1
+ function ensureBlocklet() {
2
+ return async (req, res, next) => {
3
+ const blocklet = await req.getBlocklet();
4
+ if (!blocklet) {
5
+ res.status(404).send('blocklet not found');
6
+ return;
7
+ }
8
+ req.blocklet = blocklet;
9
+ next();
10
+ };
11
+ }
12
+
13
+ module.exports = ensureBlocklet;
@@ -0,0 +1,35 @@
1
+ const { verify, decode } = require('@arcblock/jwt');
2
+
3
+ function verifyFederatedCall({ isMaster = false } = { isMaster: false }) {
4
+ return (req, res, next) => {
5
+ const { signer, data } = req.body;
6
+ const { blocklet } = req;
7
+ const federated = blocklet.settings.federated || {};
8
+ const sites = federated?.sites || [];
9
+
10
+ // TODO: @zhanghan 是否需要检查 status === approved?
11
+ // 暂时先不检查,member 只能更新自己的信息,无法更新自身的 status,目前没有任何有权限的信息的修改能力
12
+ const validSite = sites.find((siteItem) => {
13
+ let result = siteItem.appPid === signer;
14
+ if (isMaster) {
15
+ result = result && siteItem.isMaster !== false;
16
+ }
17
+ return result;
18
+ });
19
+
20
+ if (!validSite) {
21
+ res.status(403).send('Unauthorized');
22
+ return;
23
+ }
24
+
25
+ if (!verify(data, validSite.pk)) {
26
+ res.status(403).send('Invalid signature');
27
+ return;
28
+ }
29
+
30
+ req.body.verifyData = decode(data);
31
+ next();
32
+ };
33
+ }
34
+
35
+ module.exports = verifyFederatedCall;
@@ -3,13 +3,14 @@ const fs = require('fs');
3
3
  const cloneDeep = require('lodash/cloneDeep');
4
4
  const dayjs = require('@abtnode/util/lib/dayjs');
5
5
  const JWT = require('@arcblock/jwt');
6
+ const { isValid } = require('@arcblock/did');
6
7
  const joinUrl = require('url-join');
7
8
  const handleInstanceInStore = require('@abtnode/core/lib/util/public-to-store');
8
9
  const { getPassportStatusEndpoint } = require('@abtnode/auth/lib/auth');
9
10
  const { createPassportVC, createPassport, createUserPassport } = require('@abtnode/auth/lib/passport');
10
11
 
11
12
  const formatContext = require('@abtnode/util/lib/format-context');
12
- const { parseUserAvatar, getAvatarFile } = require('@abtnode/util/lib/user');
13
+ const { getUserAvatarUrl, getAvatarFile, getAppAvatarUrl } = require('@abtnode/util/lib/user');
13
14
  const {
14
15
  attachSendLogoContext,
15
16
  ensureBlockletExist,
@@ -27,7 +28,12 @@ const {
27
28
  findComponentByIdV2,
28
29
  forEachComponentV2Sync,
29
30
  } = require('@blocklet/meta/lib/util');
30
- const { WELLKNOWN_SERVICE_PATH_PREFIX, USER_AVATAR_PATH_PREFIX, ROLES } = require('@abtnode/constant');
31
+ const {
32
+ WELLKNOWN_SERVICE_PATH_PREFIX,
33
+ USER_AVATAR_PATH_PREFIX,
34
+ ROLES,
35
+ USER_AVATAR_URL_PREFIX,
36
+ } = require('@abtnode/constant');
31
37
  const logger = require('@abtnode/logger')(require('../../package.json').name);
32
38
 
33
39
  const { createDownloadLogStream } = require('@abtnode/util/lib/log');
@@ -109,7 +115,14 @@ module.exports = {
109
115
 
110
116
  try {
111
117
  const blocklet = await req.getBlocklet();
112
- const { fileName } = req.params;
118
+ let { fileName } = req.params;
119
+
120
+ if (isValid(fileName)) {
121
+ const user = await node.getUser({ teamDid: blocklet.appPid, user: { did: fileName } });
122
+ if (user && user.avatar.startsWith(USER_AVATAR_URL_PREFIX)) {
123
+ fileName = user.avatar.split('/').pop();
124
+ }
125
+ }
113
126
 
114
127
  const avatarFile = getAvatarFile(blocklet.env.dataDir, fileName);
115
128
 
@@ -174,7 +187,8 @@ module.exports = {
174
187
  const { wallet, name, passportColor } = await req.getBlockletInfo();
175
188
  // NOTICE: 这里的 did 是从 session 中取到的永久 did,不需要查询 connectedAccount
176
189
  const user = await node.getUser({ teamDid, user: { did: userDid } });
177
- user.avatar = await parseUserAvatar(user.avatar, { dataDir: blocklet.env.dataDir });
190
+ const appUrl = blocklet.environments.find((x) => x.key === 'BLOCKLET_APP_URL').value;
191
+ user.avatar = getUserAvatarUrl(appUrl, user.avatar);
178
192
 
179
193
  const { pk, locale = 'en' } = user;
180
194
 
@@ -200,12 +214,11 @@ module.exports = {
200
214
  const role = ROLES.OWNER;
201
215
  const hasOwnerPassport = (user.passports || []).some((x) => x.name === role);
202
216
  if (hasOwnerPassport === false) {
203
- const appUrl = blocklet.environments.find((item) => item.key === 'BLOCKLET_APP_URL').value;
204
-
205
217
  // create vc
206
218
  const vc = createPassportVC({
207
219
  issuerName: name,
208
220
  issuerWallet: wallet,
221
+ issuerAvatarUrl: getAppAvatarUrl(appUrl),
209
222
  ownerDid: userDid,
210
223
  passport: await createPassport({
211
224
  name: role,