abler-api 0.1.81 → 0.2.3
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/dist/cjs/pp-util.js +292 -48
- package/package.json +2 -2
package/dist/cjs/pp-util.js
CHANGED
|
@@ -37,7 +37,7 @@ const {
|
|
|
37
37
|
kvStorage: kvStorage$1,
|
|
38
38
|
redisSimulator
|
|
39
39
|
} = require$$4__default["default"];
|
|
40
|
-
let conf$3, err$
|
|
40
|
+
let conf$3, err$1;
|
|
41
41
|
// const dbCheck = require('../dbupdate/dd-version');
|
|
42
42
|
|
|
43
43
|
/**
|
|
@@ -57,7 +57,7 @@ class preconditions$2 {
|
|
|
57
57
|
static setAppName(aName) {
|
|
58
58
|
ppUtil$4.configNeeded();
|
|
59
59
|
conf$3 = ppUtil$4.appConfig;
|
|
60
|
-
err$
|
|
60
|
+
err$1 = ppUtil$4.appErrCfg;
|
|
61
61
|
preconditions$2.appName = aName || conf$3.thisApp;
|
|
62
62
|
return preconditions$2;
|
|
63
63
|
}
|
|
@@ -167,12 +167,15 @@ async function createEnvSettingFile(fileName, lastEnvSettingEx) {
|
|
|
167
167
|
}
|
|
168
168
|
return t_f$3("已創建新的本機環境配置文件\n%s\n请檢查並正確設置各項內容,然後將配置版本號修改為%s", fileName, conf$3._envSettings.default.cfgVersion);
|
|
169
169
|
} catch (e) {
|
|
170
|
-
return t_f$3("創建本機環境配置文件失敗\n《%s》\n%s", fileName, err$
|
|
170
|
+
return t_f$3("創建本機環境配置文件失敗\n《%s》\n%s", fileName, err$1.ERROR(e).message);
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
async function checkExEnvSetting(loadjs) {
|
|
174
174
|
if (conf$3._envSetting.localSettingImported) return "";
|
|
175
175
|
try {
|
|
176
|
+
if (typeof conf$3.getEnvSettingFileName !== "function") {
|
|
177
|
+
throw new Error("必须在 config 中定义 getEnvSettingFileName 方法获取本地配置文件名");
|
|
178
|
+
}
|
|
176
179
|
let fileName = conf$3.getEnvSettingFileName();
|
|
177
180
|
if (!fs$1.existsSync(fileName)) {
|
|
178
181
|
return await createEnvSettingFile(fileName);
|
|
@@ -222,7 +225,7 @@ async function checkRedis() {
|
|
|
222
225
|
}
|
|
223
226
|
async function initDb(dbSql) {
|
|
224
227
|
try {
|
|
225
|
-
dbUtil$1.config(conf$3, err$
|
|
228
|
+
dbUtil$1.config(conf$3, err$1, dbSql);
|
|
226
229
|
} catch (e) {
|
|
227
230
|
if (e.reason === 'bad decrypt') {
|
|
228
231
|
e = new Error(t_f$3("解密数据库密码(%s)失败", conf$3.dbconn.dbpwd));
|
|
@@ -235,7 +238,7 @@ async function initDb(dbSql) {
|
|
|
235
238
|
}
|
|
236
239
|
async function initKvStorage(dbSql) {
|
|
237
240
|
try {
|
|
238
|
-
kvStorage$1.config(conf$3, err$
|
|
241
|
+
kvStorage$1.config(conf$3, err$1);
|
|
239
242
|
if (conf$3.redis.enabled) {
|
|
240
243
|
kvStorage$1.setRedis(ppRedis.newClient("kv"));
|
|
241
244
|
} else {
|
|
@@ -258,7 +261,8 @@ const {
|
|
|
258
261
|
t
|
|
259
262
|
} = require$$2__default["default"];
|
|
260
263
|
const {
|
|
261
|
-
ppUtil: ppUtil$3
|
|
264
|
+
ppUtil: ppUtil$3,
|
|
265
|
+
ppCrypto
|
|
262
266
|
} = require$$3__default["default"];
|
|
263
267
|
const {
|
|
264
268
|
dbUtil,
|
|
@@ -268,7 +272,7 @@ const {
|
|
|
268
272
|
netUtil: netUtil$1
|
|
269
273
|
} = require$$6__default["default"];
|
|
270
274
|
const preconditions$1 = ppPrecond;
|
|
271
|
-
let conf$2, appSetting,
|
|
275
|
+
let conf$2, appSetting, errCfg, dbSql;
|
|
272
276
|
const pnToken = "access_token",
|
|
273
277
|
hnToken = pnToken,
|
|
274
278
|
pnApiKey = "apiKey",
|
|
@@ -276,8 +280,6 @@ const pnToken = "access_token",
|
|
|
276
280
|
// 我们接收到的 headers 中的字段名总是全小写的
|
|
277
281
|
pnApiSecret = "apiSecret",
|
|
278
282
|
hnApiSecret = pnApiSecret.toLowerCase();
|
|
279
|
-
const MD5 = ppUtil$3.MD5,
|
|
280
|
-
moveProperty = ppUtil$3.moveProperty;
|
|
281
283
|
class apiUtil$2 {
|
|
282
284
|
static debugFlag = ppUtil$3.newGuid(); //应用必须设置,否则谁也不知道是啥
|
|
283
285
|
static testFlag = ppUtil$3.newGuid();
|
|
@@ -288,13 +290,14 @@ class apiUtil$2 {
|
|
|
288
290
|
message: 'no basic auth validator'
|
|
289
291
|
};
|
|
290
292
|
};
|
|
293
|
+
static appAsPrefix = '?';
|
|
291
294
|
|
|
292
295
|
// static apiCallRecSaver;
|
|
293
296
|
|
|
294
297
|
static config(appConfig, appErrCfg, appDbSql) {
|
|
295
298
|
conf$2 = appConfig;
|
|
296
299
|
appSetting = conf$2?.appSetting;
|
|
297
|
-
|
|
300
|
+
errCfg = appErrCfg;
|
|
298
301
|
dbSql = appDbSql;
|
|
299
302
|
ppUtil$3.config(appConfig, appErrCfg);
|
|
300
303
|
kvStorage.config(appConfig, appErrCfg);
|
|
@@ -302,6 +305,7 @@ class apiUtil$2 {
|
|
|
302
305
|
apiUtil$2.testFlag = appSetting?.testFlag || apiUtil$2.testFlag;
|
|
303
306
|
// apiUtil.apiCallRecSaver = apiCallRecSaver;
|
|
304
307
|
preconditions$1.setAppName();
|
|
308
|
+
apiUtil$2.appAsPrefix = conf$2.thisApp.toLowerCase();
|
|
305
309
|
}
|
|
306
310
|
|
|
307
311
|
//#region ===== 需要应用系统重写的方法
|
|
@@ -406,7 +410,7 @@ class apiUtil$2 {
|
|
|
406
410
|
if (this[name] === undefined) return defaultValue;
|
|
407
411
|
let result = +this[name];
|
|
408
412
|
if (typeof result !== "number" || result < minValue || result > maxValue) {
|
|
409
|
-
throw [
|
|
413
|
+
throw [errCfg.INVALID_PARAM, t_f$2("参数 %s 必须是 %s ~ %s 之间的数字", name, minValue, maxValue)];
|
|
410
414
|
}
|
|
411
415
|
return result;
|
|
412
416
|
});
|
|
@@ -471,7 +475,7 @@ class apiUtil$2 {
|
|
|
471
475
|
*/
|
|
472
476
|
static apiFail(error, req, spOrderNum) {
|
|
473
477
|
configNeeded();
|
|
474
|
-
let response =
|
|
478
|
+
let response = errCfg.ERROR(error, errCfg.errorLangParamFlag + ppUtil$3.getMsgLang(req));
|
|
475
479
|
response.datetime = new Date();
|
|
476
480
|
if (spOrderNum) {
|
|
477
481
|
response.spOrderNum = spOrderNum;
|
|
@@ -679,11 +683,11 @@ class apiUtil$2 {
|
|
|
679
683
|
delete params.token;
|
|
680
684
|
} else {
|
|
681
685
|
// 如果没有token,则返回错误
|
|
682
|
-
console.log(
|
|
686
|
+
console.log(errCfg.TOKEN_NEEDED);
|
|
683
687
|
if (res) {
|
|
684
|
-
res.send(
|
|
688
|
+
res.send(errCfg.ERROR(errCfg.TOKEN_NEEDED));
|
|
685
689
|
} else if (!noErr) {
|
|
686
|
-
throw
|
|
690
|
+
throw errCfg.TOKEN_NEEDED;
|
|
687
691
|
}
|
|
688
692
|
}
|
|
689
693
|
}
|
|
@@ -701,7 +705,7 @@ class apiUtil$2 {
|
|
|
701
705
|
// return await checkInternalToken(req);
|
|
702
706
|
const params = apiUtil$2.extractParams(req);
|
|
703
707
|
req.accessToken = req.accessToken || apiUtil$2.extractToken(req);
|
|
704
|
-
if (req.accessToken === MD5(ppUtil$3.commonHashDisturbing)) {
|
|
708
|
+
if (req.accessToken === ppCrypto.MD5(ppUtil$3.commonHashDisturbing)) {
|
|
705
709
|
//todo: 检查IP
|
|
706
710
|
params.__internal__ = true;
|
|
707
711
|
req.ignoreToken = true;
|
|
@@ -722,7 +726,7 @@ class apiUtil$2 {
|
|
|
722
726
|
return true;
|
|
723
727
|
} else {
|
|
724
728
|
// console.log('token error.', req.accessToken);
|
|
725
|
-
throw
|
|
729
|
+
throw errCfg.TOKEN_INVALID;
|
|
726
730
|
}
|
|
727
731
|
}
|
|
728
732
|
|
|
@@ -755,7 +759,7 @@ class apiUtil$2 {
|
|
|
755
759
|
let dbgToken = req.headers['__debug__'] || req.headers[apiUtil$2.debugFlag];
|
|
756
760
|
if (dbgToken) {
|
|
757
761
|
let envId = process.env.ECS_DEPLOY_ID;
|
|
758
|
-
req.__debug__ = dbgToken && dbgToken === MD5(req.headers.timestamp, ppUtil$3.commonHashDisturbing) && (envId === "florist_longdan" || envId === "shuzi");
|
|
762
|
+
req.__debug__ = dbgToken && dbgToken === ppCrypto.MD5(req.headers.timestamp, ppUtil$3.commonHashDisturbing) && (envId === "florist_longdan" || envId === "shuzi");
|
|
759
763
|
} else {
|
|
760
764
|
req.__debug__ = false;
|
|
761
765
|
}
|
|
@@ -775,7 +779,7 @@ class apiUtil$2 {
|
|
|
775
779
|
let dbgToken = req.headers["__postman__"] || req.headers[apiUtil$2.testFlag];
|
|
776
780
|
if (dbgToken) {
|
|
777
781
|
let envId = process.env.ECS_DEPLOY_ID;
|
|
778
|
-
let testToken = MD5(req.headers.timestamp, apiUtil$2.extractToken(req, null, true) || req.headers.apikey);
|
|
782
|
+
let testToken = ppCrypto.MD5(req.headers.timestamp, apiUtil$2.extractToken(req, null, true) || req.headers.apikey);
|
|
779
783
|
req.__postman__ = dbgToken === testToken && (envId.indexOf("myfacesign.com") < 0 || envId.indexOf(".dev.") > 0);
|
|
780
784
|
} else {
|
|
781
785
|
req.__postman__ = false;
|
|
@@ -995,7 +999,7 @@ class apiUtil$2 {
|
|
|
995
999
|
return certInfo;
|
|
996
1000
|
}).catch(e => {
|
|
997
1001
|
console.log('获取企业证书失败:\n', e);
|
|
998
|
-
throw [
|
|
1002
|
+
throw [errCfg.ACCESS_REFUSED, t_f$2("获取企业证书失败:%s", errCfg.ERROR(e).message)];
|
|
999
1003
|
});
|
|
1000
1004
|
}
|
|
1001
1005
|
|
|
@@ -1005,20 +1009,20 @@ class apiUtil$2 {
|
|
|
1005
1009
|
* @param req
|
|
1006
1010
|
* @returns {Promise<*|boolean>}
|
|
1007
1011
|
*/
|
|
1008
|
-
static async
|
|
1012
|
+
static async verifyApiTokenSignature(tokenData, req) {
|
|
1009
1013
|
try {
|
|
1010
1014
|
if (!req.headers.timestamp) {
|
|
1011
|
-
throw [
|
|
1015
|
+
throw [errCfg.ACCESS_REFUSED, t_f$2("必须在请求头中设置时戳")];
|
|
1012
1016
|
}
|
|
1013
1017
|
let timestamp = parseInt(req.headers.timestamp);
|
|
1014
1018
|
let currentTime = new Date().valueOf();
|
|
1015
1019
|
let maxTimeDiff = apiUtil$2.isDebugMode(req) ? 7 * 24 * 3600 : apiUtil$2.isTestMode(req) ? 4 * 3600 : 100; // 100
|
|
1016
1020
|
if (timestamp.toString() == "NaN" || currentTime < timestamp - 5000 || currentTime - timestamp > maxTimeDiff * 1000) {
|
|
1017
|
-
throw [
|
|
1021
|
+
throw [errCfg.ACCESS_REFUSED, t_f$2("时戳(%s)无效,当前时戳:%s,时差 %s秒", timestamp, currentTime, (currentTime - timestamp) / 1000)];
|
|
1018
1022
|
}
|
|
1019
1023
|
let signature = req.headers.signature;
|
|
1020
1024
|
if (!signature) {
|
|
1021
|
-
throw [
|
|
1025
|
+
throw [errCfg.ACCESS_REFUSED, t_f$2("必须在请求头中提供签名")];
|
|
1022
1026
|
}
|
|
1023
1027
|
let signData = apiUtil$2.extractToken(req, null, true) || tokenData.apiKey;
|
|
1024
1028
|
signData += timestamp;
|
|
@@ -1064,7 +1068,7 @@ class apiUtil$2 {
|
|
|
1064
1068
|
verifyOK = false;
|
|
1065
1069
|
if (!tokenData.certPublicKeySpare) {
|
|
1066
1070
|
throw {
|
|
1067
|
-
eo:
|
|
1071
|
+
eo: errCfg.ACCESS_REFUSED,
|
|
1068
1072
|
msgArgv: t("签名验证异常"),
|
|
1069
1073
|
data: e
|
|
1070
1074
|
};
|
|
@@ -1109,7 +1113,7 @@ class apiUtil$2 {
|
|
|
1109
1113
|
} catch (e) {
|
|
1110
1114
|
console.log("SPO服务签名验证异常(备用证书), ", e);
|
|
1111
1115
|
throw {
|
|
1112
|
-
eo:
|
|
1116
|
+
eo: errCfg.ACCESS_REFUSED,
|
|
1113
1117
|
msgArgv: t("签名验证异常(spare)"),
|
|
1114
1118
|
data: e
|
|
1115
1119
|
};
|
|
@@ -1117,7 +1121,7 @@ class apiUtil$2 {
|
|
|
1117
1121
|
}
|
|
1118
1122
|
if (!verifyOK) {
|
|
1119
1123
|
console.log("SPO服务签名验证失败, tokenData: ", tokenData);
|
|
1120
|
-
throw [
|
|
1124
|
+
throw [errCfg.ACCESS_REFUSED, t("签名验证未通过")];
|
|
1121
1125
|
}
|
|
1122
1126
|
return verifyOK;
|
|
1123
1127
|
}
|
|
@@ -1133,26 +1137,26 @@ class apiUtil$2 {
|
|
|
1133
1137
|
if (!tokenData.companyInfo) {
|
|
1134
1138
|
tokenData.companyInfo = await apiUtil$2.queryCompanyInfo(tokenData.apiKey, true).catch(e => {
|
|
1135
1139
|
console.log("执行企业信息查询发生异常", e);
|
|
1136
|
-
throw [
|
|
1140
|
+
throw [errCfg.API_KEY_INVALID, t("执行企业信息查询发生异常:") + e.message || ""];
|
|
1137
1141
|
});
|
|
1138
1142
|
}
|
|
1139
1143
|
const companyInfo = tokenData.companyInfo;
|
|
1140
1144
|
if (companyInfo != null) {
|
|
1141
1145
|
let secret = apiUtil$2._getApiSecret(tokenData.apiKey);
|
|
1142
|
-
if (secret !== tokenData.apiSecret) throw
|
|
1146
|
+
if (secret !== tokenData.apiSecret) throw errCfg.API_SCREPT_INVALID;
|
|
1143
1147
|
// 不再检查 IP 白名单,改为验证证书签名
|
|
1144
1148
|
// console.log("fromIp:", tokenData.clientIp, "ipWhiteList:", companyInfo.ipWhiteList);
|
|
1145
1149
|
// let whiteList = companyInfo.ipWhiteList || "";
|
|
1146
1150
|
// // 没有设置ip白名单的就不检查了
|
|
1147
1151
|
// if (whiteList == "" || ipMatched(tokenData.clientIp, whiteList + ",127.0.0.1,1"))
|
|
1148
1152
|
if (apiUtil$2.signatureVerified(req)) return companyInfo;
|
|
1149
|
-
return apiUtil$2.
|
|
1153
|
+
return apiUtil$2.verifyApiTokenSignature(tokenData, req).then(x => {
|
|
1150
1154
|
return companyInfo;
|
|
1151
1155
|
}, e => {
|
|
1152
1156
|
throw e;
|
|
1153
1157
|
});
|
|
1154
1158
|
}
|
|
1155
|
-
throw [
|
|
1159
|
+
throw [errCfg.API_KEY_INVALID, tokenData.apiKey];
|
|
1156
1160
|
}
|
|
1157
1161
|
|
|
1158
1162
|
/**
|
|
@@ -1195,7 +1199,7 @@ class apiUtil$2 {
|
|
|
1195
1199
|
} else {
|
|
1196
1200
|
paramExists = typeof params[paramName] != 'undefined' || typeof params[paramName + paramSufix] != 'undefined';
|
|
1197
1201
|
}
|
|
1198
|
-
if (!paramExists) return ppUtil$3.errorPormise(
|
|
1202
|
+
if (!paramExists) return ppUtil$3.errorPormise(errCfg.ERROR(errCfg.PARAMETER_NEEDED, paramName));
|
|
1199
1203
|
}
|
|
1200
1204
|
return Promise.resolve(paramSufix);
|
|
1201
1205
|
}
|
|
@@ -1261,7 +1265,7 @@ class apiUtil$2 {
|
|
|
1261
1265
|
const myEnvHosts = apiUtil$2.getEnvHosts(params);
|
|
1262
1266
|
const myIp = ppUtil$3.getMyIp();
|
|
1263
1267
|
const host = params.host || myIp;
|
|
1264
|
-
if (!myEnvHosts.hosts.contains(host)) throw [
|
|
1268
|
+
if (!myEnvHosts.hosts.contains(host)) throw [errCfg.ACCESS_REFUSED, t_f$2("环境%s貌似无此主机:%s", conf$2.envId, params.host)];
|
|
1265
1269
|
if (!apiUtil$2.hostIsMySelf(host, options._res.req)) {
|
|
1266
1270
|
let result = await apiUtil$2.forwardsTo(host, myEnvHosts.port, params, options._res.req, myIp);
|
|
1267
1271
|
options._res.send(result);
|
|
@@ -1292,7 +1296,7 @@ class apiUtil$2 {
|
|
|
1292
1296
|
let result = await netUtil$1.apiRequest(reqOptions);
|
|
1293
1297
|
if (result.status !== 200) {
|
|
1294
1298
|
console.log(t_f$2("访问 %s 出错", reqOptions.url), result.status, result.res.statusMessage);
|
|
1295
|
-
throw [
|
|
1299
|
+
throw [errCfg.EXCEPTION, `[${result.status}] - ${result.res.statusMessage}`];
|
|
1296
1300
|
}
|
|
1297
1301
|
return result.data;
|
|
1298
1302
|
}
|
|
@@ -1314,7 +1318,7 @@ class apiUtil$2 {
|
|
|
1314
1318
|
key = keys[i];
|
|
1315
1319
|
value = parent[key];
|
|
1316
1320
|
if (value === undefined) {
|
|
1317
|
-
throw [
|
|
1321
|
+
throw [errCfg.INVALID_PARAM, t_f$2("无此配置项: %s", key)];
|
|
1318
1322
|
}
|
|
1319
1323
|
}
|
|
1320
1324
|
return {
|
|
@@ -1350,6 +1354,246 @@ class apiUtil$2 {
|
|
|
1350
1354
|
static corsCheckOrigin(origin, callback) {
|
|
1351
1355
|
callback(null, apiUtil$2.isOriginAllowed(origin));
|
|
1352
1356
|
}
|
|
1357
|
+
static validCek(cek, checkExpired = true) {
|
|
1358
|
+
if (!cek) {
|
|
1359
|
+
throw [errCfg.ACCESS_REFUSED, t("CEK(内容加密密钥)尚未申请或已过期")];
|
|
1360
|
+
}
|
|
1361
|
+
if (checkExpired && new Date().valueOf() >= cek.expireAt) {
|
|
1362
|
+
throw errCfg.CEK_EXPIRED;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
static encryptContent(content, cek) {
|
|
1366
|
+
return ppCrypto.encryptData(content, cek.key, 'base64');
|
|
1367
|
+
}
|
|
1368
|
+
static decryptContent(content, cek) {
|
|
1369
|
+
this.validCek(cek);
|
|
1370
|
+
try {
|
|
1371
|
+
const result = ppCrypto.decryptData(content, cek.key, 'utf8');
|
|
1372
|
+
if (result.startsWith('{') || result.startsWith('[')) {
|
|
1373
|
+
return JSON.parse(result);
|
|
1374
|
+
}
|
|
1375
|
+
return result;
|
|
1376
|
+
} catch (e) {
|
|
1377
|
+
throw [errCfg.DECRYPT_FAIL, e.message];
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
static decryptRequestContent(req, res) {
|
|
1381
|
+
if (!appSetting.e2eEncryptionNeeded) return;
|
|
1382
|
+
let params = apiUtil$2.extractParams(req);
|
|
1383
|
+
if (params.encryptedContent) {
|
|
1384
|
+
if (['DELETE', 'GET'].contains(req.method)) {
|
|
1385
|
+
params.encryptedContent = decodeURIComponent(params.encryptedContent);
|
|
1386
|
+
}
|
|
1387
|
+
if (!req.appInfo) {
|
|
1388
|
+
throw t('应用系统信息未知');
|
|
1389
|
+
}
|
|
1390
|
+
const decryptedContent = apiUtil$2.decryptContent(params.encryptedContent, req.appInfo.cek);
|
|
1391
|
+
delete params.encryptedContent;
|
|
1392
|
+
Object.assign(req.params, decryptedContent);
|
|
1393
|
+
}
|
|
1394
|
+
if (res) {
|
|
1395
|
+
apiUtil$2.validCek(req.appInfo.cek);
|
|
1396
|
+
res.cek = req.appInfo.cek; // 设置用于对称加密的CEK
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
static async restoreObj(key) {
|
|
1400
|
+
return kvStorage.restoreObj(key).catch(() => null);
|
|
1401
|
+
}
|
|
1402
|
+
static cekStoreKey(apiKey, req) {
|
|
1403
|
+
return `cek_${this.appAsPrefix}_${apiKey}_${apiUtil$2.getClientIp(req)}`;
|
|
1404
|
+
}
|
|
1405
|
+
static async generateCek(appInfo, req, key) {
|
|
1406
|
+
//todo: 检查既有 cek
|
|
1407
|
+
let t = new Date().valueOf();
|
|
1408
|
+
const cek = {
|
|
1409
|
+
key: key || ppUtil$3.newGuid() + ppUtil$3.newGuid(),
|
|
1410
|
+
//要求32字节
|
|
1411
|
+
issueAt: t,
|
|
1412
|
+
expireAt: t + appSetting.cekExpireTime * 1000
|
|
1413
|
+
};
|
|
1414
|
+
if (appInfo) {
|
|
1415
|
+
// appInfo.cek = cek;
|
|
1416
|
+
await kvStorage.storeObj(this.cekStoreKey(appInfo.apiKey, req), cek, appSetting.cekExpireTime);
|
|
1417
|
+
}
|
|
1418
|
+
return cek;
|
|
1419
|
+
}
|
|
1420
|
+
static async getContentEncKey(options) {
|
|
1421
|
+
const req = options._res.req;
|
|
1422
|
+
try {
|
|
1423
|
+
const appInfo = req.appInfo;
|
|
1424
|
+
//todo: 检查既有CEK,限制频繁申请
|
|
1425
|
+
const apiKey = appInfo.apiKey;
|
|
1426
|
+
let cek = null;
|
|
1427
|
+
if (appSetting.e2eEncryptionNeeded) {
|
|
1428
|
+
const cekKey = apiUtil$2.cekStoreKey(apiKey, req);
|
|
1429
|
+
// cek = await apiUtil.restoreObj(cekKey);
|
|
1430
|
+
cek = await apiUtil$2.restoreObj(cekKey);
|
|
1431
|
+
if (cek && new Date().valueOf() + 60 * 1000 >= cek.expireAt) cek = null;
|
|
1432
|
+
if (!cek) {
|
|
1433
|
+
cek = await apiUtil$2.generateCek(appInfo, req);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
let result = apiUtil$2.apiSuccess(cek, req);
|
|
1437
|
+
delete result.message; //减少数据尺寸,否则数据太长无法用公钥加密
|
|
1438
|
+
delete result._elapse;
|
|
1439
|
+
// options._res.publicKey = appInfo.publicKey; // 设置用于加密结果的公钥
|
|
1440
|
+
return result;
|
|
1441
|
+
} catch (e) {
|
|
1442
|
+
return apiUtil$2.apiFail(e, req);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
static async restoreAppInfo(apiKey, req) {
|
|
1446
|
+
let appInfo = await apiUtil$2.restoreObj(`${this.appAsPrefix}App_${apiKey}`, appSetting.tokenExpireTime);
|
|
1447
|
+
if (!appInfo) {
|
|
1448
|
+
appInfo = await dbUtil.dbQueryOneAndUnstringify(dbSql.APP_QUERY, {
|
|
1449
|
+
apiKey
|
|
1450
|
+
}, "exData");
|
|
1451
|
+
if (appInfo) {
|
|
1452
|
+
// await generateCek(appInfo);
|
|
1453
|
+
await kvStorage.storeObj(`cbpApp_${appInfo.apiKey}`, appInfo, appSetting.tokenExpireTime);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
// if (appInfo && !appInfo.cek && appSetting.e2eEncryptionNeeded) {
|
|
1457
|
+
if (appInfo && appSetting.e2eEncryptionNeeded) {
|
|
1458
|
+
appInfo.cek = await apiUtil$2.restoreObj(apiUtil$2.cekStoreKey(apiKey, req));
|
|
1459
|
+
}
|
|
1460
|
+
return appInfo;
|
|
1461
|
+
}
|
|
1462
|
+
static async generateApiSignature(options) {
|
|
1463
|
+
const params = options.replacements || options;
|
|
1464
|
+
await this.parametersOK(params, 'apiKey', 'privateKey');
|
|
1465
|
+
// console.log("generateApiSignature for apiKey", params.apiKey);
|
|
1466
|
+
// console.log("privateKey", params.privateKey);
|
|
1467
|
+
const result = {
|
|
1468
|
+
headers: {
|
|
1469
|
+
"api-key": params.apiKey
|
|
1470
|
+
},
|
|
1471
|
+
body: {
|
|
1472
|
+
encryptedContent: ''
|
|
1473
|
+
}
|
|
1474
|
+
};
|
|
1475
|
+
const appInfo = await this.restoreAppInfo(params.apiKey, options._res.req);
|
|
1476
|
+
if (!appInfo) {
|
|
1477
|
+
throw [errCfg.ACCESS_REFUSED, t_f$2("apiKey (%s) 无效", params.apiKey)];
|
|
1478
|
+
}
|
|
1479
|
+
let content = '';
|
|
1480
|
+
if (appSetting.e2eEncryptionNeeded) {
|
|
1481
|
+
if (params.decryptedContent) {
|
|
1482
|
+
// if (!appInfo.cek) {
|
|
1483
|
+
// await generateCek(appInfo);
|
|
1484
|
+
// }
|
|
1485
|
+
await this.parametersOK(params, 'cek');
|
|
1486
|
+
const cek = await this.generateCek(undefined, undefined, params.cek.key || params.cek); //避免过期
|
|
1487
|
+
// commonUtil.validCek(params.cek);
|
|
1488
|
+
content = this.encryptContent(params.decryptedContent, cek);
|
|
1489
|
+
result.body.encryptedContent = content;
|
|
1490
|
+
}
|
|
1491
|
+
} else {
|
|
1492
|
+
// result.body = null
|
|
1493
|
+
content = JSON.stringify(params.decryptedContent);
|
|
1494
|
+
}
|
|
1495
|
+
if (appSetting.reqSignatureNeeded) {
|
|
1496
|
+
result.headers.timestamp = new Date().valueOf();
|
|
1497
|
+
result.headers.signature = this.getRequestSignature(appInfo.apiKey, result.headers.timestamp, content, params.privateKey);
|
|
1498
|
+
}
|
|
1499
|
+
return result;
|
|
1500
|
+
}
|
|
1501
|
+
static getRequestSignature(apiKey, timestamp, content, privateKey) {
|
|
1502
|
+
let dataToSign = apiKey + timestamp + (content || '');
|
|
1503
|
+
const sign = crypto.createSign('RSA-SHA256');
|
|
1504
|
+
sign.update(dataToSign);
|
|
1505
|
+
return sign.sign(privateKey, 'base64');
|
|
1506
|
+
}
|
|
1507
|
+
static async decryptApiResponse(options) {
|
|
1508
|
+
const params = options.replacements || options;
|
|
1509
|
+
await this.parametersOK(params, 'encryptedContent', 'cek/privateKey');
|
|
1510
|
+
let result;
|
|
1511
|
+
if (params.cek) {
|
|
1512
|
+
const cek = await this.generateCek(undefined, undefined, params.cek.key || params.cek); //避免过期
|
|
1513
|
+
result = this.decryptContent(params.encryptedContent, cek);
|
|
1514
|
+
console.log('CEK 解密结果:', result);
|
|
1515
|
+
} else {
|
|
1516
|
+
let content = await ppCrypto.rsaDecrypt(params);
|
|
1517
|
+
result = JSON.parse(content.decryptedContent);
|
|
1518
|
+
// result = await commonUtil.rsaDecrypt(params);
|
|
1519
|
+
}
|
|
1520
|
+
return result;
|
|
1521
|
+
}
|
|
1522
|
+
static async reqAppInfoNeeded(req) {
|
|
1523
|
+
if (!req.appInfo) {
|
|
1524
|
+
let apiKey = req.headers["api-key"] || req.leInfo?.apiKey || req.userInfo?.apiKey;
|
|
1525
|
+
if (!apiKey) {
|
|
1526
|
+
throw [errCfg.ACCESS_REFUSED, t("必须在请求头中设置api-key")];
|
|
1527
|
+
}
|
|
1528
|
+
req.appInfo = await this.restoreAppInfo(apiKey, req);
|
|
1529
|
+
if (!req.appInfo) {
|
|
1530
|
+
throw [errCfg.ACCESS_REFUSED, t_f$2("api-key (%s) 无效", apiKey)];
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
static async verifyApiSignature(req) {
|
|
1535
|
+
if (req.appInfo) {
|
|
1536
|
+
return true;
|
|
1537
|
+
}
|
|
1538
|
+
await this.reqAppInfoNeeded(req);
|
|
1539
|
+
if (!appSetting.reqSignatureNeeded) return true;
|
|
1540
|
+
if (!req.headers.timestamp) {
|
|
1541
|
+
throw [errCfg.ACCESS_REFUSED, t_f$2("必须在请求头中设置时戳")];
|
|
1542
|
+
}
|
|
1543
|
+
let timestamp = parseInt(req.headers.timestamp);
|
|
1544
|
+
let currentTime = new Date().valueOf();
|
|
1545
|
+
let maxTimeDiff = apiUtil$2.isDebugMode(req) ? 7 * 24 * 3600 : apiUtil$2.isTestMode(req) ? 4 * 3600 : 300; // 100
|
|
1546
|
+
if (timestamp.toString() === "NaN" || currentTime < timestamp - 5000 || currentTime - timestamp > maxTimeDiff * 1000) {
|
|
1547
|
+
throw [errCfg.ACCESS_REFUSED, t_f$2("时戳(%s)无效,当前时戳:%s,时差 %s秒", timestamp, currentTime, (currentTime - timestamp) / 1000)];
|
|
1548
|
+
}
|
|
1549
|
+
let signature = req.headers.signature;
|
|
1550
|
+
if (!signature) {
|
|
1551
|
+
throw [errCfg.ACCESS_REFUSED, t_f$2("必须在请求头中提供签名")];
|
|
1552
|
+
}
|
|
1553
|
+
const params = apiUtil$2.extractParams(req);
|
|
1554
|
+
const content = appSetting.e2eEncryptionNeeded ? params?.encryptedContent || '' : JSON.stringify(params || null);
|
|
1555
|
+
const apiKey = req.appInfo.apiKey;
|
|
1556
|
+
let signData = apiKey + timestamp + content;
|
|
1557
|
+
let verify = crypto.createVerify('RSA-SHA256');
|
|
1558
|
+
verify.update(signData);
|
|
1559
|
+
let verifyOK = false;
|
|
1560
|
+
try {
|
|
1561
|
+
verifyOK = verify.verify(req.appInfo.publicKey, signature, 'base64');
|
|
1562
|
+
} catch (e) {
|
|
1563
|
+
console.log("服务签名验证异常, ", e);
|
|
1564
|
+
verifyOK = false;
|
|
1565
|
+
}
|
|
1566
|
+
if (!verifyOK) {
|
|
1567
|
+
// console.log("服务签名验证失败, tokenData: ", tokenData);
|
|
1568
|
+
throw [errCfg.ACCESS_REFUSED, t("签名验证未通过")];
|
|
1569
|
+
}
|
|
1570
|
+
return verifyOK;
|
|
1571
|
+
}
|
|
1572
|
+
static checkWhiteList(req) {
|
|
1573
|
+
const ipWhiteList = req.appInfo.exData?.ipWhiteList;
|
|
1574
|
+
if (!ipWhiteList || ipWhiteList.length === 0) {
|
|
1575
|
+
// 没有设置ip白名单的就不检查了
|
|
1576
|
+
return true;
|
|
1577
|
+
}
|
|
1578
|
+
let clientIp = apiUtil$2.getClientIp(req);
|
|
1579
|
+
console.log("fromIp:", clientIp, "ipWhiteList:", ipWhiteList);
|
|
1580
|
+
if (!clientIp) {
|
|
1581
|
+
throw errCfg.GET_CLIENTIP_FAIL;
|
|
1582
|
+
}
|
|
1583
|
+
return this.ipMatched(clientIp, ipWhiteList, true);
|
|
1584
|
+
}
|
|
1585
|
+
static ipMatched(fromIp, whiteList, localAllowed) {
|
|
1586
|
+
let ips = Array.isArray(whiteList) ? whiteList : (whiteList || '').split(',');
|
|
1587
|
+
for (let i = 0; i < ips.length; i++) {
|
|
1588
|
+
let ip = ips[i];
|
|
1589
|
+
if (ip === fromIp) return true;
|
|
1590
|
+
if (ip.indexOf("*") > 0) {
|
|
1591
|
+
let l = ip.indexOf("*");
|
|
1592
|
+
if (fromIp.substring(0, l) === ip.substring(0, l)) return true;
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
return localAllowed && (fromIp === "127.0.0.1" || fromIp === "1");
|
|
1596
|
+
}
|
|
1353
1597
|
|
|
1354
1598
|
//#endregion
|
|
1355
1599
|
|
|
@@ -1363,7 +1607,7 @@ class apiUtil$2 {
|
|
|
1363
1607
|
await apiUtil$2.checkRequestToken(req);
|
|
1364
1608
|
next();
|
|
1365
1609
|
} catch (e) {
|
|
1366
|
-
res.send(
|
|
1610
|
+
res.send(errCfg.ERROR(e));
|
|
1367
1611
|
}
|
|
1368
1612
|
return false;
|
|
1369
1613
|
}
|
|
@@ -1405,7 +1649,7 @@ class apiUtil$2 {
|
|
|
1405
1649
|
}
|
|
1406
1650
|
return true;
|
|
1407
1651
|
} catch (e) {
|
|
1408
|
-
res.send(
|
|
1652
|
+
res.send(errCfg.ERROR(e));
|
|
1409
1653
|
return false;
|
|
1410
1654
|
}
|
|
1411
1655
|
}
|
|
@@ -1426,9 +1670,9 @@ class apiUtil$2 {
|
|
|
1426
1670
|
req.isMobile = true;
|
|
1427
1671
|
if (!apiUtil$2.signatureVerified(req)) {
|
|
1428
1672
|
let params = apiUtil$2.extractParams(req);
|
|
1429
|
-
moveProperty(params, req.headers, "access_token");
|
|
1430
|
-
moveProperty(params, req.headers, "timestamp");
|
|
1431
|
-
if (moveProperty(params, req.headers, "signature")) {
|
|
1673
|
+
ppUtil$3.moveProperty(params, req.headers, "access_token");
|
|
1674
|
+
ppUtil$3.moveProperty(params, req.headers, "timestamp");
|
|
1675
|
+
if (ppUtil$3.moveProperty(params, req.headers, "signature")) {
|
|
1432
1676
|
req.headers.signature = decodeURIComponent(req.headers.signature);
|
|
1433
1677
|
}
|
|
1434
1678
|
}
|
|
@@ -1448,11 +1692,11 @@ class apiUtil$2 {
|
|
|
1448
1692
|
apiUtil$2.createApiCallRec(tokenData, req);
|
|
1449
1693
|
let errResponse = null;
|
|
1450
1694
|
if (!tokenData.clientIp) {
|
|
1451
|
-
errResponse = apiUtil$2.spoApiFail(
|
|
1695
|
+
errResponse = apiUtil$2.spoApiFail(errCfg.GET_CLIENTIP_FAIL, req);
|
|
1452
1696
|
} else if (!tokenData.apiKey) {
|
|
1453
|
-
errResponse = apiUtil$2.spoApiFail([
|
|
1697
|
+
errResponse = apiUtil$2.spoApiFail([errCfg.PARAMETER_NEEDED, "apiKey"], req);
|
|
1454
1698
|
} else if (!tokenData.apiSecret) {
|
|
1455
|
-
errResponse = apiUtil$2.spoApiFail([
|
|
1699
|
+
errResponse = apiUtil$2.spoApiFail([errCfg.PARAMETER_NEEDED, "apiSecret"], req);
|
|
1456
1700
|
}
|
|
1457
1701
|
if (errResponse != null) {
|
|
1458
1702
|
apiUtil$2._saveApiCallRec(req.apiCallRec, errResponse);
|
|
@@ -1481,7 +1725,7 @@ class apiUtil$2 {
|
|
|
1481
1725
|
}
|
|
1482
1726
|
let accessToken = apiUtil$2.extractToken(req);
|
|
1483
1727
|
if (!accessToken) {
|
|
1484
|
-
res.send(apiUtil$2.spoApiFail(
|
|
1728
|
+
res.send(apiUtil$2.spoApiFail(errCfg.TOKEN_NEEDED, req));
|
|
1485
1729
|
// apiUtil.sendOrRedirect(req, res, spoApiFail(err.TOKEN_NEEDED, req));
|
|
1486
1730
|
return;
|
|
1487
1731
|
}
|
|
@@ -1501,7 +1745,7 @@ class apiUtil$2 {
|
|
|
1501
1745
|
return apiUtil$2.$checkApiKeyOld(req, res, next);
|
|
1502
1746
|
}).catch(e => {
|
|
1503
1747
|
const params = apiUtil$2.extractParams(req);
|
|
1504
|
-
apiUtil$2.sendOrRedirect(res, params.redirectUrl || params.callbackUrl, apiUtil$2.spoApiFail(
|
|
1748
|
+
apiUtil$2.sendOrRedirect(res, params.redirectUrl || params.callbackUrl, apiUtil$2.spoApiFail(errCfg.TOKEN_INVALID, req));
|
|
1505
1749
|
});
|
|
1506
1750
|
}
|
|
1507
1751
|
|
|
@@ -1611,7 +1855,7 @@ class apiUtil$2 {
|
|
|
1611
1855
|
await apiUtil$2.parametersOK(params, "value");
|
|
1612
1856
|
let c = apiUtil$2.parseCfgPath(cfgItem);
|
|
1613
1857
|
if (typeof c.value === "object") {
|
|
1614
|
-
throw [
|
|
1858
|
+
throw [errCfg.ACCESS_REFUSED, t_f$2("暂不支持修改复杂配置")];
|
|
1615
1859
|
}
|
|
1616
1860
|
// c.oldValue = c.value;
|
|
1617
1861
|
c.newValue = params.value;
|
|
@@ -1664,11 +1908,11 @@ class apiUtil$2 {
|
|
|
1664
1908
|
//#endregion
|
|
1665
1909
|
}
|
|
1666
1910
|
function configNeeded() {
|
|
1667
|
-
if (!conf$2 || !
|
|
1911
|
+
if (!conf$2 || !errCfg) {
|
|
1668
1912
|
ppUtil$3.configNeeded();
|
|
1669
1913
|
conf$2 = ppUtil$3.appConfig;
|
|
1670
1914
|
appSetting = conf$2.appSetting;
|
|
1671
|
-
|
|
1915
|
+
errCfg = ppUtil$3.appErrCfg;
|
|
1672
1916
|
}
|
|
1673
1917
|
}
|
|
1674
1918
|
var ppUtilApi = apiUtil$2;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "abler-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "API服务相关工具",
|
|
5
5
|
"main": "./dist/cjs/pp-util.js",
|
|
6
6
|
"-module": "./dist/es/pp-util.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"license": "ISC",
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"q": "^1.5.1",
|
|
19
|
-
"abler-util": ">=0.
|
|
19
|
+
"abler-util": ">=0.3.0",
|
|
20
20
|
"abler-db": ">=0.1.1",
|
|
21
21
|
"abler-i18n": "^0.1.20",
|
|
22
22
|
"abler-messenger": ">=0.1.1",
|