fhrons-mobile-next 2.0.5 → 2.0.6-beta.0
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/assets/js/crypto/config.js +5 -0
- package/assets/js/crypto/index.js +10 -0
- package/assets/js/crypto/legacy.cjs +558 -0
- package/fhcommon/dist/fhcommon.min.js +2 -1
- package/fhcommon/upload.js +6 -10
- package/index.js +2 -0
- package/package.json +3 -1
- package/tests/crypto-vectors.json +46 -0
- package/tests/crypto.cjs +90 -0
- package/fhcommon/index.js +0 -33
- package/fhcommon/package.json +0 -40
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "2026/10/27 gongml [需求 待补充] 从用户提供的原始JS生成的测试向量;换钥后需同步重新生成",
|
|
3
|
+
"keyFingerprint": "196af9ef9904021d3901857d8efea5f73f205d515e12cc4eb487781d8baf88ce",
|
|
4
|
+
"vectors": [
|
|
5
|
+
{
|
|
6
|
+
"input": "",
|
|
7
|
+
"encrypted": ""
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"input": "123456",
|
|
11
|
+
"encrypted": "GVwmud8sJbfkvimcp3tDYA=="
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"input": "P@ssWord_2026!",
|
|
15
|
+
"encrypted": "W8VH72O0AWjleIyrPAXXnQ=="
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"input": "中文密码测试",
|
|
19
|
+
"encrypted": "ni6Pac84PpP4dYURVy/wQGVGnNdSZhw+yLxvN+Hxhnc="
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"input": "aaaaaaaaaaaaaaa",
|
|
23
|
+
"encrypted": "oUvGpGsp5gJu/H/v916O6Q=="
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"input": "aaaaaaaaaaaaaaaa",
|
|
27
|
+
"encrypted": "GAKYuhzXBB9PTHjY2BehwmGQY9tmQelw+nbU+rRsJd8="
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"input": "aaaaaaaaaaaaaaaaa",
|
|
31
|
+
"encrypted": "GAKYuhzXBB9PTHjY2BehwtgK1VErXDPkRPfWFaiddMA="
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"input": "emoji😀测试",
|
|
35
|
+
"encrypted": "an8PLfuVi8rdozHJ0q0NbsMx1HpZDWnHVq7gB6zfbys="
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"input": "line\nbreak\t空格 ",
|
|
39
|
+
"encrypted": "FXTf5ytUh3dA+VOH2TwGf05ySu8VRKN++IGtGuZVZps="
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"input": "{\"status\":\"SUCCESS\",\"tmpSecretId\":\"test-id\",\"tmpSecretKey\":\"test-only\",\"token\":\"test-token\"}",
|
|
43
|
+
"encrypted": "ybzBJqxEAwZhpDtjdzfOLCSHOadOdQKMDHkS+HapGI+tWOV/O27cUYn03paeq6AS9g4tuAkPISCqsHai3WSuyQfAe/H7d1m6CxXizFW+iGUEsmtU1LsP2OJHRS7QTue0"
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
package/tests/crypto.cjs
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// 2026/10/27 gongml [需求 待补充] 验证原SM4协议、全局兼容入口、上传内置解密和发布文件完整性
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const vm = require('vm');
|
|
6
|
+
const { createHash } = require('crypto');
|
|
7
|
+
const root = path.resolve(__dirname, '..');
|
|
8
|
+
const cryptoDir = path.join(root, 'assets/js/crypto');
|
|
9
|
+
const config = require(path.join(cryptoDir, 'config'));
|
|
10
|
+
const codec = require(path.join(cryptoDir, 'legacy.cjs'));
|
|
11
|
+
let checks = 0;
|
|
12
|
+
function check(value, message) {
|
|
13
|
+
assert.ok(value, message);
|
|
14
|
+
checks += 1;
|
|
15
|
+
}
|
|
16
|
+
const baseline = require('./crypto-vectors.json');
|
|
17
|
+
check(createHash('sha256').update(config.secretKey).digest('hex') === baseline.keyFingerprint, '换钥后请使用服务端确认的原始脚本重新生成测试向量');
|
|
18
|
+
const samples = baseline.vectors.map(item => item.input);
|
|
19
|
+
for (const { input, encrypted } of baseline.vectors) {
|
|
20
|
+
check(codec.encrypt(input) === encrypted, '与用户原始JS测试向量不一致');
|
|
21
|
+
check(codec.decrypt(encrypted) === input, '原始密文解密失败');
|
|
22
|
+
}
|
|
23
|
+
check(codec.encrypt(null) === '', '保留原脚本null输入行为');
|
|
24
|
+
const wrapper = fs.readFileSync(path.join(cryptoDir, 'index.js'), 'utf8')
|
|
25
|
+
.replace("import FHEncrypt from './legacy.cjs';", '')
|
|
26
|
+
.replace(/^export const /gm, 'const ')
|
|
27
|
+
.replace('export default FHEncrypt;', '');
|
|
28
|
+
const browser = { window: {}, FHEncrypt: codec };
|
|
29
|
+
vm.runInNewContext(wrapper, browser);
|
|
30
|
+
check(browser.window.FHENCYPT === codec, '模块加载时同步注册全局兼容对象');
|
|
31
|
+
vm.runInNewContext(wrapper, { FHEncrypt: codec });
|
|
32
|
+
checks += 1;
|
|
33
|
+
const rootEntry = fs.readFileSync(path.join(root, 'index.js'), 'utf8');
|
|
34
|
+
check(rootEntry.indexOf("from './assets/js/crypto'") < rootEntry.indexOf("import './fhcommon/dist/fhcommon.min.js'"), '加密入口必须在公共包之前初始化');
|
|
35
|
+
const uploadSource = fs.readFileSync(path.join(root, 'fhcommon/upload.js'), 'utf8');
|
|
36
|
+
check(uploadSource.includes("import FHEncrypt from '../assets/js/crypto'"), '上传显式导入本地实现');
|
|
37
|
+
check(uploadSource.includes('FHEncrypt.decrypt(res.data || res)'), '上传响应使用本地解密');
|
|
38
|
+
check(!uploadSource.includes('fhEncypt.js'), '上传源码不能请求远程加密脚本');
|
|
39
|
+
const distPath = path.join(root, 'fhcommon/dist/fhcommon.min.js');
|
|
40
|
+
const dist = fs.readFileSync(distPath, 'utf8');
|
|
41
|
+
check(!dist.includes('fhEncypt.js'), '发布产物不能包含远程加密脚本地址,请先执行npm run build:common');
|
|
42
|
+
const Vue = { use(plugin) { plugin.install(this); }, component() {} };
|
|
43
|
+
const isolatedWindow = { Vue, axios: {}, COS: {}, lrz: {}, html2canvas: {} };
|
|
44
|
+
const context = { window: isolatedWindow, self: isolatedWindow, console, process: { env: {} }, setTimeout, clearTimeout, setInterval, clearInterval, URL, URLSearchParams, TextEncoder, TextDecoder, navigator: { userAgent: 'crypto-test' } };
|
|
45
|
+
try {
|
|
46
|
+
vm.runInNewContext(dist, context);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
throw new Error(`公共发布包加载失败: ${error.message}`);
|
|
49
|
+
}
|
|
50
|
+
check(typeof isolatedWindow.FHENCYPT.encrypt === 'function', '公共发布包独立加载也能初始化加密入口');
|
|
51
|
+
for (const input of samples) {
|
|
52
|
+
check(isolatedWindow.FHENCYPT.encrypt(input) === codec.encrypt(input), '公共发布包和源码加密结果一致');
|
|
53
|
+
check(isolatedWindow.FHENCYPT.decrypt(codec.encrypt(input)) === input, '公共发布包和源码交叉解密一致');
|
|
54
|
+
}
|
|
55
|
+
// 2026/10/27 gongml [需求 待补充] 无全局加密对象时验证真实上传函数的凭证解密和错误传播
|
|
56
|
+
async function verifyUpload() {
|
|
57
|
+
const credentials = { status: 'SUCCESS', tmpSecretId: 'test-id', tmpSecretKey: 'test-only', token: 'test-token', sessionToken: 'session-test', bucketName: 'test-bucket', region: 'test-region', cdnUrl: 'https://example.invalid' };
|
|
58
|
+
let response = { data: codec.encrypt(JSON.stringify(credentials)) };
|
|
59
|
+
let uploaded = 0;
|
|
60
|
+
const requests = [];
|
|
61
|
+
function MockCOS(options) {
|
|
62
|
+
options.getAuthorization({}, value => check(value.TmpSecretKey === credentials.tmpSecretKey, '上传使用正确解密后的临时凭证'));
|
|
63
|
+
this.putObject = value => {
|
|
64
|
+
check(value.Key === 'test/file.png', '上传保持原对象路径');
|
|
65
|
+
uploaded += 1;
|
|
66
|
+
return Promise.resolve({ statusCode: 200 });
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const axios = { post(url, data) { requests.push({ url, data }); return Promise.resolve(response); } };
|
|
70
|
+
const mockWindow = { axios, COS: MockCOS, lrz: {} };
|
|
71
|
+
const scope = { FHEncrypt: codec, window: mockWindow, axios, COS: MockCOS, process: { env: { VUE_APP_NAME: 'crypto-test', VUE_APP_ENV_CONFIG: 'uat' } }, location: { origin: 'https://example.invalid' }, console: { log() {}, error() {} } };
|
|
72
|
+
vm.createContext(scope);
|
|
73
|
+
vm.runInContext(uploadSource.replace(/^import FHEncrypt[^\n]+/m, '').replace(/^export /gm, ''), scope);
|
|
74
|
+
const param = { file: { name: 'file.png', type: 'image/png' }, keyFunc: () => '/test/file.png' };
|
|
75
|
+
const url = await scope.upload(param);
|
|
76
|
+
check(url === 'https://example.invalid/test/file.png', '上传返回原协议文件地址');
|
|
77
|
+
check(!mockWindow.FHENCYPT, '上传不依赖全局加密对象');
|
|
78
|
+
check(requests.length === 1 && requests[0].url === '/funapi/cbsconfig/oss/secureSign', '仅请求上传凭证接口');
|
|
79
|
+
response = { data: credentials };
|
|
80
|
+
await scope.upload(param);
|
|
81
|
+
check(uploaded === 2, '继续兼容未加密对象响应');
|
|
82
|
+
response = { data: codec.encrypt(JSON.stringify({ status: 'FAILED', message: 'test failure' })) };
|
|
83
|
+
await assert.rejects(scope.upload(param), /test failure/);
|
|
84
|
+
checks += 1;
|
|
85
|
+
check(uploaded === 2, '接口失败时不执行上传');
|
|
86
|
+
}
|
|
87
|
+
verifyUpload().then(() => process.stdout.write(`crypto checks passed: ${checks}\n`)).catch(error => {
|
|
88
|
+
process.stderr.write(`${error.stack}\n`);
|
|
89
|
+
process.exitCode = 1;
|
|
90
|
+
});
|
package/fhcommon/index.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// import FHCommon from './dist/fhcommon.min.js'
|
|
2
|
-
// if (typeof window !== 'undefined' && window.Vue) {
|
|
3
|
-
// window.Vue.use(FHCommon)
|
|
4
|
-
// window.FHUpload = FHCommon
|
|
5
|
-
// }
|
|
6
|
-
|
|
7
|
-
// // expose FHCommon as default export
|
|
8
|
-
// export default FHCommon;
|
|
9
|
-
|
|
10
|
-
// 由于minio不支持esm模块导入,也就不支持treeshaking来去掉未用到的minio代码,所以只能准备两套代码
|
|
11
|
-
import { upload, compressFile, loopCompressFile } from './upload'
|
|
12
|
-
import { getDomImage } from './packages/html2canvas'
|
|
13
|
-
|
|
14
|
-
import FHImage from "./packages/fhImage";
|
|
15
|
-
import FHPresetImage from "./packages/fhPresetImage";
|
|
16
|
-
const FHCommon = {
|
|
17
|
-
upload: upload,
|
|
18
|
-
compressFile: compressFile,
|
|
19
|
-
loopCompressFile: loopCompressFile,
|
|
20
|
-
getDomImage,
|
|
21
|
-
install (Vue) {
|
|
22
|
-
window.FHCommon = FHCommon
|
|
23
|
-
Vue.component('FHImage',FHImage)
|
|
24
|
-
Vue.component('FHPresetImage',FHPresetImage)
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
if (typeof window !== 'undefined' && window.Vue) {
|
|
28
|
-
window.Vue.use(FHCommon)
|
|
29
|
-
window.FHUpload = FHCommon
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// expose FHCommon as default export
|
|
33
|
-
export default FHCommon;
|
package/fhcommon/package.json
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "fhcommon",
|
|
3
|
-
"version": "2.0.0",
|
|
4
|
-
"description": "泛华前端的上传方法",
|
|
5
|
-
"main": "dist/fhcommon.min.js",
|
|
6
|
-
"module": "dist/fhcommon.min.js",
|
|
7
|
-
"devDependencies": {
|
|
8
|
-
"@babel/core": "^7.25.8",
|
|
9
|
-
"@babel/preset-env": "^7.25.8",
|
|
10
|
-
"babel-loader": "^9.1.3",
|
|
11
|
-
"crypto-browserify": "^3.12.0",
|
|
12
|
-
"css-loader": "^6.7.4",
|
|
13
|
-
"eslint": "^9.0.0",
|
|
14
|
-
"https-browserify": "^1.0.0",
|
|
15
|
-
"less-loader": "^11.1.4",
|
|
16
|
-
"path-browserify": "^1.0.1",
|
|
17
|
-
"stream-browserify": "^3.0.0",
|
|
18
|
-
"stream-http": "^3.2.0",
|
|
19
|
-
"style-loader": "^3.3.3",
|
|
20
|
-
"terser-webpack-plugin": "^5.3.10",
|
|
21
|
-
"timers-browserify": "^2.0.12",
|
|
22
|
-
"uglify-js": "^3.17.4",
|
|
23
|
-
"vm-browserify": "^1.1.2",
|
|
24
|
-
"vue-loader": "^15.9.8",
|
|
25
|
-
"vue-template-compiler": "^2.7.16",
|
|
26
|
-
"webpack": "^5.91.0",
|
|
27
|
-
"webpack-cli": "^5.0.0",
|
|
28
|
-
"webpack-dev-server": "^5.0.4"
|
|
29
|
-
},
|
|
30
|
-
"scripts": {
|
|
31
|
-
"test": "echo \"Error: no test specified\" && exit 1",
|
|
32
|
-
"build": "npx webpack --config webpack.config.js"
|
|
33
|
-
},
|
|
34
|
-
"author": "",
|
|
35
|
-
"license": "ISC",
|
|
36
|
-
"dependencies": {
|
|
37
|
-
"core-js": "^3.38.1",
|
|
38
|
-
"minio": "^7.0.18"
|
|
39
|
-
}
|
|
40
|
-
}
|