@abtnode/blocklet-services 1.16.19-beta-e6aac665 → 1.16.19-beta-710018ca

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.
Files changed (66) hide show
  1. package/api/libs/connect/v1.js +1 -5
  2. package/api/libs/connect/v2.js +1 -5
  3. package/api/libs/image.js +62 -62
  4. package/api/libs/open-graph/emoji.js +2 -2
  5. package/api/libs/open-graph/index.js +1 -0
  6. package/api/routes/oauth.js +5 -1
  7. package/api/routes/user.js +42 -25
  8. package/api/validators/login.js +3 -1
  9. package/build/asset-manifest.json +59 -61
  10. package/build/index.html +1 -1
  11. package/build/service-worker.js +1 -1
  12. package/build/service-worker.js.map +1 -1
  13. package/build/static/css/5547.75913953.chunk.css +12 -0
  14. package/build/static/js/{1148.5ccba08e.chunk.js → 1148.67f53e43.chunk.js} +2 -2
  15. package/build/static/js/{1233.276cf2d0.chunk.js → 1233.43c6131b.chunk.js} +3 -3
  16. package/build/static/js/1905.656cb960.chunk.js +2 -0
  17. package/build/static/js/2838.bc235457.chunk.js +3 -0
  18. package/build/static/js/2940.5fc76234.chunk.js +2 -0
  19. package/build/static/js/3033.f950a32b.chunk.js +2 -0
  20. package/build/static/js/3430.28c5bd04.chunk.js +2 -0
  21. package/build/static/js/3708.937e3176.chunk.js +3 -0
  22. package/build/static/js/{4023.5fe8180a.chunk.js → 4023.623d31df.chunk.js} +2 -2
  23. package/build/static/js/4461.2e764a6b.chunk.js +2 -0
  24. package/build/static/js/{4587.9a042d46.chunk.js → 4587.0d0fd3ae.chunk.js} +2 -2
  25. package/build/static/js/5070.267c5f25.chunk.js +2 -0
  26. package/build/static/js/5547.1bea60f5.chunk.js +3 -0
  27. package/build/static/js/{5683.051a03c1.chunk.js → 5683.b87f7ca2.chunk.js} +2 -2
  28. package/build/static/js/6032.406db269.chunk.js +2 -0
  29. package/build/static/js/716.8cd88d8c.chunk.js +3 -0
  30. package/build/static/js/7419.4eabaec0.chunk.js +2 -0
  31. package/build/static/js/779.21153e82.chunk.js +2 -0
  32. package/build/static/js/7858.b08b86e5.chunk.js +2 -0
  33. package/build/static/js/{8181.6c2a7dcb.chunk.js → 8181.bc510ea3.chunk.js} +2 -2
  34. package/build/static/js/8393.c6c4c762.chunk.js +2 -0
  35. package/build/static/js/{8622.6aa0a4d4.chunk.js → 8622.c87f0f5b.chunk.js} +2 -2
  36. package/build/static/js/{8944.939de854.chunk.js → 8944.d3a98445.chunk.js} +2 -2
  37. package/build/static/js/{8641.bec13444.chunk.js → 8983.6213f391.chunk.js} +2 -2
  38. package/build/static/js/9017.369db632.chunk.js +2 -0
  39. package/build/static/js/{9314.f0add972.chunk.js → 9314.2073578f.chunk.js} +2 -2
  40. package/build/static/js/main.43cea647.js +3 -0
  41. package/build/static/js/{main.6f748ae5.js.LICENSE.txt → main.43cea647.js.LICENSE.txt} +1 -1
  42. package/package.json +23 -23
  43. package/build/static/css/5547.e016de4c.chunk.css +0 -12
  44. package/build/static/js/2801.33d2f238.chunk.js +0 -2
  45. package/build/static/js/2838.ec459fda.chunk.js +0 -3
  46. package/build/static/js/2940.ce32ab3f.chunk.js +0 -2
  47. package/build/static/js/3033.9fe46d7f.chunk.js +0 -2
  48. package/build/static/js/3430.dc830483.chunk.js +0 -2
  49. package/build/static/js/3708.7e2ad66b.chunk.js +0 -3
  50. package/build/static/js/4461.f9a883d3.chunk.js +0 -2
  51. package/build/static/js/5070.31a74ba7.chunk.js +0 -2
  52. package/build/static/js/5434.523d071d.chunk.js +0 -2
  53. package/build/static/js/5547.42b98889.chunk.js +0 -3
  54. package/build/static/js/6032.1001afd3.chunk.js +0 -2
  55. package/build/static/js/716.e68425d7.chunk.js +0 -3
  56. package/build/static/js/779.73350f02.chunk.js +0 -2
  57. package/build/static/js/7858.52930f63.chunk.js +0 -2
  58. package/build/static/js/8393.ea7ef05d.chunk.js +0 -2
  59. package/build/static/js/840.5bc210dd.chunk.js +0 -2
  60. package/build/static/js/9017.56c20b17.chunk.js +0 -2
  61. package/build/static/js/main.6f748ae5.js +0 -3
  62. /package/build/static/js/{1233.276cf2d0.chunk.js.LICENSE.txt → 1233.43c6131b.chunk.js.LICENSE.txt} +0 -0
  63. /package/build/static/js/{2838.ec459fda.chunk.js.LICENSE.txt → 2838.bc235457.chunk.js.LICENSE.txt} +0 -0
  64. /package/build/static/js/{3708.7e2ad66b.chunk.js.LICENSE.txt → 3708.937e3176.chunk.js.LICENSE.txt} +0 -0
  65. /package/build/static/js/{5547.42b98889.chunk.js.LICENSE.txt → 5547.1bea60f5.chunk.js.LICENSE.txt} +0 -0
  66. /package/build/static/js/{716.e68425d7.chunk.js.LICENSE.txt → 716.8cd88d8c.chunk.js.LICENSE.txt} +0 -0
