@abtnode/blocklet-services 1.16.13 → 1.16.14-beta-0c29907f
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 +5 -0
- package/api/libs/auth/utils.js +6 -8
- package/api/libs/connect/session.js +75 -23
- package/api/libs/connect/shared.js +12 -1
- package/api/libs/connect/v1.js +32 -3
- package/api/libs/jwt.js +8 -3
- package/api/middlewares/check-federated-cors-call.js +15 -0
- package/api/middlewares/ensure-blocklet.js +13 -0
- package/api/middlewares/verify-federated-call.js +35 -0
- package/api/routes/blocklet.js +19 -6
- package/api/routes/federated.js +365 -0
- package/api/routes/oauth.js +8 -7
- package/api/services/auth/connect/invite.js +2 -1
- package/api/services/auth/connect/issue-passport.js +2 -8
- package/api/services/auth/connect/login.js +9 -1
- package/api/services/auth/connect/receive-transfer-app-owner.js +3 -1
- package/api/services/auth/session.js +3 -1
- package/api/services/notification/blocklet-events-notifier.js +79 -28
- package/api/util/blocklet-utils.js +27 -0
- package/api/util/federated.js +41 -0
- package/build/asset-manifest.json +76 -73
- 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/{8730.ecc708b7.chunk.css → 2130.b9437a64.chunk.css} +1 -1
- package/build/static/css/4603.dce369d5.chunk.css +2 -0
- package/build/static/js/1162.ff3136ae.chunk.js +2 -0
- package/build/static/js/1237.1d0b8f1e.chunk.js +3 -0
- package/build/static/js/2130.59fef4ad.chunk.js +3 -0
- package/build/static/js/3242.5dea77f3.chunk.js +2 -0
- package/build/static/js/3464.12806bfe.chunk.js +2 -0
- package/build/static/js/{3800.a4ffc6c2.chunk.js → 3800.faac00ca.chunk.js} +2 -2
- package/build/static/js/{3953.b81b810f.chunk.js → 3953.e3e51520.chunk.js} +2 -2
- package/build/static/js/3963.2e06f9bc.chunk.js +3 -0
- package/build/static/js/4319.e99b5af9.chunk.js +2 -0
- package/build/static/js/{445.4ee5d0c9.chunk.js → 445.6da52f3e.chunk.js} +3 -3
- package/build/static/js/4492.97b0d4f6.chunk.js +2 -0
- package/build/static/js/4547.b26ceabb.chunk.js +2 -0
- package/build/static/js/4603.f6b8d272.chunk.js +2 -0
- package/build/static/js/4651.0a58cbc3.chunk.js +3 -0
- package/build/static/js/5050.538edf89.chunk.js +3 -0
- package/build/static/js/5052.91c69c75.chunk.js +2 -0
- package/build/static/js/5176.68fd3fde.chunk.js +2 -0
- package/build/static/js/5233.faabaae9.chunk.js +2 -0
- package/build/static/js/5430.8114614c.chunk.js +2 -0
- package/build/static/js/5645.a17cd258.chunk.js +2 -0
- package/build/static/js/5711.aa17ef28.chunk.js +2 -0
- package/build/static/js/6315.59e078f6.chunk.js +2 -0
- package/build/static/js/652.5fc00cd3.chunk.js +2 -0
- package/build/static/js/6737.4f37dd4a.chunk.js +2 -0
- package/build/static/js/6891.6c7018ab.chunk.js +2 -0
- package/build/static/js/7345.4b4f8941.chunk.js +3 -0
- package/build/static/js/7371.da3c5cc3.chunk.js +2 -0
- package/build/static/js/{766.724b9aea.chunk.js → 766.5dd16d47.chunk.js} +2 -2
- package/build/static/js/8031.d9af3d84.chunk.js +2 -0
- package/build/static/js/8395.e5aa519c.chunk.js +2 -0
- package/build/static/js/868.aa2a1c4d.chunk.js +2 -0
- package/build/static/js/9476.bc480cfc.chunk.js +2 -0
- package/build/static/js/982.11dc355f.chunk.js +3 -0
- package/build/static/js/main.efa52345.js +3 -0
- package/build/static/media/index.20414a3fa1e6c5498a67.cjs +1 -0
- package/package.json +36 -35
- package/build/static/js/1162.c3a07ee0.chunk.js +0 -2
- package/build/static/js/1237.03f3a68a.chunk.js +0 -3
- package/build/static/js/1610.13323d09.chunk.js +0 -2
- package/build/static/js/2793.00991f08.chunk.js +0 -2
- package/build/static/js/2961.8237c11f.chunk.js +0 -3
- package/build/static/js/3242.86fc9bc6.chunk.js +0 -2
- package/build/static/js/338.2f0211d7.chunk.js +0 -2
- package/build/static/js/4319.5363b467.chunk.js +0 -2
- package/build/static/js/4547.b013b965.chunk.js +0 -2
- package/build/static/js/4651.ea828d09.chunk.js +0 -3
- package/build/static/js/5050.af9b1e9c.chunk.js +0 -3
- package/build/static/js/5052.da35270f.chunk.js +0 -2
- package/build/static/js/5176.36610ff7.chunk.js +0 -2
- package/build/static/js/5233.1f42910b.chunk.js +0 -2
- package/build/static/js/5430.f101b648.chunk.js +0 -2
- package/build/static/js/5645.48cf12d4.chunk.js +0 -2
- package/build/static/js/5711.13090883.chunk.js +0 -2
- package/build/static/js/5782.cf7e6574.chunk.js +0 -2
- package/build/static/js/6315.dec40b9b.chunk.js +0 -2
- package/build/static/js/652.0bde51bd.chunk.js +0 -2
- package/build/static/js/6737.719ec631.chunk.js +0 -2
- package/build/static/js/7240.6bbf13e8.chunk.js +0 -3
- package/build/static/js/7345.fb7fa7cf.chunk.js +0 -3
- package/build/static/js/7371.04b43294.chunk.js +0 -2
- package/build/static/js/8395.c43afdf4.chunk.js +0 -2
- package/build/static/js/868.cd5abe55.chunk.js +0 -2
- package/build/static/js/8730.0ac0b4df.chunk.js +0 -3
- package/build/static/js/9476.302a7b02.chunk.js +0 -2
- package/build/static/js/9645.822c1d69.chunk.js +0 -2
- package/build/static/js/main.c3544e79.js +0 -3
- /package/build/static/js/{1237.03f3a68a.chunk.js.LICENSE.txt → 1237.1d0b8f1e.chunk.js.LICENSE.txt} +0 -0
- /package/build/static/js/{8730.0ac0b4df.chunk.js.LICENSE.txt → 2130.59fef4ad.chunk.js.LICENSE.txt} +0 -0
- /package/build/static/js/{7240.6bbf13e8.chunk.js.LICENSE.txt → 3963.2e06f9bc.chunk.js.LICENSE.txt} +0 -0
- /package/build/static/js/{445.4ee5d0c9.chunk.js.LICENSE.txt → 445.6da52f3e.chunk.js.LICENSE.txt} +0 -0
- /package/build/static/js/{4651.ea828d09.chunk.js.LICENSE.txt → 4651.0a58cbc3.chunk.js.LICENSE.txt} +0 -0
- /package/build/static/js/{5050.af9b1e9c.chunk.js.LICENSE.txt → 5050.538edf89.chunk.js.LICENSE.txt} +0 -0
- /package/build/static/js/{7345.fb7fa7cf.chunk.js.LICENSE.txt → 7345.4b4f8941.chunk.js.LICENSE.txt} +0 -0
- /package/build/static/js/{2961.8237c11f.chunk.js.LICENSE.txt → 982.11dc355f.chunk.js.LICENSE.txt} +0 -0
- /package/build/static/js/{main.c3544e79.js.LICENSE.txt → main.efa52345.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
const { WELLKNOWN_SERVICE_PATH_PREFIX } = require('@abtnode/constant');
|
|
2
|
+
const { signV2 } = require('@arcblock/jwt');
|
|
3
|
+
const normalizePathPrefix = require('@abtnode/util/lib/normalize-path-prefix');
|
|
4
|
+
const cloneDeep = require('lodash/cloneDeep');
|
|
5
|
+
const get = require('lodash/get');
|
|
6
|
+
|
|
7
|
+
const cors = require('cors');
|
|
8
|
+
const { LOGIN_PROVIDER } = require('@blocklet/constant');
|
|
9
|
+
const pick = require('lodash/pick');
|
|
10
|
+
|
|
11
|
+
const { api } = require('../libs/api');
|
|
12
|
+
const initJwt = require('../libs/jwt');
|
|
13
|
+
const { createTokenFn, getDidConnectVersion } = require('../util');
|
|
14
|
+
const verifyFederatedCall = require('../middlewares/verify-federated-call');
|
|
15
|
+
const ensureBlocklet = require('../middlewares/ensure-blocklet');
|
|
16
|
+
const checkFederatedCorsCall = require('../middlewares/check-federated-cors-call');
|
|
17
|
+
const { getUserAvatarUrl } = require('../util/federated');
|
|
18
|
+
|
|
19
|
+
const PREFIX = WELLKNOWN_SERVICE_PATH_PREFIX;
|
|
20
|
+
|
|
21
|
+
const prefix = `${PREFIX}/api/federated`;
|
|
22
|
+
|
|
23
|
+
module.exports = {
|
|
24
|
+
preInit(server) {
|
|
25
|
+
server.options(`${prefix}/prelogin`, cors({ credentials: true, origin: true }));
|
|
26
|
+
server.options(`${prefix}/login`, cors({ credentials: true, origin: true }));
|
|
27
|
+
server.options(`${prefix}/logout`, cors({ credentials: true, origin: true }));
|
|
28
|
+
},
|
|
29
|
+
init(server, node, options) {
|
|
30
|
+
// step 1 申请加入(member 向 master 申请)
|
|
31
|
+
server.post(`${prefix}/join`, ensureBlocklet(), async (req, res) => {
|
|
32
|
+
const { blocklet } = req;
|
|
33
|
+
const { site } = req.body;
|
|
34
|
+
|
|
35
|
+
const federated = cloneDeep(blocklet.settings.federated || {});
|
|
36
|
+
let sites = federated?.sites || [];
|
|
37
|
+
if (sites.length === 0) {
|
|
38
|
+
const nodeInfo = await req.getNodeInfo();
|
|
39
|
+
const blockletInfo = await req.getBlockletInfo();
|
|
40
|
+
const masterSite = {
|
|
41
|
+
appId: blockletInfo.wallet.address,
|
|
42
|
+
appPid: blockletInfo.permanentWallet.address,
|
|
43
|
+
migratedFrom: blocklet.migratedFrom || [],
|
|
44
|
+
appName: blockletInfo.name,
|
|
45
|
+
appDescription: blockletInfo.description,
|
|
46
|
+
appUrl: blockletInfo.appUrl,
|
|
47
|
+
appLogo:
|
|
48
|
+
blocklet.environmentObj.BLOCKLET_APP_LOGO ||
|
|
49
|
+
normalizePathPrefix(`${WELLKNOWN_SERVICE_PATH_PREFIX}/blocklet/logo`) ||
|
|
50
|
+
'/',
|
|
51
|
+
appLogoRect: blocklet.environmentObj.BLOCKLET_APP_LOGO_RECT,
|
|
52
|
+
did: blockletInfo.did,
|
|
53
|
+
pk: blockletInfo.permanentWallet.publicKey,
|
|
54
|
+
serverId: nodeInfo.did,
|
|
55
|
+
serverVersion: nodeInfo.version,
|
|
56
|
+
version: blocklet.meta.version,
|
|
57
|
+
};
|
|
58
|
+
sites = [masterSite];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let masterConfig = federated.config;
|
|
62
|
+
if (!masterConfig) {
|
|
63
|
+
masterConfig = {
|
|
64
|
+
appId: blocklet.appDid,
|
|
65
|
+
appPid: blocklet.appPid || blocklet.appDid,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (site) {
|
|
69
|
+
sites.push({
|
|
70
|
+
...site,
|
|
71
|
+
appliedAt: new Date(),
|
|
72
|
+
status: 'pending',
|
|
73
|
+
isMaster: false,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
// member 申请后,将 member 展示在列表中
|
|
77
|
+
// 更新的是自己
|
|
78
|
+
await node.setFederated({
|
|
79
|
+
did: blocklet.appDid,
|
|
80
|
+
config: {
|
|
81
|
+
config: masterConfig,
|
|
82
|
+
sites,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// 将新增的数据返回给 member
|
|
87
|
+
res.json({
|
|
88
|
+
sites,
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// step 2 审批(master 申批 member)
|
|
93
|
+
// core/state/lib/blocklet/manager/disk.js -> auditFederatedLogin
|
|
94
|
+
server.post(`${prefix}/audit-res`, ensureBlocklet(), verifyFederatedCall(), async (req, res) => {
|
|
95
|
+
const { blocklet } = req;
|
|
96
|
+
const { delegation, roles, appId, status } = req.body.verifyData;
|
|
97
|
+
const federated = cloneDeep(blocklet.settings.federated || {});
|
|
98
|
+
federated.config.delegation = delegation;
|
|
99
|
+
if (status === 'approved') {
|
|
100
|
+
const trustedPassports = blocklet.trustedPassports || [];
|
|
101
|
+
const hasTrustedPassport = trustedPassports.find((item) => item.issuerDid === appId);
|
|
102
|
+
if (!hasTrustedPassport) {
|
|
103
|
+
await node.configTrustedPassports({
|
|
104
|
+
teamDid: blocklet.meta.did,
|
|
105
|
+
trustedPassports: [
|
|
106
|
+
...trustedPassports,
|
|
107
|
+
{
|
|
108
|
+
issuerDid: appId,
|
|
109
|
+
remark: 'Generated on join federated login',
|
|
110
|
+
mappings: roles.map((item) => {
|
|
111
|
+
return {
|
|
112
|
+
from: { passport: item.name },
|
|
113
|
+
to: { role: 'guest' },
|
|
114
|
+
};
|
|
115
|
+
}),
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
await node.setFederated({
|
|
123
|
+
did: blocklet.meta.did,
|
|
124
|
+
config: federated,
|
|
125
|
+
});
|
|
126
|
+
res.json(federated);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// step 3 同步站点群信息(master 向 member 广播站点群信息,广播请求在 manager/disk.js 文件中 class FederatedBlockletManager 发起 )
|
|
130
|
+
// 该路由为 member 接受响应的 api(只允许 master 调用)
|
|
131
|
+
server.post(`${prefix}/sync`, ensureBlocklet(), verifyFederatedCall({ isMaster: true }), async (req, res) => {
|
|
132
|
+
const { sites } = req.body.verifyData;
|
|
133
|
+
const { blocklet } = req;
|
|
134
|
+
const federated = cloneDeep(blocklet.settings.federated || {});
|
|
135
|
+
federated.sites = sites;
|
|
136
|
+
await node.setFederated({
|
|
137
|
+
did: blocklet.meta.did,
|
|
138
|
+
config: federated,
|
|
139
|
+
});
|
|
140
|
+
res.json(federated);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// step 4 检测自动登录条件(member 向 master 发起请求)
|
|
144
|
+
server.post(
|
|
145
|
+
`${prefix}/prelogin`,
|
|
146
|
+
cors({ credentials: true, origin: true }),
|
|
147
|
+
ensureBlocklet(),
|
|
148
|
+
checkFederatedCorsCall(),
|
|
149
|
+
async (req, res) => {
|
|
150
|
+
const { blocklet } = req;
|
|
151
|
+
|
|
152
|
+
const teamDid = blocklet.meta.did;
|
|
153
|
+
let user = null;
|
|
154
|
+
if (req.user) {
|
|
155
|
+
user = { ...req.user };
|
|
156
|
+
const currentUser = await node.getUser({
|
|
157
|
+
teamDid,
|
|
158
|
+
user: {
|
|
159
|
+
did: req.user.did,
|
|
160
|
+
},
|
|
161
|
+
options: {
|
|
162
|
+
enableConnectedAccount: true,
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
user.avatar = getUserAvatarUrl(currentUser.avatar, blocklet);
|
|
166
|
+
}
|
|
167
|
+
const blockletInfo = await req.getBlockletInfo();
|
|
168
|
+
const site = {
|
|
169
|
+
appId: blockletInfo.wallet.address,
|
|
170
|
+
appPid: blockletInfo.permanentWallet.address,
|
|
171
|
+
appName: blockletInfo.name,
|
|
172
|
+
appDescription: blockletInfo.description,
|
|
173
|
+
appUrl: blockletInfo.appUrl,
|
|
174
|
+
appLogo:
|
|
175
|
+
blocklet.environmentObj.BLOCKLET_APP_LOGO ||
|
|
176
|
+
normalizePathPrefix(`${WELLKNOWN_SERVICE_PATH_PREFIX}/blocklet/logo`) ||
|
|
177
|
+
'/',
|
|
178
|
+
appLogoRect: blocklet.environmentObj.BLOCKLET_APP_LOGO_RECT,
|
|
179
|
+
did: blockletInfo.did,
|
|
180
|
+
};
|
|
181
|
+
res.json({ user, site });
|
|
182
|
+
}
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
// step 5 完成自动登录(member 向 master 请求)
|
|
186
|
+
server.post(
|
|
187
|
+
`${prefix}/login`,
|
|
188
|
+
cors({ credentials: true, origin: true }),
|
|
189
|
+
ensureBlocklet(),
|
|
190
|
+
checkFederatedCorsCall(),
|
|
191
|
+
async (req, res) => {
|
|
192
|
+
if (!req.user) {
|
|
193
|
+
res.status(401).send('Unauthorized');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
let data;
|
|
197
|
+
const { onlyWriteCookie = false } = req.body;
|
|
198
|
+
|
|
199
|
+
if (!onlyWriteCookie) {
|
|
200
|
+
const { blocklet } = req;
|
|
201
|
+
const { permanentWallet } = await req.getBlockletInfo();
|
|
202
|
+
const teamDid = blocklet.meta.did;
|
|
203
|
+
const federatedSites = blocklet.settings?.federated?.sites || [];
|
|
204
|
+
const loginSite = federatedSites.find((item) => item.appUrl === req.headers.origin);
|
|
205
|
+
|
|
206
|
+
const currentUser = await node.getUser({
|
|
207
|
+
teamDid,
|
|
208
|
+
user: {
|
|
209
|
+
did: req.user.did,
|
|
210
|
+
},
|
|
211
|
+
options: {
|
|
212
|
+
enableConnectedAccount: true,
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const user = pick(currentUser, ['did', 'pk', 'fullName', 'locale']);
|
|
217
|
+
user.lastLoginIp = get(req, 'headers[x-real-ip]') || '';
|
|
218
|
+
|
|
219
|
+
if (currentUser.email) {
|
|
220
|
+
user.email = currentUser.email;
|
|
221
|
+
}
|
|
222
|
+
if (currentUser.avatar) {
|
|
223
|
+
user.avatar = getUserAvatarUrl(currentUser.avatar, blocklet);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
({ data } = await api.post(`${loginSite.appUrl}${prefix}/token`, {
|
|
228
|
+
signer: permanentWallet.address,
|
|
229
|
+
data: signV2(permanentWallet.address, permanentWallet.secretKey, {
|
|
230
|
+
appId: teamDid,
|
|
231
|
+
role: req.user.role,
|
|
232
|
+
user,
|
|
233
|
+
passport: req.user.passport,
|
|
234
|
+
}),
|
|
235
|
+
}));
|
|
236
|
+
} catch {
|
|
237
|
+
res.status(500).send('Generate token error');
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
res.cookie('login_token', req.token, {
|
|
242
|
+
// HACK: 目前 sessionToken 的有效期就是 1h
|
|
243
|
+
maxAge: 60 * 60 * 1000,
|
|
244
|
+
expires: new Date(Date.now() + 60 * 60 * 1000),
|
|
245
|
+
sameSite: 'none',
|
|
246
|
+
secure: true,
|
|
247
|
+
});
|
|
248
|
+
res.json(data);
|
|
249
|
+
}
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
// step 6 发放自动登录 token(master 向 member 发起生成 token 请求)
|
|
253
|
+
server.post(`${prefix}/token`, ensureBlocklet(), verifyFederatedCall(), async (req, res) => {
|
|
254
|
+
const { user, appId, role, passport } = req.body.verifyData;
|
|
255
|
+
const { createSessionToken } = initJwt(node, options);
|
|
256
|
+
const createToken = createTokenFn(createSessionToken);
|
|
257
|
+
const { secret } = await req.getBlockletInfo();
|
|
258
|
+
const { blocklet } = req;
|
|
259
|
+
const teamDid = blocklet.meta.did;
|
|
260
|
+
|
|
261
|
+
const sessionConfig = blocklet.settings?.session || {};
|
|
262
|
+
const trustedPassports = blocklet.trustedPassports || [];
|
|
263
|
+
const masterPassport = trustedPassports.find((item) => item.issuerDid === appId);
|
|
264
|
+
const findMapping = masterPassport ? masterPassport.mappings.find((item) => item.from.passport === role) : null;
|
|
265
|
+
const targetPassport = findMapping
|
|
266
|
+
? {
|
|
267
|
+
role: findMapping.to?.role || 'guest',
|
|
268
|
+
name: findMapping.to?.role || 'Guest',
|
|
269
|
+
id: passport?.id || '',
|
|
270
|
+
}
|
|
271
|
+
: { role: 'guest', name: 'Guest' };
|
|
272
|
+
const doc = await node.loginUser({
|
|
273
|
+
teamDid,
|
|
274
|
+
user: {
|
|
275
|
+
...user,
|
|
276
|
+
// HACK: @zhanghan 这里会将 passport 插入到当前用户的 passport 列表中,federated 登录不应该插入 passport
|
|
277
|
+
// passport: findMapping ? targetPassport : null,
|
|
278
|
+
connectedAccount: {
|
|
279
|
+
provider: LOGIN_PROVIDER.FEDERATED,
|
|
280
|
+
id: appId,
|
|
281
|
+
did: user.did,
|
|
282
|
+
pk: user.pk,
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
const { sessionToken, refreshToken } = createToken(
|
|
287
|
+
doc.did,
|
|
288
|
+
{
|
|
289
|
+
secret,
|
|
290
|
+
role: targetPassport.role,
|
|
291
|
+
passport: targetPassport,
|
|
292
|
+
fullName: doc.fullName,
|
|
293
|
+
provider: LOGIN_PROVIDER.FEDERATED,
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
...sessionConfig,
|
|
297
|
+
didConnectVersion: getDidConnectVersion(req),
|
|
298
|
+
}
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
res.json({ sessionToken, refreshToken });
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// member 要求 master 退出登录
|
|
305
|
+
server.post(
|
|
306
|
+
`${prefix}/logout`,
|
|
307
|
+
cors({ credentials: true, origin: true }),
|
|
308
|
+
ensureBlocklet(),
|
|
309
|
+
checkFederatedCorsCall(),
|
|
310
|
+
(req, res) => {
|
|
311
|
+
res.clearCookie('login_token');
|
|
312
|
+
res.send('');
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
// member 主动调起 master 登录(实现登录 member 时,自动登录 master)
|
|
317
|
+
|
|
318
|
+
server.post(`${prefix}/loginByMember`, ensureBlocklet(), verifyFederatedCall(), async (req, res) => {
|
|
319
|
+
const { user, appId, passport } = req.body.verifyData;
|
|
320
|
+
const { createSessionToken } = initJwt(node, options);
|
|
321
|
+
const createToken = createTokenFn(createSessionToken);
|
|
322
|
+
const { secret } = await req.getBlockletInfo();
|
|
323
|
+
const { blocklet } = req;
|
|
324
|
+
const teamDid = blocklet.meta.did;
|
|
325
|
+
|
|
326
|
+
const sessionConfig = blocklet.settings?.session || {};
|
|
327
|
+
const prevUser = await node.getUser({
|
|
328
|
+
teamDid,
|
|
329
|
+
user: { did: user.did },
|
|
330
|
+
options: { enableConnectedAccount: true },
|
|
331
|
+
});
|
|
332
|
+
// HACK: member 调用 master 时,将 passport 的 role 还原为 master 中原有的 role
|
|
333
|
+
const targetPassport = passport?.id ? (prevUser?.passports || []).find((item) => item.id === passport.id) : null;
|
|
334
|
+
const doc = await node.loginUser({
|
|
335
|
+
teamDid,
|
|
336
|
+
user: {
|
|
337
|
+
...user,
|
|
338
|
+
passport: targetPassport,
|
|
339
|
+
connectedAccount: {
|
|
340
|
+
provider: LOGIN_PROVIDER.WALLET,
|
|
341
|
+
id: appId,
|
|
342
|
+
did: user.did,
|
|
343
|
+
pk: user.pk,
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const { sessionToken, refreshToken } = createToken(
|
|
349
|
+
doc.did,
|
|
350
|
+
{
|
|
351
|
+
secret,
|
|
352
|
+
passport: targetPassport,
|
|
353
|
+
role: targetPassport?.role || 'guest',
|
|
354
|
+
fullName: doc.fullName,
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
...sessionConfig,
|
|
358
|
+
didConnectVersion: getDidConnectVersion(req),
|
|
359
|
+
}
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
res.json({ sessionToken, refreshToken });
|
|
363
|
+
});
|
|
364
|
+
},
|
|
365
|
+
};
|
package/api/routes/oauth.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { handleInvitationReceive, getApplicationInfo } = require('@abtnode/auth/lib/auth');
|
|
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
|
-
const {
|
|
4
|
+
const { extractUserAvatar, getUserAvatarUrl, getAppAvatarUrl } = require('@abtnode/util/lib/user');
|
|
5
5
|
const { fromAppDid } = require('@arcblock/did-ext');
|
|
6
6
|
const { getBlockletAppIdList } = require('@blocklet/meta/lib/util');
|
|
7
7
|
const get = require('lodash/get');
|
|
@@ -198,11 +198,10 @@ async function invite(req, node, options) {
|
|
|
198
198
|
});
|
|
199
199
|
const { dataDir, name: applicationName } = await getApplicationInfo({ node, nodeInfo, teamDid });
|
|
200
200
|
if (currentUser) {
|
|
201
|
-
const avatar = await parseUserAvatar(currentUser.avatar, { did: teamDid, dataDir });
|
|
202
201
|
profile = {
|
|
203
202
|
email: currentUser.email,
|
|
204
203
|
fullName: currentUser.fullName,
|
|
205
|
-
avatar,
|
|
204
|
+
avatar: getUserAvatarUrl(baseUrl, currentUser.avatar),
|
|
206
205
|
};
|
|
207
206
|
userDid = currentUser.did;
|
|
208
207
|
userPk = currentUser.pk;
|
|
@@ -213,7 +212,7 @@ async function invite(req, node, options) {
|
|
|
213
212
|
profile = {
|
|
214
213
|
email,
|
|
215
214
|
fullName,
|
|
216
|
-
avatar,
|
|
215
|
+
avatar: getUserAvatarUrl(baseUrl, avatar),
|
|
217
216
|
};
|
|
218
217
|
}
|
|
219
218
|
|
|
@@ -390,12 +389,12 @@ module.exports = {
|
|
|
390
389
|
const userDid = req.user.did;
|
|
391
390
|
const blocklet = await req.getBlockletInfo();
|
|
392
391
|
const nodeInfo = await req.getNodeInfo();
|
|
393
|
-
const { did: teamDid, wallet: blockletWallet } = blocklet;
|
|
394
|
-
const {
|
|
392
|
+
const { did: teamDid, wallet: blockletWallet, appUrl } = blocklet;
|
|
393
|
+
const { passportColor } = await getApplicationInfo({ node, nodeInfo, teamDid });
|
|
395
394
|
const issuerDidList = uniq([blockletWallet.address, ...getBlockletAppIdList(blocklet)]);
|
|
396
395
|
// NOTICE: 这里获取的 did 是当前登录用户的永久 did,无需查询 connectedAccount
|
|
397
396
|
const user = await node.getUser({ teamDid, user: { did: userDid } });
|
|
398
|
-
const ownerAvatarUrl =
|
|
397
|
+
const ownerAvatarUrl = getUserAvatarUrl(appUrl, user.avatar);
|
|
399
398
|
const { passports = [] } = user || {};
|
|
400
399
|
// NOTICE: 保持每一种 role 的 passport 只有一个即可
|
|
401
400
|
const passportTypes = uniqBy(
|
|
@@ -417,7 +416,9 @@ module.exports = {
|
|
|
417
416
|
title: x.title,
|
|
418
417
|
issuer: x.issuer.name,
|
|
419
418
|
issuerDid: x.issuer.id,
|
|
419
|
+
issuerAvatarUrl: getAppAvatarUrl(appUrl),
|
|
420
420
|
ownerName: user?.fullName,
|
|
421
|
+
ownerDid: userDid,
|
|
421
422
|
ownerAvatarUrl,
|
|
422
423
|
isDataUrl: true,
|
|
423
424
|
preferredColor: passportColor || 'auto',
|
|
@@ -36,7 +36,7 @@ module.exports = function createRoutes(node, authenticator, createSessionToken)
|
|
|
36
36
|
};
|
|
37
37
|
},
|
|
38
38
|
|
|
39
|
-
signature: async ({ extraParams, context: { request, didwallet } }) => {
|
|
39
|
+
signature: async ({ extraParams, context: { request, baseUrl, didwallet } }) => {
|
|
40
40
|
const { locale, inviteId } = extraParams;
|
|
41
41
|
checkWalletVersion({ didwallet, locale });
|
|
42
42
|
const nodeInfo = await request.getNodeInfo();
|
|
@@ -48,6 +48,7 @@ module.exports = function createRoutes(node, authenticator, createSessionToken)
|
|
|
48
48
|
teamDid,
|
|
49
49
|
inviteId,
|
|
50
50
|
locale,
|
|
51
|
+
baseUrl,
|
|
51
52
|
});
|
|
52
53
|
},
|
|
53
54
|
},
|
|
@@ -20,19 +20,13 @@ module.exports = function createRoutes(node) {
|
|
|
20
20
|
},
|
|
21
21
|
|
|
22
22
|
claims: {
|
|
23
|
-
signature: async ({ extraParams, context: { request, didwallet } }) => {
|
|
23
|
+
signature: async ({ extraParams, context: { request, baseUrl, didwallet } }) => {
|
|
24
24
|
const { id, locale } = extraParams;
|
|
25
25
|
checkWalletVersion({ didwallet, locale });
|
|
26
26
|
const nodeInfo = await node.getNodeInfo();
|
|
27
27
|
const teamDid = request.headers['x-blocklet-did'];
|
|
28
28
|
|
|
29
|
-
return createIssuePassportRequest({
|
|
30
|
-
node,
|
|
31
|
-
nodeInfo,
|
|
32
|
-
teamDid,
|
|
33
|
-
id,
|
|
34
|
-
locale,
|
|
35
|
-
});
|
|
29
|
+
return createIssuePassportRequest({ node, nodeInfo, teamDid, id, locale, baseUrl });
|
|
36
30
|
},
|
|
37
31
|
},
|
|
38
32
|
|
|
@@ -36,7 +36,15 @@ module.exports = function createRoutes(node, authenticator, createSessionToken)
|
|
|
36
36
|
action: 'login',
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
await updateSession(
|
|
39
|
+
await updateSession(
|
|
40
|
+
{
|
|
41
|
+
sessionToken: result.sessionToken,
|
|
42
|
+
refreshToken: result.refreshToken,
|
|
43
|
+
federatedSessionToken: result.federatedSessionToken || '',
|
|
44
|
+
federatedRefreshToken: result.federatedRefreshToken || '',
|
|
45
|
+
},
|
|
46
|
+
true
|
|
47
|
+
);
|
|
40
48
|
|
|
41
49
|
return result;
|
|
42
50
|
} catch (err) {
|
|
@@ -203,7 +203,8 @@ module.exports = function createRoutes(node, _, createSessionToken) {
|
|
|
203
203
|
passportColor,
|
|
204
204
|
dataDir,
|
|
205
205
|
secret,
|
|
206
|
-
|
|
206
|
+
logo,
|
|
207
|
+
} = await getApplicationInfo({ node, teamDid: appPid, baseUrl });
|
|
207
208
|
|
|
208
209
|
const statusEndpointBaseUrl = baseUrl;
|
|
209
210
|
const endpoint = baseUrl;
|
|
@@ -211,6 +212,7 @@ module.exports = function createRoutes(node, _, createSessionToken) {
|
|
|
211
212
|
const vcParams = {
|
|
212
213
|
issuerName,
|
|
213
214
|
issuerWallet,
|
|
215
|
+
issuerAvatarUrl: logo,
|
|
214
216
|
ownerDid: userDid,
|
|
215
217
|
passport: await createPassport({
|
|
216
218
|
name: ROLES.OWNER,
|
|
@@ -3,6 +3,7 @@ const joinUrl = require('url-join');
|
|
|
3
3
|
const SealedBox = require('tweetnacl-sealedbox-js');
|
|
4
4
|
const { decodeEncryptionKey } = require('@abtnode/util/lib/security');
|
|
5
5
|
const { WELLKNOWN_SERVICE_PATH_PREFIX, USER_AVATAR_URL_PREFIX, USER_AVATAR_PATH_PREFIX } = require('@abtnode/constant');
|
|
6
|
+
const { LOGIN_PROVIDER } = require('@blocklet/constant');
|
|
6
7
|
|
|
7
8
|
const { PREFIXES } = require('../../util/constants');
|
|
8
9
|
const { createTokenFn, getDidConnectVersion } = require('../../util');
|
|
@@ -62,6 +63,7 @@ module.exports = {
|
|
|
62
63
|
passport: req.user.passport,
|
|
63
64
|
role: req.user.role,
|
|
64
65
|
fullName: user.fullName,
|
|
66
|
+
provider: req.user.provider || LOGIN_PROVIDER.WALLET,
|
|
65
67
|
},
|
|
66
68
|
{ ...sessionConfig, didConnectVersion: getDidConnectVersion(req) }
|
|
67
69
|
);
|
|
@@ -77,7 +79,7 @@ module.exports = {
|
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
res.json({ user, nextToken, nextRefreshToken });
|
|
82
|
+
res.json({ user, nextToken, nextRefreshToken, provider: req.user.provider || LOGIN_PROVIDER.WALLET });
|
|
81
83
|
};
|
|
82
84
|
|
|
83
85
|
const sessionApi = `${prefix}/api/did/session`;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const get = require('lodash/get');
|
|
2
|
+
const uniqBy = require('lodash/uniqBy');
|
|
3
|
+
const uniq = require('lodash/uniq');
|
|
2
4
|
|
|
3
5
|
const JWT = require('@arcblock/jwt');
|
|
4
6
|
|
|
@@ -11,57 +13,103 @@ const eventHub =
|
|
|
11
13
|
const logger = require('@abtnode/logger')(require('../../../package.json').name);
|
|
12
14
|
const cache = require('../../cache');
|
|
13
15
|
|
|
16
|
+
const getComponentNamesWithVersion = (app) => {
|
|
17
|
+
const { componentDids = [], children = [] } = app;
|
|
18
|
+
|
|
19
|
+
const componentNames = uniq(componentDids).map((x) => {
|
|
20
|
+
const component = children.find((y) => y.meta.did === x);
|
|
21
|
+
return `${component.meta.title}@${component.meta.version}`;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return `${componentNames}`;
|
|
25
|
+
};
|
|
26
|
+
|
|
14
27
|
const messages = {
|
|
15
|
-
[BlockletEvents.
|
|
28
|
+
[BlockletEvents.componentInstalled]: ({ data }) => ({
|
|
16
29
|
title: {
|
|
17
|
-
en: '
|
|
18
|
-
zh: '
|
|
30
|
+
en: 'Component Installed Success',
|
|
31
|
+
zh: '组件安装成功',
|
|
19
32
|
},
|
|
20
33
|
body: {
|
|
21
|
-
en:
|
|
22
|
-
zh: `${
|
|
34
|
+
en: `Component ${getComponentNamesWithVersion(data)} is installed successfully for ${data.meta.title}.`,
|
|
35
|
+
zh: `${data.meta.title} 中的组件 ${getComponentNamesWithVersion(data)} 安装成功`,
|
|
23
36
|
},
|
|
24
37
|
}),
|
|
25
|
-
[BlockletEvents.
|
|
38
|
+
[BlockletEvents.componentInstallFailed]: ({ data }) => ({
|
|
26
39
|
title: {
|
|
27
|
-
en: '
|
|
28
|
-
zh: '
|
|
40
|
+
en: 'Component Install Failed',
|
|
41
|
+
zh: '组件安装失败',
|
|
29
42
|
},
|
|
30
43
|
body: {
|
|
31
|
-
en: `${
|
|
32
|
-
zh: `${
|
|
44
|
+
en: `${getComponentNamesWithVersion(data)} installed failed for ${data.meta.title}: ${data?.error?.message}`,
|
|
45
|
+
zh: `${data.meta.title} 中的组件 ${getComponentNamesWithVersion(data)} 安装失败: ${data?.error?.message}`,
|
|
33
46
|
},
|
|
34
47
|
}),
|
|
35
|
-
[BlockletEvents.
|
|
48
|
+
[BlockletEvents.componentUpgraded]: ({ data }) => ({
|
|
36
49
|
title: {
|
|
37
|
-
en: '
|
|
38
|
-
zh: '
|
|
50
|
+
en: 'Component Upgrade Success',
|
|
51
|
+
zh: '组件升级成功',
|
|
39
52
|
},
|
|
40
53
|
body: {
|
|
41
|
-
en:
|
|
42
|
-
zh:
|
|
54
|
+
en: `${getComponentNamesWithVersion(data)} is upgraded successfully for ${data.meta.title}.`,
|
|
55
|
+
zh: `${data.meta.title} 中的组件 ${getComponentNamesWithVersion(data)} 升级成功`,
|
|
43
56
|
},
|
|
44
57
|
}),
|
|
45
|
-
[BlockletEvents.
|
|
58
|
+
[BlockletEvents.componentUpgradeFailed]: ({ data }) => ({
|
|
46
59
|
title: {
|
|
47
|
-
en: '
|
|
48
|
-
zh: '
|
|
60
|
+
en: 'Component Upgrade Failed',
|
|
61
|
+
zh: '组件升级失败',
|
|
49
62
|
},
|
|
50
63
|
body: {
|
|
51
|
-
en:
|
|
52
|
-
zh:
|
|
64
|
+
en: `${getComponentNamesWithVersion(data)} upgraded failed for ${data.meta.title}: ${data?.error?.message}`,
|
|
65
|
+
zh: `${data.meta.title} 中的组件 ${getComponentNamesWithVersion(data)} 升级失败: ${data?.error?.message}`,
|
|
53
66
|
},
|
|
54
67
|
}),
|
|
55
|
-
[BlockletEvents.
|
|
68
|
+
[BlockletEvents.componentRemoved]: ({ data }) => ({
|
|
69
|
+
title: {
|
|
70
|
+
en: 'Component Removed Success',
|
|
71
|
+
zh: '组件删除成功',
|
|
72
|
+
},
|
|
73
|
+
body: {
|
|
74
|
+
en: `${getComponentNamesWithVersion(data)} is removed successfully for ${data.meta.title}.`,
|
|
75
|
+
zh: `${data.meta.title} 中的组件 ${getComponentNamesWithVersion(data)} 删除成功`,
|
|
76
|
+
},
|
|
77
|
+
}),
|
|
78
|
+
[BlockletEvents.started]: ({ data = {} }) => {
|
|
79
|
+
return {
|
|
80
|
+
title: {
|
|
81
|
+
en: 'Component Start Success',
|
|
82
|
+
zh: '组件启动成功',
|
|
83
|
+
},
|
|
84
|
+
body: {
|
|
85
|
+
en: `${getComponentNamesWithVersion(data)} started successfully for ${data.meta.title}.`,
|
|
86
|
+
zh: `${data.meta.title} 中的组件 ${getComponentNamesWithVersion(data)} 启动成功`,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
[BlockletEvents.startFailed]: ({ data }) => ({
|
|
56
92
|
title: {
|
|
57
|
-
en: '
|
|
58
|
-
zh: '
|
|
93
|
+
en: 'Component Start Failed',
|
|
94
|
+
zh: 'Component 启动失败',
|
|
59
95
|
},
|
|
60
96
|
body: {
|
|
61
|
-
en:
|
|
62
|
-
zh:
|
|
97
|
+
en: `${getComponentNamesWithVersion(data)} started failed: ${data.error?.message}`,
|
|
98
|
+
zh: `${data.meta.title} 中的组件 ${getComponentNamesWithVersion(data)} 启动失败: ${data.error?.message}`,
|
|
63
99
|
},
|
|
64
100
|
}),
|
|
101
|
+
[BlockletEvents.stopped]: ({ data = {} }) => {
|
|
102
|
+
return {
|
|
103
|
+
title: {
|
|
104
|
+
en: 'Component Stop Success',
|
|
105
|
+
zh: '组件已停止运行',
|
|
106
|
+
},
|
|
107
|
+
body: {
|
|
108
|
+
en: `${getComponentNamesWithVersion(data)} stopped successfully for ${data.meta.title}.`,
|
|
109
|
+
zh: `${data.meta.title} 中的组件 ${getComponentNamesWithVersion(data)} 已停止运行`,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
},
|
|
65
113
|
[EVENTS.USER_ADDED]: ({ data }) => ({
|
|
66
114
|
title: {
|
|
67
115
|
en: 'New member join',
|
|
@@ -78,8 +126,11 @@ const DEFAULT_LOCALE = 'en';
|
|
|
78
126
|
|
|
79
127
|
const init = ({ node, notificationService }) => {
|
|
80
128
|
[
|
|
81
|
-
BlockletEvents.
|
|
82
|
-
BlockletEvents.
|
|
129
|
+
BlockletEvents.componentInstalled,
|
|
130
|
+
BlockletEvents.componentInstallFailed,
|
|
131
|
+
BlockletEvents.componentUpgraded,
|
|
132
|
+
BlockletEvents.componentUpgradeFailed,
|
|
133
|
+
BlockletEvents.componentRemoved,
|
|
83
134
|
BlockletEvents.started,
|
|
84
135
|
BlockletEvents.startFailed,
|
|
85
136
|
BlockletEvents.stopped,
|
|
@@ -115,7 +166,7 @@ const init = ({ node, notificationService }) => {
|
|
|
115
166
|
query: { approved: true, role: ROLES.OWNER },
|
|
116
167
|
});
|
|
117
168
|
|
|
118
|
-
const users = (adminUsers || []).concat(ownerUsers).filter(Boolean);
|
|
169
|
+
const users = uniqBy((adminUsers || []).concat(ownerUsers).filter(Boolean), 'did');
|
|
119
170
|
|
|
120
171
|
if (!users.length) {
|
|
121
172
|
return;
|