[codex] fix anyrouter checkin session failure classification (#353)
* fix anyrouter checkin session failure classification * fix auth failure classification review follow-up
This commit is contained in:
@@ -16,6 +16,8 @@ const COOKIE_SESSION_TOKEN = 'cookie-session-token';
|
|||||||
const COOKIE_REQUIRES_USER_TOKEN = 'cookie-requires-user';
|
const COOKIE_REQUIRES_USER_TOKEN = 'cookie-requires-user';
|
||||||
const CHECKIN_ALREADY_TOKEN = 'checkin-already-token';
|
const CHECKIN_ALREADY_TOKEN = 'checkin-already-token';
|
||||||
const CHECKIN_INVALID_URL_TOKEN = 'checkin-invalid-url-token';
|
const CHECKIN_INVALID_URL_TOKEN = 'checkin-invalid-url-token';
|
||||||
|
const CHECKIN_INVALID_URL_EXPIRED_SESSION_TOKEN = 'checkin-invalid-url-expired-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 GROUP_EXPIRED_TOKEN = 'group-expired-token';
|
const GROUP_EXPIRED_TOKEN = 'group-expired-token';
|
||||||
@@ -354,6 +356,28 @@ describe('NewApiAdapter', () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof req.headers.cookie === 'string'
|
||||||
|
&& (
|
||||||
|
req.headers.cookie.includes(`session=${CHECKIN_INVALID_URL_TOKEN}`)
|
||||||
|
|| req.headers.cookie.includes(`token=${CHECKIN_INVALID_URL_TOKEN}`)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ success: false, message: 'temporary self probe failure' }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof req.headers.cookie === 'string' && req.headers.cookie.includes(`session=${CHECKIN_INVALID_URL_EXPIRED_SESSION_TOKEN}`)) {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ success: false, message: '无权进行此操作,未登录且未提供 access token' }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof req.headers.cookie === 'string' && req.headers.cookie.includes(`session=${CHECKIN_INVALID_URL_FORBIDDEN_SESSION_TOKEN}`)) {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ success: false, message: 'forbidden' }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
res.writeHead(401, { 'Content-Type': 'application/json' });
|
res.writeHead(401, { 'Content-Type': 'application/json' });
|
||||||
res.end(JSON.stringify({ success: false, message: 'unauthorized' }));
|
res.end(JSON.stringify({ success: false, message: 'unauthorized' }));
|
||||||
return;
|
return;
|
||||||
@@ -375,6 +399,26 @@ describe('NewApiAdapter', () => {
|
|||||||
res.end(JSON.stringify({ error: { message: 'Invalid URL (POST /api/user/checkin)' } }));
|
res.end(JSON.stringify({ error: { message: 'Invalid URL (POST /api/user/checkin)' } }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (typeof req.headers.authorization === 'string' && req.headers.authorization === `Bearer ${CHECKIN_INVALID_URL_EXPIRED_SESSION_TOKEN}`) {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ error: { message: 'Invalid URL (POST /api/user/checkin)' } }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof req.headers.cookie === 'string' && req.headers.cookie.includes(`session=${CHECKIN_INVALID_URL_EXPIRED_SESSION_TOKEN}`)) {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ error: { message: 'Invalid URL (POST /api/user/checkin)' } }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof req.headers.authorization === 'string' && req.headers.authorization === `Bearer ${CHECKIN_INVALID_URL_FORBIDDEN_SESSION_TOKEN}`) {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ error: { message: 'Invalid URL (POST /api/user/checkin)' } }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof req.headers.cookie === 'string' && req.headers.cookie.includes(`session=${CHECKIN_INVALID_URL_FORBIDDEN_SESSION_TOKEN}`)) {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ error: { message: 'Invalid URL (POST /api/user/checkin)' } }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (typeof req.headers.authorization === 'string' && req.headers.authorization === `Bearer ${CHECKIN_ALREADY_TOKEN}`) {
|
if (typeof req.headers.authorization === 'string' && req.headers.authorization === `Bearer ${CHECKIN_ALREADY_TOKEN}`) {
|
||||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
res.end(JSON.stringify({ success: false, message: '今天已经签到过啦' }));
|
res.end(JSON.stringify({ success: false, message: '今天已经签到过啦' }));
|
||||||
@@ -421,6 +465,16 @@ describe('NewApiAdapter', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (req.url === '/api/user/sign_in') {
|
if (req.url === '/api/user/sign_in') {
|
||||||
|
if (typeof req.headers.cookie === 'string' && req.headers.cookie.includes(`session=${CHECKIN_INVALID_URL_EXPIRED_SESSION_TOKEN}`)) {
|
||||||
|
res.writeHead(401, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof req.headers.cookie === 'string' && req.headers.cookie.includes(`session=${CHECKIN_INVALID_URL_FORBIDDEN_SESSION_TOKEN}`)) {
|
||||||
|
res.writeHead(401, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (typeof req.headers.cookie === 'string' && req.headers.cookie.includes(`session=${CHECKIN_ALREADY_TOKEN}`)) {
|
if (typeof req.headers.cookie === 'string' && req.headers.cookie.includes(`session=${CHECKIN_ALREADY_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' }));
|
||||||
@@ -617,6 +671,24 @@ describe('NewApiAdapter', () => {
|
|||||||
expect(result.message).toContain('Invalid URL');
|
expect(result.message).toContain('Invalid URL');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('prefers cookie session auth failure over invalid-url fallback when cookie session is expired', async () => {
|
||||||
|
const adapter = new NewApiAdapter();
|
||||||
|
const result = await adapter.checkin(baseUrl, CHECKIN_INVALID_URL_EXPIRED_SESSION_TOKEN, 131936);
|
||||||
|
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
expect(result.message).toContain('access token');
|
||||||
|
expect(result.message).not.toContain('Invalid URL');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('treats forbidden self probe responses as cookie session auth failures', async () => {
|
||||||
|
const adapter = new NewApiAdapter();
|
||||||
|
const result = await adapter.checkin(baseUrl, CHECKIN_INVALID_URL_FORBIDDEN_SESSION_TOKEN, 131936);
|
||||||
|
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
expect(result.message).toContain('forbidden');
|
||||||
|
expect(result.message).not.toContain('Invalid URL');
|
||||||
|
});
|
||||||
|
|
||||||
it('summarizes cloudflare tunnel HTML failures to concise checkin error', async () => {
|
it('summarizes cloudflare tunnel HTML failures to concise checkin error', async () => {
|
||||||
const adapter = new NewApiAdapter();
|
const adapter = new NewApiAdapter();
|
||||||
const result = await adapter.checkin(baseUrl, CHECKIN_CLOUDFLARE_530_TOKEN, 11494);
|
const result = await adapter.checkin(baseUrl, CHECKIN_CLOUDFLARE_530_TOKEN, 11494);
|
||||||
|
|||||||
@@ -629,6 +629,72 @@ export class NewApiAdapter extends BasePlatformAdapter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isMissingCheckinEndpointMessage(message?: string | null): boolean {
|
||||||
|
if (!message) return false;
|
||||||
|
const text = message.toLowerCase();
|
||||||
|
return (
|
||||||
|
text.includes('invalid url (post /api/user/checkin)') ||
|
||||||
|
(text.includes('http 404') && text.includes('/api/user/checkin')) ||
|
||||||
|
text.includes('checkin endpoint not found') ||
|
||||||
|
text.includes('check-in is not supported') ||
|
||||||
|
text.includes('checkin is not supported') ||
|
||||||
|
text.includes('does not support checkin') ||
|
||||||
|
text.includes('not support checkin')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private isCookieSessionFailureMessage(message?: string | null): boolean {
|
||||||
|
if (!message) return false;
|
||||||
|
const text = message.toLowerCase();
|
||||||
|
return (
|
||||||
|
text.includes('access token') ||
|
||||||
|
text.includes('unauthorized') ||
|
||||||
|
text.includes('forbidden') ||
|
||||||
|
text.includes('new-api-user') ||
|
||||||
|
text.includes('user id') ||
|
||||||
|
text.includes('invalid token') ||
|
||||||
|
text.includes('expired') ||
|
||||||
|
text.includes('无权') ||
|
||||||
|
text.includes('未登录') ||
|
||||||
|
text.includes('未提供') ||
|
||||||
|
text.includes('未授权') ||
|
||||||
|
text.includes('not login') ||
|
||||||
|
text.includes('not logged')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async detectCookieSessionFailureMessage(
|
||||||
|
baseUrl: string,
|
||||||
|
accessToken: string,
|
||||||
|
candidateUserIds: Array<number | null | undefined>,
|
||||||
|
): Promise<string | null> {
|
||||||
|
let failureMessage: string | null = null;
|
||||||
|
const rememberFailure = (message: string) => {
|
||||||
|
if (failureMessage) return;
|
||||||
|
const text = message.trim();
|
||||||
|
if (!this.isCookieSessionFailureMessage(text)) return;
|
||||||
|
failureMessage = text;
|
||||||
|
};
|
||||||
|
|
||||||
|
const uniqueCandidateUserIds = Array.from(new Set(
|
||||||
|
candidateUserIds.filter((value): value is number => typeof value === 'number' && value > 0),
|
||||||
|
));
|
||||||
|
|
||||||
|
if (uniqueCandidateUserIds.length === 0) {
|
||||||
|
await this.fetchUserSelfByCookie(baseUrl, accessToken, undefined, rememberFailure);
|
||||||
|
return failureMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const userId of uniqueCandidateUserIds) {
|
||||||
|
await this.fetchUserSelfByCookie(baseUrl, accessToken, userId, rememberFailure);
|
||||||
|
if (failureMessage) {
|
||||||
|
return failureMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return failureMessage;
|
||||||
|
}
|
||||||
|
|
||||||
private async fetchJsonRawWithCookie<T>(
|
private async fetchJsonRawWithCookie<T>(
|
||||||
url: string,
|
url: string,
|
||||||
options?: UndiciRequestInit,
|
options?: UndiciRequestInit,
|
||||||
@@ -1078,6 +1144,17 @@ export class NewApiAdapter extends BasePlatformAdapter {
|
|||||||
if (retriedCookieResult) return retriedCookieResult;
|
if (retriedCookieResult) return retriedCookieResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isMissingCheckinEndpointMessage(firstFailureMessage)) {
|
||||||
|
const cookieSessionFailureMessage = await this.detectCookieSessionFailureMessage(
|
||||||
|
baseUrl,
|
||||||
|
accessToken,
|
||||||
|
[resolvedUserId, alternateCookieUserId],
|
||||||
|
);
|
||||||
|
if (cookieSessionFailureMessage) {
|
||||||
|
return { success: false, message: cookieSessionFailureMessage };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return { success: false, message: firstFailureMessage || 'checkin failed' };
|
return { success: false, message: firstFailureMessage || 'checkin failed' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user