const http = require('http'); const net = require('net'); const fs = require('fs'); // 配置 const PORT = 8080; const TOKEN_PREFIX = 'wk_'; // 用户口令从环境变量读取(JSON),未设置时用弱默认——生产环境务必通过 WORKPOD_AUTH_USERS 注入强口令 const VALID_PASSWORDS = JSON.parse(process.env.WORKPOD_AUTH_USERS || '{"wk":"1234567","admin":"admin123"}'); const WORKSPACES = [ { path: '/', name: '默认工作区', desc: '/workspace', port: 7681 }, { path: '/hszd', name: '华商智地', desc: '/workspace/wk-hszd', port: 7682 }, ]; // Token 存储 (内存) const tokens = new Set(); // 生成 token function createToken(user) { const t = TOKEN_PREFIX + user + '_' + Date.now(); tokens.add(t); // 1小时过期清理 setTimeout(() => tokens.delete(t), 3600000); return t; } // 验证 token function validateToken(token) { return token && tokens.has(token); } // 解析 URL 路径,匹配工作区 function matchWorkspace(pathname) { for (const ws of WORKSPACES) { if (pathname === ws.path || pathname.startsWith(ws.path + '/')) { const proxyPath = ws.path === '/' ? pathname : pathname.slice(ws.path.length) || '/'; return { ws, proxyPath }; } } return null; } // 代理请求到 ttyd function proxyToTtyd(req, res, port, path) { const options = { hostname: '127.0.0.1', port: port, path: path, method: req.method, headers: { ...req.headers, host: '127.0.0.1:' + port, 'X-WorkPod-Auth': '1' } }; const proxyReq = http.request(options, (proxyRes) => { res.writeHead(proxyRes.statusCode, proxyRes.headers); proxyRes.pipe(res); }); proxyReq.on('error', () => { if (!res.headersSent) res.writeHead(502); res.end('Bad Gateway'); }); req.pipe(proxyReq); } // 登录页 HTML const LOGIN_HTML = fs.readFileSync('/var/www/workpod/login.html', 'utf-8'); const server = http.createServer((req, res) => { const url = new URL(req.url, 'http://' + req.headers.host); const token = url.searchParams.get('token') || ''; // 登录页 if (url.pathname === '/login') { res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); return res.end(LOGIN_HTML); } // 认证 API if (url.pathname === '/auth/check') { const auth = req.headers.authorization; if (!auth || !auth.startsWith('Basic ')) { res.writeHead(401); return res.end('Unauthorized'); } const decoded = Buffer.from(auth.slice(6), 'base64').toString(); const [user, pass] = decoded.split(':'); if (!VALID_PASSWORDS[user] || VALID_PASSWORDS[user] !== pass) { res.writeHead(401); return res.end('Unauthorized'); } res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end(createToken(user)); } // 工作空间列表 if (url.pathname === '/api/workspaces') { if (!validateToken(token)) { res.writeHead(401); return res.end('Unauthorized'); } res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify(WORKSPACES)); } // ttyd 内部端点 (/token, /ws 等) — 直接代理到默认工作区 if (url.pathname === '/token' || url.pathname.startsWith('/ttyd/')) { proxyToTtyd(req, res, WORKSPACES[0].port, url.pathname + url.search); return; } // Token 验证 if (!validateToken(token)) { res.writeHead(302, { 'Location': '/login' }); return res.end(); } // 路由到对应工作区 (普通 HTTP) const matched = matchWorkspace(url.pathname); if (matched) { const { ws, proxyPath } = matched; proxyToTtyd(req, res, ws.port, proxyPath + url.search); return; } // 未匹配路径 -> 登录页 res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); res.end(LOGIN_HTML); }); // WebSocket 代理 (upgrade 事件) server.on('upgrade', (req, socket, head) => { const url = new URL(req.url, 'http://' + req.headers.host); const token = url.searchParams.get('token') || ''; // Token 验证 if (!validateToken(token)) { socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n'); socket.destroy(); return; } // 匹配工作区 const matched = matchWorkspace(url.pathname); if (!matched) { socket.write('HTTP/1.1 404 Not Found\r\n\r\n'); socket.destroy(); return; } const { ws, proxyPath } = matched; const proxyUrl = new URL(proxyPath + url.search, 'http://127.0.0.1:' + ws.port); // 建立到 ttyd 的 TCP 连接 const target = net.connect(ws.port, '127.0.0.1', () => { // 构造 upgrade 请求头 const headers = { ...req.headers, host: '127.0.0.1:' + ws.port, 'X-WorkPod-Auth': '1' }; const requestLine = 'GET ' + proxyUrl.pathname + proxyUrl.search + ' HTTP/1.1\r\n'; const headerLines = Object.entries(headers).map(([k, v]) => k + ': ' + v).join('\r\n'); target.write(requestLine + headerLines + '\r\n\r\n'); // 如果有缓冲数据(head),转发给 ttyd if (head.length > 0) target.write(head); // 双向管道: 浏览器 <-> ttyd target.pipe(socket); socket.pipe(target); }); target.on('error', () => socket.destroy()); socket.on('error', () => target.destroy()); }); server.listen(PORT, () => { console.log('[auth-proxy] 认证代理已启动,端口: ' + PORT); });