174 lines
5.5 KiB
JavaScript
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);
|
|
});
|