@@ -66,11 +66,7 @@ module.exports = (node, opts) => {
66
66
 
67
67
  const handlerOpts = {
68
68
  authenticator,
69
- tokenStorage: new DynamicStorage({
70
- dbPath: path.join(opts.dataDir, 'connections.db'),
71
- model: 'Connection',
72
- primaryKey: 'token',
73
- }),
69
+ tokenStorage: new DynamicStorage({ dbPath: path.join(opts.dataDir, 'connections.db') }),
74
70
  sendNotificationFn: async (connectedDid, message, { req }) => {
75
71
  const { wallet } = await req.getBlockletInfo();
76
72
  return sendToUser(
@@ -22,11 +22,7 @@ module.exports = (node, opts) => {
22
22
  const handlers = createHandlers({
23
23
  logger,
24
24
  authenticator,
25
- storage: new DynamicStorage({
26
- dbPath: path.join(opts.dataDir, 'connections.db'),
27
- model: 'ConnectionV2',
28
- primaryKey: 'sessionId',
29
- }),
25
+ storage: new DynamicStorage({ dbPath: path.join(opts.dataDir, 'connections.db'), v2: true }),
30
26
  socketPathname: `${WELLKNOWN_SERVICE_PATH_PREFIX}/api/connect/relay/websocket`,
31
27
  sendNotificationFn: async (connectedDid, message, { request }) => {
32
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
@@ -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];
@@ -75,6 +75,7 @@ async function login(req, node, options) {
75
75
  throw new ApiError(400, t('oauthCantBeOwner', locale));
76
76
  }
77
77
  const { did: teamDid, wallet: blockletWallet, secret, appUrl } = await req.getBlockletInfo();
78
+
78
79
  let userWallet;
79
80
  let oauthInfo;
80
81
 
@@ -94,6 +95,7 @@ async function login(req, node, options) {
94
95
  oauthInfo = await authClient.getProfile(token);
95
96
  userWallet = fromAppDid(oauthInfo.sub, blockletWallet.secretKey);
96
97
  }
98
+
97
99
  const userDid = userWallet.address;
98
100
  const userPk = userWallet.publicKey;
99
101
 
@@ -275,6 +277,8 @@ async function invite(req, node, options) {
275
277
  let userWallet;
276
278
  let oauthInfo;
277
279
 
280
+ const { did: teamDid, wallet: blockletWallet, secret } = await req.getBlockletInfo();
281
+
278
282
  // NOTICE: 如果是统一登录,则向 master 站点发起 oauth 登录请求,auth0 的账户信息必须由 master 来生成
279
283
  if (sourceAppPid) {
280
284
  const data = await getOAuthUserInfo({
@@ -292,7 +296,7 @@ async function invite(req, node, options) {
292
296
  oauthInfo = await authClient.getProfile(token);
293
297
  userWallet = fromAppDid(oauthInfo.sub, blockletWallet.secretKey);
294
298
  }
295
- const { did: teamDid, wallet: blockletWallet, secret } = await req.getBlockletInfo();
299
+
296
300
  const nodeInfo = await req.getNodeInfo();
297
301
  let userDid = userWallet.address;
298
302
  let userPk = userWallet.publicKey;
@@ -7,9 +7,9 @@ const createTranslator = require('@abtnode/util/lib/translate');
7
7
  const logger = require('@abtnode/logger')('blocklet-services:user');
8
8
  const { isFromPublicKey } = require('@arcblock/did');
9
9
  const { LOGIN_PROVIDER } = require('@blocklet/constant');
10
- const { fromPublicKey } = require('@ocap/wallet');
11
10
  const sortBy = require('lodash/sortBy');
12
11
  const head = require('lodash/head');
12
+ const { verify } = require('@arcblock/jwt');
13
13
 
14
14
  const { isInvitedUserOnly, createTokenFn, getDidConnectVersion } = require('../util');
15
15
  const initJwt = require('../libs/jwt');
@@ -19,6 +19,7 @@ const { loginWalletSchema, loginOAuthSchema, loginUserWalletSchema } = require('
19
19
  const verifySig = require('../middlewares/verify-sig');
20
20
  const { getAvatarByEmail } = require('../libs/auth/utils');
21
21
  const ensureBlocklet = require('../middlewares/ensure-blocklet');
22
+ const { getUserWithinFederated } = require('../util/federated');
22
23
 
23
24
  const validateUser = (user) => {
24
25
  try {
@@ -38,12 +39,14 @@ const translations = {
38
39
  notAllowed: '你没有权限登录该节点',
39
40
  needComponentId: '缺少登录参数: componentId',
40
41
  userInfoError: '登录用户信息有误',
42
+ notExist: '用户不存在',
41
43
  },
42
44
  en: {
43
45
  needInviteToLogin: 'You need to be invited to sign in to this app',
44
46
  notAllowed: 'You are not allowed to login to this node',
45
47
  needComponentId: 'componentId is required when login user',
46
48
  userInfoError: 'Login user info is invalid',
49
+ notExist: 'User is not exist',
47
50
  },
48
51
  };
49
52
 
@@ -57,7 +60,13 @@ async function checkNeedInvite({ req, node, teamDid, componentId, locale }) {
57
60
  }
58
61
  }
59
62
 
60
- function checkUserEnable(user, { locale }) {
63
+ function ensureUserExist(user, { locale }) {
64
+ if (!user) {
65
+ throw new ApiError(404, t('notExist', locale));
66
+ }
67
+ }
68
+
69
+ function ensureUserEnable(user, { locale }) {
61
70
  if (!user.approved) {
62
71
  throw new ApiError(403, t('notAllowed', locale));
63
72
  }
@@ -130,7 +139,7 @@ async function loginWallet(
130
139
  let profile = {};
131
140
  if (currentUser) {
132
141
  if (updateInfo) {
133
- await checkUserEnable(currentUser, { locale });
142
+ ensureUserEnable(currentUser, { locale });
134
143
  profile = await composeProfileData({ avatar, email, fullName }, { node, req, teamDid });
135
144
  }
136
145
  } else {
@@ -192,7 +201,7 @@ async function loginOAuth(
192
201
  let profile = {};
193
202
  if (currentUser) {
194
203
  if (updateInfo) {
195
- await checkUserEnable(currentUser, { locale });
204
+ ensureUserEnable(currentUser, { locale });
196
205
  profile = await composeProfileData({ avatar, email, fullName }, { node, req, teamDid });
197
206
  }
198
207
  } else {
@@ -313,21 +322,12 @@ async function login(req, node, options) {
313
322
  };
314
323
  }
315
324
 
316
- async function verifyUserSig({ userDid, signature, teamDid, data }, { node }) {
317
- const currentUser = await node.getUser({
318
- teamDid,
319
- user: {
320
- did: userDid,
321
- },
322
- options: {
323
- enableConnectedAccount: true,
324
- },
325
- });
326
- if (!currentUser) {
327
- throw new Error('user is not exist');
328
- }
329
- const userWallet = fromPublicKey(currentUser.pk);
330
- const valid = userWallet.verify(data, signature);
325
+ async function verifyUserSig({ userDid, signature, teamDid, sourceAppPid, userPk }, { node, locale, blocklet }) {
326
+ const currentUser = await getUserWithinFederated({ sourceAppPid, teamDid, userDid, userPk }, { node, blocklet });
327
+ ensureUserExist(currentUser, { locale });
328
+ ensureUserEnable(currentUser, { locale });
329
+
330
+ const valid = verify(signature, currentUser.pk);
331
331
  if (!valid) {
332
332
  throw new Error('invalid signature');
333
333
  }
@@ -357,31 +357,48 @@ module.exports = {
357
357
  * @summary 暂时不允许用户注册,只允许登录
358
358
  */
359
359
  server.post(`${prefixApi}/loginByWallet`, async (req, res) => {
360
- const { userDid, signature, walletOS, nonce, visitorId, passportId, sourceAppPid = null, locale } = req.body;
360
+ const {
361
+ userDid,
362
+ userPk,
363
+ signature,
364
+ walletOS,
365
+ challenge,
366
+ visitorId,
367
+ passportId,
368
+ sourceAppPid = null,
369
+ locale,
370
+ componentId,
371
+ } = req.body;
361
372
  const { error } = loginUserWalletSchema.validate({
362
373
  userDid,
374
+ userPk,
363
375
  signature,
364
376
  walletOS,
365
- nonce,
377
+ challenge,
366
378
  visitorId,
367
379
  passportId,
368
380
  sourceAppPid,
369
381
  locale,
382
+ componentId,
370
383
  });
371
384
  if (error) {
372
385
  throw new ApiError(400, error.message);
373
386
  }
387
+ // FIXME: @zhanghan 需要根据 componentId 来判断当前 component 设置的访问权限,来看当前要登录的用户是否有登录的权限
388
+ const blocklet = await req.getBlocklet();
374
389
  const { did: teamDid, secret } = await req.getBlockletInfo();
375
- const currentUser = await verifyUserSig({ userDid, signature, teamDid, data: nonce }, { node });
390
+ const currentUser = await verifyUserSig(
391
+ { userDid, signature, teamDid, sourceAppPid, userPk },
392
+ { node, locale, blocklet }
393
+ );
376
394
  const { createSessionToken } = initJwt(node, options);
377
395
  const createToken = createTokenFn(createSessionToken);
378
- const blocklet = await req.getBlocklet();
379
396
  const sessionConfig = blocklet.settings?.session || {};
380
397
  const passports = currentUser?.passports || [];
381
398
 
382
399
  let passport = null;
383
400
  if (passportId) {
384
- passport = passports.find((x) => x.passportId === passportId);
401
+ passport = passports.find((x) => x.status === 'valid' && x.passportId === passportId);
385
402
  }
386
403
  if (!passport) {
387
404
  const now = new Date().getTime();
@@ -452,7 +469,7 @@ module.exports = {
452
469
  res.json({
453
470
  sessionToken,
454
471
  refreshToken,
455
- nonce,
472
+ challenge,
456
473
  visitorId: userSession.visitorId,
457
474
  });
458
475
  });
@@ -20,13 +20,15 @@ const loginOAuthSchema = Joi.object({
20
20
 
21
21
  const loginUserWalletSchema = Joi.object({
22
22
  userDid: Joi.DID().trim().required(),
23
+ userPk: Joi.string().required(),
23
24
  signature: Joi.string().required(),
24
25
  walletOS: Joi.string().required(),
25
- nonce: Joi.string().required(),
26
+ challenge: Joi.string().required(),
26
27
  visitorId: Joi.string().optional(),
27
28
  passportId: Joi.string().optional(),
28
29
  sourceAppPid: Joi.DID().trim().empty(null),
29
30
  locale: Joi.string().optional(),
31
+ componentId: Joi.string().required(),
30
32
  }).empty(null);
31
33
 
32
34
  exports.loginWalletSchema = loginWalletSchema;