liuq 4 달 전
부모
커밋
fa0e99f655
1개의 변경된 파일166개의 추가작업 그리고 142개의 파일을 삭제
  1. 166 142
      server.js

+ 166 - 142
server.js

@@ -895,194 +895,217 @@ app.get('/api/auto-login/:siteId', async (req, res) => {
     
     console.log(`[${requestId}] 登录成功!`);
     
-    // OAuth2 跨端口增强方案
+    // OAuth2 跨端口:调试页面方案
     if (config.loginMethod === 'home-assistant' && loginResult.useEnhancedRedirect) {
-      console.log(`[${requestId}] 🚀 Home Assistant OAuth2 跨端口方案`);
-      console.log(`[${requestId}] 已获取 Token(用于验证)`);
-      console.log(`[${requestId}] 使用 iframe 预加载方案,防止前端路由抢跑`);
+      console.log(`[${requestId}] 🚀 Home Assistant OAuth2 - 调试重定向方案`);
+      console.log(`[${requestId}] Token 已获取: ${loginResult.tokens.access_token.substring(0, 30)}...`);
+      console.log(`[${requestId}] Authorization Code: ${loginResult.redirectUrl.match(/code=([^&]+)/)?.[1]}`);
+      console.log(`[${requestId}] 重定向 URL: ${loginResult.redirectUrl}`);
       
       const magicLink = loginResult.redirectUrl;
-      const targetBaseUrl = loginResult.targetBaseUrl;
+      const authCode = magicLink.match(/code=([^&]+)/)?.[1] || 'unknown';
       
-      // 生成增强的 OAuth2 中间页面
-      // 核心策略:使用隐藏 iframe 先让 HA 处理 code,然后再显示跳转
-      const oauth2EnhancedHtml = `
+      // 生成调试页面
+      const debugHtml = `
 <!DOCTYPE html>
 <html lang="zh-CN">
 <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>正在登录 Home Assistant...</title>
+    <title>Home Assistant OAuth2 调试</title>
     <style>
         body {
-            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-            color: white;
             font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
-            display: flex;
-            flex-direction: column;
-            justify-content: center;
-            align-items: center;
-            height: 100vh;
+            background: #1e1e1e;
+            color: #d4d4d4;
+            padding: 20px;
             margin: 0;
         }
-        .container { text-align: center; max-width: 500px; padding: 20px; }
-        .loader {
-            border: 4px solid rgba(255, 255, 255, 0.3);
-            border-top: 4px solid white;
-            border-radius: 50%;
-            width: 60px;
-            height: 60px;
-            animation: spin 1s linear infinite;
-            margin: 0 auto 30px;
+        .container {
+            max-width: 1000px;
+            margin: 0 auto;
         }
-        @keyframes spin {
-            0% { transform: rotate(0deg); }
-            100% { transform: rotate(360deg); }
+        h1 {
+            color: #4ec9b0;
+            border-bottom: 2px solid #4ec9b0;
+            padding-bottom: 10px;
         }
-        h2 { margin: 0 0 15px 0; font-size: 28px; }
-        .status { margin: 10px 0; opacity: 0.9; font-size: 16px; }
-        .step { 
-            background: rgba(255,255,255,0.1); 
-            padding: 10px 20px; 
-            border-radius: 8px; 
-            margin: 5px 0;
-            font-size: 14px;
-        }
-        .step.active { background: rgba(255,255,255,0.3); }
-        .step.done { background: rgba(76,175,80,0.5); }
-        #countdown { 
-            font-size: 48px; 
-            font-weight: bold; 
+        .section {
+            background: #252526;
+            border: 1px solid #3e3e42;
+            border-radius: 8px;
+            padding: 20px;
             margin: 20px 0;
-            text-shadow: 0 2px 10px rgba(0,0,0,0.3);
         }
-        .info {
-            margin-top: 20px;
+        .section h2 {
+            color: #569cd6;
+            margin-top: 0;
+        }
+        pre {
+            background: #1e1e1e;
+            border: 1px solid #3e3e42;
+            border-radius: 4px;
+            padding: 15px;
+            overflow-x: auto;
+        }
+        .success { color: #4ec9b0; }
+        .warning { color: #ce9178; }
+        .error { color: #f48771; }
+        .button {
+            background: #0e639c;
+            color: white;
+            border: none;
+            padding: 12px 24px;
+            border-radius: 6px;
+            font-size: 16px;
+            cursor: pointer;
+            margin: 10px 5px;
+        }
+        .button:hover { background: #1177bb; }
+        .button.secondary {
+            background: #3e3e42;
+        }
+        .button.secondary:hover { background: #505050; }
+        #log {
+            background: #1e1e1e;
+            border: 1px solid #3e3e42;
+            border-radius: 4px;
+            padding: 15px;
+            max-height: 300px;
+            overflow-y: auto;
+            font-family: 'Consolas', 'Monaco', monospace;
             font-size: 12px;
-            opacity: 0.7;
+        }
+        .log-entry {
+            margin: 5px 0;
+            padding: 5px;
+            border-left: 3px solid #569cd6;
+            padding-left: 10px;
         }
     </style>
 </head>
 <body>
     <div class="container">
-        <div class="loader"></div>
-        <h2>✓ OAuth2 认证成功</h2>
-        <div class="status">正在完成跨端口登录流程...</div>
+        <h1>🔍 Home Assistant OAuth2 登录调试</h1>
+        
+        <div class="section">
+            <h2>✅ 后端登录成功</h2>
+            <p>Authorization Code 已获取,Token 已验证。</p>
+            <p><strong class="success">Authorization Code:</strong> <code>${authCode.substring(0, 20)}...</code></p>
+        </div>
+        
+        <div class="section">
+            <h2>📋 OAuth2 跨端口问题分析</h2>
+            <p class="warning">⚠️ 检测到跨端口场景:</p>
+            <ul>
+                <li>Node.js 后端:<code>222.243.138.146:8889</code></li>
+                <li>Home Assistant:<code>222.243.138.146:8123</code></li>
+                <li>localStorage 隔离:不同端口无法共享 Token</li>
+            </ul>
+        </div>
+        
+        <div class="section">
+            <h2>🎯 手动测试步骤</h2>
+            <p>请按以下步骤测试,帮助我们诊断问题:</p>
+            
+            <h3>测试 1:直接访问魔术链接</h3>
+            <p>复制下面的 URL 到新标签页,看是否能登录:</p>
+            <pre>${magicLink}</pre>
+            <button class="button" onclick="window.open('${magicLink}', '_blank')">
+                🔗 在新标签页打开
+            </button>
+            
+            <h3>测试 2:在当前标签页跳转</h3>
+            <p>让当前页面跳转过去(可能有更好的效果):</p>
+            <button class="button secondary" onclick="window.location.href='${magicLink}'">
+                ➡️ 当前标签页跳转
+            </button>
+            
+            <h3>测试 3:iframe 预加载然后跳转</h3>
+            <p>使用 iframe 预加载,5秒后跳转:</p>
+            <button class="button secondary" onclick="testIframeMethod()">
+                🔄 使用 iframe 方案
+            </button>
+        </div>
         
-        <div class="step" id="step1">步骤 1: 获取授权码 ✓</div>
-        <div class="step active" id="step2">步骤 2: 预加载 Home Assistant</div>
-        <div class="step" id="step3">步骤 3: 等待 Token 交换</div>
-        <div class="step" id="step4">步骤 4: 跳转到仪表盘</div>
+        <div class="section">
+            <h2>📊 实时日志</h2>
+            <div id="log"></div>
+        </div>
         
-        <div id="countdown">5</div>
-        <div class="info">请勿关闭此页面</div>
+        <div class="section">
+            <h2>💡 建议</h2>
+            <p>如果以上测试都失败,强烈建议使用 <strong class="success">Trusted Networks</strong> 方案:</p>
+            <ul>
+                <li>✅ 官方支持,100% 可靠</li>
+                <li>✅ 无需复杂的 OAuth2 流程</li>
+                <li>✅ 零延迟,直接登录</li>
+            </ul>
+            <button class="button" onclick="alert('请在 Home Assistant 的 configuration.yaml 中配置:\\n\\nhomeassistant:\\n  auth_providers:\\n    - type: trusted_networks\\n      trusted_networks:\\n        - 118.251.191.88/32\\n      trusted_users:\\n        118.251.191.88/32: YOUR_USER_ID\\n      allow_bypass_login: true\\n    - type: homeassistant')">
+                📖 查看 Trusted Networks 配置
+            </button>
+        </div>
     </div>
     
-    <!-- 隐藏 iframe 用于预加载 HA 并触发 code -> token 交换 -->
-    <iframe id="authFrame" style="position:absolute;width:0;height:0;border:0;"></iframe>
+    <iframe id="testFrame" style="display:none;"></iframe>
     
     <script>
-        (function() {
-            const iframe = document.getElementById('authFrame');
-            const step1 = document.getElementById('step1');
-            const step2 = document.getElementById('step2');
-            const step3 = document.getElementById('step3');
-            const step4 = document.getElementById('step4');
-            const countdownEl = document.getElementById('countdown');
-            
-            const magicLink = "${magicLink}";
-            const targetUrl = "${targetBaseUrl}";
-            
-            console.log('========================================');
-            console.log('[OAuth2 跨端口] 开始处理');
-            console.log('[OAuth2 跨端口] 魔术链接:', magicLink);
-            console.log('[OAuth2 跨端口] 目标地址:', targetUrl);
-            console.log('========================================');
-            
-            let seconds = 5;
-            let iframeLoaded = false;
-            
-            // 步骤 1: 已完成(后端已获取 code)
-            step1.classList.add('done');
-            
-            // 步骤 2: 使用 iframe 预加载带有 code 的 URL
-            console.log('[步骤 2/4] 使用 iframe 预加载 HA...');
+        const logDiv = document.getElementById('log');
+        const iframe = document.getElementById('testFrame');
+        
+        function addLog(msg, type = 'info') {
+            const entry = document.createElement('div');
+            entry.className = 'log-entry';
+            entry.textContent = new Date().toLocaleTimeString() + ' - ' + msg;
+            logDiv.appendChild(entry);
+            logDiv.scrollTop = logDiv.scrollHeight;
+            console.log('[调试] ' + msg);
+        }
+        
+        function testIframeMethod() {
+            addLog('开始 iframe 测试...');
+            addLog('加载 URL: ${magicLink}');
             
+            let loaded = false;
             iframe.onload = function() {
-                if (!iframeLoaded) {
-                    iframeLoaded = true;
-                    console.log('[步骤 2/4] ✓ iframe 加载完成');
-                    step2.classList.remove('active');
-                    step2.classList.add('done');
-                    step3.classList.add('active');
-                    console.log('[步骤 3/4] 等待 HA 前端完成 Token 交换...');
-                    console.log('[步骤 3/4] HA 前端正在后台处理 OAuth2 code');
+                if (!loaded) {
+                    loaded = true;
+                    addLog('✓ iframe 加载完成');
+                    addLog('等待 5 秒后跳转...');
+                    
+                    let countdown = 5;
+                    const timer = setInterval(function() {
+                        countdown--;
+                        addLog('倒计时: ' + countdown + '秒');
+                        if (countdown <= 0) {
+                            clearInterval(timer);
+                            addLog('正在跳转到 Home Assistant...');
+                            window.location.href = '${targetBaseUrl}';
+                        }
+                    }, 1000);
                 }
             };
             
             iframe.onerror = function(e) {
-                console.error('[步骤 2/4] ✗ iframe 加载失败:', e);
-                console.log('[步骤 2/4] 将继续倒计时后跳转...');
+                addLog('✗ iframe 加载失败: ' + e, 'error');
             };
             
-            // 加载带有 authorization code 的 URL
-            // HA 前端会在 iframe 中处理这个 code 并换取 token
-            console.log('[步骤 2/4] 加载 URL:', magicLink);
-            iframe.src = magicLink;
-            
-            // 步骤 3 & 4: 倒计时后跳转
-            const countdown = setInterval(function() {
-                seconds--;
-                countdownEl.textContent = seconds;
-                
-                if (seconds === 2 && iframeLoaded) {
-                    console.log('[步骤 3/4] ✓ 假设 Token 交换已完成');
-                    step3.classList.remove('active');
-                    step3.classList.add('done');
-                    step4.classList.add('active');
-                    console.log('[步骤 4/4] 准备跳转到主界面...');
-                }
-                
-                if (seconds <= 0) {
-                    clearInterval(countdown);
-                    step4.classList.remove('active');
-                    step4.classList.add('done');
-                    console.log('[步骤 4/4] ✓ 跳转中...');
-                    console.log('========================================');
-                    
-                    // 最终跳转到 HA 主页
-                    // 由于 iframe 已经处理了 code,HA 应该已经有了 token
-                    window.location.href = targetUrl;
-                }
-            }, 1000);
-            
-            // 允许用户点击立即跳转(如果等不及)
-            document.body.style.cursor = 'pointer';
-            document.body.onclick = function() {
-                if (seconds <= 3) {  // 至少等待 2 秒
-                    console.log('[用户操作] 提前跳转');
-                    clearInterval(countdown);
-                    window.location.href = targetUrl;
-                }
-            };
-            
-            // 保险机制:10秒后强制跳转
-            setTimeout(function() {
-                console.log('[超时保护] 10秒后强制跳转');
-                clearInterval(countdown);
-                window.location.href = targetUrl;
-            }, 10000);
-            
-        })();
+            iframe.src = '${magicLink}';
+        }
+        
+        addLog('后端 OAuth2 登录成功');
+        addLog('Authorization Code: ${authCode.substring(0, 20)}...');
+        addLog('请选择测试方法');
     </script>
 </body>
 </html>
       `;
       
+      console.log(`[${requestId}] 返回调试页面,供手动测试`);
       console.log(`[${requestId}] 总耗时: ${Date.now() - startTime}ms`);
       console.log('='.repeat(80) + '\n');
       
-      return res.send(oauth2EnhancedHtml);
+      return res.send(debugHtml);
     }
     
     // 对于 Home Assistant,如果使用传统 redirect 方式(降级方案)
@@ -1364,3 +1387,4 @@ app.listen(PORT, '0.0.0.0', () => {
   console.log(`\n💡 提示: 确保防火墙已配置端口映射 (前端:8888, 后端:8889 -> 外网)`);
   console.log('='.repeat(80) + '\n');
 });
+