@digilogiclabs/platform-core 1.4.0 → 1.5.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/dist/index.d.mts +1513 -132
- package/dist/index.d.ts +1513 -132
- package/dist/index.js +1322 -58
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1233 -24
- package/dist/index.mjs.map +1 -1
- package/dist/migrate.js +0 -0
- package/dist/security-headers.js.map +1 -1
- package/dist/security-headers.mjs.map +1 -1
- package/dist/testing.js +3 -1
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +9 -2
- package/dist/testing.mjs.map +1 -1
- package/package.json +11 -11
package/dist/index.js
CHANGED
|
@@ -1761,7 +1761,7 @@ var init_RedisCache = __esm({
|
|
|
1761
1761
|
* Create a RedisCache from configuration
|
|
1762
1762
|
*/
|
|
1763
1763
|
static async create(config) {
|
|
1764
|
-
const { default:
|
|
1764
|
+
const { default: Redis2 } = await import("ioredis");
|
|
1765
1765
|
let client;
|
|
1766
1766
|
if (config.cluster) {
|
|
1767
1767
|
const { Cluster } = await import("ioredis");
|
|
@@ -1774,7 +1774,7 @@ var init_RedisCache = __esm({
|
|
|
1774
1774
|
}
|
|
1775
1775
|
});
|
|
1776
1776
|
} else if (config.sentinel) {
|
|
1777
|
-
client = new
|
|
1777
|
+
client = new Redis2({
|
|
1778
1778
|
sentinels: config.sentinel.sentinels,
|
|
1779
1779
|
name: config.sentinel.name,
|
|
1780
1780
|
password: config.password,
|
|
@@ -1783,7 +1783,7 @@ var init_RedisCache = __esm({
|
|
|
1783
1783
|
});
|
|
1784
1784
|
} else {
|
|
1785
1785
|
const options = toIORedisOptions(config);
|
|
1786
|
-
client = typeof options === "string" ? new
|
|
1786
|
+
client = typeof options === "string" ? new Redis2(options) : new Redis2(options);
|
|
1787
1787
|
}
|
|
1788
1788
|
if (!config.lazyConnect) {
|
|
1789
1789
|
await new Promise((resolve, reject) => {
|
|
@@ -1917,9 +1917,9 @@ var init_RedisCache = __esm({
|
|
|
1917
1917
|
}
|
|
1918
1918
|
async subscribe(channel, callback) {
|
|
1919
1919
|
if (!this.subscriberClient) {
|
|
1920
|
-
const { default:
|
|
1920
|
+
const { default: Redis2 } = await import("ioredis");
|
|
1921
1921
|
const options = toIORedisOptions(this.config);
|
|
1922
|
-
this.subscriberClient = typeof options === "string" ? new
|
|
1922
|
+
this.subscriberClient = typeof options === "string" ? new Redis2(options) : new Redis2(options);
|
|
1923
1923
|
this.subscriberClient.on("message", (ch, message) => {
|
|
1924
1924
|
const callbacks = this.subscriptions.get(ch);
|
|
1925
1925
|
if (callbacks) {
|
|
@@ -6984,9 +6984,7 @@ var init_NodeCrypto = __esm({
|
|
|
6984
6984
|
);
|
|
6985
6985
|
}
|
|
6986
6986
|
this.masterKey = Buffer.from(config.masterKey, "hex");
|
|
6987
|
-
this.hmacKey = config.hmacKey ? Buffer.from(config.hmacKey, "hex") : Buffer.from(
|
|
6988
|
-
(0, import_crypto25.hkdfSync)("sha256", this.masterKey, "", "hmac-key", 32)
|
|
6989
|
-
);
|
|
6987
|
+
this.hmacKey = config.hmacKey ? Buffer.from(config.hmacKey, "hex") : Buffer.from((0, import_crypto25.hkdfSync)("sha256", this.masterKey, "", "hmac-key", 32));
|
|
6990
6988
|
const keyId = this.generateKeyId();
|
|
6991
6989
|
const dek = this.deriveDEK(keyId);
|
|
6992
6990
|
this.keys.set(keyId, {
|
|
@@ -7156,6 +7154,7 @@ __export(src_exports, {
|
|
|
7156
7154
|
CircuitBreakerRegistry: () => CircuitBreakerRegistry,
|
|
7157
7155
|
CircuitOpenError: () => CircuitOpenError,
|
|
7158
7156
|
CommonApiErrors: () => CommonApiErrors,
|
|
7157
|
+
CommonRateLimits: () => CommonRateLimits,
|
|
7159
7158
|
ConsoleEmail: () => ConsoleEmail,
|
|
7160
7159
|
ConsoleLogger: () => ConsoleLogger,
|
|
7161
7160
|
CronPresets: () => CronPresets,
|
|
@@ -7172,17 +7171,21 @@ __export(src_exports, {
|
|
|
7172
7171
|
DatabaseNotification: () => DatabaseNotification,
|
|
7173
7172
|
DatabasePromptStore: () => DatabasePromptStore,
|
|
7174
7173
|
DatabaseProviderSchema: () => DatabaseProviderSchema,
|
|
7174
|
+
DateRangeSchema: () => DateRangeSchema,
|
|
7175
7175
|
DefaultTimeouts: () => DefaultTimeouts,
|
|
7176
7176
|
EmailConfigSchema: () => EmailConfigSchema,
|
|
7177
7177
|
EmailProviderSchema: () => EmailProviderSchema,
|
|
7178
|
+
EmailSchema: () => EmailSchema,
|
|
7178
7179
|
EnvSecrets: () => EnvSecrets,
|
|
7179
7180
|
FallbackStrategies: () => FallbackStrategies,
|
|
7180
7181
|
GenericOIDCAuthSSO: () => GenericOIDCAuthSSO,
|
|
7181
7182
|
GoogleAIAdapter: () => GoogleAIAdapter,
|
|
7182
7183
|
HTML_TAG_PATTERN: () => HTML_TAG_PATTERN,
|
|
7183
7184
|
HttpWebhook: () => HttpWebhook,
|
|
7185
|
+
KEYCLOAK_DEFAULT_ROLES: () => KEYCLOAK_DEFAULT_ROLES,
|
|
7184
7186
|
LogLevelSchema: () => LogLevelSchema,
|
|
7185
7187
|
LoggingConfigSchema: () => LoggingConfigSchema,
|
|
7188
|
+
LoginSchema: () => LoginSchema,
|
|
7186
7189
|
MemoryAI: () => MemoryAI,
|
|
7187
7190
|
MemoryAIUsage: () => MemoryAIUsage,
|
|
7188
7191
|
MemoryAuditLog: () => MemoryAuditLog,
|
|
@@ -7221,7 +7224,11 @@ __export(src_exports, {
|
|
|
7221
7224
|
ObservabilityConfigSchema: () => ObservabilityConfigSchema,
|
|
7222
7225
|
OpenAIAdapter: () => OpenAIAdapter,
|
|
7223
7226
|
PG_ERROR_MAP: () => PG_ERROR_MAP,
|
|
7227
|
+
PaginationSchema: () => PaginationSchema,
|
|
7228
|
+
PasswordSchema: () => PasswordSchema,
|
|
7224
7229
|
PaymentErrorMessages: () => PaymentErrorMessages,
|
|
7230
|
+
PersonNameSchema: () => PersonNameSchema,
|
|
7231
|
+
PhoneSchema: () => PhoneSchema,
|
|
7225
7232
|
PineconeRAG: () => PineconeRAG,
|
|
7226
7233
|
PlatformConfigSchema: () => PlatformConfigSchema,
|
|
7227
7234
|
PostgresDatabase: () => PostgresDatabase,
|
|
@@ -7241,9 +7248,14 @@ __export(src_exports, {
|
|
|
7241
7248
|
RetryPredicates: () => RetryPredicates,
|
|
7242
7249
|
S3Storage: () => S3Storage,
|
|
7243
7250
|
SQL: () => SQL,
|
|
7251
|
+
SearchQuerySchema: () => SearchQuerySchema,
|
|
7244
7252
|
SecurityConfigSchema: () => SecurityConfigSchema,
|
|
7245
7253
|
SecurityHeaderPresets: () => SecurityHeaderPresets,
|
|
7254
|
+
SignupSchema: () => SignupSchema,
|
|
7255
|
+
SlugSchema: () => SlugSchema,
|
|
7246
7256
|
SmtpEmail: () => SmtpEmail,
|
|
7257
|
+
StandardAuditActions: () => StandardAuditActions,
|
|
7258
|
+
StandardRateLimitPresets: () => StandardRateLimitPresets,
|
|
7247
7259
|
StorageConfigSchema: () => StorageConfigSchema,
|
|
7248
7260
|
StorageProviderSchema: () => StorageProviderSchema,
|
|
7249
7261
|
StripePayment: () => StripePayment,
|
|
@@ -7259,16 +7271,32 @@ __export(src_exports, {
|
|
|
7259
7271
|
UpstashCache: () => UpstashCache,
|
|
7260
7272
|
WeaviateRAG: () => WeaviateRAG,
|
|
7261
7273
|
WebhookEventTypes: () => WebhookEventTypes,
|
|
7274
|
+
WrapperPresets: () => WrapperPresets,
|
|
7275
|
+
buildAllowlist: () => buildAllowlist,
|
|
7276
|
+
buildAuthCookies: () => buildAuthCookies,
|
|
7277
|
+
buildErrorBody: () => buildErrorBody,
|
|
7278
|
+
buildKeycloakCallbacks: () => buildKeycloakCallbacks,
|
|
7262
7279
|
buildPagination: () => buildPagination,
|
|
7280
|
+
buildRateLimitHeaders: () => buildRateLimitHeaders,
|
|
7281
|
+
buildRateLimitResponseHeaders: () => buildRateLimitResponseHeaders,
|
|
7282
|
+
buildRedirectCallback: () => buildRedirectCallback,
|
|
7283
|
+
buildTokenRefreshParams: () => buildTokenRefreshParams,
|
|
7263
7284
|
calculateBackoff: () => calculateBackoff,
|
|
7264
7285
|
calculateRetryDelay: () => calculateRetryDelay,
|
|
7286
|
+
checkEnvVars: () => checkEnvVars,
|
|
7287
|
+
checkRateLimit: () => checkRateLimit,
|
|
7265
7288
|
classifyError: () => classifyError,
|
|
7289
|
+
closeSharedRedis: () => closeSharedRedis,
|
|
7266
7290
|
composeHookRegistries: () => composeHookRegistries,
|
|
7291
|
+
constantTimeEqual: () => constantTimeEqual,
|
|
7267
7292
|
containsHtml: () => containsHtml,
|
|
7268
7293
|
containsUrls: () => containsUrls,
|
|
7269
7294
|
correlationContext: () => correlationContext,
|
|
7270
7295
|
createAIError: () => createAIError,
|
|
7271
7296
|
createAnthropicAdapter: () => createAnthropicAdapter,
|
|
7297
|
+
createAppLogger: () => createAppLogger,
|
|
7298
|
+
createAuditActor: () => createAuditActor,
|
|
7299
|
+
createAuditLogger: () => createAuditLogger,
|
|
7272
7300
|
createAuthError: () => createAuthError,
|
|
7273
7301
|
createBulkhead: () => createBulkhead,
|
|
7274
7302
|
createCacheMiddleware: () => createCacheMiddleware,
|
|
@@ -7279,6 +7307,7 @@ __export(src_exports, {
|
|
|
7279
7307
|
createErrorReport: () => createErrorReport,
|
|
7280
7308
|
createExpressHealthHandlers: () => createExpressHealthHandlers,
|
|
7281
7309
|
createExpressMetricsHandler: () => createExpressMetricsHandler,
|
|
7310
|
+
createFeatureFlags: () => createFeatureFlags,
|
|
7282
7311
|
createGoogleAIAdapter: () => createGoogleAIAdapter,
|
|
7283
7312
|
createHealthEndpoints: () => createHealthEndpoints,
|
|
7284
7313
|
createHealthServer: () => createHealthServer,
|
|
@@ -7286,6 +7315,7 @@ __export(src_exports, {
|
|
|
7286
7315
|
createIpKeyGenerator: () => createIpKeyGenerator,
|
|
7287
7316
|
createJobContext: () => createJobContext,
|
|
7288
7317
|
createLoggingMiddleware: () => createLoggingMiddleware,
|
|
7318
|
+
createMemoryRateLimitStore: () => createMemoryRateLimitStore,
|
|
7289
7319
|
createMetricsEndpoint: () => createMetricsEndpoint,
|
|
7290
7320
|
createMetricsMiddleware: () => createMetricsMiddleware,
|
|
7291
7321
|
createMetricsServer: () => createMetricsServer,
|
|
@@ -7299,8 +7329,10 @@ __export(src_exports, {
|
|
|
7299
7329
|
createPlatform: () => createPlatform,
|
|
7300
7330
|
createPlatformAsync: () => createPlatformAsync,
|
|
7301
7331
|
createRateLimitMiddleware: () => createRateLimitMiddleware,
|
|
7332
|
+
createRedisClient: () => createRedisClient,
|
|
7302
7333
|
createRequestContext: () => createRequestContext,
|
|
7303
7334
|
createRequestIdMiddleware: () => createRequestIdMiddleware,
|
|
7335
|
+
createSafeTextSchema: () => createSafeTextSchema,
|
|
7304
7336
|
createScopedMetrics: () => createScopedMetrics,
|
|
7305
7337
|
createSlowQueryMiddleware: () => createSlowQueryMiddleware,
|
|
7306
7338
|
createSsoOidcConfigsTable: () => createSsoOidcConfigsTable,
|
|
@@ -7317,8 +7349,13 @@ __export(src_exports, {
|
|
|
7317
7349
|
defangUrl: () => defangUrl,
|
|
7318
7350
|
defineMigration: () => defineMigration,
|
|
7319
7351
|
describeCron: () => describeCron,
|
|
7352
|
+
detectStage: () => detectStage,
|
|
7320
7353
|
enterpriseMigrations: () => enterpriseMigrations,
|
|
7321
7354
|
escapeHtml: () => escapeHtml,
|
|
7355
|
+
extractAuditIp: () => extractAuditIp,
|
|
7356
|
+
extractAuditRequestId: () => extractAuditRequestId,
|
|
7357
|
+
extractAuditUserAgent: () => extractAuditUserAgent,
|
|
7358
|
+
extractClientIp: () => extractClientIp,
|
|
7322
7359
|
filterChannelsByPreferences: () => filterChannelsByPreferences,
|
|
7323
7360
|
formatAmount: () => formatAmount,
|
|
7324
7361
|
generateAuditId: () => generateAuditId,
|
|
@@ -7336,37 +7373,58 @@ __export(src_exports, {
|
|
|
7336
7373
|
generateVersion: () => generateVersion,
|
|
7337
7374
|
generateWebhookId: () => generateWebhookId,
|
|
7338
7375
|
generateWebhookSecret: () => generateWebhookSecret,
|
|
7376
|
+
getBoolEnv: () => getBoolEnv,
|
|
7339
7377
|
getContext: () => getContext,
|
|
7340
|
-
getCorrelationId: () =>
|
|
7378
|
+
getCorrelationId: () => getCorrelationId2,
|
|
7341
7379
|
getDefaultConfig: () => getDefaultConfig,
|
|
7380
|
+
getEndSessionEndpoint: () => getEndSessionEndpoint,
|
|
7342
7381
|
getEnterpriseMigrations: () => getEnterpriseMigrations,
|
|
7382
|
+
getEnvSummary: () => getEnvSummary,
|
|
7383
|
+
getIntEnv: () => getIntEnv,
|
|
7343
7384
|
getLogMeta: () => getLogMeta,
|
|
7344
7385
|
getNextCronRun: () => getNextCronRun,
|
|
7386
|
+
getOptionalEnv: () => getOptionalEnv,
|
|
7387
|
+
getRateLimitStatus: () => getRateLimitStatus,
|
|
7345
7388
|
getRequestId: () => getRequestId,
|
|
7389
|
+
getRequiredEnv: () => getRequiredEnv,
|
|
7390
|
+
getSharedRedis: () => getSharedRedis,
|
|
7346
7391
|
getTenantId: () => getTenantId,
|
|
7392
|
+
getTokenEndpoint: () => getTokenEndpoint,
|
|
7347
7393
|
getTraceId: () => getTraceId,
|
|
7348
7394
|
getUserId: () => getUserId,
|
|
7395
|
+
hasAllRoles: () => hasAllRoles,
|
|
7396
|
+
hasAnyRole: () => hasAnyRole,
|
|
7397
|
+
hasRole: () => hasRole,
|
|
7349
7398
|
isAIError: () => isAIError,
|
|
7399
|
+
isAllowlisted: () => isAllowlisted,
|
|
7350
7400
|
isApiError: () => isApiError,
|
|
7351
7401
|
isAuthError: () => isAuthError,
|
|
7352
7402
|
isInContext: () => isInContext,
|
|
7353
7403
|
isInQuietHours: () => isInQuietHours,
|
|
7354
7404
|
isPaymentError: () => isPaymentError,
|
|
7405
|
+
isTokenExpired: () => isTokenExpired,
|
|
7355
7406
|
isValidCron: () => isValidCron,
|
|
7356
7407
|
loadConfig: () => loadConfig,
|
|
7357
7408
|
matchAction: () => matchAction,
|
|
7358
7409
|
matchEventType: () => matchEventType,
|
|
7410
|
+
parseKeycloakRoles: () => parseKeycloakRoles,
|
|
7359
7411
|
raceTimeout: () => raceTimeout,
|
|
7412
|
+
refreshKeycloakToken: () => refreshKeycloakToken,
|
|
7413
|
+
resetRateLimitForKey: () => resetRateLimitForKey,
|
|
7414
|
+
resolveIdentifier: () => resolveIdentifier,
|
|
7415
|
+
resolveRateLimitIdentifier: () => resolveRateLimitIdentifier,
|
|
7360
7416
|
retryable: () => retryable,
|
|
7361
7417
|
runWithContext: () => runWithContext,
|
|
7362
7418
|
runWithContextAsync: () => runWithContextAsync,
|
|
7363
7419
|
safeValidateConfig: () => safeValidateConfig,
|
|
7420
|
+
sanitizeApiError: () => sanitizeApiError,
|
|
7364
7421
|
sanitizeForEmail: () => sanitizeForEmail,
|
|
7365
7422
|
sqlMigration: () => sqlMigration,
|
|
7366
7423
|
stripHtml: () => stripHtml,
|
|
7367
7424
|
timedHealthCheck: () => timedHealthCheck,
|
|
7368
7425
|
toHealthCheckResult: () => toHealthCheckResult,
|
|
7369
7426
|
validateConfig: () => validateConfig,
|
|
7427
|
+
validateEnvVars: () => validateEnvVars,
|
|
7370
7428
|
withCorrelation: () => withCorrelation,
|
|
7371
7429
|
withCorrelationAsync: () => withCorrelationAsync,
|
|
7372
7430
|
withFallback: () => withFallback,
|
|
@@ -14352,7 +14410,9 @@ var RAGConfigSchema = import_zod.z.object({
|
|
|
14352
14410
|
var CryptoConfigSchema = import_zod.z.object({
|
|
14353
14411
|
enabled: import_zod.z.boolean().default(false).describe("Enable field-level encryption"),
|
|
14354
14412
|
masterKey: import_zod.z.string().optional().describe("256-bit master key as hex (64 chars). Required when enabled."),
|
|
14355
|
-
hmacKey: import_zod.z.string().optional().describe(
|
|
14413
|
+
hmacKey: import_zod.z.string().optional().describe(
|
|
14414
|
+
"HMAC key for deterministic hashing (derived from master key if not provided)"
|
|
14415
|
+
)
|
|
14356
14416
|
}).refine(
|
|
14357
14417
|
(data) => {
|
|
14358
14418
|
if (data.enabled) {
|
|
@@ -15867,9 +15927,9 @@ async function createCacheAdapter(config) {
|
|
|
15867
15927
|
"Upstash requires UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN environment variables"
|
|
15868
15928
|
);
|
|
15869
15929
|
}
|
|
15870
|
-
const { Redis } = await import("@upstash/redis");
|
|
15930
|
+
const { Redis: Redis2 } = await import("@upstash/redis");
|
|
15871
15931
|
const { UpstashCache: UpstashCache2 } = await Promise.resolve().then(() => (init_UpstashCache(), UpstashCache_exports));
|
|
15872
|
-
const client = new
|
|
15932
|
+
const client = new Redis2({
|
|
15873
15933
|
url: config.cache.upstashUrl,
|
|
15874
15934
|
token: config.cache.upstashToken
|
|
15875
15935
|
});
|
|
@@ -16168,15 +16228,17 @@ function validateTlsSecurity(config) {
|
|
|
16168
16228
|
async function createPlatformAsync(config) {
|
|
16169
16229
|
const finalConfig = config ? deepMerge(loadConfig(), config) : loadConfig();
|
|
16170
16230
|
validateTlsSecurity(finalConfig);
|
|
16171
|
-
const [db, cache, storage, email, queue, tracing, crypto2] = await Promise.all(
|
|
16172
|
-
|
|
16173
|
-
|
|
16174
|
-
|
|
16175
|
-
|
|
16176
|
-
|
|
16177
|
-
|
|
16178
|
-
|
|
16179
|
-
|
|
16231
|
+
const [db, cache, storage, email, queue, tracing, crypto2] = await Promise.all(
|
|
16232
|
+
[
|
|
16233
|
+
createDatabaseAdapter(finalConfig),
|
|
16234
|
+
createCacheAdapter(finalConfig),
|
|
16235
|
+
createStorageAdapter(finalConfig),
|
|
16236
|
+
createEmailAdapter(finalConfig),
|
|
16237
|
+
createQueueAdapter(finalConfig),
|
|
16238
|
+
createTracingAdapter(finalConfig),
|
|
16239
|
+
createCryptoAdapter(finalConfig)
|
|
16240
|
+
]
|
|
16241
|
+
);
|
|
16180
16242
|
const logger = createLogger(finalConfig);
|
|
16181
16243
|
const metrics = createMetrics(finalConfig);
|
|
16182
16244
|
const ai = await createAIAdapter(finalConfig);
|
|
@@ -17756,6 +17818,7 @@ var FallbackStrategies = {
|
|
|
17756
17818
|
};
|
|
17757
17819
|
|
|
17758
17820
|
// src/security.ts
|
|
17821
|
+
var import_crypto27 = require("crypto");
|
|
17759
17822
|
var URL_PROTOCOL_PATTERN = /(https?:\/\/|ftp:\/\/|www\.)\S+/i;
|
|
17760
17823
|
var URL_DOMAIN_PATTERN = /\b[\w.-]+\.(com|net|org|io|co|dev|app|xyz|info|biz|me|us|uk|edu|gov)\b/i;
|
|
17761
17824
|
var HTML_TAG_PATTERN = /<[^>]*>/;
|
|
@@ -17780,6 +17843,37 @@ function defangUrl(str) {
|
|
|
17780
17843
|
function sanitizeForEmail(str) {
|
|
17781
17844
|
return escapeHtml(str);
|
|
17782
17845
|
}
|
|
17846
|
+
function constantTimeEqual(a, b) {
|
|
17847
|
+
try {
|
|
17848
|
+
const aBuf = Buffer.from(a, "utf-8");
|
|
17849
|
+
const bBuf = Buffer.from(b, "utf-8");
|
|
17850
|
+
if (aBuf.length !== bBuf.length) return false;
|
|
17851
|
+
return (0, import_crypto27.timingSafeEqual)(aBuf, bBuf);
|
|
17852
|
+
} catch {
|
|
17853
|
+
return false;
|
|
17854
|
+
}
|
|
17855
|
+
}
|
|
17856
|
+
function sanitizeApiError(error, statusCode, isDevelopment = false) {
|
|
17857
|
+
if (statusCode >= 400 && statusCode < 500) {
|
|
17858
|
+
const message = error instanceof Error ? error.message : String(error || "Bad request");
|
|
17859
|
+
return { message };
|
|
17860
|
+
}
|
|
17861
|
+
const result = {
|
|
17862
|
+
message: "An internal error occurred. Please try again later.",
|
|
17863
|
+
code: "INTERNAL_ERROR"
|
|
17864
|
+
};
|
|
17865
|
+
if (isDevelopment && error instanceof Error) {
|
|
17866
|
+
result.stack = error.stack;
|
|
17867
|
+
}
|
|
17868
|
+
return result;
|
|
17869
|
+
}
|
|
17870
|
+
function getCorrelationId2(headers) {
|
|
17871
|
+
const get = typeof headers === "function" ? headers : (name) => {
|
|
17872
|
+
const val = headers[name] ?? headers[name.toLowerCase()];
|
|
17873
|
+
return Array.isArray(val) ? val[0] : val;
|
|
17874
|
+
};
|
|
17875
|
+
return get("x-request-id") || get("X-Request-ID") || get("x-correlation-id") || get("X-Correlation-ID") || crypto.randomUUID();
|
|
17876
|
+
}
|
|
17783
17877
|
|
|
17784
17878
|
// src/security-headers.ts
|
|
17785
17879
|
var SecurityHeaderPresets = {
|
|
@@ -18013,6 +18107,850 @@ function buildPagination(page, limit, total) {
|
|
|
18013
18107
|
};
|
|
18014
18108
|
}
|
|
18015
18109
|
|
|
18110
|
+
// src/auth/keycloak.ts
|
|
18111
|
+
var KEYCLOAK_DEFAULT_ROLES = [
|
|
18112
|
+
"offline_access",
|
|
18113
|
+
"uma_authorization"
|
|
18114
|
+
];
|
|
18115
|
+
function parseKeycloakRoles(accessToken, additionalDefaultRoles = []) {
|
|
18116
|
+
if (!accessToken) return [];
|
|
18117
|
+
try {
|
|
18118
|
+
const parts = accessToken.split(".");
|
|
18119
|
+
if (parts.length !== 3) return [];
|
|
18120
|
+
const payload = parts[1];
|
|
18121
|
+
const decoded = JSON.parse(atob(payload));
|
|
18122
|
+
const realmRoles = decoded.realm_roles ?? decoded.realm_access?.roles;
|
|
18123
|
+
if (!Array.isArray(realmRoles)) return [];
|
|
18124
|
+
const filterSet = /* @__PURE__ */ new Set([
|
|
18125
|
+
...KEYCLOAK_DEFAULT_ROLES,
|
|
18126
|
+
...additionalDefaultRoles
|
|
18127
|
+
]);
|
|
18128
|
+
return realmRoles.filter(
|
|
18129
|
+
(role) => typeof role === "string" && !filterSet.has(role)
|
|
18130
|
+
);
|
|
18131
|
+
} catch {
|
|
18132
|
+
return [];
|
|
18133
|
+
}
|
|
18134
|
+
}
|
|
18135
|
+
function hasRole(roles, role) {
|
|
18136
|
+
return roles?.includes(role) ?? false;
|
|
18137
|
+
}
|
|
18138
|
+
function hasAnyRole(roles, requiredRoles) {
|
|
18139
|
+
if (!roles || roles.length === 0) return false;
|
|
18140
|
+
return requiredRoles.some((role) => roles.includes(role));
|
|
18141
|
+
}
|
|
18142
|
+
function hasAllRoles(roles, requiredRoles) {
|
|
18143
|
+
if (!roles || roles.length === 0) return false;
|
|
18144
|
+
return requiredRoles.every((role) => roles.includes(role));
|
|
18145
|
+
}
|
|
18146
|
+
function isTokenExpired(expiresAt, bufferMs = 6e4) {
|
|
18147
|
+
if (!expiresAt) return true;
|
|
18148
|
+
return Date.now() >= expiresAt - bufferMs;
|
|
18149
|
+
}
|
|
18150
|
+
function buildTokenRefreshParams(config, refreshToken) {
|
|
18151
|
+
return new URLSearchParams({
|
|
18152
|
+
grant_type: "refresh_token",
|
|
18153
|
+
client_id: config.clientId,
|
|
18154
|
+
client_secret: config.clientSecret,
|
|
18155
|
+
refresh_token: refreshToken
|
|
18156
|
+
});
|
|
18157
|
+
}
|
|
18158
|
+
function getTokenEndpoint(issuer) {
|
|
18159
|
+
const base = issuer.endsWith("/") ? issuer.slice(0, -1) : issuer;
|
|
18160
|
+
return `${base}/protocol/openid-connect/token`;
|
|
18161
|
+
}
|
|
18162
|
+
function getEndSessionEndpoint(issuer) {
|
|
18163
|
+
const base = issuer.endsWith("/") ? issuer.slice(0, -1) : issuer;
|
|
18164
|
+
return `${base}/protocol/openid-connect/logout`;
|
|
18165
|
+
}
|
|
18166
|
+
async function refreshKeycloakToken(config, refreshToken, additionalDefaultRoles) {
|
|
18167
|
+
try {
|
|
18168
|
+
const endpoint = getTokenEndpoint(config.issuer);
|
|
18169
|
+
const params = buildTokenRefreshParams(config, refreshToken);
|
|
18170
|
+
const response = await fetch(endpoint, {
|
|
18171
|
+
method: "POST",
|
|
18172
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
18173
|
+
body: params
|
|
18174
|
+
});
|
|
18175
|
+
if (!response.ok) {
|
|
18176
|
+
const body = await response.text().catch(() => "");
|
|
18177
|
+
return {
|
|
18178
|
+
ok: false,
|
|
18179
|
+
error: `Token refresh failed: HTTP ${response.status} - ${body}`
|
|
18180
|
+
};
|
|
18181
|
+
}
|
|
18182
|
+
const data = await response.json();
|
|
18183
|
+
return {
|
|
18184
|
+
ok: true,
|
|
18185
|
+
tokens: {
|
|
18186
|
+
accessToken: data.access_token,
|
|
18187
|
+
refreshToken: data.refresh_token ?? refreshToken,
|
|
18188
|
+
idToken: data.id_token,
|
|
18189
|
+
expiresAt: Date.now() + data.expires_in * 1e3,
|
|
18190
|
+
roles: parseKeycloakRoles(data.access_token, additionalDefaultRoles)
|
|
18191
|
+
}
|
|
18192
|
+
};
|
|
18193
|
+
} catch (error) {
|
|
18194
|
+
return {
|
|
18195
|
+
ok: false,
|
|
18196
|
+
error: error instanceof Error ? error.message : "Token refresh failed"
|
|
18197
|
+
};
|
|
18198
|
+
}
|
|
18199
|
+
}
|
|
18200
|
+
|
|
18201
|
+
// src/auth/nextjs-keycloak.ts
|
|
18202
|
+
function buildAuthCookies(config = {}) {
|
|
18203
|
+
const { domain, sessionToken = true, callbackUrl = true } = config;
|
|
18204
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
18205
|
+
const cookieDomain = isProduction ? domain : void 0;
|
|
18206
|
+
const baseOptions = {
|
|
18207
|
+
httpOnly: true,
|
|
18208
|
+
sameSite: "lax",
|
|
18209
|
+
path: "/",
|
|
18210
|
+
secure: isProduction,
|
|
18211
|
+
domain: cookieDomain
|
|
18212
|
+
};
|
|
18213
|
+
const cookies = {
|
|
18214
|
+
pkceCodeVerifier: {
|
|
18215
|
+
name: "authjs.pkce.code_verifier",
|
|
18216
|
+
options: { ...baseOptions }
|
|
18217
|
+
},
|
|
18218
|
+
state: {
|
|
18219
|
+
name: "authjs.state",
|
|
18220
|
+
options: { ...baseOptions }
|
|
18221
|
+
}
|
|
18222
|
+
};
|
|
18223
|
+
if (sessionToken) {
|
|
18224
|
+
cookies.sessionToken = {
|
|
18225
|
+
name: isProduction ? "__Secure-authjs.session-token" : "authjs.session-token",
|
|
18226
|
+
options: { ...baseOptions }
|
|
18227
|
+
};
|
|
18228
|
+
}
|
|
18229
|
+
if (callbackUrl) {
|
|
18230
|
+
cookies.callbackUrl = {
|
|
18231
|
+
name: isProduction ? "__Secure-authjs.callback-url" : "authjs.callback-url",
|
|
18232
|
+
options: { ...baseOptions }
|
|
18233
|
+
};
|
|
18234
|
+
}
|
|
18235
|
+
return cookies;
|
|
18236
|
+
}
|
|
18237
|
+
function buildRedirectCallback(config = {}) {
|
|
18238
|
+
const { allowWwwVariant = false } = config;
|
|
18239
|
+
return async ({ url, baseUrl }) => {
|
|
18240
|
+
if (url.startsWith("/")) return `${baseUrl}${url}`;
|
|
18241
|
+
try {
|
|
18242
|
+
if (new URL(url).origin === baseUrl) return url;
|
|
18243
|
+
} catch {
|
|
18244
|
+
return baseUrl;
|
|
18245
|
+
}
|
|
18246
|
+
if (allowWwwVariant) {
|
|
18247
|
+
try {
|
|
18248
|
+
const urlHost = new URL(url).hostname;
|
|
18249
|
+
const baseHost = new URL(baseUrl).hostname;
|
|
18250
|
+
if (urlHost === `www.${baseHost}` || baseHost === `www.${urlHost}`) {
|
|
18251
|
+
return url;
|
|
18252
|
+
}
|
|
18253
|
+
} catch {
|
|
18254
|
+
}
|
|
18255
|
+
}
|
|
18256
|
+
return baseUrl;
|
|
18257
|
+
};
|
|
18258
|
+
}
|
|
18259
|
+
function buildKeycloakCallbacks(config) {
|
|
18260
|
+
const {
|
|
18261
|
+
issuer,
|
|
18262
|
+
clientId,
|
|
18263
|
+
clientSecret,
|
|
18264
|
+
defaultRoles = [],
|
|
18265
|
+
debug = process.env.NODE_ENV === "development"
|
|
18266
|
+
} = config;
|
|
18267
|
+
const kcConfig = { issuer, clientId, clientSecret };
|
|
18268
|
+
function log(message, meta) {
|
|
18269
|
+
if (debug) {
|
|
18270
|
+
console.log(`[Auth] ${message}`, meta ? JSON.stringify(meta) : "");
|
|
18271
|
+
}
|
|
18272
|
+
}
|
|
18273
|
+
return {
|
|
18274
|
+
/**
|
|
18275
|
+
* JWT callback — stores Keycloak tokens and handles refresh.
|
|
18276
|
+
*
|
|
18277
|
+
* Compatible with Auth.js v5 JWT callback signature.
|
|
18278
|
+
*/
|
|
18279
|
+
async jwt({
|
|
18280
|
+
token,
|
|
18281
|
+
user,
|
|
18282
|
+
account
|
|
18283
|
+
}) {
|
|
18284
|
+
if (user) {
|
|
18285
|
+
token.id = token.sub ?? user.id;
|
|
18286
|
+
}
|
|
18287
|
+
if (account?.provider === "keycloak") {
|
|
18288
|
+
token.accessToken = account.access_token;
|
|
18289
|
+
token.refreshToken = account.refresh_token;
|
|
18290
|
+
token.idToken = account.id_token;
|
|
18291
|
+
token.roles = parseKeycloakRoles(
|
|
18292
|
+
account.access_token,
|
|
18293
|
+
defaultRoles
|
|
18294
|
+
);
|
|
18295
|
+
token.accessTokenExpires = account.expires_at ? account.expires_at * 1e3 : Date.now() + 3e5;
|
|
18296
|
+
return token;
|
|
18297
|
+
}
|
|
18298
|
+
if (!isTokenExpired(token.accessTokenExpires)) {
|
|
18299
|
+
return token;
|
|
18300
|
+
}
|
|
18301
|
+
if (token.refreshToken) {
|
|
18302
|
+
log("Token expired, attempting refresh...");
|
|
18303
|
+
const result = await refreshKeycloakToken(
|
|
18304
|
+
kcConfig,
|
|
18305
|
+
token.refreshToken,
|
|
18306
|
+
defaultRoles
|
|
18307
|
+
);
|
|
18308
|
+
if (result.ok) {
|
|
18309
|
+
token.accessToken = result.tokens.accessToken;
|
|
18310
|
+
token.idToken = result.tokens.idToken ?? token.idToken;
|
|
18311
|
+
token.refreshToken = result.tokens.refreshToken ?? token.refreshToken;
|
|
18312
|
+
token.accessTokenExpires = result.tokens.expiresAt;
|
|
18313
|
+
token.roles = result.tokens.roles;
|
|
18314
|
+
delete token.error;
|
|
18315
|
+
log("Token refreshed OK");
|
|
18316
|
+
return token;
|
|
18317
|
+
}
|
|
18318
|
+
log("Token refresh failed", { error: result.error });
|
|
18319
|
+
return { ...token, error: "RefreshTokenError" };
|
|
18320
|
+
}
|
|
18321
|
+
log("Token expired but no refresh token available");
|
|
18322
|
+
return { ...token, error: "RefreshTokenError" };
|
|
18323
|
+
},
|
|
18324
|
+
/**
|
|
18325
|
+
* Session callback — maps JWT fields to the session object.
|
|
18326
|
+
*
|
|
18327
|
+
* Compatible with Auth.js v5 session callback signature.
|
|
18328
|
+
*/
|
|
18329
|
+
async session({
|
|
18330
|
+
session,
|
|
18331
|
+
token
|
|
18332
|
+
}) {
|
|
18333
|
+
const user = session.user;
|
|
18334
|
+
if (user) {
|
|
18335
|
+
user.id = token.id || token.sub;
|
|
18336
|
+
user.roles = token.roles || [];
|
|
18337
|
+
}
|
|
18338
|
+
session.idToken = token.idToken;
|
|
18339
|
+
session.accessToken = token.accessToken;
|
|
18340
|
+
if (token.error) {
|
|
18341
|
+
session.error = token.error;
|
|
18342
|
+
}
|
|
18343
|
+
return session;
|
|
18344
|
+
}
|
|
18345
|
+
};
|
|
18346
|
+
}
|
|
18347
|
+
|
|
18348
|
+
// src/auth/api-security.ts
|
|
18349
|
+
var StandardRateLimitPresets = {
|
|
18350
|
+
/** General API: 100/min, 200/min authenticated */
|
|
18351
|
+
apiGeneral: {
|
|
18352
|
+
limit: 100,
|
|
18353
|
+
windowSeconds: 60,
|
|
18354
|
+
authenticatedLimit: 200
|
|
18355
|
+
},
|
|
18356
|
+
/** Admin operations: 100/min (admins are trusted) */
|
|
18357
|
+
adminAction: {
|
|
18358
|
+
limit: 100,
|
|
18359
|
+
windowSeconds: 60
|
|
18360
|
+
},
|
|
18361
|
+
/** AI/expensive operations: 20/hour, 50/hour authenticated */
|
|
18362
|
+
aiRequest: {
|
|
18363
|
+
limit: 20,
|
|
18364
|
+
windowSeconds: 3600,
|
|
18365
|
+
authenticatedLimit: 50
|
|
18366
|
+
},
|
|
18367
|
+
/** Auth attempts: 5/15min with 15min block */
|
|
18368
|
+
authAttempt: {
|
|
18369
|
+
limit: 5,
|
|
18370
|
+
windowSeconds: 900,
|
|
18371
|
+
blockDurationSeconds: 900
|
|
18372
|
+
},
|
|
18373
|
+
/** Contact/public forms: 10/hour */
|
|
18374
|
+
publicForm: {
|
|
18375
|
+
limit: 10,
|
|
18376
|
+
windowSeconds: 3600,
|
|
18377
|
+
blockDurationSeconds: 1800
|
|
18378
|
+
},
|
|
18379
|
+
/** Checkout/billing: 10/hour with 1hr block */
|
|
18380
|
+
checkout: {
|
|
18381
|
+
limit: 10,
|
|
18382
|
+
windowSeconds: 3600,
|
|
18383
|
+
blockDurationSeconds: 3600
|
|
18384
|
+
}
|
|
18385
|
+
};
|
|
18386
|
+
function resolveRateLimitIdentifier(session, clientIp) {
|
|
18387
|
+
if (session?.user?.id) {
|
|
18388
|
+
return { identifier: `user:${session.user.id}`, isAuthenticated: true };
|
|
18389
|
+
}
|
|
18390
|
+
if (session?.user?.email) {
|
|
18391
|
+
return {
|
|
18392
|
+
identifier: `email:${session.user.email}`,
|
|
18393
|
+
isAuthenticated: true
|
|
18394
|
+
};
|
|
18395
|
+
}
|
|
18396
|
+
return { identifier: `ip:${clientIp}`, isAuthenticated: false };
|
|
18397
|
+
}
|
|
18398
|
+
function extractClientIp(getHeader) {
|
|
18399
|
+
return getHeader("cf-connecting-ip") || getHeader("x-real-ip") || getHeader("x-forwarded-for")?.split(",")[0]?.trim() || "unknown";
|
|
18400
|
+
}
|
|
18401
|
+
function buildRateLimitHeaders(limit, remaining, resetAtMs) {
|
|
18402
|
+
return {
|
|
18403
|
+
"X-RateLimit-Limit": String(limit),
|
|
18404
|
+
"X-RateLimit-Remaining": String(Math.max(0, remaining)),
|
|
18405
|
+
"X-RateLimit-Reset": String(Math.ceil(resetAtMs / 1e3))
|
|
18406
|
+
};
|
|
18407
|
+
}
|
|
18408
|
+
function buildErrorBody(error, extra) {
|
|
18409
|
+
return { error, ...extra };
|
|
18410
|
+
}
|
|
18411
|
+
var WrapperPresets = {
|
|
18412
|
+
/** Public route: no auth, rate limited */
|
|
18413
|
+
public: {
|
|
18414
|
+
requireAuth: false,
|
|
18415
|
+
requireAdmin: false,
|
|
18416
|
+
rateLimit: "apiGeneral"
|
|
18417
|
+
},
|
|
18418
|
+
/** Authenticated route: requires session */
|
|
18419
|
+
authenticated: {
|
|
18420
|
+
requireAuth: true,
|
|
18421
|
+
requireAdmin: false,
|
|
18422
|
+
rateLimit: "apiGeneral"
|
|
18423
|
+
},
|
|
18424
|
+
/** Admin route: requires session with admin role */
|
|
18425
|
+
admin: {
|
|
18426
|
+
requireAuth: true,
|
|
18427
|
+
requireAdmin: true,
|
|
18428
|
+
rateLimit: "adminAction"
|
|
18429
|
+
},
|
|
18430
|
+
/** Legacy admin: accepts session OR bearer token */
|
|
18431
|
+
legacyAdmin: {
|
|
18432
|
+
requireAuth: true,
|
|
18433
|
+
requireAdmin: true,
|
|
18434
|
+
allowBearerToken: true,
|
|
18435
|
+
rateLimit: "adminAction"
|
|
18436
|
+
},
|
|
18437
|
+
/** AI/expensive: requires auth, strict rate limit */
|
|
18438
|
+
ai: {
|
|
18439
|
+
requireAuth: true,
|
|
18440
|
+
requireAdmin: false,
|
|
18441
|
+
rateLimit: "aiRequest"
|
|
18442
|
+
},
|
|
18443
|
+
/** Cron: no rate limit, admin-level access */
|
|
18444
|
+
cron: {
|
|
18445
|
+
requireAuth: true,
|
|
18446
|
+
requireAdmin: false,
|
|
18447
|
+
skipRateLimit: true,
|
|
18448
|
+
skipAudit: false
|
|
18449
|
+
}
|
|
18450
|
+
};
|
|
18451
|
+
|
|
18452
|
+
// src/auth/schemas.ts
|
|
18453
|
+
var import_zod2 = require("zod");
|
|
18454
|
+
var EmailSchema = import_zod2.z.string().trim().toLowerCase().email("Invalid email address");
|
|
18455
|
+
var PasswordSchema = import_zod2.z.string().min(8, "Password must be at least 8 characters").max(100, "Password must be less than 100 characters");
|
|
18456
|
+
var SlugSchema = import_zod2.z.string().min(1, "Slug is required").max(100, "Slug must be less than 100 characters").regex(
|
|
18457
|
+
/^[a-z0-9-]+$/,
|
|
18458
|
+
"Slug can only contain lowercase letters, numbers, and hyphens"
|
|
18459
|
+
);
|
|
18460
|
+
var PhoneSchema = import_zod2.z.string().regex(/^[\d\s()+.\-]{7,20}$/, "Invalid phone number format");
|
|
18461
|
+
var PersonNameSchema = import_zod2.z.string().min(2, "Name must be at least 2 characters").max(100, "Name must be less than 100 characters").regex(
|
|
18462
|
+
/^[a-zA-Z\s\-']+$/,
|
|
18463
|
+
"Name can only contain letters, spaces, hyphens and apostrophes"
|
|
18464
|
+
);
|
|
18465
|
+
function createSafeTextSchema(options) {
|
|
18466
|
+
const {
|
|
18467
|
+
min,
|
|
18468
|
+
max,
|
|
18469
|
+
allowHtml = false,
|
|
18470
|
+
allowUrls = false,
|
|
18471
|
+
fieldName = "Text"
|
|
18472
|
+
} = options ?? {};
|
|
18473
|
+
let schema = import_zod2.z.string();
|
|
18474
|
+
if (min !== void 0)
|
|
18475
|
+
schema = schema.min(min, `${fieldName} must be at least ${min} characters`);
|
|
18476
|
+
if (max !== void 0)
|
|
18477
|
+
schema = schema.max(
|
|
18478
|
+
max,
|
|
18479
|
+
`${fieldName} must be less than ${max} characters`
|
|
18480
|
+
);
|
|
18481
|
+
if (!allowHtml && !allowUrls) {
|
|
18482
|
+
return schema.refine((val) => !HTML_TAG_PATTERN.test(val), "HTML tags are not allowed").refine(
|
|
18483
|
+
(val) => !URL_PROTOCOL_PATTERN.test(val),
|
|
18484
|
+
"Links are not allowed for security reasons"
|
|
18485
|
+
).refine(
|
|
18486
|
+
(val) => !URL_DOMAIN_PATTERN.test(val),
|
|
18487
|
+
"Links are not allowed for security reasons"
|
|
18488
|
+
);
|
|
18489
|
+
}
|
|
18490
|
+
if (!allowHtml) {
|
|
18491
|
+
return schema.refine(
|
|
18492
|
+
(val) => !HTML_TAG_PATTERN.test(val),
|
|
18493
|
+
"HTML tags are not allowed"
|
|
18494
|
+
);
|
|
18495
|
+
}
|
|
18496
|
+
if (!allowUrls) {
|
|
18497
|
+
return schema.refine(
|
|
18498
|
+
(val) => !URL_PROTOCOL_PATTERN.test(val),
|
|
18499
|
+
"Links are not allowed for security reasons"
|
|
18500
|
+
).refine(
|
|
18501
|
+
(val) => !URL_DOMAIN_PATTERN.test(val),
|
|
18502
|
+
"Links are not allowed for security reasons"
|
|
18503
|
+
);
|
|
18504
|
+
}
|
|
18505
|
+
return schema;
|
|
18506
|
+
}
|
|
18507
|
+
var PaginationSchema = import_zod2.z.object({
|
|
18508
|
+
page: import_zod2.z.coerce.number().int().positive().default(1),
|
|
18509
|
+
limit: import_zod2.z.coerce.number().int().positive().max(100).default(20),
|
|
18510
|
+
sortBy: import_zod2.z.string().optional(),
|
|
18511
|
+
sortOrder: import_zod2.z.enum(["asc", "desc"]).default("desc")
|
|
18512
|
+
});
|
|
18513
|
+
var DateRangeSchema = import_zod2.z.object({
|
|
18514
|
+
startDate: import_zod2.z.string().datetime(),
|
|
18515
|
+
endDate: import_zod2.z.string().datetime()
|
|
18516
|
+
}).refine((data) => new Date(data.startDate) <= new Date(data.endDate), {
|
|
18517
|
+
message: "Start date must be before end date"
|
|
18518
|
+
});
|
|
18519
|
+
var SearchQuerySchema = import_zod2.z.object({
|
|
18520
|
+
query: import_zod2.z.string().min(1).max(200).trim(),
|
|
18521
|
+
page: import_zod2.z.coerce.number().int().positive().default(1),
|
|
18522
|
+
limit: import_zod2.z.coerce.number().int().positive().max(50).default(10)
|
|
18523
|
+
});
|
|
18524
|
+
var LoginSchema = import_zod2.z.object({
|
|
18525
|
+
email: EmailSchema,
|
|
18526
|
+
password: PasswordSchema
|
|
18527
|
+
});
|
|
18528
|
+
var SignupSchema = import_zod2.z.object({
|
|
18529
|
+
email: EmailSchema,
|
|
18530
|
+
password: PasswordSchema,
|
|
18531
|
+
name: import_zod2.z.string().min(2).max(100).optional()
|
|
18532
|
+
});
|
|
18533
|
+
|
|
18534
|
+
// src/auth/feature-flags.ts
|
|
18535
|
+
function detectStage() {
|
|
18536
|
+
const stage = process.env.DEPLOYMENT_STAGE;
|
|
18537
|
+
if (stage === "staging" || stage === "preview") return stage;
|
|
18538
|
+
if (process.env.NODE_ENV === "production") return "production";
|
|
18539
|
+
return "development";
|
|
18540
|
+
}
|
|
18541
|
+
function resolveFlagValue(value) {
|
|
18542
|
+
if (typeof value === "boolean") return value;
|
|
18543
|
+
const envValue = process.env[value.envVar];
|
|
18544
|
+
if (envValue === void 0 || envValue === "") {
|
|
18545
|
+
return value.default ?? false;
|
|
18546
|
+
}
|
|
18547
|
+
return envValue === "true" || envValue === "1";
|
|
18548
|
+
}
|
|
18549
|
+
function createFeatureFlags(definitions) {
|
|
18550
|
+
return {
|
|
18551
|
+
/**
|
|
18552
|
+
* Resolve all flags for the current environment.
|
|
18553
|
+
* Call this once at startup or per-request for dynamic flags.
|
|
18554
|
+
*/
|
|
18555
|
+
resolve(stage) {
|
|
18556
|
+
const currentStage = stage ?? detectStage();
|
|
18557
|
+
const resolved = {};
|
|
18558
|
+
for (const [key, def] of Object.entries(definitions)) {
|
|
18559
|
+
const stageKey = currentStage === "preview" ? "staging" : currentStage;
|
|
18560
|
+
resolved[key] = resolveFlagValue(def[stageKey]);
|
|
18561
|
+
}
|
|
18562
|
+
return resolved;
|
|
18563
|
+
},
|
|
18564
|
+
/**
|
|
18565
|
+
* Check if a single flag is enabled.
|
|
18566
|
+
*/
|
|
18567
|
+
isEnabled(flag, stage) {
|
|
18568
|
+
const currentStage = stage ?? detectStage();
|
|
18569
|
+
const def = definitions[flag];
|
|
18570
|
+
const stageKey = currentStage === "preview" ? "staging" : currentStage;
|
|
18571
|
+
return resolveFlagValue(def[stageKey]);
|
|
18572
|
+
},
|
|
18573
|
+
/**
|
|
18574
|
+
* Get the flag definitions (for introspection/admin UI).
|
|
18575
|
+
*/
|
|
18576
|
+
definitions
|
|
18577
|
+
};
|
|
18578
|
+
}
|
|
18579
|
+
function buildAllowlist(config) {
|
|
18580
|
+
const fromEnv = config.envVar ? process.env[config.envVar]?.split(",").map((e) => e.trim().toLowerCase()).filter(Boolean) ?? [] : [];
|
|
18581
|
+
const fallback = config.fallback?.map((e) => e.toLowerCase()) ?? [];
|
|
18582
|
+
return [.../* @__PURE__ */ new Set([...fromEnv, ...fallback])];
|
|
18583
|
+
}
|
|
18584
|
+
function isAllowlisted(email, allowlist) {
|
|
18585
|
+
return allowlist.includes(email.toLowerCase());
|
|
18586
|
+
}
|
|
18587
|
+
|
|
18588
|
+
// src/auth/rate-limiter.ts
|
|
18589
|
+
var CommonRateLimits = {
|
|
18590
|
+
/** General API: 100/min, 200/min authenticated */
|
|
18591
|
+
apiGeneral: {
|
|
18592
|
+
limit: 100,
|
|
18593
|
+
windowSeconds: 60,
|
|
18594
|
+
authenticatedLimit: 200
|
|
18595
|
+
},
|
|
18596
|
+
/** Admin actions: 100/min */
|
|
18597
|
+
adminAction: {
|
|
18598
|
+
limit: 100,
|
|
18599
|
+
windowSeconds: 60
|
|
18600
|
+
},
|
|
18601
|
+
/** Auth attempts: 10/15min with 30min block */
|
|
18602
|
+
authAttempt: {
|
|
18603
|
+
limit: 10,
|
|
18604
|
+
windowSeconds: 900,
|
|
18605
|
+
blockDurationSeconds: 1800
|
|
18606
|
+
},
|
|
18607
|
+
/** AI/expensive requests: 20/hour, 50/hour authenticated */
|
|
18608
|
+
aiRequest: {
|
|
18609
|
+
limit: 20,
|
|
18610
|
+
windowSeconds: 3600,
|
|
18611
|
+
authenticatedLimit: 50
|
|
18612
|
+
},
|
|
18613
|
+
/** Public form submissions: 5/hour with 1hr block */
|
|
18614
|
+
publicForm: {
|
|
18615
|
+
limit: 5,
|
|
18616
|
+
windowSeconds: 3600,
|
|
18617
|
+
blockDurationSeconds: 3600
|
|
18618
|
+
},
|
|
18619
|
+
/** Checkout/billing: 10/hour with 1hr block */
|
|
18620
|
+
checkout: {
|
|
18621
|
+
limit: 10,
|
|
18622
|
+
windowSeconds: 3600,
|
|
18623
|
+
blockDurationSeconds: 3600
|
|
18624
|
+
}
|
|
18625
|
+
};
|
|
18626
|
+
function createMemoryRateLimitStore() {
|
|
18627
|
+
const windows = /* @__PURE__ */ new Map();
|
|
18628
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
18629
|
+
const cleanupInterval = setInterval(() => {
|
|
18630
|
+
const now = Date.now();
|
|
18631
|
+
for (const [key, entry] of windows) {
|
|
18632
|
+
if (entry.expiresAt < now) windows.delete(key);
|
|
18633
|
+
}
|
|
18634
|
+
for (const [key, expiry] of blocks) {
|
|
18635
|
+
if (expiry < now) blocks.delete(key);
|
|
18636
|
+
}
|
|
18637
|
+
}, 60 * 1e3);
|
|
18638
|
+
if (cleanupInterval.unref) {
|
|
18639
|
+
cleanupInterval.unref();
|
|
18640
|
+
}
|
|
18641
|
+
return {
|
|
18642
|
+
async increment(key, windowMs, now) {
|
|
18643
|
+
const windowStart = now - windowMs;
|
|
18644
|
+
let entry = windows.get(key);
|
|
18645
|
+
if (!entry) {
|
|
18646
|
+
entry = { timestamps: [], expiresAt: now + windowMs + 6e4 };
|
|
18647
|
+
windows.set(key, entry);
|
|
18648
|
+
}
|
|
18649
|
+
entry.timestamps = entry.timestamps.filter((t) => t > windowStart);
|
|
18650
|
+
entry.timestamps.push(now);
|
|
18651
|
+
entry.expiresAt = now + windowMs + 6e4;
|
|
18652
|
+
return { count: entry.timestamps.length };
|
|
18653
|
+
},
|
|
18654
|
+
async isBlocked(key) {
|
|
18655
|
+
const expiry = blocks.get(key);
|
|
18656
|
+
if (!expiry || expiry < Date.now()) {
|
|
18657
|
+
blocks.delete(key);
|
|
18658
|
+
return { blocked: false, ttlMs: 0 };
|
|
18659
|
+
}
|
|
18660
|
+
return { blocked: true, ttlMs: expiry - Date.now() };
|
|
18661
|
+
},
|
|
18662
|
+
async setBlock(key, durationSeconds) {
|
|
18663
|
+
blocks.set(key, Date.now() + durationSeconds * 1e3);
|
|
18664
|
+
},
|
|
18665
|
+
async reset(key) {
|
|
18666
|
+
windows.delete(key);
|
|
18667
|
+
blocks.delete(`block:${key}`);
|
|
18668
|
+
blocks.delete(key);
|
|
18669
|
+
}
|
|
18670
|
+
};
|
|
18671
|
+
}
|
|
18672
|
+
var defaultStore;
|
|
18673
|
+
function getDefaultStore() {
|
|
18674
|
+
if (!defaultStore) {
|
|
18675
|
+
defaultStore = createMemoryRateLimitStore();
|
|
18676
|
+
}
|
|
18677
|
+
return defaultStore;
|
|
18678
|
+
}
|
|
18679
|
+
async function checkRateLimit(operation, identifier, rule, options = {}) {
|
|
18680
|
+
const store = options.store ?? getDefaultStore();
|
|
18681
|
+
const limit = options.isAuthenticated && rule.authenticatedLimit ? rule.authenticatedLimit : rule.limit;
|
|
18682
|
+
const now = Date.now();
|
|
18683
|
+
const windowMs = rule.windowSeconds * 1e3;
|
|
18684
|
+
const resetAt = now + windowMs;
|
|
18685
|
+
const key = `ratelimit:${operation}:${identifier}`;
|
|
18686
|
+
const blockKey = `ratelimit:block:${operation}:${identifier}`;
|
|
18687
|
+
try {
|
|
18688
|
+
if (rule.blockDurationSeconds) {
|
|
18689
|
+
const blockStatus = await store.isBlocked(blockKey);
|
|
18690
|
+
if (blockStatus.blocked) {
|
|
18691
|
+
return {
|
|
18692
|
+
allowed: false,
|
|
18693
|
+
remaining: 0,
|
|
18694
|
+
resetAt: now + blockStatus.ttlMs,
|
|
18695
|
+
current: limit + 1,
|
|
18696
|
+
limit,
|
|
18697
|
+
retryAfterSeconds: Math.ceil(blockStatus.ttlMs / 1e3)
|
|
18698
|
+
};
|
|
18699
|
+
}
|
|
18700
|
+
}
|
|
18701
|
+
const { count } = await store.increment(key, windowMs, now);
|
|
18702
|
+
if (count > limit) {
|
|
18703
|
+
options.logger?.warn("Rate limit exceeded", {
|
|
18704
|
+
operation,
|
|
18705
|
+
identifier,
|
|
18706
|
+
current: count,
|
|
18707
|
+
limit
|
|
18708
|
+
});
|
|
18709
|
+
if (rule.blockDurationSeconds) {
|
|
18710
|
+
await store.setBlock(blockKey, rule.blockDurationSeconds);
|
|
18711
|
+
}
|
|
18712
|
+
return {
|
|
18713
|
+
allowed: false,
|
|
18714
|
+
remaining: 0,
|
|
18715
|
+
resetAt,
|
|
18716
|
+
current: count,
|
|
18717
|
+
limit,
|
|
18718
|
+
retryAfterSeconds: Math.ceil(windowMs / 1e3)
|
|
18719
|
+
};
|
|
18720
|
+
}
|
|
18721
|
+
return {
|
|
18722
|
+
allowed: true,
|
|
18723
|
+
remaining: limit - count,
|
|
18724
|
+
resetAt,
|
|
18725
|
+
current: count,
|
|
18726
|
+
limit,
|
|
18727
|
+
retryAfterSeconds: 0
|
|
18728
|
+
};
|
|
18729
|
+
} catch (error) {
|
|
18730
|
+
options.logger?.error("Rate limit check failed, allowing request", {
|
|
18731
|
+
error: error instanceof Error ? error.message : String(error),
|
|
18732
|
+
operation,
|
|
18733
|
+
identifier
|
|
18734
|
+
});
|
|
18735
|
+
return {
|
|
18736
|
+
allowed: true,
|
|
18737
|
+
remaining: limit,
|
|
18738
|
+
resetAt,
|
|
18739
|
+
current: 0,
|
|
18740
|
+
limit,
|
|
18741
|
+
retryAfterSeconds: 0
|
|
18742
|
+
};
|
|
18743
|
+
}
|
|
18744
|
+
}
|
|
18745
|
+
async function getRateLimitStatus(operation, identifier, rule, store) {
|
|
18746
|
+
const s = store ?? getDefaultStore();
|
|
18747
|
+
const key = `ratelimit:${operation}:${identifier}`;
|
|
18748
|
+
const now = Date.now();
|
|
18749
|
+
const windowMs = rule.windowSeconds * 1e3;
|
|
18750
|
+
try {
|
|
18751
|
+
const { count } = await s.increment(key, windowMs, now);
|
|
18752
|
+
return {
|
|
18753
|
+
allowed: count <= rule.limit,
|
|
18754
|
+
remaining: Math.max(0, rule.limit - count),
|
|
18755
|
+
resetAt: now + windowMs,
|
|
18756
|
+
current: count,
|
|
18757
|
+
limit: rule.limit,
|
|
18758
|
+
retryAfterSeconds: count > rule.limit ? Math.ceil(windowMs / 1e3) : 0
|
|
18759
|
+
};
|
|
18760
|
+
} catch {
|
|
18761
|
+
return null;
|
|
18762
|
+
}
|
|
18763
|
+
}
|
|
18764
|
+
async function resetRateLimitForKey(operation, identifier, store) {
|
|
18765
|
+
const s = store ?? getDefaultStore();
|
|
18766
|
+
const key = `ratelimit:${operation}:${identifier}`;
|
|
18767
|
+
const blockKey = `ratelimit:block:${operation}:${identifier}`;
|
|
18768
|
+
await s.reset(key);
|
|
18769
|
+
await s.reset(blockKey);
|
|
18770
|
+
}
|
|
18771
|
+
function buildRateLimitResponseHeaders(result) {
|
|
18772
|
+
const headers = {
|
|
18773
|
+
"X-RateLimit-Limit": String(result.limit),
|
|
18774
|
+
"X-RateLimit-Remaining": String(result.remaining),
|
|
18775
|
+
"X-RateLimit-Reset": String(Math.ceil(result.resetAt / 1e3))
|
|
18776
|
+
};
|
|
18777
|
+
if (!result.allowed) {
|
|
18778
|
+
headers["Retry-After"] = String(result.retryAfterSeconds);
|
|
18779
|
+
}
|
|
18780
|
+
return headers;
|
|
18781
|
+
}
|
|
18782
|
+
function resolveIdentifier(session, clientIp) {
|
|
18783
|
+
if (session?.user?.id) {
|
|
18784
|
+
return { identifier: `user:${session.user.id}`, isAuthenticated: true };
|
|
18785
|
+
}
|
|
18786
|
+
if (session?.user?.email) {
|
|
18787
|
+
return {
|
|
18788
|
+
identifier: `email:${session.user.email}`,
|
|
18789
|
+
isAuthenticated: true
|
|
18790
|
+
};
|
|
18791
|
+
}
|
|
18792
|
+
return { identifier: `ip:${clientIp ?? "unknown"}`, isAuthenticated: false };
|
|
18793
|
+
}
|
|
18794
|
+
|
|
18795
|
+
// src/auth/audit.ts
|
|
18796
|
+
var StandardAuditActions = {
|
|
18797
|
+
// Authentication
|
|
18798
|
+
LOGIN_SUCCESS: "auth.login.success",
|
|
18799
|
+
LOGIN_FAILURE: "auth.login.failure",
|
|
18800
|
+
LOGOUT: "auth.logout",
|
|
18801
|
+
SESSION_REFRESH: "auth.session.refresh",
|
|
18802
|
+
PASSWORD_CHANGE: "auth.password.change",
|
|
18803
|
+
PASSWORD_RESET: "auth.password.reset",
|
|
18804
|
+
// Billing
|
|
18805
|
+
CHECKOUT_START: "billing.checkout.start",
|
|
18806
|
+
CHECKOUT_COMPLETE: "billing.checkout.complete",
|
|
18807
|
+
SUBSCRIPTION_CREATE: "billing.subscription.create",
|
|
18808
|
+
SUBSCRIPTION_CANCEL: "billing.subscription.cancel",
|
|
18809
|
+
SUBSCRIPTION_UPDATE: "billing.subscription.update",
|
|
18810
|
+
PAYMENT_FAILED: "billing.payment.failed",
|
|
18811
|
+
// Admin
|
|
18812
|
+
ADMIN_LOGIN: "admin.login",
|
|
18813
|
+
ADMIN_USER_VIEW: "admin.user.view",
|
|
18814
|
+
ADMIN_USER_UPDATE: "admin.user.update",
|
|
18815
|
+
ADMIN_CONFIG_CHANGE: "admin.config.change",
|
|
18816
|
+
// Security Events
|
|
18817
|
+
RATE_LIMIT_EXCEEDED: "security.rate_limit.exceeded",
|
|
18818
|
+
INVALID_INPUT: "security.input.invalid",
|
|
18819
|
+
UNAUTHORIZED_ACCESS: "security.access.unauthorized",
|
|
18820
|
+
OWNERSHIP_VIOLATION: "security.ownership.violation",
|
|
18821
|
+
WEBHOOK_SIGNATURE_INVALID: "security.webhook.signature_invalid",
|
|
18822
|
+
// Data
|
|
18823
|
+
DATA_EXPORT: "data.export",
|
|
18824
|
+
DATA_DELETE: "data.delete",
|
|
18825
|
+
DATA_UPDATE: "data.update"
|
|
18826
|
+
};
|
|
18827
|
+
function extractAuditIp(request) {
|
|
18828
|
+
if (!request) return void 0;
|
|
18829
|
+
const headers = [
|
|
18830
|
+
"cf-connecting-ip",
|
|
18831
|
+
// Cloudflare
|
|
18832
|
+
"x-real-ip",
|
|
18833
|
+
// Nginx
|
|
18834
|
+
"x-forwarded-for",
|
|
18835
|
+
// Standard proxy
|
|
18836
|
+
"x-client-ip"
|
|
18837
|
+
// Apache
|
|
18838
|
+
];
|
|
18839
|
+
for (const header of headers) {
|
|
18840
|
+
const value = request.headers.get(header);
|
|
18841
|
+
if (value) {
|
|
18842
|
+
return value.split(",")[0]?.trim();
|
|
18843
|
+
}
|
|
18844
|
+
}
|
|
18845
|
+
return void 0;
|
|
18846
|
+
}
|
|
18847
|
+
function extractAuditUserAgent(request) {
|
|
18848
|
+
return request?.headers.get("user-agent") ?? void 0;
|
|
18849
|
+
}
|
|
18850
|
+
function extractAuditRequestId(request) {
|
|
18851
|
+
return request?.headers.get("x-request-id") ?? crypto.randomUUID();
|
|
18852
|
+
}
|
|
18853
|
+
function createAuditActor(session) {
|
|
18854
|
+
if (!session?.user) {
|
|
18855
|
+
return { id: "anonymous", type: "anonymous" };
|
|
18856
|
+
}
|
|
18857
|
+
return {
|
|
18858
|
+
id: session.user.id ?? session.user.email ?? "unknown",
|
|
18859
|
+
email: session.user.email ?? void 0,
|
|
18860
|
+
type: "user"
|
|
18861
|
+
};
|
|
18862
|
+
}
|
|
18863
|
+
var defaultLogger = {
|
|
18864
|
+
info: (msg, meta) => console.log(msg, meta ? JSON.stringify(meta) : ""),
|
|
18865
|
+
warn: (msg, meta) => console.warn(msg, meta ? JSON.stringify(meta) : ""),
|
|
18866
|
+
error: (msg, meta) => console.error(msg, meta ? JSON.stringify(meta) : "")
|
|
18867
|
+
};
|
|
18868
|
+
function createAuditLogger(options = {}) {
|
|
18869
|
+
const { persist, logger = defaultLogger } = options;
|
|
18870
|
+
async function log(event, request) {
|
|
18871
|
+
const record = {
|
|
18872
|
+
id: crypto.randomUUID(),
|
|
18873
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18874
|
+
ip: extractAuditIp(request),
|
|
18875
|
+
userAgent: extractAuditUserAgent(request),
|
|
18876
|
+
requestId: extractAuditRequestId(request),
|
|
18877
|
+
...event
|
|
18878
|
+
};
|
|
18879
|
+
const logFn = event.outcome === "failure" || event.outcome === "blocked" ? logger.warn : logger.info;
|
|
18880
|
+
logFn(`[AUDIT] ${event.action}`, {
|
|
18881
|
+
auditId: record.id,
|
|
18882
|
+
actor: record.actor,
|
|
18883
|
+
action: record.action,
|
|
18884
|
+
resource: record.resource,
|
|
18885
|
+
outcome: record.outcome,
|
|
18886
|
+
ip: record.ip,
|
|
18887
|
+
metadata: record.metadata,
|
|
18888
|
+
reason: record.reason
|
|
18889
|
+
});
|
|
18890
|
+
if (persist) {
|
|
18891
|
+
try {
|
|
18892
|
+
await persist(record);
|
|
18893
|
+
} catch (error) {
|
|
18894
|
+
logger.error("Failed to persist audit log", {
|
|
18895
|
+
error: error instanceof Error ? error.message : String(error),
|
|
18896
|
+
auditId: record.id
|
|
18897
|
+
});
|
|
18898
|
+
}
|
|
18899
|
+
}
|
|
18900
|
+
return record;
|
|
18901
|
+
}
|
|
18902
|
+
function createTimedAudit(event, request) {
|
|
18903
|
+
const startTime = Date.now();
|
|
18904
|
+
return {
|
|
18905
|
+
success: async (metadata) => {
|
|
18906
|
+
return log(
|
|
18907
|
+
{
|
|
18908
|
+
...event,
|
|
18909
|
+
outcome: "success",
|
|
18910
|
+
metadata: {
|
|
18911
|
+
...event.metadata,
|
|
18912
|
+
...metadata,
|
|
18913
|
+
durationMs: Date.now() - startTime
|
|
18914
|
+
}
|
|
18915
|
+
},
|
|
18916
|
+
request
|
|
18917
|
+
);
|
|
18918
|
+
},
|
|
18919
|
+
failure: async (reason, metadata) => {
|
|
18920
|
+
return log(
|
|
18921
|
+
{
|
|
18922
|
+
...event,
|
|
18923
|
+
outcome: "failure",
|
|
18924
|
+
reason,
|
|
18925
|
+
metadata: {
|
|
18926
|
+
...event.metadata,
|
|
18927
|
+
...metadata,
|
|
18928
|
+
durationMs: Date.now() - startTime
|
|
18929
|
+
}
|
|
18930
|
+
},
|
|
18931
|
+
request
|
|
18932
|
+
);
|
|
18933
|
+
},
|
|
18934
|
+
blocked: async (reason, metadata) => {
|
|
18935
|
+
return log(
|
|
18936
|
+
{
|
|
18937
|
+
...event,
|
|
18938
|
+
outcome: "blocked",
|
|
18939
|
+
reason,
|
|
18940
|
+
metadata: {
|
|
18941
|
+
...event.metadata,
|
|
18942
|
+
...metadata,
|
|
18943
|
+
durationMs: Date.now() - startTime
|
|
18944
|
+
}
|
|
18945
|
+
},
|
|
18946
|
+
request
|
|
18947
|
+
);
|
|
18948
|
+
}
|
|
18949
|
+
};
|
|
18950
|
+
}
|
|
18951
|
+
return { log, createTimedAudit };
|
|
18952
|
+
}
|
|
18953
|
+
|
|
18016
18954
|
// src/http/health.ts
|
|
18017
18955
|
function createHealthEndpoints(platform, options = {}) {
|
|
18018
18956
|
const {
|
|
@@ -19091,7 +20029,7 @@ var MemoryAuditLog = class {
|
|
|
19091
20029
|
};
|
|
19092
20030
|
|
|
19093
20031
|
// src/adapters/memory/MemoryWebhook.ts
|
|
19094
|
-
var
|
|
20032
|
+
var import_crypto28 = require("crypto");
|
|
19095
20033
|
var MemoryWebhook = class {
|
|
19096
20034
|
endpoints = /* @__PURE__ */ new Map();
|
|
19097
20035
|
deliveries = /* @__PURE__ */ new Map();
|
|
@@ -19478,7 +20416,7 @@ var MemoryWebhook = class {
|
|
|
19478
20416
|
this.deliveries.set(delivery.id, delivery);
|
|
19479
20417
|
}
|
|
19480
20418
|
async executeDelivery(endpoint, event, attemptNumber) {
|
|
19481
|
-
const attemptId = `att_${Date.now().toString(36)}${(0,
|
|
20419
|
+
const attemptId = `att_${Date.now().toString(36)}${(0, import_crypto28.randomBytes)(4).toString("hex")}`;
|
|
19482
20420
|
const startTime = Date.now();
|
|
19483
20421
|
if (this.config.simulatedDelay > 0) {
|
|
19484
20422
|
await new Promise(
|
|
@@ -19532,7 +20470,7 @@ var MemoryWebhook = class {
|
|
|
19532
20470
|
this.endpoints.set(endpoint.id, endpoint);
|
|
19533
20471
|
}
|
|
19534
20472
|
computeSignature(payload, secret, algorithm) {
|
|
19535
|
-
return (0,
|
|
20473
|
+
return (0, import_crypto28.createHmac)(algorithm, secret).update(payload).digest("hex");
|
|
19536
20474
|
}
|
|
19537
20475
|
};
|
|
19538
20476
|
|
|
@@ -19883,7 +20821,7 @@ var MemoryNotification = class {
|
|
|
19883
20821
|
};
|
|
19884
20822
|
|
|
19885
20823
|
// src/adapters/memory/MemoryScheduler.ts
|
|
19886
|
-
var
|
|
20824
|
+
var import_crypto29 = require("crypto");
|
|
19887
20825
|
var MemoryScheduler = class {
|
|
19888
20826
|
config;
|
|
19889
20827
|
schedules = /* @__PURE__ */ new Map();
|
|
@@ -20167,7 +21105,7 @@ var MemoryScheduler = class {
|
|
|
20167
21105
|
}
|
|
20168
21106
|
}
|
|
20169
21107
|
async executeSchedule(schedule) {
|
|
20170
|
-
const executionId = `exec_${Date.now().toString(36)}${(0,
|
|
21108
|
+
const executionId = `exec_${Date.now().toString(36)}${(0, import_crypto29.randomBytes)(4).toString("hex")}`;
|
|
20171
21109
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
20172
21110
|
const execution = {
|
|
20173
21111
|
id: executionId,
|
|
@@ -20647,7 +21585,7 @@ CREATE INDEX IF NOT EXISTS idx_${tableName}_trace_id ON ${tableName}((context->>
|
|
|
20647
21585
|
};
|
|
20648
21586
|
|
|
20649
21587
|
// src/adapters/database/DatabaseErrorReporter.ts
|
|
20650
|
-
var
|
|
21588
|
+
var import_crypto30 = require("crypto");
|
|
20651
21589
|
var DatabaseErrorReporter = class {
|
|
20652
21590
|
db;
|
|
20653
21591
|
errorsTable;
|
|
@@ -20942,7 +21880,7 @@ CREATE INDEX IF NOT EXISTS idx_${breadcrumbsTable}_error ON ${breadcrumbsTable}(
|
|
|
20942
21880
|
if (report.breadcrumbs && report.breadcrumbs.length > 0) {
|
|
20943
21881
|
for (const crumb of report.breadcrumbs) {
|
|
20944
21882
|
await this.db.from(this.breadcrumbsTable).insert({
|
|
20945
|
-
id: `bc_${Date.now().toString(36)}${(0,
|
|
21883
|
+
id: `bc_${Date.now().toString(36)}${(0, import_crypto30.randomBytes)(4).toString("hex")}`,
|
|
20946
21884
|
error_id: report.id,
|
|
20947
21885
|
category: crumb.category,
|
|
20948
21886
|
message: crumb.message,
|
|
@@ -20982,7 +21920,7 @@ CREATE INDEX IF NOT EXISTS idx_${breadcrumbsTable}_error ON ${breadcrumbsTable}(
|
|
|
20982
21920
|
};
|
|
20983
21921
|
|
|
20984
21922
|
// src/adapters/database/DatabasePromptStore.ts
|
|
20985
|
-
var
|
|
21923
|
+
var import_crypto31 = require("crypto");
|
|
20986
21924
|
var DatabasePromptStore = class {
|
|
20987
21925
|
db;
|
|
20988
21926
|
cache;
|
|
@@ -21122,7 +22060,7 @@ CREATE INDEX IF NOT EXISTS idx_${tablePrefix}usage_experiment ON ${tablePrefix}p
|
|
|
21122
22060
|
// Prompt CRUD
|
|
21123
22061
|
// ═══════════════════════════════════════════════════════════════
|
|
21124
22062
|
async create(prompt) {
|
|
21125
|
-
const id = `prompt_${Date.now()}_${(0,
|
|
22063
|
+
const id = `prompt_${Date.now()}_${(0, import_crypto31.randomBytes)(4).toString("hex")}`;
|
|
21126
22064
|
const now = /* @__PURE__ */ new Date();
|
|
21127
22065
|
const newPrompt = {
|
|
21128
22066
|
...prompt,
|
|
@@ -21150,7 +22088,7 @@ CREATE INDEX IF NOT EXISTS idx_${tablePrefix}usage_experiment ON ${tablePrefix}p
|
|
|
21150
22088
|
created_by: newPrompt.createdBy,
|
|
21151
22089
|
updated_by: newPrompt.updatedBy
|
|
21152
22090
|
}).execute();
|
|
21153
|
-
const versionId = `pv_${Date.now()}_${(0,
|
|
22091
|
+
const versionId = `pv_${Date.now()}_${(0, import_crypto31.randomBytes)(4).toString("hex")}`;
|
|
21154
22092
|
await this.db.from(this.versionsTable).insert({
|
|
21155
22093
|
id: versionId,
|
|
21156
22094
|
prompt_id: id,
|
|
@@ -21219,7 +22157,7 @@ CREATE INDEX IF NOT EXISTS idx_${tablePrefix}usage_experiment ON ${tablePrefix}p
|
|
|
21219
22157
|
await this.db.from(this.versionsTable).update({ is_latest: false }).where("prompt_id", "=", prompt.id).execute();
|
|
21220
22158
|
const versionsResult = await this.db.from(this.versionsTable).where("prompt_id", "=", prompt.id).execute();
|
|
21221
22159
|
const newVersionNum = versionsResult.data.length + 1;
|
|
21222
|
-
const versionId = `pv_${Date.now()}_${(0,
|
|
22160
|
+
const versionId = `pv_${Date.now()}_${(0, import_crypto31.randomBytes)(4).toString("hex")}`;
|
|
21223
22161
|
await this.db.from(this.versionsTable).insert({
|
|
21224
22162
|
id: versionId,
|
|
21225
22163
|
prompt_id: prompt.id,
|
|
@@ -21474,7 +22412,7 @@ ${v2.content}`;
|
|
|
21474
22412
|
// A/B Testing
|
|
21475
22413
|
// ═══════════════════════════════════════════════════════════════
|
|
21476
22414
|
async createExperiment(experiment) {
|
|
21477
|
-
const id = `exp_${Date.now()}_${(0,
|
|
22415
|
+
const id = `exp_${Date.now()}_${(0, import_crypto31.randomBytes)(4).toString("hex")}`;
|
|
21478
22416
|
const now = /* @__PURE__ */ new Date();
|
|
21479
22417
|
const newExperiment = {
|
|
21480
22418
|
...experiment,
|
|
@@ -21588,7 +22526,7 @@ ${v2.content}`;
|
|
|
21588
22526
|
// Prompt Chains
|
|
21589
22527
|
// ═══════════════════════════════════════════════════════════════
|
|
21590
22528
|
async createChain(chain) {
|
|
21591
|
-
const id = `chain_${Date.now()}_${(0,
|
|
22529
|
+
const id = `chain_${Date.now()}_${(0, import_crypto31.randomBytes)(4).toString("hex")}`;
|
|
21592
22530
|
const now = /* @__PURE__ */ new Date();
|
|
21593
22531
|
const newChain = {
|
|
21594
22532
|
...chain,
|
|
@@ -21686,7 +22624,7 @@ ${v2.content}`;
|
|
|
21686
22624
|
// Usage & Analytics
|
|
21687
22625
|
// ═══════════════════════════════════════════════════════════════
|
|
21688
22626
|
async recordUsage(record) {
|
|
21689
|
-
const id = `usage_${Date.now()}_${(0,
|
|
22627
|
+
const id = `usage_${Date.now()}_${(0, import_crypto31.randomBytes)(4).toString("hex")}`;
|
|
21690
22628
|
const now = /* @__PURE__ */ new Date();
|
|
21691
22629
|
const usageRecord = {
|
|
21692
22630
|
...record,
|
|
@@ -21872,9 +22810,9 @@ ${v2.content}`;
|
|
|
21872
22810
|
};
|
|
21873
22811
|
|
|
21874
22812
|
// src/adapters/database/DatabaseCompliance.ts
|
|
21875
|
-
var
|
|
22813
|
+
var import_crypto32 = require("crypto");
|
|
21876
22814
|
function generateId(prefix) {
|
|
21877
|
-
return `${prefix}_${Date.now()}_${(0,
|
|
22815
|
+
return `${prefix}_${Date.now()}_${(0, import_crypto32.randomBytes)(4).toString("hex")}`;
|
|
21878
22816
|
}
|
|
21879
22817
|
function toDate(value) {
|
|
21880
22818
|
return value ? new Date(value) : void 0;
|
|
@@ -22065,7 +23003,7 @@ var DatabaseCompliance = class {
|
|
|
22065
23003
|
async createDsar(options) {
|
|
22066
23004
|
const id = generateId("dsar");
|
|
22067
23005
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
22068
|
-
const verificationToken = `verify_${(0,
|
|
23006
|
+
const verificationToken = `verify_${(0, import_crypto32.randomBytes)(16).toString("hex")}`;
|
|
22069
23007
|
const result = await this.db.from("compliance_dsars").insert({
|
|
22070
23008
|
id,
|
|
22071
23009
|
type: options.type,
|
|
@@ -23004,7 +23942,7 @@ var DatabaseCompliance = class {
|
|
|
23004
23942
|
};
|
|
23005
23943
|
|
|
23006
23944
|
// src/adapters/database/DatabaseAIUsage.ts
|
|
23007
|
-
var
|
|
23945
|
+
var import_crypto33 = require("crypto");
|
|
23008
23946
|
var DatabaseAIUsage = class {
|
|
23009
23947
|
db;
|
|
23010
23948
|
config;
|
|
@@ -23018,7 +23956,7 @@ var DatabaseAIUsage = class {
|
|
|
23018
23956
|
// Usage Recording
|
|
23019
23957
|
// ─────────────────────────────────────────────────────────────
|
|
23020
23958
|
async record(record) {
|
|
23021
|
-
const id = `usage_${Date.now()}_${(0,
|
|
23959
|
+
const id = `usage_${Date.now()}_${(0, import_crypto33.randomBytes)(4).toString("hex")}`;
|
|
23022
23960
|
const now = /* @__PURE__ */ new Date();
|
|
23023
23961
|
await this.db.from(`${this.prefix}records`).insert({
|
|
23024
23962
|
id,
|
|
@@ -23125,7 +24063,7 @@ var DatabaseAIUsage = class {
|
|
|
23125
24063
|
quota.category
|
|
23126
24064
|
);
|
|
23127
24065
|
const period = this.getPeriodBounds(quota.period, /* @__PURE__ */ new Date());
|
|
23128
|
-
const id = existing?.id || `quota_${Date.now()}_${(0,
|
|
24066
|
+
const id = existing?.id || `quota_${Date.now()}_${(0, import_crypto33.randomBytes)(4).toString("hex")}`;
|
|
23129
24067
|
const data = {
|
|
23130
24068
|
id,
|
|
23131
24069
|
tenant_id: quota.tenantId,
|
|
@@ -23262,7 +24200,7 @@ var DatabaseAIUsage = class {
|
|
|
23262
24200
|
existingResult.data[0]
|
|
23263
24201
|
) : null;
|
|
23264
24202
|
const period = this.getPeriodBounds(budget.period, /* @__PURE__ */ new Date());
|
|
23265
|
-
const id = existing?.id || `budget_${Date.now()}_${(0,
|
|
24203
|
+
const id = existing?.id || `budget_${Date.now()}_${(0, import_crypto33.randomBytes)(4).toString("hex")}`;
|
|
23266
24204
|
const data = {
|
|
23267
24205
|
id,
|
|
23268
24206
|
tenant_id: budget.tenantId,
|
|
@@ -23561,7 +24499,7 @@ var DatabaseAIUsage = class {
|
|
|
23561
24499
|
}
|
|
23562
24500
|
const items = Array.from(itemsMap.values());
|
|
23563
24501
|
const subtotal = items.reduce((sum, item) => sum + item.costUsd, 0);
|
|
23564
|
-
const id = `inv_${Date.now()}_${(0,
|
|
24502
|
+
const id = `inv_${Date.now()}_${(0, import_crypto33.randomBytes)(4).toString("hex")}`;
|
|
23565
24503
|
const now = /* @__PURE__ */ new Date();
|
|
23566
24504
|
await this.db.from(`${this.prefix}invoices`).insert({
|
|
23567
24505
|
id,
|
|
@@ -23783,7 +24721,7 @@ var DatabaseAIUsage = class {
|
|
|
23783
24721
|
}
|
|
23784
24722
|
}
|
|
23785
24723
|
async createAlert(tenantId, type, severity, message, metadata) {
|
|
23786
|
-
const id = `alert_${Date.now()}_${(0,
|
|
24724
|
+
const id = `alert_${Date.now()}_${(0, import_crypto33.randomBytes)(4).toString("hex")}`;
|
|
23787
24725
|
await this.db.from(`${this.prefix}alerts`).insert({
|
|
23788
24726
|
id,
|
|
23789
24727
|
tenant_id: tenantId,
|
|
@@ -23989,7 +24927,7 @@ var DatabaseAIUsage = class {
|
|
|
23989
24927
|
};
|
|
23990
24928
|
|
|
23991
24929
|
// src/adapters/database/DatabaseNotification.ts
|
|
23992
|
-
var
|
|
24930
|
+
var import_crypto34 = require("crypto");
|
|
23993
24931
|
var DatabaseNotification = class {
|
|
23994
24932
|
db;
|
|
23995
24933
|
email;
|
|
@@ -24227,7 +25165,7 @@ var DatabaseNotification = class {
|
|
|
24227
25165
|
// PUSH SUBSCRIPTIONS
|
|
24228
25166
|
// ═══════════════════════════════════════════════════════════════
|
|
24229
25167
|
async registerPushSubscription(userId, subscription) {
|
|
24230
|
-
const id = `push_${Date.now()}_${(0,
|
|
25168
|
+
const id = `push_${Date.now()}_${(0, import_crypto34.randomBytes)(4).toString("hex")}`;
|
|
24231
25169
|
const existing = await this.db.from(`${this.prefix}push_subscriptions`).where("user_id", "=", userId).where("endpoint", "=", subscription.endpoint).execute();
|
|
24232
25170
|
if (existing.data && existing.data.length > 0) {
|
|
24233
25171
|
await this.db.from(`${this.prefix}push_subscriptions`).where("user_id", "=", userId).where("endpoint", "=", subscription.endpoint).update({
|
|
@@ -24325,7 +25263,7 @@ var DatabaseNotification = class {
|
|
|
24325
25263
|
// TOPICS
|
|
24326
25264
|
// ═══════════════════════════════════════════════════════════════
|
|
24327
25265
|
async subscribeToTopic(userId, topic) {
|
|
24328
|
-
const id = `topic_${Date.now()}_${(0,
|
|
25266
|
+
const id = `topic_${Date.now()}_${(0, import_crypto34.randomBytes)(4).toString("hex")}`;
|
|
24329
25267
|
const existing = await this.db.from(`${this.prefix}notification_topic_subs`).where("user_id", "=", userId).where("topic", "=", topic).execute();
|
|
24330
25268
|
if (!existing.data || existing.data.length === 0) {
|
|
24331
25269
|
await this.db.from(`${this.prefix}notification_topic_subs`).insert({
|
|
@@ -24406,7 +25344,7 @@ var DatabaseNotification = class {
|
|
|
24406
25344
|
// PRIVATE HELPERS
|
|
24407
25345
|
// ═══════════════════════════════════════════════════════════════
|
|
24408
25346
|
async logDelivery(notificationId, channel, status, messageId, error) {
|
|
24409
|
-
const id = `del_${Date.now()}_${(0,
|
|
25347
|
+
const id = `del_${Date.now()}_${(0, import_crypto34.randomBytes)(4).toString("hex")}`;
|
|
24410
25348
|
try {
|
|
24411
25349
|
await this.db.from(`${this.prefix}notification_delivery_log`).insert({
|
|
24412
25350
|
id,
|
|
@@ -24468,7 +25406,7 @@ var DatabaseNotification = class {
|
|
|
24468
25406
|
};
|
|
24469
25407
|
|
|
24470
25408
|
// src/adapters/database/DatabaseBilling.ts
|
|
24471
|
-
var
|
|
25409
|
+
var import_crypto35 = require("crypto");
|
|
24472
25410
|
var DatabaseBilling = class {
|
|
24473
25411
|
db;
|
|
24474
25412
|
prefix;
|
|
@@ -24491,7 +25429,7 @@ var DatabaseBilling = class {
|
|
|
24491
25429
|
return `${this.prefix}${name}`;
|
|
24492
25430
|
}
|
|
24493
25431
|
generateId(prefix) {
|
|
24494
|
-
return `${prefix}_${Date.now()}_${(0,
|
|
25432
|
+
return `${prefix}_${Date.now()}_${(0, import_crypto35.randomBytes)(4).toString("hex")}`;
|
|
24495
25433
|
}
|
|
24496
25434
|
// ─────────────────────────────────────────────────────────────
|
|
24497
25435
|
// Product & Price Management
|
|
@@ -25989,7 +26927,7 @@ var DatabaseBilling = class {
|
|
|
25989
26927
|
};
|
|
25990
26928
|
|
|
25991
26929
|
// src/adapters/scheduler/QueueScheduler.ts
|
|
25992
|
-
var
|
|
26930
|
+
var import_crypto36 = require("crypto");
|
|
25993
26931
|
var QueueScheduler = class {
|
|
25994
26932
|
queue;
|
|
25995
26933
|
db;
|
|
@@ -26405,7 +27343,7 @@ CREATE INDEX IF NOT EXISTS idx_${executionsTable}_started ON ${executionsTable}(
|
|
|
26405
27343
|
}
|
|
26406
27344
|
}
|
|
26407
27345
|
async executeSchedule(schedule) {
|
|
26408
|
-
const executionId = `exec_${Date.now().toString(36)}${(0,
|
|
27346
|
+
const executionId = `exec_${Date.now().toString(36)}${(0, import_crypto36.randomBytes)(4).toString("hex")}`;
|
|
26409
27347
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
26410
27348
|
const execution = {
|
|
26411
27349
|
id: executionId,
|
|
@@ -26568,7 +27506,7 @@ CREATE INDEX IF NOT EXISTS idx_${executionsTable}_started ON ${executionsTable}(
|
|
|
26568
27506
|
};
|
|
26569
27507
|
|
|
26570
27508
|
// src/adapters/webhook/HttpWebhook.ts
|
|
26571
|
-
var
|
|
27509
|
+
var import_crypto37 = require("crypto");
|
|
26572
27510
|
var HttpWebhook = class {
|
|
26573
27511
|
db;
|
|
26574
27512
|
queue;
|
|
@@ -26917,7 +27855,7 @@ var HttpWebhook = class {
|
|
|
26917
27855
|
if (providedBuffer.length !== expectedBuffer.length) {
|
|
26918
27856
|
return { valid: false, error: "Invalid signature" };
|
|
26919
27857
|
}
|
|
26920
|
-
if (!(0,
|
|
27858
|
+
if (!(0, import_crypto37.timingSafeEqual)(providedBuffer, expectedBuffer)) {
|
|
26921
27859
|
return { valid: false, error: "Invalid signature" };
|
|
26922
27860
|
}
|
|
26923
27861
|
} catch {
|
|
@@ -27166,7 +28104,7 @@ CREATE INDEX IF NOT EXISTS idx_${attemptsTable}_delivery ON ${attemptsTable}(del
|
|
|
27166
28104
|
await this.saveDelivery(delivery);
|
|
27167
28105
|
}
|
|
27168
28106
|
async executeDelivery(endpoint, event, attemptNumber) {
|
|
27169
|
-
const attemptId = `att_${Date.now().toString(36)}${(0,
|
|
28107
|
+
const attemptId = `att_${Date.now().toString(36)}${(0, import_crypto37.randomBytes)(4).toString("hex")}`;
|
|
27170
28108
|
const startTime = Date.now();
|
|
27171
28109
|
const payloadStr = JSON.stringify(event);
|
|
27172
28110
|
const signature = this.computeSignature(
|
|
@@ -27270,7 +28208,7 @@ CREATE INDEX IF NOT EXISTS idx_${attemptsTable}_delivery ON ${attemptsTable}(del
|
|
|
27270
28208
|
await this.saveEndpoint(endpoint);
|
|
27271
28209
|
}
|
|
27272
28210
|
computeSignature(payload, secret, algorithm) {
|
|
27273
|
-
return (0,
|
|
28211
|
+
return (0, import_crypto37.createHmac)(algorithm, secret).update(payload).digest("hex");
|
|
27274
28212
|
}
|
|
27275
28213
|
endpointToRow(endpoint) {
|
|
27276
28214
|
return {
|
|
@@ -29188,7 +30126,7 @@ var GenericOIDCAuthSSO = class {
|
|
|
29188
30126
|
};
|
|
29189
30127
|
|
|
29190
30128
|
// src/adapters/postgres-tenant/PostgresTenant.ts
|
|
29191
|
-
var
|
|
30129
|
+
var import_crypto38 = require("crypto");
|
|
29192
30130
|
var tenantContextMap = /* @__PURE__ */ new Map();
|
|
29193
30131
|
var contextIdCounter2 = 0;
|
|
29194
30132
|
var currentContextId2 = null;
|
|
@@ -29970,7 +30908,273 @@ var PostgresTenant = class {
|
|
|
29970
30908
|
}
|
|
29971
30909
|
};
|
|
29972
30910
|
function generateId2() {
|
|
29973
|
-
return (0,
|
|
30911
|
+
return (0, import_crypto38.randomBytes)(8).toString("hex") + Date.now().toString(36);
|
|
30912
|
+
}
|
|
30913
|
+
|
|
30914
|
+
// src/env.ts
|
|
30915
|
+
function getRequiredEnv(key) {
|
|
30916
|
+
const value = process.env[key];
|
|
30917
|
+
if (!value) {
|
|
30918
|
+
throw new Error(`Missing required environment variable: ${key}`);
|
|
30919
|
+
}
|
|
30920
|
+
return value;
|
|
30921
|
+
}
|
|
30922
|
+
function getOptionalEnv(key, defaultValue) {
|
|
30923
|
+
return process.env[key] || defaultValue;
|
|
30924
|
+
}
|
|
30925
|
+
function getBoolEnv(key, defaultValue = false) {
|
|
30926
|
+
const value = process.env[key];
|
|
30927
|
+
if (value === void 0 || value === "") return defaultValue;
|
|
30928
|
+
return value === "true" || value === "1";
|
|
30929
|
+
}
|
|
30930
|
+
function getIntEnv(key, defaultValue) {
|
|
30931
|
+
const value = process.env[key];
|
|
30932
|
+
if (value === void 0 || value === "") return defaultValue;
|
|
30933
|
+
const parsed = parseInt(value, 10);
|
|
30934
|
+
return isNaN(parsed) ? defaultValue : parsed;
|
|
30935
|
+
}
|
|
30936
|
+
function validateEnvVars(config) {
|
|
30937
|
+
const result = checkEnvVars(config);
|
|
30938
|
+
if (!result.valid) {
|
|
30939
|
+
const lines = [];
|
|
30940
|
+
if (result.missing.length > 0) {
|
|
30941
|
+
lines.push(
|
|
30942
|
+
"Missing required environment variables:",
|
|
30943
|
+
...result.missing.map((v) => ` - ${v}`)
|
|
30944
|
+
);
|
|
30945
|
+
}
|
|
30946
|
+
if (result.missingOneOf.length > 0) {
|
|
30947
|
+
for (const group of result.missingOneOf) {
|
|
30948
|
+
lines.push(`Missing one of: ${group.join(" | ")}`);
|
|
30949
|
+
}
|
|
30950
|
+
}
|
|
30951
|
+
if (result.invalid.length > 0) {
|
|
30952
|
+
lines.push(
|
|
30953
|
+
"Invalid environment variables:",
|
|
30954
|
+
...result.invalid.map((v) => ` - ${v.key}: ${v.reason}`)
|
|
30955
|
+
);
|
|
30956
|
+
}
|
|
30957
|
+
throw new Error(lines.join("\n"));
|
|
30958
|
+
}
|
|
30959
|
+
}
|
|
30960
|
+
function checkEnvVars(config) {
|
|
30961
|
+
const missing = [];
|
|
30962
|
+
const invalid = [];
|
|
30963
|
+
const missingOneOf = [];
|
|
30964
|
+
if (config.required) {
|
|
30965
|
+
for (const key of config.required) {
|
|
30966
|
+
if (!process.env[key]) {
|
|
30967
|
+
missing.push(key);
|
|
30968
|
+
}
|
|
30969
|
+
}
|
|
30970
|
+
}
|
|
30971
|
+
if (config.requireOneOf) {
|
|
30972
|
+
for (const group of config.requireOneOf) {
|
|
30973
|
+
const hasAny = group.some((key) => !!process.env[key]);
|
|
30974
|
+
if (!hasAny) {
|
|
30975
|
+
missingOneOf.push(group);
|
|
30976
|
+
}
|
|
30977
|
+
}
|
|
30978
|
+
}
|
|
30979
|
+
if (config.validators) {
|
|
30980
|
+
for (const [key, validator] of Object.entries(config.validators)) {
|
|
30981
|
+
const value = process.env[key];
|
|
30982
|
+
if (value) {
|
|
30983
|
+
const result = validator(value);
|
|
30984
|
+
if (result !== true) {
|
|
30985
|
+
invalid.push({ key, reason: result });
|
|
30986
|
+
}
|
|
30987
|
+
}
|
|
30988
|
+
}
|
|
30989
|
+
}
|
|
30990
|
+
return {
|
|
30991
|
+
valid: missing.length === 0 && invalid.length === 0 && missingOneOf.length === 0,
|
|
30992
|
+
missing,
|
|
30993
|
+
invalid,
|
|
30994
|
+
missingOneOf
|
|
30995
|
+
};
|
|
30996
|
+
}
|
|
30997
|
+
function getEnvSummary(keys) {
|
|
30998
|
+
const summary = {};
|
|
30999
|
+
for (const key of keys) {
|
|
31000
|
+
summary[key] = !!process.env[key];
|
|
31001
|
+
}
|
|
31002
|
+
return summary;
|
|
31003
|
+
}
|
|
31004
|
+
|
|
31005
|
+
// src/app-logger.ts
|
|
31006
|
+
var LEVEL_PRIORITY2 = {
|
|
31007
|
+
debug: 0,
|
|
31008
|
+
info: 1,
|
|
31009
|
+
warn: 2,
|
|
31010
|
+
error: 3
|
|
31011
|
+
};
|
|
31012
|
+
function defaultOutput(level, message, context) {
|
|
31013
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
31014
|
+
if (isProduction) {
|
|
31015
|
+
const entry = { level, message, ...context };
|
|
31016
|
+
const method = level === "error" ? "error" : level === "warn" ? "warn" : "log";
|
|
31017
|
+
console[method](JSON.stringify(entry));
|
|
31018
|
+
} else {
|
|
31019
|
+
const prefix = `[${level.toUpperCase()}]`;
|
|
31020
|
+
const ctx = Object.keys(context).length > 0 ? " " + JSON.stringify(context) : "";
|
|
31021
|
+
const method = level === "error" ? "error" : level === "warn" ? "warn" : level === "debug" ? "debug" : "log";
|
|
31022
|
+
console[method](`${prefix} ${message}${ctx}`);
|
|
31023
|
+
}
|
|
31024
|
+
}
|
|
31025
|
+
var AppLoggerImpl = class _AppLoggerImpl {
|
|
31026
|
+
baseContext;
|
|
31027
|
+
minLevelPriority;
|
|
31028
|
+
outputFn;
|
|
31029
|
+
onErrorFn;
|
|
31030
|
+
constructor(baseContext, options) {
|
|
31031
|
+
this.baseContext = baseContext;
|
|
31032
|
+
this.minLevelPriority = LEVEL_PRIORITY2[options.minLevel] ?? 0;
|
|
31033
|
+
this.outputFn = options.output;
|
|
31034
|
+
this.onErrorFn = options.onError;
|
|
31035
|
+
}
|
|
31036
|
+
log(level, message, context) {
|
|
31037
|
+
if ((LEVEL_PRIORITY2[level] ?? 0) < this.minLevelPriority) return;
|
|
31038
|
+
const merged = {
|
|
31039
|
+
...this.baseContext,
|
|
31040
|
+
...context,
|
|
31041
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
31042
|
+
};
|
|
31043
|
+
this.outputFn(level, message, merged);
|
|
31044
|
+
}
|
|
31045
|
+
debug(message, context) {
|
|
31046
|
+
this.log("debug", message, context);
|
|
31047
|
+
}
|
|
31048
|
+
info(message, context) {
|
|
31049
|
+
this.log("info", message, context);
|
|
31050
|
+
}
|
|
31051
|
+
warn(message, context) {
|
|
31052
|
+
this.log("warn", message, context);
|
|
31053
|
+
}
|
|
31054
|
+
error(message, context) {
|
|
31055
|
+
this.log("error", message, context);
|
|
31056
|
+
if (this.onErrorFn) {
|
|
31057
|
+
try {
|
|
31058
|
+
this.onErrorFn(message, { ...this.baseContext, ...context });
|
|
31059
|
+
} catch {
|
|
31060
|
+
}
|
|
31061
|
+
}
|
|
31062
|
+
}
|
|
31063
|
+
child(context) {
|
|
31064
|
+
return new _AppLoggerImpl(
|
|
31065
|
+
{ ...this.baseContext, ...context },
|
|
31066
|
+
{
|
|
31067
|
+
minLevel: Object.entries(LEVEL_PRIORITY2).find(
|
|
31068
|
+
([, v]) => v === this.minLevelPriority
|
|
31069
|
+
)?.[0] ?? "debug",
|
|
31070
|
+
output: this.outputFn,
|
|
31071
|
+
onError: this.onErrorFn
|
|
31072
|
+
}
|
|
31073
|
+
);
|
|
31074
|
+
}
|
|
31075
|
+
forRequest(request, operation) {
|
|
31076
|
+
const requestId = request.headers.get("x-request-id") ?? request.headers.get("x-correlation-id") ?? crypto.randomUUID();
|
|
31077
|
+
let path;
|
|
31078
|
+
try {
|
|
31079
|
+
path = new URL(request.url).pathname;
|
|
31080
|
+
} catch {
|
|
31081
|
+
path = request.url;
|
|
31082
|
+
}
|
|
31083
|
+
return this.child({
|
|
31084
|
+
requestId,
|
|
31085
|
+
operation,
|
|
31086
|
+
method: request.method,
|
|
31087
|
+
path
|
|
31088
|
+
});
|
|
31089
|
+
}
|
|
31090
|
+
forJob(jobType, jobId, context) {
|
|
31091
|
+
return this.child({ jobType, jobId, ...context });
|
|
31092
|
+
}
|
|
31093
|
+
async timed(operation, fn, context) {
|
|
31094
|
+
const opLogger = this.child({ operation, ...context });
|
|
31095
|
+
opLogger.debug("Operation started");
|
|
31096
|
+
const start = Date.now();
|
|
31097
|
+
try {
|
|
31098
|
+
const result = await fn();
|
|
31099
|
+
opLogger.info("Operation completed", {
|
|
31100
|
+
durationMs: Date.now() - start
|
|
31101
|
+
});
|
|
31102
|
+
return result;
|
|
31103
|
+
} catch (error) {
|
|
31104
|
+
opLogger.error("Operation failed", {
|
|
31105
|
+
durationMs: Date.now() - start,
|
|
31106
|
+
error: error instanceof Error ? error.message : String(error)
|
|
31107
|
+
});
|
|
31108
|
+
throw error;
|
|
31109
|
+
}
|
|
31110
|
+
}
|
|
31111
|
+
};
|
|
31112
|
+
function createAppLogger(options) {
|
|
31113
|
+
const {
|
|
31114
|
+
service,
|
|
31115
|
+
minLevel = process.env.NODE_ENV === "production" ? "info" : "debug",
|
|
31116
|
+
output = defaultOutput,
|
|
31117
|
+
onError
|
|
31118
|
+
} = options;
|
|
31119
|
+
return new AppLoggerImpl({ service }, { minLevel, output, onError });
|
|
31120
|
+
}
|
|
31121
|
+
|
|
31122
|
+
// src/redis-client.ts
|
|
31123
|
+
var import_ioredis = __toESM(require("ioredis"));
|
|
31124
|
+
function createRedisClient(options) {
|
|
31125
|
+
const {
|
|
31126
|
+
url,
|
|
31127
|
+
keyPrefix,
|
|
31128
|
+
maxRetries = 3,
|
|
31129
|
+
lazyConnect = true,
|
|
31130
|
+
silent = false
|
|
31131
|
+
} = options;
|
|
31132
|
+
const logger = options.logger ?? {
|
|
31133
|
+
error: console.error
|
|
31134
|
+
};
|
|
31135
|
+
const client = new import_ioredis.default(url, {
|
|
31136
|
+
keyPrefix,
|
|
31137
|
+
maxRetriesPerRequest: maxRetries,
|
|
31138
|
+
retryStrategy(times) {
|
|
31139
|
+
if (times > maxRetries) return null;
|
|
31140
|
+
return Math.min(times * 200, 2e3);
|
|
31141
|
+
},
|
|
31142
|
+
lazyConnect
|
|
31143
|
+
});
|
|
31144
|
+
if (!silent) {
|
|
31145
|
+
client.on("error", (err) => {
|
|
31146
|
+
logger.error("[redis] Connection error", { message: err.message });
|
|
31147
|
+
});
|
|
31148
|
+
if (logger.info) {
|
|
31149
|
+
const logInfo = logger.info;
|
|
31150
|
+
client.on("connect", () => {
|
|
31151
|
+
logInfo("[redis] Connected");
|
|
31152
|
+
});
|
|
31153
|
+
}
|
|
31154
|
+
}
|
|
31155
|
+
if (lazyConnect) {
|
|
31156
|
+
client.connect().catch(() => {
|
|
31157
|
+
});
|
|
31158
|
+
}
|
|
31159
|
+
return client;
|
|
31160
|
+
}
|
|
31161
|
+
var sharedClient = null;
|
|
31162
|
+
function getSharedRedis() {
|
|
31163
|
+
if (sharedClient) return sharedClient;
|
|
31164
|
+
const url = process.env.REDIS_URL;
|
|
31165
|
+
if (!url) return null;
|
|
31166
|
+
sharedClient = createRedisClient({
|
|
31167
|
+
url,
|
|
31168
|
+
keyPrefix: process.env.REDIS_KEY_PREFIX,
|
|
31169
|
+
silent: process.env.NODE_ENV === "test"
|
|
31170
|
+
});
|
|
31171
|
+
return sharedClient;
|
|
31172
|
+
}
|
|
31173
|
+
async function closeSharedRedis() {
|
|
31174
|
+
if (sharedClient) {
|
|
31175
|
+
await sharedClient.quit();
|
|
31176
|
+
sharedClient = null;
|
|
31177
|
+
}
|
|
29974
31178
|
}
|
|
29975
31179
|
|
|
29976
31180
|
// src/migrations/Migrator.ts
|
|
@@ -30750,6 +31954,7 @@ function getEnterpriseMigrations(features) {
|
|
|
30750
31954
|
CircuitBreakerRegistry,
|
|
30751
31955
|
CircuitOpenError,
|
|
30752
31956
|
CommonApiErrors,
|
|
31957
|
+
CommonRateLimits,
|
|
30753
31958
|
ConsoleEmail,
|
|
30754
31959
|
ConsoleLogger,
|
|
30755
31960
|
CronPresets,
|
|
@@ -30766,17 +31971,21 @@ function getEnterpriseMigrations(features) {
|
|
|
30766
31971
|
DatabaseNotification,
|
|
30767
31972
|
DatabasePromptStore,
|
|
30768
31973
|
DatabaseProviderSchema,
|
|
31974
|
+
DateRangeSchema,
|
|
30769
31975
|
DefaultTimeouts,
|
|
30770
31976
|
EmailConfigSchema,
|
|
30771
31977
|
EmailProviderSchema,
|
|
31978
|
+
EmailSchema,
|
|
30772
31979
|
EnvSecrets,
|
|
30773
31980
|
FallbackStrategies,
|
|
30774
31981
|
GenericOIDCAuthSSO,
|
|
30775
31982
|
GoogleAIAdapter,
|
|
30776
31983
|
HTML_TAG_PATTERN,
|
|
30777
31984
|
HttpWebhook,
|
|
31985
|
+
KEYCLOAK_DEFAULT_ROLES,
|
|
30778
31986
|
LogLevelSchema,
|
|
30779
31987
|
LoggingConfigSchema,
|
|
31988
|
+
LoginSchema,
|
|
30780
31989
|
MemoryAI,
|
|
30781
31990
|
MemoryAIUsage,
|
|
30782
31991
|
MemoryAuditLog,
|
|
@@ -30815,7 +32024,11 @@ function getEnterpriseMigrations(features) {
|
|
|
30815
32024
|
ObservabilityConfigSchema,
|
|
30816
32025
|
OpenAIAdapter,
|
|
30817
32026
|
PG_ERROR_MAP,
|
|
32027
|
+
PaginationSchema,
|
|
32028
|
+
PasswordSchema,
|
|
30818
32029
|
PaymentErrorMessages,
|
|
32030
|
+
PersonNameSchema,
|
|
32031
|
+
PhoneSchema,
|
|
30819
32032
|
PineconeRAG,
|
|
30820
32033
|
PlatformConfigSchema,
|
|
30821
32034
|
PostgresDatabase,
|
|
@@ -30835,9 +32048,14 @@ function getEnterpriseMigrations(features) {
|
|
|
30835
32048
|
RetryPredicates,
|
|
30836
32049
|
S3Storage,
|
|
30837
32050
|
SQL,
|
|
32051
|
+
SearchQuerySchema,
|
|
30838
32052
|
SecurityConfigSchema,
|
|
30839
32053
|
SecurityHeaderPresets,
|
|
32054
|
+
SignupSchema,
|
|
32055
|
+
SlugSchema,
|
|
30840
32056
|
SmtpEmail,
|
|
32057
|
+
StandardAuditActions,
|
|
32058
|
+
StandardRateLimitPresets,
|
|
30841
32059
|
StorageConfigSchema,
|
|
30842
32060
|
StorageProviderSchema,
|
|
30843
32061
|
StripePayment,
|
|
@@ -30853,16 +32071,32 @@ function getEnterpriseMigrations(features) {
|
|
|
30853
32071
|
UpstashCache,
|
|
30854
32072
|
WeaviateRAG,
|
|
30855
32073
|
WebhookEventTypes,
|
|
32074
|
+
WrapperPresets,
|
|
32075
|
+
buildAllowlist,
|
|
32076
|
+
buildAuthCookies,
|
|
32077
|
+
buildErrorBody,
|
|
32078
|
+
buildKeycloakCallbacks,
|
|
30856
32079
|
buildPagination,
|
|
32080
|
+
buildRateLimitHeaders,
|
|
32081
|
+
buildRateLimitResponseHeaders,
|
|
32082
|
+
buildRedirectCallback,
|
|
32083
|
+
buildTokenRefreshParams,
|
|
30857
32084
|
calculateBackoff,
|
|
30858
32085
|
calculateRetryDelay,
|
|
32086
|
+
checkEnvVars,
|
|
32087
|
+
checkRateLimit,
|
|
30859
32088
|
classifyError,
|
|
32089
|
+
closeSharedRedis,
|
|
30860
32090
|
composeHookRegistries,
|
|
32091
|
+
constantTimeEqual,
|
|
30861
32092
|
containsHtml,
|
|
30862
32093
|
containsUrls,
|
|
30863
32094
|
correlationContext,
|
|
30864
32095
|
createAIError,
|
|
30865
32096
|
createAnthropicAdapter,
|
|
32097
|
+
createAppLogger,
|
|
32098
|
+
createAuditActor,
|
|
32099
|
+
createAuditLogger,
|
|
30866
32100
|
createAuthError,
|
|
30867
32101
|
createBulkhead,
|
|
30868
32102
|
createCacheMiddleware,
|
|
@@ -30873,6 +32107,7 @@ function getEnterpriseMigrations(features) {
|
|
|
30873
32107
|
createErrorReport,
|
|
30874
32108
|
createExpressHealthHandlers,
|
|
30875
32109
|
createExpressMetricsHandler,
|
|
32110
|
+
createFeatureFlags,
|
|
30876
32111
|
createGoogleAIAdapter,
|
|
30877
32112
|
createHealthEndpoints,
|
|
30878
32113
|
createHealthServer,
|
|
@@ -30880,6 +32115,7 @@ function getEnterpriseMigrations(features) {
|
|
|
30880
32115
|
createIpKeyGenerator,
|
|
30881
32116
|
createJobContext,
|
|
30882
32117
|
createLoggingMiddleware,
|
|
32118
|
+
createMemoryRateLimitStore,
|
|
30883
32119
|
createMetricsEndpoint,
|
|
30884
32120
|
createMetricsMiddleware,
|
|
30885
32121
|
createMetricsServer,
|
|
@@ -30893,8 +32129,10 @@ function getEnterpriseMigrations(features) {
|
|
|
30893
32129
|
createPlatform,
|
|
30894
32130
|
createPlatformAsync,
|
|
30895
32131
|
createRateLimitMiddleware,
|
|
32132
|
+
createRedisClient,
|
|
30896
32133
|
createRequestContext,
|
|
30897
32134
|
createRequestIdMiddleware,
|
|
32135
|
+
createSafeTextSchema,
|
|
30898
32136
|
createScopedMetrics,
|
|
30899
32137
|
createSlowQueryMiddleware,
|
|
30900
32138
|
createSsoOidcConfigsTable,
|
|
@@ -30911,8 +32149,13 @@ function getEnterpriseMigrations(features) {
|
|
|
30911
32149
|
defangUrl,
|
|
30912
32150
|
defineMigration,
|
|
30913
32151
|
describeCron,
|
|
32152
|
+
detectStage,
|
|
30914
32153
|
enterpriseMigrations,
|
|
30915
32154
|
escapeHtml,
|
|
32155
|
+
extractAuditIp,
|
|
32156
|
+
extractAuditRequestId,
|
|
32157
|
+
extractAuditUserAgent,
|
|
32158
|
+
extractClientIp,
|
|
30916
32159
|
filterChannelsByPreferences,
|
|
30917
32160
|
formatAmount,
|
|
30918
32161
|
generateAuditId,
|
|
@@ -30930,37 +32173,58 @@ function getEnterpriseMigrations(features) {
|
|
|
30930
32173
|
generateVersion,
|
|
30931
32174
|
generateWebhookId,
|
|
30932
32175
|
generateWebhookSecret,
|
|
32176
|
+
getBoolEnv,
|
|
30933
32177
|
getContext,
|
|
30934
32178
|
getCorrelationId,
|
|
30935
32179
|
getDefaultConfig,
|
|
32180
|
+
getEndSessionEndpoint,
|
|
30936
32181
|
getEnterpriseMigrations,
|
|
32182
|
+
getEnvSummary,
|
|
32183
|
+
getIntEnv,
|
|
30937
32184
|
getLogMeta,
|
|
30938
32185
|
getNextCronRun,
|
|
32186
|
+
getOptionalEnv,
|
|
32187
|
+
getRateLimitStatus,
|
|
30939
32188
|
getRequestId,
|
|
32189
|
+
getRequiredEnv,
|
|
32190
|
+
getSharedRedis,
|
|
30940
32191
|
getTenantId,
|
|
32192
|
+
getTokenEndpoint,
|
|
30941
32193
|
getTraceId,
|
|
30942
32194
|
getUserId,
|
|
32195
|
+
hasAllRoles,
|
|
32196
|
+
hasAnyRole,
|
|
32197
|
+
hasRole,
|
|
30943
32198
|
isAIError,
|
|
32199
|
+
isAllowlisted,
|
|
30944
32200
|
isApiError,
|
|
30945
32201
|
isAuthError,
|
|
30946
32202
|
isInContext,
|
|
30947
32203
|
isInQuietHours,
|
|
30948
32204
|
isPaymentError,
|
|
32205
|
+
isTokenExpired,
|
|
30949
32206
|
isValidCron,
|
|
30950
32207
|
loadConfig,
|
|
30951
32208
|
matchAction,
|
|
30952
32209
|
matchEventType,
|
|
32210
|
+
parseKeycloakRoles,
|
|
30953
32211
|
raceTimeout,
|
|
32212
|
+
refreshKeycloakToken,
|
|
32213
|
+
resetRateLimitForKey,
|
|
32214
|
+
resolveIdentifier,
|
|
32215
|
+
resolveRateLimitIdentifier,
|
|
30954
32216
|
retryable,
|
|
30955
32217
|
runWithContext,
|
|
30956
32218
|
runWithContextAsync,
|
|
30957
32219
|
safeValidateConfig,
|
|
32220
|
+
sanitizeApiError,
|
|
30958
32221
|
sanitizeForEmail,
|
|
30959
32222
|
sqlMigration,
|
|
30960
32223
|
stripHtml,
|
|
30961
32224
|
timedHealthCheck,
|
|
30962
32225
|
toHealthCheckResult,
|
|
30963
32226
|
validateConfig,
|
|
32227
|
+
validateEnvVars,
|
|
30964
32228
|
withCorrelation,
|
|
30965
32229
|
withCorrelationAsync,
|
|
30966
32230
|
withFallback,
|