@abtnode/auth 1.8.30 → 1.8.32

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 (3) hide show
  1. package/lib/auth.js +30 -3
  2. package/lib/server.js +194 -54
  3. package/package.json +15 -15
package/lib/auth.js CHANGED
@@ -9,7 +9,13 @@ const Mcrypto = require('@ocap/mcrypto');
9
9
  const Client = require('@ocap/client');
10
10
  const { fromSecretKey, WalletType } = require('@ocap/wallet');
11
11
  const getBlockletInfo = require('@blocklet/meta/lib/info');
12
- const { PASSPORT_STATUS, VC_TYPE_NODE_PASSPORT, ROLES, NODE_DATA_DIR_NAME } = require('@abtnode/constant');
12
+ const {
13
+ PASSPORT_STATUS,
14
+ VC_TYPE_NODE_PASSPORT,
15
+ ROLES,
16
+ NODE_DATA_DIR_NAME,
17
+ AUTH_CERT_TYPE,
18
+ } = require('@abtnode/constant');
13
19
  const axios = require('@abtnode/util/lib/axios');
14
20
  const { extractUserAvatar, parseUserAvatar } = require('@abtnode/util/lib/user-avatar');
15
21
 
@@ -85,6 +91,10 @@ const messages = {
85
91
  en: (types) => `Invalid credential type, expect ${types.join(' or ')}`,
86
92
  zh: (types) => `无效的凭证类型,必须是 ${types.join(' 或 ')}`,
87
93
  },
