Ver Fonte

更新12

liuq há 4 meses atrás
pai
commit
9bc336589c
1 ficheiros alterados com 46 adições e 22 exclusões
  1. 46 22
      server.js

+ 46 - 22
server.js

@@ -454,33 +454,42 @@ async function handleRSAEncryptedFormLogin(config, credentials) {
   }
 }
 
-// 处理 Home Assistant 登录(OAuth2 流程)
+// 处理 Home Assistant 登录(OAuth2 流程 - 严格匹配 redirect_uri
 async function handleHomeAssistantLogin(config, credentials) {
   const { targetBaseUrl } = config;
   
-  console.log('=== Home Assistant 登录 (OAuth2 模式) ===');
+  console.log('=== Home Assistant 登录 (OAuth2 严格模式) ===');
   console.log(`目标URL: ${targetBaseUrl}`);
   console.log(`用户名: ${credentials.username}`);
   console.log(`密码: ${'*'.repeat(credentials.password.length)}`);
   
+  // 基础请求头,伪装成浏览器
   const baseHeaders = {
     'Content-Type': 'application/json',
     'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
     'Accept': 'application/json, text/plain, */*',
-    'Origin': targetBaseUrl
+    'Origin': targetBaseUrl,
+    'Referer': `${targetBaseUrl}/`
   };
   
+  // 【关键】:OAuth2 协议要求 client_id 和 redirect_uri 在整个流程中完全一致
+  const CLIENT_ID = `${targetBaseUrl}/`;
+  const REDIRECT_URI = `${targetBaseUrl}/?auth_callback=1`;
+  
+  console.log('Client ID:', CLIENT_ID);
+  console.log('Redirect URI:', REDIRECT_URI);
+  
   try {
     // ==========================================
     // 步骤1: 创建登录流程 (Init Flow)
     // ==========================================
-    console.log('步骤1: 创建登录流程...');
+    console.log('[1/3] 初始化登录流程...');
     const flowResponse = await axios.post(
       `${targetBaseUrl}/auth/login_flow`,
       {
-        client_id: `${targetBaseUrl}/`, // clientId 必须和浏览器访问的一致
+        client_id: CLIENT_ID,
         handler: ['homeassistant', null],
-        redirect_uri: `${targetBaseUrl}/?auth_callback=1`
+        redirect_uri: REDIRECT_URI  // 【重要】:必须和最后跳转的地址完全一致
       },
       { 
         headers: baseHeaders,
@@ -516,13 +525,13 @@ async function handleHomeAssistantLogin(config, credentials) {
     // ==========================================
     // 步骤2: 提交用户名和密码 (Submit Credentials)
     // ==========================================
-    console.log('步骤2: 提交用户名和密码...');
+    console.log('[2/3] 提交用户名和密码...');
     const loginResponse = await axios.post(
       `${targetBaseUrl}/auth/login_flow/${flowId}`,
       {
         username: credentials.username,
         password: credentials.password,
-        client_id: `${targetBaseUrl}/` // 保持一致
+        client_id: CLIENT_ID  // 【重要】:必须和步骤1的 client_id 完全一致
       },
       { 
         headers: baseHeaders,
@@ -536,37 +545,52 @@ async function handleHomeAssistantLogin(config, credentials) {
     console.log(`登录响应数据:`, JSON.stringify(loginResponse.data, null, 2));
     
     // ==========================================
-    // 步骤3: 构造浏览器跳转 URL (关键修正)
+    // 步骤3: 构造浏览器跳转 URL(OAuth2 核心)
     // ==========================================
     const responseData = loginResponse.data || {};
     const responseType = responseData.type;
     
     console.log(`响应类型: ${responseType}`);
     
-    // 如果登录成功,type 通常是 'create_entry'
-    if (responseData.result) {
+    // 如果登录成功,type 为 'create_entry',result 字段包含 Authorization Code
+    if (responseData.result && responseType === 'create_entry') {
       const authCode = responseData.result;
-      console.log(`登录成功!获取到 Authorization Code: ${authCode}`);
+      console.log('[3/3] 登录成功!获取到 Authorization Code:', authCode);
       
-      // 构造魔术链接,让浏览器去完成 Token 交换
-      // 格式: http://ha-url/?auth_callback=1&code=YOUR_CODE
-      const redirectUrl = `${targetBaseUrl}/?auth_callback=1&code=${encodeURIComponent(authCode)}`;
+      // 【核心修正点】
+      // 拼接的 URL 必须是 REDIRECT_URI + &code=...
+      // ⚠️ 绝对不能改成 /lovelace,否则会导致 redirect_uri_mismatch 错误
+      // HA 前端会自动完成:提取 code → 换取 Token → 跳转到 /lovelace
+      const magicLink = `${REDIRECT_URI}&code=${encodeURIComponent(authCode)}`;
       
-      console.log('生成自动登录跳转链接:', redirectUrl);
-      console.log('Home Assistant 前端将自动识别 URL 中的 code 并完成 Token 交换');
+      console.log('✅ 生成魔术链接:', magicLink);
+      console.log('📝 说明: 浏览器将访问根路径,HA 前端 JS 会自动:');
+      console.log('   1. 识别 URL 中的 code 参数');
+      console.log('   2. 向后端换取 access_token');
+      console.log('   3. 存储 token 到 localStorage');
+      console.log('   4. 自动跳转到 /lovelace 仪表盘');
       
-      // 返回跳转 URL,让浏览器直接跳转
+      // 返回跳转 URL
       return {
         success: true,
-        redirectUrl: redirectUrl, // 告诉上层直接跳转这个 URL
-        cookies: [], // HA 不需要 Cookie
+        redirectUrl: magicLink,
+        cookies: [], // HA 不依赖 Cookie,使用 Token 认证
         response: loginResponse.data
       };
     } else {
-      console.error('登录未返回 Authorization Code:', responseData);
+      console.error('❌ 登录失败!未返回 Authorization Code');
+      console.error('响应数据:', responseData);
+      
+      // 提取错误信息
+      const errorMessage = responseData.errors?.base?.[0] 
+        || responseData.errors?.username?.[0]
+        || responseData.errors?.password?.[0]
+        || responseData.message 
+        || `登录失败,响应类型: ${responseType}`;
+      
       return {
         success: false,
-        message: '登录失败: 未返回授权码',
+        message: errorMessage,
         response: responseData
       };
     }