@abtnode/blocklet-services 1.8.3 → 1.8.4
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/api/index.js +3 -1
- package/api/libs/connect/session.js +456 -0
- package/api/libs/connect/v2.js +3 -0
- package/api/routes/{connect-relay.js → connect/relay.js} +3 -3
- package/api/routes/connect/session.js +120 -0
- package/api/services/auth/connect/login.js +17 -292
- package/api/services/auth/connect/switch-passport.js +18 -103
- package/api/services/auth/connect/switch-profile.js +17 -63
- package/api/services/notification/channel/app-channel.js +4 -1
- package/build/asset-manifest.json +7 -7
- package/build/index.html +1 -1
- package/build/static/js/{12.a879bfa6.chunk.js → 12.0e874475.chunk.js} +2 -2
- package/build/static/js/12.0e874475.chunk.js.map +1 -0
- package/build/static/js/{7.96fdf7e5.chunk.js → 7.b2428475.chunk.js} +2 -2
- package/build/static/js/7.b2428475.chunk.js.map +1 -0
- package/build/static/js/{runtime-main.7fadd252.js → runtime-main.2227285f.js} +2 -2
- package/build/static/js/{runtime-main.7fadd252.js.map → runtime-main.2227285f.js.map} +1 -1
- package/package.json +21 -21
- package/build/static/js/12.a879bfa6.chunk.js.map +0 -1
- package/build/static/js/7.96fdf7e5.chunk.js.map +0 -1
package/api/index.js
CHANGED
|
@@ -30,7 +30,8 @@ const { init: initAuth } = require('./services/auth');
|
|
|
30
30
|
const StaticService = require('./services/static');
|
|
31
31
|
const createEnvRoutes = require('./routes/env');
|
|
32
32
|
const createBlockletRoutes = require('./routes/blocklet');
|
|
33
|
-
const createConnectRelayRoutes = require('./routes/connect
|
|
33
|
+
const createConnectRelayRoutes = require('./routes/connect/relay');
|
|
34
|
+
const createConnectSessionRoutes = require('./routes/connect/session');
|
|
34
35
|
const createDnsResolver = require('./routes/dns-resolver');
|
|
35
36
|
const checkRunning = require('./middlewares/check-running');
|
|
36
37
|
const checkAdminPermission = require('./middlewares/check-admin-permission');
|
|
@@ -199,6 +200,7 @@ module.exports = function createServer(node, serverOptions = {}) {
|
|
|
199
200
|
// API: auth
|
|
200
201
|
createEnvRoutes.init(server, node, options);
|
|
201
202
|
createBlockletRoutes.init(server, node, options);
|
|
203
|
+
createConnectSessionRoutes.init(server, node, options);
|
|
202
204
|
createConnectRelayRoutes.init(server, node, options, wsRouter);
|
|
203
205
|
authRoutes.attachDidAuthHandlers(server);
|
|
204
206
|
authRoutes.createPassportRoutes.init(server, node, options);
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
// Holds shared logic for session-manager v1 and v2
|
|
2
|
+
const get = require('lodash/get');
|
|
3
|
+
const joinUrl = require('url-join');
|
|
4
|
+
const formatContext = require('@abtnode/util/lib/format-context');
|
|
5
|
+
const { extractUserAvatar } = require('@abtnode/util/lib/user-avatar');
|
|
6
|
+
const {
|
|
7
|
+
messages,
|
|
8
|
+
getUser,
|
|
9
|
+
getVCFromClaims,
|
|
10
|
+
validatePassportStatus,
|
|
11
|
+
getPassportStatusEndpoint,
|
|
12
|
+
} = require('@abtnode/auth/lib/auth');
|
|
13
|
+
const {
|
|
14
|
+
NODE_SERVICES,
|
|
15
|
+
ROLES,
|
|
16
|
+
WELLKNOWN_SERVICE_PATH_PREFIX,
|
|
17
|
+
VC_TYPE_GENERAL_PASSPORT,
|
|
18
|
+
VC_TYPE_NODE_PASSPORT,
|
|
19
|
+
WHO_CAN_ACCESS,
|
|
20
|
+
} = require('@abtnode/constant');
|
|
21
|
+
const {
|
|
22
|
+
validatePassport,
|
|
23
|
+
isUserPassportRevoked,
|
|
24
|
+
getRoleFromLocalPassport,
|
|
25
|
+
getRoleFromExternalPassport,
|
|
26
|
+
createUserPassport,
|
|
27
|
+
createPassportVC,
|
|
28
|
+
createPassport,
|
|
29
|
+
upsertToPassports,
|
|
30
|
+
} = require('@abtnode/auth/lib/passport');
|
|
31
|
+
|
|
32
|
+
const logger = require('@abtnode/logger')(require('../../../package.json').name);
|
|
33
|
+
|
|
34
|
+
const vcTypes = [VC_TYPE_GENERAL_PASSPORT, VC_TYPE_NODE_PASSPORT];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @returns {Array} config
|
|
38
|
+
* @returns {Boolean} config[0] is invited user only
|
|
39
|
+
* @returns {String} config[1] default role
|
|
40
|
+
* @returns {Boolean} config[2] issue passport
|
|
41
|
+
*/
|
|
42
|
+
const isInvitedUserOnly = async (config, node, teamDid) => {
|
|
43
|
+
const count = await node.getUsersCount({ teamDid });
|
|
44
|
+
|
|
45
|
+
// issue owner passport for first login user
|
|
46
|
+
if (count === 0) {
|
|
47
|
+
return [false, ROLES.OWNER, true];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if ([WHO_CAN_ACCESS.OWNER, WHO_CAN_ACCESS.INVITED].includes(config.whoCanAccess)) {
|
|
51
|
+
return [true];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if ([WHO_CAN_ACCESS.ALL].includes(config.whoCanAccess)) {
|
|
55
|
+
return [false, ROLES.GUEST];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return [false, ROLES.GUEST];
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
login: {
|
|
63
|
+
onConnect: async ({ node, request, userDid, locale, passportId = '' }) => {
|
|
64
|
+
const blocklet = await request.getBlocklet();
|
|
65
|
+
const config = await request.getServiceConfig(NODE_SERVICES.AUTH);
|
|
66
|
+
const { wallet, did: teamDid } = await request.getBlockletInfo();
|
|
67
|
+
|
|
68
|
+
const profileFields = get(config, 'profileFields');
|
|
69
|
+
const [invitedUserOnly] = await isInvitedUserOnly(config, node, teamDid);
|
|
70
|
+
const trustedPassports = (blocklet.trustedPassports || []).map((x) => x.issuerDid);
|
|
71
|
+
const trustedIssuers = [wallet.address, ...trustedPassports].filter(Boolean);
|
|
72
|
+
|
|
73
|
+
const claims = {
|
|
74
|
+
profile: {
|
|
75
|
+
type: 'profile',
|
|
76
|
+
description: messages.description[locale],
|
|
77
|
+
items: profileFields || ['fullName', 'avatar'],
|
|
78
|
+
},
|
|
79
|
+
verifiableCredential: {
|
|
80
|
+
type: 'verifiableCredential',
|
|
81
|
+
description: messages.requestPassport[locale],
|
|
82
|
+
item: vcTypes,
|
|
83
|
+
trustedIssuers,
|
|
84
|
+
optional: !invitedUserOnly,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
if (passportId) {
|
|
88
|
+
claims.verifiableCredential.target = passportId;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const user = await node.getUser({ teamDid: blocklet.meta.did, user: { did: userDid } });
|
|
92
|
+
if (user) {
|
|
93
|
+
delete claims.profile;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return claims;
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
onApprove: async ({ node, request, locale, challenge, userDid, userPk, claims, baseUrl, createSessionToken }) => {
|
|
100
|
+
const blocklet = await request.getBlocklet();
|
|
101
|
+
const { wallet, name, passportColor, did: teamDid } = await request.getBlockletInfo();
|
|
102
|
+
const teamAppDid = wallet.address;
|
|
103
|
+
|
|
104
|
+
// check user approved
|
|
105
|
+
const user = await getUser(node, teamDid, userDid);
|
|
106
|
+
if (user && !user.approved) {
|
|
107
|
+
throw new Error(messages.notAllowed[locale]);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Get passport
|
|
111
|
+
const trustedPassports = (blocklet.trustedPassports || []).map((x) => x.issuerDid);
|
|
112
|
+
const trustedIssuers = [teamAppDid, ...trustedPassports].filter(Boolean);
|
|
113
|
+
const { vc: inputVC } = await getVCFromClaims({
|
|
114
|
+
claims,
|
|
115
|
+
challenge,
|
|
116
|
+
trustedIssuers,
|
|
117
|
+
vcTypes,
|
|
118
|
+
locale,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
let vc = inputVC;
|
|
122
|
+
|
|
123
|
+
const config = (await request.getServiceConfig(NODE_SERVICES.AUTH)) || {};
|
|
124
|
+
const [invitedUserOnly, defaultRole, issuePassport] = await isInvitedUserOnly(config, node, teamDid);
|
|
125
|
+
|
|
126
|
+
if (invitedUserOnly && !vc) {
|
|
127
|
+
throw new Error(messages.missingCredentialClaim[locale]);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// issue passport for the first login user in a invite-only team
|
|
131
|
+
if (issuePassport) {
|
|
132
|
+
logger.info('issue passport to user at the login workflow', { role: defaultRole });
|
|
133
|
+
const profile = claims.find((x) => x.type === 'profile');
|
|
134
|
+
vc = createPassportVC({
|
|
135
|
+
issuerName: name,
|
|
136
|
+
issuerWallet: wallet,
|
|
137
|
+
ownerDid: userDid,
|
|
138
|
+
passport: await createPassport({
|
|
139
|
+
name: defaultRole,
|
|
140
|
+
node,
|
|
141
|
+
teamDid,
|
|
142
|
+
locale,
|
|
143
|
+
endpoint: baseUrl,
|
|
144
|
+
}),
|
|
145
|
+
endpoint: getPassportStatusEndpoint({
|
|
146
|
+
baseUrl: joinUrl(baseUrl, WELLKNOWN_SERVICE_PATH_PREFIX),
|
|
147
|
+
userDid,
|
|
148
|
+
teamDid,
|
|
149
|
+
}),
|
|
150
|
+
ownerProfile: profile,
|
|
151
|
+
preferredColor: passportColor,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Get user passport from vc
|
|
156
|
+
let passport = vc ? createUserPassport(vc) : null;
|
|
157
|
+
if (user && passport && isUserPassportRevoked(user, passport)) {
|
|
158
|
+
throw new Error(messages.passportRevoked[locale](name));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Get role
|
|
162
|
+
let role = ROLES.GUEST;
|
|
163
|
+
if (vc) {
|
|
164
|
+
await validatePassport(get(vc, 'credentialSubject.passport'));
|
|
165
|
+
const issuerId = get(vc, 'issuer.id');
|
|
166
|
+
if (issuerId === teamAppDid) {
|
|
167
|
+
role = getRoleFromLocalPassport(get(vc, 'credentialSubject.passport'));
|
|
168
|
+
} else {
|
|
169
|
+
// map external passport to local role
|
|
170
|
+
const { mappings = [] } = (blocklet.trustedPassports || []).find((x) => x.issuerDid === issuerId) || {};
|
|
171
|
+
role = await getRoleFromExternalPassport({
|
|
172
|
+
passport: get(vc, 'credentialSubject.passport'),
|
|
173
|
+
node,
|
|
174
|
+
teamDid,
|
|
175
|
+
locale,
|
|
176
|
+
mappings,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// check status of external passport if passport has an endpoint
|
|
180
|
+
const endpoint = get(vc, 'credentialStatus.id');
|
|
181
|
+
if (endpoint) {
|
|
182
|
+
await validatePassportStatus({ vcId: vc.id, endpoint, locale });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (config.whoCanAccess === WHO_CAN_ACCESS.OWNER && role !== ROLES.OWNER) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
{
|
|
190
|
+
zh: '你不是该应用的所有者',
|
|
191
|
+
en: 'You are not the owner of this application',
|
|
192
|
+
}[locale]
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Recreate passport with correct role
|
|
197
|
+
passport = vc ? createUserPassport(vc, { role }) : null;
|
|
198
|
+
|
|
199
|
+
// Update profile
|
|
200
|
+
const passportForLog = passport || { name: 'Guest', role: 'guest' };
|
|
201
|
+
if (user) {
|
|
202
|
+
// Update user
|
|
203
|
+
const doc = await node.updateUser({
|
|
204
|
+
teamDid,
|
|
205
|
+
user: {
|
|
206
|
+
did: userDid,
|
|
207
|
+
pk: userPk,
|
|
208
|
+
locale,
|
|
209
|
+
passports: upsertToPassports(user.passports || [], passport).filter(Boolean),
|
|
210
|
+
lastLoginAt: new Date().toISOString(),
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
await node.createAuditLog(
|
|
214
|
+
{
|
|
215
|
+
action: 'login',
|
|
216
|
+
args: { teamDid, userDid, passport: passportForLog },
|
|
217
|
+
context: formatContext(Object.assign(request, { user: doc })),
|
|
218
|
+
result: doc,
|
|
219
|
+
},
|
|
220
|
+
node
|
|
221
|
+
);
|
|
222
|
+
} else {
|
|
223
|
+
// Create user
|
|
224
|
+
const profile = claims.find((x) => x.type === 'profile');
|
|
225
|
+
|
|
226
|
+
const doc = await node.addUser({
|
|
227
|
+
teamDid,
|
|
228
|
+
user: {
|
|
229
|
+
...profile,
|
|
230
|
+
avatar: await extractUserAvatar(get(profile, 'avatar'), {
|
|
231
|
+
dataDir: blocklet.env.dataDir,
|
|
232
|
+
}),
|
|
233
|
+
did: userDid,
|
|
234
|
+
pk: userPk,
|
|
235
|
+
approved: true,
|
|
236
|
+
locale,
|
|
237
|
+
passports: [passport].filter(Boolean),
|
|
238
|
+
firstLoginAt: new Date().toISOString(),
|
|
239
|
+
lastLoginAt: new Date().toISOString(),
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
await node.createAuditLog(
|
|
243
|
+
{
|
|
244
|
+
action: 'addUser',
|
|
245
|
+
args: { teamDid, userDid, reason: `first login as ${passportForLog.role}` },
|
|
246
|
+
context: formatContext(Object.assign(request, { user: doc })),
|
|
247
|
+
result: doc,
|
|
248
|
+
},
|
|
249
|
+
node
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Generate new session token that client can save to localStorage
|
|
254
|
+
const sessionToken = await createSessionToken(userDid, { passport, role });
|
|
255
|
+
logger.info('login.success', { userDid, role });
|
|
256
|
+
|
|
257
|
+
if (
|
|
258
|
+
// if user provides owner passport AND app does not have owner, set this user to owner
|
|
259
|
+
(inputVC && role === ROLES.OWNER && !blocklet.settings?.owner) ||
|
|
260
|
+
// if the user will receive a owner passport AND app does not have owner, set this user to owner
|
|
261
|
+
(issuePassport && defaultRole === ROLES.OWNER && !blocklet.settings?.owner)
|
|
262
|
+
) {
|
|
263
|
+
logger.info('Bind owner for blocklet', { teamDid, userDid });
|
|
264
|
+
await node.setBlockletInitialized({ did: teamDid, owner: { did: userDid, pk: userPk } });
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// issue passport for the first login user in a invite-only team
|
|
268
|
+
if (issuePassport) {
|
|
269
|
+
return {
|
|
270
|
+
disposition: 'attachment',
|
|
271
|
+
type: 'VerifiableCredential',
|
|
272
|
+
data: vc,
|
|
273
|
+
sessionToken,
|
|
274
|
+
nextWorkflowData: {
|
|
275
|
+
userDid,
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
sessionToken,
|
|
282
|
+
nextWorkflowData: {
|
|
283
|
+
userDid,
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
switchProfile: {
|
|
290
|
+
onConnect: async ({ node, request, locale, userDid, previousUserDid }) => {
|
|
291
|
+
if (userDid && previousUserDid && userDid !== previousUserDid) {
|
|
292
|
+
throw new Error(messages.userMismatch[locale]);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const config = await request.getServiceConfig(NODE_SERVICES.AUTH);
|
|
296
|
+
if (get(config, 'allowSwitchProfile', true) === false) {
|
|
297
|
+
throw new Error(messages.actionForbidden[locale]);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const { did: teamDid } = await request.getBlockletInfo();
|
|
301
|
+
const user = await getUser(node, teamDid, userDid);
|
|
302
|
+
|
|
303
|
+
if (!user) {
|
|
304
|
+
throw new Error(messages.userNotFound[locale]);
|
|
305
|
+
}
|
|
306
|
+
if (!user.approved) {
|
|
307
|
+
throw new Error(messages.notAuthorized[locale]);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
profile: {
|
|
312
|
+
type: 'profile',
|
|
313
|
+
description: messages.description[locale],
|
|
314
|
+
items: get(config, 'profileFields') || ['fullName', 'avatar'],
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
},
|
|
318
|
+
onApprove: async ({ node, request, locale, profile, userDid }) => {
|
|
319
|
+
const blocklet = await request.getBlocklet();
|
|
320
|
+
const teamDid = blocklet.meta.did;
|
|
321
|
+
|
|
322
|
+
// check user approved
|
|
323
|
+
const user = await getUser(node, teamDid, userDid);
|
|
324
|
+
if (!user) {
|
|
325
|
+
throw new Error(messages.userNotFound[locale]);
|
|
326
|
+
}
|
|
327
|
+
if (!user.approved) {
|
|
328
|
+
throw new Error(messages.notAuthorized[locale]);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Update user
|
|
332
|
+
const doc = await node.updateUser({
|
|
333
|
+
teamDid,
|
|
334
|
+
user: {
|
|
335
|
+
...user,
|
|
336
|
+
...profile,
|
|
337
|
+
avatar: await extractUserAvatar(get(profile, 'avatar'), {
|
|
338
|
+
dataDir: blocklet.env.dataDir,
|
|
339
|
+
}),
|
|
340
|
+
locale,
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
await node.createAuditLog(
|
|
344
|
+
{
|
|
345
|
+
action: 'switchProfile',
|
|
346
|
+
args: { teamDid, userDid, profile },
|
|
347
|
+
context: formatContext(Object.assign(request, { user })),
|
|
348
|
+
result: doc,
|
|
349
|
+
},
|
|
350
|
+
node
|
|
351
|
+
);
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
|
|
355
|
+
switchPassport: {
|
|
356
|
+
onConnect: async ({ node, request, locale, userDid, previousUserDid }) => {
|
|
357
|
+
if (userDid && previousUserDid && userDid !== previousUserDid) {
|
|
358
|
+
throw new Error(messages.userMismatch[locale]);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const { wallet, did: teamDid } = await request.getBlockletInfo();
|
|
362
|
+
const user = await getUser(node, teamDid, userDid);
|
|
363
|
+
|
|
364
|
+
if (!user) {
|
|
365
|
+
throw new Error(messages.userNotFound[locale]);
|
|
366
|
+
}
|
|
367
|
+
if (!user.approved) {
|
|
368
|
+
throw new Error(messages.notAuthorized[locale]);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const blocklet = await request.getBlocklet();
|
|
372
|
+
const trustedPassports = (blocklet.trustedPassports || []).map((x) => x.issuerDid);
|
|
373
|
+
const trustedIssuers = [wallet.address, ...trustedPassports].filter(Boolean);
|
|
374
|
+
|
|
375
|
+
return {
|
|
376
|
+
verifiableCredential: {
|
|
377
|
+
type: 'verifiableCredential',
|
|
378
|
+
description: messages.requestPassport[locale],
|
|
379
|
+
item: vcTypes,
|
|
380
|
+
trustedIssuers,
|
|
381
|
+
optional: false,
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
},
|
|
385
|
+
onApprove: async ({ node, request, locale, challenge, verifiableCredential, userDid, createSessionToken }) => {
|
|
386
|
+
const blocklet = await request.getBlocklet();
|
|
387
|
+
const { wallet, name, did: teamDid } = await request.getBlockletInfo();
|
|
388
|
+
|
|
389
|
+
// Validate user
|
|
390
|
+
const user = await getUser(node, teamDid, userDid);
|
|
391
|
+
if (!user) {
|
|
392
|
+
throw new Error(messages.userNotFound[locale]);
|
|
393
|
+
}
|
|
394
|
+
if (!user.approved) {
|
|
395
|
+
throw new Error(messages.notAuthorized[locale]);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Get passport
|
|
399
|
+
const trustedPassports = (blocklet.trustedPassports || []).map((x) => x.issuerDid);
|
|
400
|
+
const trustedIssuers = [wallet.address, ...trustedPassports].filter(Boolean);
|
|
401
|
+
const { vc } = await getVCFromClaims({
|
|
402
|
+
claims: [verifiableCredential],
|
|
403
|
+
challenge,
|
|
404
|
+
trustedIssuers,
|
|
405
|
+
vcTypes,
|
|
406
|
+
locale,
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// Get user passport from vc
|
|
410
|
+
let passport = createUserPassport(vc);
|
|
411
|
+
if (passport && isUserPassportRevoked(user, passport)) {
|
|
412
|
+
throw new Error(messages.passportRevoked[locale](name));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Get role
|
|
416
|
+
let role = ROLES.GUEST;
|
|
417
|
+
await validatePassport(get(vc, 'credentialSubject.passport'));
|
|
418
|
+
const issuerId = get(vc, 'issuer.id');
|
|
419
|
+
if (issuerId === wallet.address) {
|
|
420
|
+
role = getRoleFromLocalPassport(get(vc, 'credentialSubject.passport'));
|
|
421
|
+
} else {
|
|
422
|
+
// map external passport to local role
|
|
423
|
+
const { mappings = [] } = (blocklet.trustedPassports || []).find((x) => x.issuerDid === issuerId) || {};
|
|
424
|
+
role = await getRoleFromExternalPassport({
|
|
425
|
+
passport: get(vc, 'credentialSubject.passport'),
|
|
426
|
+
node,
|
|
427
|
+
teamDid,
|
|
428
|
+
locale,
|
|
429
|
+
mappings,
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// check status of external passport if passport has an endpoint
|
|
433
|
+
const endpoint = get(vc, 'credentialStatus.id');
|
|
434
|
+
if (endpoint) {
|
|
435
|
+
await validatePassportStatus({ vcId: vc.id, endpoint, locale });
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Recreate passport with correct role
|
|
440
|
+
passport = createUserPassport(vc, { role });
|
|
441
|
+
await node.createAuditLog(
|
|
442
|
+
{
|
|
443
|
+
action: 'switchPassport',
|
|
444
|
+
args: { teamDid, userDid, passport },
|
|
445
|
+
context: formatContext(Object.assign(request, { user })),
|
|
446
|
+
result: {},
|
|
447
|
+
},
|
|
448
|
+
node
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
// Generate new session token that client can save to localStorage
|
|
452
|
+
const sessionToken = await createSessionToken(userDid, { passport, role });
|
|
453
|
+
return sessionToken;
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
};
|
package/api/libs/connect/v2.js
CHANGED
|
@@ -5,6 +5,8 @@ const createHandlers = require('@blocklet/sdk/lib/connect/handler');
|
|
|
5
5
|
const { sendToUser } = require('@blocklet/sdk/lib/util/send-notification');
|
|
6
6
|
const { WELLKNOWN_SERVICE_PATH_PREFIX } = require('@abtnode/constant');
|
|
7
7
|
|
|
8
|
+
const logger = require('@abtnode/logger')(require('../../../package.json').name);
|
|
9
|
+
|
|
8
10
|
const { appInfo, chainInfo } = require('./shared');
|
|
9
11
|
|
|
10
12
|
module.exports = (node, opts) => {
|
|
@@ -18,6 +20,7 @@ module.exports = (node, opts) => {
|
|
|
18
20
|
});
|
|
19
21
|
|
|
20
22
|
const handlers = createHandlers({
|
|
23
|
+
logger,
|
|
21
24
|
authenticator,
|
|
22
25
|
storage: new NedbStorage({
|
|
23
26
|
dbPath: path.join(opts.dataDir, 'sessions.db'),
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
const { WELLKNOWN_SERVICE_PATH_PREFIX } = require('@abtnode/constant');
|
|
2
2
|
const { attachHandlers } = require('@did-connect/relay-adapter-express');
|
|
3
|
-
const initConnectRelay = require('
|
|
3
|
+
const initConnectRelay = require('../../libs/connect/v2');
|
|
4
4
|
|
|
5
5
|
module.exports = {
|
|
6
|
-
init(
|
|
6
|
+
init(router, node, opts, wsRouter) {
|
|
7
7
|
const { handlers } = initConnectRelay(node, opts);
|
|
8
8
|
const prefix = `${WELLKNOWN_SERVICE_PATH_PREFIX}/api/connect/relay`;
|
|
9
9
|
|
|
10
10
|
// attach http handlers
|
|
11
|
-
attachHandlers(
|
|
11
|
+
attachHandlers(router, handlers, prefix);
|
|
12
12
|
|
|
13
13
|
// attach ws handlers
|
|
14
14
|
wsRouter.use(`${prefix}/websocket`, handlers.wsServer.onConnect.bind(handlers.wsServer));
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const { WELLKNOWN_SERVICE_PATH_PREFIX } = require('@abtnode/constant');
|
|
2
|
+
const { switchProfile, switchPassport, login } = require('../../libs/connect/session');
|
|
3
|
+
const initJwt = require('../../libs/jwt');
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
init(server, node, options) {
|
|
7
|
+
const prefix = `${WELLKNOWN_SERVICE_PATH_PREFIX}/api/connect/relay`;
|
|
8
|
+
|
|
9
|
+
// api for login
|
|
10
|
+
server.post(`${prefix}/login/connect`, async (req, res) => {
|
|
11
|
+
try {
|
|
12
|
+
const { locale, currentConnected } = req.body;
|
|
13
|
+
const claim = await login.onConnect({
|
|
14
|
+
node,
|
|
15
|
+
request: req,
|
|
16
|
+
locale,
|
|
17
|
+
userDid: currentConnected.userDid,
|
|
18
|
+
userPk: currentConnected.userPk,
|
|
19
|
+
passportId: req.query.passportId || '',
|
|
20
|
+
});
|
|
21
|
+
res.json([[claim.profile, claim.verifiableCredential].filter(Boolean)]);
|
|
22
|
+
} catch (err) {
|
|
23
|
+
console.error(err);
|
|
24
|
+
res.json({ error: err.message });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
server.post(`${prefix}/login/approve`, async (req, res) => {
|
|
28
|
+
try {
|
|
29
|
+
const { locale, currentStep, challenge, authUrl, currentConnected, responseClaims } = req.body;
|
|
30
|
+
const { createSessionToken } = initJwt(node, options);
|
|
31
|
+
const result = await login.onApprove({
|
|
32
|
+
node,
|
|
33
|
+
request: req,
|
|
34
|
+
locale,
|
|
35
|
+
challenge,
|
|
36
|
+
claims: responseClaims[currentStep],
|
|
37
|
+
userDid: currentConnected.userDid,
|
|
38
|
+
userPk: currentConnected.userPk,
|
|
39
|
+
baseUrl: new URL(authUrl).origin,
|
|
40
|
+
createSessionToken,
|
|
41
|
+
});
|
|
42
|
+
res.json(result);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
console.error(err);
|
|
45
|
+
res.json({ error: err.message });
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// api for switch profile
|
|
50
|
+
server.post(`${prefix}/switch-profile/connect`, async (req, res) => {
|
|
51
|
+
try {
|
|
52
|
+
const { locale, currentConnected, previousConnected } = req.body;
|
|
53
|
+
const claim = await switchProfile.onConnect({
|
|
54
|
+
node,
|
|
55
|
+
request: req,
|
|
56
|
+
locale,
|
|
57
|
+
userDid: currentConnected.userDid,
|
|
58
|
+
previousUserDid: previousConnected?.userDid,
|
|
59
|
+
});
|
|
60
|
+
res.json([[{ type: 'profile', ...claim.profile }]]);
|
|
61
|
+
} catch (err) {
|
|
62
|
+
console.error(err);
|
|
63
|
+
res.json({ error: err.message });
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
server.post(`${prefix}/switch-profile/approve`, async (req, res) => {
|
|
67
|
+
try {
|
|
68
|
+
const { locale, currentConnected, responseClaims, currentStep } = req.body;
|
|
69
|
+
const result = await switchProfile.onApprove({
|
|
70
|
+
node,
|
|
71
|
+
request: req,
|
|
72
|
+
locale,
|
|
73
|
+
profile: responseClaims[currentStep].find((x) => x.type === 'profile'),
|
|
74
|
+
userDid: currentConnected.userDid,
|
|
75
|
+
});
|
|
76
|
+
res.json(result);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
console.error(err);
|
|
79
|
+
res.json({ error: err.message });
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// api for switch passport
|
|
84
|
+
server.post(`${prefix}/switch-passport/connect`, async (req, res) => {
|
|
85
|
+
try {
|
|
86
|
+
const { locale, currentConnected, previousConnected } = req.body;
|
|
87
|
+
const claim = await switchPassport.onConnect({
|
|
88
|
+
node,
|
|
89
|
+
request: req,
|
|
90
|
+
locale,
|
|
91
|
+
userDid: currentConnected.userDid,
|
|
92
|
+
previousUserDid: previousConnected?.userDid,
|
|
93
|
+
});
|
|
94
|
+
res.json([[{ type: 'verifiableCredential', ...claim.verifiableCredential }]]);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
console.error(err);
|
|
97
|
+
res.json({ error: err.message });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
server.post(`${prefix}/switch-passport/approve`, async (req, res) => {
|
|
101
|
+
try {
|
|
102
|
+
const { locale, currentConnected, responseClaims, challenge, currentStep } = req.body;
|
|
103
|
+
const { createSessionToken } = initJwt(node, options);
|
|
104
|
+
const sessionToken = await switchPassport.onApprove({
|
|
105
|
+
node,
|
|
106
|
+
request: req,
|
|
107
|
+
locale,
|
|
108
|
+
challenge,
|
|
109
|
+
verifiableCredential: responseClaims[currentStep].find((x) => x.type === 'verifiableCredential'),
|
|
110
|
+
userDid: currentConnected.userDid,
|
|
111
|
+
createSessionToken,
|
|
112
|
+
});
|
|
113
|
+
res.json({ sessionToken });
|
|
114
|
+
} catch (err) {
|
|
115
|
+
console.error(err);
|
|
116
|
+
res.json({ error: err.message });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
};
|