@abtnode/auth 1.15.17 → 1.16.0-beta-b16cb035
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 +416 -53
- package/lib/invitation.js +98 -0
- package/lib/lost-passport.js +76 -31
- package/lib/passport.js +31 -33
- package/lib/server.js +723 -0
- package/lib/util/create-passport-svg.js +101 -0
- package/lib/util/get-auth-method.js +25 -0
- package/lib/util/passport-color.js +147 -0
- package/package.json +20 -16
package/lib/auth.js
CHANGED
|
@@ -1,28 +1,41 @@
|
|
|
1
|
+
const path = require('path');
|
|
1
2
|
const jwt = require('jsonwebtoken');
|
|
2
3
|
const semver = require('semver');
|
|
3
4
|
const joinUrl = require('url-join');
|
|
4
5
|
const get = require('lodash/get');
|
|
5
6
|
const { verifyPresentation, createCredentialList } = require('@arcblock/vc');
|
|
7
|
+
const formatContext = require('@abtnode/util/lib/format-context');
|
|
6
8
|
const Mcrypto = require('@ocap/mcrypto');
|
|
9
|
+
const Client = require('@ocap/client');
|
|
7
10
|
const { fromSecretKey, WalletType } = require('@ocap/wallet');
|
|
8
11
|
const getBlockletInfo = require('@blocklet/meta/lib/info');
|
|
9
|
-
const {
|
|
12
|
+
const {
|
|
13
|
+
PASSPORT_STATUS,
|
|
14
|
+
VC_TYPE_NODE_PASSPORT,
|
|
15
|
+
ROLES,
|
|
16
|
+
NODE_DATA_DIR_NAME,
|
|
17
|
+
AUTH_CERT_TYPE,
|
|
18
|
+
WELLKNOWN_BLOCKLET_ADMIN_PATH,
|
|
19
|
+
} = require('@abtnode/constant');
|
|
10
20
|
const axios = require('@abtnode/util/lib/axios');
|
|
21
|
+
const { extractUserAvatar, parseUserAvatar } = require('@abtnode/util/lib/user-avatar');
|
|
22
|
+
|
|
11
23
|
const logger = require('./logger');
|
|
12
24
|
const verifySignature = require('./util/verify-signature');
|
|
13
25
|
const {
|
|
14
26
|
createPassport,
|
|
15
27
|
createPassportVC,
|
|
16
|
-
createPassportSvg,
|
|
17
28
|
upsertToPassports,
|
|
18
29
|
createUserPassport,
|
|
19
30
|
getRoleFromLocalPassport,
|
|
20
31
|
} = require('./passport');
|
|
21
32
|
|
|
33
|
+
const createPassportSvg = require('./util/create-passport-svg');
|
|
34
|
+
|
|
22
35
|
const messages = {
|
|
23
36
|
description: {
|
|
24
|
-
en: '
|
|
25
|
-
zh: '
|
|
37
|
+
en: 'Connect your DID Wallet',
|
|
38
|
+
zh: '连接 DID 钱包',
|
|
26
39
|
},
|
|
27
40
|
requestProfile: {
|
|
28
41
|
en: 'Please provide following information to continue',
|
|
@@ -36,6 +49,10 @@ const messages = {
|
|
|
36
49
|
en: 'Please provide passport',
|
|
37
50
|
zh: '请提供通行证',
|
|
38
51
|
},
|
|
52
|
+
requestOwnerPassport: {
|
|
53
|
+
en: 'Please provide owner passport',
|
|
54
|
+
zh: '请提供节点的所有者通行证',
|
|
55
|
+
},
|
|
39
56
|
requestBlockletNft: {
|
|
40
57
|
en: 'Please provide Blocklet Purchase NFT',
|
|
41
58
|
zh: '请提供 Blocklet Purchase NFT',
|
|
@@ -46,6 +63,10 @@ const messages = {
|
|
|
46
63
|
},
|
|
47
64
|
|
|
48
65
|
// error
|
|
66
|
+
actionForbidden: {
|
|
67
|
+
en: 'You are not allowed perform this action',
|
|
68
|
+
zh: '你没有权限执行此操作',
|
|
69
|
+
},
|
|
49
70
|
|
|
50
71
|
notInitialized: {
|
|
51
72
|
en: 'This node is not initialized, login is disabled',
|
|
@@ -75,6 +96,10 @@ const messages = {
|
|
|
75
96
|
en: (types) => `Invalid credential type, expect ${types.join(' or ')}`,
|
|
76
97
|
zh: (types) => `无效的凭证类型,必须是 ${types.join(' 或 ')}`,
|
|
77
98
|
},
|
|
99
|
+
invalidCredentialId: {
|
|
100
|
+
en: (ids) => `Invalid credential type, expect ${[].concat(ids).join(' or ')}`,
|
|
101
|
+
zh: (ids) => `无效的凭证,ID 必须是 ${[].concat(ids).join(' 或 ')}`,
|
|
102
|
+
},
|
|
78
103
|
invalidCredentialHolder: {
|
|
79
104
|
en: 'Invalid credential holder',
|
|
80
105
|
zh: '无效的凭证持有者',
|
|
@@ -91,12 +116,16 @@ const messages = {
|
|
|
91
116
|
en: 'The account does not match the owner account of this passport, please use the DID wallet that contains the owner account of this passport to receive.',
|
|
92
117
|
zh: '该账号与此通行证的所有者账号不匹配,请使用包含此通行证所有者账号的 DID 钱包领取。',
|
|
93
118
|
},
|
|
119
|
+
userMismatch: {
|
|
120
|
+
en: 'User mismatch, please use connected DID wallet to continue.',
|
|
121
|
+
zh: '用户不匹配,请使用当前会话连接的钱包操作',
|
|
122
|
+
},
|
|
94
123
|
lowVersion: {
|
|
95
124
|
en: 'Your DID wallet version is too low, please upgrade to the latest version',
|
|
96
125
|
zh: '你的 DID 钱包版本过低,请升级至最新版本',
|
|
97
126
|
},
|
|
98
127
|
passportStatusCheckFailed: {
|
|
99
|
-
en: (message) => `Passport status check failed
|
|
128
|
+
en: (message) => `Passport status check failed: ${message}`,
|
|
100
129
|
zh: (message) => `通行证状态检测失败:${message}`,
|
|
101
130
|
},
|
|
102
131
|
unKnownStatus: {
|
|
@@ -127,6 +156,22 @@ const messages = {
|
|
|
127
156
|
en: 'Invalid Params',
|
|
128
157
|
zh: '无效的参数',
|
|
129
158
|
},
|
|
159
|
+
missingKeyPair: {
|
|
160
|
+
en: 'Missing app key pair',
|
|
161
|
+
zh: '缺少应用钥匙对',
|
|
162
|
+
},
|
|
163
|
+
missingBlockletUrl: {
|
|
164
|
+
en: 'Missing blocklet url',
|
|
165
|
+
zh: '缺少应用下载地址',
|
|
166
|
+
},
|
|
167
|
+
missingBlockletDid: {
|
|
168
|
+
en: 'Missing blocklet did',
|
|
169
|
+
zh: '缺少应用 ID',
|
|
170
|
+
},
|
|
171
|
+
missingChainHost: {
|
|
172
|
+
en: 'Missing chain host',
|
|
173
|
+
zh: '缺少链的端点地址',
|
|
174
|
+
},
|
|
130
175
|
invalidBlocklet: {
|
|
131
176
|
en: 'Invalid Blocklet',
|
|
132
177
|
zh: '无效的 Blocklet',
|
|
@@ -139,10 +184,101 @@ const messages = {
|
|
|
139
184
|
en: 'Invalid Blocklet VC',
|
|
140
185
|
zh: '无效的 Blocklet VC',
|
|
141
186
|
},
|
|
187
|
+
invalidAppVersion: {
|
|
188
|
+
en: 'App key-pair rotating can only be performed on the latest version',
|
|
189
|
+
zh: '只有最新版应用可以变更钥匙对',
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
// NFT related
|
|
193
|
+
missingProfileClaim: {
|
|
194
|
+
en: 'Owner profile not provided',
|
|
195
|
+
zh: '节点所有者信息必须提供',
|
|
196
|
+
},
|
|
197
|
+
invalidNftClaim: {
|
|
198
|
+
en: 'Invalid Asset Claim: ownerPk, ownerDid and ownerProof are required',
|
|
199
|
+
zh: '无效的 NFT Claim:ownerPk、ownerDid、ownerProof 是必填的',
|
|
200
|
+
},
|
|
201
|
+
invalidNft: {
|
|
202
|
+
en: 'Invalid server ownership NFT state',
|
|
203
|
+
zh: '无效的节点所有权 NFT',
|
|
204
|
+
},
|
|
205
|
+
invalidNftHolder: {
|
|
206
|
+
en: 'Invalid server ownership NFT holder',
|
|
207
|
+
zh: '无效的节点所有权 NFT 持有者',
|
|
208
|
+
},
|
|
209
|
+
invalidNftProof: {
|
|
210
|
+
en: 'Invalid server ownership NFT signature proof',
|
|
211
|
+
zh: '无效的节点所有权 NFT 签名',
|
|
212
|
+
},
|
|
213
|
+
invalidNftIssuer: {
|
|
214
|
+
en: 'Invalid server ownership NFT issuer',
|
|
215
|
+
zh: '无效的节点所有权 NFT 颁发者',
|
|
216
|
+
},
|
|
217
|
+
tagNotMatch: {
|
|
218
|
+
en: 'This NFT is for another blocklet server',
|
|
219
|
+
zh: '您所提供的所有权 NFT 不属于当前节点',
|
|
220
|
+
},
|
|
221
|
+
requestNft: {
|
|
222
|
+
en: 'Please provide server ownership NFT',
|
|
223
|
+
zh: '请提供节点所有权 NFT',
|
|
224
|
+
},
|
|
225
|
+
requestServerlessNFT: {
|
|
226
|
+
en: 'Please provide serverless NFT',
|
|
227
|
+
zh: '请提供无服务 NFT',
|
|
228
|
+
},
|
|
229
|
+
serverlessNftIdRequired: {
|
|
230
|
+
en: 'Serverless NFT ID is required',
|
|
231
|
+
zh: '无服务 NFT ID 是必须的',
|
|
232
|
+
},
|
|
233
|
+
nftAlreadyConsume: {
|
|
234
|
+
en: 'This NFT has already been used',
|
|
235
|
+
zh: '该 NFT 已经被使用过了',
|
|
236
|
+
},
|
|
237
|
+
nftAlreadyExpired: {
|
|
238
|
+
en: 'This NFT has expired',
|
|
239
|
+
zh: '该 NFT 已经过期',
|
|
240
|
+
},
|
|
241
|
+
missingNftClaim: {
|
|
242
|
+
en: 'Ownership NFT not provided',
|
|
243
|
+
zh: '节点所有权 NFT 必须提供',
|
|
244
|
+
},
|
|
245
|
+
noNft: {
|
|
246
|
+
en: 'This server is not initialized to accept ownership NFT',
|
|
247
|
+
zh: '该节点不能用这种方式成为管理员',
|
|
248
|
+
},
|
|
249
|
+
noTag: {
|
|
250
|
+
en: 'Tag not found from server launcher info',
|
|
251
|
+
zh: '节点初始化信息异常:缺少标签',
|
|
252
|
+
},
|
|
253
|
+
noChainHost: {
|
|
254
|
+
en: 'chainHost not found from server launcher info',
|
|
255
|
+
zh: '节点初始化信息异常:缺少链地址',
|
|
256
|
+
},
|
|
257
|
+
alreadyTransferred: {
|
|
258
|
+
en: 'The Blocklet Server already belongs to {owner}',
|
|
259
|
+
zh: '该节点已经属于 {owner}',
|
|
260
|
+
},
|
|
261
|
+
delegateTransferOwnerNFT: {
|
|
262
|
+
en: 'Sign the delegation transaction to authorize blocklet server to transfer your ownership NFT to new owner when he/she claims the transfer.',
|
|
263
|
+
zh: '签名下面的交易以授权节点转移所有权以及代表所有权的 NFT。',
|
|
264
|
+
},
|
|
265
|
+
notAllowedTransferToSelf: {
|
|
266
|
+
en: 'Not allowed to transfer the Server to yourself',
|
|
267
|
+
zh: '不能将节点转移给自己',
|
|
268
|
+
},
|
|
269
|
+
tagRequired: {
|
|
270
|
+
en: 'tag is required',
|
|
271
|
+
zh: 'tag 不能为空',
|
|
272
|
+
},
|
|
142
273
|
};
|
|
143
274
|
|
|
144
275
|
const PASSPORT_STATUS_KEY = 'passport-status';
|
|
145
276
|
|
|
277
|
+
const TEAM_TYPE = {
|
|
278
|
+
NODE: 'node',
|
|
279
|
+
BLOCKLET: 'blocklet',
|
|
280
|
+
};
|
|
281
|
+
|
|
146
282
|
const getPassportStatusEndpoint = ({ baseUrl, userDid, teamDid }) => {
|
|
147
283
|
if (!baseUrl) {
|
|
148
284
|
return null;
|
|
@@ -172,24 +308,37 @@ const getRandomMessage = (len = 16) => {
|
|
|
172
308
|
return hex.replace(/^0x/, '').toUpperCase();
|
|
173
309
|
};
|
|
174
310
|
|
|
175
|
-
const
|
|
311
|
+
const getApplicationInfo = async ({ node, nodeInfo, teamDid }) => {
|
|
176
312
|
let type;
|
|
177
313
|
let name;
|
|
178
314
|
let wallet;
|
|
315
|
+
let permanentWallet;
|
|
179
316
|
let description;
|
|
317
|
+
let passportColor;
|
|
318
|
+
let owner;
|
|
319
|
+
let dataDir;
|
|
180
320
|
|
|
181
321
|
if (teamDid === nodeInfo.did) {
|
|
182
322
|
name = nodeInfo.name;
|
|
183
323
|
description = nodeInfo.description;
|
|
184
|
-
|
|
185
|
-
|
|
324
|
+
const _wallet = getNodeWallet(nodeInfo.sk);
|
|
325
|
+
wallet = _wallet;
|
|
326
|
+
permanentWallet = _wallet;
|
|
327
|
+
type = TEAM_TYPE.NODE;
|
|
328
|
+
passportColor = 'default';
|
|
329
|
+
owner = nodeInfo.nodeOwner;
|
|
330
|
+
dataDir = path.join(node.dataDirs.data, NODE_DATA_DIR_NAME);
|
|
186
331
|
} else {
|
|
187
332
|
const blocklet = await node.getBlocklet({ did: teamDid, attachRuntimeInfo: false });
|
|
188
333
|
const blockletInfo = getBlockletInfo(blocklet, nodeInfo.sk);
|
|
189
334
|
name = blockletInfo.name;
|
|
190
335
|
description = blockletInfo.description;
|
|
191
336
|
wallet = blockletInfo.wallet;
|
|
192
|
-
|
|
337
|
+
permanentWallet = blockletInfo.permanentWallet;
|
|
338
|
+
passportColor = blockletInfo.passportColor;
|
|
339
|
+
type = TEAM_TYPE.BLOCKLET;
|
|
340
|
+
owner = get(blocklet, 'settings.owner');
|
|
341
|
+
dataDir = blocklet.env.dataDir;
|
|
193
342
|
}
|
|
194
343
|
|
|
195
344
|
return {
|
|
@@ -197,7 +346,10 @@ const getTeamInfo = async ({ node, nodeInfo, teamDid }) => {
|
|
|
197
346
|
name,
|
|
198
347
|
description,
|
|
199
348
|
wallet,
|
|
200
|
-
|
|
349
|
+
permanentWallet,
|
|
350
|
+
passportColor,
|
|
351
|
+
owner,
|
|
352
|
+
dataDir,
|
|
201
353
|
};
|
|
202
354
|
};
|
|
203
355
|
|
|
@@ -219,9 +371,21 @@ const createAuthToken = ({ did, passport, role, secret, expiresIn } = {}) => {
|
|
|
219
371
|
return token;
|
|
220
372
|
};
|
|
221
373
|
|
|
222
|
-
const
|
|
374
|
+
const createBlockletControllerAuthToken = ({ did, role, controller, secret, expiresIn } = {}) => {
|
|
375
|
+
const payload = {
|
|
376
|
+
type: AUTH_CERT_TYPE.BLOCKLET_CONTROLLER,
|
|
377
|
+
did,
|
|
378
|
+
role,
|
|
379
|
+
controller,
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
return jwt.sign(payload, secret, { expiresIn });
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
const createAuthTokenByOwnershipNFT = ({ did, role, secret, expiresIn } = {}) => {
|
|
223
386
|
const payload = {
|
|
224
|
-
type:
|
|
387
|
+
type: AUTH_CERT_TYPE.OWNERSHIP_NFT,
|
|
388
|
+
did,
|
|
225
389
|
role,
|
|
226
390
|
};
|
|
227
391
|
|
|
@@ -234,8 +398,7 @@ const getUser = async (node, teamDid, userDid) => {
|
|
|
234
398
|
};
|
|
235
399
|
|
|
236
400
|
const beforeInvitationRequest = async ({ node, teamDid, inviteId, locale = 'en' }) => {
|
|
237
|
-
const
|
|
238
|
-
const inviteInfo = invitations.find((d) => d.inviteId === inviteId);
|
|
401
|
+
const inviteInfo = await node.getInvitation({ teamDid, inviteId });
|
|
239
402
|
|
|
240
403
|
if (!inviteInfo) {
|
|
241
404
|
throw new Error(
|
|
@@ -245,17 +408,30 @@ const beforeInvitationRequest = async ({ node, teamDid, inviteId, locale = 'en'
|
|
|
245
408
|
}[locale]
|
|
246
409
|
);
|
|
247
410
|
}
|
|
411
|
+
|
|
412
|
+
const count = await node.getUsersCount({ teamDid });
|
|
413
|
+
if (count === 0) {
|
|
414
|
+
throw new Error(
|
|
415
|
+
{
|
|
416
|
+
en: 'The Application has no owner attached',
|
|
417
|
+
zh: '应用未绑定所有者',
|
|
418
|
+
}[locale]
|
|
419
|
+
);
|
|
420
|
+
}
|
|
248
421
|
};
|
|
249
422
|
|
|
250
423
|
const createInvitationRequest = async ({ node, nodeInfo, teamDid, inviteId, locale = 'en' }) => {
|
|
251
424
|
// verify invite id
|
|
252
|
-
const
|
|
253
|
-
const inviteInfo = invitations.find((d) => d.inviteId === inviteId);
|
|
425
|
+
const inviteInfo = await node.getInvitation({ teamDid, inviteId });
|
|
254
426
|
if (!inviteInfo) {
|
|
255
427
|
throw new Error('The invitation does not exist or has been used');
|
|
256
428
|
}
|
|
257
429
|
|
|
258
|
-
const {
|
|
430
|
+
const {
|
|
431
|
+
name: issuerName,
|
|
432
|
+
wallet: issuerWallet,
|
|
433
|
+
passportColor,
|
|
434
|
+
} = await getApplicationInfo({ node, nodeInfo, teamDid });
|
|
259
435
|
|
|
260
436
|
const passport = await createPassport({
|
|
261
437
|
name: inviteInfo.role,
|
|
@@ -270,12 +446,19 @@ const createInvitationRequest = async ({ node, nodeInfo, teamDid, inviteId, loca
|
|
|
270
446
|
type: 'mime:text/plain',
|
|
271
447
|
display: JSON.stringify({
|
|
272
448
|
type: 'svg',
|
|
273
|
-
content: createPassportSvg({
|
|
449
|
+
content: createPassportSvg({
|
|
450
|
+
issuer: issuerName,
|
|
451
|
+
title: passport.title,
|
|
452
|
+
issuerDid: issuerWallet.address,
|
|
453
|
+
ownerName: 'Your Name',
|
|
454
|
+
preferredColor: passportColor,
|
|
455
|
+
}),
|
|
274
456
|
}),
|
|
275
457
|
};
|
|
276
458
|
};
|
|
277
459
|
|
|
278
460
|
const handleInvitationResponse = async ({
|
|
461
|
+
req = {},
|
|
279
462
|
node,
|
|
280
463
|
nodeInfo,
|
|
281
464
|
teamDid,
|
|
@@ -286,6 +469,7 @@ const handleInvitationResponse = async ({
|
|
|
286
469
|
claims,
|
|
287
470
|
statusEndpointBaseUrl,
|
|
288
471
|
endpoint,
|
|
472
|
+
newNftOwner,
|
|
289
473
|
}) => {
|
|
290
474
|
if (!nodeInfo.nodeOwner) {
|
|
291
475
|
throw new Error(messages.notInitialized[locale]);
|
|
@@ -294,9 +478,48 @@ const handleInvitationResponse = async ({
|
|
|
294
478
|
const claim = claims.find((x) => x.type === 'signature');
|
|
295
479
|
verifySignature(claim, userDid, userPk, locale);
|
|
296
480
|
|
|
297
|
-
const
|
|
481
|
+
const inviteInfo = await node.getInvitation({ teamDid, inviteId });
|
|
482
|
+
if (!inviteInfo) {
|
|
483
|
+
throw new Error(`The invitation does not exist: ${inviteId}`);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (inviteInfo.role === 'owner' && userDid === nodeInfo.nodeOwner.did) {
|
|
487
|
+
throw new Error(messages.notAllowedTransferToSelf[locale]);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
await node.checkInvitation({ teamDid, inviteId });
|
|
491
|
+
|
|
492
|
+
if (inviteInfo.role === 'owner' && get(nodeInfo, 'ownerNft.holder')) {
|
|
493
|
+
// 这种情况下是 Transfer 有 Owner NFT 的 Blocklet Server
|
|
494
|
+
const client = new Client(nodeInfo.launcher.chainHost);
|
|
495
|
+
const ownerNftDid = get(nodeInfo, 'ownerNft.did');
|
|
496
|
+
|
|
497
|
+
const { state: assetState } = await client.getAssetState({ address: ownerNftDid });
|
|
498
|
+
if (assetState.owner !== newNftOwner) {
|
|
499
|
+
const hash = await client.transfer({
|
|
500
|
+
delegator: get(nodeInfo, 'ownerNft.holder'),
|
|
501
|
+
to: newNftOwner,
|
|
502
|
+
assets: [ownerNftDid],
|
|
503
|
+
wallet: getNodeWallet(nodeInfo.sk),
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
logger.info('transferred nft', { hash, nft: ownerNftDid });
|
|
507
|
+
await node.updateNftHolder(newNftOwner);
|
|
508
|
+
logger.info('updated owner nft holder', { holder: newNftOwner, nft: ownerNftDid });
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const {
|
|
513
|
+
name: issuerName,
|
|
514
|
+
wallet: issuerWallet,
|
|
515
|
+
type: issuerType,
|
|
516
|
+
passportColor,
|
|
517
|
+
dataDir,
|
|
518
|
+
} = await getApplicationInfo({ node, nodeInfo, teamDid });
|
|
519
|
+
|
|
520
|
+
const { remark } = inviteInfo;
|
|
298
521
|
|
|
299
|
-
const
|
|
522
|
+
const profile = claims.find((x) => x.type === 'profile');
|
|
300
523
|
|
|
301
524
|
const vcParams = {
|
|
302
525
|
issuerName,
|
|
@@ -314,10 +537,12 @@ const handleInvitationResponse = async ({
|
|
|
314
537
|
userDid,
|
|
315
538
|
teamDid,
|
|
316
539
|
}),
|
|
317
|
-
types: teamDid === nodeInfo.did ? [
|
|
540
|
+
types: teamDid === nodeInfo.did ? [VC_TYPE_NODE_PASSPORT] : [],
|
|
541
|
+
ownerProfile: profile,
|
|
542
|
+
preferredColor: passportColor,
|
|
318
543
|
};
|
|
319
544
|
|
|
320
|
-
if (issuerType ===
|
|
545
|
+
if (issuerType === TEAM_TYPE.NODE) {
|
|
321
546
|
vcParams.tag = nodeInfo.did;
|
|
322
547
|
}
|
|
323
548
|
|
|
@@ -326,27 +551,61 @@ const handleInvitationResponse = async ({
|
|
|
326
551
|
const role = getRoleFromLocalPassport(get(vc, 'credentialSubject.passport'));
|
|
327
552
|
const passport = createUserPassport(vc, { role });
|
|
328
553
|
|
|
329
|
-
const profile = claims.find((x) => x.type === 'profile');
|
|
330
|
-
|
|
331
554
|
const user = await getUser(node, teamDid, userDid);
|
|
332
555
|
|
|
556
|
+
if (role === 'owner') {
|
|
557
|
+
if (user && user.role === 'owner') {
|
|
558
|
+
throw new Error(messages.alreadyTransferred[locale](userDid));
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
await node.updateNodeOwner({ nodeOwner: { did: userDid, pk: userPk } });
|
|
562
|
+
|
|
563
|
+
const originalOwner = await getUser(node, teamDid, nodeInfo.nodeOwner.did);
|
|
564
|
+
const originalOwnerPassport = (originalOwner.passports || []).find((p) => p.role === 'owner');
|
|
565
|
+
if (originalOwnerPassport) {
|
|
566
|
+
await node.revokeUserPassport({ teamDid, userDid: nodeInfo.nodeOwner.did, passportId: originalOwnerPassport.id });
|
|
567
|
+
logger.info('passport revoked on server transfer claiming', {
|
|
568
|
+
teamDid,
|
|
569
|
+
userDid: nodeInfo.nodeOwner.did,
|
|
570
|
+
passportId: originalOwnerPassport.id,
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const avatar = await extractUserAvatar(get(profile, 'avatar'), {
|
|
576
|
+
dataDir,
|
|
577
|
+
});
|
|
578
|
+
|
|
333
579
|
if (user) {
|
|
334
|
-
await node.updateUser({
|
|
580
|
+
const doc = await node.updateUser({
|
|
335
581
|
teamDid,
|
|
336
582
|
user: {
|
|
337
583
|
...profile,
|
|
584
|
+
avatar,
|
|
338
585
|
did: userDid,
|
|
339
586
|
pk: userPk,
|
|
340
587
|
locale,
|
|
341
588
|
passports: upsertToPassports(user.passports || [], passport),
|
|
342
589
|
lastLoginAt: new Date().toISOString(),
|
|
590
|
+
lastLoginIp: get(req, 'headers[x-real-ip]') || '',
|
|
591
|
+
remark,
|
|
343
592
|
},
|
|
344
593
|
});
|
|
594
|
+
await node.createAuditLog(
|
|
595
|
+
{
|
|
596
|
+
action: 'updateUser',
|
|
597
|
+
args: { teamDid, userDid, passport, inviteId, reason: 'accepted invitation' },
|
|
598
|
+
context: formatContext(Object.assign(req, { user })),
|
|
599
|
+
result: doc,
|
|
600
|
+
},
|
|
601
|
+
node
|
|
602
|
+
);
|
|
345
603
|
} else {
|
|
346
|
-
await node.addUser({
|
|
604
|
+
const doc = await node.addUser({
|
|
347
605
|
teamDid,
|
|
348
606
|
user: {
|
|
349
607
|
...profile,
|
|
608
|
+
avatar,
|
|
350
609
|
did: userDid,
|
|
351
610
|
pk: userPk,
|
|
352
611
|
approved: true,
|
|
@@ -354,11 +613,30 @@ const handleInvitationResponse = async ({
|
|
|
354
613
|
passports: [passport],
|
|
355
614
|
firstLoginAt: new Date().toISOString(),
|
|
356
615
|
lastLoginAt: new Date().toISOString(),
|
|
616
|
+
lastLoginIp: get(req, 'headers[x-real-ip]') || '',
|
|
617
|
+
remark,
|
|
357
618
|
},
|
|
358
619
|
});
|
|
620
|
+
await node.createAuditLog(
|
|
621
|
+
{
|
|
622
|
+
action: 'addUser',
|
|
623
|
+
args: { teamDid, userDid, passport, inviteId, reason: 'accepted invitation' },
|
|
624
|
+
context: formatContext(Object.assign(req, { user: doc })),
|
|
625
|
+
result: doc,
|
|
626
|
+
},
|
|
627
|
+
node
|
|
628
|
+
);
|
|
359
629
|
}
|
|
360
630
|
|
|
361
|
-
logger.info('
|
|
631
|
+
logger.info('invite success', { userDid });
|
|
632
|
+
|
|
633
|
+
// await node.closeInvitation({ teamDid, inviteId, status: 'success', receiver: { did: userDid, role } });
|
|
634
|
+
await node.closeInvitation({
|
|
635
|
+
teamDid,
|
|
636
|
+
inviteId,
|
|
637
|
+
status: 'success',
|
|
638
|
+
receiver: { did: userDid, role, timeout: 1000 * 9999 },
|
|
639
|
+
});
|
|
362
640
|
|
|
363
641
|
return {
|
|
364
642
|
passport,
|
|
@@ -388,14 +666,25 @@ const createIssuePassportRequest = async ({ node, nodeInfo, teamDid, id, locale
|
|
|
388
666
|
if (!id) {
|
|
389
667
|
throw new Error('The issuance id does not exist');
|
|
390
668
|
}
|
|
391
|
-
|
|
392
669
|
const list = await node.getPassportIssuances({ teamDid });
|
|
393
670
|
const issuanceInfo = list.find((x) => x.id === id);
|
|
394
671
|
if (!issuanceInfo) {
|
|
395
672
|
throw new Error('The issuance does not exist or has been used');
|
|
396
673
|
}
|
|
397
674
|
|
|
398
|
-
const {
|
|
675
|
+
const {
|
|
676
|
+
name: issuerName,
|
|
677
|
+
wallet: issuerWallet,
|
|
678
|
+
passportColor,
|
|
679
|
+
owner: teamOwner,
|
|
680
|
+
dataDir,
|
|
681
|
+
} = await getApplicationInfo({ node, nodeInfo, teamDid });
|
|
682
|
+
|
|
683
|
+
if (issuanceInfo.name === ROLES.OWNER && !!teamOwner) {
|
|
684
|
+
throw new Error('Cannot receive owner passport because the owner already exists');
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const user = await getUser(node, teamDid, issuanceInfo.ownerDid);
|
|
399
688
|
|
|
400
689
|
const passport = await createPassport({
|
|
401
690
|
name: issuanceInfo.name,
|
|
@@ -410,7 +699,14 @@ const createIssuePassportRequest = async ({ node, nodeInfo, teamDid, id, locale
|
|
|
410
699
|
type: 'mime:text/plain',
|
|
411
700
|
display: JSON.stringify({
|
|
412
701
|
type: 'svg',
|
|
413
|
-
content: createPassportSvg({
|
|
702
|
+
content: createPassportSvg({
|
|
703
|
+
issuer: issuerName,
|
|
704
|
+
title: passport.title,
|
|
705
|
+
issuerDid: issuerWallet.address,
|
|
706
|
+
ownerName: get(user, 'fullName', 'Your Name'),
|
|
707
|
+
ownerAvatarUrl: await parseUserAvatar(get(user, 'avatar', ''), { dataDir }),
|
|
708
|
+
preferredColor: passportColor,
|
|
709
|
+
}),
|
|
414
710
|
}),
|
|
415
711
|
};
|
|
416
712
|
};
|
|
@@ -420,6 +716,7 @@ const createIssuePassportRequest = async ({ node, nodeInfo, teamDid, id, locale
|
|
|
420
716
|
* @param {string} statusEndpointBaseUrl passport status endpoint base url
|
|
421
717
|
*/
|
|
422
718
|
const handleIssuePassportResponse = async ({
|
|
719
|
+
req = {},
|
|
423
720
|
node,
|
|
424
721
|
nodeInfo,
|
|
425
722
|
teamDid,
|
|
@@ -429,6 +726,7 @@ const handleIssuePassportResponse = async ({
|
|
|
429
726
|
locale = 'en',
|
|
430
727
|
claims,
|
|
431
728
|
statusEndpointBaseUrl,
|
|
729
|
+
updateSession,
|
|
432
730
|
endpoint,
|
|
433
731
|
}) => {
|
|
434
732
|
// verify signature
|
|
@@ -445,17 +743,32 @@ const handleIssuePassportResponse = async ({
|
|
|
445
743
|
);
|
|
446
744
|
}
|
|
447
745
|
|
|
448
|
-
const {
|
|
746
|
+
const {
|
|
747
|
+
name: issuerName,
|
|
748
|
+
wallet: issuerWallet,
|
|
749
|
+
type: issuerType,
|
|
750
|
+
passportColor,
|
|
751
|
+
owner: teamOwner,
|
|
752
|
+
dataDir,
|
|
753
|
+
} = await getApplicationInfo({ node, nodeInfo, teamDid });
|
|
449
754
|
|
|
450
755
|
// get issuanceInfo from session
|
|
451
756
|
const list = await node.getPassportIssuances({ teamDid });
|
|
452
757
|
const issuanceInfo = list.find((x) => x.id === id);
|
|
453
758
|
const { name, ownerDid } = issuanceInfo;
|
|
454
759
|
|
|
760
|
+
if (name === ROLES.OWNER && !!teamOwner) {
|
|
761
|
+
throw new Error('Cannot receive Owner Passport because the owner already exists');
|
|
762
|
+
}
|
|
763
|
+
|
|
455
764
|
if (ownerDid !== userDid) {
|
|
456
765
|
throw new Error(messages.notOwner[locale]);
|
|
457
766
|
}
|
|
458
767
|
|
|
768
|
+
if (user) {
|
|
769
|
+
user.avatar = await parseUserAvatar(user.avatar, { dataDir });
|
|
770
|
+
}
|
|
771
|
+
|
|
459
772
|
const vcParams = {
|
|
460
773
|
issuerName,
|
|
461
774
|
issuerWallet,
|
|
@@ -472,10 +785,12 @@ const handleIssuePassportResponse = async ({
|
|
|
472
785
|
userDid,
|
|
473
786
|
teamDid,
|
|
474
787
|
}),
|
|
475
|
-
types: teamDid === nodeInfo.did ? [
|
|
788
|
+
types: teamDid === nodeInfo.did ? [VC_TYPE_NODE_PASSPORT] : [],
|
|
789
|
+
ownerProfile: user,
|
|
790
|
+
preferredColor: passportColor,
|
|
476
791
|
};
|
|
477
792
|
|
|
478
|
-
if (issuerType ===
|
|
793
|
+
if (issuerType === TEAM_TYPE.NODE) {
|
|
479
794
|
vcParams.tag = nodeInfo.did;
|
|
480
795
|
}
|
|
481
796
|
|
|
@@ -496,7 +811,23 @@ const handleIssuePassportResponse = async ({
|
|
|
496
811
|
}
|
|
497
812
|
|
|
498
813
|
// delete session
|
|
499
|
-
await node.processPassportIssuance({ teamDid, sessionId: id });
|
|
814
|
+
const result = await node.processPassportIssuance({ teamDid, sessionId: id });
|
|
815
|
+
await node.createAuditLog(
|
|
816
|
+
{
|
|
817
|
+
action: 'processPassportIssuance',
|
|
818
|
+
args: { teamDid, userDid, ...result, sessionId: id, reason: 'claimed passport' },
|
|
819
|
+
context: formatContext(Object.assign(req, { user })),
|
|
820
|
+
result,
|
|
821
|
+
},
|
|
822
|
+
node
|
|
823
|
+
);
|
|
824
|
+
|
|
825
|
+
if (name === ROLES.OWNER && issuerType === TEAM_TYPE.BLOCKLET) {
|
|
826
|
+
logger.info('Bind owner for blocklet', { teamDid, userDid });
|
|
827
|
+
await node.setBlockletInitialized({ did: teamDid, owner: { did: userDid, pk: userPk } });
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
await updateSession({ passportId: vc.id });
|
|
500
831
|
|
|
501
832
|
return {
|
|
502
833
|
disposition: 'attachment',
|
|
@@ -505,10 +836,10 @@ const handleIssuePassportResponse = async ({
|
|
|
505
836
|
};
|
|
506
837
|
};
|
|
507
838
|
|
|
508
|
-
const getVCFromClaims = async ({ claims, challenge, trustedIssuers, vcTypes, locale = 'en' }) => {
|
|
509
|
-
const credential = claims
|
|
510
|
-
(
|
|
511
|
-
|
|
839
|
+
const getVCFromClaims = async ({ claims, challenge, trustedIssuers, vcTypes, locale = 'en', vcId }) => {
|
|
840
|
+
const credential = claims
|
|
841
|
+
.filter(Boolean) // FIXES: https://github.com/ArcBlock/did-connect/issues/74
|
|
842
|
+
.find((x) => x.type === 'verifiableCredential' && vcTypes.some((item) => x.item.includes(item)));
|
|
512
843
|
|
|
513
844
|
if (!credential || !credential.presentation) {
|
|
514
845
|
return {};
|
|
@@ -534,6 +865,11 @@ const getVCFromClaims = async ({ claims, challenge, trustedIssuers, vcTypes, loc
|
|
|
534
865
|
: [presentation.verifiableCredential];
|
|
535
866
|
const vc = JSON.parse(credentials[0]);
|
|
536
867
|
|
|
868
|
+
// verify vc id
|
|
869
|
+
if (vcId && vc.id !== vcId) {
|
|
870
|
+
throw new Error(messages.invalidCredentialId[locale](vcId));
|
|
871
|
+
}
|
|
872
|
+
|
|
537
873
|
// verify vc type
|
|
538
874
|
const types = [].concat(vc.type);
|
|
539
875
|
if (!types.some((x) => vcTypes.includes(x))) {
|
|
@@ -546,8 +882,8 @@ const getVCFromClaims = async ({ claims, challenge, trustedIssuers, vcTypes, loc
|
|
|
546
882
|
};
|
|
547
883
|
};
|
|
548
884
|
|
|
549
|
-
const checkWalletVersion = ({
|
|
550
|
-
const { os, version } =
|
|
885
|
+
const checkWalletVersion = ({ didwallet, locale = 'en' }) => {
|
|
886
|
+
const { os, version } = didwallet;
|
|
551
887
|
|
|
552
888
|
const defaultExpected = '3.0.0';
|
|
553
889
|
|
|
@@ -566,11 +902,21 @@ const checkWalletVersion = ({ abtwallet, locale = 'en' }) => {
|
|
|
566
902
|
|
|
567
903
|
const getPassportStatus = async ({ node, teamDid, userDid, vcId, locale = 'en' }) => {
|
|
568
904
|
const nodeInfo = await node.getNodeInfo();
|
|
569
|
-
const {
|
|
905
|
+
const {
|
|
906
|
+
wallet: issuerWallet,
|
|
907
|
+
name: issuerName,
|
|
908
|
+
type: issuerType,
|
|
909
|
+
} = await getApplicationInfo({ node, nodeInfo, teamDid });
|
|
570
910
|
|
|
571
911
|
const actionLabel = {
|
|
572
|
-
|
|
573
|
-
|
|
912
|
+
open: {
|
|
913
|
+
zh: `${issuerType === TEAM_TYPE.NODE ? '管理节点' : '查看 Blocklet'}`,
|
|
914
|
+
en: `${issuerType === TEAM_TYPE.NODE ? 'Manage Node' : 'View Blocklet'}`,
|
|
915
|
+
},
|
|
916
|
+
manage: {
|
|
917
|
+
zh: '管理 Blocklet',
|
|
918
|
+
en: 'Manage Blocklet',
|
|
919
|
+
},
|
|
574
920
|
};
|
|
575
921
|
|
|
576
922
|
const user = await node.getUser({ teamDid, user: { did: userDid } });
|
|
@@ -598,18 +944,33 @@ const getPassportStatus = async ({ node, teamDid, userDid, vcId, locale = 'en' }
|
|
|
598
944
|
|
|
599
945
|
const actionList = [];
|
|
600
946
|
if (passport.endpoint) {
|
|
947
|
+
const claims = [];
|
|
948
|
+
|
|
949
|
+
// open blocklet
|
|
950
|
+
claims.push({
|
|
951
|
+
id: passport.endpoint,
|
|
952
|
+
type: 'navigate',
|
|
953
|
+
name: issuerType === TEAM_TYPE.NODE ? 'open-node' : 'open-blocklet',
|
|
954
|
+
scope: 'public',
|
|
955
|
+
label: actionLabel.open[locale],
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
// manage blocklet
|
|
959
|
+
const { name } = passport;
|
|
960
|
+
if ([ROLES.OWNER, ROLES.ADMIN].includes(name) && issuerType === TEAM_TYPE.BLOCKLET) {
|
|
961
|
+
claims.push({
|
|
962
|
+
id: joinUrl(passport.endpoint, WELLKNOWN_BLOCKLET_ADMIN_PATH),
|
|
963
|
+
type: 'navigate',
|
|
964
|
+
name: 'open-blocklet-admin',
|
|
965
|
+
scope: 'public',
|
|
966
|
+
label: actionLabel.manage[locale],
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
|
|
601
970
|
actionList.push(
|
|
602
971
|
...createCredentialList({
|
|
603
972
|
issuer,
|
|
604
|
-
claims
|
|
605
|
-
{
|
|
606
|
-
id: passport.endpoint,
|
|
607
|
-
type: 'navigate',
|
|
608
|
-
name: issuerType === 'node' ? 'open-node' : 'open-blocklet',
|
|
609
|
-
scope: 'public',
|
|
610
|
-
label: actionLabel[locale],
|
|
611
|
-
},
|
|
612
|
-
],
|
|
973
|
+
claims,
|
|
613
974
|
})
|
|
614
975
|
);
|
|
615
976
|
}
|
|
@@ -682,7 +1043,7 @@ const setUserInfoHeaders = (req) => {
|
|
|
682
1043
|
|
|
683
1044
|
module.exports = {
|
|
684
1045
|
getUser,
|
|
685
|
-
|
|
1046
|
+
getApplicationInfo,
|
|
686
1047
|
createAuthToken,
|
|
687
1048
|
createAuthTokenByOwnershipNFT,
|
|
688
1049
|
beforeInvitationRequest,
|
|
@@ -698,4 +1059,6 @@ module.exports = {
|
|
|
698
1059
|
getPassportStatus,
|
|
699
1060
|
validatePassportStatus,
|
|
700
1061
|
setUserInfoHeaders,
|
|
1062
|
+
createBlockletControllerAuthToken,
|
|
1063
|
+
TEAM_TYPE,
|
|
701
1064
|
};
|