@abtnode/blocklet-services 1.16.3 → 1.16.4-beta-8682e092

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.
@@ -1,23 +1,24 @@
1
1
  const { handleInvitationReceive, getApplicationInfo } = require('@abtnode/auth/lib/auth');
2
2
  const { upsertToPassports, createPassportSvg } = require('@abtnode/auth/lib/passport');
3
- const { WELLKNOWN_SERVICE_PATH_PREFIX, NODE_SERVICES, USER_TYPE, PASSPORT_STATUS } = require('@abtnode/constant');
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
5
  const { types } = require('@arcblock/did');
6
6
  const { fromAppDid } = require('@arcblock/did-ext');
7
7
  const { getBlockletAppIdList } = require('@blocklet/meta/lib/util');
8
- const { uniqBy, uniq } = require('lodash');
9
8
  const get = require('lodash/get');
9
+ const uniq = require('lodash/uniq');
10
10
  const last = require('lodash/last');
11
11
  const pick = require('lodash/pick');
12
+ const uniqBy = require('lodash/uniqBy');
12
13
  const sortBy = require('lodash/sortBy');
13
14
  const joinUrl = require('url-join');
14
15
 
15
16
  const { AuthenticationClient } = require('../libs/auth/adapters/auth0');
16
17
  const { getAvatarByEmail, transferPassport } = require('../libs/auth/utils');
17
- const { getUser } = require('../libs/jwt');
18
18
  const initJwt = require('../libs/jwt');
19
19
  const { sendToUser } = require('../libs/notification');
20
20
  const { generateTranslate } = require('../libs/translate');
21
+ const { getRawUser, mergeUserData, declareAccount, migrateAccount, normalizeUser } = require('../services/oauth');
21
22
  const { isInvitedUserOnly } = require('../util');
22
23
 
23
24
  const PREFIX = WELLKNOWN_SERVICE_PATH_PREFIX;
@@ -34,123 +35,102 @@ const translations = {
34
35
  };
35
36
  const t = generateTranslate({ translations });
36
37
 
