@abtnode/blocklet-services 1.16.18-beta-7aa6be5a → 1.16.18-beta-bb4ebacc
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 -7
- package/api/libs/connect/session.js +45 -33
- package/api/middlewares/check-running.js +1 -1
- package/api/routes/blocklet.js +18 -0
- package/api/routes/env.js +4 -2
- package/api/routes/federated.js +10 -9
- package/api/routes/oauth.js +79 -60
- package/api/routes/user-session.js +23 -16
- package/api/routes/user.js +160 -22
- package/api/services/auth/connect/invite.js +21 -12
- package/api/services/auth/index.js +77 -13
- package/api/services/auth/session.js +48 -1
- package/api/services/notification/blocklet-events-notifier.js +1 -0
- package/api/socket/channel/component.js +6 -5
- package/api/socket/channel/did.js +51 -20
- package/api/socket/util.js +1 -0
- package/api/util/federated.js +13 -12
- package/api/validators/login.js +12 -0
- package/build/asset-manifest.json +144 -112
- 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/{807.65f26433.chunk.css → 1233.dba37e64.chunk.css} +1 -1
- package/build/static/js/1.138935fc.chunk.js +2 -0
- package/build/static/js/{1148.4b2a7347.chunk.js → 1148.5ccba08e.chunk.js} +2 -2
- package/build/static/js/1233.276cf2d0.chunk.js +3 -0
- package/build/static/js/1233.276cf2d0.chunk.js.LICENSE.txt +23 -0
- package/build/static/js/{2291.7ffdc7df.chunk.js → 2291.37b0ad3a.chunk.js} +2 -2
- package/build/static/js/2653.980158ea.chunk.js +2 -0
- package/build/static/js/2664.da443f31.chunk.js +2 -0
- package/build/static/js/2801.33d2f238.chunk.js +2 -0
- package/build/static/js/{2838.fcdea2fa.chunk.js → 2838.ec459fda.chunk.js} +3 -3
- package/build/static/js/2940.ce32ab3f.chunk.js +2 -0
- package/build/static/js/3025.7c99c058.chunk.js +2 -0
- package/build/static/js/{3033.6623d19d.chunk.js → 3033.9fe46d7f.chunk.js} +2 -2
- package/build/static/js/3076.28658ffc.chunk.js +2 -0
- package/build/static/js/{3131.672170fb.chunk.js → 3131.99541aca.chunk.js} +2 -2
- package/build/static/js/3430.dc830483.chunk.js +2 -0
- package/build/static/js/{3688.48e7b69c.chunk.js → 3688.679796d1.chunk.js} +2 -2
- package/build/static/js/3708.7e2ad66b.chunk.js +3 -0
- package/build/static/js/3842.e1bb702b.chunk.js +3 -0
- package/build/static/js/3953.ddf8be86.chunk.js +2 -0
- package/build/static/js/3979.f9a5aa48.chunk.js +3 -0
- package/build/static/js/{807.249d2d01.chunk.js.LICENSE.txt → 3979.f9a5aa48.chunk.js.LICENSE.txt} +0 -18
- package/build/static/js/3994.e53e7ce2.chunk.js +2 -0
- package/build/static/js/4009.65d1c083.chunk.js +2 -0
- package/build/static/js/{4023.1658794f.chunk.js → 4023.5fe8180a.chunk.js} +2 -2
- package/build/static/js/4076.5ca97365.chunk.js +2 -0
- package/build/static/js/4160.e45b5ba1.chunk.js +2 -0
- package/build/static/js/{4319.8d9e189e.chunk.js → 4319.9f120992.chunk.js} +2 -2
- package/build/static/js/4461.26253c6a.chunk.js +2 -0
- package/build/static/js/4587.9a042d46.chunk.js +2 -0
- package/build/static/js/4802.217e3956.chunk.js +2 -0
- package/build/static/js/4882.0435e418.chunk.js +2 -0
- package/build/static/js/5070.31a74ba7.chunk.js +2 -0
- package/build/static/js/5468.b54ce65b.chunk.js +2 -0
- package/build/static/js/5547.d68bfde3.chunk.js +3 -0
- package/build/static/js/556.e1875260.chunk.js +3 -0
- package/build/static/js/{5683.47011724.chunk.js → 5683.051a03c1.chunk.js} +2 -2
- package/build/static/js/6032.1001afd3.chunk.js +2 -0
- package/build/static/js/6139.161b2e7f.chunk.js +2 -0
- package/build/static/js/6218.9a4feced.chunk.js +2 -0
- package/build/static/js/655.cdee65b4.chunk.js +2 -0
- package/build/static/js/{6658.090d923f.chunk.js → 6658.98f9956d.chunk.js} +2 -2
- package/build/static/js/{6771.a1510398.chunk.js → 6771.920fa5b6.chunk.js} +3 -3
- package/build/static/js/7006.bb90b167.chunk.js +2 -0
- package/build/static/js/{716.9c76ad65.chunk.js → 716.e68425d7.chunk.js} +3 -3
- package/build/static/js/716.e68425d7.chunk.js.LICENSE.txt +5 -0
- package/build/static/js/7305.465df4a1.chunk.js +2 -0
- package/build/static/js/7313.b53b59d8.chunk.js +2 -0
- package/build/static/js/779.73350f02.chunk.js +2 -0
- package/build/static/js/{7832.741ac84f.chunk.js → 7832.81aaf011.chunk.js} +2 -2
- package/build/static/js/7858.52930f63.chunk.js +2 -0
- package/build/static/js/8016.3ef64a2c.chunk.js +2 -0
- package/build/static/js/8181.6c2a7dcb.chunk.js +2 -0
- package/build/static/js/8393.ea7ef05d.chunk.js +2 -0
- package/build/static/js/840.5bc210dd.chunk.js +2 -0
- package/build/static/js/8604.ece06cf7.chunk.js +3 -0
- package/build/static/js/{8622.f9639db9.chunk.js → 8622.ebd44812.chunk.js} +2 -2
- package/build/static/js/{8641.506d61d5.chunk.js → 8641.bec13444.chunk.js} +2 -2
- package/build/static/js/8702.bb84d009.chunk.js +2 -0
- package/build/static/js/{8792.8b009f81.chunk.js → 8792.0f55707c.chunk.js} +2 -2
- package/build/static/js/8944.559ade67.chunk.js +2 -0
- package/build/static/js/9017.56c20b17.chunk.js +2 -0
- package/build/static/js/9033.ebf65926.chunk.js +2 -0
- package/build/static/js/9107.7f32925d.chunk.js +2 -0
- package/build/static/js/9314.f0add972.chunk.js +2 -0
- package/build/static/js/9582.2c539fcf.chunk.js +2 -0
- package/build/static/js/{9596.5251398b.chunk.js → 9596.a4eead04.chunk.js} +2 -2
- package/build/static/js/9865.d5dfa72b.chunk.js +2 -0
- package/build/static/js/{9900.cce3f786.chunk.js → 9900.3b72165e.chunk.js} +2 -2
- package/build/static/js/9996.5545ea26.chunk.js +2 -0
- package/build/static/js/main.36b0deea.js +3 -0
- package/build/static/js/{main.50d5cec4.js.LICENSE.txt → main.36b0deea.js.LICENSE.txt} +1 -1
- package/package.json +38 -37
- package/build/static/js/1431.29b0bc0a.chunk.js +0 -3
- package/build/static/js/2093.3ce9b59d.chunk.js +0 -3
- package/build/static/js/2653.6610534f.chunk.js +0 -2
- package/build/static/js/2686.890b32ed.chunk.js +0 -2
- package/build/static/js/2972.2d5268c3.chunk.js +0 -2
- package/build/static/js/3025.a48c96f5.chunk.js +0 -2
- package/build/static/js/3038.40d611d1.chunk.js +0 -2
- package/build/static/js/3490.4d3a51ea.chunk.js +0 -2
- package/build/static/js/4022.5ec79b56.chunk.js +0 -2
- package/build/static/js/4076.bcc72fcc.chunk.js +0 -2
- package/build/static/js/4461.f412553b.chunk.js +0 -2
- package/build/static/js/4587.906e3023.chunk.js +0 -2
- package/build/static/js/4716.a8a5b75d.chunk.js +0 -2
- package/build/static/js/4764.51208c0d.chunk.js +0 -2
- package/build/static/js/4802.fe56c787.chunk.js +0 -2
- package/build/static/js/5465.dd0f7a20.chunk.js +0 -2
- package/build/static/js/5547.5a341867.chunk.js +0 -3
- package/build/static/js/5662.03f06ae0.chunk.js +0 -2
- package/build/static/js/5673.0dd67df6.chunk.js +0 -2
- package/build/static/js/5960.35db200d.chunk.js +0 -2
- package/build/static/js/6139.cabc22a2.chunk.js +0 -2
- package/build/static/js/6498.698608f8.chunk.js +0 -2
- package/build/static/js/6868.881bd141.chunk.js +0 -2
- package/build/static/js/7006.8f0645e5.chunk.js +0 -2
- package/build/static/js/7050.3281ad5f.chunk.js +0 -2
- package/build/static/js/7367.15383006.chunk.js +0 -2
- package/build/static/js/779.b64e7db1.chunk.js +0 -2
- package/build/static/js/7858.e4167dfd.chunk.js +0 -2
- package/build/static/js/807.249d2d01.chunk.js +0 -3
- package/build/static/js/8604.a8ef404e.chunk.js +0 -3
- package/build/static/js/8702.2d858942.chunk.js +0 -2
- package/build/static/js/8944.2d60df14.chunk.js +0 -2
- package/build/static/js/main.50d5cec4.js +0 -3
- /package/build/static/js/{2838.fcdea2fa.chunk.js.LICENSE.txt → 2838.ec459fda.chunk.js.LICENSE.txt} +0 -0
- /package/build/static/js/{2093.3ce9b59d.chunk.js.LICENSE.txt → 3708.7e2ad66b.chunk.js.LICENSE.txt} +0 -0
- /package/build/static/js/{716.9c76ad65.chunk.js.LICENSE.txt → 3842.e1bb702b.chunk.js.LICENSE.txt} +0 -0
- /package/build/static/js/{5547.5a341867.chunk.js.LICENSE.txt → 5547.d68bfde3.chunk.js.LICENSE.txt} +0 -0
- /package/build/static/js/{1431.29b0bc0a.chunk.js.LICENSE.txt → 556.e1875260.chunk.js.LICENSE.txt} +0 -0
- /package/build/static/js/{6771.a1510398.chunk.js.LICENSE.txt → 6771.920fa5b6.chunk.js.LICENSE.txt} +0 -0
- /package/build/static/js/{8604.a8ef404e.chunk.js.LICENSE.txt → 8604.ece06cf7.chunk.js.LICENSE.txt} +0 -0
package/api/routes/user.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { NODE_SERVICES, WELLKNOWN_SERVICE_PATH_PREFIX } = require('@abtnode/constant');
|
|
2
|
-
const { getApplicationInfo
|
|
2
|
+
const { getApplicationInfo } = require('@abtnode/auth/lib/auth');
|
|
3
3
|
const { fromAppDid } = require('@arcblock/did-ext');
|
|
4
4
|
const { extractUserAvatar } = require('@abtnode/util/lib/user');
|
|
5
5
|
const formatContext = require('@abtnode/util/lib/format-context');
|
|
@@ -7,12 +7,15 @@ 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
|
+
const sortBy = require('lodash/sortBy');
|
|
12
|
+
const head = require('lodash/head');
|
|
10
13
|
|
|
11
14
|
const { isInvitedUserOnly, createTokenFn, getDidConnectVersion } = require('../util');
|
|
12
15
|
const initJwt = require('../libs/jwt');
|
|
13
16
|
const { getAvatarByUrl } = require('../libs/auth/utils');
|
|
14
17
|
const { ApiError } = require('../util/error');
|
|
15
|
-
const { loginWalletSchema, loginOAuthSchema } = require('../validators/login');
|
|
18
|
+
const { loginWalletSchema, loginOAuthSchema, loginUserWalletSchema } = require('../validators/login');
|
|
16
19
|
const verifySig = require('../middlewares/verify-sig');
|
|
17
20
|
const { getAvatarByEmail } = require('../libs/auth/utils');
|
|
18
21
|
const ensureBlocklet = require('../middlewares/ensure-blocklet');
|
|
@@ -213,7 +216,6 @@ async function loginOAuth(
|
|
|
213
216
|
if (!validateUser(loginParams.user) || !validateUser(loginParams.user.connectedAccount)) {
|
|
214
217
|
throw new ApiError(400, t('userInfoError', locale));
|
|
215
218
|
}
|
|
216
|
-
// TODO: @zhanghan 暂不支持统一登录模式登录
|
|
217
219
|
const doc = await node.loginUser(loginParams);
|
|
218
220
|
return doc;
|
|
219
221
|
}
|
|
@@ -240,14 +242,14 @@ async function login(req, node, options) {
|
|
|
240
242
|
const { did: teamDid, wallet: blockletWallet, secret } = await req.getBlockletInfo();
|
|
241
243
|
const blocklet = await req.getBlocklet();
|
|
242
244
|
|
|
243
|
-
let
|
|
245
|
+
let userDoc;
|
|
244
246
|
if (provider === LOGIN_PROVIDER.WALLET) {
|
|
245
|
-
|
|
247
|
+
userDoc = await loginWallet(
|
|
246
248
|
{ did, pk, avatar, email, fullName },
|
|
247
249
|
{ node, req, locale, componentId, teamDid, updateInfo, sourceAppPid }
|
|
248
250
|
);
|
|
249
251
|
} else {
|
|
250
|
-
|
|
252
|
+
userDoc = await loginOAuth(
|
|
251
253
|
{ provider, id, avatar, email, fullName },
|
|
252
254
|
{ node, req, locale, componentId, teamDid, blockletWallet, updateInfo, sourceAppPid }
|
|
253
255
|
);
|
|
@@ -259,9 +261,9 @@ async function login(req, node, options) {
|
|
|
259
261
|
await node.createAuditLog(
|
|
260
262
|
{
|
|
261
263
|
action: 'login',
|
|
262
|
-
args: { teamDid, userDid:
|
|
263
|
-
context: formatContext(Object.assign(req, { user:
|
|
264
|
-
result:
|
|
264
|
+
args: { teamDid, userDid: userDoc.did, passport, provider },
|
|
265
|
+
context: formatContext(Object.assign(req, { user: userDoc })),
|
|
266
|
+
result: userDoc,
|
|
265
267
|
},
|
|
266
268
|
node
|
|
267
269
|
);
|
|
@@ -271,35 +273,66 @@ async function login(req, node, options) {
|
|
|
271
273
|
const createToken = createTokenFn(createSessionToken);
|
|
272
274
|
const sessionConfig = blocklet.settings?.session || {};
|
|
273
275
|
const { sessionToken, refreshToken } = createToken(
|
|
274
|
-
|
|
276
|
+
userDoc.did,
|
|
275
277
|
{
|
|
276
278
|
secret,
|
|
277
279
|
passport,
|
|
278
280
|
role: passport.role,
|
|
279
|
-
fullName:
|
|
281
|
+
fullName: userDoc.fullName,
|
|
280
282
|
provider,
|
|
281
283
|
walletOS: 'api',
|
|
282
284
|
},
|
|
283
285
|
{ ...sessionConfig, didConnectVersion: getDidConnectVersion(req) }
|
|
284
286
|
);
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
287
|
+
const lastLoginIp = req.headers['x-real-ip'];
|
|
288
|
+
const ua = req.headers['user-agent'];
|
|
289
|
+
const userSession = await node.upsertUserSession({
|
|
290
|
+
visitorId,
|
|
291
|
+
teamDid,
|
|
292
|
+
userDid: userDoc.did,
|
|
293
|
+
appPid: teamDid,
|
|
294
|
+
status: 'online',
|
|
295
|
+
ua,
|
|
296
|
+
lastLoginIp,
|
|
297
|
+
});
|
|
298
|
+
node.syncUserSession({
|
|
299
|
+
teamDid,
|
|
300
|
+
userDid: userDoc.did,
|
|
301
|
+
visitorId: userSession.visitorId,
|
|
302
|
+
passportId: passport?.id,
|
|
303
|
+
targetAppPid: sourceAppPid,
|
|
304
|
+
ua,
|
|
305
|
+
lastLoginIp,
|
|
306
|
+
});
|
|
295
307
|
return {
|
|
296
|
-
user:
|
|
308
|
+
user: userDoc,
|
|
297
309
|
token: sessionToken,
|
|
298
310
|
refreshToken,
|
|
299
311
|
visitorId: userSession?.visitorId || '',
|
|
300
312
|
};
|
|
301
313
|
}
|
|
302
314
|
|
|
315
|
+
async function verifyUserSig({ userDid, signature, teamDid, data }, { node }) {
|
|
316
|
+
const currentUser = await node.getUser({
|
|
317
|
+
teamDid,
|
|
318
|
+
user: {
|
|
319
|
+
did: userDid,
|
|
320
|
+
},
|
|
321
|
+
options: {
|
|
322
|
+
enableConnectedAccount: true,
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
if (!currentUser) {
|
|
326
|
+
throw new Error('user is not exist');
|
|
327
|
+
}
|
|
328
|
+
const userWallet = fromPublicKey(currentUser.pk);
|
|
329
|
+
const valid = userWallet.verify(data, signature);
|
|
330
|
+
if (!valid) {
|
|
331
|
+
throw new Error('invalid signature');
|
|
332
|
+
}
|
|
333
|
+
return currentUser;
|
|
334
|
+
}
|
|
335
|
+
|
|
303
336
|
module.exports = {
|
|
304
337
|
init(server, node, options) {
|
|
305
338
|
async function loginFn(req, res) {
|
|
@@ -318,6 +351,111 @@ module.exports = {
|
|
|
318
351
|
server.post(`${prefix}/login`, verifySig, loginFn);
|
|
319
352
|
server.post(`${prefixApi}/login`, verifySig, loginFn);
|
|
320
353
|
|
|
354
|
+
/**
|
|
355
|
+
* @description 用于在 native 钱包中完成用户的自动登录
|
|
356
|
+
* @summary 暂时不允许用户注册,只允许登录
|
|
357
|
+
*/
|
|
358
|
+
server.post(`${prefixApi}/loginByWallet`, async (req, res) => {
|
|
359
|
+
const { userDid, signature, walletOS, nonce, visitorId, passportId, sourceAppPid, locale } = req.body;
|
|
360
|
+
const { error } = loginUserWalletSchema.validate({
|
|
361
|
+
userDid,
|
|
362
|
+
signature,
|
|
363
|
+
walletOS,
|
|
364
|
+
nonce,
|
|
365
|
+
visitorId,
|
|
366
|
+
passportId,
|
|
367
|
+
sourceAppPid,
|
|
368
|
+
locale,
|
|
369
|
+
});
|
|
370
|
+
if (error) {
|
|
371
|
+
throw new ApiError(400, error.message);
|
|
372
|
+
}
|
|
373
|
+
const { did: teamDid, secret } = await req.getBlockletInfo();
|
|
374
|
+
const currentUser = await verifyUserSig({ userDid, signature, teamDid, data: nonce }, { node });
|
|
375
|
+
const { createSessionToken } = initJwt(node, options);
|
|
376
|
+
const createToken = createTokenFn(createSessionToken);
|
|
377
|
+
const blocklet = await req.getBlocklet();
|
|
378
|
+
const sessionConfig = blocklet.settings?.session || {};
|
|
379
|
+
const passports = currentUser?.passports || [];
|
|
380
|
+
|
|
381
|
+
let passport = null;
|
|
382
|
+
if (passportId) {
|
|
383
|
+
passport = passports.find((x) => x.passportId === passportId);
|
|
384
|
+
}
|
|
385
|
+
if (!passport) {
|
|
386
|
+
const now = new Date().getTime();
|
|
387
|
+
passport = head(
|
|
388
|
+
sortBy(passports, (x) => {
|
|
389
|
+
const lastLoginAt = new Date(x.lastLoginAt).getTime();
|
|
390
|
+
if (typeof lastLoginAt === 'number') {
|
|
391
|
+
return now - lastLoginAt;
|
|
392
|
+
}
|
|
393
|
+
return now;
|
|
394
|
+
})
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (!passport) {
|
|
399
|
+
passport = { name: 'Guest', role: 'guest' };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const lastLoginIp = req.get('x-real-ip');
|
|
403
|
+
const loginedUser = await node.loginUser({
|
|
404
|
+
teamDid,
|
|
405
|
+
user: {
|
|
406
|
+
did: currentUser.did,
|
|
407
|
+
pk: currentUser.pk,
|
|
408
|
+
locale,
|
|
409
|
+
lastLoginIp,
|
|
410
|
+
sourceAppPid,
|
|
411
|
+
connectedAccount: {
|
|
412
|
+
provider: LOGIN_PROVIDER.WALLET,
|
|
413
|
+
did: currentUser.did,
|
|
414
|
+
pk: currentUser.pk,
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
const { sessionToken, refreshToken } = createToken(
|
|
420
|
+
loginedUser.did,
|
|
421
|
+
{
|
|
422
|
+
secret,
|
|
423
|
+
passport,
|
|
424
|
+
role: passport?.role || 'guest',
|
|
425
|
+
fullName: loginedUser.fullName,
|
|
426
|
+
provider: 'wallet',
|
|
427
|
+
walletOS,
|
|
428
|
+
},
|
|
429
|
+
{ ...sessionConfig, didConnectVersion: getDidConnectVersion(req) }
|
|
430
|
+
);
|
|
431
|
+
const ua = req.headers['user-agent'];
|
|
432
|
+
const userSession = await node.upsertUserSession({
|
|
433
|
+
visitorId,
|
|
434
|
+
teamDid,
|
|
435
|
+
userDid: loginedUser.did,
|
|
436
|
+
appPid: teamDid,
|
|
437
|
+
status: 'online',
|
|
438
|
+
ua,
|
|
439
|
+
lastLoginIp,
|
|
440
|
+
});
|
|
441
|
+
node.syncUserSession({
|
|
442
|
+
teamDid,
|
|
443
|
+
userDid,
|
|
444
|
+
visitorId: userSession.visitorId,
|
|
445
|
+
passportId,
|
|
446
|
+
targetAppPid: sourceAppPid,
|
|
447
|
+
ua,
|
|
448
|
+
lastLoginIp,
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
res.json({
|
|
452
|
+
sessionToken,
|
|
453
|
+
refreshToken,
|
|
454
|
+
nonce,
|
|
455
|
+
visitorId: userSession.visitorId,
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
|
|
321
459
|
server.post(`${prefixApi}/logout`, ensureBlocklet(), async (req, res) => {
|
|
322
460
|
if (req.user) {
|
|
323
461
|
const { blocklet } = req;
|
|
@@ -6,7 +6,6 @@ const {
|
|
|
6
6
|
messages,
|
|
7
7
|
checkWalletVersion,
|
|
8
8
|
beforeInvitationRequest,
|
|
9
|
-
upsertUserSession,
|
|
10
9
|
} = require('@abtnode/auth/lib/auth');
|
|
11
10
|
const { getSourceAppPid } = require('@blocklet/sdk/lib/util/login');
|
|
12
11
|
const { NODE_SERVICES, WELLKNOWN_SERVICE_PATH_PREFIX } = require('@abtnode/constant');
|
|
@@ -98,17 +97,27 @@ module.exports = function createRoutes(node, authenticator, createSessionToken)
|
|
|
98
97
|
{ ...sessionConfig, didConnectVersion: getDidConnectVersion(req) }
|
|
99
98
|
);
|
|
100
99
|
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
100
|
+
const lastLoginIp = req.headers['x-real-ip'];
|
|
101
|
+
const ua = req.headers['user-agent'];
|
|
102
|
+
const userSessionDoc = await node.upsertUserSession({
|
|
103
|
+
teamDid,
|
|
104
|
+
visitorId,
|
|
105
|
+
userDid,
|
|
106
|
+
appPid: teamDid,
|
|
107
|
+
passportId: response.data.id,
|
|
108
|
+
status: 'online',
|
|
109
|
+
ua,
|
|
110
|
+
lastLoginIp,
|
|
111
|
+
});
|
|
112
|
+
node.syncUserSession({
|
|
113
|
+
teamDid,
|
|
114
|
+
userDid,
|
|
115
|
+
visitorId: userSessionDoc.visitorId,
|
|
116
|
+
passportId: passport?.id,
|
|
117
|
+
targetAppPid: sourceAppPid,
|
|
118
|
+
ua,
|
|
119
|
+
lastLoginIp,
|
|
120
|
+
});
|
|
112
121
|
await updateSession(
|
|
113
122
|
{
|
|
114
123
|
sessionToken,
|
|
@@ -84,12 +84,16 @@ const init = ({ node, options }) => {
|
|
|
84
84
|
};
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
|
-
* @
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
87
|
+
* @typedef {object} CheckAuthRes
|
|
88
|
+
* @property {boolean} blocked
|
|
89
|
+
* @property {boolean} authenticated
|
|
90
|
+
* @property {boolean} authorized
|
|
91
|
+
* @property {boolean} ignored
|
|
92
|
+
* @property {string} payable
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @returns {CheckAuthRes} res
|
|
93
97
|
*/
|
|
94
98
|
const checkAuth = async ({ req } = {}) => {
|
|
95
99
|
const config = (await req.getServiceConfig(NODE_SERVICES.AUTH)) || {};
|
|
@@ -107,24 +111,74 @@ const init = ({ node, options }) => {
|
|
|
107
111
|
|
|
108
112
|
const teamDid = req.getBlockletDid();
|
|
109
113
|
|
|
114
|
+
// 所有人都可以访问的情况
|
|
110
115
|
if (!config.whoCanAccess || config.whoCanAccess === WHO_CAN_ACCESS.ALL) {
|
|
111
116
|
if (config.blockUnauthenticated && !req.user) {
|
|
112
|
-
return {
|
|
117
|
+
return {
|
|
118
|
+
blocked: true,
|
|
119
|
+
authenticated: false,
|
|
120
|
+
};
|
|
113
121
|
}
|
|
114
122
|
} else if (!req.user) {
|
|
123
|
+
// 需要被邀请才能访问的情况
|
|
115
124
|
const rbac = await node.getRBAC(teamDid);
|
|
116
125
|
const allRoles = await rbac.getRoles(teamDid);
|
|
117
126
|
const payableRole = allRoles.find((x) => x.extra?.acquire?.pay);
|
|
118
|
-
return {
|
|
127
|
+
return {
|
|
128
|
+
blocked: true,
|
|
129
|
+
authenticated: false,
|
|
130
|
+
payable: payableRole?.extra?.acquire?.pay,
|
|
131
|
+
};
|
|
119
132
|
} else if (config.whoCanAccess === WHO_CAN_ACCESS.OWNER && req.user.role !== ROLES.OWNER) {
|
|
120
|
-
|
|
133
|
+
// 需要 owner 才能访问
|
|
134
|
+
return {
|
|
135
|
+
blocked: true,
|
|
136
|
+
authenticated: true,
|
|
137
|
+
authorized: false,
|
|
138
|
+
requiredRoles: [
|
|
139
|
+
{
|
|
140
|
+
name: ROLES.OWNER,
|
|
141
|
+
title: 'Owner',
|
|
142
|
+
description: 'Owner',
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
};
|
|
121
146
|
} else if (config.whoCanAccess.startsWith(WHO_CAN_ACCESS_PREFIX_ROLES)) {
|
|
147
|
+
// 指定 passport 才能访问
|
|
148
|
+
// 需要用户拥有的权限
|
|
122
149
|
const roles = getRolesFromAuthConfig(config);
|
|
123
150
|
if (!roles.includes(req.user.role)) {
|
|
124
151
|
const rbac = await node.getRBAC(teamDid);
|
|
152
|
+
// 系统中的所有权限
|
|
125
153
|
const allRoles = await rbac.getRoles(teamDid);
|
|
126
|
-
|
|
127
|
-
return {
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
blocked: true,
|
|
157
|
+
authenticated: true,
|
|
158
|
+
authorized: false,
|
|
159
|
+
requiredRoles: roles
|
|
160
|
+
.map((item) => {
|
|
161
|
+
if (item.name === ROLES.OWNER) {
|
|
162
|
+
return {
|
|
163
|
+
name: item.name,
|
|
164
|
+
title: 'Owner',
|
|
165
|
+
description: 'Owner',
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const findRole = allRoles.find((x) => x.name === item);
|
|
169
|
+
if (!findRole) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
name: findRole.name,
|
|
175
|
+
title: findRole.title,
|
|
176
|
+
description: findRole.description,
|
|
177
|
+
payable: findRole?.extra?.acquire?.pay,
|
|
178
|
+
};
|
|
179
|
+
})
|
|
180
|
+
.filter((x) => x),
|
|
181
|
+
};
|
|
128
182
|
}
|
|
129
183
|
}
|
|
130
184
|
|
|
@@ -254,7 +308,7 @@ const init = ({ node, options }) => {
|
|
|
254
308
|
};
|
|
255
309
|
|
|
256
310
|
middlewares.checkAuth = async (req, res, next) => {
|
|
257
|
-
const { blocked, authenticated, authorized, payable } = await checkAuth({ req });
|
|
311
|
+
const { blocked, authenticated, authorized, payable, requiredRoles } = await checkAuth({ req });
|
|
258
312
|
|
|
259
313
|
if (blocked) {
|
|
260
314
|
if (!authenticated) {
|
|
@@ -269,7 +323,17 @@ const init = ({ node, options }) => {
|
|
|
269
323
|
|
|
270
324
|
if (!authorized) {
|
|
271
325
|
if (req.accepts(['html', 'json']) === 'html') {
|
|
272
|
-
res.redirect(
|
|
326
|
+
res.redirect(
|
|
327
|
+
getRedirectUrl({
|
|
328
|
+
req,
|
|
329
|
+
pagePath: '/login',
|
|
330
|
+
params: {
|
|
331
|
+
authenticated: 1,
|
|
332
|
+
payable,
|
|
333
|
+
requiredRoles: JSON.stringify(requiredRoles),
|
|
334
|
+
},
|
|
335
|
+
})
|
|
336
|
+
);
|
|
273
337
|
} else {
|
|
274
338
|
// Security principles: user should not known the reason
|
|
275
339
|
res.status(404).json({ code: 404, error: REASON_404 });
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
const nocache = require('nocache');
|
|
2
2
|
const joinUrl = require('url-join');
|
|
3
3
|
const pick = require('lodash/pick');
|
|
4
|
+
const omit = require('lodash/omit');
|
|
5
|
+
const merge = require('lodash/merge');
|
|
4
6
|
const {
|
|
5
7
|
WELLKNOWN_SERVICE_PATH_PREFIX,
|
|
6
8
|
USER_AVATAR_URL_PREFIX,
|
|
@@ -9,6 +11,7 @@ const {
|
|
|
9
11
|
} = require('@abtnode/constant');
|
|
10
12
|
const { LOGIN_PROVIDER } = require('@blocklet/constant');
|
|
11
13
|
|
|
14
|
+
const isUrl = require('is-url');
|
|
12
15
|
const { PREFIXES } = require('../../util/constants');
|
|
13
16
|
const { createTokenFn, getDidConnectVersion } = require('../../util');
|
|
14
17
|
|
|
@@ -57,7 +60,7 @@ module.exports = {
|
|
|
57
60
|
.map((x) => pick(x, ['id', 'name', 'title', 'role']));
|
|
58
61
|
|
|
59
62
|
res.json({
|
|
60
|
-
user,
|
|
63
|
+
user: omit(user, ['extra']),
|
|
61
64
|
provider: req.user.provider || LOGIN_PROVIDER.WALLET,
|
|
62
65
|
walletOS: req.user.walletOS,
|
|
63
66
|
});
|
|
@@ -67,6 +70,50 @@ module.exports = {
|
|
|
67
70
|
router.get(sessionApi, nocache(), sessionBearerToken, handleSession);
|
|
68
71
|
router.post(sessionApi, nocache(), sessionBearerToken, handleSession);
|
|
69
72
|
|
|
73
|
+
// update user extra: settings, webhooks
|
|
74
|
+
const extraApi = `${prefix}/api/user/extra`;
|
|
75
|
+
const checkUser = async (req, res, next) => {
|
|
76
|
+
const { token } = req;
|
|
77
|
+
await ensureUser({ req, token });
|
|
78
|
+
if (!req.user) {
|
|
79
|
+
res.status(403).json(null);
|
|
80
|
+
} else {
|
|
81
|
+
next();
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
router.get(extraApi, nocache(), sessionBearerToken, checkUser, async (req, res) => {
|
|
85
|
+
const teamDid = req.getBlockletDid();
|
|
86
|
+
const user = await node.getUser({ teamDid, user: { did: req.user.did } });
|
|
87
|
+
res.json(user.extra || {});
|
|
88
|
+
});
|
|
89
|
+
router.post(extraApi, nocache(), sessionBearerToken, checkUser, async (req, res) => {
|
|
90
|
+
const teamDid = req.getBlockletDid();
|
|
91
|
+
const exist = await node.getUser({ teamDid, user: { did: req.user.did } });
|
|
92
|
+
const user = await node.updateUserExtra({
|
|
93
|
+
teamDid,
|
|
94
|
+
did: req.user.did,
|
|
95
|
+
extra: JSON.stringify(merge({}, exist.extra || {}, req.body)),
|
|
96
|
+
});
|
|
97
|
+
res.json(user.extra);
|
|
98
|
+
});
|
|
99
|
+
router.put(extraApi, nocache(), sessionBearerToken, checkUser, async (req, res) => {
|
|
100
|
+
if (['slack', 'api'].includes(req.body.type) === false) {
|
|
101
|
+
res.status(400).send({ error: 'invalid webhook type' });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (isUrl(req.body.url) === false) {
|
|
106
|
+
res.status(400).send({ error: 'invalid webhook url' });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
await node.sendTestMessage({
|
|
110
|
+
webhook: { type: req.body.type, params: [{ name: 'url', value: req.body.url }] },
|
|
111
|
+
message: `This is a test message from user ${req.user.did}`,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
res.json({ success: true });
|
|
115
|
+
});
|
|
116
|
+
|
|
70
117
|
router.post(`${prefix}/api/did/refreshSession`, refreshBearerToken, async (req, res) => {
|
|
71
118
|
const token = req.refreshToken;
|
|
72
119
|
if (token) {
|
|
@@ -189,6 +189,7 @@ const init = ({ node, notificationService }) => {
|
|
|
189
189
|
notification: {
|
|
190
190
|
title: message.title[locale] || message.title[DEFAULT_LOCALE],
|
|
191
191
|
body: message.body[locale] || message.body[DEFAULT_LOCALE],
|
|
192
|
+
source: 'app',
|
|
192
193
|
},
|
|
193
194
|
};
|
|
194
195
|
notificationService.sendToUser.exec(input);
|
|
@@ -100,17 +100,18 @@ const sendToAppComponents = async ({ event, appDid, componentDid: inputComponent
|
|
|
100
100
|
const componentDid = component.meta.did;
|
|
101
101
|
|
|
102
102
|
if (!inputComponentDid || componentDid === inputComponentDid) {
|
|
103
|
-
//
|
|
104
|
-
|
|
103
|
+
// appPid is diff with appDid when app development mode
|
|
104
|
+
// appPid is diff with appDid when app has been rotated
|
|
105
|
+
const appPid = app.appPid || appDid;
|
|
105
106
|
|
|
106
107
|
// eslint-disable-next-line no-loop-func
|
|
107
|
-
broadcast(wsServer, getComponentChannel(
|
|
108
|
+
broadcast(wsServer, getComponentChannel(appPid, componentDid), event, notification, async (count) => {
|
|
108
109
|
// FIXME @linchen 组件以 cluster 模式启动时, 是否确保所有组件实例都收到消息?
|
|
109
110
|
if (count <= 0) {
|
|
110
|
-
logger.info('Online component client was not found', {
|
|
111
|
+
logger.info('Online component client was not found', { appPid, componentDid });
|
|
111
112
|
await lock.acquire();
|
|
112
113
|
try {
|
|
113
|
-
await states.message.insert({ did: getCacheId(
|
|
114
|
+
await states.message.insert({ did: getCacheId(appPid, componentDid), event, data: notification });
|
|
114
115
|
lock.release();
|
|
115
116
|
} catch (error) {
|
|
116
117
|
lock.release();
|
|
@@ -8,6 +8,9 @@ const { NODE_MODES } = require('@abtnode/constant');
|
|
|
8
8
|
const { getWalletDid } = require('@blocklet/sdk/lib/did');
|
|
9
9
|
const JWT = require('@arcblock/jwt');
|
|
10
10
|
const pMap = require('p-map');
|
|
11
|
+
const uniqBy = require('lodash/uniqBy');
|
|
12
|
+
const uniq = require('lodash/uniq');
|
|
13
|
+
const get = require('lodash/get');
|
|
11
14
|
|
|
12
15
|
// eslint-disable-next-line global-require
|
|
13
16
|
const logger = require('@abtnode/logger')(`${require('../../../package.json').name}:notification`);
|
|
@@ -34,21 +37,25 @@ const sendToUserDid = async ({ sender, receiver: rawDid, notification, options,
|
|
|
34
37
|
const { keepForOfflineUser = true } = options || {};
|
|
35
38
|
const receiver = Array.isArray(rawDid) ? rawDid : [rawDid];
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
let receiverDidList = [];
|
|
41
|
+
let receiverEmailList = [];
|
|
42
|
+
let webhookList = [];
|
|
43
|
+
|
|
44
|
+
const webhookSenders = new Map();
|
|
39
45
|
|
|
40
46
|
// sender.appDid 就是当前 blockletDid,通知的发送方就是这个 blocklet 本身
|
|
41
47
|
const teamDid = sender.appDid;
|
|
42
48
|
|
|
43
49
|
await pMap(receiver, async (item) => {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const receiverDid = getWalletDid(
|
|
50
|
-
const receiverEmail =
|
|
51
|
-
|
|
50
|
+
const user = await node.getUser({ teamDid, user: { did: item }, options: { enableConnectedAccount: true } });
|
|
51
|
+
const walletEnabled = get(user, 'extra.notifications.wallet', true);
|
|
52
|
+
const emailEnabled = get(user, 'extra.notifications.email', true);
|
|
53
|
+
const webhooks = get(user, 'extra.webhooks', []);
|
|
54
|
+
|
|
55
|
+
const receiverDid = getWalletDid(user) || item;
|
|
56
|
+
const receiverEmail = user?.email;
|
|
57
|
+
|
|
58
|
+
if (receiverDid && walletEnabled) {
|
|
52
59
|
try {
|
|
53
60
|
await validateReceiver(receiverDid);
|
|
54
61
|
receiverDidList.push(receiverDid);
|
|
@@ -56,17 +63,21 @@ const sendToUserDid = async ({ sender, receiver: rawDid, notification, options,
|
|
|
56
63
|
/* empty */
|
|
57
64
|
}
|
|
58
65
|
}
|
|
59
|
-
if (receiverEmail) {
|
|
66
|
+
if (receiverEmail && emailEnabled) {
|
|
60
67
|
try {
|
|
61
68
|
await validateEmail(receiverEmail);
|
|
62
69
|
receiverEmailList.push({
|
|
63
70
|
email: receiverEmail,
|
|
64
|
-
locale:
|
|
71
|
+
locale: user?.locale || 'en',
|
|
65
72
|
});
|
|
66
73
|
} catch {
|
|
67
74
|
/* empty */
|
|
68
75
|
}
|
|
69
76
|
}
|
|
77
|
+
|
|
78
|
+
if (Array.isArray(webhooks)) {
|
|
79
|
+
webhookList.push(...webhooks.filter((x) => x.type && x.url));
|
|
80
|
+
}
|
|
70
81
|
});
|
|
71
82
|
|
|
72
83
|
if (receiverDidList.length === 0 && receiverEmailList.length === 0) {
|
|
@@ -85,6 +96,11 @@ const sendToUserDid = async ({ sender, receiver: rawDid, notification, options,
|
|
|
85
96
|
// parse notification
|
|
86
97
|
const notifications = parseNotification(notification, senderInfo);
|
|
87
98
|
|
|
99
|
+
// uniq receivers
|
|
100
|
+
receiverDidList = uniq(receiverDidList);
|
|
101
|
+
receiverEmailList = uniqBy(receiverEmailList, 'email');
|
|
102
|
+
webhookList = uniqBy(webhookList, 'url');
|
|
103
|
+
|
|
88
104
|
// send notification
|
|
89
105
|
notifications.forEach((data) => {
|
|
90
106
|
for (const receiverDid of receiverDidList) {
|
|
@@ -95,14 +111,29 @@ const sendToUserDid = async ({ sender, receiver: rawDid, notification, options,
|
|
|
95
111
|
}
|
|
96
112
|
});
|
|
97
113
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
|
|
114
|
+
|
|
115
|
+
// Do not send email for component activities
|
|
116
|
+
if (data.source !== 'app') {
|
|
117
|
+
// NOTICE: 目前只有 notification 的通知能够发送邮件,并且 type 可能为 undefined
|
|
118
|
+
if ([undefined, NOTIFICATION_TYPES.NOTIFICATION].includes(data.type)) {
|
|
119
|
+
for (const receiverEmail of receiverEmailList) {
|
|
120
|
+
sendEmail(receiverEmail.email, data, { teamDid: sender.appDid, node, locale: receiverEmail.locale }).catch(
|
|
121
|
+
(error) => {
|
|
122
|
+
logger.error('Failed to send email', { error });
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// send webhook
|
|
129
|
+
for (const webhook of webhookList) {
|
|
130
|
+
let webhookSender = webhookSenders.get(webhook.type);
|
|
131
|
+
if (!webhookSender) {
|
|
132
|
+
webhookSender = node.getMessageSender(webhook.type);
|
|
133
|
+
webhookSenders.set(webhook.type, webhookSender);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
webhookSender.sendNotification(webhook.url, notification);
|
|
106
137
|
}
|
|
107
138
|
}
|
|
108
139
|
});
|
package/api/socket/util.js
CHANGED
|
@@ -29,6 +29,7 @@ const parseNotification = (notification, senderInfo) => {
|
|
|
29
29
|
did: senderInfo.permanentWallet.address,
|
|
30
30
|
pk: senderInfo.permanentWallet.pk,
|
|
31
31
|
name: senderInfo.name,
|
|
32
|
+
logo: senderInfo.logo,
|
|
32
33
|
// actualDid is the did of the application that is used to decrypt the message if needed
|
|
33
34
|
actualDid: senderInfo.wallet.address,
|
|
34
35
|
actualPk: senderInfo.wallet.pk,
|