fix(auth-files): handle html challenge content

This commit is contained in:
zhoukailian
2026-05-18 15:09:05 +08:00
Unverified
parent 9e77afac4b
commit 300f73e5e0
7 changed files with 129 additions and 58 deletions
@@ -34,6 +34,7 @@ export function AuthFilesPrefixProxyEditorModal(props: AuthFilesPrefixProxyEdito
}
};
const previewText = formatJsonText(updatedText);
const invalidContentPreview = editor?.invalidContentPreview ?? '';
return (
<Modal
@@ -100,60 +101,70 @@ export function AuthFilesPrefixProxyEditorModal(props: AuthFilesPrefixProxyEdito
</div>
<div className={styles.prefixProxyJsonWrapper}>
<label className={styles.prefixProxyLabel}>
{t('auth_files.prefix_proxy_source_label')}
{editor.json
? t('auth_files.prefix_proxy_source_label')
: t('auth_files.prefix_proxy_invalid_content_label')}
</label>
<textarea
className={styles.prefixProxyTextarea}
rows={10}
readOnly
value={previewText}
/>
</div>
<div className={styles.prefixProxyFields}>
<Input
label={t('auth_files.prefix_label')}
value={editor.prefix}
disabled={disableControls || editor.saving || !editor.json}
onChange={(e) => onChange('prefix', e.target.value)}
/>
<Input
label={t('auth_files.proxy_url_label')}
value={editor.proxyUrl}
placeholder={t('auth_files.proxy_url_placeholder')}
disabled={disableControls || editor.saving || !editor.json}
onChange={(e) => onChange('proxyUrl', e.target.value)}
/>
<Input
label={t('auth_files.priority_label')}
value={editor.priority}
placeholder={t('auth_files.priority_placeholder')}
hint={t('auth_files.priority_hint')}
disabled={disableControls || editor.saving || !editor.json}
onChange={(e) => onChange('priority', e.target.value)}
/>
<div className="form-group">
<label>{t('auth_files.headers_label')}</label>
{editor.json ? (
<textarea
className={`input ${editor.headersError ? styles.prefixProxyTextareaInvalid : ''}`}
value={editor.headersText}
placeholder={t('auth_files.headers_placeholder')}
rows={4}
aria-invalid={Boolean(editor.headersError)}
disabled={disableControls || editor.saving || !editor.json}
onChange={(e) => onChange('headersText', e.target.value)}
className={styles.prefixProxyTextarea}
rows={10}
readOnly
value={previewText}
/>
{editor.headersError && <div className="error-box">{editor.headersError}</div>}
<div className="hint">{t('auth_files.headers_hint')}</div>
</div>
<Input
label={t('auth_files.note_label')}
value={editor.note}
placeholder={t('auth_files.note_placeholder')}
hint={t('auth_files.note_hint')}
disabled={disableControls || editor.saving || !editor.json}
onChange={(e) => onChange('note', e.target.value)}
/>
) : (
<pre className={styles.prefixProxyInvalidContentPreview}>
{invalidContentPreview}
</pre>
)}
</div>
{editor.json && (
<div className={styles.prefixProxyFields}>
<Input
label={t('auth_files.prefix_label')}
value={editor.prefix}
disabled={disableControls || editor.saving || !editor.json}
onChange={(e) => onChange('prefix', e.target.value)}
/>
<Input
label={t('auth_files.proxy_url_label')}
value={editor.proxyUrl}
placeholder={t('auth_files.proxy_url_placeholder')}
disabled={disableControls || editor.saving || !editor.json}
onChange={(e) => onChange('proxyUrl', e.target.value)}
/>
<Input
label={t('auth_files.priority_label')}
value={editor.priority}
placeholder={t('auth_files.priority_placeholder')}
hint={t('auth_files.priority_hint')}
disabled={disableControls || editor.saving || !editor.json}
onChange={(e) => onChange('priority', e.target.value)}
/>
<div className="form-group">
<label>{t('auth_files.headers_label')}</label>
<textarea
className={`input ${editor.headersError ? styles.prefixProxyTextareaInvalid : ''}`}
value={editor.headersText}
placeholder={t('auth_files.headers_placeholder')}
rows={4}
aria-invalid={Boolean(editor.headersError)}
disabled={disableControls || editor.saving || !editor.json}
onChange={(e) => onChange('headersText', e.target.value)}
/>
{editor.headersError && <div className="error-box">{editor.headersError}</div>}
<div className="hint">{t('auth_files.headers_hint')}</div>
</div>
<Input
label={t('auth_files.note_label')}
value={editor.note}
placeholder={t('auth_files.note_placeholder')}
hint={t('auth_files.note_hint')}
disabled={disableControls || editor.saving || !editor.json}
onChange={(e) => onChange('note', e.target.value)}
/>
</div>
)}
</>
)}
</div>
@@ -10,6 +10,9 @@ type AuthFileHeadersErrorKey =
| 'auth_files.headers_invalid_json'
| 'auth_files.headers_invalid_object'
| 'auth_files.headers_invalid_value';
type AuthFileContentErrorKey =
| 'auth_files.prefix_proxy_invalid_json'
| 'auth_files.prefix_proxy_html_challenge';
export type PrefixProxyEditorField = 'prefix' | 'proxyUrl' | 'priority' | 'note' | 'headersText';
@@ -23,6 +26,7 @@ export type PrefixProxyEditorState = {
error: string | null;
originalText: string;
rawText: string;
invalidContentPreview: string;
json: Record<string, unknown> | null;
prefix: string;
proxyUrl: string;
@@ -90,6 +94,33 @@ const parseHeadersText = (
const normalizeTextField = (value: unknown): string =>
typeof value === 'string' ? value.trim() : '';
const INVALID_CONTENT_PREVIEW_LIMIT = 1000;
const buildInvalidContentPreview = (text: string): string => {
const trimmed = text.trim();
if (!trimmed) return '';
if (trimmed.length <= INVALID_CONTENT_PREVIEW_LIMIT) return trimmed;
return `${trimmed.slice(0, INVALID_CONTENT_PREVIEW_LIMIT)}\n...`;
};
const getAuthFileContentErrorKey = (text: string): AuthFileContentErrorKey => {
const head = text.trimStart().slice(0, 4096).toLowerCase();
const looksLikeHtml =
head.startsWith('<!doctype html') ||
head.startsWith('<html') ||
head.includes('<head') ||
head.includes('<body');
const looksLikeChallenge =
head.includes('cf_chl') ||
head.includes('__cf_chl_tk') ||
head.includes('challenge-platform') ||
head.includes('cloudflare');
return looksLikeHtml || looksLikeChallenge
? 'auth_files.prefix_proxy_html_challenge'
: 'auth_files.prefix_proxy_invalid_json';
};
const hasKeys = (value: Record<string, unknown> | AuthFileFieldsPatch | null): boolean =>
Boolean(value && Object.keys(value).length > 0);
@@ -302,6 +333,7 @@ export function useAuthFilesPrefixProxyEditor(
error: null,
originalText: '',
rawText: '',
invalidContentPreview: '',
json: null,
prefix: '',
proxyUrl: '',
@@ -321,28 +353,32 @@ export function useAuthFilesPrefixProxyEditor(
try {
parsed = JSON.parse(trimmed) as unknown;
} catch {
const errorKey = getAuthFileContentErrorKey(trimmed);
setPrefixProxyEditor((prev) => {
if (!prev || prev.fileName !== name) return prev;
return {
...prev,
loading: false,
error: t('auth_files.prefix_proxy_invalid_json'),
rawText: trimmed,
originalText: trimmed,
error: t(errorKey),
rawText: '',
originalText: '',
invalidContentPreview: buildInvalidContentPreview(trimmed),
};
});
return;
}
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
const errorKey = getAuthFileContentErrorKey(trimmed);
setPrefixProxyEditor((prev) => {
if (!prev || prev.fileName !== name) return prev;
return {
...prev,
loading: false,
error: t('auth_files.prefix_proxy_invalid_json'),
rawText: trimmed,
originalText: trimmed,
error: t(errorKey),
rawText: '',
originalText: '',
invalidContentPreview: buildInvalidContentPreview(trimmed),
};
});
return;
@@ -370,6 +406,7 @@ export function useAuthFilesPrefixProxyEditor(
loading: false,
originalText,
rawText: originalText,
invalidContentPreview: '',
json,
prefix,
proxyUrl,
+2
View File
@@ -591,6 +591,7 @@
"prefix_proxy_loading": "Loading auth file...",
"prefix_proxy_info_label": "Auth file info (info)",
"prefix_proxy_source_label": "Auth file JSON (preview)",
"prefix_proxy_invalid_content_label": "Downloaded content (truncated)",
"prefix_label": "Prefix (prefix)",
"proxy_url_label": "Proxy URL (proxy_url)",
"prefix_placeholder": "",
@@ -615,6 +616,7 @@
"headers_invalid_object": "Custom headers must be a JSON object.",
"headers_invalid_value": "Each custom header value must be a string.",
"prefix_proxy_invalid_json": "This auth file is not a JSON object, so fields cannot be edited.",
"prefix_proxy_html_challenge": "Downloaded content is an HTML challenge page, not an auth JSON object. Re-authenticate or replace this auth file before editing fields.",
"prefix_proxy_saved_success": "Updated auth file \"{{name}}\" successfully",
"quota_refresh_success": "Quota refreshed for \"{{name}}\"",
"quota_refresh_failed": "Failed to refresh quota for \"{{name}}\": {{message}}"
+2
View File
@@ -591,6 +591,7 @@
"prefix_proxy_loading": "Загрузка файла авторизации...",
"prefix_proxy_info_label": "Информация о файле авторизации (info)",
"prefix_proxy_source_label": "JSON файла авторизации (предпросмотр)",
"prefix_proxy_invalid_content_label": "Скачанное содержимое (сокращено)",
"prefix_label": "Префикс (prefix)",
"proxy_url_label": "URL прокси (proxy_url)",
"prefix_placeholder": "",
@@ -609,6 +610,7 @@
"note_hint": "Необязательно. Используется для описания назначения или владельца учётных данных; оставьте пустым, чтобы не записывать.",
"note_display": "Заметка",
"prefix_proxy_invalid_json": "Этот файл авторизации не является JSON-объектом, поэтому поля нельзя редактировать.",
"prefix_proxy_html_challenge": "Скачанное содержимое является HTML-страницей проверки, а не JSON-объектом авторизации. Повторно авторизуйтесь или замените файл перед редактированием полей.",
"prefix_proxy_saved_success": "Файл авторизации \"{{name}}\" успешно обновлён",
"card_tools_title": "Инструменты",
"quota_refresh_single": "Обновить квоту",
+2
View File
@@ -591,6 +591,7 @@
"prefix_proxy_loading": "正在加载认证文件...",
"prefix_proxy_info_label": "认证文件信息(info",
"prefix_proxy_source_label": "认证文件 JSON(预览)",
"prefix_proxy_invalid_content_label": "下载内容(已截断)",
"prefix_label": "前缀(prefix",
"proxy_url_label": "代理 URLproxy_url",
"prefix_placeholder": "",
@@ -615,6 +616,7 @@
"headers_invalid_object": "自定义请求头必须是 JSON 对象。",
"headers_invalid_value": "每个自定义请求头的值都必须是字符串。",
"prefix_proxy_invalid_json": "该认证文件不是 JSON 对象,无法编辑字段。",
"prefix_proxy_html_challenge": "下载到的是 HTML 验证页面,不是认证 JSON 对象。请重新认证或替换该认证文件后再编辑字段。",
"prefix_proxy_saved_success": "已更新认证文件 \"{{name}}\"",
"quota_refresh_success": "已刷新 \"{{name}}\" 的额度",
"quota_refresh_failed": "刷新 \"{{name}}\" 的额度失败:{{message}}"
+2
View File
@@ -591,6 +591,7 @@
"prefix_proxy_loading": "正在載入驗證檔案...",
"prefix_proxy_info_label": "驗證檔案資訊(info",
"prefix_proxy_source_label": "驗證檔案 JSON(預覽)",
"prefix_proxy_invalid_content_label": "下載內容(已截斷)",
"prefix_label": "前綴(prefix",
"proxy_url_label": "代理 URLproxy_url",
"prefix_placeholder": "",
@@ -615,6 +616,7 @@
"headers_invalid_object": "自訂請求標頭必須是 JSON 物件。",
"headers_invalid_value": "每個自訂請求標頭的值都必須是字串。",
"prefix_proxy_invalid_json": "該驗證檔案不是 JSON 物件,無法編輯欄位。",
"prefix_proxy_html_challenge": "下載到的是 HTML 驗證頁面,不是驗證 JSON 物件。請重新驗證或替換該驗證檔案後再編輯欄位。",
"prefix_proxy_saved_success": "已更新驗證檔案「{{name}}」",
"quota_refresh_success": "已重新整理「{{name}}」的配額",
"quota_refresh_failed": "重新整理「{{name}}」的配額失敗:{{message}}"
+17 -2
View File
@@ -100,7 +100,6 @@
}
}
.filterAllIconWrap {
position: relative;
border-color: color-mix(in srgb, var(--primary-color) 20%, var(--border-color));
@@ -119,7 +118,6 @@
color: color-mix(in srgb, var(--primary-color) 70%, var(--text-primary));
}
.filterContent {
display: flex;
flex-direction: column;
@@ -1388,6 +1386,23 @@
}
}
.prefixProxyInvalidContentPreview {
width: 100%;
max-height: 220px;
margin: 0;
padding: $spacing-sm $spacing-md;
border: 1px solid var(--border-color);
border-radius: $radius-md;
background-color: var(--bg-secondary);
color: var(--text-secondary);
font-size: 12px;
font-family: monospace;
line-height: 1.5;
white-space: pre-wrap;
overflow: auto;
box-sizing: border-box;
}
.prefixProxyFields {
display: grid;
grid-template-columns: 1fr;