Spaces:
Sleeping
Sleeping
Update server.js
Browse files
server.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
| 1 |
const express = require('express');
|
| 2 |
const path = require('path');
|
| 3 |
const axios = require('axios');
|
|
|
|
| 4 |
const app = express();
|
| 5 |
const port = process.env.PORT || 8080;
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
// 从环境变量获取 HuggingFace 用户名和对应的 API Token 映射
|
| 8 |
const userTokenMapping = {};
|
| 9 |
const usernames = [];
|
|
@@ -26,6 +31,10 @@ if (hfUserConfig) {
|
|
| 26 |
const ADMIN_USERNAME = process.env.USER_NAME || 'admin';
|
| 27 |
const ADMIN_PASSWORD = process.env.USER_PASSWORD || 'password';
|
| 28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
// 缓存管理
|
| 30 |
class SpaceCache {
|
| 31 |
constructor() {
|
|
@@ -58,6 +67,65 @@ app.get('/api/config', (req, res) => {
|
|
| 58 |
res.json({ usernames: usernames.join(',') });
|
| 59 |
});
|
| 60 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
// 获取所有 spaces 列表(包括私有)
|
| 62 |
app.get('/api/proxy/spaces', async (req, res) => {
|
| 63 |
try {
|
|
@@ -121,8 +189,8 @@ app.get('/api/proxy/spaces', async (req, res) => {
|
|
| 121 |
}
|
| 122 |
});
|
| 123 |
|
| 124 |
-
// 代理重启 Space
|
| 125 |
-
app.post('/api/proxy/restart/:repoId(*)', async (req, res) => {
|
| 126 |
try {
|
| 127 |
const { repoId } = req.params;
|
| 128 |
console.log(`尝试重启 Space: ${repoId}`);
|
|
@@ -148,8 +216,8 @@ app.post('/api/proxy/restart/:repoId(*)', async (req, res) => {
|
|
| 148 |
}
|
| 149 |
});
|
| 150 |
|
| 151 |
-
// 代理重建 Space
|
| 152 |
-
app.post('/api/proxy/rebuild/:repoId(*)', async (req, res) => {
|
| 153 |
try {
|
| 154 |
const { repoId } = req.params;
|
| 155 |
console.log(`尝试重建 Space: ${repoId}`);
|
|
@@ -336,6 +404,17 @@ app.get('*', (req, res) => {
|
|
| 336 |
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
| 337 |
});
|
| 338 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 339 |
app.listen(port, () => {
|
| 340 |
console.log(`Server running on port ${port}`);
|
| 341 |
console.log(`User configurations:`, usernames.map(user => `${user}: ${userTokenMapping[user] ? 'Token Configured' : 'No Token'}`).join(', ') || 'None');
|
|
|
|
| 1 |
const express = require('express');
|
| 2 |
const path = require('path');
|
| 3 |
const axios = require('axios');
|
| 4 |
+
const crypto = require('crypto');
|
| 5 |
const app = express();
|
| 6 |
const port = process.env.PORT || 8080;
|
| 7 |
|
| 8 |
+
// 启用 JSON 和 URL-encoded 请求解析
|
| 9 |
+
app.use(express.json());
|
| 10 |
+
app.use(express.urlencoded({ extended: true }));
|
| 11 |
+
|
| 12 |
// 从环境变量获取 HuggingFace 用户名和对应的 API Token 映射
|
| 13 |
const userTokenMapping = {};
|
| 14 |
const usernames = [];
|
|
|
|
| 31 |
const ADMIN_USERNAME = process.env.USER_NAME || 'admin';
|
| 32 |
const ADMIN_PASSWORD = process.env.USER_PASSWORD || 'password';
|
| 33 |
|
| 34 |
+
// 存储会话 token 的简单内存数据库(生产环境中应使用数据库或 Redis)
|
| 35 |
+
const sessions = new Map();
|
| 36 |
+
const SESSION_TIMEOUT = 24 * 60 * 60 * 1000; // 24小时超时
|
| 37 |
+
|
| 38 |
// 缓存管理
|
| 39 |
class SpaceCache {
|
| 40 |
constructor() {
|
|
|
|
| 67 |
res.json({ usernames: usernames.join(',') });
|
| 68 |
});
|
| 69 |
|
| 70 |
+
// 登录 API 接口
|
| 71 |
+
app.post('/api/login', (req, res) => {
|
| 72 |
+
const { username, password } = req.body;
|
| 73 |
+
if (username === ADMIN_USERNAME && password === ADMIN_PASSWORD) {
|
| 74 |
+
// 生成一个随机 token 作为会话标识
|
| 75 |
+
const token = crypto.randomBytes(16).toString('hex');
|
| 76 |
+
const expiresAt = Date.now() + SESSION_TIMEOUT;
|
| 77 |
+
sessions.set(token, { username, expiresAt });
|
| 78 |
+
console.log(`用户 ${username} 登录成功,生成 token: ${token.slice(0, 8)}...`);
|
| 79 |
+
res.json({ success: true, token });
|
| 80 |
+
} else {
|
| 81 |
+
console.log(`用户 ${username} 登录失败,凭据无效`);
|
| 82 |
+
res.status(401).json({ success: false, message: '用户名或密码错误' });
|
| 83 |
+
}
|
| 84 |
+
});
|
| 85 |
+
|
| 86 |
+
// 验证登录状态 API 接口
|
| 87 |
+
app.post('/api/verify-token', (req, res) => {
|
| 88 |
+
const { token } = req.body;
|
| 89 |
+
const session = sessions.get(token);
|
| 90 |
+
if (session && session.expiresAt > Date.now()) {
|
| 91 |
+
res.json({ success: true, message: 'Token 有效' });
|
| 92 |
+
} else {
|
| 93 |
+
if (session) {
|
| 94 |
+
sessions.delete(token); // 删除过期的 token
|
| 95 |
+
console.log(`Token ${token.slice(0, 8)}... 已过期,已删除`);
|
| 96 |
+
}
|
| 97 |
+
res.status(401).json({ success: false, message: 'Token 无效或已过期' });
|
| 98 |
+
}
|
| 99 |
+
});
|
| 100 |
+
|
| 101 |
+
// 登出 API 接口
|
| 102 |
+
app.post('/api/logout', (req, res) => {
|
| 103 |
+
const { token } = req.body;
|
| 104 |
+
sessions.delete(token);
|
| 105 |
+
console.log(`Token ${token.slice(0, 8)}... 已手动登出`);
|
| 106 |
+
res.json({ success: true, message: '登出成功' });
|
| 107 |
+
});
|
| 108 |
+
|
| 109 |
+
// 中间件:验证请求中的 token
|
| 110 |
+
const authenticateToken = (req, res, next) => {
|
| 111 |
+
const authHeader = req.headers['authorization'];
|
| 112 |
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
| 113 |
+
return res.status(401).json({ error: '未提供有效的认证令牌' });
|
| 114 |
+
}
|
| 115 |
+
const token = authHeader.split(' ')[1];
|
| 116 |
+
const session = sessions.get(token);
|
| 117 |
+
if (session && session.expiresAt > Date.now()) {
|
| 118 |
+
req.session = session;
|
| 119 |
+
next();
|
| 120 |
+
} else {
|
| 121 |
+
if (session) {
|
| 122 |
+
sessions.delete(token); // 删除过期的 token
|
| 123 |
+
console.log(`Token ${token.slice(0, 8)}... 已过期,拒绝访问`);
|
| 124 |
+
}
|
| 125 |
+
return res.status(401).json({ error: '认证令牌无效或已过期' });
|
| 126 |
+
}
|
| 127 |
+
};
|
| 128 |
+
|
| 129 |
// 获取所有 spaces 列表(包括私有)
|
| 130 |
app.get('/api/proxy/spaces', async (req, res) => {
|
| 131 |
try {
|
|
|
|
| 189 |
}
|
| 190 |
});
|
| 191 |
|
| 192 |
+
// 代理重启 Space(需要认证)
|
| 193 |
+
app.post('/api/proxy/restart/:repoId(*)', authenticateToken, async (req, res) => {
|
| 194 |
try {
|
| 195 |
const { repoId } = req.params;
|
| 196 |
console.log(`尝试重启 Space: ${repoId}`);
|
|
|
|
| 216 |
}
|
| 217 |
});
|
| 218 |
|
| 219 |
+
// 代理重建 Space(需要认证)
|
| 220 |
+
app.post('/api/proxy/rebuild/:repoId(*)', authenticateToken, async (req, res) => {
|
| 221 |
try {
|
| 222 |
const { repoId } = req.params;
|
| 223 |
console.log(`尝试重建 Space: ${repoId}`);
|
|
|
|
| 404 |
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
| 405 |
});
|
| 406 |
|
| 407 |
+
// 定期清理过期的会话
|
| 408 |
+
setInterval(() => {
|
| 409 |
+
const now = Date.now();
|
| 410 |
+
for (const [token, session] of sessions.entries()) {
|
| 411 |
+
if (session.expiresAt < now) {
|
| 412 |
+
sessions.delete(token);
|
| 413 |
+
console.log(`Token ${token.slice(0, 8)}... 已过期,自动清理`);
|
| 414 |
+
}
|
| 415 |
+
}
|
| 416 |
+
}, 60 * 60 * 1000); // 每小时清理一次
|
| 417 |
+
|
| 418 |
app.listen(port, () => {
|
| 419 |
console.log(`Server running on port ${port}`);
|
| 420 |
console.log(`User configurations:`, usernames.map(user => `${user}: ${userTokenMapping[user] ? 'Token Configured' : 'No Token'}`).join(', ') || 'None');
|