@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleep2agi/commhub-server",
3
- "version": "0.5.0-preview.17",
3
+ "version": "0.5.0-preview.19",
4
4
  "description": "CommHub MCP Server — AI Agent communication hub with SSE push, MCP protocol, and REST API",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
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 双写