@abtnode/blocklet-services 1.16.17-beta-3232a7af → 1.16.17-beta-703fb879
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 +2 -0
- package/api/libs/connect/session.js +17 -2
- package/api/libs/open-graph/emoji.js +80 -0
- package/api/libs/open-graph/index.js +14 -1
- package/api/routes/federated.js +108 -2
- package/api/routes/oauth.js +46 -9
- package/api/routes/user-session.js +165 -0
- package/api/routes/user.js +21 -0
- package/api/services/auth/connect/login.js +13 -2
- package/api/services/auth/index.js +17 -8
- package/api/services/image/index.js +5 -0
- package/api/util/index.js +8 -1
- package/api/util/user-session.js +47 -0
- package/build/asset-manifest.json +79 -75
- package/build/index.html +1 -1
- package/build/service-worker.js +1 -1
- package/build/service-worker.js.map +1 -1
- package/build/static/css/{5982.ac464505.chunk.css → 8702.3ffd8486.chunk.css} +1 -1
- package/build/static/js/1148.e5dc907a.chunk.js +2 -0
- package/build/static/js/{6711.fa6dfc37.chunk.js → 1359.add26d23.chunk.js} +2 -2
- package/build/static/js/{2093.e2fce549.chunk.js → 2093.3192d241.chunk.js} +3 -3
- package/build/static/js/{2653.1307684f.chunk.js → 2653.6a6c1c6c.chunk.js} +2 -2
- package/build/static/js/2972.2d5268c3.chunk.js +2 -0
- package/build/static/js/3025.e2b1dac4.chunk.js +2 -0
- package/build/static/js/{9409.7d4a802c.chunk.js → 3033.1bbcc14e.chunk.js} +2 -2
- package/build/static/js/{1462.a4d2a902.chunk.js → 3131.f75fa792.chunk.js} +2 -2
- package/build/static/js/{3688.6edc63ba.chunk.js → 3688.2cea0226.chunk.js} +2 -2
- package/build/static/js/{4023.8cfc3ec6.chunk.js → 4023.a79b0858.chunk.js} +2 -2
- package/build/static/js/4076.e73cb63a.chunk.js +2 -0
- package/build/static/js/{6737.53cccea9.chunk.js → 4587.5b31e02e.chunk.js} +2 -2
- package/build/static/js/4716.1ecc2bb6.chunk.js +2 -0
- package/build/static/js/{4802.b2e38d47.chunk.js → 4802.d185b9f1.chunk.js} +2 -2
- package/build/static/js/5012.551dbbc3.chunk.js +2 -0
- package/build/static/js/5547.5a341867.chunk.js +3 -0
- package/build/static/js/5569.a7e151fc.chunk.js +2 -0
- package/build/static/js/{1565.f3579441.chunk.js → 5662.919cc4a2.chunk.js} +2 -2
- package/build/static/js/{9102.6dab0f45.chunk.js → 5982.d9eb7e90.chunk.js} +2 -2
- package/build/static/js/{6139.f7ca5bd6.chunk.js → 6139.cf6eb9b1.chunk.js} +2 -2
- package/build/static/js/6473.d89e783e.chunk.js +2 -0
- package/build/static/js/{8437.13d39ce4.chunk.js → 6637.b2e35031.chunk.js} +2 -2
- package/build/static/js/6658.0a27e04e.chunk.js +2 -0
- package/build/static/js/7050.3281ad5f.chunk.js +2 -0
- package/build/static/js/{716.cf9ba82c.chunk.js → 716.438b9c22.chunk.js} +3 -3
- package/build/static/js/779.99b3f0d2.chunk.js +2 -0
- package/build/static/js/7858.bac05f8f.chunk.js +2 -0
- package/build/static/js/8622.8478c900.chunk.js +2 -0
- package/build/static/js/8641.f2bb0ffc.chunk.js +2 -0
- package/build/static/js/{5982.0abe67c3.chunk.js → 8702.957f131a.chunk.js} +2 -2
- package/build/static/js/873.d6d31b32.chunk.js +3 -0
- package/build/static/js/{8792.fa2c5f5e.chunk.js → 8792.eff43237.chunk.js} +2 -2
- package/build/static/js/8944.09a1c53a.chunk.js +2 -0
- package/build/static/js/9403.b72e020c.chunk.js +2 -0
- package/build/static/js/9596.0e51cd6e.chunk.js +2 -0
- package/build/static/js/9900.b66896e6.chunk.js +2 -0
- package/build/static/js/{4682.f90d26fd.chunk.js → 9982.4f1ebb7f.chunk.js} +2 -2
- package/build/static/js/main.3cf7fedb.js +3 -0
- package/package.json +22 -22
- package/build/static/js/1480.88c590c2.chunk.js +0 -2
- package/build/static/js/1760.374a6e01.chunk.js +0 -2
- package/build/static/js/2393.528e5343.chunk.js +0 -2
- package/build/static/js/2836.f3dbcb77.chunk.js +0 -2
- package/build/static/js/3025.7dc2fe04.chunk.js +0 -2
- package/build/static/js/4164.5adbbbba.chunk.js +0 -2
- package/build/static/js/4420.05d9cb43.chunk.js +0 -2
- package/build/static/js/4716.bd8bed1b.chunk.js +0 -2
- package/build/static/js/5176.1963ce79.chunk.js +0 -2
- package/build/static/js/5547.e35129c6.chunk.js +0 -3
- package/build/static/js/6186.67c279db.chunk.js +0 -2
- package/build/static/js/6378.29466d9e.chunk.js +0 -2
- package/build/static/js/6576.02f6c898.chunk.js +0 -2
- package/build/static/js/6856.2eb98754.chunk.js +0 -2
- package/build/static/js/7226.4eca7c02.chunk.js +0 -3
- package/build/static/js/941.7757135a.chunk.js +0 -2
- package/build/static/js/9620.301493bc.chunk.js +0 -2
- package/build/static/js/9899.277d673f.chunk.js +0 -2
- package/build/static/js/main.f1e55cd8.js +0 -3
- /package/build/static/js/{2093.e2fce549.chunk.js.LICENSE.txt → 2093.3192d241.chunk.js.LICENSE.txt} +0 -0
- /package/build/static/js/{5547.e35129c6.chunk.js.LICENSE.txt → 5547.5a341867.chunk.js.LICENSE.txt} +0 -0
- /package/build/static/js/{716.cf9ba82c.chunk.js.LICENSE.txt → 716.438b9c22.chunk.js.LICENSE.txt} +0 -0
- /package/build/static/js/{7226.4eca7c02.chunk.js.LICENSE.txt → 873.d6d31b32.chunk.js.LICENSE.txt} +0 -0
- /package/build/static/js/{main.f1e55cd8.js.LICENSE.txt → main.3cf7fedb.js.LICENSE.txt} +0 -0
package/api/index.js
CHANGED
|
@@ -38,6 +38,7 @@ const createEnvRoutes = require('./routes/env');
|
|
|
38
38
|
const createOAuthRoutes = require('./routes/oauth');
|
|
39
39
|
const createFederatedRoutes = require('./routes/federated');
|
|
40
40
|
const createUserRoutes = require('./routes/user');
|
|
41
|
+
const createUserSessionRoutes = require('./routes/user-session');
|
|
41
42
|
const createBlockletRoutes = require('./routes/blocklet');
|
|
42
43
|
const createConnectRelayRoutes = require('./routes/connect/relay');
|
|
43
44
|
const createConnectSessionRoutes = require('./routes/connect/session');
|
|
@@ -260,6 +261,7 @@ module.exports = function createServer(node, serverOptions = {}) {
|
|
|
260
261
|
// API: auth
|
|
261
262
|
createOAuthRoutes.init(server, node, options);
|
|
262
263
|
createFederatedRoutes.init(server, node, options);
|
|
264
|
+
createUserSessionRoutes.init(server, node, options);
|
|
263
265
|
createUserRoutes.init(server, node, options);
|
|
264
266
|
createEnvRoutes.init(server, node, options);
|
|
265
267
|
createBlockletRoutes.init(server, node);
|
|
@@ -49,6 +49,7 @@ const { transferPassport } = require('../auth/utils');
|
|
|
49
49
|
const { migrateAccount, declareAccount } = require('../../services/oauth');
|
|
50
50
|
const { getTrustedIssuers, getFederatedTrustedIssuers } = require('../../util/blocklet-utils');
|
|
51
51
|
const { getUserAvatarUrl, migrateAuth0, getFederatedMaster, shouldSyncFederated } = require('../../util/federated');
|
|
52
|
+
const { upsertUserSession } = require('../../util/user-session');
|
|
52
53
|
|
|
53
54
|
const vcTypes = [VC_TYPE_GENERAL_PASSPORT, VC_TYPE_NODE_PASSPORT];
|
|
54
55
|
|
|
@@ -210,6 +211,7 @@ module.exports = {
|
|
|
210
211
|
createSessionToken,
|
|
211
212
|
componentId,
|
|
212
213
|
action,
|
|
214
|
+
visitorId,
|
|
213
215
|
}) => {
|
|
214
216
|
const blocklet = await request.getBlocklet();
|
|
215
217
|
const blockletInfo = await request.getBlockletInfo();
|
|
@@ -408,9 +410,8 @@ module.exports = {
|
|
|
408
410
|
);
|
|
409
411
|
}
|
|
410
412
|
|
|
411
|
-
// NOTICE: 采用异步来更新,不阻塞接口的正常响应
|
|
412
413
|
if (shouldSyncFederated(sourceAppPid, masterSite, blocklet)) {
|
|
413
|
-
node.syncFederated({
|
|
414
|
+
await node.syncFederated({
|
|
414
415
|
did: teamDid,
|
|
415
416
|
data: {
|
|
416
417
|
users: [
|
|
@@ -429,6 +430,18 @@ module.exports = {
|
|
|
429
430
|
});
|
|
430
431
|
}
|
|
431
432
|
|
|
433
|
+
const userSessionDoc = await upsertUserSession(
|
|
434
|
+
{
|
|
435
|
+
teamDid,
|
|
436
|
+
visitorId,
|
|
437
|
+
userDid: realDid,
|
|
438
|
+
appPid: teamDid,
|
|
439
|
+
passportId: passport?.id,
|
|
440
|
+
status: 'online',
|
|
441
|
+
},
|
|
442
|
+
{ req: request, node, sourceAppPid }
|
|
443
|
+
);
|
|
444
|
+
|
|
432
445
|
// Generate new session token that client can save to localStorage
|
|
433
446
|
const createToken = createTokenFn(createSessionToken);
|
|
434
447
|
const sessionConfig = blocklet.settings?.session || {};
|
|
@@ -468,6 +481,7 @@ module.exports = {
|
|
|
468
481
|
data: vc,
|
|
469
482
|
sessionToken,
|
|
470
483
|
refreshToken,
|
|
484
|
+
visitorId: userSessionDoc.visitorId,
|
|
471
485
|
nextWorkflowData: {
|
|
472
486
|
userDid: realDid,
|
|
473
487
|
},
|
|
@@ -477,6 +491,7 @@ module.exports = {
|
|
|
477
491
|
return {
|
|
478
492
|
sessionToken,
|
|
479
493
|
refreshToken,
|
|
494
|
+
visitorId: userSessionDoc.visitorId,
|
|
480
495
|
nextWorkflowData: {
|
|
481
496
|
userDid: realDid,
|
|
482
497
|
},
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/* eslint-disable consistent-return */
|
|
2
|
+
/* eslint-disable no-bitwise */
|
|
3
|
+
const fetch = require('node-fetch').default;
|
|
4
|
+
|
|
5
|
+
const U200D = String.fromCharCode(8205); // zero-width joiner
|
|
6
|
+
const UFE0Fg = /\uFE0F/g; // variation selector regex
|
|
7
|
+
|
|
8
|
+
const getIconCode = (char) => toCodePoint(char.indexOf(U200D) < 0 ? char.replace(UFE0Fg, '') : char);
|
|
9
|
+
|
|
10
|
+
const toCodePoint = (unicodeSurrogates) => {
|
|
11
|
+
const r = [];
|
|
12
|
+
let c = 0;
|
|
13
|
+
let p = 0;
|
|
14
|
+
let i = 0;
|
|
15
|
+
while (i < unicodeSurrogates.length) {
|
|
16
|
+
c = unicodeSurrogates.charCodeAt(i++);
|
|
17
|
+
if (p) {
|
|
18
|
+
r.push((65536 + ((p - 55296) << 10) + (c - 56320)).toString(16));
|
|
19
|
+
p = 0;
|
|
20
|
+
} else if (c >= 55296 && c <= 56319) {
|
|
21
|
+
p = c;
|
|
22
|
+
} else {
|
|
23
|
+
r.push(c.toString(16));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return r.join('-');
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const apis = {
|
|
30
|
+
twemoji: (code) => `https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/svg/${code.toLowerCase()}.svg`,
|
|
31
|
+
openmoji: 'https://cdn.jsdelivr.net/npm/@svgmoji/openmoji@2.0.0/svg/',
|
|
32
|
+
blobmoji: 'https://cdn.jsdelivr.net/npm/@svgmoji/blob@2.0.0/svg/',
|
|
33
|
+
noto: 'https://cdn.jsdelivr.net/gh/svgmoji/svgmoji/packages/svgmoji__noto/svg/',
|
|
34
|
+
fluent: (code) => `https://cdn.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/${code.toLowerCase()}_color.svg`,
|
|
35
|
+
fluentFlat: (code) =>
|
|
36
|
+
`https://cdn.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/${code.toLowerCase()}_flat.svg`,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// https://github.com/svgmoji/svgmoji
|
|
40
|
+
const loadEmoji = (code, type) => {
|
|
41
|
+
const api = apis[type] ?? apis.twemoji;
|
|
42
|
+
if (typeof api === 'function') {
|
|
43
|
+
return fetch(api(code));
|
|
44
|
+
}
|
|
45
|
+
return fetch(`${api}${code.toUpperCase()}.svg`);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const cache = new Map();
|
|
49
|
+
const loadDynamicAsset = (emojiType = 'twemoji') => {
|
|
50
|
+
const fn = async (languageCode, text) => {
|
|
51
|
+
if (languageCode === 'emoji') {
|
|
52
|
+
const code = getIconCode(text);
|
|
53
|
+
try {
|
|
54
|
+
const emoji = await loadEmoji(code, emojiType);
|
|
55
|
+
return `data:image/svg+xml;base64,${btoa(await emoji.text())}`;
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.error(`Failed to fetch emoji: ${text}:${code}`, err);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return async (...args) => {
|
|
63
|
+
const key = JSON.stringify({ ...args, emojiType });
|
|
64
|
+
const cached = cache.get(key);
|
|
65
|
+
if (cached) {
|
|
66
|
+
return cached;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const font = await fn(...args);
|
|
70
|
+
cache.set(key, font);
|
|
71
|
+
return font;
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
module.exports = {
|
|
76
|
+
apis,
|
|
77
|
+
getIconCode,
|
|
78
|
+
loadEmoji,
|
|
79
|
+
loadDynamicAsset,
|
|
80
|
+
};
|
|
@@ -15,6 +15,7 @@ const logger = require('@abtnode/logger')('@abtnode/blocklet-services/og');
|
|
|
15
15
|
|
|
16
16
|
const { getTemplate, getLogoSvg } = require('./template');
|
|
17
17
|
const { getCacheFilePath } = require('../image');
|
|
18
|
+
const emoji = require('./emoji');
|
|
18
19
|
|
|
19
20
|
const TEMPLATES = ['default', 'section', 'cover'];
|
|
20
21
|
|
|
@@ -64,6 +65,12 @@ const schema = Joi.object({
|
|
|
64
65
|
logo: Joi.string()
|
|
65
66
|
.uri({ scheme: ['https'] })
|
|
66
67
|
.optional(),
|
|
68
|
+
|
|
69
|
+
// custom emoji
|
|
70
|
+
emoji: Joi.string()
|
|
71
|
+
.valid(...Object.keys(emoji.apis))
|
|
72
|
+
.optional()
|
|
73
|
+
.default('twemoji'),
|
|
67
74
|
}).options({ stripUnknown: true, allowUnknown: true, noDefaults: false });
|
|
68
75
|
|
|
69
76
|
const generateTasks = {};
|
|
@@ -133,9 +140,14 @@ const convertExternalImage = (url, dest) => {
|
|
|
133
140
|
headers: { Accept: 'image/*' },
|
|
134
141
|
},
|
|
135
142
|
(res) => {
|
|
143
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
144
|
+
reject(new Error(`unexpected external image status: ${res.statusCode}`));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
136
148
|
const [type] = (res.headers['content-type'] || '').split('/');
|
|
137
149
|
if (type !== 'image') {
|
|
138
|
-
reject(new Error(
|
|
150
|
+
reject(new Error(`unexpected external image format: ${type}`));
|
|
139
151
|
return;
|
|
140
152
|
}
|
|
141
153
|
|
|
@@ -226,6 +238,7 @@ const generateOgImage = async (params, tmpDir) => {
|
|
|
226
238
|
style: 'normal',
|
|
227
239
|
},
|
|
228
240
|
],
|
|
241
|
+
loadAdditionalAsset: emoji.loadDynamicAsset(params.emoji),
|
|
229
242
|
});
|
|
230
243
|
|
|
231
244
|
if (params.format === 'svg') {
|
package/api/routes/federated.js
CHANGED
|
@@ -312,7 +312,7 @@ module.exports = {
|
|
|
312
312
|
const { blocklet } = req;
|
|
313
313
|
const { verifySite } = req.body;
|
|
314
314
|
const teamDid = blocklet.appPid;
|
|
315
|
-
const { users = null, sites = null } = req.body.verifyData;
|
|
315
|
+
const { users = null, sites = null, userSessions = null } = req.body.verifyData;
|
|
316
316
|
|
|
317
317
|
// FIXME: @zhanghan 校验 users 和 sites 数据合法性
|
|
318
318
|
const pendingList = [];
|
|
@@ -346,11 +346,31 @@ module.exports = {
|
|
|
346
346
|
}
|
|
347
347
|
}
|
|
348
348
|
|
|
349
|
+
if (!isNil(userSessions)) {
|
|
350
|
+
if (Array.isArray(userSessions)) {
|
|
351
|
+
for (const userSession of userSessions) {
|
|
352
|
+
const { action, ...userSessionItem } = userSession;
|
|
353
|
+
pendingList.push(
|
|
354
|
+
limitSync(async () => {
|
|
355
|
+
if (action === 'login') {
|
|
356
|
+
await node.upsertUserSession(
|
|
357
|
+
{ ...userSessionItem, teamDid, status: 'online' },
|
|
358
|
+
{ req, node, sourceAppPid: teamDid }
|
|
359
|
+
);
|
|
360
|
+
} else if (action === 'logout') {
|
|
361
|
+
await node.logoutUser({ ...userSessionItem, teamDid });
|
|
362
|
+
}
|
|
363
|
+
})
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
349
369
|
await Promise.all(pendingList);
|
|
350
370
|
await node.createAuditLog(
|
|
351
371
|
{
|
|
352
372
|
action: 'syncFederated',
|
|
353
|
-
args: { users, sites, callerSite: verifySite, teamDid },
|
|
373
|
+
args: { users, sites, userSessions, callerSite: verifySite, teamDid },
|
|
354
374
|
context: {
|
|
355
375
|
user: getAuditLogActorByFederatedSite(verifySite),
|
|
356
376
|
},
|
|
@@ -661,6 +681,84 @@ module.exports = {
|
|
|
661
681
|
res.json({ sessionToken, refreshToken });
|
|
662
682
|
});
|
|
663
683
|
|
|
684
|
+
// 用于在 master 站点登录页面获取 member 登录的 token
|
|
685
|
+
server.post(`${prefix}/loginByMaster`, ensureBlocklet(), verifyFederatedCall(), async (req, res) => {
|
|
686
|
+
const { verifySite } = req.body;
|
|
687
|
+
const { passport, user, walletOS, provider } = req.body.verifyData;
|
|
688
|
+
const { createSessionToken } = initJwt(node, options);
|
|
689
|
+
const createToken = createTokenFn(createSessionToken);
|
|
690
|
+
const { secret } = await req.getBlockletInfo();
|
|
691
|
+
const { blocklet } = req;
|
|
692
|
+
const teamDid = blocklet.appPid;
|
|
693
|
+
|
|
694
|
+
const sessionConfig = blocklet.settings?.session || {};
|
|
695
|
+
const prevUser = await node.getUser({
|
|
696
|
+
teamDid,
|
|
697
|
+
user: { did: user.did },
|
|
698
|
+
options: { enableConnectedAccount: true },
|
|
699
|
+
});
|
|
700
|
+
// HACK: member 调用 master 时,将 passport 的 role 还原为 master 中原有的 role
|
|
701
|
+
const targetPassport = passport?.id ? (prevUser?.passports || []).find((item) => item.id === passport.id) : null;
|
|
702
|
+
|
|
703
|
+
// HACK: 用户在 master 中存在时,不更新任何用户信息;不存在时,将新增一个用户
|
|
704
|
+
const filterUserInfo = prevUser ? {} : user;
|
|
705
|
+
if (filterUserInfo.avatar) {
|
|
706
|
+
let avatar = await getAvatarByUrl(filterUserInfo.avatar);
|
|
707
|
+
const nodeInfo = await req.getNodeInfo();
|
|
708
|
+
|
|
709
|
+
const { dataDir } = await getApplicationInfo({ node, nodeInfo, teamDid });
|
|
710
|
+
avatar = await extractUserAvatar(avatar, { dataDir });
|
|
711
|
+
filterUserInfo.avatar = avatar;
|
|
712
|
+
}
|
|
713
|
+
const realDid = prevUser?.did || user.did;
|
|
714
|
+
const realPk = prevUser?.pk || user.pk;
|
|
715
|
+
// NOTICE: 这里是 Master 登录,不需要 sourceAppPid 字段
|
|
716
|
+
const newUser = await node.loginUser({
|
|
717
|
+
teamDid,
|
|
718
|
+
user: {
|
|
719
|
+
...filterUserInfo,
|
|
720
|
+
did: realDid,
|
|
721
|
+
pk: realPk,
|
|
722
|
+
passport: targetPassport,
|
|
723
|
+
connectedAccount: {
|
|
724
|
+
provider: provider || LOGIN_PROVIDER.WALLET,
|
|
725
|
+
did: user.did,
|
|
726
|
+
pk: user.pk,
|
|
727
|
+
},
|
|
728
|
+
},
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
const { sessionToken, refreshToken } = createToken(
|
|
732
|
+
user.did,
|
|
733
|
+
{
|
|
734
|
+
secret,
|
|
735
|
+
passport: targetPassport,
|
|
736
|
+
role: targetPassport?.role || 'guest',
|
|
737
|
+
fullName: newUser.fullName,
|
|
738
|
+
provider: provider || LOGIN_PROVIDER.WALLET,
|
|
739
|
+
walletOS,
|
|
740
|
+
sourceAppPid: verifySite.appPid,
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
...sessionConfig,
|
|
744
|
+
didConnectVersion: getDidConnectVersion(req),
|
|
745
|
+
}
|
|
746
|
+
);
|
|
747
|
+
|
|
748
|
+
await node.createAuditLog(
|
|
749
|
+
{
|
|
750
|
+
action: 'loginByMaster',
|
|
751
|
+
args: { masterSite: verifySite, teamDid, blocklet },
|
|
752
|
+
context: {
|
|
753
|
+
user: newUser,
|
|
754
|
+
},
|
|
755
|
+
},
|
|
756
|
+
node
|
|
757
|
+
);
|
|
758
|
+
|
|
759
|
+
res.json({ sessionToken, refreshToken });
|
|
760
|
+
});
|
|
761
|
+
|
|
664
762
|
// member 向 master 申请 auth0 账号的 migrate
|
|
665
763
|
server.post(`${prefix}/migrateAuth0`, ensureBlocklet(), verifyFederatedCall(), async (req, res) => {
|
|
666
764
|
const { blocklet } = req;
|
|
@@ -728,5 +826,13 @@ module.exports = {
|
|
|
728
826
|
);
|
|
729
827
|
res.json(data);
|
|
730
828
|
});
|
|
829
|
+
|
|
830
|
+
server.post(`${prefix}/getPassport`, ensureBlocklet(), verifyFederatedCall(), async (req, res) => {
|
|
831
|
+
const { blocklet } = req;
|
|
832
|
+
const teamDid = blocklet.appPid;
|
|
833
|
+
const { passportId } = req.body.verifyData;
|
|
834
|
+
const result = await node.getPassportById({ teamDid, passportId });
|
|
835
|
+
res.json(result);
|
|
836
|
+
});
|
|
731
837
|
},
|
|
732
838
|
};
|
package/api/routes/oauth.js
CHANGED
|
@@ -24,6 +24,7 @@ const { sendToUser } = require('../libs/notification');
|
|
|
24
24
|
const { isInvitedUserOnly, createTokenFn, getDidConnectVersion } = require('../util');
|
|
25
25
|
const { ApiError } = require('../util/error');
|
|
26
26
|
const { loginAuth0, getFederatedMaster, getOAuthUserInfo, shouldSyncFederated } = require('../util/federated');
|
|
27
|
+
const { upsertUserSession } = require('../util/user-session');
|
|
27
28
|
|
|
28
29
|
const PREFIX = WELLKNOWN_SERVICE_PATH_PREFIX;
|
|
29
30
|
|
|
@@ -64,7 +65,7 @@ function getAuthClient(blocklet, provider) {
|
|
|
64
65
|
|
|
65
66
|
async function login(req, node, options) {
|
|
66
67
|
const blocklet = await req.getBlocklet();
|
|
67
|
-
const { token, locale = 'en', provider, componentId, sourceAppPid } = req.body;
|
|
68
|
+
const { token, locale = 'en', provider, componentId, sourceAppPid, visitorId } = req.body;
|
|
68
69
|
|
|
69
70
|
if (!blocklet.settings?.owner) {
|
|
70
71
|
throw new ApiError(400, t('oauthCantBeOwner', locale));
|
|
@@ -187,7 +188,6 @@ async function login(req, node, options) {
|
|
|
187
188
|
);
|
|
188
189
|
|
|
189
190
|
const masterSite = getFederatedMaster(blocklet);
|
|
190
|
-
// NOTICE: 采用异步来更新,不阻塞接口的正常响应
|
|
191
191
|
if (shouldSyncFederated(sourceAppPid, masterSite, blocklet)) {
|
|
192
192
|
const syncUserData = {
|
|
193
193
|
did: userDid,
|
|
@@ -196,7 +196,7 @@ async function login(req, node, options) {
|
|
|
196
196
|
avatar: getUserAvatarUrl(appUrl, profile.avatar),
|
|
197
197
|
connectedAccount: [connectedAccount],
|
|
198
198
|
};
|
|
199
|
-
node.syncFederated({
|
|
199
|
+
await node.syncFederated({
|
|
200
200
|
did: teamDid,
|
|
201
201
|
data: {
|
|
202
202
|
users: [
|
|
@@ -210,6 +210,22 @@ async function login(req, node, options) {
|
|
|
210
210
|
});
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
+
const userSessionDoc = await upsertUserSession(
|
|
214
|
+
{
|
|
215
|
+
teamDid,
|
|
216
|
+
userDid,
|
|
217
|
+
visitorId,
|
|
218
|
+
appPid: teamDid,
|
|
219
|
+
passportId: passport?.id,
|
|
220
|
+
status: 'online',
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
node,
|
|
224
|
+
req,
|
|
225
|
+
sourceAppPid: sourceAppPid || masterSite.appPid,
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
|
|
213
229
|
const { createSessionToken } = initJwt(node, options);
|
|
214
230
|
|
|
215
231
|
const createToken = createTokenFn(createSessionToken);
|
|
@@ -235,11 +251,12 @@ async function login(req, node, options) {
|
|
|
235
251
|
return {
|
|
236
252
|
sessionToken,
|
|
237
253
|
refreshToken,
|
|
254
|
+
visitorId: userSessionDoc.visitorId,
|
|
238
255
|
};
|
|
239
256
|
}
|
|
240
257
|
|
|
241
258
|
async function invite(req, node, options) {
|
|
242
|
-
const { locale, inviteId, token, baseUrl, provider = LOGIN_PROVIDER.AUTH0, sourceAppPid } = req.body;
|
|
259
|
+
const { locale, inviteId, token, baseUrl, provider = LOGIN_PROVIDER.AUTH0, sourceAppPid, visitorId } = req.body;
|
|
243
260
|
const blocklet = await req.getBlocklet();
|
|
244
261
|
let userWallet;
|
|
245
262
|
let oauthInfo;
|
|
@@ -312,6 +329,7 @@ async function invite(req, node, options) {
|
|
|
312
329
|
locale,
|
|
313
330
|
provider,
|
|
314
331
|
});
|
|
332
|
+
const masterSite = getFederatedMaster(blocklet);
|
|
315
333
|
|
|
316
334
|
if (currentUser) {
|
|
317
335
|
const walletDid = getWalletDid(currentUser);
|
|
@@ -351,8 +369,7 @@ async function invite(req, node, options) {
|
|
|
351
369
|
sourceAppPid,
|
|
352
370
|
},
|
|
353
371
|
});
|
|
354
|
-
|
|
355
|
-
// NOTICE: 采用异步来更新,不阻塞接口的正常响应
|
|
372
|
+
|
|
356
373
|
if (shouldSyncFederated(sourceAppPid, masterSite, blocklet)) {
|
|
357
374
|
const syncUserData = {
|
|
358
375
|
did: userDid,
|
|
@@ -360,7 +377,7 @@ async function invite(req, node, options) {
|
|
|
360
377
|
...profile,
|
|
361
378
|
connectedAccount: [connectedAccount],
|
|
362
379
|
};
|
|
363
|
-
node.syncFederated({
|
|
380
|
+
await node.syncFederated({
|
|
364
381
|
did: teamDid,
|
|
365
382
|
data: {
|
|
366
383
|
users: [
|
|
@@ -391,7 +408,27 @@ async function invite(req, node, options) {
|
|
|
391
408
|
return sessionToken;
|
|
392
409
|
}
|
|
393
410
|
|
|
394
|
-
|
|
411
|
+
const userSessionDoc = await upsertUserSession(
|
|
412
|
+
{
|
|
413
|
+
teamDid,
|
|
414
|
+
userDid,
|
|
415
|
+
visitorId,
|
|
416
|
+
appPid: teamDid,
|
|
417
|
+
passportId: passport.id,
|
|
418
|
+
status: 'online',
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
node,
|
|
422
|
+
req,
|
|
423
|
+
sourceAppPid: sourceAppPid || masterSite.appPid,
|
|
424
|
+
}
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
return {
|
|
428
|
+
sessionToken,
|
|
429
|
+
refreshToken,
|
|
430
|
+
visitorId: userSessionDoc.visitorId,
|
|
431
|
+
};
|
|
395
432
|
}
|
|
396
433
|
|
|
397
434
|
// 给 DID Wallet 绑定 Auth0 的流程
|
|
@@ -679,8 +716,8 @@ module.exports = {
|
|
|
679
716
|
|
|
680
717
|
async function getUserFn(req, res) {
|
|
681
718
|
const { token, provider } = req.body;
|
|
682
|
-
const blocklet = await req.getBlocklet();
|
|
683
719
|
const { wallet: blockletWallet } = await req.getBlockletInfo();
|
|
720
|
+
const blocklet = await req.getBlocklet();
|
|
684
721
|
const authClient = getAuthClient(blocklet, provider);
|
|
685
722
|
const oauthInfo = await authClient.getProfile(token);
|
|
686
723
|
const userWallet = fromAppDid(oauthInfo.sub, blockletWallet.secretKey);
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
2
|
+
const { WELLKNOWN_SERVICE_PATH_PREFIX } = require('@abtnode/constant');
|
|
3
|
+
const { LOGIN_PROVIDER } = require('@blocklet/constant');
|
|
4
|
+
const defaults = require('lodash/defaults');
|
|
5
|
+
const cloneDeep = require('lodash/cloneDeep');
|
|
6
|
+
const pLimit = require('p-limit');
|
|
7
|
+
|
|
8
|
+
const ensureBlocklet = require('../middlewares/ensure-blocklet');
|
|
9
|
+
const { upsertUserSession } = require('../util/user-session');
|
|
10
|
+
const { getUserAvatarUrl } = require('../util/federated');
|
|
11
|
+
|
|
12
|
+
const prefix = `${WELLKNOWN_SERVICE_PATH_PREFIX}/api/user-session`;
|
|
13
|
+
const limit = pLimit(5);
|
|
14
|
+
|
|
15
|
+
async function getPassportRole(passportId, { node, teamDid }) {
|
|
16
|
+
const passport = await node.getPassportById({ teamDid, passportId });
|
|
17
|
+
return passport?.role;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function getPassportRoleFromFederatedSite(passportId, { appPid, node, teamDid, federated }) {
|
|
21
|
+
const currentSite = federated.sites.find((x) => x.appPid === appPid);
|
|
22
|
+
if (currentSite) {
|
|
23
|
+
const passport = await node.getPassportFromFederated({
|
|
24
|
+
site: currentSite,
|
|
25
|
+
passportId,
|
|
26
|
+
teamDid,
|
|
27
|
+
});
|
|
28
|
+
return passport?.role;
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function patchUserSessionData(userSession, { blocklet, appPid, teamDid, node }) {
|
|
34
|
+
// 修正 avatar 地址,从 bn:// 转换为 http
|
|
35
|
+
if (userSession.user?.avatar) {
|
|
36
|
+
userSession.user.avatar = getUserAvatarUrl(userSession.user.avatar, blocklet);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const federated = defaults(cloneDeep(blocklet.settings.federated || {}), {
|
|
40
|
+
config: {
|
|
41
|
+
appId: blocklet.appDid,
|
|
42
|
+
appPid: teamDid,
|
|
43
|
+
},
|
|
44
|
+
sites: [],
|
|
45
|
+
});
|
|
46
|
+
const site = federated.sites.find((siteItem) => siteItem.appPid === userSession.appPid);
|
|
47
|
+
// 获取 appName,展示在前端
|
|
48
|
+
if (site) {
|
|
49
|
+
userSession.appName = site.appName;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 通过 userSession 信息判断 passport 对应的 role(针对 appPid 做转换)
|
|
53
|
+
let role;
|
|
54
|
+
if (userSession.passportId) {
|
|
55
|
+
if (appPid === teamDid) {
|
|
56
|
+
role = await getPassportRole(userSession?.passportId, { node, teamDid });
|
|
57
|
+
} else {
|
|
58
|
+
role = await getPassportRoleFromFederatedSite(userSession.passportId, {
|
|
59
|
+
appPid,
|
|
60
|
+
node,
|
|
61
|
+
teamDid,
|
|
62
|
+
federated,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
role = userSession.user.role;
|
|
67
|
+
}
|
|
68
|
+
userSession.user.role = role || 'guest';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = {
|
|
72
|
+
init(app, node) {
|
|
73
|
+
// NOTE: 保留 /login 路由,该功能不是针对于某一个实体来操作的,需要更明确表达意图
|
|
74
|
+
app.post(`${prefix}/login`, ensureBlocklet(), async (req, res) => {
|
|
75
|
+
const { blocklet } = req;
|
|
76
|
+
const { visitorId, userDid, appPid, passportId } = req.body;
|
|
77
|
+
if (!userDid) {
|
|
78
|
+
res.status(400).json({ error: 'userDid is required' });
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (!appPid) {
|
|
82
|
+
res.status(400).json({ error: 'appPid is required' });
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const teamDid = blocklet.appPid;
|
|
87
|
+
|
|
88
|
+
const userSessions = await node.getUserSession({
|
|
89
|
+
teamDid,
|
|
90
|
+
userDid,
|
|
91
|
+
visitorId,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (userSessions.length > 0) {
|
|
95
|
+
const user = await node.getUser({ teamDid, user: { did: userDid } });
|
|
96
|
+
const federated = defaults(cloneDeep(blocklet.settings.federated || {}), {
|
|
97
|
+
config: {
|
|
98
|
+
appId: blocklet.appDid,
|
|
99
|
+
appPid: teamDid,
|
|
100
|
+
},
|
|
101
|
+
sites: [],
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const memberSite = federated.sites.find((item) => item.appPid === appPid);
|
|
105
|
+
|
|
106
|
+
const result = await node.loginFederated({
|
|
107
|
+
did: appPid,
|
|
108
|
+
data: {
|
|
109
|
+
user,
|
|
110
|
+
passport: passportId ? { id: passportId } : undefined,
|
|
111
|
+
walletOS: 'web',
|
|
112
|
+
provider: LOGIN_PROVIDER.WALLET,
|
|
113
|
+
},
|
|
114
|
+
site: memberSite,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const userSessionDoc = await upsertUserSession(
|
|
118
|
+
{
|
|
119
|
+
teamDid: appPid,
|
|
120
|
+
userDid,
|
|
121
|
+
visitorId,
|
|
122
|
+
appPid,
|
|
123
|
+
passportId,
|
|
124
|
+
status: 'online',
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
node,
|
|
128
|
+
req,
|
|
129
|
+
sourceAppPid: teamDid,
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
res.json({ ...result, visitorId: userSessionDoc.visitorId });
|
|
134
|
+
} else {
|
|
135
|
+
res.json({});
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
app.get(`${prefix}`, ensureBlocklet(), async (req, res) => {
|
|
140
|
+
const { blocklet } = req;
|
|
141
|
+
const teamDid = blocklet.appPid;
|
|
142
|
+
const { visitorId, userDid, appPid = teamDid } = req.query;
|
|
143
|
+
|
|
144
|
+
if (!visitorId) {
|
|
145
|
+
res.json([]);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const userSessions = await node.getUserSession({
|
|
150
|
+
teamDid,
|
|
151
|
+
userDid,
|
|
152
|
+
visitorId,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
patchUserSessionData();
|
|
156
|
+
|
|
157
|
+
const pendingList = userSessions.map((item) =>
|
|
158
|
+
limit(() => patchUserSessionData(item, { blocklet, appPid, teamDid, node }))
|
|
159
|
+
);
|
|
160
|
+
await Promise.all(pendingList);
|
|
161
|
+
|
|
162
|
+
res.json(userSessions);
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
};
|
package/api/routes/user.js
CHANGED
|
@@ -15,6 +15,7 @@ const { ApiError } = require('../util/error');
|
|
|
15
15
|
const { loginWalletSchema, loginOAuthSchema } = require('../validators/login');
|
|
16
16
|
const verifySig = require('../middlewares/verify-sig');
|
|
17
17
|
const { getAvatarByEmail } = require('../libs/auth/utils');
|
|
18
|
+
const ensureBlocklet = require('../middlewares/ensure-blocklet');
|
|
18
19
|
|
|
19
20
|
const validateUser = (user) => {
|
|
20
21
|
try {
|
|
@@ -298,5 +299,25 @@ module.exports = {
|
|
|
298
299
|
}
|
|
299
300
|
server.post(`${prefix}/login`, verifySig, loginFn);
|
|
300
301
|
server.post(`${prefixApi}/login`, verifySig, loginFn);
|
|
302
|
+
|
|
303
|
+
server.post(`${prefixApi}/logout`, ensureBlocklet(), async (req, res) => {
|
|
304
|
+
if (req.user) {
|
|
305
|
+
const { blocklet } = req;
|
|
306
|
+
const teamDid = blocklet.appPid;
|
|
307
|
+
const { visitorId } = req.body;
|
|
308
|
+
if (!visitorId) {
|
|
309
|
+
res.status(400).json({ error: 'visitorId is required' });
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
await node.logoutUser({
|
|
314
|
+
userDid: req.user.did,
|
|
315
|
+
visitorId,
|
|
316
|
+
appPid: teamDid,
|
|
317
|
+
teamDid,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
res.json({});
|
|
321
|
+
});
|
|
301
322
|
},
|
|
302
323
|
};
|