@rei-standard/amsg-server 2.6.0-next.0 → 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.
- package/dist/{chunk-YKDKY2LE.cjs → chunk-GN5QTMCL.cjs} +163 -87
- package/dist/{chunk-PJAW56VN.mjs → chunk-PVNLB6TF.mjs} +159 -83
- package/dist/{cloudflare-BwOfBtLF.d.cts → cloudflare-B-E51VjP.d.cts} +241 -115
- package/dist/{cloudflare-BwOfBtLF.d.ts → cloudflare-B-E51VjP.d.ts} +241 -115
- package/dist/cloudflare.cjs +2 -2
- package/dist/cloudflare.d.cts +1 -2
- package/dist/cloudflare.d.ts +1 -2
- package/dist/cloudflare.mjs +1 -1
- package/dist/index.cjs +11 -11
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
|
@@ -162,53 +162,127 @@ function validateScheduleMessagePayload(payload) {
|
|
|
162
162
|
return { valid: true };
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
// src/server/lib/webcrypto-utils.js
|
|
166
|
+
import { toUint8, concatBytes, base64UrlToBytes } from "@rei-standard/amsg-shared";
|
|
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(toUint8(buf));
|
|
174
|
+
}
|
|
175
|
+
function bytesToBase64(buf) {
|
|
176
|
+
const bytes = toUint8(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 = toUint8(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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
214
|
+
var base64ToBytes = 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
|
+
concatBytes(ciphertext, authTag)
|
|
245
|
+
);
|
|
246
|
+
return new Uint8Array(plain);
|
|
169
247
|
}
|
|
170
|
-
function
|
|
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);
|
|
251
|
+
}
|
|
252
|
+
async function decryptPayload(encryptedPayload, encryptionKey) {
|
|
171
253
|
const { iv, authTag, encryptedData } = encryptedPayload;
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
254
|
+
const plain = await aesGcmOpen(
|
|
255
|
+
encryptionKey,
|
|
256
|
+
base64ToBytes(iv),
|
|
257
|
+
base64ToBytes(encryptedData),
|
|
258
|
+
base64ToBytes(authTag)
|
|
176
259
|
);
|
|
177
|
-
|
|
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
264
|
const iv = randomBytes(12);
|
|
187
|
-
const
|
|
188
|
-
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
189
|
-
const authTag = cipher.getAuthTag();
|
|
265
|
+
const { ciphertext, authTag } = await aesGcmSeal(encryptionKey, iv, utf8(plaintext));
|
|
190
266
|
return {
|
|
191
|
-
iv: iv
|
|
192
|
-
authTag: authTag
|
|
193
|
-
encryptedData:
|
|
267
|
+
iv: bytesToBase64(iv),
|
|
268
|
+
authTag: bytesToBase64(authTag),
|
|
269
|
+
encryptedData: bytesToBase64(ciphertext)
|
|
194
270
|
};
|
|
195
271
|
}
|
|
196
|
-
function encryptForStorage(text, encryptionKey) {
|
|
272
|
+
async function encryptForStorage(text, encryptionKey) {
|
|
197
273
|
const iv = randomBytes(16);
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
const authTag = cipher.getAuthTag();
|
|
201
|
-
return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted}`;
|
|
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
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
279
|
+
const plain = await aesGcmOpen(
|
|
280
|
+
encryptionKey,
|
|
281
|
+
hexToBytes(ivHex),
|
|
282
|
+
hexToBytes(encryptedDataHex),
|
|
283
|
+
hexToBytes(authTagHex)
|
|
209
284
|
);
|
|
210
|
-
|
|
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
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
314
|
-
|
|
315
386
|
// src/server/lib/db-errors.js
|
|
316
387
|
function isUniqueViolation(error) {
|
|
317
388
|
if (!error || typeof error !== "object") return false;
|
|
@@ -322,7 +393,6 @@ function isUniqueViolation(error) {
|
|
|
322
393
|
}
|
|
323
394
|
|
|
324
395
|
// src/server/lib/message-processor.js
|
|
325
|
-
import { randomUUID } from "crypto";
|
|
326
396
|
import {
|
|
327
397
|
buildContentPush,
|
|
328
398
|
buildReasoningPush,
|
|
@@ -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") {
|
|
@@ -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
|
-
|
|
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 ||
|
|
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: 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: {
|
|
@@ -1425,6 +1494,35 @@ function createSingleUserInitHandler(ctx) {
|
|
|
1425
1494
|
return { POST };
|
|
1426
1495
|
}
|
|
1427
1496
|
|
|
1497
|
+
// src/server/handlers/vapid-public-key.js
|
|
1498
|
+
function createVapidPublicKeyHandler(ctx) {
|
|
1499
|
+
async function GET(url, headers) {
|
|
1500
|
+
const effectiveHeaders = headers || url || {};
|
|
1501
|
+
const tenantResult = await ctx.tenantManager.resolveTenant(effectiveHeaders);
|
|
1502
|
+
if (!tenantResult.ok) {
|
|
1503
|
+
return tenantResult.error;
|
|
1504
|
+
}
|
|
1505
|
+
const publicKey = ctx.vapid && ctx.vapid.publicKey;
|
|
1506
|
+
if (!publicKey) {
|
|
1507
|
+
return {
|
|
1508
|
+
status: 503,
|
|
1509
|
+
body: {
|
|
1510
|
+
success: false,
|
|
1511
|
+
error: {
|
|
1512
|
+
code: "VAPID_NOT_CONFIGURED",
|
|
1513
|
+
message: "VAPID \u516C\u94A5\u672A\u914D\u7F6E\uFF1A\u8BF7\u4E3A\u672C Worker \u8BBE\u7F6E VAPID_PUBLIC_KEY"
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
return {
|
|
1519
|
+
status: 200,
|
|
1520
|
+
body: { success: true, publicKey }
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
return { GET };
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1428
1526
|
// src/server/single-user.js
|
|
1429
1527
|
function createSingleUserServer(config) {
|
|
1430
1528
|
if (!config || !config.db) throw new Error("[amsg-server single-user] config.db is required");
|
|
@@ -1452,36 +1550,12 @@ function createSingleUserServer(config) {
|
|
|
1452
1550
|
scheduleMessage: createScheduleMessageHandler(ctx),
|
|
1453
1551
|
updateMessage: createUpdateMessageHandler(ctx),
|
|
1454
1552
|
cancelMessage: createCancelMessageHandler(ctx),
|
|
1455
|
-
messages: createMessagesHandler(ctx)
|
|
1553
|
+
messages: createMessagesHandler(ctx),
|
|
1554
|
+
vapidPublicKey: createVapidPublicKeyHandler(ctx)
|
|
1456
1555
|
}
|
|
1457
1556
|
};
|
|
1458
1557
|
}
|
|
1459
1558
|
|
|
1460
|
-
// src/server/lib/webcrypto-utils.js
|
|
1461
|
-
import { toUint8, concatBytes, base64UrlToBytes } from "@rei-standard/amsg-shared";
|
|
1462
|
-
var TEXT_ENCODER = new TextEncoder();
|
|
1463
|
-
var TEXT_DECODER = new TextDecoder("utf-8", { fatal: false });
|
|
1464
|
-
function utf8(str) {
|
|
1465
|
-
return TEXT_ENCODER.encode(String(str));
|
|
1466
|
-
}
|
|
1467
|
-
function bytesToBase64Url(buf) {
|
|
1468
|
-
const bytes = toUint8(buf);
|
|
1469
|
-
let bin = "";
|
|
1470
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
1471
|
-
bin += String.fromCharCode(bytes[i]);
|
|
1472
|
-
}
|
|
1473
|
-
const b64 = typeof btoa === "function" ? btoa(bin) : Buffer.from(bin, "binary").toString("base64");
|
|
1474
|
-
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
1475
|
-
}
|
|
1476
|
-
function jsonToBase64Url(value) {
|
|
1477
|
-
return bytesToBase64Url(utf8(JSON.stringify(value)));
|
|
1478
|
-
}
|
|
1479
|
-
function randomBytes2(n) {
|
|
1480
|
-
const out = new Uint8Array(n);
|
|
1481
|
-
globalThis.crypto.getRandomValues(out);
|
|
1482
|
-
return out;
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
1559
|
// src/server/lib/webpush-webcrypto.js
|
|
1486
1560
|
import { normalizeVapidSubject } from "@rei-standard/amsg-shared";
|
|
1487
1561
|
var KEY_INFO_PREFIX = utf8("WebPush: info\0");
|
|
@@ -1569,7 +1643,7 @@ async function encryptPushPayload({ plaintext, uaPublicKey, authSecret }) {
|
|
|
1569
1643
|
);
|
|
1570
1644
|
const keyInfo = concatBytes(KEY_INFO_PREFIX, uaPublicKey, asPublicRaw);
|
|
1571
1645
|
const ikm = await hkdfSha256(authSecret, ecdhSecret, keyInfo, 32);
|
|
1572
|
-
const salt =
|
|
1646
|
+
const salt = randomBytes(16);
|
|
1573
1647
|
const cekBytes = await hkdfSha256(salt, ikm, CEK_INFO, 16);
|
|
1574
1648
|
const cek = await globalThis.crypto.subtle.importKey(
|
|
1575
1649
|
"raw",
|
|
@@ -1741,6 +1815,8 @@ function createSingleUserCloudflareWorker(buildConfig) {
|
|
|
1741
1815
|
result = await server.handlers.updateMessage.PUT(url, headers, await request.text());
|
|
1742
1816
|
} else if (method === "DELETE" && pathname.endsWith("/cancel-message")) {
|
|
1743
1817
|
result = await server.handlers.cancelMessage.DELETE(url, headers);
|
|
1818
|
+
} else if (method === "GET" && pathname.endsWith("/vapid-public-key")) {
|
|
1819
|
+
result = await server.handlers.vapidPublicKey.GET(url, headers);
|
|
1744
1820
|
} else {
|
|
1745
1821
|
result = { status: 404, body: { success: false, error: { code: "NOT_FOUND", message: "Unknown route" } } };
|
|
1746
1822
|
}
|