@abtnode/auth 1.8.66 → 1.8.67-beta-794a8082

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 +15 -3
  2. package/lib/server.js +169 -82
  3. package/package.json +15 -14
package/lib/auth.js CHANGED
@@ -156,6 +156,18 @@ const messages = {
156
156
  en: 'Invalid Params',
157
157
  zh: '无效的参数',
158
158
  },
159
+ missingKeyPair: {
160
+ en: 'Missing app key pair',
161
+ zh: '缺少应用钥匙对',
162
+ },
163
+ missingBlockletUrl: {
164
+ en: 'Missing blocklet url',
165
+ zh: '缺少应用下载地址',
166
+ },
167
+ missingChainHost: {
168
+ en: 'Missing chain host',
169
+ zh: '缺少链的端点地址',
170
+ },
159
171
  invalidBlocklet: {
160
172
  en: 'Invalid Blocklet',
161
173
  zh: '无效的 Blocklet',
@@ -809,9 +821,9 @@ const handleIssuePassportResponse = async ({
809
821
  };
810
822
 
811
823
  const getVCFromClaims = async ({ claims, challenge, trustedIssuers, vcTypes, locale = 'en', vcId }) => {
812
- const credential = claims.find(
813
- (x) => x.type === 'verifiableCredential' && vcTypes.some((item) => x.item.includes(item))
814
- );
824
+ const credential = claims
825
+ .filter(Boolean) // FIXES: https://github.com/ArcBlock/did-connect/issues/74
826
+ .find((x) => x.type === 'verifiableCredential' && vcTypes.some((item) => x.item.includes(item)));
815
827
 
816
828
  if (!credential || !credential.presentation) {
817
829
  return {};
package/lib/server.js CHANGED
@@ -1,11 +1,14 @@
1
1
  const get = require('lodash/get');
2
+ const pick = require('lodash/pick');
2
3
  const isEmpty = require('lodash/isEmpty');
3
4
  const last = require('lodash/last');
4
5
  const { isNFTExpired, isNFTConsumed } = require('@abtnode/util/lib/nft');
5
6
  const Client = require('@ocap/client');
6
7
  const { fromPublicKey } = require('@ocap/wallet');
7
- const { fromBase58, toAddress } = require('@ocap/util');
8
+ const { fromBase58, toAddress, toHex } = require('@ocap/util');
8
9
  const { toTypeInfo, isFromPublicKey } = require('@arcblock/did');
10
+ const urlFriendly = require('@blocklet/meta/lib/url-friendly').default;
11
+ const { slugify } = require('transliteration');
9
12
  const formatContext = require('@abtnode/util/lib/format-context');
10
13
  const {
11
14
  ROLES,
@@ -219,6 +222,15 @@ const authenticateByNFT = async ({ node, claims, userDid, challenge, locale, isA
219
222
  return { role: ROLES.OWNER, teamDid: info.did, nft: state, user, passport: null, ownerDid, ownerNFT: address };
220
223
  };
221
224
 
225
+ const authenticateBySession = async ({ node, userDid }) => {
226
+ const info = await node.getNodeInfo();
227
+ const user = await getUser(node, info.did, userDid);
228
+ const passport = (user.passports || []).find(
229
+ (x) => x.status === 'valid' && ['owner', 'admin', 'member'].includes(x.role)
230
+ );
231
+ return { role: passport ? passport.role : ROLES.GUEST, teamDid: info.did, user, passport: null };
232
+ };
233
+
222
234
  const getAuthVcClaim =
223
235
  ({ node, launchBlocklet, blocklet, options }) =>
224
236
  async ({ extraParams: { locale, passportId }, context: { didwallet } }) => {
@@ -273,28 +285,57 @@ const getAuthNFTClaim =
273
285
  return getOwnershipNFTClaim(node, locale);
274
286
  };
275
287
 
276
- const getLaunchBlockletClaims = (node, authMethod) => {
277
- if (authMethod === 'vc') {
288
+ const getKeyPairClaim =
289
+ () =>
290
+ async ({ extraParams: { locale, title }, context: { didwallet } }) => {
291
+ checkWalletVersion({ didwallet, locale });
292
+
293
+ const description = {
294
+ en: 'Please generate a new key-pair for this application',
295
+ zh: '请为应用创建新的钥匙对',
296
+ };
297
+
278
298
  return {
279
- serverPassport: ['verifiableCredential', getAuthVcClaim({ node, launchBlocklet: true })],
299
+ mfa: !process.env.DID_CONNECT_MFA_DISABLED,
300
+ description: description[locale] || description.en,
301
+ moniker: (urlFriendly(slugify(title)) || 'application').toLowerCase(),
302
+ targetType: {
303
+ role: 'application',
304
+ hash: 'sha3',
305
+ key: 'ed25519',
306
+ encoding: 'base58',
307
+ },
280
308
  };
281
- }
309
+ };
282
310
 
283
- return {
284
- serverNFT: ['asset', getAuthNFTClaim({ node })],
311
+ const getLaunchBlockletClaims = (node, authMethod) => {
312
+ const claims = {
313
+ blockletAppKeypair: ['keyPair', getKeyPairClaim()],
285
314
  };
315
+
316
+ if (authMethod === 'vc') {
317
+ claims.serverPassport = ['verifiableCredential', getAuthVcClaim({ node, launchBlocklet: true })];
318
+ }
319
+
320
+ if (authMethod === 'nft') {
321
+ claims.serverNFT = ['asset', getAuthNFTClaim({ node })];
322
+ }
323
+
324
+ return claims;
286
325
  };
287
326
 
327
+ // FIXME: @wangshijun should be changed to blocklet appSK owner claim?
288
328
  const getSetupBlockletClaims = (node, authMethod, blocklet) => {
329
+ const claims = {};
330
+
289
331
  if (authMethod === 'vc') {
290
- return {
291
- serverPassport: ['verifiableCredential', getAuthVcClaim({ node, blocklet })],
292
- };
332
+ claims.serverPassport = ['verifiableCredential', getAuthVcClaim({ node, blocklet })];
333
+ }
334
+ if (authMethod === 'nft') {
335
+ claims.serverNFT = ['asset', getAuthNFTClaim({ node })];
293
336
  }
294
337
 
295
- return {
296
- serverNFT: ['asset', getAuthNFTClaim({ node })],
297
- };
338
+ return claims;
298
339
  };
299
340
 
300
341
  const getOwnershipNFTClaim = async (node, locale) => {
@@ -361,7 +402,7 @@ const ensureBlockletPermission = async ({
361
402
  launchBlocklet: true,
362
403
  blocklet,
363
404
  });
364
- } else {
405
+ } else if (authMethod === 'nft') {
365
406
  result = await authenticateByNFT({
366
407
  node,
367
408
  locale,
@@ -371,9 +412,14 @@ const ensureBlockletPermission = async ({
371
412
  isAuth,
372
413
  chainHost,
373
414
  });
415
+ } else {
416
+ result = await authenticateBySession({
417
+ node,
418
+ userDid,
419
+ });
374
420
  }
375
- const { teamDid, role } = result;
376
421
 
422
+ const { teamDid, role } = result;
377
423
  const permissions = await node.getPermissionsByRole({ teamDid, role: { name: role } });
378
424
  if (!permissions.some((item) => ['mutate_blocklets'].includes(item.name))) {
379
425
  throw new Error(messages.notAuthorized[locale]);
@@ -385,17 +431,38 @@ const ensureBlockletPermission = async ({
385
431
  const createLaunchBlockletHandler =
386
432
  (node, authMethod) =>
387
433
  async ({ claims, challenge, userDid, updateSession, req, extraParams }) => {
388
- const { locale, blockletMetaUrl, chainHost } = extraParams;
434
+ const { locale, blockletMetaUrl, title, description, chainHost } = extraParams;
389
435
  logger.info('createLaunchBlockletHandler', extraParams);
390
436
 
391
- if (!blockletMetaUrl) {
392
- logger.error('blockletMetaUrl must be provided');
393
- throw new Error(messages.invalidParams[locale]);
437
+ const keyPair = claims.find((x) => x.type === 'keyPair');
438
+ if (!keyPair) {
439
+ logger.error('app keyPair must be provided');
440
+ throw new Error(messages.missingKeyPair[locale]);
441
+ }
442
+
443
+ if (!blockletMetaUrl && !title && !description) {
444
+ logger.error('blockletMetaUrl | title + description must be provided');
445
+ throw new Error(messages.missingBlockletUrl[locale]);
394
446
  }
395
447
 
396
448
  if (authMethod === 'nft' && !chainHost) {
397
449
  logger.error('chainHost must be provided');
398
- throw new Error(messages.invalidParams[locale]);
450
+ throw new Error(messages.missingChainHost[locale]);
451
+ }
452
+
453
+ let blocklet;
454
+ if (blockletMetaUrl) {
455
+ blocklet = await node.getBlockletMetaFromUrl({ url: blockletMetaUrl, checkPrice: true });
456
+ if (!blocklet.meta) {
457
+ throw new Error(messages.invalidBlocklet[locale]);
458
+ }
459
+
460
+ if (!blocklet.isFree) {
461
+ if (isEmpty(extraParams?.previousWorkflowData?.downloadTokenList)) {
462
+ logger.error('downloadTokenList must be provided');
463
+ throw new Error(messages.invalidParams[locale]);
464
+ }
465
+ }
399
466
  }
400
467
 
401
468
  const { role, passport, user, extra, nft } = await ensureBlockletPermission({
@@ -407,22 +474,9 @@ const createLaunchBlockletHandler =
407
474
  locale,
408
475
  isAuth: false,
409
476
  chainHost,
477
+ blocklet,
410
478
  });
411
479
 
412
- const blocklet = await node.getBlockletMetaFromUrl({ url: blockletMetaUrl, checkPrice: true });
413
- if (!blocklet.meta) {
414
- throw new Error(messages.invalidBlocklet[locale]);
415
- }
416
-
417
- if (!blocklet.isFree) {
418
- if (isEmpty(extraParams?.previousWorkflowData?.downloadTokenList)) {
419
- logger.error('downloadTokenList must be provided');
420
- throw new Error(messages.invalidParams[locale]);
421
- }
422
- }
423
-
424
- const { did } = blocklet.meta;
425
-
426
480
  let controller;
427
481
 
428
482
  let sessionToken = '';
@@ -434,76 +488,109 @@ const createLaunchBlockletHandler =
434
488
  secret,
435
489
  expiresIn: LAUNCH_BLOCKLET_TOKEN_EXPIRE,
436
490
  });
437
- } else if (role === SERVER_ROLES.EXTERNAL_BLOCKLET_CONTROLLER) {
438
- controller = extra.controller;
439
- sessionToken = createBlockletControllerAuthToken({
440
- did: userDid,
441
- role,
442
- controller,
443
- secret,
444
- expiresIn: EXTERNAL_LAUNCH_BLOCKLET_TOKEN_EXPIRE,
445
- });
446
- } else {
447
- sessionToken = createAuthTokenByOwnershipNFT({
448
- did: userDid,
449
- role,
450
- secret,
451
- expiresIn: LAUNCH_BLOCKLET_TOKEN_EXPIRE,
452
- });
491
+ }
492
+ if (authMethod === 'nft') {
493
+ if (role === SERVER_ROLES.EXTERNAL_BLOCKLET_CONTROLLER) {
494
+ controller = extra.controller;
495
+ sessionToken = createBlockletControllerAuthToken({
496
+ did: userDid,
497
+ role,
498
+ controller,
499
+ secret,
500
+ expiresIn: EXTERNAL_LAUNCH_BLOCKLET_TOKEN_EXPIRE,
501
+ });
502
+ } else {
503
+ sessionToken = createAuthTokenByOwnershipNFT({
504
+ did: userDid,
505
+ role,
506
+ secret,
507
+ expiresIn: LAUNCH_BLOCKLET_TOKEN_EXPIRE,
508
+ });
509
+ }
453
510
  }
454
511
 
455
- await updateSession({ sessionToken }, true);
512
+ if (sessionToken) {
513
+ await updateSession({ sessionToken }, true);
514
+ }
456
515
 
457
- const blockletDid =
458
- role === SERVER_ROLES.EXTERNAL_BLOCKLET_CONTROLLER
459
- ? toExternalBlocklet(blocklet.meta.name, controller.nftId, { didOnly: true })
460
- : blocklet.meta.did;
516
+ if (blocklet) {
517
+ const blockletDid =
518
+ role === SERVER_ROLES.EXTERNAL_BLOCKLET_CONTROLLER
519
+ ? toExternalBlocklet(blocklet.meta.name, controller.nftId, { didOnly: true })
520
+ : blocklet.meta.did;
461
521
 
462
- await updateSession({
463
- blockletDid,
464
- });
522
+ await updateSession({ blockletDid });
465
523
 
466
- // 检查是否已安装,这里不做升级的处理
467
- const existedBlocklet = await node.getBlocklet({ did: blockletDid, attachRuntimeInfo: false });
524
+ // 检查是否已安装,这里不做升级的处理
525
+ const existedBlocklet = await node.getBlocklet({ did: blockletDid, attachRuntimeInfo: false });
468
526
 
469
- // 如果是 serverless, 并且已经消费过了,但是没有安装,则抛出异常
470
- if (!existedBlocklet && role === SERVER_ROLES.EXTERNAL_BLOCKLET_CONTROLLER && isNFTConsumed(nft)) {
471
- throw new Error(messages.nftAlreadyConsume[locale]);
472
- }
527
+ // 如果是 serverless, 并且已经消费过了,但是没有安装,则抛出异常
528
+ if (!existedBlocklet && role === SERVER_ROLES.EXTERNAL_BLOCKLET_CONTROLLER && isNFTConsumed(nft)) {
529
+ throw new Error(messages.nftAlreadyConsume[locale]);
530
+ }
473
531
 
474
- if (existedBlocklet) {
475
- await updateSession({ isInstalled: true });
476
- logger.info('blocklet already exists', { blockletDid });
477
- return;
532
+ if (existedBlocklet) {
533
+ await updateSession({ isInstalled: true });
534
+ logger.info('blocklet already exists', { blockletDid });
535
+ return;
536
+ }
478
537
  }
479
538
 
480
- const tmp = await node.installBlocklet({
481
- url: blockletMetaUrl,
482
- delay: 1000 * 4, // delay 4 seconds to download, wait for ws connection from frontend
483
- downloadTokenList: extraParams?.previousWorkflowData?.downloadTokenList,
484
- controller: role === SERVER_ROLES.EXTERNAL_BLOCKLET_CONTROLLER ? controller : null,
485
- });
486
-
487
- await node.createAuditLog(
539
+ logger.info('start install blocklet', { blockletMetaUrl, title, description });
540
+ const tmp = await node.installBlocklet(
488
541
  {
489
- action: 'installBlocklet',
490
- args: { url: blockletMetaUrl },
491
- context: formatContext(Object.assign(req, { user })),
492
- result: tmp,
542
+ url: blockletMetaUrl,
543
+ title,
544
+ description,
545
+ appSk: toHex(keyPair.secret),
546
+ delay: 1000 * 4, // delay 4 seconds to download, wait for ws connection from frontend
547
+ downloadTokenList: extraParams?.previousWorkflowData?.downloadTokenList,
548
+ controller: role === SERVER_ROLES.EXTERNAL_BLOCKLET_CONTROLLER ? controller : null,
493
549
  },
494
- node
550
+ formatContext(Object.assign(req, { user: { ...pick(user, ['did', 'fullName']), role } }))
495
551
  );
496
- logger.info('start install blocklet', { blockletDid, bundleDid: did });
552
+
553
+ await updateSession({ blockletDid: tmp.meta.did });
554
+ };
555
+
556
+ const getBlockletPermissionChecker =
557
+ (node) =>
558
+ async ({ userDid, extraParams }) => {
559
+ const { locale = 'en', connectedDid } = extraParams;
560
+
561
+ if (!connectedDid || userDid !== connectedDid) {
562
+ throw new Error(
563
+ {
564
+ en: 'Please use current connected wallet to install blocklet',
565
+ zh: '请使用当前登录的钱包来安装应用',
566
+ }[locale]
567
+ );
568
+ }
569
+
570
+ const info = await node.getNodeInfo();
571
+ const user = await getUser(node, info.did, userDid);
572
+ const passport = (user.passports || []).find((x) => x.status === 'valid' && ['owner', 'admin'].includes(x.role));
573
+ if (!passport) {
574
+ throw new Error(
575
+ {
576
+ en: 'You do not have permission to install blocklets on this server',
577
+ zh: '你无权在此节点上安装应用',
578
+ }[locale]
579
+ );
580
+ }
497
581
  };
498
582
 
499
583
  module.exports = {
500
584
  getAuthVcClaim,
585
+ getKeyPairClaim,
501
586
  authenticateByVc,
502
587
  authenticateByNFT,
588
+ authenticateBySession,
503
589
  getOwnershipNFTClaim,
504
590
  getLaunchBlockletClaims,
505
591
  createLaunchBlockletHandler,
506
592
  ensureBlockletPermission,
593
+ getBlockletPermissionChecker,
507
594
  getSetupBlockletClaims,
508
595
  getTrustedIssuers,
509
596
  getServerlessNFTClaim,
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.8.66",
6
+ "version": "1.8.67-beta-794a8082",
7
7
  "description": "Simple lib to manage auth in ABT Node",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -20,27 +20,28 @@
20
20
  "author": "linchen <linchen1987@foxmail.com> (http://github.com/linchen1987)",
21
21
  "license": "MIT",
22
22
  "dependencies": {
23
- "@abtnode/constant": "1.8.66",
24
- "@abtnode/logger": "1.8.66",
25
- "@abtnode/util": "1.8.66",
26
- "@arcblock/did": "1.18.42",
27
- "@arcblock/jwt": "^1.18.42",
28
- "@arcblock/vc": "1.18.42",
29
- "@blocklet/constant": "1.8.66",
30
- "@blocklet/meta": "1.8.66",
31
- "@ocap/client": "1.18.42",
32
- "@ocap/mcrypto": "1.18.42",
33
- "@ocap/util": "1.18.42",
34
- "@ocap/wallet": "1.18.42",
23
+ "@abtnode/constant": "1.8.67-beta-794a8082",
24
+ "@abtnode/logger": "1.8.67-beta-794a8082",
25
+ "@abtnode/util": "1.8.67-beta-794a8082",
26
+ "@arcblock/did": "1.18.54",
27
+ "@arcblock/jwt": "^1.18.54",
28
+ "@arcblock/vc": "1.18.54",
29
+ "@blocklet/constant": "1.8.67-beta-794a8082",
30
+ "@blocklet/meta": "1.8.67-beta-794a8082",
31
+ "@ocap/client": "1.18.54",
32
+ "@ocap/mcrypto": "1.18.54",
33
+ "@ocap/util": "1.18.54",
34
+ "@ocap/wallet": "1.18.54",
35
35
  "axios": "^0.27.2",
36
36
  "joi": "17.7.0",
37
37
  "jsonwebtoken": "^9.0.0",
38
38
  "lodash": "^4.17.21",
39
39
  "semver": "^7.3.8",
40
+ "transliteration": "^2.3.5",
40
41
  "url-join": "^4.0.1"
41
42
  },
42
43
  "devDependencies": {
43
44
  "jest": "^27.5.1"
44
45
  },
45
- "gitHead": "282247fd0ce6702e1d6920e90e2b3be0408cf879"
46
+ "gitHead": "f4ad32bea4d80b12971fb6bef941bdbe2af6a834"
46
47
  }