From 89f5b74b4fee4900bfe9d09bb4fca17a7ba945a8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Apr 2026 02:42:29 +0000 Subject: [PATCH 1/4] Fix auth persistence and OAuth popup flow Agent-Logs-Url: https://github.com/crazyrob425/BlacklistedAIProxy/sessions/63e61e1c-0502-4d15-91ef-0b43d3ac4c84 Co-authored-by: crazyrob425 <247058665+crazyrob425@users.noreply.github.com> --- static/app/auth.js | 4 +- static/app/provider-manager.js | 415 +++++++++++++++++++-------------- static/login.html | 9 +- 3 files changed, 245 insertions(+), 183 deletions(-) diff --git a/static/app/auth.js b/static/app/auth.js index a9fc412e7..59238451d 100644 --- a/static/app/auth.js +++ b/static/app/auth.js @@ -52,6 +52,8 @@ class AuthManager { if (rememberMe) { const expiryTime = Date.now() + (7 * 24 * 60 * 60 * 1000); // 7天 localStorage.setItem(this.expiryKey, expiryTime.toString()); + } else { + localStorage.removeItem(this.expiryKey); } } @@ -366,4 +368,4 @@ export { getAuthHeaders }; -console.log('认证模块已加载'); \ No newline at end of file +console.log('认证模块已加载'); diff --git a/static/app/provider-manager.js b/static/app/provider-manager.js index 9c208de53..99bd9050b 100644 --- a/static/app/provider-manager.js +++ b/static/app/provider-manager.js @@ -766,6 +766,27 @@ function generateAddGroupButton(providerType) { * 处理生成授权链接 * @param {string} providerType - 提供商类型 */ +function preOpenAuthPopup() { + const width = 600; + const height = 700; + const left = (window.screen.width - width) / 2 + 600; + const top = (window.screen.height - height) / 2; + + try { + const popup = window.open( + '', + 'OAuthAuthWindow', + `width=${width},height=${height},left=${left},top=${top},status=no,resizable=yes,scrollbars=yes` + ); + if (popup && popup.document) { + popup.document.title = 'OAuth'; + } + return popup; + } catch (error) { + return null; + } +} + async function handleGenerateAuthUrl(providerType) { // 如果是 Kiro OAuth,先显示认证方式选择对话框 if (providerType === 'claude-kiro-oauth') { @@ -785,7 +806,8 @@ async function handleGenerateAuthUrl(providerType) { return; } - await executeGenerateAuthUrl(providerType, {}); + const popupRef = preOpenAuthPopup(); + await executeGenerateAuthUrl(providerType, { ui: { popupRef } }); } /** @@ -856,7 +878,8 @@ function showCodexAuthMethodSelector(providerType) { if (method === 'batch-import') { showCodexBatchImportModal(providerType); } else { - await executeGenerateAuthUrl(providerType, {}); + const popupRef = preOpenAuthPopup(); + await executeGenerateAuthUrl(providerType, { ui: { popupRef } }); } }); }); @@ -1246,7 +1269,8 @@ function showKiroAuthMethodSelector(providerType) { } else if (method === 'aws-import') { showKiroAwsImportModal(); } else { - await executeGenerateAuthUrl(providerType, { method }); + const popupRef = preOpenAuthPopup(); + await executeGenerateAuthUrl(providerType, { method, ui: { popupRef } }); } }); }); @@ -1320,7 +1344,8 @@ function showGeminiAuthMethodSelector(providerType) { if (method === 'batch-import') { showGeminiBatchImportModal(providerType); } else { - await executeGenerateAuthUrl(providerType, {}); + const popupRef = preOpenAuthPopup(); + await executeGenerateAuthUrl(providerType, { ui: { popupRef } }); } }); }); @@ -2744,6 +2769,7 @@ function showKiroAwsImportModal() { */ async function executeGenerateAuthUrl(providerType, extraOptions = {}) { try { + const { ui = {}, ...requestOptions } = extraOptions; showToast(t('common.info'), t('modal.provider.auth.initializing'), 'info'); // 使用 fileUploadHandler 中的 getProviderKey 获取目录名称 @@ -2754,7 +2780,7 @@ async function executeGenerateAuthUrl(providerType, extraOptions = {}) { { saveToConfigs: true, providerDir: providerDir, - ...extraOptions + ...requestOptions } ); @@ -2778,7 +2804,7 @@ async function executeGenerateAuthUrl(providerType, extraOptions = {}) { } // 显示授权信息模态框 - showAuthModal(response.authUrl, response.authInfo); + showAuthModal(response.authUrl, response.authInfo, ui); } else { showToast(t('common.error'), t('modal.provider.auth.failed'), 'error'); } @@ -2809,7 +2835,7 @@ function getAuthFilePath(provider) { * @param {string} authUrl - 授权URL * @param {Object} authInfo - 授权信息 */ -function showAuthModal(authUrl, authInfo) { +function showAuthModal(authUrl, authInfo, uiOptions = {}) { const modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.style.display = 'flex'; @@ -2961,6 +2987,206 @@ function showAuthModal(authUrl, authInfo) { `; document.body.appendChild(modal); + + let authWindow = uiOptions.popupRef || null; + let pollTimer = null; + let popupInitialized = false; + + const cleanupAuthListeners = () => { + if (pollTimer) { + clearInterval(pollTimer); + pollTimer = null; + } + window.removeEventListener('oauth_success_event', handleOAuthSuccess); + window.removeEventListener('message', handlePopupMessage); + popupInitialized = false; + }; + + // 监听 OAuth 成功事件,自动关闭窗口和模态框 + const handleOAuthSuccess = () => { + if (authWindow && !authWindow.closed) { + authWindow.close(); + } + modal.remove(); + cleanupAuthListeners(); + + // 授权成功后刷新配置和提供商列表 + loadProviders(); + loadConfigList(); + }; + + // 回调页主动 postMessage 时,优先使用父页面关闭子窗口 + const handlePopupMessage = (event) => { + if (event.origin !== window.location.origin) { + return; + } + + const data = event.data; + if (!data || data.type !== 'oauth-popup-complete') { + return; + } + + if (data.provider && data.provider !== authInfo.provider) { + return; + } + + handleOAuthSuccess(); + }; + + const ensureManualCallbackUi = () => { + const urlSection = modal.querySelector('.auth-url-section'); + if (!urlSection || modal.querySelector('.manual-callback-section')) { + return; + } + const manualInputHtml = ` +
+

