@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.
- package/lib/auth.js +15 -3
- package/lib/server.js +169 -82
- 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
|
|
813
|
-
(
|
|
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
|
|
277
|
-
|
|
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
|
-
|
|
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
|
-
|
|
284
|
-
|
|
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
|
-
|
|
291
|
-
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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.
|
|
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
|
-
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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
|
-
|
|
512
|
+
if (sessionToken) {
|
|
513
|
+
await updateSession({ sessionToken }, true);
|
|
514
|
+
}
|
|
456
515
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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
|
-
|
|
463
|
-
blockletDid,
|
|
464
|
-
});
|
|
522
|
+
await updateSession({ blockletDid });
|
|
465
523
|
|
|
466
|
-
|
|
467
|
-
|
|
524
|
+
// 检查是否已安装,这里不做升级的处理
|
|
525
|
+
const existedBlocklet = await node.getBlocklet({ did: blockletDid, attachRuntimeInfo: false });
|
|
468
526
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
532
|
+
if (existedBlocklet) {
|
|
533
|
+
await updateSession({ isInstalled: true });
|
|
534
|
+
logger.info('blocklet already exists', { blockletDid });
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
478
537
|
}
|
|
479
538
|
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
550
|
+
formatContext(Object.assign(req, { user: { ...pick(user, ['did', 'fullName']), role } }))
|
|
495
551
|
);
|
|
496
|
-
|
|
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.
|
|
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.
|
|
24
|
-
"@abtnode/logger": "1.8.
|
|
25
|
-
"@abtnode/util": "1.8.
|
|
26
|
-
"@arcblock/did": "1.18.
|
|
27
|
-
"@arcblock/jwt": "^1.18.
|
|
28
|
-
"@arcblock/vc": "1.18.
|
|
29
|
-
"@blocklet/constant": "1.8.
|
|
30
|
-
"@blocklet/meta": "1.8.
|
|
31
|
-
"@ocap/client": "1.18.
|
|
32
|
-
"@ocap/mcrypto": "1.18.
|
|
33
|
-
"@ocap/util": "1.18.
|
|
34
|
-
"@ocap/wallet": "1.18.
|
|
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": "
|
|
46
|
+
"gitHead": "f4ad32bea4d80b12971fb6bef941bdbe2af6a834"
|
|
46
47
|
}
|