liuq 4 ヶ月 前
コミット
93dc649ebb
3 ファイル変更150 行追加27 行削除
  1. 86 0
      package-lock.json
  2. 8 7
      package.json
  3. 56 20
      server.js

+ 86 - 0
package-lock.json

@@ -9,10 +9,12 @@
       "version": "1.0.0",
       "dependencies": {
         "axios": "^1.6.2",
+        "axios-cookiejar-support": "^6.0.5",
         "cookie-parser": "^1.4.6",
         "cors": "^2.8.5",
         "express": "^4.18.2",
         "node-rsa": "^1.1.1",
+        "tough-cookie": "^6.0.0",
         "vue": "^3.4.21"
       },
       "devDependencies": {
@@ -916,6 +918,15 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/agent-base": {
+      "version": "7.1.4",
+      "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-7.1.4.tgz",
+      "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 14"
+      }
+    },
     "node_modules/ansi-regex": {
       "version": "5.0.1",
       "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -968,12 +979,32 @@
       "resolved": "https://registry.npmmirror.com/axios/-/axios-1.13.2.tgz",
       "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "follow-redirects": "^1.15.6",
         "form-data": "^4.0.4",
         "proxy-from-env": "^1.1.0"
       }
     },
+    "node_modules/axios-cookiejar-support": {
+      "version": "6.0.5",
+      "resolved": "https://registry.npmmirror.com/axios-cookiejar-support/-/axios-cookiejar-support-6.0.5.tgz",
+      "integrity": "sha512-ldPOQCJWB0ipugkTNVB8QRl/5L2UgfmVNVQtS9en1JQJ1wW588PqAmymnwmmgc12HLDzDtsJ28xE2ppj4rD4ng==",
+      "license": "MIT",
+      "dependencies": {
+        "http-cookie-agent": "^7.0.3"
+      },
+      "engines": {
+        "node": ">=20.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/3846masa"
+      },
+      "peerDependencies": {
+        "axios": ">=0.20.0",
+        "tough-cookie": ">=4.0.0"
+      }
+    },
     "node_modules/body-parser": {
       "version": "1.20.4",
       "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.4.tgz",
@@ -1676,6 +1707,30 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/http-cookie-agent": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmmirror.com/http-cookie-agent/-/http-cookie-agent-7.0.3.tgz",
+      "integrity": "sha512-EeZo7CGhfqPW6R006rJa4QtZZUpBygDa2HZH3DJqsTzTjyRE6foDBVQIv/pjVsxHC8z2GIdbB1Hvn9SRorP3WQ==",
+      "license": "MIT",
+      "dependencies": {
+        "agent-base": "^7.1.4"
+      },
+      "engines": {
+        "node": ">=20.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/3846masa"
+      },
+      "peerDependencies": {
+        "tough-cookie": "^4.0.0 || ^5.0.0 || ^6.0.0",
+        "undici": "^7.0.0"
+      },
+      "peerDependenciesMeta": {
+        "undici": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/http-errors": {
       "version": "2.0.1",
       "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.1.tgz",
@@ -2381,6 +2436,24 @@
         "url": "https://github.com/chalk/supports-color?sponsor=1"
       }
     },
+    "node_modules/tldts": {
+      "version": "7.0.19",
+      "resolved": "https://registry.npmmirror.com/tldts/-/tldts-7.0.19.tgz",
+      "integrity": "sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==",
+      "license": "MIT",
+      "dependencies": {
+        "tldts-core": "^7.0.19"
+      },
+      "bin": {
+        "tldts": "bin/cli.js"
+      }
+    },
+    "node_modules/tldts-core": {
+      "version": "7.0.19",
+      "resolved": "https://registry.npmmirror.com/tldts-core/-/tldts-core-7.0.19.tgz",
+      "integrity": "sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==",
+      "license": "MIT"
+    },
     "node_modules/toidentifier": {
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz",
@@ -2390,6 +2463,19 @@
         "node": ">=0.6"
       }
     },