${t('oauth.manual.title')}

+

${t('oauth.manual.desc')}

+
+ + +
+
+ `; + urlSection.insertAdjacentHTML('afterend', manualInputHtml); + }; + + const processCallback = (urlStr, isManualInput = false) => { + try { + // 尝试清理 URL(有些用户可能会复制多余的文字) + const cleanUrlStr = urlStr.trim().match(/https?:\/\/[^\s]+/)?.[0] || urlStr.trim(); + const url = new URL(cleanUrlStr); + + if (url.searchParams.has('code') || url.searchParams.has('token')) { + if (pollTimer) { + clearInterval(pollTimer); + pollTimer = null; + } + // 构造本地可处理的 URL,只修改 hostname,保持原始 URL 的端口号不变 + const localUrl = new URL(url.href); + localUrl.hostname = window.location.hostname; + localUrl.protocol = window.location.protocol; + + showToast(t('common.info'), t('oauth.processing'), 'info'); + + // 如果是手动输入,直接通过 fetch 请求处理,然后关闭子窗口 + if (isManualInput) { + // 通过服务端API处理手动输入的回调URL + window.apiClient.post('/oauth/manual-callback', { + provider: authInfo.provider, + callbackUrl: url.href, //使用localhost访问 + authMethod: authInfo.authMethod + }) + .then(response => { + if (response.success) { + console.log('OAuth 回调处理成功'); + handleOAuthSuccess(); + showToast(t('common.success'), t('oauth.success.msg'), 'success'); + } else { + console.error('OAuth 回调处理失败:', response.error); + showToast(t('common.error'), response.error || t('oauth.error.process'), 'error'); + } + }) + .catch(err => { + console.error('OAuth 回调请求失败:', err); + showToast(t('common.error'), t('oauth.error.process'), 'error'); + }); + } else { + // 自动监听模式:优先在子窗口中跳转(如果没关) + if (authWindow && !authWindow.closed) { + authWindow.location.href = localUrl.href; + } else { + // 备选方案:通过 fetch 请求 + // 通过 fetch 请求本地服务器处理回调 + fetch(localUrl.href) + .then(response => { + if (response.ok) { + console.log('OAuth 回调处理成功'); + } else { + console.error('OAuth 回调处理失败:', response.status); + } + }) + .catch(err => { + console.error('OAuth 回调请求失败:', err); + }); + } + } + + } else { + showToast(t('common.warning'), t('oauth.invalid.url'), 'warning'); + } + } catch (err) { + console.error('处理回调失败:', err); + showToast(t('common.error'), t('oauth.error.format'), 'error'); + } + }; + + const initializePopupHandlers = () => { + if (popupInitialized) return; + popupInitialized = true; + window.addEventListener('oauth_success_event', handleOAuthSuccess); + window.addEventListener('message', handlePopupMessage); + ensureManualCallbackUi(); + + const manualInput = modal.querySelector('.manual-callback-input'); + const applyBtn = modal.querySelector('.apply-callback-btn'); + if (applyBtn) { + applyBtn.addEventListener('click', () => { + processCallback(manualInput.value, true); + }); + } + + pollTimer = setInterval(() => { + try { + if (!authWindow || authWindow.closed) { + cleanupAuthListeners(); + return; + } + // 如果能读到说明回到了同域 + const currentUrl = authWindow.location.href; + if (currentUrl && (currentUrl.includes('code=') || currentUrl.includes('token='))) { + processCallback(currentUrl); + } + } catch (e) { + // 跨域受限是正常的 + } + }, 1000); + }; + + const openAuthPopup = () => { + const width = 600; + const height = 700; + const left = (window.screen.width - width) / 2 + 600; + const top = (window.screen.height - height) / 2; + + if (authWindow && !authWindow.closed) { + try { + authWindow.location.href = authUrl; + authWindow.focus(); + } catch (err) { + // ignore + } + } else { + authWindow = window.open( + authUrl, + 'OAuthAuthWindow', + `width=${width},height=${height},left=${left},top=${top},status=no,resizable=yes,scrollbars=yes` + ); + } + + if (authWindow) { + showToast(t('common.info'), t('oauth.window.opened'), 'info'); + initializePopupHandlers(); + } else { + showToast(t('common.error'), t('oauth.window.blocked'), 'error'); + } + }; + + if (authWindow && !authWindow.closed) { + openAuthPopup(); + } // 关闭按钮事件 const closeBtn = modal.querySelector('.modal-close'); @@ -3024,180 +3250,7 @@ function showAuthModal(authUrl, authInfo) { // 在浏览器中打开按钮 const openBtn = modal.querySelector('.open-auth-btn'); openBtn.addEventListener('click', () => { - // 使用子窗口打开,以便监听 URL 变化 - const width = 600; - const height = 700; - const left = (window.screen.width - width) / 2 + 600; - const top = (window.screen.height - height) / 2; - - const authWindow = window.open( - authUrl, - 'OAuthAuthWindow', - `width=${width},height=${height},left=${left},top=${top},status=no,resizable=yes,scrollbars=yes` - ); - - let pollTimer = null; - const cleanupAuthListeners = () => { - if (pollTimer) { - clearInterval(pollTimer); - pollTimer = null; - } - window.removeEventListener('oauth_success_event', handleOAuthSuccess); - window.removeEventListener('message', handlePopupMessage); - }; - - // 监听 OAuth 成功事件,自动关闭窗口和模态框 - const handleOAuthSuccess = () => { - if (authWindow && !authWindow.closed) { - authWindow.close(); - } - modal.remove(); - cleanupAuthListeners(); - - // 授权成功后刷新配置和提供商列表 - loadProviders(); - loadConfigList(); - }; - - // 回调页主动 postMessage 时,优先使用父页面关闭子窗口 - const handlePopupMessage = (event) => { - if (event.origin !== window.location.origin) { - return; - } - - const data = event.data; - if (!data || data.type !== 'oauth-popup-complete') { - return; - } - - if (data.provider && data.provider !== authInfo.provider) { - return; - } - - handleOAuthSuccess(); - }; - - window.addEventListener('oauth_success_event', handleOAuthSuccess); - window.addEventListener('message', handlePopupMessage); - - if (authWindow) { - showToast(t('common.info'), t('oauth.window.opened'), 'info'); - - // 添加手动输入回调 URL 的 UI - const urlSection = modal.querySelector('.auth-url-section'); - if (urlSection && !modal.querySelector('.manual-callback-section')) { - const manualInputHtml = ` -
-