94
+ invalidCredentialId: {
95
+ en: (ids) => `Invalid credential type, expect ${[].concat(ids).join(' or ')}`,
96
+ zh: (ids) => `无效的凭证,ID 必须是 ${[].concat(ids).join(' 或 ')}`,
97
+ },
88
98
  invalidCredentialHolder: {
89
99
  en: 'Invalid credential holder',
90
100
  zh: '无效的凭证持有者',
@@ -307,9 +317,20 @@ const createAuthToken = ({ did, passport, role, secret, expiresIn } = {}) => {
307
317
  return token;
308
318
  };
309
319
 
320
+ const createBlockletControllerAuthToken = ({ did, role, controller, secret, expiresIn } = {}) => {
321
+ const payload = {
322
+ type: AUTH_CERT_TYPE.BLOCKLET_CONTROLLER,
323
+ did,
324
+ role,
325
+ controller,
326
+ };
327
+
328
+ return jwt.sign(payload, secret, { expiresIn });
329
+ };
330
+
310
331
  const createAuthTokenByOwnershipNFT = ({ did, role, secret, expiresIn } = {}) => {
311
332
  const payload = {
312
- type: 'ownership_nft',
333
+ type: AUTH_CERT_TYPE.OWNERSHIP_NFT,
313
334
  did,
314
335
  role,
315
336
  };
@@ -748,7 +769,7 @@ const handleIssuePassportResponse = async ({
748
769
  };
749
770
  };
750
771
 
751
- const getVCFromClaims = async ({ claims, challenge, trustedIssuers, vcTypes, locale = 'en' }) => {
772
+ const getVCFromClaims = async ({ claims, challenge, trustedIssuers, vcTypes, locale = 'en', vcId }) => {
752
773
  const credential = claims.find(
753
774
  (x) => x.type === 'verifiableCredential' && vcTypes.some((item) => x.item.includes(item))
754
775
  );
@@ -777,6 +798,11 @@ const getVCFromClaims = async ({ claims, challenge, trustedIssuers, vcTypes, loc
777
798
  : [presentation.verifiableCredential];
778
799
  const vc = JSON.parse(credentials[0]);
779
800
 
801
+ // verify vc id
802
+ if (vcId && vc.id !== vcId) {
803
+ throw new Error(messages.invalidCredentialId[locale](vcId));
804
+ }
805
+
780
806
  // verify vc type
781
807
  const types = [].concat(vc.type);
782
808
  if (!types.some((x) => vcTypes.includes(x))) {
@@ -941,4 +967,5 @@ module.exports = {
941
967
  getPassportStatus,
942
968
  validatePassportStatus,
943
969
  setUserInfoHeaders,
970
+ createBlockletControllerAuthToken,
944
971
  };
package/lib/server.js CHANGED
@@ -6,13 +6,15 @@ const { fromPublicKey } = require('@ocap/wallet');
6
6
  const { fromBase58, toAddress } = require('@ocap/util');
7
7
  const { toTypeInfo, isFromPublicKey } = require('@arcblock/did');
8
8
  const formatContext = require('@abtnode/util/lib/format-context');
9
- const semver = require('semver');
10
9
  const {
11
10
  ROLES,
12
11
  VC_TYPE_GENERAL_PASSPORT,
13
12
  VC_TYPE_NODE_PASSPORT,
14
13
  NFT_TYPE_SERVER_OWNERSHIP,
14
+ VC_TYPE_SERVER_SHARE,
15
+ SERVER_ROLES,
15
16
  } = require('@abtnode/constant');
17
+ const { toExternalBlocklet } = require('@blocklet/meta/lib/did');
16
18
  const {
17
19
  messages,
18
20
  getVCFromClaims,
@@ -20,6 +22,7 @@ const {
20
22
  validatePassportStatus,
21
23
  createAuthToken,
22
24
  createAuthTokenByOwnershipNFT,
25
+ createBlockletControllerAuthToken,
23
26
  checkWalletVersion,
24
27
  } = require('./auth');
25
28
  const {
@@ -33,9 +36,64 @@ const logger = require('./logger');
33
36
 
34
37
  const secret = process.env.ABT_NODE_SESSION_SECRET;
35
38
  const LAUNCH_BLOCKLET_TOKEN_EXPIRE = '1d';
36
- const abtnodeVcTypes = [VC_TYPE_GENERAL_PASSPORT, VC_TYPE_NODE_PASSPORT];
37
39
 
38
- const authenticateByVc = async ({ node, locale, userDid, claims, challenge, requireNodeInitialized = true }) => {
40
+ // External token should expire after 20 min
41
+ // Assuming the blocklet installation will take no more than 20 min
42
+ const EXTERNAL_LAUNCH_BLOCKLET_TOKEN_EXPIRE = '20m';
43
+
44
+ const abtnodeVcTypes = (launchBlocklet) =>
45
+ launchBlocklet
46
+ ? [VC_TYPE_GENERAL_PASSPORT, VC_TYPE_NODE_PASSPORT, VC_TYPE_SERVER_SHARE]
47
+ : [VC_TYPE_GENERAL_PASSPORT, VC_TYPE_NODE_PASSPORT];
48
+
49
+ const ensureLauncherIssuer = (issuers, nodeInfo) => {
50
+ // TODO: server 开启共享后, 才接收 launcher 颁发的 nft
51
+
52
+ const launcherDid = get(nodeInfo, 'launcher.did');
53
+ if (launcherDid) {
54
+ issuers.push(launcherDid);
55
+ }
56
+ };
57
+
58
+ const getExternalPassport = async (vc, nodeInfo) => {
59
+ // TODO: server 开启共享后, 才接收 launcher 颁发的 nft
60
+
61
+ const launcherDid = get(nodeInfo, 'launcher.did');
62
+ if (!launcherDid) {
63
+ return null;
64
+ }
65
+
66
+ if (vc.issuer.id !== launcherDid) {
67
+ return null;
68
+ }
69
+
70
+ if (!vc.type.includes(VC_TYPE_SERVER_SHARE)) {
71
+ throw new Error('Cannot get NFT issued by launcher: Invalid type');
72
+ }
73
+
74
+ if (vc.credentialSubject.serverDid !== nodeInfo.did) {
75
+ throw new Error('Cannot get NFT issued by launcher: Invalid Server DID');
76
+ }
77
+
78
+ // TODO more info from vc and launcher
79
+ // FIXME expireDate 需要动态获取
80
+ return {
81
+ name: SERVER_ROLES.EXTERNAL_BLOCKLET_CONTROLLER,
82
+ appMaxCount: vc.credentialSubject.appMaxCount || 1,
83
+ expireDate: vc.credentialSubject.expireDate,
84
+ };
85
+ };
86
+
87
+ const authenticateByVc = async ({
88
+ node,
89
+ locale,
90
+ userDid,
91
+ claims,
92
+ challenge,
93
+ requireNodeInitialized = true,
94
+ launchBlocklet,
95
+ blocklet,
96
+ }) => {
39
97
  if (requireNodeInitialized) {
40
98
  if ((await node.isInitialized()) === false) {
41
99
  throw new Error(messages.notInitialized[locale]);
@@ -46,27 +104,54 @@ const authenticateByVc = async ({ node, locale, userDid, claims, challenge, requ
46
104
  const teamDid = info.did;
47
105
  const { name } = info;
48
106
 
49
- // check user approved
50
- const user = await getUser(node, teamDid, userDid);
51
- if (user && !user.approved) {
52
- throw new Error(messages.notAllowed[locale]);
53
- }
54
-
55
107
  // Get passport
56
108
  const trustedPassports = (info.trustedPassports || []).map((x) => x.issuerDid);
57
109
  const trustedIssuers = [info.did, ...trustedPassports].filter(Boolean);
110
+
111
+ if (launchBlocklet) {
112
+ ensureLauncherIssuer(trustedIssuers, info);
113
+ }
114
+
58
115
  const { vc, types: passportTypes } = await getVCFromClaims({
59
116
  claims,
60
117
  challenge,
61
118
  trustedIssuers,
62
- vcTypes: abtnodeVcTypes,
119
+ vcTypes: abtnodeVcTypes(launchBlocklet),
63
120
  locale,
121
+ vcId: blocklet?.controller?.vcId,
64
122
  });
65
123
 
66
124
  if (!vc) {
67
125
  throw new Error(messages.missingCredentialClaim[locale]);
68
126
  }
69
127
 
128
+ // external user
129
+ if (launchBlocklet) {
130
+ const externalPassport = await getExternalPassport(vc, info);
131
+ if (externalPassport) {
132
+ return {
133
+ role: externalPassport.name,
134
+ extra: {
135
+ controller: {
136
+ vcId: vc.id,
137
+ appMaxCount: externalPassport.appMaxCount,
138
+ expireDate: externalPassport.expireDate,
139
+ },
140
+ },
141
+ user: {
142
+ did: userDid,
143
+ },
144
+ teamDid,
145
+ };
146
+ }
147
+ }
148
+
149
+ // check user approved
150
+ const user = await getUser(node, teamDid, userDid);
151
+ if (user && !user.approved) {
152
+ throw new Error(messages.notAllowed[locale]);
153
+ }
154
+
70
155
  // Get user passport from vc
71
156
  let passport = createUserPassport(vc);
72
157
  if (user && isUserPassportRevoked(user, passport)) {
@@ -162,41 +247,73 @@ const authenticateByNFT = async ({ node, claims, userDid, challenge, locale }) =
162
247
  };
163
248
 
164
249
  const getAuthVcClaim =
165
- (node) =>
250
+ ({ node, launchBlocklet, blocklet }) =>
166
251
  async ({ extraParams: { locale, passportId }, context: { didwallet } }) => {
167
252
  checkWalletVersion({ didwallet, locale });
168
- const info = await node.getNodeInfo();
169
- const trustedPassports = (info.trustedPassports || []).map((x) => x.issuerDid);
170
- const trustedIssuers = [info.did, ...trustedPassports].filter(Boolean);
171
- const claim = {
253
+
254
+ const baseClaim = {
172
255
  description: messages.requestPassport[locale],
173
256
  optional: false,
174
- item: abtnodeVcTypes,
175
- trustedIssuers,
257
+ item: abtnodeVcTypes(launchBlocklet),
176
258
  };
177
259
 
260
+ if (blocklet && blocklet?.controller?.vcId) {
261
+ return {
262
+ ...baseClaim,
263
+ target: blocklet.controller.vcId,
264
+ };
265
+ }
266
+
178
267
  if (passportId) {
179
- claim.target = passportId;
268
+ return {
269
+ ...baseClaim,
270
+ target: passportId,
271
+ };
180
272
  }
181
273
 
182
- return claim;
274
+ const info = await node.getNodeInfo();
275
+ const trustedPassports = (info.trustedPassports || []).map((x) => x.issuerDid);
276
+
277
+ const trustedIssuers = [info.did, ...trustedPassports].filter(Boolean);
278
+
279
+ if (launchBlocklet) {
280
+ ensureLauncherIssuer(trustedIssuers, info);
281
+ }
282
+
283
+ return {
284
+ ...baseClaim,
285
+ trustedIssuers,
286
+ };
287
+ };
288
+
289
+ const getAuthNFTClaim =
290
+ ({ node }) =>
291
+ async ({ extraParams: { locale }, context: { didwallet } }) => {
292
+ checkWalletVersion({ didwallet, locale });
293
+ return getOwnershipNFTClaim(node, locale);
183
294
  };
184
295
 
185
296
  const getLaunchBlockletClaims = (node, authMethod) => {
186
297
  if (authMethod === 'vc') {
187
298
  return {
188
- serverPassport: ['verifiableCredential', getAuthVcClaim(node)],
299
+ serverPassport: ['verifiableCredential', getAuthVcClaim({ node, launchBlocklet: true })],
189
300
  };
190
301
  }
191
302
 
192
303
  return {
193
- serverNFT: [
194
- 'asset',
195
- async ({ extraParams: { locale }, context: { didwallet } }) => {
196
- checkWalletVersion({ didwallet, locale });
197
- return getOwnershipNFTClaim(node, locale);
198
- },
199
- ],
304
+ serverNFT: ['asset', getAuthNFTClaim({ node })],
305
+ };
306
+ };
307
+
308
+ const getSetupBlockletClaims = (node, authMethod, blocklet) => {
309
+ if (authMethod === 'vc') {
310
+ return {
311
+ serverPassport: ['verifiableCredential', getAuthVcClaim({ node, blocklet })],
312
+ };
313
+ }
314
+
315
+ return {
316
+ serverNFT: ['asset', getAuthNFTClaim({ node })],
200
317
  };
201
318
  };
202
319
 
@@ -222,7 +339,7 @@ const getOwnershipNFTClaim = async (node, locale) => {
222
339
  };
223
340
  };
224
341
 
225
- const ensureBlockletPermission = async ({ authMethod, node, userDid, claims, challenge, locale }) => {
342
+ const ensureBlockletPermission = async ({ authMethod, node, userDid, claims, challenge, locale, blocklet }) => {
226
343
  let result;
227
344
  if (authMethod === 'vc') {
228
345
  result = await authenticateByVc({
@@ -232,6 +349,8 @@ const ensureBlockletPermission = async ({ authMethod, node, userDid, claims, cha
232
349
  challenge,
233
350
  requireNodeInitialized: false,
234
351
  locale,
352
+ launchBlocklet: true,
353
+ blocklet,
235
354
  });
236
355
  } else {
237
356
  result = await authenticateByNFT({
@@ -245,7 +364,7 @@ const ensureBlockletPermission = async ({ authMethod, node, userDid, claims, cha
245
364
  const { teamDid, role } = result;
246
365
 
247
366
  const permissions = await node.getPermissionsByRole({ teamDid, role: { name: role } });
248
- if (!permissions.some((item) => item.name === 'mutate_blocklet')) {
367
+ if (!permissions.some((item) => ['mutate_blocklet'].includes(item.name))) {
249
368
  throw new Error(messages.notAuthorized[locale]);
250
369
  }
251
370
 
@@ -263,7 +382,7 @@ const createLaunchBlockletHandler =
263
382
  throw new Error(messages.invalidParams[locale]);
264
383
  }
265
384
 
266
- const { role, passport, user } = await ensureBlockletPermission({
385
+ const { role, passport, user, extra } = await ensureBlockletPermission({
267
386
  authMethod,
268
387
  node,
269
388
  userDid,
@@ -286,15 +405,29 @@ const createLaunchBlockletHandler =
286
405
 
287
406
  const { did } = blocklet.meta;
288
407
 
408
+ let controller;
409
+
289
410
  let sessionToken = '';
290
411
  if (authMethod === 'vc') {
291
- sessionToken = createAuthToken({
292
- did: userDid,
293
- passport,
294
- role,
295
- secret,
296
- expiresIn: LAUNCH_BLOCKLET_TOKEN_EXPIRE,
297
- });
412
+ if (role === SERVER_ROLES.EXTERNAL_BLOCKLET_CONTROLLER) {
413
+ // TODO get more info from vc and launcher
414
+ controller = extra.controller;
415
+ sessionToken = createBlockletControllerAuthToken({
416
+ did: userDid,
417
+ role,
418
+ controller,
419
+ secret,
420
+ expiresIn: EXTERNAL_LAUNCH_BLOCKLET_TOKEN_EXPIRE,
421
+ });
422
+ } else {
423
+ sessionToken = createAuthToken({
424
+ did: userDid,
425
+ passport,
426
+ role,
427
+ secret,
428
+ expiresIn: LAUNCH_BLOCKLET_TOKEN_EXPIRE,
429
+ });
430
+ }
298
431
  } else {
299
432
  sessionToken = createAuthTokenByOwnershipNFT({
300
433
  did: userDid,
@@ -304,31 +437,37 @@ const createLaunchBlockletHandler =
304
437
  });
305
438
  }
306
439
 
307
- // 检查是否已安装,这里不做升级的处理
308
- const existedBlocklet = await node.getBlocklet({ did, attachRuntimeInfo: false });
309
440
  await updateSession({ sessionToken }, true);
310
441
 
311
- if (existedBlocklet) {
312
- const storageData = { did: userDid };
313
-
314
- if (semver.gt(blocklet.meta.version, existedBlocklet.meta.version)) {
315
- const appDidEnv = existedBlocklet.environments.find((e) => e.key === 'BLOCKLET_APP_ID');
316
- storageData.upgradeAvailable = {
317
- appDid: appDidEnv ? appDidEnv.value : '',
318
- did: existedBlocklet.meta.did,
319
- currentVersion: existedBlocklet.meta.version,
320
- version: blocklet.meta.version,
321
- };
322
- }
442
+ const blockletDid =
443
+ role === SERVER_ROLES.EXTERNAL_BLOCKLET_CONTROLLER
444
+ ? toExternalBlocklet(blocklet.meta.name, userDid, { didOnly: true })
445
+ : blocklet.meta.did;
323
446
 
324
- await updateSession(storageData);
325
- logger.info('blocklet already exists', { did });
447
+ await updateSession({
448
+ blockletDid,
449
+ });
450
+
451
+ // 检查是否已安装,这里不做升级的处理
452
+ const existedBlocklet = await node.getBlocklet({ did: blockletDid, attachRuntimeInfo: false });
453
+
454
+ if (existedBlocklet) {
455
+ await updateSession({ isInstalled: true });
456
+ logger.info('blocklet already exists', { blockletDid });
326
457
  return;
327
458
  }
328
459
 
329
460
  const tmp = await node.installBlocklet({
330
461
  url: blockletMetaUrl,
462
+ delay: 1000 * 4, // delay 4 seconds to download, wait for ws connection from frontend
331
463
  downloadTokenList: extraParams?.previousWorkflowData?.downloadTokenList,
464
+ controller:
465
+ role === SERVER_ROLES.EXTERNAL_BLOCKLET_CONTROLLER
466
+ ? {
467
+ ...controller,
468
+ id: userDid,
469
+ }
470
+ : null,
332
471
  });
333
472
 
334
473
  await node.createAuditLog(
@@ -340,7 +479,7 @@ const createLaunchBlockletHandler =
340
479
  },
341
480
  node
342
481
  );
343
- logger.info('start install blocklet', { did });
482
+ logger.info('start install blocklet', { blockletDid, bundleDid: did });
344
483
  };
345
484
 
346
485
  module.exports = {
@@ -351,4 +490,5 @@ module.exports = {
351
490
  getLaunchBlockletClaims,
352
491
  createLaunchBlockletHandler,
353
492
  ensureBlockletPermission,
493
+ getSetupBlockletClaims,
354
494
  };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.8.30",
6
+ "version": "1.8.32",
7
7
  "description": "Simple lib to manage auth in ABT Node",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -20,20 +20,20 @@
20
20
  "author": "linchen <linchen1987@foxmail.com> (http://github.com/linchen1987)",
21
21
  "license": "MIT",
22
22
  "dependencies": {
23
- "@abtnode/constant": "1.8.30",
24
- "@abtnode/logger": "1.8.30",
25
- "@abtnode/util": "1.8.30",
26
- "@arcblock/did": "1.17.23",
27
- "@arcblock/jwt": "^1.17.23",
28
- "@arcblock/vc": "1.17.23",
29
- "@blocklet/constant": "1.8.30",
30
- "@blocklet/meta": "1.8.30",
31
- "@ocap/client": "1.17.23",
32
- "@ocap/mcrypto": "1.17.23",
33
- "@ocap/util": "1.17.23",
34
- "@ocap/wallet": "1.17.23",
23
+ "@abtnode/constant": "1.8.32",
24
+ "@abtnode/logger": "1.8.32",
25
+ "@abtnode/util": "1.8.32",
26
+ "@arcblock/did": "1.18.1",
27
+ "@arcblock/jwt": "^1.18.1",
28
+ "@arcblock/vc": "1.18.1",
29
+ "@blocklet/constant": "1.8.32",
30
+ "@blocklet/meta": "1.8.32",
31
+ "@ocap/client": "1.18.1",
32
+ "@ocap/mcrypto": "1.18.1",
33
+ "@ocap/util": "1.18.1",
34
+ "@ocap/wallet": "1.18.1",
35
35
  "axios": "^0.27.2",
36
- "joi": "^17.6.2",
36
+ "joi": "17.6.3",
37
37
  "jsonwebtoken": "^8.5.1",
38
38
  "lodash": "^4.17.21",
39
39
  "semver": "^7.3.8",
@@ -42,5 +42,5 @@
42
42
  "devDependencies": {
43
43
  "jest": "^27.5.1"
44
44
  },
45
- "gitHead": "cb294f4ee7382c6ef7692b6c2c33ad080fced13e"
45
+ "gitHead": "8502244aeda5926b3433c1dd9142e63233e3c23c"
46
46
  }