fix: 支持 SOCKS5 系统代理并修复脱敏令牌重复同步 (#154)
* fix: 支持 SOCKS5 系统代理并修复脱敏令牌重复同步 * fix: 避免复用完全模糊的脱敏令牌 * fix: 修复 reviewer 反馈的令牌同步与 SOCKS5 问题 * fix: avoid masked token sync collisions --------- Co-authored-by: Brucents <mail@urlk.cn> Co-authored-by: cita <juricek.chen@gmail.com>
This commit is contained in:
Generated
+19
-32
@@ -28,6 +28,7 @@
|
|||||||
"node-cron": "^4.2.1",
|
"node-cron": "^4.2.1",
|
||||||
"nodemailer": "^8.0.1",
|
"nodemailer": "^8.0.1",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
|
"socks": "^2.8.7",
|
||||||
"undici": "^6.24.1",
|
"undici": "^6.24.1",
|
||||||
"ws": "^8.19.0"
|
"ws": "^8.19.0"
|
||||||
},
|
},
|
||||||
@@ -227,7 +228,6 @@
|
|||||||
"integrity": "sha512-Nt9hri7nbOo0RipAsGjIssHkpLMHHN/P7QqENywAq5TLsoYDzUyJGny8FEiD/9KJUxtGH8blGpMedilI6kK3rA==",
|
"integrity": "sha512-Nt9hri7nbOo0RipAsGjIssHkpLMHHN/P7QqENywAq5TLsoYDzUyJGny8FEiD/9KJUxtGH8blGpMedilI6kK3rA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@algolia/client-common": "5.49.1",
|
"@algolia/client-common": "5.49.1",
|
||||||
"@algolia/requester-browser-xhr": "5.49.1",
|
"@algolia/requester-browser-xhr": "5.49.1",
|
||||||
@@ -380,7 +380,6 @@
|
|||||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.29.0",
|
"@babel/code-frame": "^7.29.0",
|
||||||
"@babel/generator": "^7.29.0",
|
"@babel/generator": "^7.29.0",
|
||||||
@@ -707,7 +706,6 @@
|
|||||||
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
|
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
"fast-json-stable-stringify": "^2.0.0",
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
@@ -753,7 +751,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
|
||||||
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dnd-kit/accessibility": "^3.1.1",
|
"@dnd-kit/accessibility": "^3.1.1",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
@@ -1243,6 +1240,7 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cross-dirname": "^0.1.0",
|
"cross-dirname": "^0.1.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
@@ -1264,6 +1262,7 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"graceful-fs": "^4.2.0",
|
"graceful-fs": "^4.2.0",
|
||||||
"jsonfile": "^6.0.1",
|
"jsonfile": "^6.0.1",
|
||||||
@@ -1280,6 +1279,7 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"universalify": "^2.0.0"
|
"universalify": "^2.0.0"
|
||||||
},
|
},
|
||||||
@@ -1294,6 +1294,7 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10.0.0"
|
"node": ">= 10.0.0"
|
||||||
}
|
}
|
||||||
@@ -4444,7 +4445,6 @@
|
|||||||
"integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
|
"integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
@@ -4854,7 +4854,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz",
|
||||||
"integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==",
|
"integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
@@ -4882,7 +4881,6 @@
|
|||||||
"integrity": "sha512-gT+oueVQkqnj6ajGJXblFR4iavIXWsGAFCk3dP4Kki5+a9R4NMt0JARdk6s8cUKcfUoqP5dAtDSLU8xYUTFV+Q==",
|
"integrity": "sha512-gT+oueVQkqnj6ajGJXblFR4iavIXWsGAFCk3dP4Kki5+a9R4NMt0JARdk6s8cUKcfUoqP5dAtDSLU8xYUTFV+Q==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
"pg-protocol": "*",
|
"pg-protocol": "*",
|
||||||
@@ -4914,7 +4912,6 @@
|
|||||||
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
|
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
@@ -5641,7 +5638,6 @@
|
|||||||
"integrity": "sha512-X3Pp2aRQhg4xUC6PQtkubn5NpRKuUPQ9FPDQlx36SmpFwwH2N0/tw4c+NXV3nw3PsgeUs+BuWGP0gjz3TvENLQ==",
|
"integrity": "sha512-X3Pp2aRQhg4xUC6PQtkubn5NpRKuUPQ9FPDQlx36SmpFwwH2N0/tw4c+NXV3nw3PsgeUs+BuWGP0gjz3TvENLQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@algolia/abtesting": "1.15.1",
|
"@algolia/abtesting": "1.15.1",
|
||||||
"@algolia/client-abtesting": "5.49.1",
|
"@algolia/client-abtesting": "5.49.1",
|
||||||
@@ -6076,7 +6072,6 @@
|
|||||||
"integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
|
"integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.11",
|
"follow-redirects": "^1.15.11",
|
||||||
"form-data": "^4.0.5",
|
"form-data": "^4.0.5",
|
||||||
@@ -6131,7 +6126,6 @@
|
|||||||
"integrity": "sha512-RxD2Vd96sQDjQr20kdP+F+dK/1OUNiVOl200vKBZY8u0vTwysfolF6Hq+3ZK2+h8My9YvZhHsF+RSGZW2VYrPQ==",
|
"integrity": "sha512-RxD2Vd96sQDjQr20kdP+F+dK/1OUNiVOl200vKBZY8u0vTwysfolF6Hq+3ZK2+h8My9YvZhHsF+RSGZW2VYrPQ==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bindings": "^1.5.0",
|
"bindings": "^1.5.0",
|
||||||
"prebuild-install": "^7.1.1"
|
"prebuild-install": "^7.1.1"
|
||||||
@@ -6211,7 +6205,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
@@ -6615,7 +6608,6 @@
|
|||||||
"integrity": "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==",
|
"integrity": "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chevrotain/cst-dts-gen": "11.1.2",
|
"@chevrotain/cst-dts-gen": "11.1.2",
|
||||||
"@chevrotain/gast": "11.1.2",
|
"@chevrotain/gast": "11.1.2",
|
||||||
@@ -6971,7 +6963,8 @@
|
|||||||
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
|
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true,
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/cross-env": {
|
"node_modules/cross-env": {
|
||||||
"version": "7.0.3",
|
"version": "7.0.3",
|
||||||
@@ -7020,7 +7013,6 @@
|
|||||||
"integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==",
|
"integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10"
|
"node": ">=0.10"
|
||||||
}
|
}
|
||||||
@@ -7454,7 +7446,6 @@
|
|||||||
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
@@ -7889,7 +7880,6 @@
|
|||||||
"integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==",
|
"integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"app-builder-lib": "26.8.1",
|
"app-builder-lib": "26.8.1",
|
||||||
"builder-util": "26.8.1",
|
"builder-util": "26.8.1",
|
||||||
@@ -8485,6 +8475,7 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/asar": "^3.2.1",
|
"@electron/asar": "^3.2.1",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
@@ -8505,6 +8496,7 @@
|
|||||||
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"graceful-fs": "^4.1.2",
|
"graceful-fs": "^4.1.2",
|
||||||
"jsonfile": "^4.0.0",
|
"jsonfile": "^4.0.0",
|
||||||
@@ -8694,7 +8686,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"esbuild": "bin/esbuild"
|
"esbuild": "bin/esbuild"
|
||||||
},
|
},
|
||||||
@@ -9085,7 +9076,6 @@
|
|||||||
"integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==",
|
"integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tabbable": "^6.4.0"
|
"tabbable": "^6.4.0"
|
||||||
}
|
}
|
||||||
@@ -9852,7 +9842,6 @@
|
|||||||
"version": "10.1.0",
|
"version": "10.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
|
||||||
"integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
|
"integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12"
|
"node": ">= 12"
|
||||||
@@ -11079,6 +11068,7 @@
|
|||||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimist": "^1.2.6"
|
"minimist": "^1.2.6"
|
||||||
},
|
},
|
||||||
@@ -11123,7 +11113,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.18.2.tgz",
|
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.18.2.tgz",
|
||||||
"integrity": "sha512-UfEShBFAZZEAKjySnTUuE7BgqkYT4mx+RjoJ5aqtmwSSvNcJ/QxQPXz/y3jSxNiVRedPfgccmuBtiPCSiEEytw==",
|
"integrity": "sha512-UfEShBFAZZEAKjySnTUuE7BgqkYT4mx+RjoJ5aqtmwSSvNcJ/QxQPXz/y3jSxNiVRedPfgccmuBtiPCSiEEytw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"aws-ssl-profiles": "^1.1.2",
|
"aws-ssl-profiles": "^1.1.2",
|
||||||
"denque": "^2.1.0",
|
"denque": "^2.1.0",
|
||||||
@@ -11666,7 +11655,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz",
|
||||||
"integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==",
|
"integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pg-connection-string": "^2.12.0",
|
"pg-connection-string": "^2.12.0",
|
||||||
"pg-pool": "^3.13.0",
|
"pg-pool": "^3.13.0",
|
||||||
@@ -11764,7 +11752,6 @@
|
|||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -11945,6 +11932,7 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commander": "^9.4.0"
|
"commander": "^9.4.0"
|
||||||
},
|
},
|
||||||
@@ -11962,6 +11950,7 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.20.0 || >=14"
|
"node": "^12.20.0 || >=14"
|
||||||
}
|
}
|
||||||
@@ -12149,7 +12138,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0"
|
"loose-envify": "^1.1.0"
|
||||||
},
|
},
|
||||||
@@ -12162,7 +12150,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0",
|
"loose-envify": "^1.1.0",
|
||||||
"scheduler": "^0.23.2"
|
"scheduler": "^0.23.2"
|
||||||
@@ -12451,6 +12438,7 @@
|
|||||||
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"glob": "^7.1.3"
|
"glob": "^7.1.3"
|
||||||
},
|
},
|
||||||
@@ -12463,7 +12451,8 @@
|
|||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/rimraf/node_modules/brace-expansion": {
|
"node_modules/rimraf/node_modules/brace-expansion": {
|
||||||
"version": "1.1.12",
|
"version": "1.1.12",
|
||||||
@@ -12471,6 +12460,7 @@
|
|||||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
@@ -12483,6 +12473,7 @@
|
|||||||
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
|
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fs.realpath": "^1.0.0",
|
"fs.realpath": "^1.0.0",
|
||||||
"inflight": "^1.0.4",
|
"inflight": "^1.0.4",
|
||||||
@@ -12504,6 +12495,7 @@
|
|||||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
},
|
},
|
||||||
@@ -13064,7 +13056,6 @@
|
|||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
||||||
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
|
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6.0.0",
|
"node": ">= 6.0.0",
|
||||||
@@ -13075,7 +13066,6 @@
|
|||||||
"version": "2.8.7",
|
"version": "2.8.7",
|
||||||
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz",
|
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz",
|
||||||
"integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==",
|
"integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ip-address": "^10.0.1",
|
"ip-address": "^10.0.1",
|
||||||
@@ -13485,6 +13475,7 @@
|
|||||||
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
|
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"rimraf": "~2.6.2"
|
"rimraf": "~2.6.2"
|
||||||
@@ -14250,7 +14241,6 @@
|
|||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -14504,7 +14494,6 @@
|
|||||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.4.4",
|
"fdir": "^6.4.4",
|
||||||
@@ -16646,7 +16635,6 @@
|
|||||||
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.21.3",
|
"esbuild": "^0.21.3",
|
||||||
"postcss": "^8.4.43",
|
"postcss": "^8.4.43",
|
||||||
@@ -16762,7 +16750,6 @@
|
|||||||
"integrity": "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==",
|
"integrity": "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-dom": "3.5.29",
|
"@vue/compiler-dom": "3.5.29",
|
||||||
"@vue/compiler-sfc": "3.5.29",
|
"@vue/compiler-sfc": "3.5.29",
|
||||||
|
|||||||
@@ -78,6 +78,7 @@
|
|||||||
"node-cron": "^4.2.1",
|
"node-cron": "^4.2.1",
|
||||||
"nodemailer": "^8.0.1",
|
"nodemailer": "^8.0.1",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
|
"socks": "^2.8.7",
|
||||||
"undici": "^6.24.1",
|
"undici": "^6.24.1",
|
||||||
"ws": "^8.19.0"
|
"ws": "^8.19.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ const createApiTokenMock = vi.fn();
|
|||||||
const getUserGroupsMock = vi.fn();
|
const getUserGroupsMock = vi.fn();
|
||||||
const deleteApiTokenMock = vi.fn();
|
const deleteApiTokenMock = vi.fn();
|
||||||
|
|
||||||
|
type AccountTokenServiceModule = typeof import('../../services/accountTokenService.js');
|
||||||
|
|
||||||
vi.mock('../../services/platforms/index.js', () => ({
|
vi.mock('../../services/platforms/index.js', () => ({
|
||||||
getAdapter: () => ({
|
getAdapter: () => ({
|
||||||
getApiTokens: (...args: unknown[]) => getApiTokensMock(...args),
|
getApiTokens: (...args: unknown[]) => getApiTokensMock(...args),
|
||||||
@@ -28,6 +30,7 @@ describe('account tokens sync routes with site status', () => {
|
|||||||
let app: FastifyInstance;
|
let app: FastifyInstance;
|
||||||
let db: DbModule['db'];
|
let db: DbModule['db'];
|
||||||
let schema: DbModule['schema'];
|
let schema: DbModule['schema'];
|
||||||
|
let maskToken: AccountTokenServiceModule['maskToken'];
|
||||||
let dataDir = '';
|
let dataDir = '';
|
||||||
let seedId = 0;
|
let seedId = 0;
|
||||||
|
|
||||||
@@ -63,9 +66,11 @@ describe('account tokens sync routes with site status', () => {
|
|||||||
|
|
||||||
await import('../../db/migrate.js');
|
await import('../../db/migrate.js');
|
||||||
const dbModule = await import('../../db/index.js');
|
const dbModule = await import('../../db/index.js');
|
||||||
|
const accountTokenServiceModule = await import('../../services/accountTokenService.js');
|
||||||
const routesModule = await import('./accountTokens.js');
|
const routesModule = await import('./accountTokens.js');
|
||||||
db = dbModule.db;
|
db = dbModule.db;
|
||||||
schema = dbModule.schema;
|
schema = dbModule.schema;
|
||||||
|
maskToken = accountTokenServiceModule.maskToken;
|
||||||
|
|
||||||
app = Fastify();
|
app = Fastify();
|
||||||
await app.register(routesModule.accountTokensRoutes);
|
await app.register(routesModule.accountTokensRoutes);
|
||||||
@@ -196,6 +201,247 @@ describe('account tokens sync routes with site status', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('reuses an existing ready token when upstream only returns the matching masked token', async () => {
|
||||||
|
const { account } = await seedAccount({ siteStatus: 'active' });
|
||||||
|
const fullToken = 'sk-real-token-1234';
|
||||||
|
await db.insert(schema.accountTokens).values({
|
||||||
|
accountId: account.id,
|
||||||
|
name: 'masked-only',
|
||||||
|
token: fullToken,
|
||||||
|
source: 'manual',
|
||||||
|
enabled: true,
|
||||||
|
isDefault: true,
|
||||||
|
tokenGroup: 'default',
|
||||||
|
valueStatus: 'ready' as any,
|
||||||
|
}).run();
|
||||||
|
|
||||||
|
getApiTokensMock.mockResolvedValue([
|
||||||
|
{ name: 'masked-only', key: maskToken(fullToken), enabled: true, tokenGroup: 'default' },
|
||||||
|
]);
|
||||||
|
getApiTokenMock.mockResolvedValue(null);
|
||||||
|
|
||||||
|
const response = await app.inject({
|
||||||
|
method: 'POST',
|
||||||
|
url: `/api/account-tokens/sync/${account.id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response.json()).toMatchObject({
|
||||||
|
success: true,
|
||||||
|
synced: true,
|
||||||
|
status: 'synced',
|
||||||
|
created: 0,
|
||||||
|
updated: 1,
|
||||||
|
maskedPending: 0,
|
||||||
|
total: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tokenRows = await db.select()
|
||||||
|
.from(schema.accountTokens)
|
||||||
|
.where(eq(schema.accountTokens.accountId, account.id))
|
||||||
|
.all();
|
||||||
|
expect(tokenRows).toHaveLength(1);
|
||||||
|
expect(tokenRows[0]).toMatchObject({
|
||||||
|
name: 'masked-only',
|
||||||
|
token: fullToken,
|
||||||
|
source: 'sync',
|
||||||
|
enabled: true,
|
||||||
|
isDefault: true,
|
||||||
|
tokenGroup: 'default',
|
||||||
|
});
|
||||||
|
expect((tokenRows[0] as any).valueStatus).toBe('ready');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes matching masked_pending placeholders after reusing a ready token', async () => {
|
||||||
|
const { account } = await seedAccount({ siteStatus: 'active' });
|
||||||
|
const fullToken = 'sk-real-token-1234';
|
||||||
|
const maskedToken = maskToken(fullToken);
|
||||||
|
|
||||||
|
await db.insert(schema.accountTokens).values({
|
||||||
|
accountId: account.id,
|
||||||
|
name: 'masked-only',
|
||||||
|
token: fullToken,
|
||||||
|
source: 'manual',
|
||||||
|
enabled: true,
|
||||||
|
isDefault: true,
|
||||||
|
tokenGroup: 'default',
|
||||||
|
valueStatus: 'ready' as any,
|
||||||
|
}).run();
|
||||||
|
await db.insert(schema.accountTokens).values({
|
||||||
|
accountId: account.id,
|
||||||
|
name: 'masked-only',
|
||||||
|
token: maskedToken,
|
||||||
|
source: 'sync',
|
||||||
|
enabled: false,
|
||||||
|
isDefault: false,
|
||||||
|
tokenGroup: 'default',
|
||||||
|
valueStatus: 'masked_pending' as any,
|
||||||
|
}).run();
|
||||||
|
|
||||||
|
getApiTokensMock.mockResolvedValue([
|
||||||
|
{ name: 'masked-only', key: maskedToken, enabled: true, tokenGroup: 'default' },
|
||||||
|
]);
|
||||||
|
getApiTokenMock.mockResolvedValue(null);
|
||||||
|
|
||||||
|
const response = await app.inject({
|
||||||
|
method: 'POST',
|
||||||
|
url: `/api/account-tokens/sync/${account.id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response.json()).toMatchObject({
|
||||||
|
success: true,
|
||||||
|
synced: true,
|
||||||
|
status: 'synced',
|
||||||
|
created: 0,
|
||||||
|
updated: 1,
|
||||||
|
maskedPending: 0,
|
||||||
|
total: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tokenRows = await db.select()
|
||||||
|
.from(schema.accountTokens)
|
||||||
|
.where(eq(schema.accountTokens.accountId, account.id))
|
||||||
|
.all();
|
||||||
|
expect(tokenRows).toHaveLength(1);
|
||||||
|
expect(tokenRows[0]).toMatchObject({
|
||||||
|
name: 'masked-only',
|
||||||
|
token: fullToken,
|
||||||
|
source: 'sync',
|
||||||
|
enabled: true,
|
||||||
|
isDefault: true,
|
||||||
|
tokenGroup: 'default',
|
||||||
|
});
|
||||||
|
expect((tokenRows[0] as any).valueStatus).toBe('ready');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not reuse a different ready token when another logical token shares the same masked value', async () => {
|
||||||
|
const { account } = await seedAccount({ siteStatus: 'active' });
|
||||||
|
const firstFullToken = 'sk-real-token-1234';
|
||||||
|
const secondFullToken = 'sk-real-zzzzz-1234';
|
||||||
|
const sharedMaskedToken = maskToken(firstFullToken);
|
||||||
|
|
||||||
|
expect(maskToken(secondFullToken)).toBe(sharedMaskedToken);
|
||||||
|
|
||||||
|
await db.insert(schema.accountTokens).values({
|
||||||
|
accountId: account.id,
|
||||||
|
name: 'first-token',
|
||||||
|
token: firstFullToken,
|
||||||
|
source: 'manual',
|
||||||
|
enabled: true,
|
||||||
|
isDefault: true,
|
||||||
|
tokenGroup: 'default',
|
||||||
|
valueStatus: 'ready' as any,
|
||||||
|
}).run();
|
||||||
|
await db.insert(schema.accountTokens).values({
|
||||||
|
accountId: account.id,
|
||||||
|
name: 'second-token',
|
||||||
|
token: sharedMaskedToken,
|
||||||
|
source: 'sync',
|
||||||
|
enabled: false,
|
||||||
|
isDefault: false,
|
||||||
|
tokenGroup: 'default',
|
||||||
|
valueStatus: 'masked_pending' as any,
|
||||||
|
}).run();
|
||||||
|
|
||||||
|
getApiTokensMock.mockResolvedValue([
|
||||||
|
{ name: 'second-token', key: sharedMaskedToken, enabled: true, tokenGroup: 'default' },
|
||||||
|
]);
|
||||||
|
getApiTokenMock.mockResolvedValue(null);
|
||||||
|
|
||||||
|
const response = await app.inject({
|
||||||
|
method: 'POST',
|
||||||
|
url: `/api/account-tokens/sync/${account.id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response.json()).toMatchObject({
|
||||||
|
success: true,
|
||||||
|
synced: true,
|
||||||
|
status: 'synced',
|
||||||
|
created: 0,
|
||||||
|
updated: 1,
|
||||||
|
maskedPending: 1,
|
||||||
|
total: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tokenRows = await db.select()
|
||||||
|
.from(schema.accountTokens)
|
||||||
|
.where(eq(schema.accountTokens.accountId, account.id))
|
||||||
|
.all();
|
||||||
|
expect(tokenRows).toHaveLength(2);
|
||||||
|
expect(tokenRows.find((row) => row.token === firstFullToken)).toMatchObject({
|
||||||
|
name: 'first-token',
|
||||||
|
token: firstFullToken,
|
||||||
|
source: 'manual',
|
||||||
|
enabled: true,
|
||||||
|
isDefault: true,
|
||||||
|
tokenGroup: 'default',
|
||||||
|
});
|
||||||
|
const maskedRow = tokenRows.find((row) => row.name === 'second-token');
|
||||||
|
expect(maskedRow).toMatchObject({
|
||||||
|
token: sharedMaskedToken,
|
||||||
|
source: 'sync',
|
||||||
|
enabled: false,
|
||||||
|
isDefault: false,
|
||||||
|
tokenGroup: 'default',
|
||||||
|
});
|
||||||
|
expect((maskedRow as any)?.valueStatus).toBe('masked_pending');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps fully ambiguous short masks as masked_pending instead of reusing a ready token', async () => {
|
||||||
|
const { account } = await seedAccount({ siteStatus: 'active' });
|
||||||
|
const fullToken = 'sk-abcd';
|
||||||
|
await db.insert(schema.accountTokens).values({
|
||||||
|
accountId: account.id,
|
||||||
|
name: 'short-token',
|
||||||
|
token: fullToken,
|
||||||
|
source: 'manual',
|
||||||
|
enabled: true,
|
||||||
|
isDefault: true,
|
||||||
|
tokenGroup: 'default',
|
||||||
|
valueStatus: 'ready' as any,
|
||||||
|
}).run();
|
||||||
|
|
||||||
|
getApiTokensMock.mockResolvedValue([
|
||||||
|
{ name: 'short-token', key: maskToken(fullToken), enabled: true, tokenGroup: 'default' },
|
||||||
|
]);
|
||||||
|
getApiTokenMock.mockResolvedValue(null);
|
||||||
|
|
||||||
|
const response = await app.inject({
|
||||||
|
method: 'POST',
|
||||||
|
url: `/api/account-tokens/sync/${account.id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response.json()).toMatchObject({
|
||||||
|
success: true,
|
||||||
|
synced: true,
|
||||||
|
status: 'synced',
|
||||||
|
reason: 'upstream_masked_tokens',
|
||||||
|
created: 1,
|
||||||
|
updated: 0,
|
||||||
|
maskedPending: 1,
|
||||||
|
total: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tokenRows = await db.select()
|
||||||
|
.from(schema.accountTokens)
|
||||||
|
.where(eq(schema.accountTokens.accountId, account.id))
|
||||||
|
.all();
|
||||||
|
expect(tokenRows).toHaveLength(2);
|
||||||
|
expect(tokenRows.find((row) => row.token === fullToken)).toBeDefined();
|
||||||
|
const maskedRow = tokenRows.find((row) => row.token === 'sk-***');
|
||||||
|
expect(maskedRow).toMatchObject({
|
||||||
|
name: 'short-token',
|
||||||
|
source: 'sync',
|
||||||
|
enabled: false,
|
||||||
|
isDefault: false,
|
||||||
|
tokenGroup: 'default',
|
||||||
|
});
|
||||||
|
expect((maskedRow as any)?.valueStatus).toBe('masked_pending');
|
||||||
|
});
|
||||||
|
|
||||||
it('rejects sync and token management for apikey connections', async () => {
|
it('rejects sync and token management for apikey connections', async () => {
|
||||||
const { account } = await seedAccount({ siteStatus: 'active', accessToken: '' });
|
const { account } = await seedAccount({ siteStatus: 'active', accessToken: '' });
|
||||||
await db.update(schema.accounts)
|
await db.update(schema.accounts)
|
||||||
|
|||||||
@@ -60,6 +60,41 @@ export function isMaskedTokenValue(token: string | null | undefined): boolean {
|
|||||||
return value.includes('*') || value.includes('•');
|
return value.includes('*') || value.includes('•');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeMaskedTokenForCompare(token: string | null | undefined): string {
|
||||||
|
return normalizeTokenForDisplay(token).replace(/•/g, '*');
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchesMaskedTokenValue(
|
||||||
|
fullToken: string | null | undefined,
|
||||||
|
maskedToken: string | null | undefined,
|
||||||
|
): boolean {
|
||||||
|
const normalizedFull = normalizeTokenForDisplay(fullToken);
|
||||||
|
const normalizedMasked = normalizeMaskedTokenForCompare(maskedToken);
|
||||||
|
if (!normalizedFull || !normalizedMasked) return false;
|
||||||
|
|
||||||
|
if (!isMaskedTokenValue(normalizedMasked)) {
|
||||||
|
return normalizedFull === normalizedMasked;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstMaskIndex = normalizedMasked.search(/[\*]/);
|
||||||
|
const lastMaskIndex = Math.max(
|
||||||
|
normalizedMasked.lastIndexOf('*'),
|
||||||
|
normalizedMasked.lastIndexOf('•'),
|
||||||
|
);
|
||||||
|
if (firstMaskIndex < 0 || lastMaskIndex < firstMaskIndex) {
|
||||||
|
return normalizedFull === normalizedMasked;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prefix = normalizedMasked.slice(0, firstMaskIndex);
|
||||||
|
const suffix = normalizedMasked.slice(lastMaskIndex + 1);
|
||||||
|
const visiblePrefix = prefix.replace(/^sk-/i, '');
|
||||||
|
if (!visiblePrefix && !suffix) return false;
|
||||||
|
if (normalizedFull.length < prefix.length + suffix.length) return false;
|
||||||
|
if (prefix && !normalizedFull.startsWith(prefix)) return false;
|
||||||
|
if (suffix && !normalizedFull.endsWith(suffix)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeTokenValueStatus(value: string | null | undefined): AccountTokenValueStatus {
|
function normalizeTokenValueStatus(value: string | null | undefined): AccountTokenValueStatus {
|
||||||
const normalized = (value || '').trim().toLowerCase();
|
const normalized = (value || '').trim().toLowerCase();
|
||||||
return normalized === ACCOUNT_TOKEN_VALUE_STATUS_MASKED_PENDING
|
return normalized === ACCOUNT_TOKEN_VALUE_STATUS_MASKED_PENDING
|
||||||
@@ -306,6 +341,62 @@ export async function syncTokensFromUpstream(accountId: number, upstreamTokens:
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const matchingReadyByMaskedValue = nextValueStatus === ACCOUNT_TOKEN_VALUE_STATUS_MASKED_PENDING
|
||||||
|
? existing.filter((row) => (
|
||||||
|
resolveAccountTokenValueStatus(row) === ACCOUNT_TOKEN_VALUE_STATUS_READY
|
||||||
|
&& matchesMaskedTokenValue(row.token, tokenValue)
|
||||||
|
&& row.name === tokenName
|
||||||
|
&& sameTokenGroup(row.tokenGroup, row.name, tokenGroup, tokenName)
|
||||||
|
))
|
||||||
|
: [];
|
||||||
|
const readyMaskedMatch = matchingReadyByMaskedValue.length === 1
|
||||||
|
? matchingReadyByMaskedValue[0]
|
||||||
|
: null;
|
||||||
|
if (readyMaskedMatch) {
|
||||||
|
const staleMaskedPlaceholders = existing.filter((row) => (
|
||||||
|
row.id !== readyMaskedMatch.id
|
||||||
|
&& resolveAccountTokenValueStatus(row) === ACCOUNT_TOKEN_VALUE_STATUS_MASKED_PENDING
|
||||||
|
&& matchesMaskedTokenValue(row.token, tokenValue)
|
||||||
|
&& row.name === tokenName
|
||||||
|
&& sameTokenGroup(row.tokenGroup, row.name, tokenGroup, tokenName)
|
||||||
|
));
|
||||||
|
|
||||||
|
await db.update(schema.accountTokens)
|
||||||
|
.set({
|
||||||
|
name: tokenName,
|
||||||
|
tokenGroup,
|
||||||
|
valueStatus: ACCOUNT_TOKEN_VALUE_STATUS_READY,
|
||||||
|
source: 'sync',
|
||||||
|
enabled,
|
||||||
|
updatedAt: now,
|
||||||
|
})
|
||||||
|
.where(eq(schema.accountTokens.id, readyMaskedMatch.id))
|
||||||
|
.run();
|
||||||
|
readyMaskedMatch.name = tokenName;
|
||||||
|
readyMaskedMatch.tokenGroup = tokenGroup;
|
||||||
|
readyMaskedMatch.valueStatus = ACCOUNT_TOKEN_VALUE_STATUS_READY;
|
||||||
|
readyMaskedMatch.enabled = enabled;
|
||||||
|
readyMaskedMatch.source = 'sync';
|
||||||
|
readyMaskedMatch.updatedAt = now;
|
||||||
|
|
||||||
|
if (staleMaskedPlaceholders.length > 0) {
|
||||||
|
for (const placeholder of staleMaskedPlaceholders) {
|
||||||
|
await db.delete(schema.accountTokens)
|
||||||
|
.where(eq(schema.accountTokens.id, placeholder.id))
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
for (const placeholder of staleMaskedPlaceholders) {
|
||||||
|
const placeholderIndex = existing.findIndex((row) => row.id === placeholder.id);
|
||||||
|
if (placeholderIndex >= 0) {
|
||||||
|
existing.splice(placeholderIndex, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updated++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const matchingPlaceholder = existing.find((row) => (
|
const matchingPlaceholder = existing.find((row) => (
|
||||||
isMaskedPendingAccountToken(row)
|
isMaskedPendingAccountToken(row)
|
||||||
&& row.name === tokenName
|
&& row.name === tokenName
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import { once } from 'node:events';
|
||||||
import { mkdtempSync } from 'node:fs';
|
import { mkdtempSync } from 'node:fs';
|
||||||
|
import { createServer } from 'node:http';
|
||||||
|
import { connect as connectSocket } from 'node:net';
|
||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { sql } from 'drizzle-orm';
|
import { sql } from 'drizzle-orm';
|
||||||
import { Headers } from 'undici';
|
import { SocksClient } from 'socks';
|
||||||
|
import { Headers, fetch } from 'undici';
|
||||||
|
|
||||||
type DbModule = typeof import('../db/index.js');
|
type DbModule = typeof import('../db/index.js');
|
||||||
|
|
||||||
@@ -71,6 +75,72 @@ describe('siteProxy', () => {
|
|||||||
expect('dispatcher' in requestInit).toBe(true);
|
expect('dispatcher' in requestInit).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('injects a working dispatcher for socks5 system proxies', async () => {
|
||||||
|
const upstreamServer = createServer((_request, response) => {
|
||||||
|
response.writeHead(200, { 'content-type': 'application/json' });
|
||||||
|
response.end(JSON.stringify({ ok: true }));
|
||||||
|
});
|
||||||
|
upstreamServer.listen(0, '127.0.0.1');
|
||||||
|
await once(upstreamServer, 'listening');
|
||||||
|
const upstreamAddress = upstreamServer.address();
|
||||||
|
if (!upstreamAddress || typeof upstreamAddress === 'string') {
|
||||||
|
throw new Error('Failed to determine upstream server address');
|
||||||
|
}
|
||||||
|
const requestUrl = `http://proxy-site.example.com:${upstreamAddress.port}/v1/chat/completions`;
|
||||||
|
|
||||||
|
await db.insert(schema.settings).values({
|
||||||
|
key: 'system_proxy_url',
|
||||||
|
value: JSON.stringify('socks5h://127.0.0.1:1080'),
|
||||||
|
}).run();
|
||||||
|
await db.run(sql`
|
||||||
|
INSERT INTO sites (name, url, platform, use_system_proxy)
|
||||||
|
VALUES ('proxy-site', ${`http://proxy-site.example.com:${upstreamAddress.port}`}, 'new-api', 1)
|
||||||
|
`);
|
||||||
|
|
||||||
|
const createConnectionSpy = vi.spyOn(SocksClient, 'createConnection').mockImplementation(async () => {
|
||||||
|
const socket = connectSocket(upstreamAddress.port, '127.0.0.1');
|
||||||
|
await once(socket, 'connect');
|
||||||
|
return { socket } as Awaited<ReturnType<typeof SocksClient.createConnection>>;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { withSiteProxyRequestInit } = await import('./siteProxy.js');
|
||||||
|
const requestInit = await withSiteProxyRequestInit(requestUrl, {
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect('dispatcher' in requestInit).toBe(true);
|
||||||
|
|
||||||
|
const response = await fetch(requestUrl, requestInit);
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(createConnectionSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(createConnectionSpy).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
|
command: 'connect',
|
||||||
|
proxy: expect.objectContaining({
|
||||||
|
host: '127.0.0.1',
|
||||||
|
port: 1080,
|
||||||
|
type: 5,
|
||||||
|
}),
|
||||||
|
destination: expect.objectContaining({
|
||||||
|
host: 'proxy-site.example.com',
|
||||||
|
port: upstreamAddress.port,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
} finally {
|
||||||
|
createConnectionSpy.mockRestore();
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
upstreamServer.close((error) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('merges site custom headers by matched request url and keeps explicit headers authoritative', async () => {
|
it('merges site custom headers by matched request url and keeps explicit headers authoritative', async () => {
|
||||||
await db.insert(schema.sites).values({
|
await db.insert(schema.sites).values({
|
||||||
name: 'headers-site',
|
name: 'headers-site',
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import { db, schema } from '../db/index.js';
|
import { db, schema } from '../db/index.js';
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
import { config } from '../config.js';
|
import { config } from '../config.js';
|
||||||
|
import { lookup as dnsLookup } from 'node:dns';
|
||||||
|
import { isIP, type Socket } from 'node:net';
|
||||||
|
import { connect as tlsConnect, type TLSSocket } from 'node:tls';
|
||||||
|
import { SocksClient } from 'socks';
|
||||||
import type { Dispatcher, RequestInit as UndiciRequestInit } from 'undici';
|
import type { Dispatcher, RequestInit as UndiciRequestInit } from 'undici';
|
||||||
import { ProxyAgent } from 'undici';
|
import { Agent as UndiciAgent, ProxyAgent } from 'undici';
|
||||||
import { mergeHeadersWithSiteCustomHeaders } from './siteCustomHeaders.js';
|
import { mergeHeadersWithSiteCustomHeaders } from './siteCustomHeaders.js';
|
||||||
|
|
||||||
const SITE_PROXY_CACHE_TTL_MS = 3_000;
|
const SITE_PROXY_CACHE_TTL_MS = 3_000;
|
||||||
@@ -15,6 +19,15 @@ const SUPPORTED_PROXY_PROTOCOLS = new Set([
|
|||||||
'socks5:',
|
'socks5:',
|
||||||
'socks5h:',
|
'socks5h:',
|
||||||
]);
|
]);
|
||||||
|
const SOCKS_PROXY_PROTOCOLS = new Set([
|
||||||
|
'socks:',
|
||||||
|
'socks4:',
|
||||||
|
'socks4a:',
|
||||||
|
'socks5:',
|
||||||
|
'socks5h:',
|
||||||
|
]);
|
||||||
|
const DEFAULT_PROXY_CONNECT_TIMEOUT_MS = 10_000;
|
||||||
|
const DEFAULT_PROXY_KEEPALIVE_INITIAL_DELAY_MS = 60_000;
|
||||||
|
|
||||||
type SiteProxyRow = {
|
type SiteProxyRow = {
|
||||||
siteUrl: string;
|
siteUrl: string;
|
||||||
@@ -45,6 +58,27 @@ let siteProxyCache: {
|
|||||||
|
|
||||||
const dispatcherCache = new Map<string, Dispatcher>();
|
const dispatcherCache = new Map<string, Dispatcher>();
|
||||||
|
|
||||||
|
type ParsedSocksProxyConfig = {
|
||||||
|
shouldLookup: boolean;
|
||||||
|
proxy: {
|
||||||
|
host: string;
|
||||||
|
port: number;
|
||||||
|
type: 4 | 5;
|
||||||
|
userId?: string;
|
||||||
|
password?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type UndiciConnectOptions = {
|
||||||
|
hostname: string;
|
||||||
|
host?: string;
|
||||||
|
protocol: string;
|
||||||
|
port: string;
|
||||||
|
servername?: string;
|
||||||
|
localAddress?: string | null;
|
||||||
|
httpSocket?: Socket;
|
||||||
|
};
|
||||||
|
|
||||||
function normalizeSiteUrl(value: string): string {
|
function normalizeSiteUrl(value: string): string {
|
||||||
const trimmed = (value || '').trim();
|
const trimmed = (value || '').trim();
|
||||||
if (!trimmed) return '';
|
if (!trimmed) return '';
|
||||||
@@ -115,7 +149,10 @@ function getDispatcherByProxyUrl(proxyUrl: string): Dispatcher | undefined {
|
|||||||
if (cached) return cached;
|
if (cached) return cached;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const dispatcher = new ProxyAgent(normalized);
|
const parsedProxyUrl = new URL(normalized);
|
||||||
|
const dispatcher = SOCKS_PROXY_PROTOCOLS.has(parsedProxyUrl.protocol.toLowerCase())
|
||||||
|
? createSocksDispatcher(parsedProxyUrl)
|
||||||
|
: new ProxyAgent(normalized);
|
||||||
dispatcherCache.set(normalized, dispatcher);
|
dispatcherCache.set(normalized, dispatcher);
|
||||||
return dispatcher;
|
return dispatcher;
|
||||||
} catch {
|
} catch {
|
||||||
@@ -123,6 +160,131 @@ function getDispatcherByProxyUrl(proxyUrl: string): Dispatcher | undefined {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseSocksProxyUrl(proxyUrl: URL): ParsedSocksProxyConfig {
|
||||||
|
let shouldLookup = false;
|
||||||
|
let type: 4 | 5 = 5;
|
||||||
|
|
||||||
|
switch (proxyUrl.protocol.toLowerCase()) {
|
||||||
|
case 'socks4:':
|
||||||
|
shouldLookup = true;
|
||||||
|
type = 4;
|
||||||
|
break;
|
||||||
|
case 'socks4a:':
|
||||||
|
type = 4;
|
||||||
|
break;
|
||||||
|
case 'socks5:':
|
||||||
|
shouldLookup = true;
|
||||||
|
type = 5;
|
||||||
|
break;
|
||||||
|
case 'socks:':
|
||||||
|
case 'socks5h:':
|
||||||
|
type = 5;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new TypeError(`Unsupported SOCKS proxy protocol: ${proxyUrl.protocol}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxy: ParsedSocksProxyConfig['proxy'] = {
|
||||||
|
host: proxyUrl.hostname,
|
||||||
|
port: Number.parseInt(proxyUrl.port, 10) || 1080,
|
||||||
|
type,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (proxyUrl.username) {
|
||||||
|
proxy.userId = decodeURIComponent(proxyUrl.username);
|
||||||
|
}
|
||||||
|
if (proxyUrl.password) {
|
||||||
|
proxy.password = decodeURIComponent(proxyUrl.password);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { shouldLookup, proxy };
|
||||||
|
}
|
||||||
|
|
||||||
|
function applySocketDefaults(socket: Socket | TLSSocket) {
|
||||||
|
socket.setNoDelay(true);
|
||||||
|
socket.setKeepAlive(true, DEFAULT_PROXY_KEEPALIVE_INITIAL_DELAY_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveSocksDestinationHost(hostname: string): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
dnsLookup(hostname, {}, (error, address) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve(address);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createSocksSocket(
|
||||||
|
connectOptions: UndiciConnectOptions,
|
||||||
|
socksProxy: ParsedSocksProxyConfig,
|
||||||
|
): Promise<Socket | TLSSocket> {
|
||||||
|
if (!connectOptions.hostname) {
|
||||||
|
throw new Error('Missing hostname for SOCKS proxy request');
|
||||||
|
}
|
||||||
|
|
||||||
|
const destinationHost = socksProxy.shouldLookup
|
||||||
|
? await resolveSocksDestinationHost(connectOptions.hostname)
|
||||||
|
: connectOptions.hostname;
|
||||||
|
const destinationPort = Number.parseInt(connectOptions.port, 10)
|
||||||
|
|| (connectOptions.protocol === 'https:' ? 443 : 80);
|
||||||
|
|
||||||
|
const { socket } = await SocksClient.createConnection({
|
||||||
|
proxy: socksProxy.proxy,
|
||||||
|
destination: {
|
||||||
|
host: destinationHost,
|
||||||
|
port: destinationPort,
|
||||||
|
},
|
||||||
|
command: 'connect',
|
||||||
|
timeout: DEFAULT_PROXY_CONNECT_TIMEOUT_MS,
|
||||||
|
socket_options: connectOptions.localAddress
|
||||||
|
? { localAddress: connectOptions.localAddress } as any
|
||||||
|
: undefined,
|
||||||
|
});
|
||||||
|
applySocketDefaults(socket);
|
||||||
|
|
||||||
|
if (connectOptions.protocol !== 'https:') {
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await new Promise<TLSSocket>((resolve, reject) => {
|
||||||
|
const tlsSocket = tlsConnect({
|
||||||
|
socket,
|
||||||
|
host: connectOptions.hostname,
|
||||||
|
servername: connectOptions.servername || (!isIP(connectOptions.hostname) ? connectOptions.hostname : undefined),
|
||||||
|
ALPNProtocols: ['http/1.1'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const cleanup = (error: Error) => {
|
||||||
|
socket.destroy();
|
||||||
|
tlsSocket.destroy();
|
||||||
|
reject(error);
|
||||||
|
};
|
||||||
|
|
||||||
|
tlsSocket.once('secureConnect', () => {
|
||||||
|
tlsSocket.off('error', cleanup);
|
||||||
|
applySocketDefaults(tlsSocket);
|
||||||
|
resolve(tlsSocket);
|
||||||
|
});
|
||||||
|
tlsSocket.once('error', cleanup);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSocksDispatcher(proxyUrl: URL): Dispatcher {
|
||||||
|
const socksProxy = parseSocksProxyUrl(proxyUrl);
|
||||||
|
return new UndiciAgent({
|
||||||
|
connect: (connectOptions, callback) => {
|
||||||
|
void createSocksSocket(connectOptions, socksProxy)
|
||||||
|
.then((socket) => callback(null, socket))
|
||||||
|
.catch((error) => {
|
||||||
|
callback(error instanceof Error ? error : new Error(String(error)), null as any);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function normalizeSiteProxyUrl(input: unknown): string | null {
|
export function normalizeSiteProxyUrl(input: unknown): string | null {
|
||||||
if (typeof input !== 'string') return null;
|
if (typeof input !== 'string') return null;
|
||||||
const trimmed = input.trim();
|
const trimmed = input.trim();
|
||||||
|
|||||||
Reference in New Issue
Block a user