@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.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: Redis } = await import("ioredis");
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 Redis({
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 Redis(options) : new Redis(options);
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: Redis } = await import("ioredis");
1920
+ const { default: Redis2 } = await import("ioredis");
1921
1921
  const options = toIORedisOptions(this.config);
1922
- this.subscriberClient = typeof options === "string" ? new Redis(options) : new Redis(options);
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: () => 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("HMAC key for deterministic hashing (derived from master key if not provided)")
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 Redis({
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
- createDatabaseAdapter(finalConfig),
16173
- createCacheAdapter(finalConfig),
16174
- createStorageAdapter(finalConfig),
16175
- createEmailAdapter(finalConfig),
16176
- createQueueAdapter(finalConfig),
16177
- createTracingAdapter(finalConfig),
16178
- createCryptoAdapter(finalConfig)
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 import_crypto27 = require("crypto");
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, import_crypto27.randomBytes)(4).toString("hex")}`;
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, import_crypto27.createHmac)(algorithm, secret).update(payload).digest("hex");
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 import_crypto28 = require("crypto");
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, import_crypto28.randomBytes)(4).toString("hex")}`;
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 import_crypto29 = require("crypto");
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, import_crypto29.randomBytes)(4).toString("hex")}`,
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 import_crypto30 = require("crypto");
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, import_crypto30.randomBytes)(4).toString("hex")}`;
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, import_crypto30.randomBytes)(4).toString("hex")}`;
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, import_crypto30.randomBytes)(4).toString("hex")}`;
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, import_crypto30.randomBytes)(4).toString("hex")}`;
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, import_crypto30.randomBytes)(4).toString("hex")}`;
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, import_crypto30.randomBytes)(4).toString("hex")}`;
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 import_crypto31 = require("crypto");
22813
+ var import_crypto32 = require("crypto");
21876
22814
  function generateId(prefix) {
21877
- return `${prefix}_${Date.now()}_${(0, import_crypto31.randomBytes)(4).toString("hex")}`;
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, import_crypto31.randomBytes)(16).toString("hex")}`;
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 import_crypto32 = require("crypto");
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, import_crypto32.randomBytes)(4).toString("hex")}`;
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, import_crypto32.randomBytes)(4).toString("hex")}`;
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, import_crypto32.randomBytes)(4).toString("hex")}`;
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, import_crypto32.randomBytes)(4).toString("hex")}`;
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, import_crypto32.randomBytes)(4).toString("hex")}`;
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 import_crypto33 = require("crypto");
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, import_crypto33.randomBytes)(4).toString("hex")}`;
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, import_crypto33.randomBytes)(4).toString("hex")}`;
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, import_crypto33.randomBytes)(4).toString("hex")}`;
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 import_crypto34 = require("crypto");
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, import_crypto34.randomBytes)(4).toString("hex")}`;
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 import_crypto35 = require("crypto");
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, import_crypto35.randomBytes)(4).toString("hex")}`;
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 import_crypto36 = require("crypto");
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, import_crypto36.timingSafeEqual)(providedBuffer, expectedBuffer)) {
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, import_crypto36.randomBytes)(4).toString("hex")}`;
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, import_crypto36.createHmac)(algorithm, secret).update(payload).digest("hex");
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 import_crypto37 = require("crypto");
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, import_crypto37.randomBytes)(8).toString("hex") + Date.now().toString(36);
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,