@musnows/scriverse 0.2.0 → 0.3.0

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.
Files changed (70) hide show
  1. package/README.en.md +13 -4
  2. package/README.md +13 -4
  3. package/dist/ai.js +3374 -0
  4. package/dist/ai.js.map +1 -0
  5. package/dist/app.js +1046 -0
  6. package/dist/app.js.map +1 -0
  7. package/dist/cli-core.js +147 -20
  8. package/dist/cli-core.js.map +1 -1
  9. package/dist/credential-vault.js +40 -0
  10. package/dist/credential-vault.js.map +1 -0
  11. package/dist/database.js +1122 -0
  12. package/dist/database.js.map +1 -0
  13. package/dist/docx-security.js +184 -0
  14. package/dist/docx-security.js.map +1 -0
  15. package/dist/domain.js +21 -0
  16. package/dist/domain.js.map +1 -0
  17. package/dist/errors.js +16 -0
  18. package/dist/errors.js.map +1 -0
  19. package/dist/image-captcha.js +173 -0
  20. package/dist/image-captcha.js.map +1 -0
  21. package/dist/image-metadata.js +128 -0
  22. package/dist/image-metadata.js.map +1 -0
  23. package/dist/import-security.js +46 -0
  24. package/dist/import-security.js.map +1 -0
  25. package/dist/parser.js +223 -0
  26. package/dist/parser.js.map +1 -0
  27. package/dist/public/ai-context-meter.js +10 -0
  28. package/dist/public/ai-conversation.js +3 -0
  29. package/dist/public/ai-mentions.js +55 -0
  30. package/dist/public/ai-message-actions.js +27 -0
  31. package/dist/public/ai-message-meta.js +15 -0
  32. package/dist/public/ai-message-time.js +23 -0
  33. package/dist/public/ai-prompt-keyboard.js +3 -0
  34. package/dist/public/app.js +4255 -0
  35. package/dist/public/character-profile.d.ts +9 -0
  36. package/dist/public/character-profile.js +65 -0
  37. package/dist/public/character-version.d.ts +2 -0
  38. package/dist/public/character-version.js +31 -0
  39. package/dist/public/entity-version.js +34 -0
  40. package/dist/public/icon.svg +10 -0
  41. package/dist/public/index.html +547 -0
  42. package/dist/public/line-number-layout.js +15 -0
  43. package/dist/public/markdown.js +199 -0
  44. package/dist/public/model-config.d.ts +17 -0
  45. package/dist/public/model-config.js +57 -0
  46. package/dist/public/page-route.d.ts +11 -0
  47. package/dist/public/page-route.js +81 -0
  48. package/dist/public/relationship-graph.js +2017 -0
  49. package/dist/public/site.webmanifest +17 -0
  50. package/dist/public/styles.css +1399 -0
  51. package/dist/public/text-formatting.d.ts +2 -0
  52. package/dist/public/text-formatting.js +20 -0
  53. package/dist/public/theme-init.js +8 -0
  54. package/dist/public/theme.js +13 -0
  55. package/dist/public/whitespace-visualization.js +20 -0
  56. package/dist/request-context.js +9 -0
  57. package/dist/request-context.js.map +1 -0
  58. package/dist/security.js +235 -0
  59. package/dist/security.js.map +1 -0
  60. package/dist/server-runtime.js +77 -0
  61. package/dist/server-runtime.js.map +1 -0
  62. package/dist/server.js +15 -0
  63. package/dist/server.js.map +1 -0
  64. package/dist/store.js +2492 -0
  65. package/dist/store.js.map +1 -0
  66. package/dist/user-auth.js +489 -0
  67. package/dist/user-auth.js.map +1 -0
  68. package/dist/utils.js +50 -0
  69. package/dist/utils.js.map +1 -0
  70. package/package.json +4 -9
