@rei-standard/amsg-server 1.2.0 → 2.0.0-pre1

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/index.cjs CHANGED
@@ -1,8 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } }
2
-
3
- var _chunk74H4WV6Qcjs = require('./chunk-74H4WV6Q.cjs');
4
-
5
- // src/server/adapters/factory.js
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/server/adapters/factory.js
6
2
  async function createAdapter(config) {
7
3
  if (!config || !config.driver) {
8
4
  throw new Error(
@@ -16,11 +12,11 @@ async function createAdapter(config) {
16
12
  }
17
13
  switch (config.driver) {
18
14
  case "neon": {
19
- const { NeonAdapter } = await Promise.resolve().then(() => _interopRequireWildcard(require("./neon-ZQS3KT6C.cjs")));
15
+ const { NeonAdapter } = await Promise.resolve().then(() => _interopRequireWildcard(require("./neon-BFUS25UX.cjs")));
20
16
  return new NeonAdapter(config.connectionString);
21
17
  }
22
18
  case "pg": {
23
- const { PgAdapter } = await Promise.resolve().then(() => _interopRequireWildcard(require("./pg-2JJP632A.cjs")));
19
+ const { PgAdapter } = await Promise.resolve().then(() => _interopRequireWildcard(require("./pg-IIH3M4OM.cjs")));
24
20
  return new PgAdapter(config.connectionString);
25
21
  }
26
22
  default:
@@ -30,123 +26,6 @@ async function createAdapter(config) {
30
26
  }
31
27
  }
32
28
 
33
- // src/server/handlers/init-database.js
34
- function createInitDatabaseHandler(ctx) {
35
- async function GET() {
36
- const result = await ctx.db.initSchema();
37
- const columnNames = result.columns.map((c) => c.name);
38
- const missingColumns = _chunk74H4WV6Qcjs.REQUIRED_COLUMNS.filter((col) => !columnNames.includes(col));
39
- if (missingColumns.length > 0) {
40
- console.warn("[init-database] \u26A0\uFE0F Missing columns:", missingColumns);
41
- }
42
- return {
43
- status: 200,
44
- body: {
45
- success: true,
46
- message: "\u6570\u636E\u5E93\u521D\u59CB\u5316\u6210\u529F\uFF01\u5EFA\u8BAE\u7ACB\u5373\u5220\u9664\u6B64 API \u6587\u4EF6\u3002",
47
- data: {
48
- tables: ["scheduled_messages", "system_config"],
49
- columnsCreated: result.columnsCreated,
50
- indexesCreated: result.indexesCreated,
51
- indexesFailed: result.indexesFailed,
52
- details: { columns: result.columns, indexes: result.indexes },
53
- nextSteps: [
54
- "1. \u8C03\u7528 /api/v1/init-master-key \u4E00\u6B21\u6027\u751F\u6210\u4E3B\u5BC6\u94A5\u5E76\u79BB\u7EBF\u4FDD\u5B58",
55
- "2. \u5BA2\u6237\u7AEF\u4F7F\u7528 UUID v4 \u4F5C\u4E3A X-User-Id",
56
- "3. \u5BA2\u6237\u7AEF\u8C03\u7528 /api/v1/get-user-key \u83B7\u53D6 userKey \u5E76\u7F13\u5B58"
57
- ]
58
- }
59
- }
60
- };
61
- }
62
- return { GET };
63
- }
64
-
65
- // src/server/handlers/init-master-key.js
66
- var _crypto = require('crypto');
67
- function makeFingerprint(masterKey) {
68
- return _crypto.createHash.call(void 0, "sha256").update(masterKey).digest("hex").slice(0, 16);
69
- }
70
- function createInitMasterKeyHandler(ctx) {
71
- async function POST() {
72
- const masterKey = _crypto.randomBytes.call(void 0, 32).toString("hex");
73
- const inserted = await ctx.db.setMasterKeyOnce(masterKey);
74
- if (!inserted) {
75
- return {
76
- status: 409,
77
- body: {
78
- success: false,
79
- error: {
80
- code: "MASTER_KEY_ALREADY_INITIALIZED",
81
- message: "\u4E3B\u5BC6\u94A5\u5DF2\u521D\u59CB\u5316\uFF0C\u65E0\u6CD5\u518D\u6B21\u83B7\u53D6"
82
- }
83
- }
84
- };
85
- }
86
- return {
87
- status: 201,
88
- body: {
89
- success: true,
90
- data: {
91
- masterKey,
92
- fingerprint: makeFingerprint(masterKey),
93
- version: 1
94
- }
95
- }
96
- };
97
- }
98
- return { POST };
99
- }
100
-
101
- // src/server/lib/encryption.js
102
-
103
- function deriveUserEncryptionKey(userId, masterKey) {
104
- return _crypto.createHash.call(void 0, "sha256").update(masterKey + userId).digest("hex").slice(0, 64);
105
- }
106
- function decryptPayload(encryptedPayload, encryptionKey) {
107
- const { iv, authTag, encryptedData } = encryptedPayload;
108
- const decipher = _crypto.createDecipheriv.call(void 0,
109
- "aes-256-gcm",
110
- Buffer.from(encryptionKey, "hex"),
111
- Buffer.from(iv, "base64")
112
- );
113
- decipher.setAuthTag(Buffer.from(authTag, "base64"));
114
- const decrypted = Buffer.concat([
115
- decipher.update(Buffer.from(encryptedData, "base64")),
116
- decipher.final()
117
- ]);
118
- return JSON.parse(decrypted.toString("utf8"));
119
- }
120
- function encryptPayload(payload, encryptionKey) {
121
- const plaintext = typeof payload === "string" ? payload : JSON.stringify(payload);
122
- const iv = _crypto.randomBytes.call(void 0, 12);
123
- const cipher = _crypto.createCipheriv.call(void 0, "aes-256-gcm", Buffer.from(encryptionKey, "hex"), iv);
124
- const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
125
- const authTag = cipher.getAuthTag();
126
- return {
127
- iv: iv.toString("base64"),
128
- authTag: authTag.toString("base64"),
129
- encryptedData: encrypted.toString("base64")
130
- };
131
- }
132
- function encryptForStorage(text, encryptionKey) {
133
- const iv = _crypto.randomBytes.call(void 0, 16);
134
- const cipher = _crypto.createCipheriv.call(void 0, "aes-256-gcm", Buffer.from(encryptionKey, "hex"), iv);
135
- const encrypted = cipher.update(text, "utf8", "hex") + cipher.final("hex");
136
- const authTag = cipher.getAuthTag();
137
- return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted}`;
138
- }
139
- function decryptFromStorage(encryptedText, encryptionKey) {
140
- const [ivHex, authTagHex, encryptedDataHex] = encryptedText.split(":");
141
- const decipher = _crypto.createDecipheriv.call(void 0,
142
- "aes-256-gcm",
143
- Buffer.from(encryptionKey, "hex"),
144
- Buffer.from(ivHex, "hex")
145
- );
146
- decipher.setAuthTag(Buffer.from(authTagHex, "hex"));
147
- return decipher.update(encryptedDataHex, "hex", "utf8") + decipher.final("utf8");
148
- }
149
-
150
29
  // src/server/lib/validation.js
151
30
  function isValidISO8601(dateString) {
152
31
  const date = new Date(dateString);
@@ -224,55 +103,6 @@ function validateScheduleMessagePayload(payload) {
224
103
  return { valid: true };
225
104
  }
226
105
 
227
- // src/server/handlers/get-user-key.js
228
- function createGetUserKeyHandler(ctx) {
229
- async function GET(headers) {
230
- const userId = headers["x-user-id"];
231
- if (!userId) {
232
- return {
233
- status: 400,
234
- body: { success: false, error: { code: "USER_ID_REQUIRED", message: "\u7F3A\u5C11\u7528\u6237\u6807\u8BC6\u7B26" } }
235
- };
236
- }
237
- if (!isValidUUIDv4(userId)) {
238
- return {
239
- status: 400,
240
- body: { success: false, error: { code: "INVALID_USER_ID_FORMAT", message: "X-User-Id \u5FC5\u987B\u662F UUID v4 \u683C\u5F0F" } }
241
- };
242
- }
243
- const masterKey = await ctx.db.getMasterKey();
244
- if (!masterKey) {
245
- return {
246
- status: 503,
247
- body: { success: false, error: { code: "MASTER_KEY_NOT_INITIALIZED", message: "\u4E3B\u5BC6\u94A5\u5C1A\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 /api/v1/init-master-key" } }
248
- };
249
- }
250
- return {
251
- status: 200,
252
- body: {
253
- success: true,
254
- data: {
255
- userKey: deriveUserEncryptionKey(userId, masterKey),
256
- version: 1
257
- }
258
- }
259
- };
260
- }
261
- return { GET };
262
- }
263
-
264
- // src/server/handlers/schedule-message.js
265
-
266
-
267
- // src/server/lib/db-errors.js
268
- function isUniqueViolation(error) {
269
- if (!error || typeof error !== "object") return false;
270
- const code = error.code;
271
- if (code === "23505") return true;
272
- const message = typeof error.message === "string" ? error.message.toLowerCase() : "";
273
- return message.includes("duplicate key") || message.includes("unique constraint");
274
- }
275
-
276
106
  // src/server/lib/request.js
277
107
  var REQUEST_ERRORS = {
278
108
  INVALID_JSON: { code: "INVALID_JSON", message: "\u8BF7\u6C42\u4F53\u4E0D\u662F\u6709\u6548\u7684 JSON" },
@@ -295,6 +125,12 @@ function parseBodyAsObject(body, options = {}) {
295
125
  }
296
126
  return { ok: true, data: parsed };
297
127
  }
128
+ function parseJsonBody(body) {
129
+ return parseBodyAsObject(body, {
130
+ invalidJson: REQUEST_ERRORS.INVALID_JSON,
131
+ invalidType: REQUEST_ERRORS.INVALID_REQUEST_BODY
132
+ });
133
+ }
298
134
  function isPlainObject(value) {
299
135
  return typeof value === "object" && value !== null && !Array.isArray(value);
300
136
  }
@@ -315,14 +151,240 @@ function parseEncryptedBody(body) {
315
151
  }
316
152
  return parsedBody;
317
153
  }
154
+ function getHeader(headers = {}, name) {
155
+ if (!headers || typeof headers !== "object") {
156
+ return "";
157
+ }
158
+ const lowerName = String(name || "").toLowerCase();
159
+ if (!lowerName) return "";
160
+ for (const [key, value] of Object.entries(headers)) {
161
+ if (String(key).toLowerCase() === lowerName) {
162
+ return String(value || "").trim();
163
+ }
164
+ }
165
+ return "";
166
+ }
167
+
168
+ // src/server/handlers/init-tenant.js
169
+ function createInitTenantHandler(ctx) {
170
+ async function POST(headers, body) {
171
+ const initSecret = getHeader(headers, "x-init-secret");
172
+ if (ctx.tenant.initSecret && initSecret !== ctx.tenant.initSecret) {
173
+ return {
174
+ status: 401,
175
+ body: {
176
+ success: false,
177
+ error: {
178
+ code: "INVALID_INIT_AUTH",
179
+ message: "\u521D\u59CB\u5316\u9274\u6743\u5931\u8D25"
180
+ }
181
+ }
182
+ };
183
+ }
184
+ const parsed = parseJsonBody(body);
185
+ if (!parsed.ok) {
186
+ return {
187
+ status: 400,
188
+ body: {
189
+ success: false,
190
+ error: parsed.error
191
+ }
192
+ };
193
+ }
194
+ const payload = parsed.data;
195
+ const tenantId = payload.tenantId || void 0;
196
+ const driver = payload.driver;
197
+ const databaseUrl = payload.databaseUrl || payload.connectionString;
198
+ if (tenantId && !isValidUUIDv4(tenantId)) {
199
+ return {
200
+ status: 400,
201
+ body: {
202
+ success: false,
203
+ error: {
204
+ code: "INVALID_TENANT_ID",
205
+ message: "tenantId \u5FC5\u987B\u662F UUID v4 \u683C\u5F0F"
206
+ }
207
+ }
208
+ };
209
+ }
210
+ if (!databaseUrl || !driver) {
211
+ return {
212
+ status: 400,
213
+ body: {
214
+ success: false,
215
+ error: {
216
+ code: "INVALID_PARAMETERS",
217
+ message: "\u7F3A\u5C11 driver \u6216 databaseUrl \u53C2\u6570"
218
+ }
219
+ }
220
+ };
221
+ }
222
+ try {
223
+ const result = await ctx.tenantManager.initializeTenant({
224
+ tenantId,
225
+ driver,
226
+ connectionString: databaseUrl
227
+ });
228
+ return {
229
+ status: 201,
230
+ body: {
231
+ success: true,
232
+ data: {
233
+ tenantId: result.tenantId,
234
+ tenantToken: result.tenantToken,
235
+ cronToken: result.cronToken,
236
+ cronWebhookUrl: result.cronWebhookUrl,
237
+ masterKeyFingerprint: result.masterKeyFingerprint
238
+ }
239
+ }
240
+ };
241
+ } catch (error) {
242
+ if (error.message === "INVALID_DRIVER") {
243
+ return {
244
+ status: 400,
245
+ body: {
246
+ success: false,
247
+ error: {
248
+ code: "INVALID_DRIVER",
249
+ message: "driver \u5FC5\u987B\u662F neon \u6216 pg"
250
+ }
251
+ }
252
+ };
253
+ }
254
+ if (error.message === "INVALID_DATABASE_URL") {
255
+ return {
256
+ status: 400,
257
+ body: {
258
+ success: false,
259
+ error: {
260
+ code: "INVALID_DATABASE_URL",
261
+ message: "databaseUrl \u4E0D\u80FD\u4E3A\u7A7A"
262
+ }
263
+ }
264
+ };
265
+ }
266
+ if (error.message === "TENANT_ALREADY_INITIALIZED") {
267
+ return {
268
+ status: 409,
269
+ body: {
270
+ success: false,
271
+ error: {
272
+ code: "TENANT_ALREADY_INITIALIZED",
273
+ message: "tenantId \u5DF2\u5B58\u5728\uFF0C\u4E0D\u80FD\u91CD\u590D\u521D\u59CB\u5316"
274
+ }
275
+ }
276
+ };
277
+ }
278
+ throw error;
279
+ }
280
+ }
281
+ return { POST };
282
+ }
283
+
284
+ // src/server/lib/encryption.js
285
+ var _crypto = require('crypto');
286
+ function deriveUserEncryptionKey(userId, masterKey) {
287
+ return _crypto.createHash.call(void 0, "sha256").update(masterKey + userId).digest("hex").slice(0, 64);
288
+ }
289
+ function decryptPayload(encryptedPayload, encryptionKey) {
290
+ const { iv, authTag, encryptedData } = encryptedPayload;
291
+ const decipher = _crypto.createDecipheriv.call(void 0,
292
+ "aes-256-gcm",
293
+ Buffer.from(encryptionKey, "hex"),
294
+ Buffer.from(iv, "base64")
295
+ );
296
+ decipher.setAuthTag(Buffer.from(authTag, "base64"));
297
+ const decrypted = Buffer.concat([
298
+ decipher.update(Buffer.from(encryptedData, "base64")),
299
+ decipher.final()
300
+ ]);
301
+ return JSON.parse(decrypted.toString("utf8"));
302
+ }
303
+ function encryptPayload(payload, encryptionKey) {
304
+ const plaintext = typeof payload === "string" ? payload : JSON.stringify(payload);
305
+ const iv = _crypto.randomBytes.call(void 0, 12);
306
+ const cipher = _crypto.createCipheriv.call(void 0, "aes-256-gcm", Buffer.from(encryptionKey, "hex"), iv);
307
+ const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
308
+ const authTag = cipher.getAuthTag();
309
+ return {
310
+ iv: iv.toString("base64"),
311
+ authTag: authTag.toString("base64"),
312
+ encryptedData: encrypted.toString("base64")
313
+ };
314
+ }
315
+ function encryptForStorage(text, encryptionKey) {
316
+ const iv = _crypto.randomBytes.call(void 0, 16);
317
+ const cipher = _crypto.createCipheriv.call(void 0, "aes-256-gcm", Buffer.from(encryptionKey, "hex"), iv);
318
+ const encrypted = cipher.update(text, "utf8", "hex") + cipher.final("hex");
319
+ const authTag = cipher.getAuthTag();
320
+ return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted}`;
321
+ }
322
+ function decryptFromStorage(encryptedText, encryptionKey) {
323
+ const [ivHex, authTagHex, encryptedDataHex] = encryptedText.split(":");
324
+ const decipher = _crypto.createDecipheriv.call(void 0,
325
+ "aes-256-gcm",
326
+ Buffer.from(encryptionKey, "hex"),
327
+ Buffer.from(ivHex, "hex")
328
+ );
329
+ decipher.setAuthTag(Buffer.from(authTagHex, "hex"));
330
+ return decipher.update(encryptedDataHex, "hex", "utf8") + decipher.final("utf8");
331
+ }
332
+
333
+ // src/server/handlers/get-user-key.js
334
+ function createGetUserKeyHandler(ctx) {
335
+ async function GET(url, headers) {
336
+ const effectiveHeaders = headers || url || {};
337
+ const tenantResult = await ctx.tenantManager.resolveTenant(effectiveHeaders);
338
+ if (!tenantResult.ok) {
339
+ return tenantResult.error;
340
+ }
341
+ const { masterKey } = tenantResult.context;
342
+ const userId = getHeader(effectiveHeaders, "x-user-id");
343
+ if (!userId) {
344
+ return {
345
+ status: 400,
346
+ body: { success: false, error: { code: "USER_ID_REQUIRED", message: "\u7F3A\u5C11\u7528\u6237\u6807\u8BC6\u7B26" } }
347
+ };
348
+ }
349
+ if (!isValidUUIDv4(userId)) {
350
+ return {
351
+ status: 400,
352
+ body: { success: false, error: { code: "INVALID_USER_ID_FORMAT", message: "X-User-Id \u5FC5\u987B\u662F UUID v4 \u683C\u5F0F" } }
353
+ };
354
+ }
355
+ return {
356
+ status: 200,
357
+ body: {
358
+ success: true,
359
+ data: {
360
+ userKey: deriveUserEncryptionKey(userId, masterKey),
361
+ version: 1
362
+ }
363
+ }
364
+ };
365
+ }
366
+ return { GET };
367
+ }
368
+
369
+ // src/server/handlers/schedule-message.js
370
+
371
+
372
+ // src/server/lib/db-errors.js
373
+ function isUniqueViolation(error) {
374
+ if (!error || typeof error !== "object") return false;
375
+ const code = error.code;
376
+ if (code === "23505") return true;
377
+ const message = typeof error.message === "string" ? error.message.toLowerCase() : "";
378
+ return message.includes("duplicate key") || message.includes("unique constraint");
379
+ }
318
380
 
319
381
  // src/server/lib/message-processor.js
320
382
 
321
383
  async function processSingleMessage(task, ctx, providedMasterKey) {
322
384
  try {
323
- const masterKey = providedMasterKey || await ctx.db.getMasterKey();
385
+ const masterKey = providedMasterKey || ctx.masterKey;
324
386
  if (!masterKey) {
325
- return { success: false, messagesSent: 0, error: "MASTER_KEY_NOT_INITIALIZED" };
387
+ return { success: false, messagesSent: 0, error: "TENANT_MASTER_KEY_MISSING" };
326
388
  }
327
389
  const userKey = deriveUserEncryptionKey(task.user_id, masterKey);
328
390
  const decryptedPayload = JSON.parse(decryptFromStorage(task.encrypted_payload, userKey));
@@ -382,11 +444,11 @@ async function processSingleMessage(task, ctx, providedMasterKey) {
382
444
  }
383
445
  async function processMessagesByUuid(uuid, ctx, maxRetries = 2, userId, providedMasterKey) {
384
446
  let retryCount = 0;
385
- const masterKey = providedMasterKey || await ctx.db.getMasterKey();
447
+ const masterKey = providedMasterKey || ctx.masterKey;
386
448
  if (!masterKey) {
387
449
  return {
388
450
  success: false,
389
- error: { code: "MASTER_KEY_NOT_INITIALIZED", message: "\u4E3B\u5BC6\u94A5\u5C1A\u672A\u521D\u59CB\u5316" }
451
+ error: { code: "TENANT_MASTER_KEY_MISSING", message: "\u79DF\u6237\u4E3B\u5BC6\u94A5\u4E0D\u5B58\u5728\u6216\u914D\u7F6E\u5F02\u5E38" }
390
452
  };
391
453
  }
392
454
  while (retryCount <= maxRetries) {
@@ -467,9 +529,16 @@ async function _callAI(payload) {
467
529
  // src/server/handlers/schedule-message.js
468
530
  function createScheduleMessageHandler(ctx) {
469
531
  async function POST(headers, body) {
470
- const isEncrypted = headers["x-payload-encrypted"] === "true";
471
- const encryptionVersion = headers["x-encryption-version"];
472
- const userId = headers["x-user-id"];
532
+ const tenantResult = await ctx.tenantManager.resolveTenant(headers);
533
+ if (!tenantResult.ok) {
534
+ return tenantResult.error;
535
+ }
536
+ const tenantCtx = tenantResult.context;
537
+ const db = tenantCtx.db;
538
+ const masterKey = tenantCtx.masterKey;
539
+ const isEncrypted = getHeader(headers, "x-payload-encrypted") === "true";
540
+ const encryptionVersion = getHeader(headers, "x-encryption-version");
541
+ const userId = getHeader(headers, "x-user-id");
473
542
  if (!isEncrypted) {
474
543
  return { status: 400, body: { success: false, error: { code: "ENCRYPTION_REQUIRED", message: "\u8BF7\u6C42\u4F53\u5FC5\u987B\u52A0\u5BC6" } } };
475
544
  }
@@ -482,10 +551,6 @@ function createScheduleMessageHandler(ctx) {
482
551
  if (encryptionVersion !== "1") {
483
552
  return { status: 400, body: { success: false, error: { code: "UNSUPPORTED_ENCRYPTION_VERSION", message: "\u52A0\u5BC6\u7248\u672C\u4E0D\u652F\u6301" } } };
484
553
  }
485
- const masterKey = await ctx.db.getMasterKey();
486
- if (!masterKey) {
487
- return { status: 503, body: { success: false, error: { code: "MASTER_KEY_NOT_INITIALIZED", message: "\u4E3B\u5BC6\u94A5\u5C1A\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 /api/v1/init-master-key" } } };
488
- }
489
554
  const parsedBody = parseEncryptedBody(body);
490
555
  if (!parsedBody.ok) {
491
556
  return { status: 400, body: { success: false, error: parsedBody.error } };
@@ -553,7 +618,7 @@ function createScheduleMessageHandler(ctx) {
553
618
  }
554
619
  let dbResult;
555
620
  try {
556
- dbResult = await ctx.db.createTask({
621
+ dbResult = await db.createTask({
557
622
  user_id: userId,
558
623
  uuid: taskUuid,
559
624
  encrypted_payload: encryptedPayload,
@@ -580,7 +645,11 @@ function createScheduleMessageHandler(ctx) {
580
645
  }
581
646
  if (payload.messageType === "instant") {
582
647
  try {
583
- const sendResult = await processMessagesByUuid(taskUuid, ctx, 2, userId, masterKey);
648
+ const sendResult = await processMessagesByUuid(taskUuid, {
649
+ ...ctx,
650
+ db,
651
+ masterKey
652
+ }, 2, userId, masterKey);
584
653
  if (!sendResult.success) {
585
654
  return { status: 500, body: { success: false, error: { code: "MESSAGE_SEND_FAILED", message: "\u6D88\u606F\u53D1\u9001\u5931\u8D25", details: sendResult.error } } };
586
655
  }
@@ -622,7 +691,19 @@ function createScheduleMessageHandler(ctx) {
622
691
 
623
692
  // src/server/handlers/send-notifications.js
624
693
  function createSendNotificationsHandler(ctx) {
625
- async function POST(headers) {
694
+ async function POST(urlOrHeaders, maybeHeaders) {
695
+ const url = typeof urlOrHeaders === "string" ? urlOrHeaders : "";
696
+ const headers = maybeHeaders || (typeof urlOrHeaders === "object" ? urlOrHeaders : {});
697
+ const tenantResult = await ctx.tenantManager.resolveTenant(headers, {
698
+ allowCronToken: true,
699
+ url
700
+ });
701
+ if (!tenantResult.ok) {
702
+ return tenantResult.error;
703
+ }
704
+ const tenantCtx = tenantResult.context;
705
+ const db = tenantCtx.db;
706
+ const masterKey = tenantCtx.masterKey;
626
707
  if (!ctx.vapid.email || !ctx.vapid.publicKey || !ctx.vapid.privateKey) {
627
708
  return {
628
709
  status: 500,
@@ -642,26 +723,8 @@ function createSendNotificationsHandler(ctx) {
642
723
  }
643
724
  };
644
725
  }
645
- const authHeader = (headers["authorization"] || "").trim();
646
- const expectedAuth = `Bearer ${ctx.cronSecret}`;
647
- if (authHeader !== expectedAuth) {
648
- return { status: 401, body: { success: false, error: { code: "UNAUTHORIZED", message: "Cron Secret \u9A8C\u8BC1\u5931\u8D25" } } };
649
- }
650
- const masterKey = await ctx.db.getMasterKey();
651
- if (!masterKey) {
652
- return {
653
- status: 503,
654
- body: {
655
- success: false,
656
- error: {
657
- code: "MASTER_KEY_NOT_INITIALIZED",
658
- message: "\u4E3B\u5BC6\u94A5\u5C1A\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 /api/v1/init-master-key"
659
- }
660
- }
661
- };
662
- }
663
726
  const startTime = Date.now();
664
- const tasks = await ctx.db.getPendingTasks(50);
727
+ const tasks = await db.getPendingTasks(50);
665
728
  const MAX_CONCURRENT = 8;
666
729
  const results = {
667
730
  totalTasks: tasks.length,
@@ -675,11 +738,11 @@ function createSendNotificationsHandler(ctx) {
675
738
  results.failedCount++;
676
739
  try {
677
740
  if (task.retry_count >= 3) {
678
- await ctx.db.updateTaskById(task.id, { status: "failed" });
741
+ await db.updateTaskById(task.id, { status: "failed" });
679
742
  results.failedTasks.push({ taskId: task.id, reason, retryCount: task.retry_count, status: "permanently_failed" });
680
743
  } else {
681
744
  const nextRetryTime = new Date(Date.now() + (task.retry_count + 1) * 2 * 60 * 1e3);
682
- await ctx.db.updateTaskById(task.id, { next_send_at: nextRetryTime.toISOString(), retry_count: task.retry_count + 1 });
745
+ await db.updateTaskById(task.id, { next_send_at: nextRetryTime.toISOString(), retry_count: task.retry_count + 1 });
683
746
  results.failedTasks.push({ taskId: task.id, reason, retryCount: task.retry_count + 1, nextRetryAt: nextRetryTime.toISOString() });
684
747
  }
685
748
  } catch (updateError) {
@@ -695,7 +758,7 @@ function createSendNotificationsHandler(ctx) {
695
758
  results.failedCount++;
696
759
  let markedSent = false;
697
760
  try {
698
- await ctx.db.updateTaskById(task.id, { status: "sent", retry_count: 0 });
761
+ await db.updateTaskById(task.id, { status: "sent", retry_count: 0 });
699
762
  markedSent = true;
700
763
  } catch (_markSentError) {
701
764
  markedSent = false;
@@ -710,7 +773,11 @@ function createSendNotificationsHandler(ctx) {
710
773
  async function processTask(task) {
711
774
  let sendResult;
712
775
  try {
713
- sendResult = await processSingleMessage(task, ctx);
776
+ sendResult = await processSingleMessage(task, {
777
+ ...ctx,
778
+ db,
779
+ masterKey
780
+ }, masterKey);
714
781
  } catch (error) {
715
782
  await handleDeliveryFailure(task, error.message || "\u6D88\u606F\u53D1\u9001\u5931\u8D25");
716
783
  return;
@@ -723,7 +790,7 @@ function createSendNotificationsHandler(ctx) {
723
790
  const userKey = deriveUserEncryptionKey(task.user_id, masterKey);
724
791
  const decryptedPayload = JSON.parse(decryptFromStorage(task.encrypted_payload, userKey));
725
792
  if (decryptedPayload.recurrenceType === "none") {
726
- await ctx.db.deleteTaskById(task.id);
793
+ await db.deleteTaskById(task.id);
727
794
  results.deletedOnceOffTasks++;
728
795
  } else {
729
796
  let nextSendAt;
@@ -733,7 +800,7 @@ function createSendNotificationsHandler(ctx) {
733
800
  } else if (decryptedPayload.recurrenceType === "weekly") {
734
801
  nextSendAt = new Date(currentSendAt.getTime() + 7 * 24 * 60 * 60 * 1e3);
735
802
  }
736
- await ctx.db.updateTaskById(task.id, { next_send_at: nextSendAt.toISOString(), retry_count: 0 });
803
+ await db.updateTaskById(task.id, { next_send_at: nextSendAt.toISOString(), retry_count: 0 });
737
804
  results.updatedRecurringTasks++;
738
805
  }
739
806
  results.successCount++;
@@ -757,7 +824,7 @@ function createSendNotificationsHandler(ctx) {
757
824
  await Promise.race(processing);
758
825
  }
759
826
  }
760
- await ctx.db.cleanupOldTasks(7);
827
+ await db.cleanupOldTasks(7);
761
828
  const executionTime = Date.now() - startTime;
762
829
  return {
763
830
  status: 200,
@@ -784,30 +851,33 @@ function createSendNotificationsHandler(ctx) {
784
851
  // src/server/handlers/update-message.js
785
852
  function createUpdateMessageHandler(ctx) {
786
853
  async function PUT(url, headers, body) {
854
+ const tenantResult = await ctx.tenantManager.resolveTenant(headers, { url });
855
+ if (!tenantResult.ok) {
856
+ return tenantResult.error;
857
+ }
858
+ const tenantCtx = tenantResult.context;
859
+ const db = tenantCtx.db;
860
+ const masterKey = tenantCtx.masterKey;
787
861
  const u = new URL(url, "https://dummy");
788
862
  const taskUuid = u.searchParams.get("id");
789
863
  if (!taskUuid) {
790
864
  return { status: 400, body: { success: false, error: { code: "TASK_ID_REQUIRED", message: "\u7F3A\u5C11\u4EFB\u52A1ID" } } };
791
865
  }
792
- const userId = headers["x-user-id"];
866
+ const userId = getHeader(headers, "x-user-id");
793
867
  if (!userId) {
794
868
  return { status: 400, body: { success: false, error: { code: "USER_ID_REQUIRED", message: "\u7F3A\u5C11\u7528\u6237\u6807\u8BC6\u7B26" } } };
795
869
  }
796
870
  if (!isValidUUIDv4(userId)) {
797
871
  return { status: 400, body: { success: false, error: { code: "INVALID_USER_ID_FORMAT", message: "X-User-Id \u5FC5\u987B\u662F UUID v4 \u683C\u5F0F" } } };
798
872
  }
799
- const isEncrypted = headers["x-payload-encrypted"] === "true";
800
- const encryptionVersion = headers["x-encryption-version"];
873
+ const isEncrypted = getHeader(headers, "x-payload-encrypted") === "true";
874
+ const encryptionVersion = getHeader(headers, "x-encryption-version");
801
875
  if (!isEncrypted) {
802
876
  return { status: 400, body: { success: false, error: { code: "ENCRYPTION_REQUIRED", message: "\u8BF7\u6C42\u4F53\u5FC5\u987B\u52A0\u5BC6" } } };
803
877
  }
804
878
  if (encryptionVersion !== "1") {
805
879
  return { status: 400, body: { success: false, error: { code: "UNSUPPORTED_ENCRYPTION_VERSION", message: "\u52A0\u5BC6\u7248\u672C\u4E0D\u652F\u6301" } } };
806
880
  }
807
- const masterKey = await ctx.db.getMasterKey();
808
- if (!masterKey) {
809
- return { status: 503, body: { success: false, error: { code: "MASTER_KEY_NOT_INITIALIZED", message: "\u4E3B\u5BC6\u94A5\u5C1A\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 /api/v1/init-master-key" } } };
810
- }
811
881
  const parsedBody = parseEncryptedBody(body);
812
882
  if (!parsedBody.ok) {
813
883
  return { status: 400, body: { success: false, error: parsedBody.error } };
@@ -829,9 +899,9 @@ function createUpdateMessageHandler(ctx) {
829
899
  if (updates.recurrenceType && !["none", "daily", "weekly"].includes(updates.recurrenceType)) {
830
900
  return { status: 400, body: { success: false, error: { code: "INVALID_UPDATE_DATA", message: "\u66F4\u65B0\u6570\u636E\u683C\u5F0F\u9519\u8BEF", details: { invalidFields: ["recurrenceType"] } } } };
831
901
  }
832
- const existingTask = await ctx.db.getTaskByUuid(taskUuid, userId);
902
+ const existingTask = await db.getTaskByUuid(taskUuid, userId);
833
903
  if (!existingTask) {
834
- const taskStatus = await ctx.db.getTaskStatus(taskUuid, userId);
904
+ const taskStatus = await db.getTaskStatus(taskUuid, userId);
835
905
  if (!taskStatus) {
836
906
  return { status: 404, body: { success: false, error: { code: "TASK_NOT_FOUND", message: "\u6307\u5B9A\u7684\u4EFB\u52A1\u4E0D\u5B58\u5728\u6216\u5DF2\u88AB\u5220\u9664" } } };
837
907
  }
@@ -848,7 +918,7 @@ function createUpdateMessageHandler(ctx) {
848
918
  };
849
919
  const encryptedPayload = encryptForStorage(JSON.stringify(updatedData), userKey);
850
920
  const extraFields = updates.nextSendAt ? { next_send_at: updates.nextSendAt } : void 0;
851
- const result = await ctx.db.updateTaskByUuid(taskUuid, userId, encryptedPayload, extraFields);
921
+ const result = await db.updateTaskByUuid(taskUuid, userId, encryptedPayload, extraFields);
852
922
  if (!result) {
853
923
  return { status: 409, body: { success: false, error: { code: "UPDATE_CONFLICT", message: "\u4EFB\u52A1\u66F4\u65B0\u5931\u8D25\uFF0C\u4EFB\u52A1\u53EF\u80FD\u5DF2\u88AB\u4FEE\u6539\u6216\u5220\u9664" } } };
854
924
  }
@@ -870,19 +940,24 @@ function createUpdateMessageHandler(ctx) {
870
940
  // src/server/handlers/cancel-message.js
871
941
  function createCancelMessageHandler(ctx) {
872
942
  async function DELETE(url, headers) {
943
+ const tenantResult = await ctx.tenantManager.resolveTenant(headers, { url });
944
+ if (!tenantResult.ok) {
945
+ return tenantResult.error;
946
+ }
947
+ const db = tenantResult.context.db;
873
948
  const u = new URL(url, "https://dummy");
874
949
  const taskUuid = u.searchParams.get("id");
875
950
  if (!taskUuid) {
876
951
  return { status: 400, body: { success: false, error: { code: "TASK_ID_REQUIRED", message: "\u7F3A\u5C11\u4EFB\u52A1ID" } } };
877
952
  }
878
- const userId = headers["x-user-id"];
953
+ const userId = getHeader(headers, "x-user-id");
879
954
  if (!userId) {
880
955
  return { status: 400, body: { success: false, error: { code: "USER_ID_REQUIRED", message: "\u7F3A\u5C11\u7528\u6237\u6807\u8BC6\u7B26" } } };
881
956
  }
882
957
  if (!isValidUUIDv4(userId)) {
883
958
  return { status: 400, body: { success: false, error: { code: "INVALID_USER_ID_FORMAT", message: "X-User-Id \u5FC5\u987B\u662F UUID v4 \u683C\u5F0F" } } };
884
959
  }
885
- const deleted = await ctx.db.deleteTaskByUuid(taskUuid, userId);
960
+ const deleted = await db.deleteTaskByUuid(taskUuid, userId);
886
961
  if (!deleted) {
887
962
  return {
888
963
  status: 404,
@@ -903,7 +978,14 @@ function createCancelMessageHandler(ctx) {
903
978
  // src/server/handlers/messages.js
904
979
  function createMessagesHandler(ctx) {
905
980
  async function GET(url, headers) {
906
- const userId = headers["x-user-id"];
981
+ const tenantResult = await ctx.tenantManager.resolveTenant(headers, { url });
982
+ if (!tenantResult.ok) {
983
+ return tenantResult.error;
984
+ }
985
+ const tenantCtx = tenantResult.context;
986
+ const db = tenantCtx.db;
987
+ const masterKey = tenantCtx.masterKey;
988
+ const userId = getHeader(headers, "x-user-id");
907
989
  if (!userId) {
908
990
  return {
909
991
  status: 400,
@@ -926,14 +1008,7 @@ function createMessagesHandler(ctx) {
926
1008
  if (isNaN(offset) || offset < 0) {
927
1009
  return { status: 400, body: { success: false, error: { code: "INVALID_PARAMETERS", message: "offset \u53C2\u6570\u65E0\u6548\uFF0C\u5FC5\u987B\u4E3A\u975E\u8D1F\u6574\u6570" } } };
928
1010
  }
929
- const { tasks, total } = await ctx.db.listTasks(userId, { status, limit, offset });
930
- const masterKey = await ctx.db.getMasterKey();
931
- if (!masterKey) {
932
- return {
933
- status: 503,
934
- body: { success: false, error: { code: "MASTER_KEY_NOT_INITIALIZED", message: "\u4E3B\u5BC6\u94A5\u5C1A\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 /api/v1/init-master-key" } }
935
- };
936
- }
1011
+ const { tasks, total } = await db.listTasks(userId, { status, limit, offset });
937
1012
  const userKey = deriveUserEncryptionKey(userId, masterKey);
938
1013
  const decryptedTasks = tasks.map((task) => {
939
1014
  const decrypted = JSON.parse(decryptFromStorage(task.encrypted_payload, userKey));
@@ -969,6 +1044,318 @@ function createMessagesHandler(ctx) {
969
1044
  return { GET };
970
1045
  }
971
1046
 
1047
+ // src/server/tenant/blob-store.js
1048
+
1049
+ var inMemoryNamespaces = /* @__PURE__ */ new Map();
1050
+ function base64UrlEncode(input) {
1051
+ return Buffer.from(input).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
1052
+ }
1053
+ function base64UrlDecode(input) {
1054
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
1055
+ const padLength = (4 - normalized.length % 4) % 4;
1056
+ return Buffer.from(normalized + "=".repeat(padLength), "base64");
1057
+ }
1058
+ function getKekBuffer(kek) {
1059
+ const value = String(kek || "").trim();
1060
+ if (!value) {
1061
+ throw new Error("[rei-standard-amsg-server] tenant.kek is required");
1062
+ }
1063
+ return _crypto.createHash.call(void 0, "sha256").update(value).digest();
1064
+ }
1065
+ function encryptConfig(config, kekBuffer) {
1066
+ const iv = _crypto.randomBytes.call(void 0, 12);
1067
+ const cipher = _crypto.createCipheriv.call(void 0, "aes-256-gcm", kekBuffer, iv);
1068
+ const plaintext = Buffer.from(JSON.stringify(config), "utf8");
1069
+ const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
1070
+ const authTag = cipher.getAuthTag();
1071
+ return `v1.${base64UrlEncode(iv)}.${base64UrlEncode(authTag)}.${base64UrlEncode(ciphertext)}`;
1072
+ }
1073
+ function decryptConfig(encrypted, kekBuffer) {
1074
+ const parts = String(encrypted || "").split(".");
1075
+ if (parts.length !== 4 || parts[0] !== "v1") {
1076
+ throw new Error("INVALID_TENANT_CONFIG");
1077
+ }
1078
+ const iv = base64UrlDecode(parts[1]);
1079
+ const authTag = base64UrlDecode(parts[2]);
1080
+ const ciphertext = base64UrlDecode(parts[3]);
1081
+ const decipher = _crypto.createDecipheriv.call(void 0, "aes-256-gcm", kekBuffer, iv);
1082
+ decipher.setAuthTag(authTag);
1083
+ const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
1084
+ return JSON.parse(plaintext);
1085
+ }
1086
+ function createInMemoryStore(namespace) {
1087
+ if (!inMemoryNamespaces.has(namespace)) {
1088
+ inMemoryNamespaces.set(namespace, /* @__PURE__ */ new Map());
1089
+ }
1090
+ const namespaceMap = inMemoryNamespaces.get(namespace);
1091
+ return {
1092
+ async get(key) {
1093
+ return namespaceMap.has(key) ? namespaceMap.get(key) : null;
1094
+ },
1095
+ async set(key, value) {
1096
+ namespaceMap.set(key, value);
1097
+ },
1098
+ async delete(key) {
1099
+ namespaceMap.delete(key);
1100
+ }
1101
+ };
1102
+ }
1103
+ async function resolveBlobStore(namespace) {
1104
+ if (globalThis.__REI_BLOB_STORE__ && typeof globalThis.__REI_BLOB_STORE__.get === "function") {
1105
+ return globalThis.__REI_BLOB_STORE__;
1106
+ }
1107
+ try {
1108
+ const blobModule = await Promise.resolve().then(() => _interopRequireWildcard(require("@netlify/blobs")));
1109
+ const getStore = blobModule.getStore || _optionalChain([blobModule, 'access', _ => _.default, 'optionalAccess', _2 => _2.getStore]);
1110
+ if (typeof getStore === "function") {
1111
+ return getStore({ name: namespace });
1112
+ }
1113
+ } catch (_error) {
1114
+ }
1115
+ return createInMemoryStore(namespace);
1116
+ }
1117
+ function createTenantBlobStore(options) {
1118
+ const namespace = String(options.namespace || "rei-tenants").trim() || "rei-tenants";
1119
+ const kekBuffer = getKekBuffer(options.kek);
1120
+ function getTenantKey(tenantId) {
1121
+ return `tenant/${tenantId}`;
1122
+ }
1123
+ return {
1124
+ async getTenantConfig(tenantId) {
1125
+ const store = await resolveBlobStore(namespace);
1126
+ const encrypted = await store.get(getTenantKey(tenantId), { type: "text" });
1127
+ if (!encrypted) return null;
1128
+ return decryptConfig(encrypted, kekBuffer);
1129
+ },
1130
+ async setTenantConfig(config) {
1131
+ const store = await resolveBlobStore(namespace);
1132
+ const encrypted = encryptConfig(config, kekBuffer);
1133
+ await store.set(getTenantKey(config.tenantId), encrypted);
1134
+ },
1135
+ async deleteTenantConfig(tenantId) {
1136
+ const store = await resolveBlobStore(namespace);
1137
+ await store.delete(getTenantKey(tenantId));
1138
+ }
1139
+ };
1140
+ }
1141
+
1142
+ // src/server/tenant/context.js
1143
+
1144
+
1145
+ // src/server/tenant/token.js
1146
+
1147
+ function base64UrlEncode2(input) {
1148
+ return Buffer.from(input).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
1149
+ }
1150
+ function base64UrlDecode2(input) {
1151
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
1152
+ const padLength = (4 - normalized.length % 4) % 4;
1153
+ return Buffer.from(normalized + "=".repeat(padLength), "base64");
1154
+ }
1155
+ function sign(input, secret) {
1156
+ return base64UrlEncode2(_crypto.createHmac.call(void 0, "sha256", secret).update(input).digest());
1157
+ }
1158
+ function nowEpochSeconds() {
1159
+ return Math.floor(Date.now() / 1e3);
1160
+ }
1161
+ function createTenantToken(params, secret) {
1162
+ const issuedAt = nowEpochSeconds();
1163
+ const expiresInSeconds = Number(params.expiresInSeconds || 60 * 60 * 24 * 30);
1164
+ const payload = {
1165
+ tid: params.tenantId,
1166
+ typ: params.type,
1167
+ iat: issuedAt,
1168
+ exp: issuedAt + expiresInSeconds,
1169
+ v: 1
1170
+ };
1171
+ const header = { alg: "HS256", typ: "JWT" };
1172
+ const encodedHeader = base64UrlEncode2(JSON.stringify(header));
1173
+ const encodedPayload = base64UrlEncode2(JSON.stringify(payload));
1174
+ const signingInput = `${encodedHeader}.${encodedPayload}`;
1175
+ const signature = sign(signingInput, secret);
1176
+ return `${signingInput}.${signature}`;
1177
+ }
1178
+ function verifyTenantToken(token, secret, options = {}) {
1179
+ if (!token || typeof token !== "string") {
1180
+ throw new Error("INVALID_TENANT_AUTH");
1181
+ }
1182
+ const parts = token.split(".");
1183
+ if (parts.length !== 3) {
1184
+ throw new Error("INVALID_TENANT_AUTH");
1185
+ }
1186
+ const [encodedHeader, encodedPayload, receivedSignature] = parts;
1187
+ const signingInput = `${encodedHeader}.${encodedPayload}`;
1188
+ const expectedSignature = sign(signingInput, secret);
1189
+ const receivedBuffer = Buffer.from(receivedSignature);
1190
+ const expectedBuffer = Buffer.from(expectedSignature);
1191
+ if (receivedBuffer.length !== expectedBuffer.length || !_crypto.timingSafeEqual.call(void 0, receivedBuffer, expectedBuffer)) {
1192
+ throw new Error("INVALID_TENANT_AUTH");
1193
+ }
1194
+ let payload;
1195
+ try {
1196
+ payload = JSON.parse(base64UrlDecode2(encodedPayload).toString("utf8"));
1197
+ } catch (e3) {
1198
+ throw new Error("INVALID_TENANT_AUTH");
1199
+ }
1200
+ if (!payload || typeof payload !== "object" || payload.v !== 1) {
1201
+ throw new Error("INVALID_TENANT_AUTH");
1202
+ }
1203
+ if (!payload.tid || !payload.typ || !payload.exp) {
1204
+ throw new Error("INVALID_TENANT_AUTH");
1205
+ }
1206
+ if (payload.exp <= nowEpochSeconds()) {
1207
+ throw new Error("INVALID_TENANT_AUTH");
1208
+ }
1209
+ const expectedTypes = options.expectedTypes || [];
1210
+ if (expectedTypes.length > 0 && !expectedTypes.includes(payload.typ)) {
1211
+ throw new Error("INVALID_TENANT_AUTH");
1212
+ }
1213
+ return payload;
1214
+ }
1215
+ function extractBearerToken(headers = {}) {
1216
+ let authorization = "";
1217
+ for (const [key, value] of Object.entries(headers || {})) {
1218
+ if (String(key).toLowerCase() === "authorization") {
1219
+ authorization = String(value || "").trim();
1220
+ break;
1221
+ }
1222
+ }
1223
+ if (!authorization.toLowerCase().startsWith("bearer ")) {
1224
+ return "";
1225
+ }
1226
+ return authorization.slice(7).trim();
1227
+ }
1228
+
1229
+ // src/server/tenant/context.js
1230
+ function fingerprint(masterKey) {
1231
+ return _crypto.createHash.call(void 0, "sha256").update(masterKey).digest("hex").slice(0, 16);
1232
+ }
1233
+ function sanitizeDbConfig(driver, connectionString) {
1234
+ const normalizedDriver = String(driver || "").trim();
1235
+ const normalizedConnectionString = String(connectionString || "").trim();
1236
+ if (!["neon", "pg"].includes(normalizedDriver)) {
1237
+ throw new Error("INVALID_DRIVER");
1238
+ }
1239
+ if (!normalizedConnectionString) {
1240
+ throw new Error("INVALID_DATABASE_URL");
1241
+ }
1242
+ return {
1243
+ driver: normalizedDriver,
1244
+ connectionString: normalizedConnectionString
1245
+ };
1246
+ }
1247
+ function parseTokenFromUrl(url) {
1248
+ if (!url || typeof url !== "string") {
1249
+ return "";
1250
+ }
1251
+ try {
1252
+ const parsed = new URL(url, "https://dummy.local");
1253
+ return String(parsed.searchParams.get("token") || "").trim();
1254
+ } catch (e4) {
1255
+ return "";
1256
+ }
1257
+ }
1258
+ function createTenantContextManager(options) {
1259
+ const tenantStore = options.tenantStore;
1260
+ const tokenSigningKey = String(options.tokenSigningKey || "").trim();
1261
+ const publicBaseUrl = String(options.publicBaseUrl || "").replace(/\/$/, "");
1262
+ const adapterFactory = options.adapterFactory || createAdapter;
1263
+ const adapterCache = /* @__PURE__ */ new Map();
1264
+ if (!tokenSigningKey) {
1265
+ throw new Error("[rei-standard-amsg-server] tenant.tokenSigningKey is required");
1266
+ }
1267
+ async function getOrCreateAdapter(dbConfig) {
1268
+ const cacheKey = `${dbConfig.driver}:${dbConfig.connectionString}`;
1269
+ if (!adapterCache.has(cacheKey)) {
1270
+ const adapter = await adapterFactory(dbConfig);
1271
+ adapterCache.set(cacheKey, adapter);
1272
+ }
1273
+ return adapterCache.get(cacheKey);
1274
+ }
1275
+ async function initializeTenant({ driver, connectionString, tenantId: providedTenantId }) {
1276
+ const dbConfig = sanitizeDbConfig(driver, connectionString);
1277
+ const tenantId = String(providedTenantId || _crypto.randomUUID.call(void 0, )).trim();
1278
+ const existing = await tenantStore.getTenantConfig(tenantId);
1279
+ if (existing) {
1280
+ throw new Error("TENANT_ALREADY_INITIALIZED");
1281
+ }
1282
+ const adapter = await getOrCreateAdapter(dbConfig);
1283
+ await adapter.initSchema();
1284
+ const masterKey = _crypto.randomBytes.call(void 0, 32).toString("hex");
1285
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1286
+ await tenantStore.setTenantConfig({
1287
+ tenantId,
1288
+ db: dbConfig,
1289
+ masterKey,
1290
+ createdAt: now,
1291
+ updatedAt: now
1292
+ });
1293
+ const tenantToken = createTenantToken({ tenantId, type: "tenant" }, tokenSigningKey);
1294
+ const cronToken = createTenantToken({ tenantId, type: "cron" }, tokenSigningKey);
1295
+ return {
1296
+ tenantId,
1297
+ masterKeyFingerprint: fingerprint(masterKey),
1298
+ tenantToken,
1299
+ cronToken,
1300
+ cronWebhookUrl: publicBaseUrl ? `${publicBaseUrl}/api/v1/send-notifications?token=${cronToken}` : ""
1301
+ };
1302
+ }
1303
+ async function resolveTenant(headers, options2 = {}) {
1304
+ const allowCronToken = options2.allowCronToken === true;
1305
+ const url = options2.url || "";
1306
+ const bearerToken = extractBearerToken(headers);
1307
+ const tokenFromQuery = allowCronToken ? parseTokenFromUrl(url) : "";
1308
+ const token = bearerToken || tokenFromQuery;
1309
+ if (!token) {
1310
+ return {
1311
+ ok: false,
1312
+ error: {
1313
+ status: 401,
1314
+ body: { success: false, error: { code: "INVALID_TENANT_AUTH", message: "\u7F3A\u5C11\u79DF\u6237\u9274\u6743\u4FE1\u606F" } }
1315
+ }
1316
+ };
1317
+ }
1318
+ let payload;
1319
+ try {
1320
+ payload = verifyTenantToken(token, tokenSigningKey, {
1321
+ expectedTypes: allowCronToken ? ["cron"] : ["tenant"]
1322
+ });
1323
+ } catch (_error) {
1324
+ return {
1325
+ ok: false,
1326
+ error: {
1327
+ status: 401,
1328
+ body: { success: false, error: { code: "INVALID_TENANT_AUTH", message: "\u79DF\u6237\u4EE4\u724C\u65E0\u6548\u6216\u5DF2\u8FC7\u671F" } }
1329
+ }
1330
+ };
1331
+ }
1332
+ const tenantConfig = await tenantStore.getTenantConfig(payload.tid);
1333
+ if (!tenantConfig) {
1334
+ return {
1335
+ ok: false,
1336
+ error: {
1337
+ status: 401,
1338
+ body: { success: false, error: { code: "INVALID_TENANT_AUTH", message: "\u79DF\u6237\u914D\u7F6E\u4E0D\u5B58\u5728\u6216\u5DF2\u5931\u6548" } }
1339
+ }
1340
+ };
1341
+ }
1342
+ const adapter = await getOrCreateAdapter(tenantConfig.db);
1343
+ return {
1344
+ ok: true,
1345
+ context: {
1346
+ tenantId: tenantConfig.tenantId,
1347
+ tokenType: payload.typ,
1348
+ db: adapter,
1349
+ masterKey: tenantConfig.masterKey
1350
+ }
1351
+ };
1352
+ }
1353
+ return {
1354
+ initializeTenant,
1355
+ resolveTenant
1356
+ };
1357
+ }
1358
+
972
1359
  // src/server/index.js
973
1360
  function normalizeVapidSubject(email) {
974
1361
  const trimmedEmail = String(email || "").trim();
@@ -977,7 +1364,7 @@ function normalizeVapidSubject(email) {
977
1364
  }
978
1365
  async function createReiServer(config) {
979
1366
  if (!config) throw new Error("[rei-standard-amsg-server] config is required");
980
- const adapter = await createAdapter(config.db);
1367
+ if (!config.tenant) throw new Error("[rei-standard-amsg-server] tenant config is required");
981
1368
  let webpushModule;
982
1369
  try {
983
1370
  const webpushImport = await Promise.resolve().then(() => _interopRequireWildcard(require("web-push")));
@@ -995,28 +1382,39 @@ async function createReiServer(config) {
995
1382
  vapid.privateKey
996
1383
  );
997
1384
  }
1385
+ const tenantStore = createTenantBlobStore({
1386
+ namespace: config.tenant.blobNamespace || "rei-tenants",
1387
+ kek: config.tenant.kek
1388
+ });
1389
+ const tenantManager = createTenantContextManager({
1390
+ tenantStore,
1391
+ tokenSigningKey: config.tenant.tokenSigningKey,
1392
+ publicBaseUrl: config.tenant.publicBaseUrl,
1393
+ adapterFactory: config.tenant.adapterFactory
1394
+ });
1395
+ const initSecret = String(config.tenant.initSecret || "").trim();
998
1396
  const ctx = {
999
- db: adapter,
1000
- cronSecret: config.cronSecret || "",
1001
1397
  vapid: {
1002
1398
  email: vapid.email || "",
1003
1399
  publicKey: vapid.publicKey || "",
1004
1400
  privateKey: vapid.privateKey || ""
1005
1401
  },
1006
- webpush: webpushModule
1402
+ webpush: webpushModule,
1403
+ tenant: {
1404
+ initSecret
1405
+ },
1406
+ tenantManager
1007
1407
  };
1008
1408
  return {
1009
1409
  handlers: {
1010
- initDatabase: createInitDatabaseHandler(ctx),
1011
- initMasterKey: createInitMasterKeyHandler(ctx),
1410
+ initTenant: createInitTenantHandler(ctx),
1012
1411
  getUserKey: createGetUserKeyHandler(ctx),
1013
1412
  scheduleMessage: createScheduleMessageHandler(ctx),
1014
1413
  sendNotifications: createSendNotificationsHandler(ctx),
1015
1414
  updateMessage: createUpdateMessageHandler(ctx),
1016
1415
  cancelMessage: createCancelMessageHandler(ctx),
1017
1416
  messages: createMessagesHandler(ctx)
1018
- },
1019
- adapter
1417
+ }
1020
1418
  };
1021
1419
  }
1022
1420
 
@@ -1031,4 +1429,6 @@ async function createReiServer(config) {
1031
1429
 
1032
1430
 
1033
1431
 
1034
- exports.createAdapter = createAdapter; exports.createReiServer = createReiServer; exports.decryptFromStorage = decryptFromStorage; exports.decryptPayload = decryptPayload; exports.deriveUserEncryptionKey = deriveUserEncryptionKey; exports.encryptForStorage = encryptForStorage; exports.isValidISO8601 = isValidISO8601; exports.isValidUUID = isValidUUID; exports.isValidUUIDv4 = isValidUUIDv4; exports.isValidUrl = isValidUrl; exports.validateScheduleMessagePayload = validateScheduleMessagePayload;
1432
+
1433
+
1434
+ exports.createAdapter = createAdapter; exports.createReiServer = createReiServer; exports.createTenantToken = createTenantToken; exports.decryptFromStorage = decryptFromStorage; exports.decryptPayload = decryptPayload; exports.deriveUserEncryptionKey = deriveUserEncryptionKey; exports.encryptForStorage = encryptForStorage; exports.isValidISO8601 = isValidISO8601; exports.isValidUUID = isValidUUID; exports.isValidUUIDv4 = isValidUUIDv4; exports.isValidUrl = isValidUrl; exports.validateScheduleMessagePayload = validateScheduleMessagePayload; exports.verifyTenantToken = verifyTenantToken;