@sleep2agi/commhub-server 0.5.0-preview.19 → 0.5.0-preview.20

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/README.md CHANGED
@@ -89,9 +89,20 @@ delivered/acked/running → reassign → delivered (新agent)
89
89
 
90
90
  ## 鉴权
91
91
 
92
- 设置 `COMMHUB_AUTH_TOKEN` 后, 所有端点需要 Bearer token:
93
- - Header: `Authorization: Bearer <token>`
94
- - Query: `?token=<token>`
92
+ 两种认证方式:
93
+ 1. **V3 用户系统** (推荐): `POST /api/auth/register` → 获取 `atok_xxx` token
94
+ 2. **全局 token** (传统): `COMMHUB_AUTH_TOKEN` 环境变量
95
+
96
+ Header: `Authorization: Bearer <token>` 或 Query: `?token=<token>`
97
+
98
+ ## V3 功能
99
+
100
+ - **用户系统**: 注册/登录/Token 认证
101
+ - **多网络**: 每个用户可创建多个独立网络
102
+ - **网络隔离**: 不同网络的数据完全隔离
103
+ - **试用授权**: 14 天免费试用, 到期需授权码
104
+ - **审计日志**: 所有操作记录
105
+ - **限流**: 注册 30/min, 登录 10/min (per IP)
95
106
  - `/health` 不需要 auth
96
107
 
97
108
  ## License
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleep2agi/commhub-server",
3
- "version": "0.5.0-preview.19",
3
+ "version": "0.5.0-preview.20",
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/auth.ts CHANGED
@@ -132,3 +132,12 @@ export function createNetwork(userId: string, name: string, description?: string
132
132
  );
133
133
  return { ok: true, network_id: networkId, network_name: name };
134
134
  }
135
+
136
+ export function changePassword(userId: string, oldPassword: string, newPassword: string): { ok: boolean; error?: string } {
137
+ if (!newPassword || newPassword.length < 6) return { ok: false, error: "new password must be at least 6 characters" };
138
+ const user = db.query<any, [string]>("SELECT password_hash FROM users WHERE user_id = ?1").get(userId);
139
+ if (!user) return { ok: false, error: "user not found" };
140
+ if (user.password_hash !== hashPassword(oldPassword)) return { ok: false, error: "incorrect current password" };
141
+ db.run("UPDATE users SET password_hash = ?1, updated_at = datetime('now') WHERE user_id = ?2", [hashPassword(newPassword), userId]);
142
+ return { ok: true };
143
+ }
package/src/index.ts CHANGED
@@ -4,7 +4,7 @@ import { z } from "zod/v4";
4
4
  import { registerTools } from "./tools.js";
5
5
  import { db, logTaskEvent, logAudit } from "./db.js";
6
6
  import { createSSEStream, pushEvent, pushBroadcast, getSSEStats } from "./push.js";
7
- import { register, login, resolveToken, getUserNetworks, createNetwork, type AuthUser } from "./auth.js";
7
+ import { register, login, resolveToken, getUserNetworks, createNetwork, changePassword, type AuthUser } from "./auth.js";
8
8
 
9
9
  const PORT = Number(process.env.PORT) || 9200;
10
10
  const AUTH_TOKEN = process.env.COMMHUB_AUTH_TOKEN;
@@ -290,6 +290,21 @@ Bun.serve({
290
290
  }
291
291
  }
292
292
 
293
+ if (url.pathname === "/api/auth/password" && req.method === "POST") {
294
+ const token = req.headers.get("Authorization")?.replace("Bearer ", "");
295
+ if (!token) return withCors(req, Response.json({ ok: false, error: "token required" }, { status: 401 }));
296
+ const resolved = resolveToken(token);
297
+ if (!resolved) return withCors(req, Response.json({ ok: false, error: "invalid token" }, { status: 401 }));
298
+ try {
299
+ const body = await req.json() as any;
300
+ const result = changePassword(resolved.user.user_id, body.old_password, body.new_password);
301
+ if (result.ok) logAudit(resolved.user.user_id, resolved.user.username, "password_changed", "user", resolved.user.user_id);
302
+ return withCors(req, Response.json(result, { status: result.ok ? 200 : 400 }));
303
+ } catch (e: any) {
304
+ return withCors(req, Response.json({ ok: false, error: e.message }, { status: 400 }));
305
+ }
306
+ }
307
+
293
308
  // ── V3: Network management ──
294
309
  if (url.pathname === "/api/networks" && req.method === "GET") {
295
310
  const token = req.headers.get("Authorization")?.replace("Bearer ", "") || url.searchParams.get("token");