@@ -0,0 +1,489 @@
1
+ import { createHash, randomBytes, randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
2
+ import { PLATFORM_AI_WORK_ID } from "./database.js";
3
+ import { AppError, notFound } from "./errors.js";
4
+ import { runWithRequestActor } from "./request-context.js";
5
+ const sessionCookieName = "scriverse_session";
6
+ const sessionLifetimeMs = 30 * 24 * 60 * 60_000;
7
+ const apiKeyPrefix = "scrv_";
8
+ function sha256(value) {
9
+ return createHash("sha256").update(value).digest("hex");
10
+ }
11
+ function safeEqual(left, right) {
12
+ const leftBuffer = Buffer.from(left);
13
+ const rightBuffer = Buffer.from(right);
14
+ return leftBuffer.length === rightBuffer.length && timingSafeEqual(leftBuffer, rightBuffer);
15
+ }
16
+ function normalizeUsername(value) {
17
+ return value.normalize("NFKC").trim().toLocaleLowerCase("zh-CN");
18
+ }
19
+ function parseCookies(header) {
20
+ const result = new Map();
21
+ for (const part of header?.split(";") ?? []) {
22
+ const separator = part.indexOf("=");
23
+ if (separator < 0)
24
+ continue;
25
+ const name = part.slice(0, separator).trim();
26
+ try {
27
+ result.set(name, decodeURIComponent(part.slice(separator + 1).trim()));
28
+ }
29
+ catch {
30
+ // 无效 Cookie 不参与身份验证。
31
+ }
32
+ }
33
+ return result;
34
+ }
35
+ function apiKeyCredential(request) {
36
+ const direct = request.get("x-scriverse-api-key")?.trim();
37
+ if (direct)
38
+ return direct;
39
+ const authorization = request.get("authorization");
40
+ if (!authorization?.startsWith("Bearer "))
41
+ return null;
42
+ return authorization.slice(7).trim();
43
+ }
44
+ function mapUser(row) {
45
+ const avatarSha256 = row.avatar_sha256 === null || row.avatar_sha256 === undefined
46
+ ? null
47
+ : String(row.avatar_sha256);
48
+ return {
49
+ userId: String(row.id),
50
+ username: String(row.username),
51
+ displayName: String(row.display_name),
52
+ role: String(row.role) === "admin" ? "admin" : "user",
53
+ status: String(row.status) === "disabled" ? "disabled" : "active",
54
+ createdAt: String(row.created_at),
55
+ avatarUrl: avatarSha256
56
+ ? `/api/user-avatars/${encodeURIComponent(String(row.id))}?v=${encodeURIComponent(avatarSha256)}`
57
+ : null
58
+ };
59
+ }
60
+ function passwordDigest(password, salt) {
61
+ return scryptSync(password, salt, 64, { N: 16_384, r: 8, p: 1, maxmem: 64 * 1024 * 1024 }).toString("base64");
62
+ }
63
+ function workIdFromPath(database, pathname) {
64
+ const decoded = pathname.split("/").map((part) => {
65
+ try {
66
+ return decodeURIComponent(part);
67
+ }
68
+ catch {
69
+ return part;
70
+ }
71
+ });
72
+ if (decoded[1] !== "api")
73
+ return null;
74
+ if (decoded[2] === "works" && decoded[3] && decoded[3] !== "import")
75
+ return decoded[3];
76
+ const tableByResource = {
77
+ volumes: "volumes",
78
+ chapters: "chapters",
79
+ settings: "settings",
80
+ characters: "characters",
81
+ races: "races",
82
+ organizations: "organizations",
83
+ "timeline-tracks": "timeline_tracks",
84
+ timeline: "timeline_events",
85
+ relationships: "relationships",
86
+ foreshadows: "foreshadows",
87
+ reviews: "review_items",
88
+ tasks: "analysis_tasks",
89
+ "ai-conversations": "ai_conversations",
90
+ suggestions: "ai_suggestions"
91
+ };
92
+ const table = tableByResource[decoded[2] ?? ""];
93
+ if (table && decoded[3]) {
94
+ const row = database.get(`SELECT work_id FROM ${table} WHERE id = ?`, decoded[3]);
95
+ if (row)
96
+ return row.work_id;
97
+ if (table === "chapters") {
98
+ const version = database.get("SELECT work_id FROM chapter_versions WHERE chapter_id = ? LIMIT 1", decoded[3]);
99
+ if (version)
100
+ return version.work_id;
101
+ }
102
+ if (table === "characters") {
103
+ const version = database.get("SELECT work_id FROM character_versions WHERE character_id = ? LIMIT 1", decoded[3]);
104
+ if (version)
105
+ return version.work_id;
106
+ }
107
+ throw notFound("记录");
108
+ }
109
+ if (decoded[2] === "foreshadow-occurrences" && decoded[3]) {
110
+ const row = database.get(`SELECT foreshadow.work_id FROM foreshadow_occurrences occurrence
111
+ JOIN foreshadows foreshadow ON foreshadow.id = occurrence.foreshadow_id WHERE occurrence.id = ?`, decoded[3]);
112
+ if (!row)
113
+ throw notFound("伏笔出现点");
114
+ return row.work_id;
115
+ }
116
+ if (decoded[2] === "entity-versions" && decoded[3] && decoded[4]) {
117
+ const row = database.get("SELECT work_id FROM entity_versions WHERE entity_type = ? AND entity_id = ? LIMIT 1", decoded[3], decoded[4]);
118
+ if (!row)
119
+ throw notFound("版本记录");
120
+ return row.work_id;
121
+ }
122
+ return null;
123
+ }
124
+ export class UserAuthService {
125
+ database;
126
+ constructor(database) {
127
+ this.database = database;
128
+ }
129
+ resolveWorkId(pathname) {
130
+ return workIdFromPath(this.database, pathname);
131
+ }
132
+ hasUsers() {
133
+ return Number(this.database.get("SELECT COUNT(*) AS count FROM users")?.count ?? 0) > 0;
134
+ }
135
+ createSession(userId) {
136
+ const token = randomBytes(32).toString("base64url");
137
+ const csrfToken = randomBytes(24).toString("base64url");
138
+ const sessionId = randomUUID();
139
+ const timestamp = new Date();
140
+ this.database.run(`INSERT INTO user_sessions (id, user_id, token_hash, csrf_token, created_at, expires_at, last_seen_at)
141
+ VALUES (?, ?, ?, ?, ?, ?, ?)`, sessionId, userId, sha256(token), csrfToken, timestamp.toISOString(), new Date(timestamp.getTime() + sessionLifetimeMs).toISOString(), timestamp.toISOString());
142
+ const user = this.getUser(userId);
143
+ return { token, session: { id: sessionId, user, csrfToken } };
144
+ }
145
+ register(input) {
146
+ const normalizedUsername = normalizeUsername(input.username);
147
+ const timestamp = new Date().toISOString();
148
+ const userId = randomUUID();
149
+ const salt = randomBytes(16).toString("base64url");
150
+ return this.database.transaction(() => {
151
+ const role = Number(this.database.get("SELECT COUNT(*) AS count FROM users")?.count ?? 0) === 0 ? "admin" : "user";
152
+ this.database.run(`INSERT INTO users (id, username, normalized_username, display_name, password_hash, password_salt, role, status, created_at, updated_at)
153
+ VALUES (?, ?, ?, ?, ?, ?, ?, 'active', ?, ?)`, userId, input.username.trim(), normalizedUsername, input.username.trim(), passwordDigest(input.password, salt), salt, role, timestamp, timestamp);
154
+ if (role === "admin") {
155
+ this.database.run("UPDATE works SET owner_user_id = ? WHERE owner_user_id IS NULL AND id <> ?", userId, PLATFORM_AI_WORK_ID);
156
+ this.database.run(`INSERT OR IGNORE INTO work_memberships (work_id, user_id, role, invited_by_user_id, created_at)
157
+ SELECT id, ?, 'owner', ?, ? FROM works WHERE owner_user_id = ? AND id <> ?`, userId, userId, timestamp, userId, PLATFORM_AI_WORK_ID);
158
+ }
159
+ return this.createSession(userId);
160
+ });
161
+ }
162
+ login(username, password) {
163
+ const row = this.database.get("SELECT * FROM users WHERE normalized_username = ?", normalizeUsername(username));
164
+ const fallbackSalt = "invalid-login-salt";
165
+ const calculated = passwordDigest(password, String(row?.password_salt ?? fallbackSalt));
166
+ const valid = row && safeEqual(calculated, String(row.password_hash));
167
+ if (!valid)
168
+ throw new AppError(401, "INVALID_CREDENTIALS", "用户名或密码不正确");
169
+ const user = mapUser(row);
170
+ if (user.status !== "active")
171
+ throw new AppError(403, "ACCOUNT_DISABLED", "该账户已被停用");
172
+ this.database.run("UPDATE users SET last_login_at = ?, updated_at = ? WHERE id = ?", new Date().toISOString(), new Date().toISOString(), user.userId);
173
+ return this.createSession(user.userId);
174
+ }
175
+ authenticate(request) {
176
+ const token = parseCookies(request.get("cookie")).get(sessionCookieName);
177
+ if (!token)
178
+ return null;
179
+ const row = this.database.get(`SELECT session.id AS session_id, session.csrf_token, session.expires_at, session.revoked_at,
180
+ user.* FROM user_sessions session JOIN users user ON user.id = session.user_id WHERE session.token_hash = ?`, sha256(token));
181
+ if (!row || row.revoked_at || String(row.expires_at) <= new Date().toISOString())
182
+ return null;
183
+ const user = mapUser(row);
184
+ if (user.status !== "active")
185
+ return null;
186
+ this.database.run("UPDATE user_sessions SET last_seen_at = ? WHERE id = ?", new Date().toISOString(), String(row.session_id));
187
+ return { id: String(row.session_id), csrfToken: String(row.csrf_token), user };
188
+ }
189
+ authenticateApiKey(request) {
190
+ const key = apiKeyCredential(request);
191
+ if (!key || !key.startsWith(apiKeyPrefix) || key.length > 200)
192
+ return null;
193
+ const row = this.database.get(`SELECT api_key.key_prefix, user.* FROM user_api_keys api_key
194
+ JOIN users user ON user.id = api_key.user_id WHERE api_key.key_hash = ?`, sha256(key));
195
+ if (!row)
196
+ return null;
197
+ const user = mapUser(row);
198
+ if (user.status !== "active")
199
+ return null;
200
+ this.database.run("UPDATE user_api_keys SET last_used_at = ? WHERE user_id = ?", new Date().toISOString(), user.userId);
201
+ return { user, prefix: String(row.key_prefix) };
202
+ }
203
+ hasApiKeyCredential(request) {
204
+ return apiKeyCredential(request) !== null;
205
+ }
206
+ getApiKeyStatus(userId) {
207
+ this.getUser(userId);
208
+ const row = this.database.get("SELECT * FROM user_api_keys WHERE user_id = ?", userId);
209
+ if (!row) {
210
+ return { configured: false, prefix: null, createdAt: null, rotatedAt: null, lastUsedAt: null };
211
+ }
212
+ return {
213
+ configured: true,
214
+ prefix: String(row.key_prefix),
215
+ createdAt: String(row.created_at),
216
+ rotatedAt: String(row.rotated_at),
217
+ lastUsedAt: row.last_used_at === null ? null : String(row.last_used_at)
218
+ };
219
+ }
220
+ resetApiKey(userId) {
221
+ this.getUser(userId);
222
+ const apiKey = `${apiKeyPrefix}${randomBytes(32).toString("base64url")}`;
223
+ const prefix = apiKey.slice(0, 13);
224
+ const timestamp = new Date().toISOString();
225
+ this.database.run(`INSERT INTO user_api_keys (user_id, key_hash, key_prefix, created_at, rotated_at, last_used_at)
226
+ VALUES (?, ?, ?, ?, ?, NULL)
227
+ ON CONFLICT(user_id) DO UPDATE SET
228
+ key_hash = excluded.key_hash,
229
+ key_prefix = excluded.key_prefix,
230
+ rotated_at = excluded.rotated_at,
231
+ last_used_at = NULL`, userId, sha256(apiKey), prefix, timestamp, timestamp);
232
+ return { ...this.getApiKeyStatus(userId), apiKey };
233
+ }
234
+ revoke(sessionId) {
235
+ this.database.run("UPDATE user_sessions SET revoked_at = ? WHERE id = ?", new Date().toISOString(), sessionId);
236
+ }
237
+ getUser(userId) {
238
+ const row = this.database.get("SELECT * FROM users WHERE id = ?", userId);
239
+ if (!row)
240
+ throw notFound("用户");
241
+ return mapUser(row);
242
+ }
243
+ listUsers() {
244
+ return this.database.all("SELECT * FROM users ORDER BY created_at, username").map(mapUser);
245
+ }
246
+ directory(query) {
247
+ const escapedQuery = query.trim().slice(0, 100).replace(/[\\%_]/gu, (character) => `\\${character}`);
248
+ const pattern = `%${escapedQuery}%`;
249
+ return this.database.all(`SELECT * FROM users WHERE status = 'active' AND (username LIKE ? ESCAPE '\\' OR display_name LIKE ? ESCAPE '\\')
250
+ ORDER BY username LIMIT 50`, pattern, pattern).map((row) => {
251
+ const user = mapUser(row);
252
+ return { userId: user.userId, username: user.username, displayName: user.displayName, avatarUrl: user.avatarUrl };
253
+ });
254
+ }
255
+ updateUser(actor, userId, input) {
256
+ const current = this.getUser(userId);
257
+ const nextRole = input.role ?? current.role;
258
+ const nextStatus = input.status ?? current.status;
259
+ if (actor.userId === userId && (nextRole !== "admin" || nextStatus !== "active")) {
260
+ throw new AppError(409, "CANNOT_DISABLE_SELF", "不能停用自己或移除自己的管理员身份");
261
+ }
262
+ if (current.role === "admin" && current.status === "active" && (nextRole !== "admin" || nextStatus !== "active")) {
263
+ const activeAdmins = Number(this.database.get("SELECT COUNT(*) AS count FROM users WHERE role = 'admin' AND status = 'active'")?.count ?? 0);
264
+ if (activeAdmins <= 1)
265
+ throw new AppError(409, "LAST_ADMIN_REQUIRED", "系统至少需要保留一名可用管理员");
266
+ }
267
+ this.database.run("UPDATE users SET role = ?, status = ?, updated_at = ? WHERE id = ?", nextRole, nextStatus, new Date().toISOString(), userId);
268
+ if (nextStatus === "disabled")
269
+ this.database.run("UPDATE user_sessions SET revoked_at = ? WHERE user_id = ? AND revoked_at IS NULL", new Date().toISOString(), userId);
270
+ return this.getUser(userId);
271
+ }
272
+ updateProfile(userId, displayName) {
273
+ this.database.run("UPDATE users SET display_name = ?, updated_at = ? WHERE id = ?", displayName.trim(), new Date().toISOString(), userId);
274
+ return this.getUser(userId);
275
+ }
276
+ setAvatar(userId, input) {
277
+ this.getUser(userId);
278
+ const timestamp = new Date().toISOString();
279
+ const digest = createHash("sha256").update(input.content).digest("hex");
280
+ this.database.transaction(() => {
281
+ this.database.run(`INSERT INTO user_avatars (user_id, mime_type, content, byte_length, sha256, width, height, updated_at)
282
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
283
+ ON CONFLICT(user_id) DO UPDATE SET mime_type = excluded.mime_type, content = excluded.content,
284
+ byte_length = excluded.byte_length, sha256 = excluded.sha256, width = excluded.width,
285
+ height = excluded.height, updated_at = excluded.updated_at`, userId, input.mimeType, input.content, input.content.byteLength, digest, input.width, input.height, timestamp);
286
+ this.database.run("UPDATE users SET avatar_updated_at = ?, avatar_sha256 = ?, updated_at = ? WHERE id = ?", timestamp, digest, timestamp, userId);
287
+ });
288
+ return this.getUser(userId);
289
+ }
290
+ getAvatar(userId) {
291
+ this.getUser(userId);
292
+ const row = this.database.get("SELECT * FROM user_avatars WHERE user_id = ?", userId);
293
+ if (!row)
294
+ throw notFound("用户头像");
295
+ return {
296
+ mimeType: String(row.mime_type),
297
+ content: Buffer.from(row.content),
298
+ byteLength: Number(row.byte_length),
299
+ sha256: String(row.sha256),
300
+ width: Number(row.width),
301
+ height: Number(row.height),
302
+ updatedAt: String(row.updated_at)
303
+ };
304
+ }
305
+ deleteAvatar(userId) {
306
+ this.getUser(userId);
307
+ this.database.transaction(() => {
308
+ this.database.run("DELETE FROM user_avatars WHERE user_id = ?", userId);
309
+ this.database.run("UPDATE users SET avatar_updated_at = NULL, avatar_sha256 = NULL, updated_at = ? WHERE id = ?", new Date().toISOString(), userId);
310
+ });
311
+ return this.getUser(userId);
312
+ }
313
+ changePassword(userId, sessionId, currentPassword, newPassword) {
314
+ const row = this.database.get("SELECT * FROM users WHERE id = ?", userId);
315
+ if (!row)
316
+ throw notFound("用户");
317
+ const calculated = passwordDigest(currentPassword, String(row.password_salt));
318
+ if (!safeEqual(calculated, String(row.password_hash)))
319
+ throw new AppError(401, "INVALID_CURRENT_PASSWORD", "当前密码不正确");
320
+ const salt = randomBytes(16).toString("base64url");
321
+ const timestamp = new Date().toISOString();
322
+ this.database.transaction(() => {
323
+ this.database.run("UPDATE users SET password_hash = ?, password_salt = ?, updated_at = ? WHERE id = ?", passwordDigest(newPassword, salt), salt, timestamp, userId);
324
+ this.database.run("UPDATE user_sessions SET revoked_at = ? WHERE user_id = ? AND id <> ? AND revoked_at IS NULL", timestamp, userId, sessionId);
325
+ });
326
+ }
327
+ listMembers(workId) {
328
+ return this.database.all(`SELECT membership.role, membership.created_at, user.id, user.username, user.display_name, user.status, user.avatar_sha256
329
+ FROM work_memberships membership JOIN users user ON user.id = membership.user_id
330
+ WHERE membership.work_id = ? ORDER BY CASE membership.role WHEN 'owner' THEN 0 ELSE 1 END, user.username`, workId).map((row) => ({
331
+ userId: String(row.id), username: String(row.username), displayName: String(row.display_name),
332
+ role: String(row.role), status: String(row.status), createdAt: String(row.created_at),
333
+ avatarUrl: row.avatar_sha256
334
+ ? `/api/user-avatars/${encodeURIComponent(String(row.id))}?v=${encodeURIComponent(String(row.avatar_sha256))}`
335
+ : null
336
+ }));
337
+ }
338
+ addMember(workId, userId, invitedByUserId) {
339
+ const user = this.getUser(userId);
340
+ if (user.status !== "active")
341
+ throw new AppError(409, "USER_DISABLED", "不能邀请已停用用户");
342
+ this.database.run(`INSERT INTO work_memberships (work_id, user_id, role, invited_by_user_id, created_at)
343
+ VALUES (?, ?, 'editor', ?, ?) ON CONFLICT(work_id, user_id) DO UPDATE SET role = 'editor', invited_by_user_id = excluded.invited_by_user_id`, workId, userId, invitedByUserId, new Date().toISOString());
344
+ return this.listMembers(workId);
345
+ }
346
+ removeMember(workId, userId) {
347
+ const work = this.database.get("SELECT owner_user_id FROM works WHERE id = ?", workId);
348
+ if (!work)
349
+ throw notFound("作品");
350
+ if (String(work.owner_user_id ?? "") === userId)
351
+ throw new AppError(409, "OWNER_REQUIRED", "不能移除作品创建者");
352
+ this.database.run("DELETE FROM work_memberships WHERE work_id = ? AND user_id = ?", workId, userId);
353
+ return this.listMembers(workId);
354
+ }
355
+ workRole(user, workId, allowAdminAccess = true) {
356
+ if (allowAdminAccess && user.role === "admin")
357
+ return "admin";
358
+ const work = this.database.get("SELECT owner_user_id FROM works WHERE id = ?", workId);
359
+ if (!work)
360
+ throw notFound("作品");
361
+ if (String(work.owner_user_id ?? "") === user.userId)
362
+ return "owner";
363
+ const membership = this.database.get("SELECT role FROM work_memberships WHERE work_id = ? AND user_id = ?", workId, user.userId);
364
+ return String(membership?.role ?? "") === "editor" ? "editor" : null;
365
+ }
366
+ assertWorkAccess(user, workId, write = false, ownerOnly = false, allowAdminAccess = true) {
367
+ if (workId === PLATFORM_AI_WORK_ID && (!allowAdminAccess || user.role !== "admin"))
368
+ throw new AppError(403, "ADMIN_REQUIRED", "该操作仅限系统管理员");
369
+ const role = this.workRole(user, workId, allowAdminAccess);
370
+ if (!role)
371
+ throw new AppError(403, "WORK_ACCESS_DENIED", "你没有访问这部作品的权限");
372
+ if (ownerOnly && role !== "admin" && role !== "owner")
373
+ throw new AppError(403, "WORK_OWNER_REQUIRED", "该操作仅限作品创建者或系统管理员");
374
+ if (write && !["admin", "owner", "editor"].includes(role))
375
+ throw new AppError(403, "WORK_EDIT_DENIED", "你没有编辑这部作品的权限");
376
+ }
377
+ }
378
+ export function setSessionCookie(response, token, secure) {
379
+ response.cookie(sessionCookieName, token, {
380
+ httpOnly: true,
381
+ sameSite: "lax",
382
+ secure,
383
+ path: "/",
384
+ maxAge: sessionLifetimeMs
385
+ });
386
+ }
387
+ export function clearSessionCookie(response, secure) {
388
+ response.clearCookie(sessionCookieName, { httpOnly: true, sameSite: "lax", secure, path: "/" });
389
+ }
390
+ export function createUserSessionMiddleware(auth, disabled = false) {
391
+ return (request, response, next) => {
392
+ if (disabled)
393
+ return runWithRequestActor(null, next);
394
+ const apiKey = auth.authenticateApiKey(request);
395
+ if (auth.hasApiKeyCredential(request) && !apiKey) {
396
+ response.status(401).json({ error: { code: "API_KEY_INVALID", message: "API Key 无效或已失效" } });
397
+ return;
398
+ }
399
+ const session = apiKey ? null : auth.authenticate(request);
400
+ if (apiKey) {
401
+ request.authApiKey = apiKey;
402
+ request.authUser = apiKey.user;
403
+ request.authMethod = "api-key";
404
+ }
405
+ else if (session) {
406
+ request.authSession = session;
407
+ request.authUser = session.user;
408
+ request.authMethod = "session";
409
+ }
410
+ const isPublic = request.path === "/api/health"
411
+ || request.path === "/api/auth/session"
412
+ || (request.path === "/api/auth/register" && request.method === "POST")
413
+ || (request.path === "/api/auth/login" && request.method === "POST")
414
+ || !request.path.startsWith("/api/");
415
+ if (!session && !apiKey && !isPublic) {
416
+ response.status(401).json({ error: { code: "AUTH_REQUIRED", message: "请先登录" } });
417
+ return;
418
+ }
419
+ if (session && !["GET", "HEAD", "OPTIONS"].includes(request.method) && request.path !== "/api/auth/login" && request.path !== "/api/auth/register") {
420
+ const csrf = request.get("x-csrf-token") ?? "";
421
+ if (!safeEqual(csrf, session.csrfToken)) {
422
+ response.status(403).json({ error: { code: "CSRF_TOKEN_INVALID", message: "请求校验失败,请刷新页面后重试" } });
423
+ return;
424
+ }
425
+ }
426
+ const user = apiKey?.user ?? session?.user ?? null;
427
+ return runWithRequestActor(user ? { ...user, authentication: apiKey ? "api-key" : "session" } : null, next);
428
+ };
429
+ }
430
+ const cliApiRules = [
431
+ { methods: ["GET"], path: /^\/api\/cli\/session$/u },
432
+ { methods: ["GET", "POST"], path: /^\/api\/works$/u },
433
+ { methods: ["GET", "PATCH"], path: /^\/api\/works\/[^/]+$/u },
434
+ { methods: ["GET"], path: /^\/api\/works\/[^/]+\/(?:outlines|foreshadows|settings|characters|races|organizations|timeline-tracks|timeline|relationships|search|export|audit-logs)$/u },
435
+ { methods: ["POST"], path: /^\/api\/works\/[^/]+\/(?:volumes|chapters|foreshadows|settings|characters|races|organizations|timeline-tracks|timeline|relationships)$/u },
436
+ { methods: ["GET", "PATCH"], path: /^\/api\/volumes\/[^/]+$/u },
437
+ { methods: ["GET", "PATCH"], path: /^\/api\/chapters\/[^/]+$/u },
438
+ { methods: ["GET"], path: /^\/api\/chapters\/[^/]+\/(?:versions|outline)$/u },
439
+ { methods: ["POST"], path: /^\/api\/chapters\/[^/]+\/(?:restore|move)$/u },
440
+ { methods: ["PUT"], path: /^\/api\/chapters\/[^/]+\/outline$/u },
441
+ { methods: ["GET", "PATCH"], path: /^\/api\/(?:settings|characters|races|organizations|timeline-tracks|timeline|relationships|foreshadows)\/[^/]+$/u },
442
+ { methods: ["GET"], path: /^\/api\/characters\/[^/]+\/versions$/u },
443
+ { methods: ["POST"], path: /^\/api\/characters\/[^/]+\/restore$/u },
444
+ { methods: ["GET"], path: /^\/api\/entity-versions\/[^/]+\/[^/]+$/u },
445
+ { methods: ["POST"], path: /^\/api\/entity-versions\/[^/]+\/[^/]+\/restore$/u }
446
+ ];
447
+ export function createCliApiScopeMiddleware(disabled = false) {
448
+ return (request, response, next) => {
449
+ if (disabled || request.authMethod !== "api-key")
450
+ return next();
451
+ if (cliApiRules.some((rule) => rule.methods.includes(request.method) && rule.path.test(request.path)))
452
+ return next();
453
+ response.status(403).json({ error: { code: "CLI_SCOPE_DENIED", message: "API Key 不能访问用户管理、系统管理或未开放给 CLI 的接口" } });
454
+ };
455
+ }
456
+ export function createWorkAuthorizationMiddleware(auth, disabled = false) {
457
+ return (request, _response, next) => {
458
+ if (disabled || !request.path.startsWith("/api/") || request.path.startsWith("/api/auth/") || request.path === "/api/health")
459
+ return next();
460
+ const user = request.authUser;
461
+ if (!user)
462
+ throw new AppError(401, "AUTH_REQUIRED", "请先登录");
463
+ if (request.path.startsWith("/api/platform/") || request.path.startsWith("/api/providers/") || request.path.startsWith("/api/models/")) {
464
+ if (user.role !== "admin")
465
+ throw new AppError(403, "ADMIN_REQUIRED", "该操作仅限系统管理员");
466
+ return next();
467
+ }
468
+ if (/^\/api\/works\/[^/]+\/providers/u.test(request.path)) {
469
+ if (user.role !== "admin")
470
+ throw new AppError(403, "ADMIN_REQUIRED", "该操作仅限系统管理员");
471
+ return next();
472
+ }
473
+ if (request.path.startsWith("/api/users") && !request.path.startsWith("/api/users/directory")) {
474
+ if (user.role !== "admin")
475
+ throw new AppError(403, "ADMIN_REQUIRED", "该操作仅限系统管理员");
476
+ return next();
477
+ }
478
+ const workId = auth.resolveWorkId(request.path);
479
+ if (!workId)
480
+ return next();
481
+ const write = !["GET", "HEAD", "OPTIONS"].includes(request.method);
482
+ const ownerOnly = write && (/^\/api\/works\/[^/]+$/u.test(request.path)
483
+ || /^\/api\/works\/[^/]+\/cover$/u.test(request.path)
484
+ || /^\/api\/works\/[^/]+\/members(?:\/[^/]+)?$/u.test(request.path));
485
+ auth.assertWorkAccess(user, workId, write, ownerOnly, request.authMethod !== "api-key");
486
+ next();
487
+ };
488
+ }
489
+ //# sourceMappingURL=user-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-auth.js","sourceRoot":"","sources":["../src/user-auth.ts"],"names":[],"mappings":"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"}
package/dist/utils.js ADDED
@@ -0,0 +1,50 @@
1
+ import { randomUUID } from "node:crypto";
2
+ export function id(prefix) {
3
+ return `${prefix}_${randomUUID().replaceAll("-", "")}`;
4
+ }
5
+ export function now() {
6
+ return new Date().toISOString();
7
+ }
8
+ export function countWords(text) {
9
+ const compact = text.replace(/\s+/gu, "");
10
+ const chinese = compact.match(/[\p{Script=Han}]/gu)?.length ?? 0;
11
+ const withoutChinese = text.replace(/[\p{Script=Han}]/gu, " ");
12
+ const latin = withoutChinese.match(/[\p{L}\p{N}]+/gu)?.length ?? 0;
13
+ return chinese + latin;
14
+ }
15
+ export function normalizeParagraphSpacing(value) {
16
+ return value
17
+ .replace(/\r\n?/gu, "\n")
18
+ .replace(/[\t\p{Zs}\uFEFF]+$/gmu, "")
19
+ .replace(/^\n+/u, "")
20
+ .replace(/\n+$/u, "")
21
+ .replace(/\n{3,}/gu, "\n\n");
22
+ }
23
+ export function json(value, fallback) {
24
+ if (!value)
25
+ return fallback;
26
+ try {
27
+ return JSON.parse(value);
28
+ }
29
+ catch {
30
+ return fallback;
31
+ }
32
+ }
33
+ export function normalizeBaseUrl(value) {
34
+ return value.replace(/\/+$/u, "").replace(/\/chat\/completions$/u, "");
35
+ }
36
+ export function normalizeUploadFileName(value) {
37
+ if ([...value].some((character) => character.codePointAt(0) > 0xff))
38
+ return value;
39
+ const decoded = Buffer.from(value, "latin1").toString("utf8");
40
+ return decoded.includes("�") ? value : decoded;
41
+ }
42
+ export function maskSecret(secret) {
43
+ if (secret.length <= 8)
44
+ return "********";
45
+ return `${secret.slice(0, 3)}${"*".repeat(Math.min(12, secret.length - 7))}${secret.slice(-4)}`;
46
+ }
47
+ export function clamp(value, min, max) {
48
+ return Math.min(max, Math.max(min, value));
49
+ }
50
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,UAAU,EAAE,CAAC,MAAc;IAC/B,OAAO,GAAG,MAAM,IAAI,UAAU,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,GAAG;IACjB,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC;IACjE,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,GAAG,CAAC,CAAC;IAC/D,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC;IACnE,OAAO,OAAO,GAAG,KAAK,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,KAAa;IACrD,OAAO,KAAK;SACT,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC;SACxB,OAAO,CAAC,uBAAuB,EAAE,EAAE,CAAC;SACpC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;SACpB,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;SACpB,OAAO,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,IAAI,CAAI,KAAgC,EAAE,QAAW;IACnE,IAAI,CAAC,KAAK;QAAE,OAAO,QAAQ,CAAC;IAC5B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAM,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,OAAO,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,uBAAuB,EAAE,EAAE,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,KAAa;IACnD,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAE,GAAG,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACnF,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC9D,OAAO,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,UAAU,CAAC;IAC1C,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAClG,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,KAAa,EAAE,GAAW,EAAE,GAAW;IAC3D,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;AAC7C,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@musnows/scriverse",
3
- "version": "0.2.0",
4
- "description": "Command-line client for the Scriverse long-form fiction workspace",
3
+ "version": "0.3.0",
4
+ "description": "Command-line client and local server for the Scriverse long-form fiction workspace",
5
5
  "license": "MIT",
6
6
  "author": "musnows",
7
7
  "homepage": "https://scriverse.top/",
@@ -20,12 +20,7 @@
20
20
  ],
21
21
  "type": "module",
22
22
  "files": [
23
- "dist/cli.js",
24
- "dist/cli.js.map",
25
- "dist/cli-core.js",
26
- "dist/cli-core.js.map",
27
- "dist/cli-contract.js",
28
- "dist/cli-contract.js.map"
23
+ "dist/**/*"
29
24
  ],
30
25
  "bin": {
31
26
  "scriverse": "dist/cli.js"
@@ -40,7 +35,7 @@
40
35
  "dev": "tsx watch src/server.ts",
41
36
  "cli": "tsx src/cli.ts",
42
37
  "clean": "node --input-type=module -e \"import { rmSync } from 'node:fs'; rmSync('dist', { recursive: true, force: true })\"",
43
- "build": "npm run clean && tsc -p tsconfig.build.json",
38
+ "build": "npm run clean && tsc -p tsconfig.build.json && node scripts/copy-public.mjs",
44
39
  "prepack": "npm run build",
45
40
  "start": "node dist/server.js",
46
41
  "typecheck": "tsc -p tsconfig.json --noEmit",