oak-backend-base 4.1.8 → 4.1.10

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.
Files changed (2) hide show
  1. package/lib/Synchronizer.js +71 -10
  2. package/package.json +4 -4
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
+ const crypto_1 = require("crypto");
4
5
  const types_1 = require("oak-domain/lib/types");
5
6
  const relationPath_1 = require("oak-domain/lib/utils/relationPath");
6
7
  const assert_1 = tslib_1.__importDefault(require("assert"));
@@ -11,6 +12,52 @@ const uuid_1 = require("oak-domain/lib/utils/uuid");
11
12
  const lodash_2 = require("lodash");
12
13
  const OAK_SYNC_HEADER_ENTITY = 'oak-sync-entity';
13
14
  const OAK_SYNC_HEADER_ENTITY_ID = 'oak-sync-entity-id';
15
+ const OAK_SYNC_HEADER_TIMESTAMP = 'oak-sync-timestamp';
16
+ const OAK_SYNC_HEADER_NONCE = 'oak-sync-nonce';
17
+ const OAK_SYNC_HEADER_SIGN = 'oak-sync-sign';
18
+ function generateSignStr(body, ts, nonce) {
19
+ return `${body}\n${ts}\n${nonce}`;
20
+ }
21
+ async function sign(privateKey, body) {
22
+ const ts = Date.now();
23
+ const nonce = await (0, uuid_1.generateNewIdAsync)();
24
+ const sign2 = (0, crypto_1.createSign)('SHA256');
25
+ sign2.update(generateSignStr(body, `${ts}`, nonce));
26
+ sign2.end();
27
+ const signature = sign2.sign(privateKey).toString('hex');
28
+ return {
29
+ ts,
30
+ nonce,
31
+ signature,
32
+ };
33
+ }
34
+ function verify(publicKey, body, ts, nonce, signature) {
35
+ const verify2 = (0, crypto_1.createVerify)('SHA256');
36
+ verify2.update(generateSignStr(body, ts, nonce));
37
+ verify2.end();
38
+ return verify2.verify(publicKey, signature, 'hex');
39
+ }
40
+ async function fetchWithTimeout(url, options, timeout = 5000) {
41
+ const controller = new AbortController();
42
+ const signal = controller.signal;
43
+ // 设置超时
44
+ const timeoutId = setTimeout(() => {
45
+ controller.abort();
46
+ }, timeout);
47
+ // 发起 fetch 请求并传递 signal
48
+ return fetch(url, Object.assign({}, options, { signal }))
49
+ .then(response => {
50
+ clearTimeout(timeoutId); // 如果请求成功,清除超时
51
+ return response;
52
+ })
53
+ .catch(error => {
54
+ clearTimeout(timeoutId); // 如果请求失败,清除超时
55
+ if (error.name === 'AbortError') {
56
+ throw new types_1.OakRequestTimeoutException();
57
+ }
58
+ throw error; // 其他错误
59
+ });
60
+ }
14
61
  class Synchronizer {
15
62
  config;
16
63
  schema;
@@ -19,7 +66,7 @@ class Synchronizer {
19
66
  contextBuilder;
20
67
  pushAccessMap = {};
21
68
  async startChannel2(context, channel) {
22
- const { queue, api, selfEncryptInfo, entity, entityId, onFailed } = channel;
69
+ const { queue, api, selfEncryptInfo, entity, entityId, onFailed, timeout } = channel;
23
70
  // todo 加密
24
71
  const opers = queue.map(ele => ele.oper);
25
72
  if (process.env.NODE_ENV === 'development') {
@@ -31,15 +78,20 @@ class Synchronizer {
31
78
  const finalApi = (0, path_1.join)(api, selfEncryptInfo.id);
32
79
  channel.queue = [];
33
80
  try {
34
- const res = await fetch(finalApi, {
81
+ const body = JSON.stringify(opers);
82
+ const { ts, nonce, signature } = await sign(selfEncryptInfo.privateKey, body);
83
+ const res = await fetchWithTimeout(finalApi, {
35
84
  method: 'post',
36
85
  headers: {
37
86
  'Content-Type': 'application/json',
38
87
  [OAK_SYNC_HEADER_ENTITY]: entity,
39
88
  [OAK_SYNC_HEADER_ENTITY_ID]: entityId,
89
+ [OAK_SYNC_HEADER_TIMESTAMP]: `${ts}`,
90
+ [OAK_SYNC_HEADER_NONCE]: nonce,
91
+ [OAK_SYNC_HEADER_SIGN]: signature,
40
92
  },
41
- body: JSON.stringify(opers),
42
- });
93
+ body,
94
+ }, timeout || 5000);
43
95
  if (res.status !== 200) {
44
96
  throw new Error(`sync数据时,访问api「${finalApi}」的结果不是200。「${res.status}」`);
45
97
  }
@@ -133,7 +185,7 @@ class Synchronizer {
133
185
  }
134
186
  }));
135
187
  }
136
- pushOperToChannel(oper, userId, url, endpoint, remoteEntity, remoteEntityId, selfEncryptInfo, onSynchronized, onFailed) {
188
+ pushOperToChannel(oper, userId, url, endpoint, remoteEntity, remoteEntityId, selfEncryptInfo, onSynchronized, onFailed, timeout) {
137
189
  if (!this.channelDict[userId]) {
138
190
  // channel上缓存这些信息,暂不支持动态更新
139
191
  this.channelDict[userId] = {
@@ -143,6 +195,7 @@ class Synchronizer {
143
195
  entityId: remoteEntityId,
144
196
  selfEncryptInfo,
145
197
  onFailed,
198
+ timeout,
146
199
  };
147
200
  }
148
201
  else {
@@ -189,7 +242,7 @@ class Synchronizer {
189
242
  if (pushEntityNodes && pushEntityNodes.length > 0) {
190
243
  // 每个pushEntityNode代表配置的一个remoteEntity
191
244
  await Promise.all(pushEntityNodes.map(async (node) => {
192
- const { projection, groupByUsers, getRemotePushInfo: getRemoteAccessInfo, groupBySelfEntity, endpoint, actions, onSynchronized, onFailed } = node;
245
+ const { projection, groupByUsers, getRemotePushInfo: getRemoteAccessInfo, groupBySelfEntity, endpoint, actions, onSynchronized, onFailed, timeout } = node;
193
246
  // 定义中应该不可能没有actions
194
247
  if (!actions || actions.includes(action)) {
195
248
  const rows = await context.select(targetEntity, {
@@ -223,7 +276,7 @@ class Synchronizer {
223
276
  userId,
224
277
  remoteEntityId: entityId,
225
278
  });
226
- this.pushOperToChannel(oper2, userId, url, endpoint, entity, entityId, selfEncryptInfo, onSynchronized, onFailed);
279
+ this.pushOperToChannel(oper2, userId, url, endpoint, entity, entityId, selfEncryptInfo, onSynchronized, onFailed, timeout);
227
280
  };
228
281
  for (const userId in userSendDict) {
229
282
  if (userId !== operatorId || !oper.bornAt) {
@@ -334,7 +387,7 @@ class Synchronizer {
334
387
  const { remotes, self } = config;
335
388
  // 根据remotes定义,建立从entity到需要同步的远端结点信息的Map
336
389
  remotes.forEach((remote) => {
337
- const { getPushInfo, pushEntities: pushEntityDefs, endpoint, pathToUser, relationName: rnRemote, onFailed } = remote;
390
+ const { getPushInfo, pushEntities: pushEntityDefs, endpoint, pathToUser, relationName: rnRemote, onFailed, timeout } = remote;
338
391
  if (pushEntityDefs) {
339
392
  const pushEntities = [];
340
393
  const endpoint2 = (0, path_1.join)(endpoint || 'sync', self.entity);
@@ -402,6 +455,7 @@ class Synchronizer {
402
455
  actions,
403
456
  onSynchronized,
404
457
  onFailed,
458
+ timeout,
405
459
  }];
406
460
  }
407
461
  else {
@@ -415,6 +469,7 @@ class Synchronizer {
415
469
  actions,
416
470
  onSynchronized,
417
471
  onFailed,
472
+ timeout,
418
473
  });
419
474
  }
420
475
  }
@@ -462,7 +517,7 @@ class Synchronizer {
462
517
  fn: async (context, params, headers, req, body) => {
463
518
  // body中是传过来的oper数组信息
464
519
  const { entity, entityId } = params;
465
- const { [OAK_SYNC_HEADER_ENTITY]: meEntity, [OAK_SYNC_HEADER_ENTITY_ID]: meEntityId } = headers;
520
+ const { [OAK_SYNC_HEADER_ENTITY]: meEntity, [OAK_SYNC_HEADER_ENTITY_ID]: meEntityId, [OAK_SYNC_HEADER_NONCE]: syncNonce, [OAK_SYNC_HEADER_TIMESTAMP]: syncTs, [OAK_SYNC_HEADER_SIGN]: syncSign } = headers;
466
521
  if (process.env.NODE_ENV === 'development') {
467
522
  console.log('接收到来自远端的sync数据', entity, JSON.stringify(body));
468
523
  }
@@ -493,7 +548,13 @@ class Synchronizer {
493
548
  if (cxtInfo) {
494
549
  await context.initialize(cxtInfo);
495
550
  }
496
- // todo 解密
551
+ const syncTimestamp = parseInt(syncTs, 10);
552
+ if (!(Date.now() - syncTimestamp < 10000)) {
553
+ throw new Error('同步时钟漂移过长');
554
+ }
555
+ if (!verify(publicKey, JSON.stringify(body), syncTs, syncNonce, syncSign)) {
556
+ throw new Error('sync验签失败');
557
+ }
497
558
  const opers = body;
498
559
  const ids = opers.map(ele => ele.id);
499
560
  const existsIds = (await context.select('oper', {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oak-backend-base",
3
- "version": "4.1.8",
3
+ "version": "4.1.10",
4
4
  "description": "oak-backend-base",
5
5
  "main": "lib/index",
6
6
  "author": {
@@ -20,10 +20,10 @@
20
20
  "mysql": "^2.18.1",
21
21
  "mysql2": "^2.3.3",
22
22
  "node-schedule": "^2.1.0",
23
- "oak-common-aspect": "^3.0.2",
23
+ "oak-common-aspect": "^3.0.4",
24
24
  "oak-db": "^3.3.2",
25
- "oak-domain": "^5.1.9",
26
- "oak-frontend-base": "^5.3.18",
25
+ "oak-domain": "^5.1.10",
26
+ "oak-frontend-base": "^5.3.19",
27
27
  "socket.io": "^4.7.2",
28
28
  "socket.io-client": "^4.7.2",
29
29
  "uuid": "^8.3.2"