@sleep2agi/commhub-server 0.5.0-preview.17 → 0.5.0-preview.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.ts +32 -0
- package/src/tools.ts +13 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -9,6 +9,29 @@ import { register, login, resolveToken, getUserNetworks, createNetwork, type Aut
|
|
|
9
9
|
const PORT = Number(process.env.PORT) || 9200;
|
|
10
10
|
const AUTH_TOKEN = process.env.COMMHUB_AUTH_TOKEN;
|
|
11
11
|
|
|
12
|
+
// ── Rate limiter (in-memory, per IP) ──
|
|
13
|
+
const rateLimits = new Map<string, { count: number; resetAt: number }>();
|
|
14
|
+
function checkRateLimit(ip: string, maxPerMinute = 60): boolean {
|
|
15
|
+
// Skip rate limiting for localhost/internal/unknown (dev/test)
|
|
16
|
+
if (!ip || ip === "unknown" || ip === "127.0.0.1" || ip === "::1") return true;
|
|
17
|
+
const now = Date.now();
|
|
18
|
+
const entry = rateLimits.get(ip);
|
|
19
|
+
if (!entry || now > entry.resetAt) {
|
|
20
|
+
rateLimits.set(ip, { count: 1, resetAt: now + 60000 });
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
if (entry.count >= maxPerMinute) return false;
|
|
24
|
+
entry.count++;
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
// Cleanup stale entries every 5 minutes
|
|
28
|
+
setInterval(() => {
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
for (const [ip, entry] of rateLimits) {
|
|
31
|
+
if (now > entry.resetAt) rateLimits.delete(ip);
|
|
32
|
+
}
|
|
33
|
+
}, 300000);
|
|
34
|
+
|
|
12
35
|
// ── Factory: 每个请求创建新的 McpServer(stateless 模式)──
|
|
13
36
|
function createServer(clientIP?: string, enforceNetworkId?: string | null): McpServer {
|
|
14
37
|
const server = new McpServer({
|
|
@@ -203,6 +226,10 @@ Bun.serve({
|
|
|
203
226
|
|
|
204
227
|
// ── V3: Auth endpoints (public) ──
|
|
205
228
|
if (url.pathname === "/api/auth/register" && req.method === "POST") {
|
|
229
|
+
const clientIP = req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || "unknown";
|
|
230
|
+
if (!checkRateLimit(clientIP, 30)) {
|
|
231
|
+
return withCors(req, Response.json({ ok: false, error: "too many requests, try again later" }, { status: 429 }));
|
|
232
|
+
}
|
|
206
233
|
try {
|
|
207
234
|
const body = await req.json() as any;
|
|
208
235
|
const result = register(body.username, body.password, body.email, body.display_name);
|
|
@@ -214,6 +241,11 @@ Bun.serve({
|
|
|
214
241
|
}
|
|
215
242
|
|
|
216
243
|
if (url.pathname === "/api/auth/login" && req.method === "POST") {
|
|
244
|
+
const clientIP = req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || "unknown";
|
|
245
|
+
if (!checkRateLimit(clientIP, 10)) {
|
|
246
|
+
logAudit(null, null, "login_rate_limited", "auth", null, clientIP);
|
|
247
|
+
return withCors(req, Response.json({ ok: false, error: "too many attempts, try again later" }, { status: 429 }));
|
|
248
|
+
}
|
|
217
249
|
try {
|
|
218
250
|
const body = await req.json() as any;
|
|
219
251
|
const result = login(body.username, body.password);
|
package/src/tools.ts
CHANGED
|
@@ -351,6 +351,19 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
|
|
|
351
351
|
},
|
|
352
352
|
async ({ alias, task, priority, context, from_session, ttl_seconds, network_id: netId }) => {
|
|
353
353
|
const effectiveNetId = getNetworkId(netId);
|
|
354
|
+
|
|
355
|
+
// License check
|
|
356
|
+
const license = db.query<any, []>("SELECT type, expires_at FROM licenses ORDER BY created_at LIMIT 1").get();
|
|
357
|
+
if (license?.expires_at) {
|
|
358
|
+
const now = new Date().toISOString().replace("T", " ").slice(0, 19);
|
|
359
|
+
if (license.expires_at < now) {
|
|
360
|
+
return { content: [{ type: "text" as const, text: JSON.stringify({
|
|
361
|
+
ok: false, error: "license_expired",
|
|
362
|
+
message: "Trial expired. Activate a license: anet activate <key>",
|
|
363
|
+
}) }] };
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
354
367
|
console.log(`[${ts()}] ${from_session} → send_task → ${alias}: ${task.slice(0, 60)}${priority === "high" ? " [HIGH]" : ""}`);
|
|
355
368
|
const id = uuidv4();
|
|
356
369
|
// 事务:inbox + tasks 双写
|