@forklaunch/core 3.0.1 → 3.2.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.
- package/bin/gateway-mock/routes/email.mjs +91 -4
- package/bin/gateway-mock/server.mjs +39 -21
- package/lib/http/emailClient.d.ts +46 -0
- package/lib/http/emailClient.d.ts.map +1 -1
- package/lib/http/index.d.ts +2 -0
- package/lib/http/index.d.ts.map +1 -1
- package/lib/http/index.js +393 -91
- package/lib/http/index.js.map +1 -1
- package/lib/http/index.mjs +351 -53
- package/lib/http/index.mjs.map +1 -1
- package/lib/http/middleware/request/enrichDetails.middleware.d.ts.map +1 -1
- package/lib/http/paymentsClient.d.ts +19 -8
- package/lib/http/paymentsClient.d.ts.map +1 -1
- package/lib/http/previewModelGatewayClient.d.ts +48 -0
- package/lib/http/previewModelGatewayClient.d.ts.map +1 -0
- package/lib/http/telemetry/auditLogger.d.ts +5 -1
- package/lib/http/telemetry/auditLogger.d.ts.map +1 -1
- package/lib/http/telemetry/requestAudit.d.ts +30 -0
- package/lib/http/telemetry/requestAudit.d.ts.map +1 -0
- package/lib/persistence/complianceChanges.d.ts +30 -0
- package/lib/persistence/complianceChanges.d.ts.map +1 -0
- package/lib/persistence/compliancePropertyBuilder.d.ts.map +1 -1
- package/lib/persistence/compliantField.d.ts +41 -8
- package/lib/persistence/compliantField.d.ts.map +1 -1
- package/lib/persistence/defineComplianceEntity.d.ts.map +1 -1
- package/lib/persistence/index.d.ts +2 -1
- package/lib/persistence/index.d.ts.map +1 -1
- package/lib/persistence/index.js +150 -18
- package/lib/persistence/index.js.map +1 -1
- package/lib/persistence/index.mjs +148 -18
- package/lib/persistence/index.mjs.map +1 -1
- package/package.json +34 -34
package/lib/http/index.js
CHANGED
|
@@ -63,12 +63,16 @@ __export(http_exports, {
|
|
|
63
63
|
VoiceClient: () => VoiceClient,
|
|
64
64
|
VoiceRequestError: () => VoiceRequestError,
|
|
65
65
|
WhatsAppRequestError: () => WhatsAppRequestError,
|
|
66
|
+
auditComplianceAccess: () => auditComplianceAccess,
|
|
67
|
+
auditEnabled: () => auditEnabled,
|
|
68
|
+
auditEventType: () => auditEventType,
|
|
66
69
|
createContext: () => createContext,
|
|
67
70
|
createEmailClient: () => createEmailClient,
|
|
68
71
|
createHmacToken: () => createHmacToken,
|
|
69
72
|
createInstanceGatewayTransport: () => createInstanceGatewayTransport,
|
|
70
73
|
createModelGatewayClient: () => createModelGatewayClient,
|
|
71
74
|
createPaymentsClient: () => createPaymentsClient,
|
|
75
|
+
createPreviewModelGatewayClient: () => createPreviewModelGatewayClient,
|
|
72
76
|
createSmsClient: () => createSmsClient,
|
|
73
77
|
createStripeClient: () => createStripeClient,
|
|
74
78
|
createVoiceClient: () => createVoiceClient,
|
|
@@ -114,6 +118,7 @@ __export(http_exports, {
|
|
|
114
118
|
post: () => post,
|
|
115
119
|
put: () => put,
|
|
116
120
|
recordMetric: () => recordMetric,
|
|
121
|
+
recordRequestAudit: () => recordRequestAudit,
|
|
117
122
|
resetProcessHandlersForTesting: () => resetProcessHandlersForTesting,
|
|
118
123
|
signPlatformEvent: () => signPlatformEvent,
|
|
119
124
|
smsSegments: () => smsSegments,
|
|
@@ -122,6 +127,7 @@ __export(http_exports, {
|
|
|
122
127
|
typedAuthHandler: () => typedAuthHandler,
|
|
123
128
|
typedHandler: () => typedHandler,
|
|
124
129
|
validateEmailSendRequest: () => validateEmailSendRequest,
|
|
130
|
+
validateListUnsubscribe: () => validateListUnsubscribe,
|
|
125
131
|
validateSmsSend: () => validateSmsSend,
|
|
126
132
|
verifyPlatformEvent: () => verifyPlatformEvent,
|
|
127
133
|
voiceRequestProblem: () => voiceRequestProblem
|
|
@@ -150,7 +156,7 @@ function cors(corsOptions) {
|
|
|
150
156
|
}
|
|
151
157
|
|
|
152
158
|
// src/http/router/expressLikeRouter.ts
|
|
153
|
-
var
|
|
159
|
+
var import_common12 = require("@forklaunch/common");
|
|
154
160
|
|
|
155
161
|
// src/http/guards/hasPermissionChecks.ts
|
|
156
162
|
function hasPermissionChecks(maybePermissionedAuth) {
|
|
@@ -720,7 +726,7 @@ function discriminateResponseBodies(schemaValidator, responses) {
|
|
|
720
726
|
}
|
|
721
727
|
|
|
722
728
|
// src/http/router/routerSharedLogic.ts
|
|
723
|
-
var
|
|
729
|
+
var import_common11 = require("@forklaunch/common");
|
|
724
730
|
|
|
725
731
|
// src/http/guards/hasVersionedSchema.ts
|
|
726
732
|
function hasVersionedSchema(contractDetails) {
|
|
@@ -799,8 +805,8 @@ async function getCachedJwks(jwksPublicKeyUrl) {
|
|
|
799
805
|
return cachedJwks.value;
|
|
800
806
|
} else {
|
|
801
807
|
const jwksResponse = await fetch(jwksPublicKeyUrl);
|
|
802
|
-
const
|
|
803
|
-
if (!
|
|
808
|
+
const text2 = await jwksResponse.text();
|
|
809
|
+
if (!text2) {
|
|
804
810
|
if (cachedJwks.value) {
|
|
805
811
|
return cachedJwks.value;
|
|
806
812
|
}
|
|
@@ -808,7 +814,7 @@ async function getCachedJwks(jwksPublicKeyUrl) {
|
|
|
808
814
|
`JWKS endpoint returned empty response (status ${jwksResponse.status})`
|
|
809
815
|
);
|
|
810
816
|
}
|
|
811
|
-
const jwks = JSON.parse(
|
|
817
|
+
const jwks = JSON.parse(text2).keys;
|
|
812
818
|
cachedJwks.value = jwks;
|
|
813
819
|
cachedJwks.lastUpdated = /* @__PURE__ */ new Date();
|
|
814
820
|
cachedJwks.ttl = parseInt(
|
|
@@ -1270,7 +1276,164 @@ async function parseRequestAuth(req, res, next) {
|
|
|
1270
1276
|
}
|
|
1271
1277
|
|
|
1272
1278
|
// src/http/middleware/request/enrichDetails.middleware.ts
|
|
1279
|
+
var import_common9 = require("@forklaunch/common");
|
|
1280
|
+
|
|
1281
|
+
// src/http/telemetry/requestAudit.ts
|
|
1273
1282
|
var import_common8 = require("@forklaunch/common");
|
|
1283
|
+
|
|
1284
|
+
// src/persistence/encryptedType.ts
|
|
1285
|
+
var import_core = require("@mikro-orm/core");
|
|
1286
|
+
var import_node_async_hooks = require("async_hooks");
|
|
1287
|
+
var _tenantContext = new import_node_async_hooks.AsyncLocalStorage();
|
|
1288
|
+
|
|
1289
|
+
// src/persistence/complianceChanges.ts
|
|
1290
|
+
var LISTENERS = globalThis[/* @__PURE__ */ Symbol.for("forklaunch.compliance.changeListeners")] ??= /* @__PURE__ */ new Set();
|
|
1291
|
+
function onComplianceChange(listener) {
|
|
1292
|
+
LISTENERS.add(listener);
|
|
1293
|
+
return () => LISTENERS.delete(listener);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
// src/persistence/compliantField.ts
|
|
1297
|
+
var import_core2 = require("@mikro-orm/core");
|
|
1298
|
+
|
|
1299
|
+
// src/persistence/fieldEncryptor.ts
|
|
1300
|
+
var HKDF_SALT = Buffer.alloc(0);
|
|
1301
|
+
|
|
1302
|
+
// src/persistence/compliantField.ts
|
|
1303
|
+
var LISTENERS2 = globalThis[/* @__PURE__ */ Symbol.for("forklaunch.compliance.listeners")] ??= /* @__PURE__ */ new Set();
|
|
1304
|
+
function onComplianceAccess(listener) {
|
|
1305
|
+
LISTENERS2.add(listener);
|
|
1306
|
+
return () => LISTENERS2.delete(listener);
|
|
1307
|
+
}
|
|
1308
|
+
var STATE = globalThis[/* @__PURE__ */ Symbol.for("forklaunch.compliance.state")] ??= /* @__PURE__ */ new WeakMap();
|
|
1309
|
+
var SLOTS = globalThis[/* @__PURE__ */ Symbol.for("forklaunch.compliance.slots")] ??= /* @__PURE__ */ new WeakMap();
|
|
1310
|
+
var LOADED_TENANTS = globalThis[/* @__PURE__ */ Symbol.for("forklaunch.compliance.loadedTenants")] ??= /* @__PURE__ */ new WeakMap();
|
|
1311
|
+
|
|
1312
|
+
// src/http/telemetry/auditLogger.ts
|
|
1313
|
+
var import_crypto3 = require("crypto");
|
|
1314
|
+
var AuditLogger = class {
|
|
1315
|
+
_otel;
|
|
1316
|
+
constructor(otel) {
|
|
1317
|
+
this._otel = otel;
|
|
1318
|
+
}
|
|
1319
|
+
append(entry) {
|
|
1320
|
+
try {
|
|
1321
|
+
this._otel.info(
|
|
1322
|
+
{
|
|
1323
|
+
"log.type": "audit",
|
|
1324
|
+
"audit.timestamp": entry.timestamp,
|
|
1325
|
+
"audit.userId": entry.userId ?? "",
|
|
1326
|
+
"audit.tenantId": entry.tenantId ?? "",
|
|
1327
|
+
"audit.route": entry.route,
|
|
1328
|
+
"audit.method": entry.method,
|
|
1329
|
+
"audit.bodyHash": entry.bodyHash,
|
|
1330
|
+
"audit.status": entry.status,
|
|
1331
|
+
"audit.duration": entry.duration,
|
|
1332
|
+
"audit.redactedFields": entry.redactedFields.join(","),
|
|
1333
|
+
"audit.eventType": entry.eventType,
|
|
1334
|
+
_meta: true
|
|
1335
|
+
},
|
|
1336
|
+
`audit:${entry.eventType} ${entry.method} ${entry.route} ${entry.status}`
|
|
1337
|
+
);
|
|
1338
|
+
} catch (error) {
|
|
1339
|
+
console.error("Failed to emit audit log via OTEL:", error);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
static hashBody(body) {
|
|
1343
|
+
if (body === void 0 || body === null || body === "") {
|
|
1344
|
+
return "";
|
|
1345
|
+
}
|
|
1346
|
+
return (0, import_crypto3.createHash)("sha256").update(body).digest("hex");
|
|
1347
|
+
}
|
|
1348
|
+
};
|
|
1349
|
+
|
|
1350
|
+
// src/http/telemetry/requestAudit.ts
|
|
1351
|
+
function auditEnabled() {
|
|
1352
|
+
return (0, import_common8.getEnvVar)("FORKLAUNCH_AUDIT") !== "off";
|
|
1353
|
+
}
|
|
1354
|
+
var loggers = /* @__PURE__ */ new WeakMap();
|
|
1355
|
+
function loggerFor(otel) {
|
|
1356
|
+
let logger2 = loggers.get(otel);
|
|
1357
|
+
if (!logger2) {
|
|
1358
|
+
logger2 = new AuditLogger(otel);
|
|
1359
|
+
loggers.set(otel, logger2);
|
|
1360
|
+
}
|
|
1361
|
+
return logger2;
|
|
1362
|
+
}
|
|
1363
|
+
function auditEventType(status) {
|
|
1364
|
+
if (status === 401) return "auth_failure";
|
|
1365
|
+
if (status === 403) return "rbac_deny";
|
|
1366
|
+
if (status === 429) return "rate_limit";
|
|
1367
|
+
return "http";
|
|
1368
|
+
}
|
|
1369
|
+
var text = (value) => typeof value === "string" && value ? value : null;
|
|
1370
|
+
function bodyText(body) {
|
|
1371
|
+
if (body == null || body === "") return void 0;
|
|
1372
|
+
if (typeof body === "string" || Buffer.isBuffer(body)) return String(body);
|
|
1373
|
+
try {
|
|
1374
|
+
return JSON.stringify(body);
|
|
1375
|
+
} catch {
|
|
1376
|
+
return void 0;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
function recordRequestAudit(otel, req, status, durationMs) {
|
|
1380
|
+
if (!otel || !auditEnabled()) return;
|
|
1381
|
+
const session = req.session && typeof req.session === "object" ? req.session : void 0;
|
|
1382
|
+
loggerFor(otel).append({
|
|
1383
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1384
|
+
userId: text(session?.sub) ?? text(session?.userId),
|
|
1385
|
+
tenantId: text(session?.activeOrganizationId) ?? text(session?.organizationId),
|
|
1386
|
+
route: req.originalPath ?? req.path ?? "unknown",
|
|
1387
|
+
method: req.method,
|
|
1388
|
+
bodyHash: AuditLogger.hashBody(bodyText(req.body)),
|
|
1389
|
+
status,
|
|
1390
|
+
duration: Math.max(0, Math.round(durationMs)),
|
|
1391
|
+
redactedFields: [],
|
|
1392
|
+
eventType: auditEventType(status)
|
|
1393
|
+
});
|
|
1394
|
+
}
|
|
1395
|
+
var accessAudit;
|
|
1396
|
+
function auditComplianceAccess(otel) {
|
|
1397
|
+
if (!accessAudit && auditEnabled()) {
|
|
1398
|
+
const logger2 = loggerFor(otel);
|
|
1399
|
+
const remove = onComplianceAccess(
|
|
1400
|
+
(event) => logger2.append({
|
|
1401
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1402
|
+
userId: null,
|
|
1403
|
+
tenantId: event.tenantId || null,
|
|
1404
|
+
route: event.field,
|
|
1405
|
+
method: "READ",
|
|
1406
|
+
bodyHash: "",
|
|
1407
|
+
status: 0,
|
|
1408
|
+
duration: 0,
|
|
1409
|
+
redactedFields: [event.field],
|
|
1410
|
+
eventType: "data_access"
|
|
1411
|
+
})
|
|
1412
|
+
);
|
|
1413
|
+
const removeChanges = onComplianceChange(
|
|
1414
|
+
(event) => logger2.append({
|
|
1415
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1416
|
+
userId: null,
|
|
1417
|
+
tenantId: event.tenantId,
|
|
1418
|
+
route: `${event.entity}#${event.id}`,
|
|
1419
|
+
method: event.action.toUpperCase(),
|
|
1420
|
+
bodyHash: "",
|
|
1421
|
+
status: 0,
|
|
1422
|
+
duration: 0,
|
|
1423
|
+
redactedFields: event.sensitiveFields,
|
|
1424
|
+
eventType: "data_change"
|
|
1425
|
+
})
|
|
1426
|
+
);
|
|
1427
|
+
accessAudit = () => {
|
|
1428
|
+
remove();
|
|
1429
|
+
removeChanges();
|
|
1430
|
+
accessAudit = void 0;
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
return accessAudit ?? (() => void 0);
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
// src/http/middleware/request/enrichDetails.middleware.ts
|
|
1274
1437
|
function enrichDetails(path, contractDetails, requestSchema, responseSchemas, openTelemetryCollector, globalOptions) {
|
|
1275
1438
|
return (req, res, next) => {
|
|
1276
1439
|
req.originalPath = path;
|
|
@@ -1281,12 +1444,20 @@ function enrichDetails(path, contractDetails, requestSchema, responseSchemas, op
|
|
|
1281
1444
|
req._globalOptions = globalOptions ?? (() => void 0);
|
|
1282
1445
|
req.context?.span?.setAttribute(ATTR_API_NAME, req.contractDetails?.name);
|
|
1283
1446
|
const startTime = process.hrtime();
|
|
1447
|
+
if (openTelemetryCollector) auditComplianceAccess(openTelemetryCollector);
|
|
1284
1448
|
res.on("finish", () => {
|
|
1449
|
+
const [auditSeconds, auditNanoseconds] = process.hrtime(startTime);
|
|
1450
|
+
recordRequestAudit(
|
|
1451
|
+
openTelemetryCollector,
|
|
1452
|
+
req,
|
|
1453
|
+
Number(res.statusCode),
|
|
1454
|
+
auditSeconds * 1e3 + auditNanoseconds / 1e6
|
|
1455
|
+
);
|
|
1285
1456
|
const [seconds, nanoseconds] = process.hrtime(startTime);
|
|
1286
1457
|
const durationMs = seconds + nanoseconds / 1e9;
|
|
1287
1458
|
httpServerDurationHistogram.record(durationMs, {
|
|
1288
|
-
[import_semantic_conventions.ATTR_SERVICE_NAME]: (0,
|
|
1289
|
-
[ATTR_APPLICATION_ID]: (0,
|
|
1459
|
+
[import_semantic_conventions.ATTR_SERVICE_NAME]: (0, import_common9.getEnvVar)("OTEL_SERVICE_NAME") || "unknown",
|
|
1460
|
+
[ATTR_APPLICATION_ID]: (0, import_common9.getEnvVar)("OTEL_APPLICATION_ID"),
|
|
1290
1461
|
[ATTR_API_NAME]: req.contractDetails?.name || "unknown",
|
|
1291
1462
|
[import_semantic_conventions.ATTR_HTTP_REQUEST_METHOD]: req.method,
|
|
1292
1463
|
[import_semantic_conventions.ATTR_HTTP_ROUTE]: req.originalPath || "unknown",
|
|
@@ -1298,7 +1469,7 @@ function enrichDetails(path, contractDetails, requestSchema, responseSchemas, op
|
|
|
1298
1469
|
}
|
|
1299
1470
|
|
|
1300
1471
|
// src/http/middleware/request/parse.middleware.ts
|
|
1301
|
-
var
|
|
1472
|
+
var import_common10 = require("@forklaunch/common");
|
|
1302
1473
|
var import_validator2 = require("@forklaunch/validator");
|
|
1303
1474
|
|
|
1304
1475
|
// src/http/guards/hasSend.ts
|
|
@@ -1327,7 +1498,7 @@ function parse(req, res, next) {
|
|
|
1327
1498
|
let parsedRequest;
|
|
1328
1499
|
let collectedParseErrors;
|
|
1329
1500
|
if (req.contractDetails.versions) {
|
|
1330
|
-
if ((0,
|
|
1501
|
+
if ((0, import_common10.isRecord)(req.requestSchema)) {
|
|
1331
1502
|
let runningParseErrors = "";
|
|
1332
1503
|
matchedVersions = [];
|
|
1333
1504
|
Object.entries(req.requestSchema).forEach(([version, schema]) => {
|
|
@@ -1558,13 +1729,13 @@ function compileRouteSchemas(contractDetails, schemaValidator) {
|
|
|
1558
1729
|
versionedContractDetails,
|
|
1559
1730
|
contractDetails.params
|
|
1560
1731
|
);
|
|
1561
|
-
if ((0,
|
|
1732
|
+
if ((0, import_common11.isRecord)(requestSchema)) {
|
|
1562
1733
|
requestSchema = {
|
|
1563
1734
|
...requestSchema,
|
|
1564
1735
|
[version]: versionedRequestSchema
|
|
1565
1736
|
};
|
|
1566
1737
|
}
|
|
1567
|
-
if ((0,
|
|
1738
|
+
if ((0, import_common11.isRecord)(responseSchemas)) {
|
|
1568
1739
|
responseSchemas = {
|
|
1569
1740
|
...responseSchemas,
|
|
1570
1741
|
[version]: versionedResponseSchemas
|
|
@@ -1918,8 +2089,8 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
|
|
|
1918
2089
|
...middlewares,
|
|
1919
2090
|
this.#parseAndRunControllerHandler(controllerHandler)
|
|
1920
2091
|
);
|
|
1921
|
-
(0,
|
|
1922
|
-
...this._fetchMap[(0,
|
|
2092
|
+
(0, import_common12.toRecord)(this._fetchMap)[(0, import_common12.sanitizePathSlashes)(`${this.basePath}${path}`)] = {
|
|
2093
|
+
...this._fetchMap[(0, import_common12.sanitizePathSlashes)(`${this.basePath}${path}`)] ?? {},
|
|
1923
2094
|
[method.toUpperCase()]: contractDetails.versions ? Object.fromEntries(
|
|
1924
2095
|
Object.keys(contractDetails.versions).map((version) => [
|
|
1925
2096
|
version,
|
|
@@ -1942,7 +2113,7 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
|
|
|
1942
2113
|
};
|
|
1943
2114
|
const contractDetailsName = contractDetails.name;
|
|
1944
2115
|
if (contractDetailsName) {
|
|
1945
|
-
(0,
|
|
2116
|
+
(0, import_common12.toRecord)(this.sdk)[(0, import_common12.toPrettyCamelCase)(contractDetailsName)] = contractDetails.versions ? Object.fromEntries(
|
|
1946
2117
|
Object.keys(contractDetails.versions).map((version) => [
|
|
1947
2118
|
version,
|
|
1948
2119
|
(req) => this.#localParamRequest(
|
|
@@ -2046,10 +2217,10 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
|
|
|
2046
2217
|
}
|
|
2047
2218
|
addRouterToSdk(router) {
|
|
2048
2219
|
Object.entries(router._fetchMap).map(
|
|
2049
|
-
([key, value]) => (0,
|
|
2220
|
+
([key, value]) => (0, import_common12.toRecord)(this._fetchMap)[(0, import_common12.sanitizePathSlashes)(`${this.basePath}${key}`)] = value
|
|
2050
2221
|
);
|
|
2051
|
-
const existingSdk = this.sdk[router.sdkName ?? (0,
|
|
2052
|
-
(0,
|
|
2222
|
+
const existingSdk = this.sdk[router.sdkName ?? (0, import_common12.toPrettyCamelCase)(router.basePath)];
|
|
2223
|
+
(0, import_common12.toRecord)(this.sdk)[router.sdkName ?? (0, import_common12.toPrettyCamelCase)(router.basePath)] = {
|
|
2053
2224
|
...typeof existingSdk === "object" ? existingSdk : {},
|
|
2054
2225
|
...router.sdk
|
|
2055
2226
|
};
|
|
@@ -2381,7 +2552,7 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
|
|
|
2381
2552
|
unpackSdks(sdks, path, routerUniquenessCache) {
|
|
2382
2553
|
Object.entries(sdks).forEach(([key, maybeHandler]) => {
|
|
2383
2554
|
if (isSdkHandler(maybeHandler)) {
|
|
2384
|
-
const cacheKey = (0,
|
|
2555
|
+
const cacheKey = (0, import_common12.hashString)((0, import_common12.safeStringify)(maybeHandler));
|
|
2385
2556
|
if (routerUniquenessCache.has(cacheKey)) {
|
|
2386
2557
|
throw new Error(`SDK handler ${key} is already registered`);
|
|
2387
2558
|
}
|
|
@@ -2488,7 +2659,7 @@ function isPortBound(port, host = "localhost") {
|
|
|
2488
2659
|
}
|
|
2489
2660
|
|
|
2490
2661
|
// src/http/generateHmacAuthHeaders.ts
|
|
2491
|
-
var
|
|
2662
|
+
var import_crypto4 = require("crypto");
|
|
2492
2663
|
function generateHmacAuthHeaders({
|
|
2493
2664
|
secretKey,
|
|
2494
2665
|
method,
|
|
@@ -2497,7 +2668,7 @@ function generateHmacAuthHeaders({
|
|
|
2497
2668
|
keyId = "default"
|
|
2498
2669
|
}) {
|
|
2499
2670
|
const timestamp = /* @__PURE__ */ new Date();
|
|
2500
|
-
const nonce = (0,
|
|
2671
|
+
const nonce = (0, import_crypto4.randomUUID)();
|
|
2501
2672
|
const signature = createHmacToken({
|
|
2502
2673
|
secretKey,
|
|
2503
2674
|
method,
|
|
@@ -2512,7 +2683,7 @@ function generateHmacAuthHeaders({
|
|
|
2512
2683
|
}
|
|
2513
2684
|
|
|
2514
2685
|
// src/http/instanceGateway.ts
|
|
2515
|
-
var
|
|
2686
|
+
var import_crypto5 = require("crypto");
|
|
2516
2687
|
var INSTANCE_GATEWAY_MOUNT = "/instance-gateway";
|
|
2517
2688
|
var PLATFORM_EVENT_KEY_ID = "platform";
|
|
2518
2689
|
var PLATFORM_EVENTS_PATH = "/platform-events";
|
|
@@ -2633,7 +2804,7 @@ function verifyPlatformEvent(request, options2 = {}) {
|
|
|
2633
2804
|
})
|
|
2634
2805
|
);
|
|
2635
2806
|
const received = Buffer.from(signature);
|
|
2636
|
-
if (expected.length !== received.length || !(0,
|
|
2807
|
+
if (expected.length !== received.length || !(0, import_crypto5.timingSafeEqual)(expected, received)) {
|
|
2637
2808
|
throw new PlatformEventVerificationError("Invalid signature");
|
|
2638
2809
|
}
|
|
2639
2810
|
for (const [seen, at] of seenEventNonces) {
|
|
@@ -2738,6 +2909,92 @@ async function* sseData(stream) {
|
|
|
2738
2909
|
}
|
|
2739
2910
|
}
|
|
2740
2911
|
|
|
2912
|
+
// src/http/previewModelGatewayClient.ts
|
|
2913
|
+
function record(value) {
|
|
2914
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
2915
|
+
}
|
|
2916
|
+
function invalidCatalog() {
|
|
2917
|
+
throw new ModelGatewayRequestError(
|
|
2918
|
+
502,
|
|
2919
|
+
"The local preview model catalog is invalid or expired."
|
|
2920
|
+
);
|
|
2921
|
+
}
|
|
2922
|
+
function previewCatalog(raw) {
|
|
2923
|
+
if (!record(raw) || !record(raw.preview)) return invalidCatalog();
|
|
2924
|
+
const preview = raw.preview;
|
|
2925
|
+
if (Object.keys(raw).some((key) => !["models", "preview"].includes(key)) || Object.keys(preview).some(
|
|
2926
|
+
(key) => !["expiresAt", "maxCalls", "maxUnits"].includes(key)
|
|
2927
|
+
) || !Array.isArray(raw.models) || raw.models.length !== 1 || !record(raw.models[0]) || Object.keys(raw.models[0]).some((key) => key !== "id") || typeof raw.models[0].id !== "string" || !/^[\w.:-]{1,160}$/.test(raw.models[0].id) || typeof preview.expiresAt !== "number" || !Number.isSafeInteger(preview.expiresAt) || preview.expiresAt <= Date.now() || preview.expiresAt > Date.now() + 15 * 6e4 || typeof preview.maxCalls !== "number" || !Number.isSafeInteger(preview.maxCalls) || preview.maxCalls < 1 || preview.maxCalls > 20 || typeof preview.maxUnits !== "number" || !Number.isSafeInteger(preview.maxUnits) || preview.maxUnits < 1 || preview.maxUnits > 1e5)
|
|
2928
|
+
return invalidCatalog();
|
|
2929
|
+
return {
|
|
2930
|
+
models: [{ id: raw.models[0].id }],
|
|
2931
|
+
preview: {
|
|
2932
|
+
expiresAt: preview.expiresAt,
|
|
2933
|
+
maxCalls: preview.maxCalls,
|
|
2934
|
+
maxUnits: preview.maxUnits
|
|
2935
|
+
}
|
|
2936
|
+
};
|
|
2937
|
+
}
|
|
2938
|
+
async function readPreviewCatalog(response) {
|
|
2939
|
+
if (!response.body) return invalidCatalog();
|
|
2940
|
+
const reader = response.body.getReader();
|
|
2941
|
+
const decoder = new TextDecoder();
|
|
2942
|
+
let text2 = "", bytes = 0;
|
|
2943
|
+
try {
|
|
2944
|
+
for (; ; ) {
|
|
2945
|
+
const { done, value } = await reader.read();
|
|
2946
|
+
if (done) break;
|
|
2947
|
+
bytes += value.byteLength;
|
|
2948
|
+
if (bytes > 8192) return invalidCatalog();
|
|
2949
|
+
text2 += decoder.decode(value, { stream: true });
|
|
2950
|
+
}
|
|
2951
|
+
text2 += decoder.decode();
|
|
2952
|
+
return previewCatalog(JSON.parse(text2));
|
|
2953
|
+
} catch {
|
|
2954
|
+
return invalidCatalog();
|
|
2955
|
+
} finally {
|
|
2956
|
+
await reader.cancel().catch(() => {
|
|
2957
|
+
});
|
|
2958
|
+
reader.releaseLock();
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
function createPreviewModelGatewayClient(options2 = {}) {
|
|
2962
|
+
const transport = createInstanceGatewayTransport(
|
|
2963
|
+
options2,
|
|
2964
|
+
"The local preview model gateway"
|
|
2965
|
+
);
|
|
2966
|
+
async function send(method, route, body) {
|
|
2967
|
+
try {
|
|
2968
|
+
return await transport.request(method, route, body);
|
|
2969
|
+
} catch (error) {
|
|
2970
|
+
if (error instanceof InstanceGatewayRequestError)
|
|
2971
|
+
throw new ModelGatewayRequestError(
|
|
2972
|
+
error.status,
|
|
2973
|
+
"The local preview model request was refused or is unavailable.",
|
|
2974
|
+
error.retryAfterSeconds
|
|
2975
|
+
);
|
|
2976
|
+
throw error;
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
return {
|
|
2980
|
+
async models() {
|
|
2981
|
+
const response = await send("GET", "/models");
|
|
2982
|
+
return readPreviewCatalog(response);
|
|
2983
|
+
},
|
|
2984
|
+
chat: {
|
|
2985
|
+
completions: {
|
|
2986
|
+
async create(request) {
|
|
2987
|
+
return await (await send(
|
|
2988
|
+
"POST",
|
|
2989
|
+
"/models/chat/completions",
|
|
2990
|
+
request
|
|
2991
|
+
)).json();
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
};
|
|
2996
|
+
}
|
|
2997
|
+
|
|
2741
2998
|
// src/http/emailClient.ts
|
|
2742
2999
|
var EmailRequestError = class extends InstanceGatewayRequestError {
|
|
2743
3000
|
name = "EmailRequestError";
|
|
@@ -2748,10 +3005,16 @@ var EMAIL_LIMITS = {
|
|
|
2748
3005
|
maxSubjectLength: 998,
|
|
2749
3006
|
/** text + html, UTF-8 bytes. */
|
|
2750
3007
|
maxBodyBytes: 512 * 1024,
|
|
2751
|
-
maxTags: 10
|
|
3008
|
+
maxTags: 10,
|
|
3009
|
+
/** `listUnsubscribe.url`; keeps the header within SES's limit. */
|
|
3010
|
+
maxUnsubscribeUrlLength: 512
|
|
2752
3011
|
};
|
|
2753
3012
|
var ADDRESS = /^[^\s@<>()[\]\\,;:"]+@[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)+$/;
|
|
2754
3013
|
var TAG_PART = /^[A-Za-z0-9_-]{1,256}$/;
|
|
3014
|
+
var HEADER_SAFE = /^[\x21-\x7e]+$/;
|
|
3015
|
+
var URL_DELIMITERS = /[<>,"\\]/;
|
|
3016
|
+
var MAILBOX = /^[A-Za-z0-9._+-]+@[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)+$/;
|
|
3017
|
+
var UNSUBSCRIBE_FIELDS = /* @__PURE__ */ new Set(["url", "mailto", "oneClick"]);
|
|
2755
3018
|
function isEmailAddress(address) {
|
|
2756
3019
|
return typeof address === "string" && address.length <= EMAIL_LIMITS.maxAddressLength && ADDRESS.test(address);
|
|
2757
3020
|
}
|
|
@@ -2776,6 +3039,72 @@ function addresses(value, field) {
|
|
|
2776
3039
|
}
|
|
2777
3040
|
return list;
|
|
2778
3041
|
}
|
|
3042
|
+
function fail(problem) {
|
|
3043
|
+
refuse(`listUnsubscribe: ${problem}`);
|
|
3044
|
+
}
|
|
3045
|
+
function unsubscribeUrlProblem(url) {
|
|
3046
|
+
if (typeof url !== "string" || url === "") return "url must be a string";
|
|
3047
|
+
if (url.length > EMAIL_LIMITS.maxUnsubscribeUrlLength) {
|
|
3048
|
+
return `url is longer than ${EMAIL_LIMITS.maxUnsubscribeUrlLength} characters`;
|
|
3049
|
+
}
|
|
3050
|
+
if (!HEADER_SAFE.test(url)) {
|
|
3051
|
+
return "url must be printable ASCII with no spaces or line breaks; percent-encode anything else";
|
|
3052
|
+
}
|
|
3053
|
+
if (URL_DELIMITERS.test(url)) {
|
|
3054
|
+
return 'url must not contain < > , " or \\; percent-encode them';
|
|
3055
|
+
}
|
|
3056
|
+
let parsed;
|
|
3057
|
+
try {
|
|
3058
|
+
parsed = new URL(url);
|
|
3059
|
+
} catch {
|
|
3060
|
+
return "url is not a URL";
|
|
3061
|
+
}
|
|
3062
|
+
if (parsed.protocol !== "https:") return "url must be https";
|
|
3063
|
+
if (parsed.username || parsed.password) {
|
|
3064
|
+
return "url must not carry a user name or password";
|
|
3065
|
+
}
|
|
3066
|
+
return null;
|
|
3067
|
+
}
|
|
3068
|
+
function validateListUnsubscribe(value) {
|
|
3069
|
+
if (value === void 0 || value === null) return void 0;
|
|
3070
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
3071
|
+
fail("must be an object");
|
|
3072
|
+
}
|
|
3073
|
+
const unknownFields = Object.keys(value).filter(
|
|
3074
|
+
(key) => !UNSUBSCRIBE_FIELDS.has(key)
|
|
3075
|
+
);
|
|
3076
|
+
if (unknownFields.length) {
|
|
3077
|
+
fail(`unknown field ${unknownFields.map((k) => `'${k}'`).join(", ")}`);
|
|
3078
|
+
}
|
|
3079
|
+
const url = "url" in value ? value.url : void 0;
|
|
3080
|
+
const mailto = "mailto" in value ? value.mailto : void 0;
|
|
3081
|
+
const oneClick = "oneClick" in value ? value.oneClick : void 0;
|
|
3082
|
+
if (url === void 0 && mailto === void 0) {
|
|
3083
|
+
fail("needs a url or a mailto");
|
|
3084
|
+
}
|
|
3085
|
+
if (url !== void 0) {
|
|
3086
|
+
const problem = unsubscribeUrlProblem(url);
|
|
3087
|
+
if (problem) fail(problem);
|
|
3088
|
+
}
|
|
3089
|
+
if (mailto !== void 0 && (typeof mailto !== "string" || mailto.length > EMAIL_LIMITS.maxAddressLength || !MAILBOX.test(mailto))) {
|
|
3090
|
+
fail(
|
|
3091
|
+
`mailto: '${String(mailto).slice(0, 80)}' is not a plain mailbox (letters, digits, . _ + - before the @)`
|
|
3092
|
+
);
|
|
3093
|
+
}
|
|
3094
|
+
if (oneClick !== void 0 && typeof oneClick !== "boolean") {
|
|
3095
|
+
fail("oneClick must be true or false");
|
|
3096
|
+
}
|
|
3097
|
+
if (typeof url === "string") {
|
|
3098
|
+
return {
|
|
3099
|
+
url,
|
|
3100
|
+
...typeof mailto === "string" ? { mailto } : {},
|
|
3101
|
+
...oneClick === true ? { oneClick: true } : {}
|
|
3102
|
+
};
|
|
3103
|
+
}
|
|
3104
|
+
if (oneClick === true) fail("oneClick needs a url (RFC 8058 posts to it)");
|
|
3105
|
+
if (typeof mailto === "string") return { mailto };
|
|
3106
|
+
return fail("needs a url or a mailto");
|
|
3107
|
+
}
|
|
2779
3108
|
function validateEmailSendRequest(request) {
|
|
2780
3109
|
const to = addresses(request.to, "to") ?? refuse("to is required");
|
|
2781
3110
|
const replyTo = addresses(request.replyTo, "replyTo");
|
|
@@ -2807,13 +3136,15 @@ function validateEmailSendRequest(request) {
|
|
|
2807
3136
|
refuse(`tag '${name}' is reserved`);
|
|
2808
3137
|
}
|
|
2809
3138
|
}
|
|
3139
|
+
const listUnsubscribe = validateListUnsubscribe(request.listUnsubscribe);
|
|
2810
3140
|
return {
|
|
2811
3141
|
to,
|
|
2812
3142
|
subject,
|
|
2813
3143
|
...request.text !== void 0 ? { text: request.text } : {},
|
|
2814
3144
|
...request.html !== void 0 ? { html: request.html } : {},
|
|
2815
3145
|
...replyTo ? { replyTo } : {},
|
|
2816
|
-
...names.length ? { tags } : {}
|
|
3146
|
+
...names.length ? { tags } : {},
|
|
3147
|
+
...listUnsubscribe ? { listUnsubscribe } : {}
|
|
2817
3148
|
};
|
|
2818
3149
|
}
|
|
2819
3150
|
function createEmailClient(options2 = {}) {
|
|
@@ -2823,7 +3154,7 @@ function createEmailClient(options2 = {}) {
|
|
|
2823
3154
|
INSTANCE_HMAC_KEY: options2.hmacKey ?? process.env.INSTANCE_HMAC_KEY
|
|
2824
3155
|
})) {
|
|
2825
3156
|
throw new Error(
|
|
2826
|
-
"Email is sent by the ForkLaunch platform
|
|
3157
|
+
"Email is sent by the ForkLaunch platform: PLATFORM_GATEWAY_URL, INSTANCE_ID and INSTANCE_HMAC_KEY must be set. A managed instance and an app deployed with `forklaunch deploy create` get them from the platform at deploy. Locally, `forklaunch infra add <service> email` sets them to the gateway mock in docker-compose."
|
|
2827
3158
|
);
|
|
2828
3159
|
}
|
|
2829
3160
|
const transport = createInstanceGatewayTransport(options2, "Email");
|
|
@@ -3122,8 +3453,8 @@ var VoiceClient = class {
|
|
|
3122
3453
|
async send(method, route, body) {
|
|
3123
3454
|
try {
|
|
3124
3455
|
const response = await this.transport.request(method, route, body);
|
|
3125
|
-
const
|
|
3126
|
-
return
|
|
3456
|
+
const text2 = await response.text();
|
|
3457
|
+
return text2 ? JSON.parse(text2) : void 0;
|
|
3127
3458
|
} catch (error) {
|
|
3128
3459
|
if (error instanceof InstanceGatewayRequestError) {
|
|
3129
3460
|
throw new VoiceRequestError(
|
|
@@ -3251,6 +3582,9 @@ function createStripeClient(options2 = {}) {
|
|
|
3251
3582
|
const explicitGateway = Boolean(
|
|
3252
3583
|
options2.gatewayUrl || options2.instanceId || options2.hmacKey
|
|
3253
3584
|
);
|
|
3585
|
+
if (!explicitGateway && options2.apiKey) {
|
|
3586
|
+
return new StripeClass(options2.apiKey, options2.config);
|
|
3587
|
+
}
|
|
3254
3588
|
if (!explicitGateway && !isManagedInstance()) {
|
|
3255
3589
|
if (!options2.apiKey) {
|
|
3256
3590
|
throw new Error(
|
|
@@ -4353,7 +4687,7 @@ var getCodeForStatus = (status) => {
|
|
|
4353
4687
|
var httpStatusCodes_default = HTTPStatuses;
|
|
4354
4688
|
|
|
4355
4689
|
// src/http/mcpGenerator/mcpGenerator.ts
|
|
4356
|
-
var
|
|
4690
|
+
var import_common13 = require("@forklaunch/common");
|
|
4357
4691
|
var import_zod = require("@forklaunch/validator/zod");
|
|
4358
4692
|
var import_fastmcp = require("fastmcp");
|
|
4359
4693
|
|
|
@@ -4488,7 +4822,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
|
|
|
4488
4822
|
if (discriminatedBody) {
|
|
4489
4823
|
switch (discriminatedBody.parserType) {
|
|
4490
4824
|
case "json": {
|
|
4491
|
-
parsedBody = (0,
|
|
4825
|
+
parsedBody = (0, import_common13.safeStringify)(body);
|
|
4492
4826
|
break;
|
|
4493
4827
|
}
|
|
4494
4828
|
case "text": {
|
|
@@ -4496,12 +4830,12 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
|
|
|
4496
4830
|
break;
|
|
4497
4831
|
}
|
|
4498
4832
|
case "file": {
|
|
4499
|
-
parsedBody = Buffer.from((0,
|
|
4833
|
+
parsedBody = Buffer.from((0, import_common13.safeStringify)(body));
|
|
4500
4834
|
break;
|
|
4501
4835
|
}
|
|
4502
4836
|
case "multipart": {
|
|
4503
4837
|
const formData = new FormData();
|
|
4504
|
-
if ((0,
|
|
4838
|
+
if ((0, import_common13.isRecord)(body)) {
|
|
4505
4839
|
for (const key in body) {
|
|
4506
4840
|
if (typeof body[key] === "string") {
|
|
4507
4841
|
if (schemaValidator.isInstanceOf(
|
|
@@ -4526,11 +4860,11 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
|
|
|
4526
4860
|
break;
|
|
4527
4861
|
}
|
|
4528
4862
|
case "urlEncoded": {
|
|
4529
|
-
if ((0,
|
|
4863
|
+
if ((0, import_common13.isRecord)(body)) {
|
|
4530
4864
|
parsedBody = new URLSearchParams(
|
|
4531
4865
|
Object.entries(body).map(([key, value]) => [
|
|
4532
4866
|
key,
|
|
4533
|
-
(0,
|
|
4867
|
+
(0, import_common13.safeStringify)(value)
|
|
4534
4868
|
])
|
|
4535
4869
|
);
|
|
4536
4870
|
} else {
|
|
@@ -4539,8 +4873,8 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
|
|
|
4539
4873
|
break;
|
|
4540
4874
|
}
|
|
4541
4875
|
default: {
|
|
4542
|
-
(0,
|
|
4543
|
-
parsedBody = (0,
|
|
4876
|
+
(0, import_common13.isNever)(discriminatedBody.parserType);
|
|
4877
|
+
parsedBody = (0, import_common13.safeStringify)(body);
|
|
4544
4878
|
break;
|
|
4545
4879
|
}
|
|
4546
4880
|
}
|
|
@@ -4549,7 +4883,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
|
|
|
4549
4883
|
const queryString = new URLSearchParams(
|
|
4550
4884
|
Object.entries(query).map(([key, value]) => [
|
|
4551
4885
|
key,
|
|
4552
|
-
(0,
|
|
4886
|
+
(0, import_common13.safeStringify)(value)
|
|
4553
4887
|
])
|
|
4554
4888
|
).toString();
|
|
4555
4889
|
url += queryString ? `?${queryString}` : "";
|
|
@@ -4582,7 +4916,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
|
|
|
4582
4916
|
content: [
|
|
4583
4917
|
{
|
|
4584
4918
|
type: "text",
|
|
4585
|
-
text: (0,
|
|
4919
|
+
text: (0, import_common13.safeStringify)(await response.json())
|
|
4586
4920
|
}
|
|
4587
4921
|
]
|
|
4588
4922
|
};
|
|
@@ -4737,20 +5071,20 @@ ${parseErrors.join("\n\n")}`
|
|
|
4737
5071
|
}
|
|
4738
5072
|
|
|
4739
5073
|
// src/http/middleware/response/enrichExpressLikeSend.middleware.ts
|
|
4740
|
-
var
|
|
5074
|
+
var import_common15 = require("@forklaunch/common");
|
|
4741
5075
|
var import_stream = require("stream");
|
|
4742
5076
|
|
|
4743
5077
|
// src/http/telemetry/recordMetric.ts
|
|
4744
|
-
var
|
|
5078
|
+
var import_common14 = require("@forklaunch/common");
|
|
4745
5079
|
var import_semantic_conventions4 = require("@opentelemetry/semantic-conventions");
|
|
4746
5080
|
function recordMetric(req, res) {
|
|
4747
5081
|
if (res.metricRecorded) {
|
|
4748
5082
|
return;
|
|
4749
5083
|
}
|
|
4750
|
-
const serviceName = (0,
|
|
5084
|
+
const serviceName = (0, import_common14.getEnvVar)("OTEL_SERVICE_NAME") || "unknown";
|
|
4751
5085
|
const attrs = {
|
|
4752
5086
|
[import_semantic_conventions4.ATTR_SERVICE_NAME]: serviceName,
|
|
4753
|
-
[ATTR_APPLICATION_ID]: (0,
|
|
5087
|
+
[ATTR_APPLICATION_ID]: (0, import_common14.getEnvVar)("OTEL_APPLICATION_ID"),
|
|
4754
5088
|
[ATTR_API_NAME]: req.contractDetails?.name,
|
|
4755
5089
|
[import_semantic_conventions4.ATTR_HTTP_REQUEST_METHOD]: req.method,
|
|
4756
5090
|
[import_semantic_conventions4.ATTR_HTTP_ROUTE]: req.originalPath,
|
|
@@ -4798,15 +5132,15 @@ function enrichExpressLikeSend(instance, req, res, originalOperation, originalSe
|
|
|
4798
5132
|
`attachment; filename="${data.name}"`
|
|
4799
5133
|
);
|
|
4800
5134
|
}
|
|
4801
|
-
if ((0,
|
|
4802
|
-
import_stream.Readable.from((0,
|
|
5135
|
+
if ((0, import_common15.isNodeJsWriteableStream)(res)) {
|
|
5136
|
+
import_stream.Readable.from((0, import_common15.readableStreamToAsyncIterable)(data.stream())).pipe(res);
|
|
4803
5137
|
} else {
|
|
4804
5138
|
res.type("text/plain");
|
|
4805
5139
|
res.status(500);
|
|
4806
5140
|
originalSend.call(instance, "Not a NodeJS WritableStream");
|
|
4807
5141
|
errorSent = true;
|
|
4808
5142
|
}
|
|
4809
|
-
} else if ((0,
|
|
5143
|
+
} else if ((0, import_common15.isAsyncGenerator)(data)) {
|
|
4810
5144
|
let firstPass = true;
|
|
4811
5145
|
const transformer = new import_stream.Transform({
|
|
4812
5146
|
objectMode: true,
|
|
@@ -4830,7 +5164,7 @@ ${res.locals.errorMessage}`;
|
|
|
4830
5164
|
} else {
|
|
4831
5165
|
let data2 = "";
|
|
4832
5166
|
for (const [key, value] of Object.entries(chunk)) {
|
|
4833
|
-
data2 += `${key}: ${typeof value === "string" ? value : (0,
|
|
5167
|
+
data2 += `${key}: ${typeof value === "string" ? value : (0, import_common15.safeStringify)(value)}
|
|
4834
5168
|
`;
|
|
4835
5169
|
}
|
|
4836
5170
|
data2 += "\n";
|
|
@@ -4841,7 +5175,7 @@ ${res.locals.errorMessage}`;
|
|
|
4841
5175
|
} else if (!errorSent) {
|
|
4842
5176
|
let data2 = "";
|
|
4843
5177
|
for (const [key, value] of Object.entries(chunk)) {
|
|
4844
|
-
data2 += `${key}: ${typeof value === "string" ? value : (0,
|
|
5178
|
+
data2 += `${key}: ${typeof value === "string" ? value : (0, import_common15.safeStringify)(value)}
|
|
4845
5179
|
`;
|
|
4846
5180
|
}
|
|
4847
5181
|
data2 += "\n";
|
|
@@ -4849,7 +5183,7 @@ ${res.locals.errorMessage}`;
|
|
|
4849
5183
|
}
|
|
4850
5184
|
}
|
|
4851
5185
|
});
|
|
4852
|
-
if ((0,
|
|
5186
|
+
if ((0, import_common15.isNodeJsWriteableStream)(res)) {
|
|
4853
5187
|
import_stream.Readable.from(data).pipe(transformer).pipe(res);
|
|
4854
5188
|
} else {
|
|
4855
5189
|
res.type("text/plain");
|
|
@@ -4860,7 +5194,7 @@ ${res.locals.errorMessage}`;
|
|
|
4860
5194
|
} else {
|
|
4861
5195
|
const parserType = responseBodies?.[Number(res.statusCode)]?.parserType;
|
|
4862
5196
|
res.bodyData = data;
|
|
4863
|
-
if ((0,
|
|
5197
|
+
if ((0, import_common15.isRecord)(data)) {
|
|
4864
5198
|
switch (parserType) {
|
|
4865
5199
|
case "json":
|
|
4866
5200
|
res.bodyData = "json" in data ? data.json : data;
|
|
@@ -4881,7 +5215,7 @@ ${res.locals.errorMessage}`;
|
|
|
4881
5215
|
res.bodyData = data;
|
|
4882
5216
|
break;
|
|
4883
5217
|
default:
|
|
4884
|
-
(0,
|
|
5218
|
+
(0, import_common15.isNever)(parserType);
|
|
4885
5219
|
res.bodyData = data;
|
|
4886
5220
|
break;
|
|
4887
5221
|
}
|
|
@@ -4916,7 +5250,7 @@ ${res.locals.errorMessage}`;
|
|
|
4916
5250
|
}
|
|
4917
5251
|
|
|
4918
5252
|
// src/http/openApiV3Generator/openApiV3Generator.ts
|
|
4919
|
-
var
|
|
5253
|
+
var import_common16 = require("@forklaunch/common");
|
|
4920
5254
|
var OPENAPI_DEFAULT_VERSION = /* @__PURE__ */ Symbol("default");
|
|
4921
5255
|
function toUpperCase(str) {
|
|
4922
5256
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
@@ -5139,7 +5473,7 @@ function generateOpenApiSpecs(schemaValidator, serverUrls, serverDescriptions, a
|
|
|
5139
5473
|
].forEach(({ fullPath, router }) => {
|
|
5140
5474
|
const controllerName = transformBasePath(fullPath);
|
|
5141
5475
|
router.routes.forEach((route) => {
|
|
5142
|
-
const openApiPath = (0,
|
|
5476
|
+
const openApiPath = (0, import_common16.openApiCompliantPath)(
|
|
5143
5477
|
`${fullPath}${route.path === "/" ? "" : route.path}`
|
|
5144
5478
|
);
|
|
5145
5479
|
const { name, summary, params, versions, auth, options: options2 } = route.contractDetails;
|
|
@@ -5243,44 +5577,6 @@ function generateOpenApiSpecs(schemaValidator, serverUrls, serverDescriptions, a
|
|
|
5243
5577
|
);
|
|
5244
5578
|
}
|
|
5245
5579
|
|
|
5246
|
-
// src/http/telemetry/auditLogger.ts
|
|
5247
|
-
var import_crypto5 = require("crypto");
|
|
5248
|
-
var AuditLogger = class {
|
|
5249
|
-
_otel;
|
|
5250
|
-
constructor(otel) {
|
|
5251
|
-
this._otel = otel;
|
|
5252
|
-
}
|
|
5253
|
-
append(entry) {
|
|
5254
|
-
try {
|
|
5255
|
-
this._otel.info(
|
|
5256
|
-
{
|
|
5257
|
-
"log.type": "audit",
|
|
5258
|
-
"audit.timestamp": entry.timestamp,
|
|
5259
|
-
"audit.userId": entry.userId ?? "",
|
|
5260
|
-
"audit.tenantId": entry.tenantId ?? "",
|
|
5261
|
-
"audit.route": entry.route,
|
|
5262
|
-
"audit.method": entry.method,
|
|
5263
|
-
"audit.bodyHash": entry.bodyHash,
|
|
5264
|
-
"audit.status": entry.status,
|
|
5265
|
-
"audit.duration": entry.duration,
|
|
5266
|
-
"audit.redactedFields": entry.redactedFields.join(","),
|
|
5267
|
-
"audit.eventType": entry.eventType,
|
|
5268
|
-
_meta: true
|
|
5269
|
-
},
|
|
5270
|
-
`audit:${entry.eventType} ${entry.method} ${entry.route} ${entry.status}`
|
|
5271
|
-
);
|
|
5272
|
-
} catch (error) {
|
|
5273
|
-
console.error("Failed to emit audit log via OTEL:", error);
|
|
5274
|
-
}
|
|
5275
|
-
}
|
|
5276
|
-
static hashBody(body) {
|
|
5277
|
-
if (body === void 0 || body === null || body === "") {
|
|
5278
|
-
return "";
|
|
5279
|
-
}
|
|
5280
|
-
return (0, import_crypto5.createHash)("sha256").update(body).digest("hex");
|
|
5281
|
-
}
|
|
5282
|
-
};
|
|
5283
|
-
|
|
5284
5580
|
// src/http/telemetry/evaluateTelemetryOptions.ts
|
|
5285
5581
|
function evaluateTelemetryOptions(telemetryOptions) {
|
|
5286
5582
|
return {
|
|
@@ -5325,8 +5621,8 @@ var RateLimiter = class {
|
|
|
5325
5621
|
const now = Date.now();
|
|
5326
5622
|
let counter;
|
|
5327
5623
|
try {
|
|
5328
|
-
const
|
|
5329
|
-
counter =
|
|
5624
|
+
const record2 = await this.cache.readRecord(key);
|
|
5625
|
+
counter = record2.value;
|
|
5330
5626
|
if (now >= counter.resetAt) {
|
|
5331
5627
|
counter = { count: 0, resetAt: now + windowMs };
|
|
5332
5628
|
}
|
|
@@ -5404,12 +5700,16 @@ var RateLimiter = class {
|
|
|
5404
5700
|
VoiceClient,
|
|
5405
5701
|
VoiceRequestError,
|
|
5406
5702
|
WhatsAppRequestError,
|
|
5703
|
+
auditComplianceAccess,
|
|
5704
|
+
auditEnabled,
|
|
5705
|
+
auditEventType,
|
|
5407
5706
|
createContext,
|
|
5408
5707
|
createEmailClient,
|
|
5409
5708
|
createHmacToken,
|
|
5410
5709
|
createInstanceGatewayTransport,
|
|
5411
5710
|
createModelGatewayClient,
|
|
5412
5711
|
createPaymentsClient,
|
|
5712
|
+
createPreviewModelGatewayClient,
|
|
5413
5713
|
createSmsClient,
|
|
5414
5714
|
createStripeClient,
|
|
5415
5715
|
createVoiceClient,
|
|
@@ -5455,6 +5755,7 @@ var RateLimiter = class {
|
|
|
5455
5755
|
post,
|
|
5456
5756
|
put,
|
|
5457
5757
|
recordMetric,
|
|
5758
|
+
recordRequestAudit,
|
|
5458
5759
|
resetProcessHandlersForTesting,
|
|
5459
5760
|
signPlatformEvent,
|
|
5460
5761
|
smsSegments,
|
|
@@ -5463,6 +5764,7 @@ var RateLimiter = class {
|
|
|
5463
5764
|
typedAuthHandler,
|
|
5464
5765
|
typedHandler,
|
|
5465
5766
|
validateEmailSendRequest,
|
|
5767
|
+
validateListUnsubscribe,
|
|
5466
5768
|
validateSmsSend,
|
|
5467
5769
|
verifyPlatformEvent,
|
|
5468
5770
|
voiceRequestProblem
|