Skip to content

Commit 741b3ce

Browse files
author
Taois
committed
feat: 新增文件流代理接口,并增加授权码
1 parent e4d5039 commit 741b3ce

File tree

5 files changed

+280
-13
lines changed

5 files changed

+280
-13
lines changed

.env.development

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ CLIPBOARD_ALLOWED_CHARSET=
4949
# 最大可读取问文本体积,默认2mb
5050
CLIPBOARD_MAX_READ_SIZE=
5151

52+
# 允许清理文件代理缓存
53+
allow_file_cache_clear=1
54+
5255
# API超时配置(秒)
5356
# 默认API超时时间
5457
API_TIMEOUT=20

controllers/file-proxy.js

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,27 @@ export default (fastify, options, done) => {
2121
// 缓存超时时间(5分钟)
2222
const cacheTimeout = 5 * 60 * 1000;
2323

24+
/**
25+
* 验证身份认证
26+
* @param {Object} request - Fastify请求对象
27+
* @param {Object} reply - Fastify响应对象
28+
* @returns {boolean} 验证是否通过
29+
*/
30+
function verifyAuth(request, reply) {
31+
const requiredAuth = ENV.get('PROXY_AUTH', 'drpys');
32+
const providedAuth = request.query.auth;
33+
34+
if (!providedAuth || providedAuth !== requiredAuth) {
35+
reply.status(401).send({
36+
error: 'Unauthorized',
37+
message: 'Missing or invalid auth parameter',
38+
code: 401
39+
});
40+
return false;
41+
}
42+
return true;
43+
}
44+
2445
/**
2546
* 解码参数 - 支持 base64 解码
2647
* @param {string} param - 需要解码的参数
@@ -187,6 +208,11 @@ export default (fastify, options, done) => {
187208
method: ['GET', 'HEAD'],
188209
url: '/file-proxy/proxy',
189210
handler: async (request, reply) => {
211+
// 验证身份认证
212+
if (!verifyAuth(request, reply)) {
213+
return;
214+
}
215+
190216
const { url: urlParam, headers: headersParam } = request.query;
191217

192218
console.log(`[fileProxyController] ${request.method} request for URL: ${urlParam}`);
@@ -284,6 +310,11 @@ export default (fastify, options, done) => {
284310
* GET /file-proxy/info - 获取远程文件信息(HEAD 请求)
285311
*/
286312
fastify.get('/file-proxy/info', async (request, reply) => {
313+
// 验证身份认证
314+
if (!verifyAuth(request, reply)) {
315+
return;
316+
}
317+
287318
const { url: urlParam, headers: headersParam } = request.query;
288319

289320
console.log(`[fileProxyController] Info request for URL: ${urlParam}`);
@@ -352,12 +383,17 @@ export default (fastify, options, done) => {
352383
* DELETE /file-proxy/cache - 清理缓存
353384
*/
354385
fastify.delete('/file-proxy/cache', async (request, reply) => {
386+
// 验证身份认证
387+
if (!verifyAuth(request, reply)) {
388+
return;
389+
}
390+
355391
console.log(`[fileProxyController] Cache clear request`);
356392

357393
try {
358394
// 非VERCEL环境可在设置中心控制此功能是否开启
359395
if (!process.env.VERCEL) {
360-
if (ENV.get('allow_file_cache_clear') !== '1') {
396+
if (!Number(process.env.allow_file_cache_clear)) {
361397
return reply.status(403).send({ error: 'Cache clear is not allowed by owner' });
362398
}
363399
}
@@ -401,16 +437,22 @@ export default (fastify, options, done) => {
401437
'Base64 parameter decoding',
402438
'Range request support',
403439
'Custom headers support',
404-
'CORS support'
440+
'CORS support',
441+
'Authentication protection'
405442
],
406443
endpoints: [
407-
'GET /file-proxy/health - Health check',
408-
'GET /file-proxy/proxy?url=<remote_url>&headers=<custom_headers> - Proxy remote file',
409-
'HEAD /file-proxy/proxy?url=<remote_url>&headers=<custom_headers> - Get remote file headers',
410-
'GET /file-proxy/info?url=<remote_url>&headers=<custom_headers> - Get remote file information',
411-
'DELETE /file-proxy/cache - Clear cache',
412-
'GET /file-proxy/status - Get service status'
413-
]
444+
'GET /file-proxy/health - Health check (no auth required)',
445+
'GET /file-proxy/proxy?url=<remote_url>&auth=<auth_code>&headers=<custom_headers> - Proxy remote file',
446+
'HEAD /file-proxy/proxy?url=<remote_url>&auth=<auth_code>&headers=<custom_headers> - Get remote file headers',
447+
'GET /file-proxy/info?url=<remote_url>&auth=<auth_code>&headers=<custom_headers> - Get remote file information',
448+
'DELETE /file-proxy/cache?auth=<auth_code> - Clear cache',
449+
'GET /file-proxy/status - Get service status (no auth required)'
450+
],
451+
auth: {
452+
required: true,
453+
parameter: 'auth',
454+
description: 'Authentication code required for protected endpoints'
455+
}
414456
});
415457
} catch (error) {
416458
console.error('[fileProxyController] Status request error:', error);

examples/test-file-proxy-auth.js

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
import http from 'http';
2+
3+
// 测试配置
4+
const config = {
5+
proxyHost: 'localhost',
6+
proxyPort: 3001,
7+
authCode: 'drpys',
8+
wrongAuthCode: 'wrong_auth',
9+
testUrl: 'https://httpbin.org/json'
10+
};
11+
12+
// 发送 HTTP 请求的辅助函数
13+
function makeRequest(options) {
14+
return new Promise((resolve, reject) => {
15+
const req = http.request(options, (res) => {
16+
let data = '';
17+
res.on('data', chunk => data += chunk);
18+
res.on('end', () => {
19+
resolve({
20+
statusCode: res.statusCode,
21+
headers: res.headers,
22+
data: data
23+
});
24+
});
25+
});
26+
27+
req.on('error', reject);
28+
req.end();
29+
});
30+
}
31+
32+
// 测试 1: 无身份验证参数
33+
async function testNoAuth() {
34+
console.log('\n=== 测试 1: 无身份验证参数 ===');
35+
36+
try {
37+
const options = {
38+
hostname: config.proxyHost,
39+
port: config.proxyPort,
40+
path: `/file-proxy/proxy?url=${encodeURIComponent(config.testUrl)}`,
41+
method: 'GET'
42+
};
43+
44+
console.log(`请求路径: ${options.path}`);
45+
const response = await makeRequest(options);
46+
47+
console.log(`状态码: ${response.statusCode}`);
48+
console.log(`响应数据: ${response.data.substring(0, 200)}`);
49+
50+
if (response.statusCode === 401) {
51+
console.log('✅ 无身份验证参数测试通过 - 正确返回 401');
52+
} else {
53+
console.log('❌ 无身份验证参数测试失败 - 应该返回 401');
54+
}
55+
} catch (error) {
56+
console.log(`❌ 无身份验证参数测试出错: ${error.message}`);
57+
}
58+
}
59+
60+
// 测试 2: 错误的身份验证参数
61+
async function testWrongAuth() {
62+
console.log('\n=== 测试 2: 错误的身份验证参数 ===');
63+
64+
try {
65+
const options = {
66+
hostname: config.proxyHost,
67+
port: config.proxyPort,
68+
path: `/file-proxy/proxy?url=${encodeURIComponent(config.testUrl)}&auth=${config.wrongAuthCode}`,
69+
method: 'GET'
70+
};
71+
72+
console.log(`请求路径: ${options.path}`);
73+
const response = await makeRequest(options);
74+
75+
console.log(`状态码: ${response.statusCode}`);
76+
console.log(`响应数据: ${response.data.substring(0, 200)}`);
77+
78+
if (response.statusCode === 401) {
79+
console.log('✅ 错误身份验证参数测试通过 - 正确返回 401');
80+
} else {
81+
console.log('❌ 错误身份验证参数测试失败 - 应该返回 401');
82+
}
83+
} catch (error) {
84+
console.log(`❌ 错误身份验证参数测试出错: ${error.message}`);
85+
}
86+
}
87+
88+
// 测试 3: 正确的身份验证参数
89+
async function testCorrectAuth() {
90+
console.log('\n=== 测试 3: 正确的身份验证参数 ===');
91+
92+
try {
93+
const options = {
94+
hostname: config.proxyHost,
95+
port: config.proxyPort,
96+
path: `/file-proxy/proxy?url=${encodeURIComponent(config.testUrl)}&auth=${config.authCode}`,
97+
method: 'GET'
98+
};
99+
100+
console.log(`请求路径: ${options.path}`);
101+
const response = await makeRequest(options);
102+
103+
console.log(`状态码: ${response.statusCode}`);
104+
console.log(`响应数据: ${response.data.substring(0, 200)}...`);
105+
106+
if (response.statusCode === 200) {
107+
console.log('✅ 正确身份验证参数测试通过 - 正确返回 200');
108+
} else {
109+
console.log('❌ 正确身份验证参数测试失败 - 应该返回 200');
110+
}
111+
} catch (error) {
112+
console.log(`❌ 正确身份验证参数测试出错: ${error.message}`);
113+
}
114+
}
115+
116+
// 测试 4: info 接口身份验证
117+
async function testInfoAuth() {
118+
console.log('\n=== 测试 4: info 接口身份验证 ===');
119+
120+
try {
121+
// 无 auth 参数
122+
const optionsNoAuth = {
123+
hostname: config.proxyHost,
124+
port: config.proxyPort,
125+
path: `/file-proxy/info?url=${encodeURIComponent(config.testUrl)}`,
126+
method: 'GET'
127+
};
128+
129+
console.log(`无 auth 请求路径: ${optionsNoAuth.path}`);
130+
const responseNoAuth = await makeRequest(optionsNoAuth);
131+
console.log(`无 auth 状态码: ${responseNoAuth.statusCode}`);
132+
133+
// 有 auth 参数
134+
const optionsWithAuth = {
135+
hostname: config.proxyHost,
136+
port: config.proxyPort,
137+
path: `/file-proxy/info?url=${encodeURIComponent(config.testUrl)}&auth=${config.authCode}`,
138+
method: 'GET'
139+
};
140+
141+
console.log(`有 auth 请求路径: ${optionsWithAuth.path}`);
142+
const responseWithAuth = await makeRequest(optionsWithAuth);
143+
console.log(`有 auth 状态码: ${responseWithAuth.statusCode}`);
144+
145+
if (responseNoAuth.statusCode === 401 && responseWithAuth.statusCode === 200) {
146+
console.log('✅ info 接口身份验证测试通过');
147+
} else {
148+
console.log('❌ info 接口身份验证测试失败');
149+
}
150+
} catch (error) {
151+
console.log(`❌ info 接口身份验证测试出错: ${error.message}`);
152+
}
153+
}
154+
155+
// 测试 5: cache 接口身份验证
156+
async function testCacheAuth() {
157+
console.log('\n=== 测试 5: cache 接口身份验证 ===');
158+
159+
try {
160+
// 无 auth 参数
161+
const optionsNoAuth = {
162+
hostname: config.proxyHost,
163+
port: config.proxyPort,
164+
path: `/file-proxy/cache`,
165+
method: 'DELETE'
166+
};
167+
168+
console.log(`无 auth 请求路径: ${optionsNoAuth.path}`);
169+
const responseNoAuth = await makeRequest(optionsNoAuth);
170+
console.log(`无 auth 状态码: ${responseNoAuth.statusCode}`);
171+
172+
// 有 auth 参数
173+
const optionsWithAuth = {
174+
hostname: config.proxyHost,
175+
port: config.proxyPort,
176+
path: `/file-proxy/cache?auth=${config.authCode}`,
177+
method: 'DELETE'
178+
};
179+
180+
console.log(`有 auth 请求路径: ${optionsWithAuth.path}`);
181+
const responseWithAuth = await makeRequest(optionsWithAuth);
182+
console.log(`有 auth 状态码: ${responseWithAuth.statusCode}`);
183+
184+
if (responseNoAuth.statusCode === 401 && responseWithAuth.statusCode === 200) {
185+
console.log('✅ cache 接口身份验证测试通过');
186+
} else {
187+
console.log('❌ cache 接口身份验证测试失败');
188+
}
189+
} catch (error) {
190+
console.log(`❌ cache 接口身份验证测试出错: ${error.message}`);
191+
}
192+
}
193+
194+
// 运行所有身份验证测试
195+
async function runAuthTests() {
196+
console.log('开始运行文件代理身份验证测试...');
197+
198+
await testNoAuth();
199+
await testWrongAuth();
200+
await testCorrectAuth();
201+
await testInfoAuth();
202+
await testCacheAuth();
203+
204+
console.log('\n身份验证测试完成!');
205+
}
206+
207+
runAuthTests().catch(console.error);
208+
209+
export {
210+
runAuthTests,
211+
testNoAuth,
212+
testWrongAuth,
213+
testCorrectAuth,
214+
testInfoAuth,
215+
testCacheAuth
216+
};

examples/test-file-proxy.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import http from 'http';
44
const config = {
55
proxyHost: 'localhost',
66
proxyPort: 3001,
7+
authCode: 'drpys', // 身份验证码
78
testUrls: [
89
'https://httpbin.org/json',
910
'https://httpbin.org/headers',
@@ -45,7 +46,7 @@ async function testBasicProxy() {
4546
const options = {
4647
hostname: config.proxyHost,
4748
port: config.proxyPort,
48-
path: `/file-proxy/proxy?url=${encodeURIComponent(testUrl)}`,
49+
path: `/file-proxy/proxy?url=${encodeURIComponent(testUrl)}&auth=${config.authCode}`,
4950
method: 'GET'
5051
};
5152

@@ -78,7 +79,7 @@ async function testBase64UrlProxy() {
7879
const options = {
7980
hostname: config.proxyHost,
8081
port: config.proxyPort,
81-
path: `/file-proxy/proxy?url=${encodedUrl}`,
82+
path: `/file-proxy/proxy?url=${encodedUrl}&auth=${config.authCode}`,
8283
method: 'GET'
8384
};
8485

@@ -118,7 +119,7 @@ async function testCustomHeaders() {
118119
const options = {
119120
hostname: config.proxyHost,
120121
port: config.proxyPort,
121-
path: `/file-proxy/proxy?url=${encodeURIComponent(testUrl)}&headers=${encodedHeaders}`,
122+
path: `/file-proxy/proxy?url=${encodeURIComponent(testUrl)}&headers=${encodedHeaders}&auth=${config.authCode}`,
122123
method: 'GET'
123124
};
124125

@@ -150,7 +151,7 @@ async function testHeadRequest() {
150151
const options = {
151152
hostname: config.proxyHost,
152153
port: config.proxyPort,
153-
path: `/file-proxy/proxy?url=${encodeURIComponent(testUrl)}`,
154+
path: `/file-proxy/proxy?url=${encodeURIComponent(testUrl)}&auth=${config.authCode}`,
154155
method: 'HEAD'
155156
};
156157

spider/js/设置中心.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,9 @@ var rule = {
454454
case 'videoParse':
455455
d.push(genMultiInput('mg_hz', '设置芒果解析画质', '默认为4,可自行配置成其他值 (视频质量,9=4K, 4=1080p, 3=720p, 2=560p)', images.settings));
456456
d.push(getInput('get_mg_hz', '查看芒果解析画质', images.settings));
457+
458+
d.push(genMultiInput('PROXY_AUTH', '设置代理播放授权', '默认为drpys,可自行配置成其他值', images.settings));
459+
d.push(getInput('get_PROXY_AUTH', '查看代理播放授权', images.settings));
457460
break;
458461
}
459462
return d
@@ -1286,6 +1289,7 @@ var rule = {
12861289
'cat_sub_code',
12871290
'must_sub_code',
12881291
'mg_hz',
1292+
'PROXY_AUTH',
12891293
];
12901294
let get_cookie_sets = [
12911295
'get_quark_cookie',
@@ -1319,6 +1323,7 @@ var rule = {
13191323
'get_cat_sub_code',
13201324
'get_must_sub_code',
13211325
'get_mg_hz',
1326+
'get_PROXY_AUTH',
13221327
];
13231328
if (cookie_sets.includes(action) && value) {
13241329
try {

0 commit comments

Comments
 (0)