fix anyrouter balance refresh challenge errors (#368)

This commit is contained in:
Cita
2026-04-01 16:53:01 +08:00
committed by GitHub
parent 532be86e93
commit e2d9481b95
2 changed files with 50 additions and 2 deletions
@@ -20,6 +20,7 @@ const CHECKIN_INVALID_URL_EXPIRED_SESSION_TOKEN = 'checkin-invalid-url-expired-s
const CHECKIN_INVALID_URL_FORBIDDEN_SESSION_TOKEN = 'checkin-invalid-url-forbidden-session-token'; const CHECKIN_INVALID_URL_FORBIDDEN_SESSION_TOKEN = 'checkin-invalid-url-forbidden-session-token';
const CHECKIN_CLOUDFLARE_530_TOKEN = 'checkin-cloudflare-530-token'; const CHECKIN_CLOUDFLARE_530_TOKEN = 'checkin-cloudflare-530-token';
const BALANCE_FAIL_TOKEN = 'balance-fail-token'; const BALANCE_FAIL_TOKEN = 'balance-fail-token';
const BALANCE_SHIELD_FAILURE_TOKEN = 'balance-shield-failure-token';
const GROUP_EXPIRED_TOKEN = 'group-expired-token'; const GROUP_EXPIRED_TOKEN = 'group-expired-token';
const SHIELD_LOGIN_USERNAME = 'shield-user'; const SHIELD_LOGIN_USERNAME = 'shield-user';
const SHIELD_LOGIN_PASSWORD = 'shield-pass'; const SHIELD_LOGIN_PASSWORD = 'shield-pass';
@@ -257,12 +258,38 @@ describe('NewApiAdapter', () => {
} }
if (req.url === '/api/user/self') { if (req.url === '/api/user/self') {
if (typeof req.headers.authorization === 'string' && req.headers.authorization === `Bearer ${BALANCE_SHIELD_FAILURE_TOKEN}`) {
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8',
'Set-Cookie': `cdn_sec_tc=${SHIELD_LOGIN_COOKIE}; Path=/; HttpOnly`,
});
res.end(ANYROUTER_CHALLENGE_HTML);
return;
}
if (typeof req.headers.authorization === 'string' && req.headers.authorization === `Bearer ${BALANCE_FAIL_TOKEN}`) { if (typeof req.headers.authorization === 'string' && req.headers.authorization === `Bearer ${BALANCE_FAIL_TOKEN}`) {
res.writeHead(200, { 'Content-Type': 'application/json' }); res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: false, message: '无权进行此操作,access token 无效' })); res.end(JSON.stringify({ success: false, message: '无权进行此操作,access token 无效' }));
return; return;
} }
if (
typeof req.headers.cookie === 'string' &&
(
req.headers.cookie.includes(`session=${BALANCE_SHIELD_FAILURE_TOKEN}`) ||
req.headers.cookie.includes(`token=${BALANCE_SHIELD_FAILURE_TOKEN}`)
)
) {
if (!req.headers.cookie.includes(`acw_sc__v2=${ANYROUTER_CHALLENGE_ACW}`)) {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(ANYROUTER_CHALLENGE_HTML);
return;
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: false, message: '无权进行此操作,未登录且未提供 access token' }));
return;
}
if ( if (
typeof req.headers.cookie === 'string' && typeof req.headers.cookie === 'string' &&
( (
@@ -663,6 +690,13 @@ describe('NewApiAdapter', () => {
await expect(adapter.getBalance(baseUrl, BALANCE_FAIL_TOKEN)).rejects.toThrow('access token'); await expect(adapter.getBalance(baseUrl, BALANCE_FAIL_TOKEN)).rejects.toThrow('access token');
}); });
it('prefers post-challenge cookie failure over raw html parse error when reading balance', async () => {
const adapter = new AnyRouterAdapter();
await expect(adapter.getBalance(baseUrl, BALANCE_SHIELD_FAILURE_TOKEN)).rejects
.toThrow('无权进行此操作,未登录且未提供 access token');
});
it('preserves nested checkin error message instead of generic fallback', async () => { it('preserves nested checkin error message instead of generic fallback', async () => {
const adapter = new NewApiAdapter(); const adapter = new NewApiAdapter();
const result = await adapter.checkin(baseUrl, CHECKIN_INVALID_URL_TOKEN, 11494); const result = await adapter.checkin(baseUrl, CHECKIN_INVALID_URL_TOKEN, 11494);
+15 -1
View File
@@ -549,6 +549,15 @@ export class NewApiAdapter extends BasePlatformAdapter {
return ''; return '';
} }
private isHtmlJsonParseErrorMessage(message?: string | null): boolean {
if (!message) return false;
const text = message.toLowerCase();
return (
text.includes("unexpected token '<'")
|| (text.includes('not valid json') && (text.includes('<html') || text.includes('<script')))
);
}
private isShieldChallenge(contentType: string, text: string): boolean { private isShieldChallenge(contentType: string, text: string): boolean {
const ct = (contentType || '').toLowerCase(); const ct = (contentType || '').toLowerCase();
if (ct.includes('text/html') && /var\s+arg1\s*=|acw_sc__v2|cdn_sec_tc|<script/i.test(text)) { if (ct.includes('text/html') && /var\s+arg1\s*=|acw_sc__v2|cdn_sec_tc|<script/i.test(text)) {
@@ -1162,10 +1171,15 @@ export class NewApiAdapter extends BasePlatformAdapter {
const resolvedUserId = platformUserId || await this.discoverUserId(baseUrl, accessToken); const resolvedUserId = platformUserId || await this.discoverUserId(baseUrl, accessToken);
let failureMessage: string | null = null; let failureMessage: string | null = null;
const rememberFailure = (message?: string | null) => { const rememberFailure = (message?: string | null) => {
if (failureMessage) return;
const text = typeof message === 'string' ? message.trim() : ''; const text = typeof message === 'string' ? message.trim() : '';
if (!text) return; if (!text) return;
if (!failureMessage) {
failureMessage = text; failureMessage = text;
return;
}
if (this.isHtmlJsonParseErrorMessage(failureMessage) && !this.isHtmlJsonParseErrorMessage(text)) {
failureMessage = text;
}
}; };
try { try {