+    "node_modules/tough-cookie": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-6.0.0.tgz",
+      "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==",
+      "license": "BSD-3-Clause",
+      "peer": true,
+      "dependencies": {
+        "tldts": "^7.0.5"
+      },
+      "engines": {
+        "node": ">=16"
+      }
+    },
     "node_modules/tree-kill": {
       "version": "1.2.2",
       "resolved": "https://registry.npmmirror.com/tree-kill/-/tree-kill-1.2.2.tgz",

+ 8 - 7
package.json

@@ -12,17 +12,18 @@
     "start": "concurrently \"npm run dev\" \"npm run server\""
   },
   "dependencies": {
-    "vue": "^3.4.21",
-    "express": "^4.18.2",
     "axios": "^1.6.2",
-    "node-rsa": "^1.1.1",
+    "axios-cookiejar-support": "^6.0.5",
     "cookie-parser": "^1.4.6",
-    "cors": "^2.8.5"
+    "cors": "^2.8.5",
+    "express": "^4.18.2",
+    "node-rsa": "^1.1.1",
+    "tough-cookie": "^6.0.0",
+    "vue": "^3.4.21"
   },
   "devDependencies": {
     "@vitejs/plugin-vue": "^5.0.4",
-    "vite": "^5.1.6",
-    "concurrently": "^8.2.2"
+    "concurrently": "^8.2.2",
+    "vite": "^5.1.6"
   }
 }
-

+ 56 - 20
server.js

@@ -6,6 +6,8 @@ import cookieParser from 'cookie-parser';
 import { readFileSync } from 'fs';
 import { fileURLToPath } from 'url';
 import { dirname, join } from 'path';
+import { CookieJar } from 'tough-cookie';
+import { wrapper } from 'axios-cookiejar-support';
 
 // node-rsa 是 CommonJS 模块,需要使用默认导入
 const NodeRSA = nodeRSA;