37
- async function getDerivedUser(derivedDid, { node, teamDid }) {
38
- const { users } = await node.getUsers({
39
- teamDid,
40
- query: {
41
- derivedDid,
42
- },
43
- });
44
- let currentUser;
45
- if (users.length > 0) {
46
- [currentUser] = users;
38
+ function getAuthClient(blocklet, provider) {
39
+ const oauthConfig = blocklet?.settings?.oauth || {};
40
+ const providerConfig = oauthConfig?.[provider];
41
+ if (!providerConfig) {
42
+ throw new Error(`Provider ${provider} is not valid`);
47
43
  }
48
- return currentUser;
44
+ const authClient = new AuthenticationClient({
45
+ domain: providerConfig.domain,
46
+ });
47
+ return authClient;
49
48
  }
50
49
 
51
50
  async function login(req, node, options) {
52
- const { token, locale = 'en', provider, componentId } = req.body;
53
51
  const blocklet = await req.getBlocklet();
54
52
  if (!blocklet.settings?.owner) {
55
53
  throw new Error('Cant login oauth account as owner');
56
54
  }
57
- const oauthConfig = blocklet?.settings?.oauth || {};
58
- const authClient = new AuthenticationClient({
59
- domain: oauthConfig.auth0.domain,
60
- });
55
+ const { token, locale = 'en', provider, componentId } = req.body;
56
+ const authClient = getAuthClient(blocklet, provider);
57
+
58
+ const currentTime = new Date().toISOString();
61
59
  const { did: teamDid, wallet: blockletWallet } = await req.getBlockletInfo();
62
60
  const config = await req.getServiceConfig(NODE_SERVICES.AUTH, { componentId });
63
61
  const nodeInfo = await req.getNodeInfo();
64
62
  const { dataDir } = await getApplicationInfo({ node, nodeInfo, teamDid });
65
63
  const [invitedUserOnly] = await isInvitedUserOnly(config, node, teamDid);
66
- const userInfo = await authClient.getProfile(token);
67
- const userWallet = fromAppDid(userInfo.sub, blockletWallet.secretKey, types.RoleType.ROLE_ACCOUNT);
64
+ const oauthInfo = await authClient.getProfile(token);
65
+ const userWallet = fromAppDid(oauthInfo.sub, blockletWallet.secretKey, types.RoleType.ROLE_ACCOUNT);
68
66
  const userDid = userWallet.address;
67
+ const userPk = userWallet.publicKey;
68
+ const lastLoginIp = get(req, 'headers[x-real-ip]') || '';
69
69
  let passport = { name: 'Guest', role: 'guest' };
70
- let currentUser = await getDerivedUser(userDid, { node, teamDid });
71
- // 当前 oauth 账户已经绑定了 wallet 账户
70
+ let currentUser = await getRawUser(teamDid, userDid, { getUser: node.getUser, getUsers: node.getUsers });
71
+ currentUser = await normalizeUser(teamDid, currentUser, {
72
+ updateUser: node.updateUser,
73
+ getBlockletInfo: req.getBlockletInfo,
74
+ });
75
+ // 当前账户已存在,更新账户信息
72
76
  if (currentUser) {
73
- const validPassports = currentUser.passports.filter((item) => item.status === 'valid');
77
+ const allPassports = currentUser.passports || [];
78
+ const validPassports = allPassports.filter((item) => item.status === PASSPORT_STATUS.VALID);
74
79
  const lastUsedPassport = last(sortBy(validPassports, 'lastLoginAt'));
75
80
  if (lastUsedPassport) {
76
81
  passport = pick(lastUsedPassport, ['id', 'name', 'role']);
77
82
  }
78
83
  await node.updateUser({
79
84
  teamDid,
80
- user: {
81
- did: currentUser.did,
82
- pk: currentUser.pk,
85
+ user: mergeUserData(currentUser, {
83
86
  locale,
84
- passports: upsertToPassports(
85
- currentUser.passports || [],
86
- lastUsedPassport && {
87
- ...lastUsedPassport,
88
- lastLoginAt: new Date().toISOString(),
89
- }
90
- ),
91
- lastLoginAt: new Date().toISOString(),
92
- lastLoginIp: get(req, 'headers[x-real-ip]') || '',
93
- },
87
+ lastLoginIp,
88
+ lastUsedPassport,
89
+ connectedAccount: { provider },
90
+ }),
94
91
  });
95
92
  } else {
96
93
  currentUser = {
97
94
  did: userWallet.address,
95
+ pk: userWallet.publicKey,
98
96
  };
99
- let avatar = await getAvatarByEmail(userInfo.email);
97
+ let avatar = await getAvatarByEmail(oauthInfo.email);
100
98
  avatar = await extractUserAvatar(avatar, { dataDir });
101
- const currentUserInfo = await getUser(node, teamDid, userDid);
102
99
  // 当前 oauth 账户不存在,自动添加一个新用户
103
- if (!currentUserInfo) {
104
- if (invitedUserOnly) {
105
- throw new Error(t('needInviteToLogin', locale));
106
- }
107
- await node.addUser({
108
- teamDid,
109
- user: {
110
- source: USER_TYPE.DERIVED,
111
- extraConfigs: {
112
- sourceId: userInfo.sub,
113
- sourceProvider: provider,
114
- },
115
- did: userDid,
116
- pk: userWallet.publicKey,
117
- fullName: userInfo.nickname,
118
- email: userInfo.email,
119
- avatar,
120
- locale,
121
- type: 'profile',
122
- approved: true,
123
- passports: [],
124
- firstLoginAt: new Date().toISOString(),
125
- lastLoginAt: new Date().toISOString(),
126
- lastLoginIp: get(req, 'headers[x-real-ip]') || '',
127
- },
128
- });
129
- } else {
130
- const validPassports = currentUserInfo.passports.filter((item) => item.status === 'valid');
131
- const lastUsedPassport = last(sortBy(validPassports, 'lastLoginAt'));
132
- if (lastUsedPassport) {
133
- passport = pick(lastUsedPassport, ['id', 'name', 'role']);
134
- }
135
- // 当前用户已存在,更新用户信息
136
- await node.updateUser({
137
- teamDid,
138
- user: {
139
- did: userDid,
140
- pk: userWallet.publicKey,
141
- locale,
142
- passports: upsertToPassports(
143
- currentUser.passports || [],
144
- lastUsedPassport && {
145
- ...lastUsedPassport,
146
- lastLoginAt: new Date().toISOString(),
147
- }
148
- ),
149
- lastLoginAt: new Date().toISOString(),
150
- lastLoginIp: get(req, 'headers[x-real-ip]') || '',
151
- },
152
- });
100
+ if (invitedUserOnly) {
101
+ throw new Error(t('needInviteToLogin', locale));
153
102
  }
103
+ await node.addUser({
104
+ teamDid,
105
+ user: {
106
+ extraConfigs: {
107
+ sourceProvider: provider,
108
+ connectedAccounts: [
109
+ {
110
+ provider,
111
+ id: oauthInfo.sub,
112
+ did: userDid,
113
+ pk: userPk,
114
+ lastLoginAt: currentTime,
115
+ firstLoginAt: currentTime,
116
+ },
117
+ ],
118
+ },
119
+ did: userDid,
120
+ pk: userPk,
121
+ fullName: oauthInfo.nickname,
122
+ email: oauthInfo.email,
123
+ avatar,
124
+ locale,
125
+ type: 'profile',
126
+ approved: true,
127
+ passports: [],
128
+ firstLoginAt: currentTime,
129
+ lastLoginAt: currentTime,
130
+ lastLoginIp,
131
+ },
132
+ });
133
+ await declareAccount({ wallet: userWallet, moniker: oauthInfo.nickname });
154
134
  }
155
135
 
156
136
  const { createSessionToken } = initJwt(node, options);
@@ -163,37 +143,38 @@ async function login(req, node, options) {
163
143
  }
164
144
 
165
145
  async function invite(req, node, options) {
166
- const { locale, inviteId, token, baseUrl } = req.body;
167
- const { did: teamDid, wallet: blockletWallet } = await req.getBlockletInfo();
146
+ const { locale, inviteId, token, baseUrl, provider = 'auth0' } = req.body;
147
+ const blocklet = await req.getBlocklet();
148
+ const authClient = getAuthClient(blocklet, provider);
168
149
 
150
+ const { did: teamDid, wallet: blockletWallet } = await req.getBlockletInfo();
169
151
  const nodeInfo = await req.getNodeInfo();
170
- const blocklet = await req.getBlocklet();
171
- const oauthConfig = blocklet?.settings?.oauth || {};
172
- const authClient = new AuthenticationClient({
173
- domain: oauthConfig.auth0.domain,
174
- });
175
- const userInfo = await authClient.getProfile(token);
176
- const userWallet = fromAppDid(userInfo.sub, blockletWallet.secretKey, types.RoleType.ROLE_ACCOUNT);
152
+ const oauthInfo = await authClient.getProfile(token);
153
+ const userWallet = fromAppDid(oauthInfo.sub, blockletWallet.secretKey, types.RoleType.ROLE_ACCOUNT);
177
154
  let userDid = userWallet.address;
178
155
  let userPk = userWallet.publicKey;
179
156
 
180
157
  let profile;
181
- let userSource = USER_TYPE.DERIVED;
182
- // 如果当前 oauth 账户绑定了 wallet 账户,则需要以 wallet 为目标账户来颁发通行证
183
- const bindUser = await getDerivedUser(userDid, { node, teamDid });
158
+ let currentUser = await getRawUser(teamDid, userDid, {
159
+ getUser: node.getUser,
160
+ getUsers: node.getUsers,
161
+ });
162
+ currentUser = await normalizeUser(teamDid, currentUser, {
163
+ updateUser: node.updateUser,
164
+ getBlockletInfo: req.getBlockletInfo,
165
+ });
184
166
  const { dataDir, name: applicationName } = await getApplicationInfo({ node, nodeInfo, teamDid });
185
- if (bindUser) {
186
- const avatar = await parseUserAvatar(bindUser.avatar, { did: teamDid, dataDir });
167
+ if (currentUser) {
168
+ const avatar = await parseUserAvatar(currentUser.avatar, { did: teamDid, dataDir });
187
169
  profile = {
188
- email: bindUser.email,
189
- fullName: bindUser.fullName,
170
+ email: currentUser.email,
171
+ fullName: currentUser.fullName,
190
172
  avatar,
191
173
  };
192
- userDid = bindUser.did;
193
- userPk = bindUser.pk;
194
- userSource = USER_TYPE.WALLET;
174
+ userDid = currentUser.did;
175
+ userPk = currentUser.pk;
195
176
  } else {
196
- const { email, nickname: fullName } = userInfo;
177
+ const { email, nickname: fullName } = oauthInfo;
197
178
  let avatar = await getAvatarByEmail(email);
198
179
  avatar = await extractUserAvatar(avatar, { dataDir });
199
180
  profile = {
@@ -217,60 +198,59 @@ async function invite(req, node, options) {
217
198
  teamDid,
218
199
  userDid,
219
200
  userPk,
220
- userSource,
221
201
  locale,
222
202
  });
223
- if (bindUser) {
224
- // 如果已经绑定了 wallet 账户,则将 passport 发送给 wallet 账户
225
- await sendToUser(
226
- bindUser.did,
227
- {
228
- title: 'Invitation accepted',
229
- body: `You accept an invitation to be a ${role} of ${applicationName}`,
230
- attachments: [
231
- {
232
- type: 'vc',
233
- data: {
234
- credential: response.data,
235
- tag: role,
236
- },
237
- },
238
- ],
239
- },
240
- { req }
241
- );
242
- await node.updateUser({
243
- teamDid,
244
- user: {
245
- did: bindUser.did,
246
- pk: bindUser.pk,
247
- extraConfigs: {
248
- ...(bindUser.extraConfigs || {}),
249
- connectedAccounts: [
203
+
204
+ if (currentUser) {
205
+ const { connectedAccounts = [] } = currentUser.extraConfigs || {};
206
+ const walletAccount = connectedAccounts.find((item) => item.provider === 'wallet');
207
+ if (walletAccount) {
208
+ // 如果已经绑定了 wallet 账户,则将 passport 发送给 wallet 账户
209
+ await sendToUser(
210
+ walletAccount.did,
211
+ {
212
+ title: 'Invitation accepted',
213
+ body: `You accept an invitation to be a ${role} of ${applicationName}`,
214
+ attachments: [
250
215
  {
251
- provider: 'auth0',
252
- id: userInfo.sub,
253
- lastLoginAt: new Date().toISOString(),
216
+ type: 'vc',
217
+ data: {
218
+ credential: response.data,
219
+ tag: role,
220
+ },
254
221
  },
255
222
  ],
256
223
  },
257
- },
258
- });
224
+ { req }
225
+ );
226
+ }
259
227
  } else {
260
- const currentUserInfo = await node.getUser({ teamDid, user: { did: userDid } });
261
228
  await node.updateUser({
262
229
  teamDid,
263
- user: {
264
- did: userDid,
265
- pk: userPk,
266
- extraConfigs: {
267
- ...(currentUserInfo.extraConfigs || {}),
268
- sourceId: userInfo.sub,
269
- sourceProvider: 'auth0',
230
+ user: mergeUserData(
231
+ {
232
+ did: userDid,
233
+ pk: userPk,
234
+ extraConfigs: {
235
+ sourceProvider: provider,
236
+ },
270
237
  },
271
- },
238
+ {
239
+ provider,
240
+ connectedAccount: {
241
+ provider,
242
+ id: oauthInfo.sub,
243
+ did: userWallet.address,
244
+ pk: userWallet.publicKey,
245
+ firstLoginAt: new Date().toISOString(),
246
+ lastLoginAt: new Date().toISOString(),
247
+ },
248
+ }
249
+ ),
272
250
  });
251
+ await declareAccount({ wallet: userWallet, moniker: oauthInfo.nickname });
273
252
  }
253
+
274
254
  const { createSessionToken } = initJwt(node, options);
275
255
 
276
256
  const sessionToken = await createSessionToken(userDid, { passport, role });
@@ -279,20 +259,29 @@ async function invite(req, node, options) {
279
259
 
280
260
  // eslint-disable-next-line no-unused-vars
281
261
  async function bind(req, node, options) {
282
- const userDid = req.user.did;
283
262
  const { token, locale = 'en', provider } = req.body;
284
263
  const blocklet = await req.getBlocklet();
285
- const oauthConfig = blocklet?.settings?.oauth || {};
286
- const authClient = new AuthenticationClient({
287
- domain: oauthConfig.auth0.domain,
288
- });
264
+ const authClient = getAuthClient(blocklet, provider);
265
+
266
+ const userDid = req.user.did;
289
267
  const userInfo = await authClient.getProfile(token);
290
268
  const { did: teamDid, wallet: blockletWallet } = await req.getBlockletInfo();
291
269
  const userWallet = fromAppDid(userInfo.sub, blockletWallet.secretKey, types.RoleType.ROLE_ACCOUNT);
292
270
  let oauthUser = await node.getUser({ teamDid, user: { did: userWallet.address } });
271
+ if (oauthUser) {
272
+ if (oauthUser.extraConfigs?.bindDid) {
273
+ throw new Error('OAuth account is already bind to another wallet account');
274
+ }
275
+ throw new Error('OAuth account is already a main account');
276
+ }
293
277
 
278
+ // NOTICE: 这里获得的 userDid 已经是登录返回的 rawUserDid 了,无需再 getRawUser
294
279
  const bindUser = await node.getUser({ teamDid, user: { did: userDid } });
295
280
 
281
+ if (bindUser.extraConfigs?.sourceProvider !== 'wallet') {
282
+ throw new Error("OAuth account can't bind to a oauth account");
283
+ }
284
+
296
285
  const mergePassport = (oauthUser?.passports || []).reduce((sum, cur) => {
297
286
  return upsertToPassports(sum, cur);
298
287
  }, bindUser.passports || []);
@@ -311,30 +300,31 @@ async function bind(req, node, options) {
311
300
  avatar,
312
301
  };
313
302
 
303
+ const currentTime = new Date().toISOString();
304
+
314
305
  await node.updateUser({
315
306
  teamDid,
316
307
  user: {
317
308
  ...mergeProfile,
318
- did: userDid,
319
- pk: bindUser.pk,
320
- locale,
321
- source: USER_TYPE.WALLET,
322
- extraConfigs: {
323
- ...(bindUser.extraConfigs || {}),
324
- derivedAccount: {
325
- provider,
326
- did: userWallet.address,
327
- pk: userWallet.publicKey,
309
+ ...mergeUserData(
310
+ {
311
+ did: bindUser.did,
312
+ pk: bindUser.pk,
313
+ passports: mergePassport,
314
+ extraConfigs: bindUser.extraConfigs,
328
315
  },
329
- connectedAccounts: [
330
- {
316
+ {
317
+ locale,
318
+ connectedAccount: {
331
319
  provider,
332
320
  id: userInfo.sub,
333
- lastLoginAt: new Date().toISOString(),
321
+ did: userWallet.address,
322
+ pk: userWallet.publicKey,
323
+ firstLoginAt: currentTime,
324
+ lastLoginAt: currentTime,
334
325
  },
335
- ],
336
- },
337
- passports: mergePassport,
326
+ }
327
+ ),
338
328
  },
339
329
  });
340
330
  if (oauthUser) {
@@ -350,9 +340,11 @@ async function bind(req, node, options) {
350
340
  },
351
341
  },
352
342
  });
343
+ await migrateAccount({ wallet: userWallet, blocklet, user: bindUser });
353
344
  } else {
354
345
  oauthUser = {
355
346
  did: userWallet.address,
347
+ pk: userWallet.publicKey,
356
348
  };
357
349
  }
358
350
  const nodeInfo = await req.getNodeInfo();
@@ -7,7 +7,7 @@ const { onConnect, onApprove } = bindWallet;
7
7
  module.exports = function createRoutes(node, authenticator, createSessionToken) {
8
8
  return {
9
9
  action: 'bind-wallet',
10
- onConnect: async ({ req, userDid, extraParams: { locale, passportId = '', componentId, previousUserDid } }) => {
10
+ onConnect: async ({ req, userDid, extraParams: { locale, passportId = '', componentId } }) => {
11
11
  return onConnect({
12
12
  node,
13
13
  request: req,
@@ -15,20 +15,10 @@ module.exports = function createRoutes(node, authenticator, createSessionToken)
15
15
  locale,
16
16
  passportId,
17
17
  componentId,
18
- previousUserDid,
19
18
  });
20
19
  },
21
20
 
22
- onAuth: async ({
23
- claims,
24
- challenge,
25
- userDid,
26
- userPk,
27
- updateSession,
28
- extraParams: { locale, componentId, previousUserDid },
29
- req,
30
- baseUrl,
31
- }) => {
21
+ onAuth: async ({ claims, challenge, userDid, userPk, extraParams: { locale, componentId }, req, baseUrl }) => {
32
22
  try {
33
23
  const result = await onApprove({
34
24
  node,
@@ -41,11 +31,8 @@ module.exports = function createRoutes(node, authenticator, createSessionToken)
41
31
  claims,
42
32
  createSessionToken,
43
33
  componentId,
44
- previousUserDid,
45
34
  });
46
35
 
47
- await updateSession({ sessionToken: result.sessionToken }, true);
48
-
49
36
  return result;
50
37
  } catch (err) {
51
38
  logger.error('login.error', { error: err, userDid });