@abtnode/core 1.8.68 → 1.8.69-beta-54faead3
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/lib/blocklet/downloader/bundle-downloader.js +6 -1
- package/lib/blocklet/manager/disk.js +162 -65
- package/lib/blocklet/manager/helper/install-from-backup.js +7 -6
- package/lib/blocklet/storage/backup/audit-log.js +2 -2
- package/lib/blocklet/storage/backup/base.js +7 -8
- package/lib/blocklet/storage/backup/blocklet-extras.js +22 -39
- package/lib/blocklet/storage/backup/blocklet.js +34 -17
- package/lib/blocklet/storage/backup/blocklets.js +2 -2
- package/lib/blocklet/storage/backup/data.js +2 -2
- package/lib/blocklet/storage/backup/logs.js +2 -2
- package/lib/blocklet/storage/backup/routing-rule.js +2 -2
- package/lib/blocklet/storage/backup/spaces.js +105 -39
- package/lib/blocklet/storage/restore/base.js +26 -14
- package/lib/blocklet/storage/restore/blocklet-extras.js +34 -34
- package/lib/blocklet/storage/restore/blocklet.js +49 -0
- package/lib/blocklet/storage/restore/blocklets.js +6 -3
- package/lib/blocklet/storage/restore/logs.js +2 -2
- package/lib/blocklet/storage/restore/spaces.js +64 -50
- package/lib/event.js +28 -2
- package/lib/index.js +21 -3
- package/lib/router/helper.js +27 -6
- package/lib/router/index.js +31 -0
- package/lib/states/audit-log.js +5 -3
- package/lib/states/blocklet.js +21 -5
- package/lib/states/node.js +5 -2
- package/lib/util/blocklet.js +111 -19
- package/lib/util/index.js +24 -0
- package/lib/validators/node.js +1 -0
- package/lib/webhook/index.js +1 -1
- package/package.json +26 -27
- /package/lib/{queue.js → util/queue.js} +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const { removeSync,
|
|
1
|
+
const { removeSync, readFileSync, outputJsonSync } = require('fs-extra');
|
|
2
2
|
const { isEmpty, cloneDeep } = require('lodash');
|
|
3
3
|
const { join } = require('path');
|
|
4
4
|
const security = require('@abtnode/util/lib/security');
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
const states = require('../../../states');
|
|
7
7
|
const { BaseBackup } = require('./base');
|
|
8
8
|
|
|
@@ -11,8 +11,8 @@ class BlockletExtrasBackup extends BaseBackup {
|
|
|
11
11
|
|
|
12
12
|
async export() {
|
|
13
13
|
const blockletExtra = await this.getBlockletExtra();
|
|
14
|
-
removeSync(join(this.
|
|
15
|
-
outputJsonSync(join(this.
|
|
14
|
+
removeSync(join(this.backupDir, this.filename));
|
|
15
|
+
outputJsonSync(join(this.backupDir, this.filename), blockletExtra);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -32,35 +32,35 @@ class BlockletExtrasBackup extends BaseBackup {
|
|
|
32
32
|
throw new Error('blockletExtra cannot be empty');
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
return this.
|
|
35
|
+
return this.cleanData(blockletExtra);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
*
|
|
40
40
|
* @description 清理数据并加密
|
|
41
|
-
* @param {import('@abtnode/client').BlockletState}
|
|
42
|
-
* @return {Promise<
|
|
41
|
+
* @param {import('@abtnode/client').BlockletState} raw
|
|
42
|
+
* @return {Promise<any>}
|
|
43
43
|
* @memberof BlockletExtrasBackup
|
|
44
44
|
*/
|
|
45
|
-
async
|
|
46
|
-
const blockletExtra = cloneDeep(
|
|
45
|
+
async cleanData(raw) {
|
|
46
|
+
const blockletExtra = cloneDeep(raw);
|
|
47
47
|
|
|
48
48
|
const queue = [blockletExtra];
|
|
49
49
|
while (queue.length) {
|
|
50
|
-
const
|
|
50
|
+
const current = queue.pop();
|
|
51
51
|
|
|
52
52
|
// 删除父 blocklet 的某些数据
|
|
53
|
-
if (
|
|
54
|
-
delete
|
|
55
|
-
delete
|
|
56
|
-
delete
|
|
53
|
+
if (current._id) {
|
|
54
|
+
delete current._id;
|
|
55
|
+
delete current.createdAt;
|
|
56
|
+
delete current.updatedAt;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
// 加解密
|
|
60
|
-
this.
|
|
60
|
+
this.encrypt(current.configs);
|
|
61
61
|
|
|
62
|
-
if (
|
|
63
|
-
queue.push(...
|
|
62
|
+
if (current?.children) {
|
|
63
|
+
queue.push(...current.children);
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
@@ -74,33 +74,16 @@ class BlockletExtrasBackup extends BaseBackup {
|
|
|
74
74
|
* @return {void}
|
|
75
75
|
* @memberof BlockletExtrasBackup
|
|
76
76
|
*/
|
|
77
|
-
|
|
77
|
+
encrypt(configs) {
|
|
78
78
|
if (isEmpty(configs)) {
|
|
79
79
|
return;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
// @see: https://github.com/ArcBlock/blocklet-server/blob/f561ba7290285f2e23dccb6d5323eb4d43c3cc3e/core/state/lib/index.js#L59
|
|
84
|
-
const dek = readFileSync(join(this.serverDataDir, '.sock'));
|
|
85
|
-
|
|
82
|
+
const dk = readFileSync(join(this.serverDir, '.sock'));
|
|
86
83
|
for (const config of configs) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
config.value =
|
|
90
|
-
} else if (config.secure) {
|
|
91
|
-
// secure 为 true 的配置才需要被加密保存上次到 did spaces
|
|
92
|
-
|
|
93
|
-
const encryptByServer = config.value;
|
|
94
|
-
// 先从 blocklet server 解密
|
|
95
|
-
// @see https://github.com/ArcBlock/blocklet-server/blob/f40338168a66893f325464cea79ae54c43f623b1/core/state/lib/blocklet/extras.js#L139
|
|
96
|
-
const decryptByServer = security.decrypt(encryptByServer, this.blocklet.meta.did, dek);
|
|
97
|
-
// 再用 blocklet secret 加密,然后才可以上传到 spaces
|
|
98
|
-
const encryptByBlocklet = security.encrypt(
|
|
99
|
-
decryptByServer,
|
|
100
|
-
this.blockletWallet.address,
|
|
101
|
-
Buffer.from(this.blockletWallet.secretKey)
|
|
102
|
-
);
|
|
103
|
-
config.value = encryptByBlocklet;
|
|
84
|
+
if (config.secure) {
|
|
85
|
+
const decrypted = security.decrypt(config.value, this.blocklet.meta.did, dk);
|
|
86
|
+
config.value = this.securityContext.encrypt(decrypted);
|
|
104
87
|
}
|
|
105
88
|
}
|
|
106
89
|
}
|
|
@@ -8,45 +8,62 @@ class BlockletBackup extends BaseBackup {
|
|
|
8
8
|
|
|
9
9
|
async export() {
|
|
10
10
|
const blocklet = await this.cleanData();
|
|
11
|
-
removeSync(join(this.
|
|
12
|
-
outputJsonSync(join(this.
|
|
11
|
+
removeSync(join(this.backupDir, this.filename));
|
|
12
|
+
outputJsonSync(join(this.backupDir, this.filename), blocklet);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* @description 清理数据
|
|
17
17
|
* @see blocklet.db 中需要删除哪些字段呢? https://github.com/ArcBlock/blocklet-server/issues/6120#issuecomment-1383798348
|
|
18
|
-
* @return {Promise<
|
|
18
|
+
* @return {Promise<import('@abtnode/client').BlockletState>}
|
|
19
19
|
* @memberof BlockletBackup
|
|
20
20
|
*/
|
|
21
21
|
async cleanData() {
|
|
22
|
-
const
|
|
22
|
+
const clone = cloneDeep(this.blocklet);
|
|
23
23
|
|
|
24
24
|
/** @type {import('@abtnode/client').ComponentState[]} */
|
|
25
|
-
const queue = [
|
|
25
|
+
const queue = [clone];
|
|
26
26
|
|
|
27
27
|
// 广度优先遍历
|
|
28
28
|
while (queue.length) {
|
|
29
|
-
const
|
|
29
|
+
const current = queue.pop();
|
|
30
30
|
|
|
31
31
|
// 父组件才需要删除的属性
|
|
32
|
-
if (
|
|
33
|
-
delete
|
|
34
|
-
delete
|
|
35
|
-
delete
|
|
36
|
-
delete
|
|
32
|
+
if (current._id) {
|
|
33
|
+
delete current._id;
|
|
34
|
+
delete current.createdAt;
|
|
35
|
+
delete current.startedAt;
|
|
36
|
+
delete current.installedAt;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
// 子组件和父组件都需要删除的属性
|
|
40
|
-
delete
|
|
41
|
-
delete
|
|
42
|
-
delete
|
|
40
|
+
delete current.status;
|
|
41
|
+
delete current.ports;
|
|
42
|
+
delete current.environments;
|
|
43
43
|
|
|
44
|
-
if (
|
|
45
|
-
queue.push(...
|
|
44
|
+
if (current.children) {
|
|
45
|
+
queue.push(...current.children);
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
return
|
|
49
|
+
return this.encrypt(clone);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
*
|
|
54
|
+
* @description 清理数据并加密
|
|
55
|
+
* @param {import('@abtnode/client').BlockletState} info
|
|
56
|
+
* @memberof BlockletExtrasBackup
|
|
57
|
+
*/
|
|
58
|
+
encrypt(info) {
|
|
59
|
+
if (Array.isArray(info.migratedFrom)) {
|
|
60
|
+
info.migratedFrom = info.migratedFrom.map((x) => {
|
|
61
|
+
x.appSk = this.securityContext.encrypt(x.appSk);
|
|
62
|
+
return x;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return info;
|
|
50
67
|
}
|
|
51
68
|
}
|
|
52
69
|
|
|
@@ -12,7 +12,7 @@ class BlockletsBackup extends BaseBackup {
|
|
|
12
12
|
*/
|
|
13
13
|
async export() {
|
|
14
14
|
const blockletMetas = this.getBlockletMetas(this.blocklet);
|
|
15
|
-
const serverBlockletsDir = join(this.
|
|
15
|
+
const serverBlockletsDir = join(this.serverDir, 'blocklets');
|
|
16
16
|
|
|
17
17
|
const blockletMetasFromLocal = blockletMetas.filter(
|
|
18
18
|
(b) => !validUrl.isHttpUri(b.tarball) && !validUrl.isHttpsUri(b.tarball)
|
|
@@ -21,7 +21,7 @@ class BlockletsBackup extends BaseBackup {
|
|
|
21
21
|
const dirs = [];
|
|
22
22
|
for (const blockletMeta of blockletMetasFromLocal) {
|
|
23
23
|
const sourceDir = join(serverBlockletsDir, blockletMeta.name, blockletMeta.version);
|
|
24
|
-
const zipPath = join(this.
|
|
24
|
+
const zipPath = join(this.backupDir, 'blocklets', blockletMeta.name, `${blockletMeta.version}.zip`);
|
|
25
25
|
dirs.push({ sourceDir, zipPath });
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -9,8 +9,8 @@ class DataBackup extends BaseBackup {
|
|
|
9
9
|
* @memberof BlockletsBackup
|
|
10
10
|
*/
|
|
11
11
|
async export() {
|
|
12
|
-
const blockletDataDir = join(this.
|
|
13
|
-
const blockletBackupDataDir = join(this.
|
|
12
|
+
const blockletDataDir = join(this.serverDir, 'data', this.blocklet.meta.name);
|
|
13
|
+
const blockletBackupDataDir = join(this.backupDir, 'data');
|
|
14
14
|
|
|
15
15
|
await copy(blockletDataDir, blockletBackupDataDir, { overwrite: true });
|
|
16
16
|
}
|
|
@@ -5,10 +5,10 @@ const { BaseBackup } = require('./base');
|
|
|
5
5
|
// note: 可以不需要备份
|
|
6
6
|
class LogsBackup extends BaseBackup {
|
|
7
7
|
async export() {
|
|
8
|
-
const sourceLogsDir = join(this.
|
|
8
|
+
const sourceLogsDir = join(this.serverDir, 'logs', this.blocklet.meta.name);
|
|
9
9
|
ensureDirSync(sourceLogsDir);
|
|
10
10
|
|
|
11
|
-
const targetLogsDir = join(this.
|
|
11
|
+
const targetLogsDir = join(this.backupDir, 'logs');
|
|
12
12
|
|
|
13
13
|
await copy(sourceLogsDir, targetLogsDir, {
|
|
14
14
|
overwrite: true,
|
|
@@ -11,8 +11,8 @@ class RoutingRuleBackup extends BaseBackup {
|
|
|
11
11
|
domain: `${this.blocklet.meta.did}.blocklet-domain-group`,
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
removeSync(join(this.
|
|
15
|
-
outputJsonSync(join(this.
|
|
14
|
+
removeSync(join(this.backupDir, this.filename));
|
|
15
|
+
outputJsonSync(join(this.backupDir, this.filename), routingRule);
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -1,16 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef {{
|
|
3
|
-
*
|
|
3
|
+
* appPid: string
|
|
4
|
+
* event: import('events').EventEmitter,
|
|
5
|
+
* userDid: string,
|
|
6
|
+
* referrer: string,
|
|
4
7
|
* }} SpaceBackupInput
|
|
8
|
+
*
|
|
9
|
+
* @typedef {{
|
|
10
|
+
* signer: import('@ocap/wallet').WalletObject
|
|
11
|
+
* encrypt: (v: string) => string,
|
|
12
|
+
* decrypt: (v: string) => string,
|
|
13
|
+
* delegation: string,
|
|
14
|
+
* }} SecurityContext
|
|
5
15
|
*/
|
|
6
16
|
|
|
7
17
|
const { isValid } = require('@arcblock/did');
|
|
8
|
-
const { BLOCKLET_CONFIGURABLE_KEY } = require('@blocklet/constant');
|
|
9
|
-
const {
|
|
10
|
-
const { SpaceClient, SyncFolderPushCommand } = require('@did-space/client');
|
|
18
|
+
const { BLOCKLET_CONFIGURABLE_KEY, BlockletEvents } = require('@blocklet/constant');
|
|
19
|
+
const { SpaceClient, BackupBlockletCommand } = require('@did-space/client');
|
|
11
20
|
const { ensureDirSync } = require('fs-extra');
|
|
12
21
|
const { isEmpty } = require('lodash');
|
|
13
|
-
const { join } = require('path');
|
|
22
|
+
const { join, basename } = require('path');
|
|
23
|
+
const { signV2 } = require('@arcblock/jwt');
|
|
24
|
+
const { Hasher } = require('@ocap/mcrypto');
|
|
25
|
+
const { toBuffer } = require('@ocap/util');
|
|
26
|
+
const { getAppName, getAppDescription } = require('@blocklet/meta/lib/util');
|
|
27
|
+
const getBlockletInfo = require('@blocklet/meta/lib/info');
|
|
28
|
+
const security = require('@abtnode/util/lib/security');
|
|
29
|
+
|
|
30
|
+
const logger = require('@abtnode/logger')('@abtnode/core:storage:backup');
|
|
31
|
+
|
|
14
32
|
const states = require('../../../states');
|
|
15
33
|
const { AuditLogBackup } = require('./audit-log');
|
|
16
34
|
const { BlockletBackup } = require('./blocklet');
|
|
@@ -39,7 +57,7 @@ class SpacesBackup {
|
|
|
39
57
|
* @type {string}
|
|
40
58
|
* @memberof SpacesBackup
|
|
41
59
|
*/
|
|
42
|
-
|
|
60
|
+
backupDir;
|
|
43
61
|
|
|
44
62
|
/**
|
|
45
63
|
*
|
|
@@ -47,7 +65,7 @@ class SpacesBackup {
|
|
|
47
65
|
* @type {string}
|
|
48
66
|
* @memberof SpacesBackup
|
|
49
67
|
*/
|
|
50
|
-
|
|
68
|
+
serverDir;
|
|
51
69
|
|
|
52
70
|
/**
|
|
53
71
|
*
|
|
@@ -55,15 +73,15 @@ class SpacesBackup {
|
|
|
55
73
|
* @type {string}
|
|
56
74
|
* @memberof SpacesBackup
|
|
57
75
|
*/
|
|
58
|
-
|
|
76
|
+
spaceEndpoint;
|
|
59
77
|
|
|
60
78
|
/**
|
|
61
79
|
*
|
|
62
|
-
* @description spaces
|
|
63
|
-
* @type
|
|
80
|
+
* @description spaces 安全相关的上下文
|
|
81
|
+
* @type SecurityContext
|
|
64
82
|
* @memberof SpacesBackup
|
|
65
83
|
*/
|
|
66
|
-
|
|
84
|
+
securityContext;
|
|
67
85
|
|
|
68
86
|
storages;
|
|
69
87
|
|
|
@@ -91,8 +109,8 @@ class SpacesBackup {
|
|
|
91
109
|
* @memberof SpacesBackup
|
|
92
110
|
*/
|
|
93
111
|
verify(input) {
|
|
94
|
-
if (isEmpty(input?.
|
|
95
|
-
throw new Error(`input.
|
|
112
|
+
if (isEmpty(input?.appPid) || !isValid(input?.appPid)) {
|
|
113
|
+
throw new Error(`input.appPid(${input?.appPid}) is not a valid did`);
|
|
96
114
|
}
|
|
97
115
|
}
|
|
98
116
|
|
|
@@ -108,68 +126,116 @@ class SpacesBackup {
|
|
|
108
126
|
}
|
|
109
127
|
|
|
110
128
|
async initialize() {
|
|
111
|
-
this.blocklet = await states.blocklet.
|
|
112
|
-
appDid: this.input.did,
|
|
113
|
-
});
|
|
129
|
+
this.blocklet = await states.blocklet.getBlocklet(this.input.appPid);
|
|
114
130
|
if (isEmpty(this.blocklet)) {
|
|
115
131
|
throw new Error('blocklet cannot be empty');
|
|
116
132
|
}
|
|
117
133
|
|
|
118
|
-
this.
|
|
134
|
+
this.serverDir = process.env.ABT_NODE_DATA_DIR;
|
|
135
|
+
this.backupDir = join(this.serverDir, 'tmp/backup', this.blocklet.appPid);
|
|
136
|
+
ensureDirSync(this.backupDir);
|
|
119
137
|
|
|
120
|
-
this.
|
|
121
|
-
ensureDirSync(this.blockletBackupDir);
|
|
122
|
-
|
|
123
|
-
this.spacesEndpoint = this.blocklet.environments.find(
|
|
138
|
+
this.spaceEndpoint = this.blocklet.environments.find(
|
|
124
139
|
(e) => e.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPACE_ENDPOINT
|
|
125
140
|
)?.value;
|
|
126
|
-
if (isEmpty(this.
|
|
127
|
-
throw new Error('
|
|
141
|
+
if (isEmpty(this.spaceEndpoint)) {
|
|
142
|
+
throw new Error('spaceEndpoint cannot be empty');
|
|
128
143
|
}
|
|
129
144
|
|
|
130
|
-
this.
|
|
145
|
+
this.securityContext = await this.getSecurityContext();
|
|
131
146
|
}
|
|
132
147
|
|
|
133
148
|
async export() {
|
|
149
|
+
this.input.event.emit(BlockletEvents.backupProgress, {
|
|
150
|
+
appPid: this.input.appPid,
|
|
151
|
+
message: 'Preparing data for backup...',
|
|
152
|
+
progress: 15,
|
|
153
|
+
completed: false,
|
|
154
|
+
});
|
|
134
155
|
await Promise.all(
|
|
135
156
|
this.storages.map((storage) => {
|
|
136
157
|
storage.ensureParams(this);
|
|
137
158
|
return storage.export();
|
|
138
159
|
})
|
|
139
160
|
);
|
|
161
|
+
this.input.event.emit(BlockletEvents.backupProgress, {
|
|
162
|
+
appPid: this.input.appPid,
|
|
163
|
+
message: 'Data ready, start backup...',
|
|
164
|
+
progress: 20,
|
|
165
|
+
completed: false,
|
|
166
|
+
});
|
|
140
167
|
}
|
|
141
168
|
|
|
142
169
|
async syncToSpaces() {
|
|
143
|
-
const wallet = await this.getBlockletWallet();
|
|
144
170
|
const spaceClient = new SpaceClient({
|
|
145
|
-
endpoint: this.
|
|
146
|
-
wallet,
|
|
171
|
+
endpoint: this.spaceEndpoint,
|
|
172
|
+
wallet: this.securityContext.signer,
|
|
173
|
+
delegation: this.securityContext.delegation,
|
|
147
174
|
});
|
|
148
175
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
176
|
+
const { errorCount, message } = await spaceClient.send(
|
|
177
|
+
new BackupBlockletCommand({
|
|
178
|
+
appDid: this.blocklet.appPid,
|
|
179
|
+
appName: getAppName(this.blocklet),
|
|
180
|
+
appDescription: getAppDescription(this.blocklet),
|
|
181
|
+
userDid: this.input.userDid,
|
|
182
|
+
referrer: this.input.referrer,
|
|
183
|
+
source: join(this.backupDir, '/'),
|
|
154
184
|
debug: true,
|
|
155
|
-
concurrency:
|
|
156
|
-
retryCount:
|
|
185
|
+
concurrency: 32,
|
|
186
|
+
retryCount: 10,
|
|
157
187
|
filter: (object) => {
|
|
188
|
+
// FIXME: @yejianchao 这里需要更完整的黑名单
|
|
158
189
|
return object.name !== '.DS_Store';
|
|
159
190
|
},
|
|
191
|
+
onProgress: (data) => {
|
|
192
|
+
logger.info('backup progress', { appDid: this.input.appPid, data });
|
|
193
|
+
const percent = (data.completed * 100) / data.total;
|
|
194
|
+
this.input.event.emit(BlockletEvents.backupProgress, {
|
|
195
|
+
appPid: this.input.appPid,
|
|
196
|
+
message: `Uploaded file ${basename(data.key)} (${data.completed}/${data.total})`,
|
|
197
|
+
// 0.8 是因为上传文件到 spaces 占进度的 80%,+ 20 是因为需要累加之前的进度
|
|
198
|
+
progress: +Math.ceil(percent * 0.8).toFixed(2) + 20,
|
|
199
|
+
completed: false,
|
|
200
|
+
});
|
|
201
|
+
},
|
|
160
202
|
})
|
|
161
203
|
);
|
|
162
204
|
|
|
163
205
|
if (errorCount !== 0) {
|
|
164
|
-
throw new Error(`Sync to spaces encountered ${
|
|
206
|
+
throw new Error(`Sync to spaces encountered error: ${message}`);
|
|
165
207
|
}
|
|
166
208
|
}
|
|
167
209
|
|
|
168
|
-
async
|
|
169
|
-
const
|
|
210
|
+
async getSecurityContext() {
|
|
211
|
+
const blocklet = await states.blocklet.getBlocklet(this.input.appPid);
|
|
170
212
|
const nodeInfo = await states.node.read();
|
|
171
|
-
|
|
172
|
-
|
|
213
|
+
|
|
214
|
+
const { wallet, permanentWallet } = getBlockletInfo(blocklet, nodeInfo.sk);
|
|
215
|
+
|
|
216
|
+
// FIXME: @wangshijun migrated apps can not be restored, need to change core/webapp/api/routes/auth/verify-app-ownership.js
|
|
217
|
+
const { secretKey, address } = wallet; // we encrypt using latest wallet, not the permanent wallet
|
|
218
|
+
const password = toBuffer(Hasher.SHA3.hash256(Buffer.concat([secretKey, address].map(toBuffer))));
|
|
219
|
+
const encrypt = (v) => security.encrypt(v, address, password);
|
|
220
|
+
const decrypt = (v) => security.decrypt(v, address, password);
|
|
221
|
+
|
|
222
|
+
const delegation =
|
|
223
|
+
permanentWallet.address !== wallet.address
|
|
224
|
+
? signV2(permanentWallet.address, permanentWallet.secretKey, {
|
|
225
|
+
from: permanentWallet.address,
|
|
226
|
+
to: wallet.address,
|
|
227
|
+
userPk: permanentWallet.publicKey,
|
|
228
|
+
permissions: [{ role: 'DIDSpaceAgent', spaces: ['*'] }],
|
|
229
|
+
exp: Math.floor(new Date().getTime() / 1000) + 60 * 60,
|
|
230
|
+
})
|
|
231
|
+
: '';
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
signer: wallet,
|
|
235
|
+
delegation,
|
|
236
|
+
encrypt,
|
|
237
|
+
decrypt,
|
|
238
|
+
};
|
|
173
239
|
}
|
|
174
240
|
}
|
|
175
241
|
|
|
@@ -11,15 +11,7 @@ class BaseRestore {
|
|
|
11
11
|
* @type {string}
|
|
12
12
|
* @memberof BaseRestore
|
|
13
13
|
*/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
*
|
|
18
|
-
* @description spaces 的 endpoint
|
|
19
|
-
* @type {import('@ocap/wallet').WalletObject}
|
|
20
|
-
* @memberof BaseRestore
|
|
21
|
-
*/
|
|
22
|
-
blockletWallet;
|
|
14
|
+
restoreDir;
|
|
23
15
|
|
|
24
16
|
/**
|
|
25
17
|
*
|
|
@@ -27,7 +19,7 @@ class BaseRestore {
|
|
|
27
19
|
* @type {string}
|
|
28
20
|
* @memberof BaseRestore
|
|
29
21
|
*/
|
|
30
|
-
|
|
22
|
+
serverDir;
|
|
31
23
|
|
|
32
24
|
constructor(input) {
|
|
33
25
|
this.input = input;
|
|
@@ -40,14 +32,34 @@ class BaseRestore {
|
|
|
40
32
|
* @memberof BaseRestore
|
|
41
33
|
*/
|
|
42
34
|
ensureParams(spaces) {
|
|
43
|
-
this.
|
|
44
|
-
this.
|
|
45
|
-
this.serverDataDir = spaces.serverDataDir;
|
|
35
|
+
this.restoreDir = spaces.restoreDir;
|
|
36
|
+
this.serverDir = spaces.serverDir;
|
|
46
37
|
}
|
|
47
38
|
|
|
48
|
-
|
|
39
|
+
// eslint-disable-next-line
|
|
40
|
+
async import(params) {
|
|
49
41
|
throw new Error('not implemented');
|
|
50
42
|
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Generate params for BlockletManager to install
|
|
46
|
+
*
|
|
47
|
+
* @return {object}
|
|
48
|
+
* @memberof BaseRestore
|
|
49
|
+
*/
|
|
50
|
+
getInstallParams() {
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Generate params for other restorers
|
|
56
|
+
*
|
|
57
|
+
* @return {object}
|
|
58
|
+
* @memberof BaseRestore
|
|
59
|
+
*/
|
|
60
|
+
getImportParams() {
|
|
61
|
+
return {};
|
|
62
|
+
}
|
|
51
63
|
}
|
|
52
64
|
|
|
53
65
|
module.exports = {
|
|
@@ -1,85 +1,85 @@
|
|
|
1
1
|
const { removeSync, outputJsonSync, readJSONSync } = require('fs-extra');
|
|
2
|
-
const { cloneDeep,
|
|
2
|
+
const { cloneDeep, isEmpty } = require('lodash');
|
|
3
3
|
const { join } = require('path');
|
|
4
4
|
const security = require('@abtnode/util/lib/security');
|
|
5
|
+
const { BLOCKLET_CONFIGURABLE_KEY } = require('@blocklet/constant');
|
|
5
6
|
const { BaseRestore } = require('./base');
|
|
6
7
|
|
|
7
8
|
class BlockletExtrasRestore extends BaseRestore {
|
|
8
9
|
filename = 'blocklet-extras.json';
|
|
9
10
|
|
|
10
|
-
async import() {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
async import(params) {
|
|
12
|
+
const extras = this.getExtras();
|
|
13
|
+
this.cleanExtras(extras, params);
|
|
14
|
+
removeSync(join(this.restoreDir, this.filename));
|
|
15
|
+
outputJsonSync(join(this.restoreDir, this.filename), extras);
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
*
|
|
18
20
|
* @description
|
|
19
|
-
* @return {
|
|
21
|
+
* @return {import('@abtnode/client').BlockletState}
|
|
20
22
|
* @memberof BlockletExtrasRestore
|
|
21
23
|
*/
|
|
22
|
-
|
|
24
|
+
getExtras() {
|
|
23
25
|
/**
|
|
24
26
|
* @type {import('@abtnode/client').BlockletState}
|
|
25
27
|
*/
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return this.cleanData(blockletExtra);
|
|
28
|
+
return readJSONSync(join(this.restoreDir, this.filename));
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
*
|
|
33
33
|
* @description 清理数据并加密
|
|
34
|
-
* @param {import('@abtnode/client').BlockletState}
|
|
35
|
-
* @return {Promise<void>}
|
|
34
|
+
* @param {import('@abtnode/client').BlockletState} raw
|
|
36
35
|
* @memberof BlockletExtrasRestore
|
|
37
36
|
*/
|
|
38
|
-
async
|
|
39
|
-
const blockletExtra = cloneDeep(
|
|
37
|
+
async cleanExtras(raw, params) {
|
|
38
|
+
const blockletExtra = cloneDeep(raw);
|
|
40
39
|
|
|
41
40
|
const queue = [blockletExtra];
|
|
42
41
|
while (queue.length) {
|
|
43
|
-
const
|
|
42
|
+
const current = queue.pop();
|
|
44
43
|
|
|
45
44
|
// 加解密
|
|
46
|
-
this.
|
|
45
|
+
this.decryptConfigs(current.configs, params);
|
|
47
46
|
|
|
48
|
-
if (
|
|
49
|
-
queue.push(...
|
|
47
|
+
if (current?.children) {
|
|
48
|
+
queue.push(...current.children);
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
|
-
|
|
53
|
-
return blockletExtra;
|
|
54
51
|
}
|
|
55
52
|
|
|
56
53
|
/**
|
|
57
54
|
*
|
|
58
|
-
* @description
|
|
55
|
+
* @description 解密加密的数据
|
|
59
56
|
* @param {import('@abtnode/client').ConfigEntry[]} configs
|
|
57
|
+
* @param {object} params
|
|
60
58
|
* @return {void}
|
|
61
59
|
* @memberof BlockletExtrasRestore
|
|
62
60
|
*/
|
|
63
|
-
|
|
64
|
-
if (
|
|
61
|
+
decryptConfigs(configs, params) {
|
|
62
|
+
if (isEmpty(configs)) {
|
|
65
63
|
return;
|
|
66
64
|
}
|
|
67
65
|
|
|
66
|
+
const { password } = this.input;
|
|
68
67
|
for (const config of configs) {
|
|
69
|
-
// secure 为 true 的配置才需要被加密保存上次到 did spaces
|
|
70
68
|
if (config.secure) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// 再用 blocklet secret 加密,然后才可以上传到 spaces
|
|
74
|
-
const decryptByBlocklet = security.decrypt(
|
|
75
|
-
encryptByBlocklet,
|
|
76
|
-
this.blockletWallet.address,
|
|
77
|
-
Buffer.from(this.blockletWallet.secretKey)
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
config.value = decryptByBlocklet;
|
|
69
|
+
config.value = security.decrypt(config.value, params.salt, password);
|
|
81
70
|
}
|
|
82
71
|
}
|
|
72
|
+
|
|
73
|
+
const skConfig = configs.find((x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK);
|
|
74
|
+
if (skConfig) {
|
|
75
|
+
this.appSk = skConfig.value;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
getInstallParams() {
|
|
80
|
+
return {
|
|
81
|
+
appSk: this.appSk,
|
|
82
|
+
};
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|