@rei-standard/amsg-server 2.6.0-next.1 → 2.6.0-next.2

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.
@@ -162,53 +162,127 @@ function validateScheduleMessagePayload(payload) {
162
162
  return { valid: true };
163
163
  }
164
164
 
165
+ // src/server/lib/webcrypto-utils.js
166
+
167
+ var TEXT_ENCODER = new TextEncoder();
168
+ var TEXT_DECODER = new TextDecoder("utf-8", { fatal: false });
169
+ function utf8(str) {
170
+ return TEXT_ENCODER.encode(String(str));
171
+ }
172
+ function utf8Decode(buf) {
173
+ return TEXT_DECODER.decode(_amsgshared.toUint8.call(void 0, buf));
174
+ }
175
+ function bytesToBase64(buf) {
176
+ const bytes = _amsgshared.toUint8.call(void 0, buf);
177
+ let bin = "";
178
+ for (let i = 0; i < bytes.length; i++) {
179
+ bin += String.fromCharCode(bytes[i]);
180
+ }
181
+ return typeof btoa === "function" ? btoa(bin) : Buffer.from(bin, "binary").toString("base64");
182
+ }
183
+ function bytesToBase64Url(buf) {
184
+ return bytesToBase64(buf).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
185
+ }
186
+ function bytesToHex(buf) {
187
+ const bytes = _amsgshared.toUint8.call(void 0, buf);
188
+ let out = "";
189
+ for (let i = 0; i < bytes.length; i++) {
190
+ out += bytes[i].toString(16).padStart(2, "0");
191
+ }
192
+ return out;
193
+ }
194
+ function hexToBytes(hex) {
195
+ const out = new Uint8Array(hex.length / 2);
196
+ for (let i = 0; i < out.length; i++) {
197
+ out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
198
+ }
199
+ return out;
200
+ }
201
+ function jsonToBase64Url(value) {
202
+ return bytesToBase64Url(utf8(JSON.stringify(value)));
203
+ }
204
+ function randomUUID() {
205
+ return globalThis.crypto.randomUUID();
206
+ }
207
+ function randomBytes(n) {
208
+ const out = new Uint8Array(n);
209
+ globalThis.crypto.getRandomValues(out);
210
+ return out;
211
+ }
212
+
165
213
  // src/server/lib/encryption.js
