Files
workpod/auth-proxy.js
T

174 lines
5.5 KiB
JavaScript

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);
});