abler-api 1.0.78 → 1.0.80

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/dist/cjs/pp-util.js +195 -20
  2. package/package.json +7 -7
@@ -291,6 +291,12 @@ class apiUtil$2 {
291
291
  };
292
292
  };
293
293
  static appAsPrefix = '?';
294
+ static cekEncryptOptions = {
295
+ algorithm: "aes-256-gcm",
296
+ // iv: 随机
297
+ encryptEncoding: "base64",
298
+ decryptEncoding: "utf8"
299
+ };
294
300
 
295
301
  // static apiCallRecSaver;
296
302
 
@@ -1362,32 +1368,155 @@ class apiUtil$2 {
1362
1368
  static corsCheckOrigin(origin, callback) {
1363
1369
  callback(null, apiUtil$2.isOriginAllowed(origin));
1364
1370
  }
1371
+ static async restoreObj(key) {
1372
+ return kvStorage.restoreObj(key).catch(() => null);
1373
+ }
1374
+
1375
+ //#region ==== CEK 加解密及签名 ====
1376
+
1377
+ // 应用配置:
1378
+ // appSetting:
1379
+ // e2eEncryptionNeeded
1380
+ // cekRotateTime
1381
+ // errCfg:
1382
+ // ACCESS_REFUSED
1383
+ // CEK_EXPIRED
1384
+ // PUBLIC_ENCRYPT_FAIL
1385
+ // DECRYPT_FAIL
1386
+ // dbSql
1387
+ // APP_QUERY
1388
+
1389
+ static async rotateCek(req) {
1390
+ try {
1391
+ const appInfo = req.appInfo;
1392
+ const apiKey = appInfo.apiKey;
1393
+ let cek = null;
1394
+ if (appSetting.e2eEncryptionNeeded) {
1395
+ await this.storePreviousCek(apiKey, req);
1396
+ // 生成新的CEK
1397
+ cek = await this.generateCek(appInfo, req);
1398
+ console.log("已生成新的CEK", cek);
1399
+ }
1400
+ let result = this.apiSuccess(cek, req);
1401
+ delete result.message; //减少数据尺寸,否则数据太长无法用公钥加密
1402
+ delete result._elapse;
1403
+ // options._res.publicKey = appInfo.publicKey; // 设置用于加密结果的公钥
1404
+ return result;
1405
+ } catch (e) {
1406
+ return apiUtil$2.apiFail(e, req);
1407
+ }
1408
+ }
1409
+ static cekExpired(cek) {
1410
+ return new Date().valueOf() >= cek.expireAt;
1411
+ }
1365
1412
  static validCek(cek, checkExpired = true) {
1366
1413
  if (!cek) {
1367
- throw [errCfg.ACCESS_REFUSED, t("CEK(内容加密密钥)尚未申请或已过期")];
1414
+ throw [this.errCfg.CEK_EXPIRED, t("或尚未申请")];
1368
1415
  }
1369
- if (checkExpired && new Date().valueOf() >= cek.expireAt) {
1370
- throw errCfg.CEK_EXPIRED;
1416
+ if (checkExpired && this.cekExpired(cek)) {
1417
+ throw this.errCfg.CEK_EXPIRED;
1371
1418
  }
1372
1419
  }
1420
+ static encryptContentText(contentText, cek) {
1421
+ return ppCrypto.encryptData(contentText, cek.key, this.cekEncryptOptions);
1422
+ }
1373
1423
  static encryptContent(content, cek) {
1374
- return ppCrypto.encryptData(content, cek.key, 'base64');
1424
+ // let contentText = commonUtil.orderedJsonStringify(content);
1425
+ let contentText = JSON.stringify(content);
1426
+ return this.encryptContentText(contentText, cek);
1427
+ }
1428
+ static encryptResponseContent(response, result) {
1429
+ if (!appSetting.e2eEncryptionNeeded) {
1430
+ if (response.traceId) {
1431
+ console.log(1, response.traceId, 'Response:', result);
1432
+ }
1433
+ return result;
1434
+ }
1435
+ if (response.traceId) {
1436
+ console.log(1, response.traceId, 'Response before encrypt:', result);
1437
+ }
1438
+ const {
1439
+ publicKey,
1440
+ cek
1441
+ } = response;
1442
+ if (cek) {
1443
+ if (response.newCek) {
1444
+ result.newCek = response.newCek;
1445
+ }
1446
+ if (response.traceId) {
1447
+ console.log(1, response.traceId, 'CEK:', cek);
1448
+ }
1449
+ result = {
1450
+ encryptedContent: this.encryptContent(result, cek)
1451
+ };
1452
+ } else if (publicKey) {
1453
+ if (response.traceId) {
1454
+ console.log(1, response.traceId, 'PUBLIC KEY:', publicKey);
1455
+ }
1456
+ const message = Buffer.from(JSON.stringify(result), "utf8");
1457
+ try {
1458
+ result = ppCrypto.rsaEncrypt({
1459
+ publicKey,
1460
+ message
1461
+ });
1462
+ } catch (e) {
1463
+ result = apiUtil$2.apiFail([errCfg.PUBLIC_ENCRYPT_FAIL, e.message], response.req);
1464
+ }
1465
+ }
1466
+ if (response.traceId) {
1467
+ console.log(1, response.traceId, 'Response after encrypt:', result);
1468
+ }
1469
+ return result;
1375
1470
  }
1376
- static decryptContent(content, cek) {
1471
+ static decryptContentText(content, cek) {
1377
1472
  this.validCek(cek);
1378
1473
  try {
1379
- const result = ppCrypto.decryptData(content, cek.key, 'base64', 'utf8');
1380
- if (result.startsWith('{') || result.startsWith('[')) {
1381
- return JSON.parse(result);
1382
- }
1383
- return result;
1474
+ return ppCrypto.decryptData(content, cek.key, this.cekEncryptOptions);
1384
1475
  } catch (e) {
1385
- throw [errCfg.DECRYPT_FAIL, e.message];
1476
+ // console.log("内容", content);
1477
+ // const req = RequestContext.get('req');
1478
+ // console.log("headers", req.headers);
1479
+ // console.error(e);
1480
+ throw [this.errCfg.DECRYPT_FAIL, e.message];
1386
1481
  }
1387
1482
  }
1388
- static decryptRequestContent(req, res) {
1483
+ static decryptContent(content, cek) {
1484
+ let contentText = this.decryptContentText(content, cek);
1485
+ if (contentText.startsWith('{') || contentText.startsWith('[')) {
1486
+ return JSON.parse(contentText);
1487
+ }
1488
+ return contentText;
1489
+ }
1490
+
1491
+ // static decryptRequestContent(req, res) {
1492
+ // if (!appSetting.e2eEncryptionNeeded)
1493
+ // return;
1494
+ // let params = apiUtil.extractParams(req);
1495
+ // if (params.encryptedContent) {
1496
+ // if (['DELETE', 'GET'].contains(req.method)) {
1497
+ // params.encryptedContent = decodeURIComponent(params.encryptedContent);
1498
+ // }
1499
+ // if (!req.appInfo) {
1500
+ // throw t('应用系统信息未知');
1501
+ // }
1502
+ // const decryptedContent = apiUtil.decryptContent(params.encryptedContent, req.appInfo.cek);
1503
+ // delete params.encryptedContent;
1504
+ // Object.assign(req.params, decryptedContent);
1505
+ // }
1506
+ // if (res) {
1507
+ // apiUtil.validCek(req.appInfo.cek);
1508
+ // res.cek = req.appInfo.cek; // 设置用于对称加密的CEK
1509
+ // }
1510
+ // }
1511
+
1512
+ static async decryptRequestContent(req, res) {
1389
1513
  if (!appSetting.e2eEncryptionNeeded) return;
1514
+ const appInfo = req.appInfo;
1390
1515
  let params = apiUtil$2.extractParams(req);
1516
+ if (req.traceId) {
1517
+ console.log(1, req.traceId, 'Request Params before decrypt:', params);
1518
+ console.log(1, req.traceId, 'CEK:', appInfo.cek);
1519
+ }
1391
1520
  if (params.encryptedContent) {
1392
1521
  if (['DELETE', 'GET'].contains(req.method)) {
1393
1522
  params.encryptedContent = decodeURIComponent(params.encryptedContent);
@@ -1395,23 +1524,67 @@ class apiUtil$2 {
1395
1524
  if (!req.appInfo) {
1396
1525
  throw t('应用系统信息未知');
1397
1526
  }
1398
- const decryptedContent = apiUtil$2.decryptContent(params.encryptedContent, req.appInfo.cek);
1527
+ let decryptedContent;
1528
+ try {
1529
+ if (req.traceId) {
1530
+ const dataText = this.decryptContentText(params.encryptedContent, appInfo.cek);
1531
+ console.log(1, req.traceId, 'Decrypted text:', dataText);
1532
+ }
1533
+ decryptedContent = this.decryptContent(params.encryptedContent, appInfo.cek);
1534
+ } catch (e) {
1535
+ let cek = appInfo.cek;
1536
+ console.log("解密失败 CEK", cek?.key, new Date(cek?.expireAt).format('MM.dd hh:mm:ss'));
1537
+ // 尝试用前一个 CEK 解密
1538
+ const previousCek = await this.restorePreviousCek(appInfo.apiKey, req);
1539
+ if (!previousCek) {
1540
+ throw e;
1541
+ }
1542
+ cek = previousCek;
1543
+ console.log("换用前任 CEK", cek.key, new Date(cek.expireAt).format('MM.dd hh:mm:ss'));
1544
+ try {
1545
+ decryptedContent = this.decryptContent(params.encryptedContent, cek);
1546
+ if (res) {
1547
+ res.newCek = appInfo.cek;
1548
+ }
1549
+ appInfo.cek = cek;
1550
+ } catch (e) {
1551
+ console.log("换用前任解密依然失败", "ApiKey", appInfo.apiKey, "x-api-key", req.headers["x-api-key"] || req.headers["api-key"]);
1552
+ throw e;
1553
+ }
1554
+ }
1399
1555
  delete params.encryptedContent;
1400
- Object.assign(req.params, decryptedContent);
1556
+ Object.assign(params, decryptedContent);
1557
+ }
1558
+ if (req.traceId) {
1559
+ console.log(1, req.traceId, 'Request Params after decrypt:', params);
1401
1560
  }
1402
1561
  if (res) {
1403
- apiUtil$2.validCek(req.appInfo.cek);
1404
- res.cek = req.appInfo.cek; // 设置用于对称加密的CEK
1562
+ this.validCek(appInfo.cek);
1563
+ res.cek = appInfo.cek; // 设置用于对称加密的CEK
1405
1564
  }
1406
1565
  }
1407
- static async restoreObj(key) {
1408
- return kvStorage.restoreObj(key).catch(() => null);
1566
+ static async storePreviousCek(apiKey, req) {
1567
+ let cekKey = this.cekStoreKey(apiKey, req);
1568
+ const previousCek = await this.restoreObj(cekKey);
1569
+ if (previousCek && !this.cekExpired(previousCek)) {
1570
+ // 轮换期间保留旧版本一段时间
1571
+ cekKey += "_p";
1572
+ await kvStorage.storeObj(cekKey, previousCek, appSetting.cekRotateTime);
1573
+ }
1574
+ }
1575
+ static async restorePreviousCek(apiKey, req) {
1576
+ let cekKey = this.cekStoreKey(apiKey, req) + "_p";
1577
+ return await this.restoreObj(cekKey);
1409
1578
  }
1410
1579
  static cekStoreKey(apiKey, req) {
1411
- return `cek_${this.appAsPrefix}_${apiKey}_${apiUtil$2.getClientIp(req)}`;
1580
+ // return `cek_${this.appAsPrefix}_${apiKey}_${apiUtil.getClientIp(req)}`
1581
+ let key = `cek_${this.appAsPrefix}_${apiKey}`;
1582
+ if (req?.apiClientId) {
1583
+ return `${key}@${req.apiClientId}`;
1584
+ }
1585
+ return key;
1412
1586
  }
1413
1587
  static async generateCek(appInfo, req, key) {
1414
- //todo: 检查既有 cek
1415
1588
  let t = new Date().valueOf();
1416
1589
  const cek = {
1417
1590
  key: key || ppUtil$3.newGuid() + ppUtil$3.newGuid(),
@@ -1605,6 +1778,8 @@ class apiUtil$2 {
1605
1778
 
1606
1779
  //#endregion
1607
1780
 
1781
+ //#endregion
1782
+
1608
1783
  //region ====中间件
1609
1784
 
1610
1785
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abler-api",
3
- "version": "1.0.78",
3
+ "version": "1.0.80",
4
4
  "description": "API服务相关工具",
5
5
  "main": "./dist/cjs/pp-util.js",
6
6
  "-module": "./dist/es/pp-util.js",
@@ -15,13 +15,13 @@
15
15
  "author": "peng_peng",
16
16
  "license": "ISC",
17
17
  "dependencies": {
18
- "abler-db": "^1.0.86",
19
- "abler-i18n": "^1.0.21",
20
- "abler-messenger": "^1.1.41",
21
- "abler-net": "^1.0.42",
22
- "abler-util": "^1.0.40",
18
+ "abler-db": "^1.0.88",
19
+ "abler-i18n": "^1.0.22",
20
+ "abler-messenger": "^1.1.43",
21
+ "abler-net": "^1.0.44",
22
+ "abler-util": "^1.0.42",
23
23
  "basic-auth": "^2.0.1",
24
24
  "node-cron": "^3.0.1"
25
25
  },
26
- "gitHead": "5ceb8a2723650bc8c2a3a8c200c1e7441db9dda5"
26
+ "gitHead": "24a18765761c03e679c089ea9c0c6eb255ea95e6"
27
27
  }