@@ -360,6 +362,13 @@ async function handleHomeAssistantLogin(config, credentials) {
   console.log(`用户名: ${credentials.username}`);
   console.log(`密码: ${'*'.repeat(credentials.password.length)}`);
   
+  // 创建 Cookie jar 来保持会话
+  const cookieJar = new CookieJar();
+  const client = wrapper(axios.create({
+    jar: cookieJar,
+    withCredentials: true
+  }));
+  
   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',
@@ -372,15 +381,17 @@ async function handleHomeAssistantLogin(config, credentials) {
   let initialCookies = [];
   try {
     console.log('访问首页获取初始 Cookie...');
-    const homeResponse = await axios.get(`${targetBaseUrl}/`, {
+    const homeResponse = await client.get(`${targetBaseUrl}/`, {
       headers: {
         'User-Agent': baseHeaders['User-Agent']
       },
-      withCredentials: true,
       maxRedirects: 5
     });
     initialCookies = homeResponse.headers['set-cookie'] || [];
     console.log(`获取到 ${initialCookies.length} 个初始 Cookie`);
+    // 从 Cookie jar 中获取 Cookie
+    const jarCookies = await cookieJar.getCookies(targetBaseUrl);
+    console.log(`Cookie jar 中有 ${jarCookies.length} 个 Cookie`);
   } catch (error) {
     console.log('访问首页失败(可能不需要):', error.message);
   }
@@ -389,7 +400,7 @@ async function handleHomeAssistantLogin(config, credentials) {
   console.log('步骤1: 创建登录流程...');
   let flowResponse;
   try {
-    flowResponse = await axios.post(
+    flowResponse = await client.post(
       `${targetBaseUrl}/auth/login_flow`,
       {
         client_id: `${targetBaseUrl}/`,
@@ -398,7 +409,6 @@ async function handleHomeAssistantLogin(config, credentials) {
       },
       {
         headers: baseHeaders,
-        withCredentials: true,
         maxRedirects: 0,
         validateStatus: function (status) {
           return status >= 200 && status < 500;
@@ -450,7 +460,7 @@ async function handleHomeAssistantLogin(config, credentials) {
   console.log('步骤2: 提交用户名和密码...');
   let loginResponse;
   try {
-    loginResponse = await axios.post(
+    loginResponse = await client.post(
       `${targetBaseUrl}/auth/login_flow/${flowId}`,
       {
         username: credentials.username,
@@ -459,7 +469,6 @@ async function handleHomeAssistantLogin(config, credentials) {
       },
       {
         headers: baseHeaders,
-        withCredentials: true,
         maxRedirects: 0,
         validateStatus: function (status) {
           return status >= 200 && status < 500;
@@ -495,11 +504,29 @@ async function handleHomeAssistantLogin(config, credentials) {
   
   // Home Assistant 登录成功时,type 为 "create_entry"
   if (responseType === 'create_entry') {
-    // 合并所有请求的 Cookie
+    // 从 Cookie jar 中获取所有 Cookie
+    console.log('从 Cookie jar 中获取 Cookie...');
+    const jarCookies = await cookieJar.getCookies(targetBaseUrl);
+    console.log(`Cookie jar 中有 ${jarCookies.length} 个 Cookie`);
+    
+    // 合并所有请求的 Cookie(从响应头和 Cookie jar)
     const flowCookies = flowResponse.headers['set-cookie'] || [];
     const loginCookies = loginResponse.headers['set-cookie'] || [];
     const allCookies = [...initialCookies, ...flowCookies, ...loginCookies];
     
+    // 从 Cookie jar 转换为 Set-Cookie 格式
+    jarCookies.forEach(cookie => {
+      let cookieStr = `${cookie.key}=${cookie.value}`;
+      if (cookie.path) cookieStr += `; Path=${cookie.path}`;
+      if (cookie.domain) cookieStr += `; Domain=${cookie.domain}`;
+      if (cookie.expires) cookieStr += `; Expires=${cookie.expires.toUTCString()}`;
+      if (cookie.maxAge) cookieStr += `; Max-Age=${cookie.maxAge}`;
+      if (cookie.secure) cookieStr += `; Secure`;
+      if (cookie.httpOnly) cookieStr += `; HttpOnly`;
+      if (cookie.sameSite) cookieStr += `; SameSite=${cookie.sameSite}`;
+      allCookies.push(cookieStr);
+    });
+    
     // 去重 Cookie(保留最后一个)
     const cookieMap = new Map();
     allCookies.forEach(cookie => {
@@ -531,23 +558,14 @@ async function handleHomeAssistantLogin(config, credentials) {
       
       console.log(`访问授权端点: ${authorizeUrl}`);
       
-      // 构建 Cookie 字符串用于授权请求
-      const cookieHeader = uniqueCookies.map(cookie => {
-        const cookieStr = cookie.split(';')[0]; // 只取 name=value 部分
-        return cookieStr;
-      }).join('; ');
-      
-      console.log(`使用 Cookie 头: ${cookieHeader.substring(0, 100)}...`);
-      
-      const authorizeResponse = await axios.get(authorizeUrl, {
+      // 使用 Cookie jar 的客户端访问授权端点
+      const authorizeResponse = await client.get(authorizeUrl, {
         headers: {
           'User-Agent': baseHeaders['User-Agent'],
           'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
           'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
-          'Referer': `${targetBaseUrl}/`,
-          'Cookie': cookieHeader
+          'Referer': `${targetBaseUrl}/`
         },
-        withCredentials: true,
         maxRedirects: 5,
         validateStatus: function (status) {
           return status >= 200 && status < 400;
@@ -557,15 +575,33 @@ async function handleHomeAssistantLogin(config, credentials) {
       console.log(`授权响应状态码: ${authorizeResponse.status}`);
       console.log(`授权响应 URL: ${authorizeResponse.request?.res?.responseUrl || authorizeResponse.config?.url}`);
       
+      // 从 Cookie jar 中获取授权后的 Cookie
+      const authorizeJarCookies = await cookieJar.getCookies(targetBaseUrl);
+      console.log(`授权后 Cookie jar 中有 ${authorizeJarCookies.length} 个 Cookie`);
+      
       // 获取授权响应中的 Cookie
       const authorizeCookies = authorizeResponse.headers['set-cookie'] || [];
       console.log(`授权响应获取到 ${authorizeCookies.length} 个 Cookie`);
       
-      // 合并授权 Cookie
+      // 从 Cookie jar 转换为 Set-Cookie 格式并合并
+      authorizeJarCookies.forEach(cookie => {
+        let cookieStr = `${cookie.key}=${cookie.value}`;
+        if (cookie.path) cookieStr += `; Path=${cookie.path}`;
+        if (cookie.domain) cookieStr += `; Domain=${cookie.domain}`;
+        if (cookie.expires) cookieStr += `; Expires=${cookie.expires.toUTCString()}`;
+        if (cookie.maxAge) cookieStr += `; Max-Age=${cookie.maxAge}`;
+        if (cookie.secure) cookieStr += `; Secure`;
+        if (cookie.httpOnly) cookieStr += `; HttpOnly`;
+        if (cookie.sameSite) cookieStr += `; SameSite=${cookie.sameSite}`;
+        cookieMap.set(cookie.key, cookieStr);
+      });
+      
+      // 合并授权响应中的 Cookie
       authorizeCookies.forEach(cookie => {
         const name = cookie.split('=')[0];
         cookieMap.set(name, cookie);
       });
+      
       uniqueCookies = Array.from(cookieMap.values());
       
       console.log(`授权完成!最终获取到 ${uniqueCookies.length} 个唯一 Cookie`);