@abtnode/blocklet-services 1.16.19-beta-340de95d → 1.16.19-beta-7b2db880
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/libs/connect/session.js +17 -19
- package/api/libs/connect/v2.js +2 -5
- package/api/libs/image.js +64 -64
- package/api/libs/open-graph/emoji.js +2 -2
- package/api/libs/open-graph/index.js +1 -0
- package/api/routes/federated.js +108 -19
- package/api/routes/oauth.js +24 -27
- package/api/routes/user-session.js +5 -1
- package/api/routes/user.js +1 -0
- package/api/services/auth/connect/invite.js +8 -5
- package/api/util/federated.js +45 -0
- package/api/validators/login.js +1 -1
- package/build/asset-manifest.json +17 -17
- package/build/index.html +1 -1
- package/build/service-worker.js +1 -1
- package/build/static/js/4076.8055ce74.chunk.js +2 -0
- package/build/static/js/4461.f9a883d3.chunk.js +2 -0
- package/build/static/js/5468.21a861b9.chunk.js +2 -0
- package/build/static/js/5547.42b98889.chunk.js +3 -0
- package/build/static/js/{8181.6c2a7dcb.chunk.js → 8181.bc510ea3.chunk.js} +2 -2
- package/build/static/js/8622.6aa0a4d4.chunk.js +2 -0
- package/build/static/js/{8944.939de854.chunk.js → 8944.5b8c231c.chunk.js} +2 -2
- package/build/static/js/main.09227d84.js +3 -0
- package/package.json +24 -27
- package/build/static/js/4076.f5369a1d.chunk.js +0 -2
- package/build/static/js/4461.3cd698bf.chunk.js +0 -2
- package/build/static/js/5468.b54ce65b.chunk.js +0 -2
- package/build/static/js/5547.570ded36.chunk.js +0 -3
- package/build/static/js/8622.ebd44812.chunk.js +0 -2
- package/build/static/js/main.581a3a26.js +0 -3
- /package/build/static/js/{5547.570ded36.chunk.js.LICENSE.txt → 5547.42b98889.chunk.js.LICENSE.txt} +0 -0
- /package/build/static/js/{main.581a3a26.js.LICENSE.txt → main.09227d84.js.LICENSE.txt} +0 -0
|
@@ -48,7 +48,13 @@ const { isInvitedUserOnly, createTokenFn, getDidConnectVersion } = require('../.
|
|
|
48
48
|
const { transferPassport } = require('../auth/utils');
|
|
49
49
|
const { migrateAccount, declareAccount } = require('../../services/oauth');
|
|
50
50
|
const { getTrustedIssuers, getFederatedTrustedIssuers } = require('../../util/blocklet-utils');
|
|
51
|
-
const {
|
|
51
|
+
const {
|
|
52
|
+
getUserAvatarUrl,
|
|
53
|
+
migrateAuth0,
|
|
54
|
+
getFederatedMaster,
|
|
55
|
+
shouldSyncFederated,
|
|
56
|
+
getUserWithinFederated,
|
|
57
|
+
} = require('../../util/federated');
|
|
52
58
|
|
|
53
59
|
const vcTypes = [VC_TYPE_GENERAL_PASSPORT, VC_TYPE_NODE_PASSPORT];
|
|
54
60
|
|
|
@@ -215,23 +221,16 @@ module.exports = {
|
|
|
215
221
|
const blocklet = await request.getBlocklet();
|
|
216
222
|
const blockletInfo = await request.getBlockletInfo();
|
|
217
223
|
const { wallet, secret, name, passportColor, did: teamDid } = blockletInfo;
|
|
224
|
+
const sourceAppPid = getSourceAppPid(request);
|
|
218
225
|
|
|
219
226
|
// Check user approved
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
user: {
|
|
223
|
-
did: userDid,
|
|
224
|
-
},
|
|
225
|
-
options: {
|
|
226
|
-
enableConnectedAccount: true,
|
|
227
|
-
},
|
|
228
|
-
});
|
|
229
|
-
if (user && !user.approved) {
|
|
227
|
+
const currentUser = await getUserWithinFederated({ sourceAppPid, teamDid, userDid, userPk }, { node, blocklet });
|
|
228
|
+
if (currentUser && !currentUser.approved) {
|
|
230
229
|
throw new Error(messages.notAllowed[locale]);
|
|
231
230
|
}
|
|
232
231
|
|
|
233
|
-
const realDid =
|
|
234
|
-
const realPk =
|
|
232
|
+
const realDid = currentUser?.did || userDid;
|
|
233
|
+
const realPk = currentUser?.pk || userPk;
|
|
235
234
|
|
|
236
235
|
// Get auth config
|
|
237
236
|
const authConfig = (await request.getServiceConfig(NODE_SERVICES.AUTH, { componentId })) || {};
|
|
@@ -244,7 +243,6 @@ module.exports = {
|
|
|
244
243
|
let defaultTtlPolicy = 'never';
|
|
245
244
|
let issuePassport = false;
|
|
246
245
|
|
|
247
|
-
const sourceAppPid = getSourceAppPid(request);
|
|
248
246
|
const provider = getLoginProvider(request);
|
|
249
247
|
const masterSite = getFederatedMaster(blocklet);
|
|
250
248
|
|
|
@@ -318,7 +316,7 @@ module.exports = {
|
|
|
318
316
|
|
|
319
317
|
// Get user passport from vc
|
|
320
318
|
let passport = vc ? createUserPassport(vc) : null;
|
|
321
|
-
if (
|
|
319
|
+
if (currentUser && passport && isUserPassportRevoked(currentUser, passport)) {
|
|
322
320
|
throw new Error(messages.passportRevoked[locale](passport.title, name));
|
|
323
321
|
}
|
|
324
322
|
|
|
@@ -342,19 +340,19 @@ module.exports = {
|
|
|
342
340
|
}
|
|
343
341
|
: null;
|
|
344
342
|
|
|
345
|
-
let fullName =
|
|
343
|
+
let fullName = currentUser?.fullName;
|
|
346
344
|
// Update profile
|
|
347
345
|
const passportForLog = passport || { name: 'Guest', role: 'guest' };
|
|
348
346
|
|
|
349
347
|
const connectAccount = { provider, did: userDid, pk: userPk };
|
|
350
348
|
|
|
351
349
|
let updatedUser;
|
|
352
|
-
if (
|
|
350
|
+
if (currentUser) {
|
|
353
351
|
updatedUser = await node.loginUser({
|
|
354
352
|
teamDid,
|
|
355
353
|
user: {
|
|
356
|
-
did:
|
|
357
|
-
pk:
|
|
354
|
+
did: currentUser.did,
|
|
355
|
+
pk: currentUser.pk,
|
|
358
356
|
locale,
|
|
359
357
|
passport,
|
|
360
358
|
sourceAppPid,
|
package/api/libs/connect/v2.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
|
-
const
|
|
2
|
+
const DynamicStorage = require('@abtnode/connect-storage');
|
|
3
3
|
const { Authenticator } = require('@did-connect/authenticator');
|
|
4
4
|
const createHandlers = require('@blocklet/sdk/lib/connect/handler');
|
|
5
5
|
const { sendToUser } = require('@blocklet/sdk/lib/util/send-notification');
|
|
@@ -22,10 +22,7 @@ module.exports = (node, opts) => {
|
|
|
22
22
|
const handlers = createHandlers({
|
|
23
23
|
logger,
|
|
24
24
|
authenticator,
|
|
25
|
-
storage: new
|
|
26
|
-
// FIXME: @wangshijun this does not work anymore
|
|
27
|
-
dbPath: path.join(opts.dataDir, 'sessions.db'),
|
|
28
|
-
}),
|
|
25
|
+
storage: new DynamicStorage({ dbPath: path.join(opts.dataDir, 'connections.db'), v2: true }),
|
|
29
26
|
socketPathname: `${WELLKNOWN_SERVICE_PATH_PREFIX}/api/connect/relay/websocket`,
|
|
30
27
|
sendNotificationFn: async (connectedDid, message, { request }) => {
|
|
31
28
|
const { wallet } = await request.getBlockletInfo();
|
package/api/libs/image.js
CHANGED
|
@@ -130,68 +130,6 @@ const isImageRequest = (req) => {
|
|
|
130
130
|
|
|
131
131
|
const getImageContentType = (extension) => (extension === 'svg' ? 'image/svg+xml' : `image/${extension}`);
|
|
132
132
|
|
|
133
|
-
const tasks = {};
|
|
134
|
-
const processAndRespond = (req, res, cacheDir, getSrc, ext, sendOptions = { maxAge: '356d', immutable: true }) => {
|
|
135
|
-
if (fs.existsSync(cacheDir) === false) {
|
|
136
|
-
fs.mkdirSync(cacheDir, { recursive: true });
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const params = req.imageFilter;
|
|
140
|
-
const extension = toLower(ext || path.extname(req.path).slice(1));
|
|
141
|
-
|
|
142
|
-
// NOTE: 不要使用 `req.accepts('image/webp')`,这里需要排除掉 `Accept: */*` 和 `Accept: image/*` 的情况
|
|
143
|
-
const acceptWebp = req.accepts().includes('image/webp');
|
|
144
|
-
if (!acceptWebp) {
|
|
145
|
-
if (params.f === 'webp') {
|
|
146
|
-
params.f = undefined;
|
|
147
|
-
}
|
|
148
|
-
if ((!extension || extension === 'webp') && !params.f) {
|
|
149
|
-
params.f = 'png';
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
if (!extension && !params.f) {
|
|
153
|
-
params.f = 'png';
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (!extension && !params.f) {
|
|
157
|
-
res.status(400).send('Image filter failed: either extension or format must be specified');
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const cacheKey = md5(stringify({ target: req.target, path: req.originalUrl, params }));
|
|
162
|
-
const destPath = getCacheFilePath(cacheDir, `${cacheKey}.${params.f || extension}`);
|
|
163
|
-
if (fs.existsSync(destPath)) {
|
|
164
|
-
res.header('Content-Type', getImageContentType(params.f || extension));
|
|
165
|
-
res.sendFile(destPath, sendOptions);
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// do the convert
|
|
170
|
-
tasks[cacheKey] ??= getSrc(req)
|
|
171
|
-
.then(([src, _extension]) => processImage(src, toLower(_extension), destPath, params))
|
|
172
|
-
.finally(() => {
|
|
173
|
-
setTimeout(() => {
|
|
174
|
-
delete tasks[cacheKey];
|
|
175
|
-
}, 1000);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
tasks[cacheKey]
|
|
179
|
-
.then(() => {
|
|
180
|
-
logger.info('image filter succeed', { params, url: req.originalUrl, destPath });
|
|
181
|
-
res.header('Content-Type', getImageContentType(params.f || extension));
|
|
182
|
-
res.sendFile(destPath, sendOptions);
|
|
183
|
-
})
|
|
184
|
-
.catch((err) => {
|
|
185
|
-
logger.error('image filter failed', { error: err, params, url: req.url });
|
|
186
|
-
if (params.e) {
|
|
187
|
-
res.status(500).send(`Image service error: ${err.message}`);
|
|
188
|
-
} else {
|
|
189
|
-
res.status(500);
|
|
190
|
-
res.sendFile(errorImage, { maxAge: 0 });
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
};
|
|
194
|
-
|
|
195
133
|
const processImage = (src, extension, dest, params) => {
|
|
196
134
|
return new Promise((resolve, reject) => {
|
|
197
135
|
// output stream
|
|
@@ -235,7 +173,7 @@ const processImage = (src, extension, dest, params) => {
|
|
|
235
173
|
dimensions.height = height;
|
|
236
174
|
}
|
|
237
175
|
|
|
238
|
-
const pipeline = sharp({ animated: true }).timeout({ seconds:
|
|
176
|
+
const pipeline = sharp({ animated: true, limitInputPixels: 0 }).timeout({ seconds: 60 });
|
|
239
177
|
if (rotate) {
|
|
240
178
|
pipeline.rotate(rotate);
|
|
241
179
|
}
|
|
@@ -268,7 +206,7 @@ const processImage = (src, extension, dest, params) => {
|
|
|
268
206
|
pipeline.negate();
|
|
269
207
|
}
|
|
270
208
|
|
|
271
|
-
pipeline[format || EXTENSIONS[extension]]({ quality, progressive: !!progressive, force: true });
|
|
209
|
+
pipeline[format || EXTENSIONS[extension]]({ quality, progressive: !!progressive, dither: 0, force: true });
|
|
272
210
|
|
|
273
211
|
pipeline.on('error', (err) => {
|
|
274
212
|
reject(err);
|
|
@@ -282,6 +220,68 @@ const processImage = (src, extension, dest, params) => {
|
|
|
282
220
|
});
|
|
283
221
|
};
|
|
284
222
|
|
|
223
|
+
const tasks = {};
|
|
224
|
+
const processAndRespond = (req, res, cacheDir, getSrc, ext, sendOptions = { maxAge: '356d', immutable: true }) => {
|
|
225
|
+
if (fs.existsSync(cacheDir) === false) {
|
|
226
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const params = req.imageFilter;
|
|
230
|
+
const extension = toLower(ext || path.extname(req.path).slice(1));
|
|
231
|
+
|
|
232
|
+
// NOTE: 不要使用 `req.accepts('image/webp')`,这里需要排除掉 `Accept: */*` 和 `Accept: image/*` 的情况
|
|
233
|
+
const acceptWebp = req.accepts().includes('image/webp');
|
|
234
|
+
if (!acceptWebp) {
|
|
235
|
+
if (params.f === 'webp') {
|
|
236
|
+
params.f = undefined;
|
|
237
|
+
}
|
|
238
|
+
if ((!extension || extension === 'webp') && !params.f) {
|
|
239
|
+
params.f = 'png';
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (!extension && !params.f) {
|
|
243
|
+
params.f = 'png';
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (!extension && !params.f) {
|
|
247
|
+
res.status(400).send('Image filter failed: either extension or format must be specified');
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const cacheKey = md5(stringify({ target: req.target, path: req.originalUrl, params }));
|
|
252
|
+
const destPath = getCacheFilePath(cacheDir, `${cacheKey}.${params.f || extension}`);
|
|
253
|
+
if (fs.existsSync(destPath)) {
|
|
254
|
+
res.header('Content-Type', getImageContentType(params.f || extension));
|
|
255
|
+
res.sendFile(destPath, sendOptions);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// do the convert
|
|
260
|
+
tasks[cacheKey] ??= getSrc(req)
|
|
261
|
+
.then(([src, _extension]) => processImage(src, toLower(_extension), destPath, params))
|
|
262
|
+
.finally(() => {
|
|
263
|
+
setTimeout(() => {
|
|
264
|
+
delete tasks[cacheKey];
|
|
265
|
+
}, 1000);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
tasks[cacheKey]
|
|
269
|
+
.then(() => {
|
|
270
|
+
logger.info('image filter succeed', { params, url: req.originalUrl, destPath });
|
|
271
|
+
res.header('Content-Type', getImageContentType(params.f || extension));
|
|
272
|
+
res.sendFile(destPath, sendOptions);
|
|
273
|
+
})
|
|
274
|
+
.catch((err) => {
|
|
275
|
+
logger.error('image filter failed', { error: err, params, url: req.url });
|
|
276
|
+
if (params.e) {
|
|
277
|
+
res.status(500).send(`Image service error: ${err.message}`);
|
|
278
|
+
} else {
|
|
279
|
+
res.status(500);
|
|
280
|
+
res.sendFile(errorImage, { maxAge: 0 });
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
};
|
|
284
|
+
|
|
285
285
|
module.exports = {
|
|
286
286
|
isImageAccepted,
|
|
287
287
|
isImageRequest,
|
|
@@ -5,8 +5,6 @@ const fetch = require('node-fetch').default;
|
|
|
5
5
|
const U200D = String.fromCharCode(8205); // zero-width joiner
|
|
6
6
|
const UFE0Fg = /\uFE0F/g; // variation selector regex
|
|
7
7
|
|
|
8
|
-
const getIconCode = (char) => toCodePoint(char.indexOf(U200D) < 0 ? char.replace(UFE0Fg, '') : char);
|
|
9
|
-
|
|
10
8
|
const toCodePoint = (unicodeSurrogates) => {
|
|
11
9
|
const r = [];
|
|
12
10
|
let c = 0;
|
|
@@ -26,6 +24,8 @@ const toCodePoint = (unicodeSurrogates) => {
|
|
|
26
24
|
return r.join('-');
|
|
27
25
|
};
|
|
28
26
|
|
|
27
|
+
const getIconCode = (char) => toCodePoint(char.indexOf(U200D) < 0 ? char.replace(UFE0Fg, '') : char);
|
|
28
|
+
|
|
29
29
|
const apis = {
|
|
30
30
|
twemoji: (code) => `https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/svg/${code.toLowerCase()}.svg`,
|
|
31
31
|
openmoji: 'https://cdn.jsdelivr.net/npm/@svgmoji/openmoji@2.0.0/svg/',
|
|
@@ -107,6 +107,7 @@ const getOgImage = ({ input, info, cacheDir, format, tmpDir }) => {
|
|
|
107
107
|
return destPath;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
// eslint-disable-next-line no-use-before-define
|
|
110
111
|
generateTasks[cacheKey] ??= generateOgImage(params, tmpDir).finally(() => {
|
|
111
112
|
setTimeout(() => {
|
|
112
113
|
delete generateTasks[cacheKey];
|
package/api/routes/federated.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { WELLKNOWN_SERVICE_PATH_PREFIX } = require('@abtnode/constant');
|
|
1
|
+
const { WELLKNOWN_SERVICE_PATH_PREFIX, FEDERATED } = require('@abtnode/constant');
|
|
2
2
|
const { signV2 } = require('@arcblock/jwt');
|
|
3
3
|
const normalizePathPrefix = require('@abtnode/util/lib/normalize-path-prefix');
|
|
4
4
|
const cloneDeep = require('lodash/cloneDeep');
|
|
@@ -25,13 +25,12 @@ const { createTokenFn, getDidConnectVersion } = require('../util');
|
|
|
25
25
|
const ensureBlocklet = require('../middlewares/ensure-blocklet');
|
|
26
26
|
const verifyFederatedCall = require('../middlewares/verify-federated-call');
|
|
27
27
|
const checkFederatedCorsCall = require('../middlewares/check-federated-cors-call');
|
|
28
|
-
const { getUserAvatarUrl, getFederatedMaster } = require('../util/federated');
|
|
28
|
+
const { getUserAvatarUrl, getFederatedMaster, getUserWithinFederated } = require('../util/federated');
|
|
29
29
|
const { declareAccount, migrateAccount } = require('../services/oauth');
|
|
30
30
|
|
|
31
31
|
const PREFIX = WELLKNOWN_SERVICE_PATH_PREFIX;
|
|
32
32
|
|
|
33
33
|
const prefix = `${PREFIX}/api/federated`;
|
|
34
|
-
const limitSync = pLimit(1);
|
|
35
34
|
|
|
36
35
|
function getAuditLogActorByFederatedSite(blocklet) {
|
|
37
36
|
return {
|
|
@@ -60,8 +59,13 @@ async function syncSwitchProfile(user, { node, teamDid, dataDir }) {
|
|
|
60
59
|
});
|
|
61
60
|
}
|
|
62
61
|
|
|
63
|
-
|
|
62
|
+
/**
|
|
63
|
+
* 处理站点群中某一站点推送的同步 connectAccount 的请求
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
async function syncConnectAccount(user, { node, teamDid, dataDir, blocklet }) {
|
|
64
67
|
const tempUser = pick(user, ['did', 'pk', 'avatar', 'fullName', 'email', 'connectedAccount', 'sourceAppPid']);
|
|
68
|
+
const masterSite = getFederatedMaster(blocklet);
|
|
65
69
|
|
|
66
70
|
// 处理 avatar
|
|
67
71
|
if (tempUser.avatar) {
|
|
@@ -73,15 +77,71 @@ async function syncConnectAccount(user, { node, teamDid, dataDir }) {
|
|
|
73
77
|
}
|
|
74
78
|
}
|
|
75
79
|
|
|
80
|
+
// NOTICE: 如果当前站点不是 master,就需要考虑该 member 需要向 master 请求同步一次指定账户,因为指定账户可能存在于站点群,而不存在于当前站点中的情况
|
|
81
|
+
if (masterSite.appPid !== teamDid) {
|
|
82
|
+
await getUserWithinFederated(
|
|
83
|
+
{
|
|
84
|
+
sourceAppPid: tempUser.sourceAppPid,
|
|
85
|
+
teamDid,
|
|
86
|
+
userDid: tempUser.did,
|
|
87
|
+
userPk: tempUser.pk,
|
|
88
|
+
},
|
|
89
|
+
{ blocklet, node }
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
76
93
|
await node.loginUser({
|
|
77
94
|
teamDid,
|
|
78
95
|
user: tempUser,
|
|
79
96
|
});
|
|
80
97
|
}
|
|
81
98
|
|
|
99
|
+
/**
|
|
100
|
+
* member 站点向 master 站点请求拉取一个用户信息
|
|
101
|
+
*/
|
|
102
|
+
async function pullUserAccount(user, { node, teamDid, blocklet }) {
|
|
103
|
+
const { did } = user;
|
|
104
|
+
const currentUser = await node.getUser({
|
|
105
|
+
teamDid,
|
|
106
|
+
user: {
|
|
107
|
+
did,
|
|
108
|
+
},
|
|
109
|
+
options: {
|
|
110
|
+
enableConnectedAccount: true,
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (!currentUser) return null;
|
|
115
|
+
|
|
116
|
+
const syncUser = pick(currentUser, [
|
|
117
|
+
'did',
|
|
118
|
+
'pk',
|
|
119
|
+
'fullName',
|
|
120
|
+
'email',
|
|
121
|
+
'remark',
|
|
122
|
+
'sourceProvider',
|
|
123
|
+
'locale',
|
|
124
|
+
'approved',
|
|
125
|
+
'extra',
|
|
126
|
+
'sourceAppPid',
|
|
127
|
+
]);
|
|
128
|
+
syncUser.avatar = getUserAvatarUrl(currentUser.avatar, blocklet);
|
|
129
|
+
syncUser.email = syncUser.email || '';
|
|
130
|
+
syncUser.connectedAccounts = currentUser.connectedAccounts.map((x) => {
|
|
131
|
+
const connectAccount = pick(x, ['did', 'pk', 'provider', 'id']);
|
|
132
|
+
if (!connectAccount.id) {
|
|
133
|
+
delete connectAccount.id;
|
|
134
|
+
}
|
|
135
|
+
return connectAccount;
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return syncUser;
|
|
139
|
+
}
|
|
140
|
+
|
|
82
141
|
const syncFnMaps = {
|
|
83
142
|
switchProfile: syncSwitchProfile,
|
|
84
143
|
connectAccount: syncConnectAccount,
|
|
144
|
+
pullAccount: pullUserAccount,
|
|
85
145
|
};
|
|
86
146
|
|
|
87
147
|
module.exports = {
|
|
@@ -186,6 +246,7 @@ module.exports = {
|
|
|
186
246
|
signer: permanentWallet.address,
|
|
187
247
|
data: signV2(permanentWallet.address, permanentWallet.secretKey, { sites: federated.sites }),
|
|
188
248
|
};
|
|
249
|
+
const limitSync = pLimit(FEDERATED.SYNC_LIMIT);
|
|
189
250
|
|
|
190
251
|
const waitingList = federated.sites
|
|
191
252
|
.filter((item) => item.appId !== federated.config.appId)
|
|
@@ -313,9 +374,15 @@ module.exports = {
|
|
|
313
374
|
const { verifySite } = req.body;
|
|
314
375
|
const teamDid = blocklet.appPid;
|
|
315
376
|
const { users = null, sites = null, userSessions = null } = req.body.verifyData;
|
|
377
|
+
const resultData = {
|
|
378
|
+
users: [],
|
|
379
|
+
sites: [],
|
|
380
|
+
userSessions: [],
|
|
381
|
+
};
|
|
316
382
|
|
|
317
383
|
// FIXME: @zhanghan 校验 users 和 sites 数据合法性
|
|
318
384
|
if (!isNil(sites)) {
|
|
385
|
+
const limitSync = pLimit(FEDERATED.SYNC_LIMIT);
|
|
319
386
|
const pendingSiteList = [];
|
|
320
387
|
const federated = cloneDeep(blocklet.settings.federated || {});
|
|
321
388
|
federated.sites = sites;
|
|
@@ -327,34 +394,39 @@ module.exports = {
|
|
|
327
394
|
});
|
|
328
395
|
})
|
|
329
396
|
);
|
|
330
|
-
await Promise.all(pendingSiteList);
|
|
397
|
+
const resList = await Promise.all(pendingSiteList);
|
|
398
|
+
resultData.sites = resList;
|
|
331
399
|
}
|
|
332
400
|
|
|
333
401
|
if (!isNil(users)) {
|
|
334
402
|
if (Array.isArray(users)) {
|
|
403
|
+
const limitSync = pLimit(FEDERATED.SYNC_LIMIT);
|
|
335
404
|
const pendingUserList = [];
|
|
336
405
|
const nodeInfo = await req.getNodeInfo();
|
|
337
406
|
const { dataDir } = await getApplicationInfo({ node, nodeInfo, teamDid });
|
|
338
407
|
for (const user of users) {
|
|
339
408
|
pendingUserList.push(
|
|
340
409
|
limitSync(async () => {
|
|
341
|
-
await syncFnMaps[user.action]?.(
|
|
410
|
+
const result = await syncFnMaps[user.action]?.(
|
|
342
411
|
{
|
|
343
412
|
...user,
|
|
344
413
|
sourceAppPid: user.sourceAppPid === teamDid ? null : user.sourceAppPid,
|
|
345
414
|
},
|
|
346
|
-
{ node, teamDid, dataDir }
|
|
415
|
+
{ node, teamDid, dataDir, blocklet }
|
|
347
416
|
);
|
|
417
|
+
return result;
|
|
348
418
|
})
|
|
349
419
|
);
|
|
350
420
|
}
|
|
351
|
-
await Promise.all(pendingUserList);
|
|
421
|
+
const resList = await Promise.all(pendingUserList);
|
|
422
|
+
resultData.users = resList;
|
|
352
423
|
}
|
|
353
424
|
}
|
|
354
425
|
|
|
355
426
|
if (!isNil(userSessions)) {
|
|
356
427
|
if (Array.isArray(userSessions)) {
|
|
357
428
|
const pendingUserSessionList = [];
|
|
429
|
+
const limitSync = pLimit(FEDERATED.SYNC_LIMIT);
|
|
358
430
|
for (const userSession of userSessions) {
|
|
359
431
|
const { action, ...userSessionItem } = userSession;
|
|
360
432
|
pendingUserSessionList.push(
|
|
@@ -367,7 +439,8 @@ module.exports = {
|
|
|
367
439
|
})
|
|
368
440
|
);
|
|
369
441
|
}
|
|
370
|
-
await Promise.all(pendingUserSessionList);
|
|
442
|
+
const resList = await Promise.all(pendingUserSessionList);
|
|
443
|
+
resultData.userSessions = resList;
|
|
371
444
|
}
|
|
372
445
|
}
|
|
373
446
|
|
|
@@ -381,10 +454,13 @@ module.exports = {
|
|
|
381
454
|
},
|
|
382
455
|
node
|
|
383
456
|
);
|
|
384
|
-
res.json(
|
|
457
|
+
res.json(resultData);
|
|
385
458
|
});
|
|
386
459
|
|
|
387
|
-
|
|
460
|
+
/**
|
|
461
|
+
* @deprecated
|
|
462
|
+
* step 4 检测自动登录条件(member 向 master 发起请求)
|
|
463
|
+
*/
|
|
388
464
|
server.post(
|
|
389
465
|
`${prefix}/prelogin`,
|
|
390
466
|
cors({ credentials: true, origin: true }),
|
|
@@ -425,7 +501,10 @@ module.exports = {
|
|
|
425
501
|
}
|
|
426
502
|
);
|
|
427
503
|
|
|
428
|
-
|
|
504
|
+
/**
|
|
505
|
+
* @deprecated
|
|
506
|
+
* step 5 完成 member 的自动登录(member 向 master 请求)
|
|
507
|
+
*/
|
|
429
508
|
server.post(
|
|
430
509
|
`${prefix}/login`,
|
|
431
510
|
cors({ credentials: true, origin: true }),
|
|
@@ -591,7 +670,10 @@ module.exports = {
|
|
|
591
670
|
res.json({ sessionToken, refreshToken });
|
|
592
671
|
});
|
|
593
672
|
|
|
594
|
-
|
|
673
|
+
/**
|
|
674
|
+
* @deprecated
|
|
675
|
+
* member 要求 master 退出登录
|
|
676
|
+
*/
|
|
595
677
|
server.post(
|
|
596
678
|
`${prefix}/logout`,
|
|
597
679
|
cors({ credentials: true, origin: true }),
|
|
@@ -698,11 +780,18 @@ module.exports = {
|
|
|
698
780
|
const teamDid = blocklet.appPid;
|
|
699
781
|
|
|
700
782
|
const sessionConfig = blocklet.settings?.session || {};
|
|
701
|
-
const prevUser = await
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
783
|
+
const prevUser = await getUserWithinFederated(
|
|
784
|
+
{
|
|
785
|
+
teamDid,
|
|
786
|
+
sourceAppPid: verifySite.appPid,
|
|
787
|
+
userDid: user.did,
|
|
788
|
+
userPk: user.pk,
|
|
789
|
+
},
|
|
790
|
+
{
|
|
791
|
+
node,
|
|
792
|
+
blocklet,
|
|
793
|
+
}
|
|
794
|
+
);
|
|
706
795
|
// HACK: member 调用 master 时,将 passport 的 role 还原为 master 中原有的 role
|
|
707
796
|
const targetPassport = passport?.id ? (prevUser?.passports || []).find((item) => item.id === passport.id) : null;
|
|
708
797
|
|
|
@@ -718,7 +807,6 @@ module.exports = {
|
|
|
718
807
|
}
|
|
719
808
|
const realDid = prevUser?.did || user.did;
|
|
720
809
|
const realPk = prevUser?.pk || user.pk;
|
|
721
|
-
// NOTICE: 这里是 Master 登录,不需要 sourceAppPid 字段
|
|
722
810
|
const newUser = await node.loginUser({
|
|
723
811
|
teamDid,
|
|
724
812
|
user: {
|
|
@@ -726,6 +814,7 @@ module.exports = {
|
|
|
726
814
|
did: realDid,
|
|
727
815
|
pk: realPk,
|
|
728
816
|
passport: targetPassport,
|
|
817
|
+
sourceAppPid: verifySite.appPid,
|
|
729
818
|
connectedAccount: {
|
|
730
819
|
provider: provider || LOGIN_PROVIDER.WALLET,
|
|
731
820
|
did: user.did,
|
package/api/routes/oauth.js
CHANGED
|
@@ -23,7 +23,12 @@ const initJwt = require('../libs/jwt');
|
|
|
23
23
|
const { sendToUser } = require('../libs/notification');
|
|
24
24
|
const { isInvitedUserOnly, createTokenFn, getDidConnectVersion } = require('../util');
|
|
25
25
|
const { ApiError } = require('../util/error');
|
|
26
|
-
const {
|
|
26
|
+
const {
|
|
27
|
+
getFederatedMaster,
|
|
28
|
+
getOAuthUserInfo,
|
|
29
|
+
shouldSyncFederated,
|
|
30
|
+
getUserWithinFederated,
|
|
31
|
+
} = require('../util/federated');
|
|
27
32
|
|
|
28
33
|
const PREFIX = WELLKNOWN_SERVICE_PATH_PREFIX;
|
|
29
34
|
|
|
@@ -70,6 +75,7 @@ async function login(req, node, options) {
|
|
|
70
75
|
throw new ApiError(400, t('oauthCantBeOwner', locale));
|
|
71
76
|
}
|
|
72
77
|
const { did: teamDid, wallet: blockletWallet, secret, appUrl } = await req.getBlockletInfo();
|
|
78
|
+
|
|
73
79
|
let userWallet;
|
|
74
80
|
let oauthInfo;
|
|
75
81
|
|
|
@@ -89,6 +95,7 @@ async function login(req, node, options) {
|
|
|
89
95
|
oauthInfo = await authClient.getProfile(token);
|
|
90
96
|
userWallet = fromAppDid(oauthInfo.sub, blockletWallet.secretKey);
|
|
91
97
|
}
|
|
98
|
+
|
|
92
99
|
const userDid = userWallet.address;
|
|
93
100
|
const userPk = userWallet.publicKey;
|
|
94
101
|
|
|
@@ -99,15 +106,7 @@ async function login(req, node, options) {
|
|
|
99
106
|
|
|
100
107
|
const lastLoginIp = get(req, 'headers[x-real-ip]') || '';
|
|
101
108
|
let passport = { name: 'Guest', role: 'guest' };
|
|
102
|
-
let currentUser = await
|
|
103
|
-
teamDid,
|
|
104
|
-
user: {
|
|
105
|
-
did: userDid,
|
|
106
|
-
},
|
|
107
|
-
options: {
|
|
108
|
-
enableConnectedAccount: true,
|
|
109
|
-
},
|
|
110
|
-
});
|
|
109
|
+
let currentUser = await getUserWithinFederated({ sourceAppPid, teamDid, userDid, userPk }, { node, blocklet });
|
|
111
110
|
let doc;
|
|
112
111
|
let passportForLog;
|
|
113
112
|
const fullName = currentUser?.fullName || oauthInfo?.nickname;
|
|
@@ -117,6 +116,7 @@ async function login(req, node, options) {
|
|
|
117
116
|
pk: userPk,
|
|
118
117
|
id: oauthInfo.sub,
|
|
119
118
|
};
|
|
119
|
+
const masterSite = getFederatedMaster(blocklet);
|
|
120
120
|
let profile;
|
|
121
121
|
// 当前账户已存在,更新账户信息
|
|
122
122
|
if (currentUser) {
|
|
@@ -156,6 +156,7 @@ async function login(req, node, options) {
|
|
|
156
156
|
if (invitedUserOnly) {
|
|
157
157
|
throw new ApiError(403, t('needInviteToLogin', locale));
|
|
158
158
|
}
|
|
159
|
+
|
|
159
160
|
passportForLog = { name: 'Guest', role: 'guest' };
|
|
160
161
|
profile = {
|
|
161
162
|
fullName: oauthInfo.nickname,
|
|
@@ -186,7 +187,6 @@ async function login(req, node, options) {
|
|
|
186
187
|
node
|
|
187
188
|
);
|
|
188
189
|
|
|
189
|
-
const masterSite = getFederatedMaster(blocklet);
|
|
190
190
|
const ua = req.headers['user-agent'];
|
|
191
191
|
const userSessionDoc = await node.upsertUserSession({
|
|
192
192
|
teamDid,
|
|
@@ -277,37 +277,34 @@ async function invite(req, node, options) {
|
|
|
277
277
|
let userWallet;
|
|
278
278
|
let oauthInfo;
|
|
279
279
|
|
|
280
|
+
const { did: teamDid, wallet: blockletWallet, secret } = await req.getBlockletInfo();
|
|
281
|
+
|
|
280
282
|
// NOTICE: 如果是统一登录,则向 master 站点发起 oauth 登录请求,auth0 的账户信息必须由 master 来生成
|
|
281
283
|
if (sourceAppPid) {
|
|
282
|
-
const data = await
|
|
284
|
+
const data = await getOAuthUserInfo({
|
|
283
285
|
request: req,
|
|
284
286
|
blocklet,
|
|
285
|
-
locale,
|
|
286
287
|
provider,
|
|
287
288
|
token,
|
|
288
289
|
sourceAppPid,
|
|
290
|
+
locale,
|
|
289
291
|
});
|
|
290
|
-
|
|
292
|
+
userWallet = data.wallet;
|
|
293
|
+
oauthInfo = data.info;
|
|
291
294
|
} else {
|
|
292
295
|
const authClient = getAuthClient(blocklet, provider);
|
|
293
296
|
oauthInfo = await authClient.getProfile(token);
|
|
294
297
|
userWallet = fromAppDid(oauthInfo.sub, blockletWallet.secretKey);
|
|
295
298
|
}
|
|
296
|
-
|
|
299
|
+
|
|
297
300
|
const nodeInfo = await req.getNodeInfo();
|
|
298
301
|
let userDid = userWallet.address;
|
|
299
302
|
let userPk = userWallet.publicKey;
|
|
300
303
|
|
|
301
304
|
let profile;
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
did: userDid,
|
|
306
|
-
},
|
|
307
|
-
options: {
|
|
308
|
-
enableConnectedAccount: true,
|
|
309
|
-
},
|
|
310
|
-
});
|
|
305
|
+
|
|
306
|
+
const currentUser = await getUserWithinFederated({ sourceAppPid, teamDid, userDid, userPk }, { node, blocklet });
|
|
307
|
+
|
|
311
308
|
const { dataDir, name: applicationName } = await getApplicationInfo({ node, nodeInfo, teamDid });
|
|
312
309
|
if (currentUser) {
|
|
313
310
|
profile = {
|
|
@@ -340,8 +337,8 @@ async function invite(req, node, options) {
|
|
|
340
337
|
profile,
|
|
341
338
|
statusEndpointBaseUrl,
|
|
342
339
|
teamDid,
|
|
343
|
-
userDid,
|
|
344
|
-
userPk,
|
|
340
|
+
userDid: userWallet.address,
|
|
341
|
+
userPk: userWallet.publicKey,
|
|
345
342
|
locale,
|
|
346
343
|
provider,
|
|
347
344
|
});
|
|
@@ -506,7 +503,7 @@ async function bind(req, node, options) {
|
|
|
506
503
|
if (!avatar) {
|
|
507
504
|
const nodeInfo = await req.getNodeInfo();
|
|
508
505
|
const { dataDir } = await getApplicationInfo({ node, nodeInfo, teamDid });
|
|
509
|
-
avatar = await getAvatarByEmail(userInfo.email);
|
|
506
|
+
avatar = userInfo.picture ? await getAvatarByUrl(userInfo.picture) : await getAvatarByEmail(userInfo.email);
|
|
510
507
|
avatar = await extractUserAvatar(avatar, { dataDir });
|
|
511
508
|
}
|
|
512
509
|
|