@abtnode/blocklet-services 1.16.7 → 1.16.8-beta-0c0c5eb2
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/auth/utils.js +19 -4
- package/api/libs/connect/session.js +4 -4
- package/api/middlewares/verify-sig.js +25 -0
- package/api/routes/oauth.js +34 -19
- package/api/routes/user.js +251 -0
- package/api/services/oauth/index.js +6 -3
- package/api/util/error.js +8 -0
- package/api/validators/login.js +21 -0
- package/build/asset-manifest.json +9 -9
- package/build/index.html +1 -1
- package/build/static/css/{702.f684be9e.chunk.css → 286.97b3dab6.chunk.css} +1 -1
- package/build/static/js/286.af946efa.chunk.js +3 -0
- package/build/static/js/343.0b145b64.chunk.js +2 -0
- package/build/static/js/main.0006cb61.js +3 -0
- package/package.json +17 -16
- package/build/static/js/343.b2beb5b2.chunk.js +0 -2
- package/build/static/js/702.8320bb4a.chunk.js +0 -3
- package/build/static/js/main.a741fa5b.js +0 -3
- /package/build/static/js/{702.8320bb4a.chunk.js.LICENSE.txt → 286.af946efa.chunk.js.LICENSE.txt} +0 -0
- /package/build/static/js/{main.a741fa5b.js.LICENSE.txt → main.0006cb61.js.LICENSE.txt} +0 -0
package/api/index.js
CHANGED
|
@@ -34,6 +34,7 @@ const StaticService = require('./services/static');
|
|
|
34
34
|
const StudioService = require('./services/studio');
|
|
35
35
|
const createEnvRoutes = require('./routes/env');
|
|
36
36
|
const createOAuthRoutes = require('./routes/oauth');
|
|
37
|
+
const createUserRoutes = require('./routes/user');
|
|
37
38
|
const createBlockletRoutes = require('./routes/blocklet');
|
|
38
39
|
const createConnectRelayRoutes = require('./routes/connect/relay');
|
|
39
40
|
const createConnectSessionRoutes = require('./routes/connect/session');
|
|
@@ -223,6 +224,7 @@ module.exports = function createServer(node, serverOptions = {}) {
|
|
|
223
224
|
|
|
224
225
|
// API: auth
|
|
225
226
|
createOAuthRoutes.init(server, node, options);
|
|
227
|
+
createUserRoutes.init(server, node, options);
|
|
226
228
|
createEnvRoutes.init(server, node, options);
|
|
227
229
|
createBlockletRoutes.init(server, node);
|
|
228
230
|
createConnectSessionRoutes.init(server, node, options);
|
package/api/libs/auth/utils.js
CHANGED
|
@@ -17,16 +17,30 @@ function getEmailHash(email = '') {
|
|
|
17
17
|
return md5(cleanEmail);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
async function
|
|
20
|
+
async function getAvatarByUrl(url, options = {}) {
|
|
21
|
+
const { verbose = true } = options || {};
|
|
21
22
|
try {
|
|
22
|
-
const
|
|
23
|
-
const gravatarUrl = `https://www.gravatar.com/avatar/${emailHash}`;
|
|
24
|
-
const { data } = await axios.get(gravatarUrl, {
|
|
23
|
+
const { data } = await axios.get(url, {
|
|
25
24
|
responseType: 'arraybuffer',
|
|
26
25
|
});
|
|
27
26
|
const base64Content = Buffer.from(data, 'binary').toString('base64');
|
|
28
27
|
|
|
29
28
|
return `data:image/png;base64,${base64Content}`;
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (verbose) {
|
|
31
|
+
logger.error(`Fetch avatar failed: ${url}`, { error });
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function getAvatarByEmail(email = '') {
|
|
39
|
+
try {
|
|
40
|
+
const emailHash = getEmailHash(email);
|
|
41
|
+
const gravatarUrl = `https://www.gravatar.com/avatar/${emailHash}`;
|
|
42
|
+
const avatarBase64 = await getAvatarByUrl(gravatarUrl, { verbose: false });
|
|
43
|
+
return avatarBase64;
|
|
30
44
|
} catch (error) {
|
|
31
45
|
logger.error(`Fetch gravatar failed: ${email}`, { error });
|
|
32
46
|
return null;
|
|
@@ -116,6 +130,7 @@ async function transferPassport(fromUser, toUser, { req, teamDid, node, nodeInfo
|
|
|
116
130
|
|
|
117
131
|
module.exports = {
|
|
118
132
|
getEmailHash,
|
|
133
|
+
getAvatarByUrl,
|
|
119
134
|
getAvatarByEmail,
|
|
120
135
|
transferPassport,
|
|
121
136
|
};
|
|
@@ -33,7 +33,6 @@ const {
|
|
|
33
33
|
} = require('@abtnode/auth/lib/passport');
|
|
34
34
|
const { getKeyPairClaim, getAuthPrincipalForMigrateAppToV2 } = require('@abtnode/auth/lib/server');
|
|
35
35
|
const merge = require('lodash/merge');
|
|
36
|
-
const { types } = require('@arcblock/did');
|
|
37
36
|
const { fromAppDid } = require('@arcblock/did-ext');
|
|
38
37
|
|
|
39
38
|
const { getRolesFromAuthConfig, getBlockletAppIdList } = require('@blocklet/meta/lib/util');
|
|
@@ -43,7 +42,7 @@ const logger = require('@abtnode/logger')(require('../../../package.json').name)
|
|
|
43
42
|
const { isInvitedUserOnly } = require('../../util');
|
|
44
43
|
const { transferPassport } = require('../auth/utils');
|
|
45
44
|
const { generateTranslate } = require('../translate');
|
|
46
|
-
const { mergeUserData, migrateAccount } = require('../../services/oauth');
|
|
45
|
+
const { mergeUserData, migrateAccount, declareAccount } = require('../../services/oauth');
|
|
47
46
|
|
|
48
47
|
const vcTypes = [VC_TYPE_GENERAL_PASSPORT, VC_TYPE_NODE_PASSPORT];
|
|
49
48
|
|
|
@@ -468,7 +467,7 @@ module.exports = {
|
|
|
468
467
|
},
|
|
469
468
|
onApprove: async ({ node, request, locale, profile, userDid }) => {
|
|
470
469
|
const blocklet = await request.getBlocklet();
|
|
471
|
-
const { did: teamDid,
|
|
470
|
+
const { did: teamDid, wallet: blockletWallet } = await request.getBlockletInfo();
|
|
472
471
|
|
|
473
472
|
// check user approved
|
|
474
473
|
const user = await node.getUser({
|
|
@@ -843,7 +842,8 @@ module.exports = {
|
|
|
843
842
|
const connectedAccounts = oauthUser?.extraConfigs?.connectedAccounts || [];
|
|
844
843
|
const sourceProvider = oauthUser?.extraConfigs?.sourceProvider;
|
|
845
844
|
const oauthAccount = connectedAccounts.find((item) => item.provider === sourceProvider);
|
|
846
|
-
const userWallet = fromAppDid(oauthAccount.id, blockletWallet.secretKey
|
|
845
|
+
const userWallet = fromAppDid(oauthAccount.id, blockletWallet.secretKey);
|
|
846
|
+
await declareAccount({ wallet: userWallet, blocklet });
|
|
847
847
|
await migrateAccount({ wallet: userWallet, blocklet, user: bindUser });
|
|
848
848
|
await node.createAuditLog(
|
|
849
849
|
{
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const { verify } = require('@blocklet/sdk/lib/util/verify-sign');
|
|
2
|
+
|
|
3
|
+
const verifySig = async (req, res, next) => {
|
|
4
|
+
try {
|
|
5
|
+
const blocklet = await req.getBlocklet();
|
|
6
|
+
const sig = req.get('x-blocklet-sig');
|
|
7
|
+
const data = typeof req.body === 'undefined' ? {} : req.body;
|
|
8
|
+
const params = typeof req.query === 'undefined' ? {} : req.query;
|
|
9
|
+
const verified = verify({ data, params }, sig, {
|
|
10
|
+
// NOTICE: blocklet-service 的运行环境中不包含以下环境变量,必须从 blocklet 信息中去获取并传递
|
|
11
|
+
type: blocklet.environmentObj.BLOCKLET_APP_CHAIN_TYPE,
|
|
12
|
+
appSk: blocklet.environmentObj.BLOCKLET_APP_SK,
|
|
13
|
+
});
|
|
14
|
+
if (!verified) {
|
|
15
|
+
res.status(401).json('verify sig failed');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
} catch (error) {
|
|
19
|
+
res.status(401).json('verify sig failed');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
next();
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
module.exports = verifySig;
|
package/api/routes/oauth.js
CHANGED
|
@@ -2,7 +2,6 @@ const { handleInvitationReceive, getApplicationInfo } = require('@abtnode/auth/l
|
|
|
2
2
|
const { upsertToPassports, createPassportSvg } = require('@abtnode/auth/lib/passport');
|
|
3
3
|
const { WELLKNOWN_SERVICE_PATH_PREFIX, NODE_SERVICES, PASSPORT_STATUS } = require('@abtnode/constant');
|
|
4
4
|
const { parseUserAvatar, extractUserAvatar } = require('@abtnode/util/lib/user-avatar');
|
|
5
|
-
const { types } = require('@arcblock/did');
|
|
6
5
|
const { fromAppDid } = require('@arcblock/did-ext');
|
|
7
6
|
const { getBlockletAppIdList } = require('@blocklet/meta/lib/util');
|
|
8
7
|
const get = require('lodash/get');
|
|
@@ -14,14 +13,16 @@ const sortBy = require('lodash/sortBy');
|
|
|
14
13
|
const joinUrl = require('url-join');
|
|
15
14
|
const { getWalletDid } = require('@blocklet/meta/lib/did-utils');
|
|
16
15
|
const formatContext = require('@abtnode/util/lib/format-context');
|
|
16
|
+
const logger = require('@abtnode/logger')('blocklet-services:oauth');
|
|
17
17
|
|
|
18
18
|
const { AuthenticationClient } = require('../libs/auth/adapters/auth0');
|
|
19
|
-
const { getAvatarByEmail, transferPassport } = require('../libs/auth/utils');
|
|
19
|
+
const { getAvatarByEmail, transferPassport, getAvatarByUrl } = require('../libs/auth/utils');
|
|
20
20
|
const initJwt = require('../libs/jwt');
|
|
21
21
|
const { sendToUser } = require('../libs/notification');
|
|
22
22
|
const { generateTranslate } = require('../libs/translate');
|
|
23
|
-
const { mergeUserData,
|
|
23
|
+
const { mergeUserData, migrateAccount, declareAccount } = require('../services/oauth');
|
|
24
24
|
const { isInvitedUserOnly } = require('../util');
|
|
25
|
+
const { ApiError } = require('../util/error');
|
|
25
26
|
|
|
26
27
|
const PREFIX = WELLKNOWN_SERVICE_PATH_PREFIX;
|
|
27
28
|
|
|
@@ -48,7 +49,7 @@ function getAuthClient(blocklet, provider) {
|
|
|
48
49
|
const oauthConfig = blocklet?.settings?.oauth || {};
|
|
49
50
|
const providerConfig = oauthConfig?.[provider];
|
|
50
51
|
if (!providerConfig) {
|
|
51
|
-
throw new
|
|
52
|
+
throw new ApiError(400, `Provider ${provider} is not valid`);
|
|
52
53
|
}
|
|
53
54
|
const authClient = new AuthenticationClient({
|
|
54
55
|
domain: providerConfig.domain,
|
|
@@ -61,7 +62,7 @@ async function login(req, node, options) {
|
|
|
61
62
|
const { token, locale = 'en', provider, componentId } = req.body;
|
|
62
63
|
|
|
63
64
|
if (!blocklet.settings?.owner) {
|
|
64
|
-
throw new
|
|
65
|
+
throw new ApiError(400, t('oauthCantBeOwner', locale));
|
|
65
66
|
}
|
|
66
67
|
const authClient = getAuthClient(blocklet, provider);
|
|
67
68
|
|
|
@@ -71,7 +72,8 @@ async function login(req, node, options) {
|
|
|
71
72
|
const { dataDir } = await getApplicationInfo({ node, nodeInfo, teamDid });
|
|
72
73
|
const [invitedUserOnly] = await isInvitedUserOnly(config, node, teamDid);
|
|
73
74
|
const oauthInfo = await authClient.getProfile(token);
|
|
74
|
-
|
|
75
|
+
|
|
76
|
+
const userWallet = fromAppDid(oauthInfo.sub, blockletWallet.secretKey);
|
|
75
77
|
const userDid = userWallet.address;
|
|
76
78
|
const userPk = userWallet.publicKey;
|
|
77
79
|
const lastLoginIp = get(req, 'headers[x-real-ip]') || '';
|
|
@@ -114,11 +116,11 @@ async function login(req, node, options) {
|
|
|
114
116
|
did: userWallet.address,
|
|
115
117
|
pk: userWallet.publicKey,
|
|
116
118
|
};
|
|
117
|
-
let avatar = await getAvatarByEmail(oauthInfo.email);
|
|
119
|
+
let avatar = oauthInfo.picture ? await getAvatarByUrl(oauthInfo.picture) : await getAvatarByEmail(oauthInfo.email);
|
|
118
120
|
avatar = await extractUserAvatar(avatar, { dataDir });
|
|
119
121
|
// 当前 oauth 账户不存在,自动添加一个新用户
|
|
120
122
|
if (invitedUserOnly) {
|
|
121
|
-
throw new
|
|
123
|
+
throw new ApiError(403, t('needInviteToLogin', locale));
|
|
122
124
|
}
|
|
123
125
|
passportForLog = { name: 'Guest', role: 'guest' };
|
|
124
126
|
doc = await node.loginUser({
|
|
@@ -139,7 +141,6 @@ async function login(req, node, options) {
|
|
|
139
141
|
lastLoginIp,
|
|
140
142
|
},
|
|
141
143
|
});
|
|
142
|
-
await declareAccount({ wallet: userWallet, moniker: oauthInfo.nickname, blocklet });
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
await node.createAuditLog(
|
|
@@ -170,7 +171,7 @@ async function invite(req, node, options) {
|
|
|
170
171
|
const { did: teamDid, wallet: blockletWallet, secret } = await req.getBlockletInfo();
|
|
171
172
|
const nodeInfo = await req.getNodeInfo();
|
|
172
173
|
const oauthInfo = await authClient.getProfile(token);
|
|
173
|
-
const userWallet = fromAppDid(oauthInfo.sub, blockletWallet.secretKey
|
|
174
|
+
const userWallet = fromAppDid(oauthInfo.sub, blockletWallet.secretKey);
|
|
174
175
|
let userDid = userWallet.address;
|
|
175
176
|
let userPk = userWallet.publicKey;
|
|
176
177
|
|
|
@@ -197,8 +198,8 @@ async function invite(req, node, options) {
|
|
|
197
198
|
userDid = currentUser.did;
|
|
198
199
|
userPk = currentUser.pk;
|
|
199
200
|
} else {
|
|
200
|
-
const { email, nickname: fullName } = oauthInfo;
|
|
201
|
-
let avatar = await getAvatarByEmail(email);
|
|
201
|
+
const { picture, email, nickname: fullName } = oauthInfo;
|
|
202
|
+
let avatar = picture ? await getAvatarByUrl(picture) : await getAvatarByEmail(email);
|
|
202
203
|
avatar = await extractUserAvatar(avatar, { dataDir });
|
|
203
204
|
profile = {
|
|
204
205
|
email,
|
|
@@ -261,7 +262,6 @@ async function invite(req, node, options) {
|
|
|
261
262
|
},
|
|
262
263
|
},
|
|
263
264
|
});
|
|
264
|
-
await declareAccount({ wallet: userWallet, moniker: oauthInfo.nickname, blocklet });
|
|
265
265
|
}
|
|
266
266
|
|
|
267
267
|
const { createSessionToken } = initJwt(node, options);
|
|
@@ -279,7 +279,7 @@ async function bind(req, node, options) {
|
|
|
279
279
|
const userDid = req.user.did;
|
|
280
280
|
const userInfo = await authClient.getProfile(token);
|
|
281
281
|
const { did: teamDid, wallet: blockletWallet } = await req.getBlockletInfo();
|
|
282
|
-
const userWallet = fromAppDid(userInfo.sub, blockletWallet.secretKey
|
|
282
|
+
const userWallet = fromAppDid(userInfo.sub, blockletWallet.secretKey);
|
|
283
283
|
let oauthUser = await node.getUser({
|
|
284
284
|
teamDid,
|
|
285
285
|
user: { did: userWallet.address },
|
|
@@ -288,14 +288,14 @@ async function bind(req, node, options) {
|
|
|
288
288
|
},
|
|
289
289
|
});
|
|
290
290
|
if (oauthUser) {
|
|
291
|
-
throw new
|
|
291
|
+
throw new ApiError(400, t('alreadyMainAccount', locale));
|
|
292
292
|
}
|
|
293
293
|
|
|
294
294
|
// NOTICE: 这里获得的 did 是当前登录用户的永久 did,无需再去查询 connectedAccount
|
|
295
295
|
const bindUser = await node.getUser({ teamDid, user: { did: userDid } });
|
|
296
296
|
|
|
297
297
|
if (bindUser.extraConfigs?.sourceProvider !== 'wallet') {
|
|
298
|
-
throw new
|
|
298
|
+
throw new ApiError(400, t('oauthCantBindOauth', locale));
|
|
299
299
|
}
|
|
300
300
|
|
|
301
301
|
const mergePassport = (oauthUser?.passports || []).reduce((sum, cur) => {
|
|
@@ -359,6 +359,7 @@ async function bind(req, node, options) {
|
|
|
359
359
|
},
|
|
360
360
|
},
|
|
361
361
|
});
|
|
362
|
+
await declareAccount({ wallet: userWallet, blocklet });
|
|
362
363
|
await migrateAccount({ wallet: userWallet, blocklet, user: bindUser });
|
|
363
364
|
} else {
|
|
364
365
|
oauthUser = {
|
|
@@ -428,8 +429,17 @@ module.exports = {
|
|
|
428
429
|
});
|
|
429
430
|
|
|
430
431
|
server.post(`${prefix}/bind`, async (req, res) => {
|
|
431
|
-
|
|
432
|
-
|
|
432
|
+
try {
|
|
433
|
+
await bind(req, node, options);
|
|
434
|
+
res.status(200).send('');
|
|
435
|
+
} catch (err) {
|
|
436
|
+
logger.error('Failed bind oauth', { error: err });
|
|
437
|
+
if (err instanceof ApiError) {
|
|
438
|
+
res.status(err.code).send(err.message);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
throw err;
|
|
442
|
+
}
|
|
433
443
|
});
|
|
434
444
|
|
|
435
445
|
server.post(`${prefix}/switch`, async (req, res) => {
|
|
@@ -469,7 +479,12 @@ module.exports = {
|
|
|
469
479
|
const result = await actionMap[action](req, node, options);
|
|
470
480
|
res.send(result);
|
|
471
481
|
} catch (err) {
|
|
472
|
-
|
|
482
|
+
logger.error('Failed login oauth', { error: err });
|
|
483
|
+
if (err instanceof ApiError) {
|
|
484
|
+
res.status(err.code).send(err.message);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
throw err;
|
|
473
488
|
}
|
|
474
489
|
});
|
|
475
490
|
},
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
const { NODE_SERVICES, WELLKNOWN_SERVICE_PATH_PREFIX } = require('@abtnode/constant');
|
|
2
|
+
const { getApplicationInfo } = require('@abtnode/auth/lib/auth');
|
|
3
|
+
const { fromAppDid } = require('@arcblock/did-ext');
|
|
4
|
+
const { extractUserAvatar } = require('@abtnode/util/lib/user-avatar');
|
|
5
|
+
const formatContext = require('@abtnode/util/lib/format-context');
|
|
6
|
+
const logger = require('@abtnode/logger')('blocklet-services:user');
|
|
7
|
+
|
|
8
|
+
const { generateTranslate } = require('../libs/translate');
|
|
9
|
+
const { isInvitedUserOnly } = require('../util');
|
|
10
|
+
const initJwt = require('../libs/jwt');
|
|
11
|
+
const { getAvatarByUrl } = require('../libs/auth/utils');
|
|
12
|
+
const { ApiError } = require('../util/error');
|
|
13
|
+
const { loginWalletSchema, loginOAuthSchema } = require('../validators/login');
|
|
14
|
+
const verifySig = require('../middlewares/verify-sig');
|
|
15
|
+
const { getAvatarByEmail } = require('../libs/auth/utils');
|
|
16
|
+
|
|
17
|
+
const PREFIX = WELLKNOWN_SERVICE_PATH_PREFIX;
|
|
18
|
+
|
|
19
|
+
const prefix = `${PREFIX}/user`;
|
|
20
|
+
const translations = {
|
|
21
|
+
zh: {
|
|
22
|
+
needInviteToLogin: '你需要被邀请才可以登录此应用',
|
|
23
|
+
notAllowed: '你没有权限登录该节点',
|
|
24
|
+
needComponentId: '缺少登录参数: componentId',
|
|
25
|
+
},
|
|
26
|
+
en: {
|
|
27
|
+
needInviteToLogin: 'You need to be invited to sign in to this app',
|
|
28
|
+
notAllowed: 'You are not allowed to login to this node',
|
|
29
|
+
needComponentId: 'componentId is required when login user',
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const t = generateTranslate({ translations });
|
|
34
|
+
|
|
35
|
+
async function checkNeedInvite({ req, node, teamDid, componentId, locale }) {
|
|
36
|
+
const config = await req.getServiceConfig(NODE_SERVICES.AUTH, { componentId });
|
|
37
|
+
const [invitedUserOnly] = await isInvitedUserOnly(config, node, teamDid);
|
|
38
|
+
if (invitedUserOnly) {
|
|
39
|
+
throw new ApiError(403, t('needInviteToLogin', locale));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function checkUserEnable(user, { locale }) {
|
|
44
|
+
if (!user.approved) {
|
|
45
|
+
throw new ApiError(403, t('notAllowed', locale));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function composeProfileData({ avatar, fullName, email }, { node, req, teamDid, isCreate = false }) {
|
|
50
|
+
const profile = {};
|
|
51
|
+
let avatarLocal;
|
|
52
|
+
if (email) {
|
|
53
|
+
profile.email = email;
|
|
54
|
+
// 创建模式下,需要对 avatar 和 fullname 做一个 fallback,同时对 wallet 和 oauth 用户生效
|
|
55
|
+
if (isCreate) {
|
|
56
|
+
if (!avatar) {
|
|
57
|
+
avatarLocal = await getAvatarByEmail(email);
|
|
58
|
+
}
|
|
59
|
+
if (!fullName) {
|
|
60
|
+
profile.fullName = email;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (avatar) {
|
|
65
|
+
avatarLocal = await getAvatarByUrl(avatar);
|
|
66
|
+
}
|
|
67
|
+
if (avatarLocal) {
|
|
68
|
+
const nodeInfo = await req.getNodeInfo();
|
|
69
|
+
const { dataDir } = await getApplicationInfo({ node, nodeInfo, teamDid });
|
|
70
|
+
avatarLocal = await extractUserAvatar(avatarLocal, { dataDir });
|
|
71
|
+
profile.avatar = avatarLocal;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (fullName) {
|
|
75
|
+
profile.fullName = fullName;
|
|
76
|
+
}
|
|
77
|
+
return profile;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function loginWallet(
|
|
81
|
+
{ did, pk, avatar, email, fullName },
|
|
82
|
+
{ node, req, locale, componentId, teamDid, blockletWallet }
|
|
83
|
+
) {
|
|
84
|
+
const provider = 'wallet';
|
|
85
|
+
const { error } = loginWalletSchema.validate({
|
|
86
|
+
provider,
|
|
87
|
+
did,
|
|
88
|
+
pk,
|
|
89
|
+
avatar,
|
|
90
|
+
email,
|
|
91
|
+
fullName,
|
|
92
|
+
});
|
|
93
|
+
if (error) {
|
|
94
|
+
throw new ApiError(400, error.message);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const currentUser = await node.getUser({
|
|
98
|
+
teamDid,
|
|
99
|
+
user: {
|
|
100
|
+
did,
|
|
101
|
+
},
|
|
102
|
+
options: {
|
|
103
|
+
enableConnectedAccount: true,
|
|
104
|
+
enableNormalize: true,
|
|
105
|
+
blockletSk: blockletWallet.secretKey,
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
let profile;
|
|
110
|
+
if (currentUser) {
|
|
111
|
+
await checkUserEnable(currentUser, { locale });
|
|
112
|
+
profile = await composeProfileData({ avatar, email, fullName }, { node, req, teamDid });
|
|
113
|
+
} else {
|
|
114
|
+
await checkNeedInvite({ req, node, teamDid, componentId, locale });
|
|
115
|
+
profile = await composeProfileData({ avatar, email, fullName }, { node, req, teamDid, isCreate: true });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const lastLoginIp = req.get('x-real-ip');
|
|
119
|
+
const doc = await node.loginUser({
|
|
120
|
+
teamDid,
|
|
121
|
+
user: {
|
|
122
|
+
did: currentUser?.did || pk,
|
|
123
|
+
pk: currentUser?.pk || did,
|
|
124
|
+
locale,
|
|
125
|
+
lastLoginIp,
|
|
126
|
+
connectedAccount: { provider, did, pk },
|
|
127
|
+
...profile,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// NOTICE: 使用 provider: wallet 登录的账号,其账号是由 wallet 产生的,所以不需要到链上 declare 了
|
|
132
|
+
return doc;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function loginOAuth(
|
|
136
|
+
{ provider, id, avatar, email, fullName },
|
|
137
|
+
{ node, req, locale, componentId, teamDid, blockletWallet }
|
|
138
|
+
) {
|
|
139
|
+
const { error } = loginOAuthSchema.validate({
|
|
140
|
+
provider,
|
|
141
|
+
id,
|
|
142
|
+
avatar,
|
|
143
|
+
email,
|
|
144
|
+
fullName,
|
|
145
|
+
});
|
|
146
|
+
if (error) {
|
|
147
|
+
throw new ApiError(400, error.message);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const userWallet = fromAppDid(id, blockletWallet.secretKey);
|
|
151
|
+
const userDid = userWallet.address;
|
|
152
|
+
const userPk = userWallet.publicKey;
|
|
153
|
+
const currentUser = await node.getUser({
|
|
154
|
+
teamDid,
|
|
155
|
+
user: {
|
|
156
|
+
did: userDid,
|
|
157
|
+
},
|
|
158
|
+
options: {
|
|
159
|
+
enableConnectedAccount: true,
|
|
160
|
+
enableNormalize: true,
|
|
161
|
+
blockletSk: blockletWallet.secretKey,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
let profile;
|
|
166
|
+
if (currentUser) {
|
|
167
|
+
await checkUserEnable(currentUser, { locale });
|
|
168
|
+
profile = await composeProfileData({ avatar, email, fullName }, { node, req, teamDid });
|
|
169
|
+
} else {
|
|
170
|
+
await checkNeedInvite({ req, node, teamDid, componentId, locale });
|
|
171
|
+
profile = await composeProfileData({ avatar, email, fullName }, { node, req, teamDid, isCreate: true });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const lastLoginIp = req.get('x-real-ip');
|
|
175
|
+
|
|
176
|
+
const doc = await node.loginUser({
|
|
177
|
+
teamDid,
|
|
178
|
+
user: {
|
|
179
|
+
did: currentUser?.did || userDid,
|
|
180
|
+
pk: currentUser?.pk || userPk,
|
|
181
|
+
locale,
|
|
182
|
+
lastLoginIp,
|
|
183
|
+
connectedAccount: { provider, id, did: userDid, pk: userPk },
|
|
184
|
+
...profile,
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
return doc;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function login(req, node, options) {
|
|
191
|
+
const { provider = 'wallet', did, pk, avatar, email, fullName, id, locale = 'en' } = req.body;
|
|
192
|
+
|
|
193
|
+
const componentId = req.get('x-blocklet-component-id');
|
|
194
|
+
if (!componentId) {
|
|
195
|
+
throw new ApiError(400, t('needComponentId', locale));
|
|
196
|
+
}
|
|
197
|
+
const { did: teamDid, wallet: blockletWallet, secret } = await req.getBlockletInfo();
|
|
198
|
+
|
|
199
|
+
let doc;
|
|
200
|
+
if (provider === 'wallet') {
|
|
201
|
+
doc = await loginWallet(
|
|
202
|
+
{ did, pk, avatar, email, fullName },
|
|
203
|
+
{ node, req, locale, componentId, teamDid, blockletWallet }
|
|
204
|
+
);
|
|
205
|
+
} else {
|
|
206
|
+
doc = await loginOAuth(
|
|
207
|
+
{ provider, id, avatar, email, fullName },
|
|
208
|
+
{ node, req, locale, componentId, teamDid, blockletWallet }
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// NOTICE: 这种方式限制登录的角色为 guest,后续需要登录其它角色可以通过传入 passportId 或者 role name 来实现
|
|
213
|
+
const passport = { name: 'Guest', role: 'guest' };
|
|
214
|
+
|
|
215
|
+
await node.createAuditLog(
|
|
216
|
+
{
|
|
217
|
+
action: 'login',
|
|
218
|
+
args: { teamDid, userDid: doc.did, passport, provider },
|
|
219
|
+
context: formatContext(Object.assign(req, { user: doc })),
|
|
220
|
+
result: doc,
|
|
221
|
+
},
|
|
222
|
+
node
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
const { createSessionToken } = initJwt(node, options);
|
|
226
|
+
|
|
227
|
+
const sessionToken = await createSessionToken(doc.did, {
|
|
228
|
+
secret,
|
|
229
|
+
passport,
|
|
230
|
+
role: passport.role,
|
|
231
|
+
});
|
|
232
|
+
return { user: doc, token: sessionToken };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
module.exports = {
|
|
236
|
+
init(server, node, options) {
|
|
237
|
+
server.post(`${prefix}/login`, verifySig, async (req, res) => {
|
|
238
|
+
try {
|
|
239
|
+
const data = await login(req, node, options);
|
|
240
|
+
res.status(200).json(data);
|
|
241
|
+
} catch (err) {
|
|
242
|
+
logger.error('Failed login', { error: err });
|
|
243
|
+
if (err instanceof ApiError) {
|
|
244
|
+
res.status(err.code).json(err.message);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
throw err;
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
},
|
|
251
|
+
};
|
|
@@ -6,7 +6,6 @@ const { slugify } = require('transliteration');
|
|
|
6
6
|
const { MAIN_CHAIN_ENDPOINT } = require('@abtnode/constant');
|
|
7
7
|
const { getBlockletChainInfo } = require('@blocklet/meta/lib/util');
|
|
8
8
|
const { fromAppDid } = require('@arcblock/did-ext');
|
|
9
|
-
const { types } = require('@arcblock/did');
|
|
10
9
|
const logger = require('@abtnode/logger')('blocklet-services:oauth');
|
|
11
10
|
|
|
12
11
|
function mergeUserData(oldUser, updateData) {
|
|
@@ -88,7 +87,11 @@ async function declareAccount({ wallet, moniker, blocklet }) {
|
|
|
88
87
|
chainHostList.push(chainHost);
|
|
89
88
|
}
|
|
90
89
|
const waitingList = [...new Set(chainHostList)].map((item) =>
|
|
91
|
-
declareAccountByChain({
|
|
90
|
+
declareAccountByChain({
|
|
91
|
+
chainHost: item,
|
|
92
|
+
wallet,
|
|
93
|
+
moniker: moniker || wallet.address.slice(0, 4) + wallet.address.slice(-4),
|
|
94
|
+
})
|
|
92
95
|
);
|
|
93
96
|
await Promise.all(waitingList);
|
|
94
97
|
}
|
|
@@ -191,7 +194,7 @@ async function normalizeUser(teamDid, user, { updateUser, getBlockletInfo }) {
|
|
|
191
194
|
const { wallet: blockletWallet } = await getBlockletInfo();
|
|
192
195
|
connectedAccounts.forEach((account) => {
|
|
193
196
|
if (account.id) {
|
|
194
|
-
const accountWallet = fromAppDid(account.id, blockletWallet.secretKey
|
|
197
|
+
const accountWallet = fromAppDid(account.id, blockletWallet.secretKey);
|
|
195
198
|
account.did = accountWallet.address;
|
|
196
199
|
account.pk = accountWallet.publicKey;
|
|
197
200
|
account.firstLoginAt = account.firstLoginAt || new Date().toISOString();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const { Joi } = require('@arcblock/validator');
|
|
2
|
+
|
|
3
|
+
const loginWalletSchema = Joi.object({
|
|
4
|
+
provider: 'wallet',
|
|
5
|
+
did: Joi.DID().trim().required(),
|
|
6
|
+
pk: Joi.string().required(),
|
|
7
|
+
email: Joi.string().optional(),
|
|
8
|
+
avatar: Joi.string().optional(),
|
|
9
|
+
fullName: Joi.string().optional(),
|
|
10
|
+
}).empty(null);
|
|
11
|
+
|
|
12
|
+
const loginOAuthSchema = Joi.object({
|
|
13
|
+
provider: Joi.string().allow('auth0'),
|
|
14
|
+
id: Joi.string().required(),
|
|
15
|
+
email: Joi.string().required(),
|
|
16
|
+
avatar: Joi.string().optional(),
|
|
17
|
+
fullName: Joi.string().optional(),
|
|
18
|
+
}).empty(null);
|
|
19
|
+
|
|
20
|
+
exports.loginWalletSchema = loginWalletSchema;
|
|
21
|
+
exports.loginOAuthSchema = loginOAuthSchema;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"files": {
|
|
3
3
|
"main.css": "/.blocklet/proxy/blocklet-service/static/css/main.632501d5.css",
|
|
4
|
-
"main.js": "/.blocklet/proxy/blocklet-service/static/js/main.
|
|
4
|
+
"main.js": "/.blocklet/proxy/blocklet-service/static/js/main.0006cb61.js",
|
|
5
5
|
"static/js/716.0d2a2d32.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/716.0d2a2d32.chunk.js",
|
|
6
6
|
"static/js/359.c47779c2.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/359.c47779c2.chunk.js",
|
|
7
7
|
"static/js/255.279b1bca.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/255.279b1bca.chunk.js",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"static/js/460.4763afe0.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/460.4763afe0.chunk.js",
|
|
11
11
|
"static/js/868.ac8df3a0.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/868.ac8df3a0.chunk.js",
|
|
12
12
|
"static/js/547.03d5d719.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/547.03d5d719.chunk.js",
|
|
13
|
-
"static/js/343.
|
|
13
|
+
"static/js/343.0b145b64.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/343.0b145b64.chunk.js",
|
|
14
14
|
"static/js/682.a8bf723a.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/682.a8bf723a.chunk.js",
|
|
15
15
|
"static/js/711.56427a24.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/711.56427a24.chunk.js",
|
|
16
16
|
"static/js/437.075e8453.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/437.075e8453.chunk.js",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"static/js/189.70043381.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/189.70043381.chunk.js",
|
|
20
20
|
"static/js/162.8c29e450.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/162.8c29e450.chunk.js",
|
|
21
21
|
"static/js/610.40349d57.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/610.40349d57.chunk.js",
|
|
22
|
-
"static/css/
|
|
23
|
-
"static/js/
|
|
22
|
+
"static/css/286.97b3dab6.chunk.css": "/.blocklet/proxy/blocklet-service/static/css/286.97b3dab6.chunk.css",
|
|
23
|
+
"static/js/286.af946efa.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/286.af946efa.chunk.js",
|
|
24
24
|
"static/js/199.3d76c24b.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/199.3d76c24b.chunk.js",
|
|
25
25
|
"static/js/573.b071429e.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/573.b071429e.chunk.js",
|
|
26
26
|
"static/media/ubuntu-mono-all-400-normal.woff": "/.blocklet/proxy/blocklet-service/static/media/ubuntu-mono-all-400-normal.c879328bc62e9c68268f.woff",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"router-template-styles/styles.css": "/.blocklet/proxy/blocklet-service/router-template-styles/styles.css",
|
|
46
46
|
"index.html": "/.blocklet/proxy/blocklet-service/index.html",
|
|
47
47
|
"main.632501d5.css.map": "/.blocklet/proxy/blocklet-service/static/css/main.632501d5.css.map",
|
|
48
|
-
"main.
|
|
48
|
+
"main.0006cb61.js.map": "/.blocklet/proxy/blocklet-service/static/js/main.0006cb61.js.map",
|
|
49
49
|
"716.0d2a2d32.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/716.0d2a2d32.chunk.js.map",
|
|
50
50
|
"359.c47779c2.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/359.c47779c2.chunk.js.map",
|
|
51
51
|
"255.279b1bca.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/255.279b1bca.chunk.js.map",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"460.4763afe0.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/460.4763afe0.chunk.js.map",
|
|
55
55
|
"868.ac8df3a0.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/868.ac8df3a0.chunk.js.map",
|
|
56
56
|
"547.03d5d719.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/547.03d5d719.chunk.js.map",
|
|
57
|
-
"343.
|
|
57
|
+
"343.0b145b64.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/343.0b145b64.chunk.js.map",
|
|
58
58
|
"682.a8bf723a.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/682.a8bf723a.chunk.js.map",
|
|
59
59
|
"711.56427a24.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/711.56427a24.chunk.js.map",
|
|
60
60
|
"437.075e8453.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/437.075e8453.chunk.js.map",
|
|
@@ -63,13 +63,13 @@
|
|
|
63
63
|
"189.70043381.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/189.70043381.chunk.js.map",
|
|
64
64
|
"162.8c29e450.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/162.8c29e450.chunk.js.map",
|
|
65
65
|
"610.40349d57.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/610.40349d57.chunk.js.map",
|
|
66
|
-
"
|
|
67
|
-
"
|
|
66
|
+
"286.97b3dab6.chunk.css.map": "/.blocklet/proxy/blocklet-service/static/css/286.97b3dab6.chunk.css.map",
|
|
67
|
+
"286.af946efa.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/286.af946efa.chunk.js.map",
|
|
68
68
|
"199.3d76c24b.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/199.3d76c24b.chunk.js.map",
|
|
69
69
|
"573.b071429e.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/573.b071429e.chunk.js.map"
|
|
70
70
|
},
|
|
71
71
|
"entrypoints": [
|
|
72
72
|
"static/css/main.632501d5.css",
|
|
73
|
-
"static/js/main.
|
|
73
|
+
"static/js/main.0006cb61.js"
|
|
74
74
|
]
|
|
75
75
|
}
|