166
- var _crypto = require('crypto');
167
- function deriveUserEncryptionKey(userId, masterKey) {
168
- return _crypto.createHash.call(void 0, "sha256").update(masterKey + userId).digest("hex").slice(0, 64);
214
+ var base64ToBytes = _amsgshared.base64UrlToBytes;
215
+ var TAG_LENGTH_BYTES = 16;
216
+ function importAesKey(hexKey, usage) {
217
+ return globalThis.crypto.subtle.importKey(
218
+ "raw",
219
+ hexToBytes(hexKey),
220
+ { name: "AES-GCM" },
221
+ false,
222
+ [usage]
223
+ );
224
+ }
225
+ async function aesGcmSeal(hexKey, iv, plaintextBytes) {
226
+ const key = await importAesKey(hexKey, "encrypt");
227
+ const sealed = new Uint8Array(await globalThis.crypto.subtle.encrypt(
228
+ { name: "AES-GCM", iv, tagLength: TAG_LENGTH_BYTES * 8 },
229
+ key,
230
+ plaintextBytes
231
+ ));
232
+ return {
233
+ ciphertext: sealed.slice(0, sealed.length - TAG_LENGTH_BYTES),
234
+ authTag: sealed.slice(sealed.length - TAG_LENGTH_BYTES)
235
+ };
236
+ }
237
+ async function aesGcmOpen(hexKey, iv, ciphertext, authTag) {
238
+ const key = await importAesKey(hexKey, "decrypt");
239
+ const tagBits = authTag.length * 8;
240
+ const tagLength = tagBits >= 96 && tagBits <= 128 ? tagBits : 128;
241
+ const plain = await globalThis.crypto.subtle.decrypt(
242
+ { name: "AES-GCM", iv, tagLength },
243
+ key,
244
+ _amsgshared.concatBytes.call(void 0, ciphertext, authTag)
245
+ );
246
+ return new Uint8Array(plain);
247
+ }
248
+ async function deriveUserEncryptionKey(userId, masterKey) {
249
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", utf8(masterKey + userId));
250
+ return bytesToHex(new Uint8Array(digest)).slice(0, 64);
169
251
  }
170
- function decryptPayload(encryptedPayload, encryptionKey) {
252
+ async function decryptPayload(encryptedPayload, encryptionKey) {
171
253
  const { iv, authTag, encryptedData } = encryptedPayload;
172
- const decipher = _crypto.createDecipheriv.call(void 0,
173
- "aes-256-gcm",
174
- Buffer.from(encryptionKey, "hex"),
175
- Buffer.from(iv, "base64")
254
+ const plain = await aesGcmOpen(
255
+ encryptionKey,
256
+ base64ToBytes(iv),
257
+ base64ToBytes(encryptedData),
258
+ base64ToBytes(authTag)
176
259
  );
177
- decipher.setAuthTag(Buffer.from(authTag, "base64"));
178
- const decrypted = Buffer.concat([
179
- decipher.update(Buffer.from(encryptedData, "base64")),
180
- decipher.final()
181
- ]);
182
- return JSON.parse(decrypted.toString("utf8"));
260
+ return JSON.parse(utf8Decode(plain));
183
261
  }
184
- function encryptPayload(payload, encryptionKey) {
262
+ async function encryptPayload(payload, encryptionKey) {
185
263
  const plaintext = typeof payload === "string" ? payload : JSON.stringify(payload);
186
- const iv = _crypto.randomBytes.call(void 0, 12);
187
- const cipher = _crypto.createCipheriv.call(void 0, "aes-256-gcm", Buffer.from(encryptionKey, "hex"), iv);
188
- const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
189
- const authTag = cipher.getAuthTag();
264
+ const iv = randomBytes(12);
265
+ const { ciphertext, authTag } = await aesGcmSeal(encryptionKey, iv, utf8(plaintext));
190
266
  return {
191
- iv: iv.toString("base64"),
192
- authTag: authTag.toString("base64"),
193
- encryptedData: encrypted.toString("base64")
267
+ iv: bytesToBase64(iv),
268
+ authTag: bytesToBase64(authTag),
269
+ encryptedData: bytesToBase64(ciphertext)
194
270
  };
195
271
  }
196
- function encryptForStorage(text, encryptionKey) {
197
- const iv = _crypto.randomBytes.call(void 0, 16);
198
- const cipher = _crypto.createCipheriv.call(void 0, "aes-256-gcm", Buffer.from(encryptionKey, "hex"), iv);
199
- const encrypted = cipher.update(text, "utf8", "hex") + cipher.final("hex");
200
- const authTag = cipher.getAuthTag();
201
- return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted}`;
272
+ async function encryptForStorage(text, encryptionKey) {
273
+ const iv = randomBytes(16);
274
+ const { ciphertext, authTag } = await aesGcmSeal(encryptionKey, iv, utf8(text));
275
+ return `${bytesToHex(iv)}:${bytesToHex(authTag)}:${bytesToHex(ciphertext)}`;
202
276
  }
203
- function decryptFromStorage(encryptedText, encryptionKey) {
277
+ async function decryptFromStorage(encryptedText, encryptionKey) {
204
278
  const [ivHex, authTagHex, encryptedDataHex] = encryptedText.split(":");
205
- const decipher = _crypto.createDecipheriv.call(void 0,
206
- "aes-256-gcm",
207
- Buffer.from(encryptionKey, "hex"),
208
- Buffer.from(ivHex, "hex")
279
+ const plain = await aesGcmOpen(
280
+ encryptionKey,
281
+ hexToBytes(ivHex),
282
+ hexToBytes(encryptedDataHex),
283
+ hexToBytes(authTagHex)
209
284
  );
210
- decipher.setAuthTag(Buffer.from(authTagHex, "hex"));
211
- return decipher.update(encryptedDataHex, "hex", "utf8") + decipher.final("utf8");
285
+ return utf8Decode(plain);
212
286
  }
213
287
 
214
288
  // src/server/lib/request.js
@@ -300,7 +374,7 @@ function createGetUserKeyHandler(ctx) {
300
374
  body: {
301
375
  success: true,
302
376
  data: {
303
- userKey: deriveUserEncryptionKey(userId, masterKey),
377
+ userKey: await deriveUserEncryptionKey(userId, masterKey),
304
378
  version: 1
305
379
  }
306
380
  }
@@ -309,9 +383,6 @@ function createGetUserKeyHandler(ctx) {
309
383
  return { GET };
310
384
  }
311
385
 
312
- // src/server/handlers/schedule-message.js
313
-
314
-
315
386
  // src/server/lib/db-errors.js
316
387
  function isUniqueViolation(error) {
317
388
  if (!error || typeof error !== "object") return false;
@@ -328,7 +399,6 @@ function isUniqueViolation(error) {
328
399
 
329
400
 
330
401
 
331
-
332
402
  var DEFAULT_SPLIT_REGEX = /([。!?!?]+)/;
333
403
  var SLEEP_BETWEEN_MESSAGES_MS = 1500;
334
404
  function splitOnceByRegex(chunk, regex) {
@@ -356,8 +426,8 @@ async function processSingleMessage(task, ctx, providedMasterKey) {
356
426
  if (!masterKey) {
357
427
  return { success: false, messagesSent: 0, error: "TENANT_MASTER_KEY_MISSING" };
358
428
  }
359
- const userKey = deriveUserEncryptionKey(task.user_id, masterKey);
360
- const decryptedPayload = JSON.parse(decryptFromStorage(task.encrypted_payload, userKey));
429
+ const userKey = await deriveUserEncryptionKey(task.user_id, masterKey);
430
+ const decryptedPayload = JSON.parse(await decryptFromStorage(task.encrypted_payload, userKey));
361
431
  let messageContent;
362
432
  let llmResponse = null;
363
433
  if (decryptedPayload.messageType === "fixed") {
@@ -389,12 +459,12 @@ async function processSingleMessage(task, ctx, providedMasterKey) {
389
459
  throw new Error("VAPID configuration missing - push notifications cannot be sent");
390
460
  }
391
461
  const pushSubscription = decryptedPayload.pushSubscription;
392
- const sessionId = task.id != null ? `sess_task_${task.id}` : `sess_${_crypto.randomUUID.call(void 0, )}`;
462
+ const sessionId = task.id != null ? `sess_task_${task.id}` : `sess_${randomUUID()}`;
393
463
  const source = decryptedPayload.messageType === "instant" ? "instant" : "scheduled";
394
464
  const messageSubtype = decryptedPayload.messageSubtype || "chat";
395
465
  const avatarUrl = decryptedPayload.avatarUrl || null;
396
466
  const metadata = decryptedPayload.metadata || {};
397
- const messageIdBase = task.id != null ? `msg_task_${task.id}` : `msg_${_crypto.randomUUID.call(void 0, )}_instant`;
467
+ const messageIdBase = task.id != null ? `msg_task_${task.id}` : `msg_${randomUUID()}_instant`;
398
468
  if (reasoning) {
399
469
  const reasoningPush = _amsgshared.buildReasoningPush.call(void 0, {
400
470
  messageType: decryptedPayload.messageType,
@@ -606,10 +676,10 @@ function createScheduleMessageHandler(ctx) {
606
676
  return { status: 400, body: { success: false, error: parsedBody.error } };
607
677
  }
608
678
  const encryptedBody = parsedBody.data;
679
+ const userKey = await deriveUserEncryptionKey(userId, masterKey);
609
680
  let payload;
610
681
  try {
611
- const userKey2 = deriveUserEncryptionKey(userId, masterKey);
612
- payload = decryptPayload(encryptedBody, userKey2);
682
+ payload = await decryptPayload(encryptedBody, userKey);
613
683
  } catch (error) {
614
684
  if (error instanceof SyntaxError) {
615
685
  return { status: 400, body: { success: false, error: { code: "INVALID_PAYLOAD_FORMAT", message: "\u89E3\u5BC6\u540E\u7684\u6570\u636E\u4E0D\u662F\u6709\u6548 JSON" } } };
@@ -627,8 +697,7 @@ function createScheduleMessageHandler(ctx) {
627
697
  if (!validationResult.valid) {
628
698
  return { status: 400, body: { success: false, error: { code: validationResult.errorCode, message: validationResult.errorMessage, details: validationResult.details } } };
629
699
  }
630
- const taskUuid = payload.uuid || _crypto.randomUUID.call(void 0, );
631
- const userKey = deriveUserEncryptionKey(userId, masterKey);
700
+ const taskUuid = payload.uuid || randomUUID();
632
701
  const fullTaskData = {
633
702
  contactName: payload.contactName,
634
703
  avatarUrl: payload.avatarUrl || null,
@@ -654,7 +723,7 @@ function createScheduleMessageHandler(ctx) {
654
723
  pushSubscription: payload.pushSubscription,
655
724
  metadata: payload.metadata || {}
656
725
  };
657
- const encryptedPayload = encryptForStorage(JSON.stringify(fullTaskData), userKey);
726
+ const encryptedPayload = await encryptForStorage(JSON.stringify(fullTaskData), userKey);
658
727
  if (payload.messageType === "instant") {
659
728
  if (!ctx.vapid.email || !ctx.vapid.publicKey || !ctx.vapid.privateKey) {
660
729
  return {
@@ -808,8 +877,8 @@ async function runScheduledTick(ctx) {
808
877
  return;
809
878
  }
810
879
  try {
811
- const userKey = deriveUserEncryptionKey(task.user_id, masterKey);
812
- const decryptedPayload = JSON.parse(decryptFromStorage(task.encrypted_payload, userKey));
880
+ const userKey = await deriveUserEncryptionKey(task.user_id, masterKey);
881
+ const decryptedPayload = JSON.parse(await decryptFromStorage(task.encrypted_payload, userKey));
813
882
  if (decryptedPayload.recurrenceType === "none") {
814
883
  await db.deleteTaskById(task.id);
815
884
  results.deletedOnceOffTasks++;
@@ -896,10 +965,10 @@ function createUpdateMessageHandler(ctx) {
896
965
  return { status: 400, body: { success: false, error: parsedBody.error } };
897
966
  }
898
967
  const encryptedBody = parsedBody.data;
899
- const userKey = deriveUserEncryptionKey(userId, masterKey);
968
+ const userKey = await deriveUserEncryptionKey(userId, masterKey);
900
969
  let updates;
901
970
  try {
902
- updates = decryptPayload(encryptedBody, userKey);
971
+ updates = await decryptPayload(encryptedBody, userKey);
903
972
  } catch (_error) {
904
973
  return { status: 400, body: { success: false, error: { code: "DECRYPTION_FAILED", message: "\u8BF7\u6C42\u4F53\u89E3\u5BC6\u5931\u8D25" } } };
905
974
  }
@@ -948,7 +1017,7 @@ function createUpdateMessageHandler(ctx) {
948
1017
  }
949
1018
  return { status: 409, body: { success: false, error: { code: "TASK_ALREADY_COMPLETED", message: "\u4EFB\u52A1\u5DF2\u5B8C\u6210\u6216\u5DF2\u5931\u8D25\uFF0C\u65E0\u6CD5\u66F4\u65B0" } } };
950
1019
  }
951
- const existingData = JSON.parse(decryptFromStorage(existingTask.encrypted_payload, userKey));
1020
+ const existingData = JSON.parse(await decryptFromStorage(existingTask.encrypted_payload, userKey));
952
1021
  const promptUpdates = {};
953
1022
  if (updates.completePrompt) {
954
1023
  promptUpdates.completePrompt = updates.completePrompt;
@@ -971,7 +1040,7 @@ function createUpdateMessageHandler(ctx) {
971
1040
  // string fields above are.
972
1041
  ...Object.prototype.hasOwnProperty.call(updates, "splitPattern") && { splitPattern: _nullishCoalesce(updates.splitPattern, () => ( null)) }
973
1042
  };
974
- const encryptedPayload = encryptForStorage(JSON.stringify(updatedData), userKey);
1043
+ const encryptedPayload = await encryptForStorage(JSON.stringify(updatedData), userKey);
975
1044
  const extraFields = updates.nextSendAt ? { next_send_at: updates.nextSendAt } : void 0;
976
1045
  const result = await db.updateTaskByUuid(taskUuid, userId, encryptedPayload, extraFields);
977
1046
  if (!result) {
@@ -1064,9 +1133,9 @@ function createMessagesHandler(ctx) {
1064
1133
  return { status: 400, body: { success: false, error: { code: "INVALID_PARAMETERS", message: "offset \u53C2\u6570\u65E0\u6548\uFF0C\u5FC5\u987B\u4E3A\u975E\u8D1F\u6574\u6570" } } };
1065
1134
  }
1066
1135
  const { tasks, total } = await db.listTasks(userId, { status, limit, offset });
1067
- const userKey = deriveUserEncryptionKey(userId, masterKey);
1068
- const decryptedTasks = tasks.map((task) => {
1069
- const decrypted = JSON.parse(decryptFromStorage(task.encrypted_payload, userKey));
1136
+ const userKey = await deriveUserEncryptionKey(userId, masterKey);
1137
+ const decryptedTasks = await Promise.all(tasks.map(async (task) => {
1138
+ const decrypted = JSON.parse(await decryptFromStorage(task.encrypted_payload, userKey));
1070
1139
  return {
1071
1140
  id: task.id,
1072
1141
  uuid: task.uuid,
@@ -1080,12 +1149,12 @@ function createMessagesHandler(ctx) {
1080
1149
  createdAt: task.created_at,
1081
1150
  updatedAt: task.updated_at
1082
1151
  };
1083
- });
1152
+ }));
1084
1153
  const responsePayload = {
1085
1154
  tasks: decryptedTasks,
1086
1155
  pagination: { total, limit, offset, hasMore: offset + limit < total }
1087
1156
  };
1088
- const encryptedResponse = encryptPayload(responsePayload, userKey);
1157
+ const encryptedResponse = await encryptPayload(responsePayload, userKey);
1089
1158
  return {
1090
1159
  status: 200,
1091
1160
  body: {
@@ -1487,31 +1556,6 @@ function createSingleUserServer(config) {
1487
1556
  };
1488
1557
  }
1489
1558
 
1490
- // src/server/lib/webcrypto-utils.js
1491
-
1492
- var TEXT_ENCODER = new TextEncoder();
1493
- var TEXT_DECODER = new TextDecoder("utf-8", { fatal: false });
1494
- function utf8(str) {
1495
- return TEXT_ENCODER.encode(String(str));
1496
- }
1497
- function bytesToBase64Url(buf) {
1498
- const bytes = _amsgshared.toUint8.call(void 0, buf);
1499
- let bin = "";
1500
- for (let i = 0; i < bytes.length; i++) {
1501
- bin += String.fromCharCode(bytes[i]);
1502
- }
1503
- const b64 = typeof btoa === "function" ? btoa(bin) : Buffer.from(bin, "binary").toString("base64");
1504
- return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
1505
- }
1506
- function jsonToBase64Url(value) {
1507
- return bytesToBase64Url(utf8(JSON.stringify(value)));
1508
- }
1509
- function randomBytes2(n) {
1510
- const out = new Uint8Array(n);
1511
- globalThis.crypto.getRandomValues(out);
1512
- return out;
1513
- }
1514
-
1515
1559
  // src/server/lib/webpush-webcrypto.js
1516
1560
 
1517
1561
  var KEY_INFO_PREFIX = utf8("WebPush: info\0");
@@ -1599,7 +1643,7 @@ async function encryptPushPayload({ plaintext, uaPublicKey, authSecret }) {
1599
1643
  );
1600
1644
  const keyInfo = _amsgshared.concatBytes.call(void 0, KEY_INFO_PREFIX, uaPublicKey, asPublicRaw);
1601
1645
  const ikm = await hkdfSha256(authSecret, ecdhSecret, keyInfo, 32);
1602
- const salt = randomBytes2(16);
1646
+ const salt = randomBytes(16);
1603
1647
  const cekBytes = await hkdfSha256(salt, ikm, CEK_INFO, 16);
1604
1648
  const cek = await globalThis.crypto.subtle.importKey(
1605
1649
  "raw",