${t('oauth.manual.title')}

-

${t('oauth.manual.desc')}

-
- - -
-
- `; - urlSection.insertAdjacentHTML('afterend', manualInputHtml); - } - - const manualInput = modal.querySelector('.manual-callback-input'); - const applyBtn = modal.querySelector('.apply-callback-btn'); - - // 处理回调 URL 的核心逻辑 - const processCallback = (urlStr, isManualInput = false) => { - try { - // 尝试清理 URL(有些用户可能会复制多余的文字) - const cleanUrlStr = urlStr.trim().match(/https?:\/\/[^\s]+/)?.[0] || urlStr.trim(); - const url = new URL(cleanUrlStr); - - if (url.searchParams.has('code') || url.searchParams.has('token')) { - if (pollTimer) { - clearInterval(pollTimer); - pollTimer = null; - } - // 构造本地可处理的 URL,只修改 hostname,保持原始 URL 的端口号不变 - const localUrl = new URL(url.href); - localUrl.hostname = window.location.hostname; - localUrl.protocol = window.location.protocol; - - showToast(t('common.info'), t('oauth.processing'), 'info'); - - // 如果是手动输入,直接通过 fetch 请求处理,然后关闭子窗口 - if (isManualInput) { - // 通过服务端API处理手动输入的回调URL - window.apiClient.post('/oauth/manual-callback', { - provider: authInfo.provider, - callbackUrl: url.href, //使用localhost访问 - authMethod: authInfo.authMethod - }) - .then(response => { - if (response.success) { - console.log('OAuth 回调处理成功'); - handleOAuthSuccess(); - showToast(t('common.success'), t('oauth.success.msg'), 'success'); - } else { - console.error('OAuth 回调处理失败:', response.error); - showToast(t('common.error'), response.error || t('oauth.error.process'), 'error'); - } - }) - .catch(err => { - console.error('OAuth 回调请求失败:', err); - showToast(t('common.error'), t('oauth.error.process'), 'error'); - }); - } else { - // 自动监听模式:优先在子窗口中跳转(如果没关) - if (authWindow && !authWindow.closed) { - authWindow.location.href = localUrl.href; - } else { - // 备选方案:通过 fetch 请求 - // 通过 fetch 请求本地服务器处理回调 - fetch(localUrl.href) - .then(response => { - if (response.ok) { - console.log('OAuth 回调处理成功'); - } else { - console.error('OAuth 回调处理失败:', response.status); - } - }) - .catch(err => { - console.error('OAuth 回调请求失败:', err); - }); - } - } - - } else { - showToast(t('common.warning'), t('oauth.invalid.url'), 'warning'); - } - } catch (err) { - console.error('处理回调失败:', err); - showToast(t('common.error'), t('oauth.error.format'), 'error'); - } - }; - - applyBtn.addEventListener('click', () => { - processCallback(manualInput.value, true); - }); - - // 启动定时器轮询子窗口 URL - pollTimer = setInterval(() => { - try { - if (authWindow.closed) { - cleanupAuthListeners(); - return; - } - // 如果能读到说明回到了同域 - const currentUrl = authWindow.location.href; - if (currentUrl && (currentUrl.includes('code=') || currentUrl.includes('token='))) { - processCallback(currentUrl); - } - } catch (e) { - // 跨域受限是正常的 - } - }, 1000); - } else { - showToast(t('common.error'), t('oauth.window.blocked'), 'error'); - } + openAuthPopup(); }); } diff --git a/static/login.html b/static/login.html index 69ddff0a3..8d50aafe4 100644 --- a/static/login.html +++ b/static/login.html @@ -300,6 +300,7 @@

BlacklistedAPI

if (response.ok && data.success) { // 登录成功,保存token localStorage.setItem('authToken', data.token); + localStorage.removeItem('authTokenExpiry'); // 跳转到主页 window.location.href = '/'; @@ -331,6 +332,12 @@

BlacklistedAPI

function checkLoginStatus() { const token = localStorage.getItem('authToken'); + const expiry = localStorage.getItem('authTokenExpiry'); + if (expiry && Date.now() > parseInt(expiry, 10)) { + localStorage.removeItem('authToken'); + localStorage.removeItem('authTokenExpiry'); + return; + } if (token) { // Token存在,跳转到主页 @@ -347,4 +354,4 @@

BlacklistedAPI

passwordInput.focus(); - \ No newline at end of file + From e61637f7b40c2ebb76da564be00eb1d1a8f9dd00 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Apr 2026 02:43:49 +0000 Subject: [PATCH 2/4] Refine OAuth popup positioning and comments Agent-Logs-Url: https://github.com/crazyrob425/BlacklistedAIProxy/sessions/63e61e1c-0502-4d15-91ef-0b43d3ac4c84 Co-authored-by: crazyrob425 <247058665+crazyrob425@users.noreply.github.com> --- static/app/provider-manager.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/static/app/provider-manager.js b/static/app/provider-manager.js index 99bd9050b..db6730a7b 100644 --- a/static/app/provider-manager.js +++ b/static/app/provider-manager.js @@ -769,7 +769,7 @@ function generateAddGroupButton(providerType) { function preOpenAuthPopup() { const width = 600; const height = 700; - const left = (window.screen.width - width) / 2 + 600; + const left = (window.screen.width - width) / 2; const top = (window.screen.height - height) / 2; try { @@ -3076,7 +3076,7 @@ function showAuthModal(authUrl, authInfo, uiOptions = {}) { // 通过服务端API处理手动输入的回调URL window.apiClient.post('/oauth/manual-callback', { provider: authInfo.provider, - callbackUrl: url.href, //使用localhost访问 + callbackUrl: url.href, // Use localhost access authMethod: authInfo.authMethod }) .then(response => { @@ -3158,7 +3158,7 @@ function showAuthModal(authUrl, authInfo, uiOptions = {}) { const openAuthPopup = () => { const width = 600; const height = 700; - const left = (window.screen.width - width) / 2 + 600; + const left = (window.screen.width - width) / 2; const top = (window.screen.height - height) / 2; if (authWindow && !authWindow.closed) { From 304c5dca8bbc1597ad0502b968c18b7d4e4d3bf2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Apr 2026 02:45:16 +0000 Subject: [PATCH 3/4] Refactor OAuth popup config Agent-Logs-Url: https://github.com/crazyrob425/BlacklistedAIProxy/sessions/63e61e1c-0502-4d15-91ef-0b43d3ac4c84 Co-authored-by: crazyrob425 <247058665+crazyrob425@users.noreply.github.com> --- static/app/provider-manager.js | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/static/app/provider-manager.js b/static/app/provider-manager.js index db6730a7b..f95cb578e 100644 --- a/static/app/provider-manager.js +++ b/static/app/provider-manager.js @@ -766,17 +766,28 @@ function generateAddGroupButton(providerType) { * 处理生成授权链接 * @param {string} providerType - 提供商类型 */ -function preOpenAuthPopup() { +function getAuthPopupConfig() { const width = 600; const height = 700; const left = (window.screen.width - width) / 2; const top = (window.screen.height - height) / 2; + return { + width, + height, + left, + top, + features: `width=${width},height=${height},left=${left},top=${top},status=no,resizable=yes,scrollbars=yes` + }; +} + +function preOpenAuthPopup() { + const { features } = getAuthPopupConfig(); try { const popup = window.open( '', 'OAuthAuthWindow', - `width=${width},height=${height},left=${left},top=${top},status=no,resizable=yes,scrollbars=yes` + features ); if (popup && popup.document) { popup.document.title = 'OAuth'; @@ -3125,6 +3136,10 @@ function showAuthModal(authUrl, authInfo, uiOptions = {}) { const initializePopupHandlers = () => { if (popupInitialized) return; + if (!authWindow) { + popupInitialized = false; + return; + } popupInitialized = true; window.addEventListener('oauth_success_event', handleOAuthSuccess); window.addEventListener('message', handlePopupMessage); @@ -3156,10 +3171,7 @@ function showAuthModal(authUrl, authInfo, uiOptions = {}) { }; const openAuthPopup = () => { - const width = 600; - const height = 700; - const left = (window.screen.width - width) / 2; - const top = (window.screen.height - height) / 2; + const { features } = getAuthPopupConfig(); if (authWindow && !authWindow.closed) { try { @@ -3172,7 +3184,7 @@ function showAuthModal(authUrl, authInfo, uiOptions = {}) { authWindow = window.open( authUrl, 'OAuthAuthWindow', - `width=${width},height=${height},left=${left},top=${top},status=no,resizable=yes,scrollbars=yes` + features ); } From c4e17def178142031671ceb3bb36e631f0dac6ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Apr 2026 02:46:29 +0000 Subject: [PATCH 4/4] Tidy OAuth popup comments Agent-Logs-Url: https://github.com/crazyrob425/BlacklistedAIProxy/sessions/63e61e1c-0502-4d15-91ef-0b43d3ac4c84 Co-authored-by: crazyrob425 <247058665+crazyrob425@users.noreply.github.com> --- static/app/provider-manager.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/static/app/provider-manager.js b/static/app/provider-manager.js index f95cb578e..b5686b7e9 100644 --- a/static/app/provider-manager.js +++ b/static/app/provider-manager.js @@ -3165,7 +3165,7 @@ function showAuthModal(authUrl, authInfo, uiOptions = {}) { processCallback(currentUrl); } } catch (e) { - // 跨域受限是正常的 + // Cross-origin restrictions are expected. } }, 1000); }; @@ -3173,19 +3173,19 @@ function showAuthModal(authUrl, authInfo, uiOptions = {}) { const openAuthPopup = () => { const { features } = getAuthPopupConfig(); - if (authWindow && !authWindow.closed) { - try { - authWindow.location.href = authUrl; - authWindow.focus(); - } catch (err) { - // ignore - } - } else { + if (!authWindow || authWindow.closed) { authWindow = window.open( authUrl, 'OAuthAuthWindow', features ); + } else { + try { + authWindow.location.href = authUrl; + authWindow.focus(); + } catch (err) { + // Ignore navigation errors from closed or cross-origin popups. + } } if (authWindow) {