@getstrata/core 1.0.3 → 1.0.5

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +1 -1
  3. package/dist/core/runtime/appEnv.d.ts +16 -2
  4. package/dist/core/runtime/appKeyPrefix.d.ts +8 -1
  5. package/dist/entries/audit/exportAuditLogs.js +83 -18
  6. package/dist/entries/audit/siemFormatter.js +32 -3
  7. package/dist/entries/auth/intendedUrlCookie.js +33 -4
  8. package/dist/entries/auth/jwt.js +36 -4
  9. package/dist/entries/auth/jwtGuard.js +36 -4
  10. package/dist/entries/auth/oauth/providers.js +32 -3
  11. package/dist/entries/auth/oauth/samlProvider.js +32 -3
  12. package/dist/entries/auth/passwordConfirmCookie.js +35 -6
  13. package/dist/entries/auth/sessionCookie.js +35 -6
  14. package/dist/entries/auth/sessionGuard.js +35 -6
  15. package/dist/entries/auth/tokenHash.js +33 -4
  16. package/dist/entries/cache/createCacheStore.js +32 -3
  17. package/dist/entries/database/mysqlConnection.js +1 -132
  18. package/dist/entries/facades.js +32 -3
  19. package/dist/entries/http/corsMiddleware.js +16 -1
  20. package/dist/entries/http/csrfMiddleware.js +34 -5
  21. package/dist/entries/http/csrfToken.js +34 -5
  22. package/dist/entries/http/flashMiddleware.js +33 -4
  23. package/dist/entries/http/flashSession.js +33 -4
  24. package/dist/entries/http/loginThrottleMiddleware.js +1 -373
  25. package/dist/entries/http/memoryThrottleMiddleware.js +32 -58
  26. package/dist/entries/http/response.js +16 -56
  27. package/dist/entries/http/scimThrottleMiddleware.js +32 -3
  28. package/dist/entries/http/securityHeadersMiddleware.js +32 -3
  29. package/dist/entries/http/signedUrl.js +33 -4
  30. package/dist/entries/http/throttleMiddleware.js +32 -58
  31. package/dist/entries/http/webErrorResponse.js +1 -401
  32. package/dist/entries/jobs/exportAuditLogsJob.js +83 -18
  33. package/dist/entries/lifecycle/gracefulShutdown.js +1 -50
  34. package/dist/entries/mail/mailer.js +32 -3
  35. package/dist/entries/openapi/generator.js +32 -3
  36. package/dist/entries/queue/createAppQueue.js +32 -3
  37. package/dist/entries/queue/publicQueue.js +32 -3
  38. package/dist/entries/queue/queueMetrics.js +32 -3
  39. package/dist/entries/queue/redisQueue.js +32 -3
  40. package/dist/entries/runtime/appEnv.js +18 -1
  41. package/dist/entries/runtime/appKeyPrefix.js +1 -70
  42. package/dist/entries/security/oauthState.js +33 -4
  43. package/dist/entries/security/safeFetch.js +32 -9
  44. package/dist/entries/security/safeUrl.js +1 -105
  45. package/dist/entries/security/totp.js +32 -3
  46. package/dist/entries/tenant/databaseTenantContext.js +48 -6
  47. package/dist/entries/tracing/tracingMiddleware.js +32 -3
  48. package/dist/entries/view.js +1 -778
  49. package/dist/framework/public-api.d.ts +28 -19
  50. package/dist/index.js +448 -53
  51. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -490,6 +490,26 @@ class AuthManager {
490
490
  // ../../src/core/auth/jwt.ts
491
491
  import { createHmac, timingSafeEqual } from "crypto";
492
492
 
493
+ // ../../src/core/runtime/appEnv.ts
494
+ var NON_PRODUCTION_APP_ENVS = new Set(["local", "development", "dev", "test", "testing", "ci"]);
495
+ function normalizeEnvValue(value) {
496
+ return (value ?? "").trim().toLowerCase();
497
+ }
498
+ function isProductionEnv(env = process.env) {
499
+ const appEnv = normalizeEnvValue(env.APP_ENV);
500
+ const nodeEnv = normalizeEnvValue(env.NODE_ENV);
501
+ if (appEnv === "production" || nodeEnv === "production") {
502
+ return true;
503
+ }
504
+ if (appEnv === "") {
505
+ return false;
506
+ }
507
+ return !NON_PRODUCTION_APP_ENVS.has(appEnv);
508
+ }
509
+ function envFlagEnabled(value) {
510
+ return value === "true";
511
+ }
512
+
493
513
  // ../../src/core/runtime/appKeyPrefix.ts
494
514
  function appKeyPrefix() {
495
515
  return process.env.APP_KEY_PREFIX?.trim() || "strata";
@@ -500,6 +520,18 @@ function appCookieName(kind) {
500
520
  function appDevSecret(kind) {
501
521
  return `${appKeyPrefix()}-dev-${kind}`;
502
522
  }
523
+ function requireConfiguredSecret(names, devKind, env = process.env) {
524
+ for (const name of names) {
525
+ const value = env[name]?.trim();
526
+ if (value) {
527
+ return value;
528
+ }
529
+ }
530
+ if (isProductionEnv(env)) {
531
+ throw new Error(`${names[0]} must be set outside development. Do not derive secrets from the app name.`);
532
+ }
533
+ return appDevSecret(devKind);
534
+ }
503
535
  function namespacedRedisKey(kind) {
504
536
  return `${appKeyPrefix()}:${kind}`;
505
537
  }
@@ -508,16 +540,48 @@ function smtpEhloHost() {
508
540
  const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
509
541
  return safe || "strata.local";
510
542
  }
543
+ function siemEventType() {
544
+ return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
545
+ }
546
+ function appUserAgent() {
547
+ return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
548
+ }
511
549
  function otelServiceName() {
512
550
  return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
513
551
  }
552
+ function webhookSignatureHeader() {
553
+ return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
554
+ }
555
+ function appDisplayName() {
556
+ return process.env.APP_NAME?.trim() || "Strata";
557
+ }
514
558
  function appEnv() {
515
559
  return process.env.APP_ENV?.trim() || "local";
516
560
  }
561
+ function appUrl() {
562
+ return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
563
+ }
564
+ function apiPrefix() {
565
+ const raw = process.env.API_PREFIX?.trim() || "/api/v1";
566
+ const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
567
+ const trimmed = withSlash.replace(/\/+$/, "");
568
+ return trimmed || "/api/v1";
569
+ }
570
+ function sdkClientClassName() {
571
+ const override = process.env.APP_SDK_CLASS?.trim();
572
+ if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
573
+ return override;
574
+ }
575
+ const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
576
+ return fromName ? `${fromName}Client` : "AppClient";
577
+ }
517
578
 
518
579
  // ../../src/core/auth/jwt.ts
519
580
  function resolveJwtSecret(secret) {
520
- return secret?.trim() || process.env.JWT_SECRET?.trim() || process.env.SESSION_SECRET?.trim() || appDevSecret("jwt-secret");
581
+ if (secret?.trim()) {
582
+ return secret.trim();
583
+ }
584
+ return requireConfiguredSecret(["JWT_SECRET", "SESSION_SECRET"], "jwt-secret");
521
585
  }
522
586
  function jwtTtlSeconds(override) {
523
587
  if (typeof override === "number" && Number.isInteger(override) && override > 0) {
@@ -642,6 +706,9 @@ function configureMembershipLookup(lookup) {
642
706
  function resolveMembershipLookup() {
643
707
  return membershipRepository;
644
708
  }
709
+ function resetMembershipLookupForTests() {
710
+ membershipRepository = uninitializedMembershipLookup;
711
+ }
645
712
  async function runWithMembershipContext(callback) {
646
713
  const user = currentAuthUser();
647
714
  if (!user || isGlobalAdmin(user)) {
@@ -1101,6 +1168,11 @@ function getDefaultDatabaseQuery() {
1101
1168
  }
1102
1169
  return query;
1103
1170
  }
1171
+ function resetDefaultDatabasePoolForTests() {
1172
+ const holder = defaultConnectionState();
1173
+ holder.pool = null;
1174
+ holder.query = null;
1175
+ }
1104
1176
 
1105
1177
  // ../../src/core/security/timingSafeCompare.ts
1106
1178
  import { timingSafeEqual as timingSafeEqual2 } from "crypto";
@@ -2215,6 +2287,14 @@ function appendWhereParts(tableName, where, params) {
2215
2287
  }
2216
2288
  return clauses.join(" AND ");
2217
2289
  }
2290
+ function buildWhereClause(tableName, where = {}) {
2291
+ const params = [];
2292
+ const body = appendWhereParts(tableName, where, params);
2293
+ return {
2294
+ clause: body.length > 0 ? ` WHERE ${body}` : "",
2295
+ params
2296
+ };
2297
+ }
2218
2298
  function remapExistsSql(sql, existsParams, params) {
2219
2299
  const offset = params.length;
2220
2300
  params.push(...existsParams);
@@ -5226,6 +5306,11 @@ var importMysql = defaultImportMysql;
5226
5306
  async function defaultImportMysql() {
5227
5307
  return import("mysql2/promise");
5228
5308
  }
5309
+ function resetMysqlLoaderForTests(importer) {
5310
+ mysqlModule = undefined;
5311
+ mysqlPending = undefined;
5312
+ importMysql = importer ? async () => await importer() : defaultImportMysql;
5313
+ }
5229
5314
  function mysqlApi(mod) {
5230
5315
  if (typeof mod.createPool === "function") {
5231
5316
  return mod;
@@ -6643,6 +6728,9 @@ function generateCspNonce() {
6643
6728
  function configureContentSecurityPolicy(options) {
6644
6729
  configuredHtmlOptions = { ...options };
6645
6730
  }
6731
+ function resetContentSecurityPolicyForTests() {
6732
+ configuredHtmlOptions = {};
6733
+ }
6646
6734
  function cloneDirectives(source) {
6647
6735
  const copy = {};
6648
6736
  for (const [directive, values] of Object.entries(source)) {
@@ -6753,11 +6841,6 @@ function readRequestCookie(request, name) {
6753
6841
  function readBunRequestCookie(request, name) {
6754
6842
  return request.cookies.get(name) ?? readRequestCookie(request, name);
6755
6843
  }
6756
- // ../../src/core/runtime/appEnv.ts
6757
- function isProductionEnv(env = process.env) {
6758
- return env.APP_ENV === "production" || env.NODE_ENV === "production";
6759
- }
6760
-
6761
6844
  // ../../src/core/http/corsMiddleware.ts
6762
6845
  function defaultAllowedOrigins() {
6763
6846
  return isProductionEnv() ? "" : "*";
@@ -6823,7 +6906,7 @@ function csrfCookieName() {
6823
6906
  return process.env.CSRF_COOKIE_NAME?.trim() || appCookieName("csrf");
6824
6907
  }
6825
6908
  function resolveCsrfSecret() {
6826
- return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || appDevSecret("csrf-secret");
6909
+ return requireConfiguredSecret(["SESSION_SECRET", "OAUTH_STATE_SECRET", "ADMIN_API_TOKEN"], "csrf-secret");
6827
6910
  }
6828
6911
  function csrfVerifyOptions() {
6829
6912
  return { secret: resolveCsrfSecret(), maxAge: CSRF_TTL_MS };
@@ -6838,7 +6921,7 @@ function tokensMatch(left, right) {
6838
6921
  }
6839
6922
  function createCsrfTokenCookie() {
6840
6923
  const token = Bun.CSRF.generate(resolveCsrfSecret(), { expiresIn: CSRF_TTL_MS });
6841
- const secure = process.env.APP_ENV === "production" ? "; Secure" : "";
6924
+ const secure = isProductionEnv() ? "; Secure" : "";
6842
6925
  return {
6843
6926
  token,
6844
6927
  cookie: `${csrfCookieName()}=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Max-Age=${Math.floor(CSRF_TTL_MS / 1000)}${secure}`
@@ -6958,7 +7041,7 @@ function flashCookieName() {
6958
7041
  return process.env.FLASH_COOKIE_NAME?.trim() || appCookieName("flash");
6959
7042
  }
6960
7043
  function resolveFlashSecret() {
6961
- return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || appDevSecret("flash-secret");
7044
+ return requireConfiguredSecret(["SESSION_SECRET", "OAUTH_STATE_SECRET"], "flash-secret");
6962
7045
  }
6963
7046
  function signFlashPayload(payload, issuedAt) {
6964
7047
  const signature = createHmac2("sha256", resolveFlashSecret()).update(`${payload}.${issuedAt}`).digest("hex");
@@ -7068,6 +7151,49 @@ function getQueryParams(request) {
7068
7151
  }
7069
7152
  return new URL(request.url).searchParams;
7070
7153
  }
7154
+ function parseOptionalPositiveIntQueryParam(params, name) {
7155
+ const value = params.get(name);
7156
+ if (value === null || value.trim() === "") {
7157
+ return;
7158
+ }
7159
+ const parsed = Number.parseInt(value, 10);
7160
+ if (!Number.isInteger(parsed) || parsed <= 0) {
7161
+ throw new BadRequestError(`Invalid query parameter "${name}". Expected a positive integer.`);
7162
+ }
7163
+ return parsed;
7164
+ }
7165
+ function parseOptionalBooleanQueryParam(params, name) {
7166
+ const value = params.get(name);
7167
+ if (value === null || value.trim() === "") {
7168
+ return;
7169
+ }
7170
+ switch (value.toLowerCase()) {
7171
+ case "true":
7172
+ case "1":
7173
+ return true;
7174
+ case "false":
7175
+ case "0":
7176
+ return false;
7177
+ default:
7178
+ throw new BadRequestError(`Invalid query parameter "${name}". Expected a boolean.`);
7179
+ }
7180
+ }
7181
+ function parseOptionalEnumQueryParam(params, name, allowedValues) {
7182
+ const value = params.get(name);
7183
+ if (value === null || value.trim() === "") {
7184
+ return;
7185
+ }
7186
+ if (!allowedValues.includes(value)) {
7187
+ throw new BadRequestError(`Invalid query parameter "${name}". Expected one of: ${allowedValues.join(", ")}.`);
7188
+ }
7189
+ return value;
7190
+ }
7191
+ function expectObject(value, label = "request body") {
7192
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
7193
+ throw new BadRequestError(`${label} must be a JSON object.`);
7194
+ }
7195
+ return value;
7196
+ }
7071
7197
  async function parseJsonBody(request, validator) {
7072
7198
  let payload;
7073
7199
  try {
@@ -7077,6 +7203,55 @@ async function parseJsonBody(request, validator) {
7077
7203
  }
7078
7204
  return validator(payload);
7079
7205
  }
7206
+ function readRequiredString(payload, field, options = {}) {
7207
+ const value = payload[field];
7208
+ if (typeof value !== "string" || value.trim() === "") {
7209
+ throw new BadRequestError(`"${field}" is required and must be a string.`);
7210
+ }
7211
+ const trimmed = value.trim();
7212
+ if (options.minLength !== undefined && trimmed.length < options.minLength) {
7213
+ throw new BadRequestError(`"${field}" must be at least ${options.minLength} characters.`);
7214
+ }
7215
+ if (options.maxLength !== undefined && trimmed.length > options.maxLength) {
7216
+ throw new BadRequestError(`"${field}" must be at most ${options.maxLength} characters.`);
7217
+ }
7218
+ if (options.pattern && !options.pattern.test(trimmed)) {
7219
+ throw new BadRequestError(`"${field}" has an invalid format.`);
7220
+ }
7221
+ return trimmed;
7222
+ }
7223
+ function readOptionalString(payload, field, options = {}) {
7224
+ if (!(field in payload) || payload[field] === undefined) {
7225
+ return;
7226
+ }
7227
+ return readRequiredString(payload, field, options);
7228
+ }
7229
+ function readRequiredEnum(payload, field, allowedValues) {
7230
+ const value = readRequiredString(payload, field);
7231
+ if (!allowedValues.includes(value)) {
7232
+ throw new BadRequestError(`"${field}" must be one of: ${allowedValues.join(", ")}.`);
7233
+ }
7234
+ return value;
7235
+ }
7236
+ function readOptionalEnum(payload, field, allowedValues) {
7237
+ if (!(field in payload) || payload[field] === undefined) {
7238
+ return;
7239
+ }
7240
+ return readRequiredEnum(payload, field, allowedValues);
7241
+ }
7242
+ function readRequiredPositiveInt(payload, field) {
7243
+ const value = payload[field];
7244
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
7245
+ throw new BadRequestError(`"${field}" is required and must be a positive integer.`);
7246
+ }
7247
+ return value;
7248
+ }
7249
+ function readOptionalPositiveInt(payload, field) {
7250
+ if (!(field in payload) || payload[field] === undefined) {
7251
+ return;
7252
+ }
7253
+ return readRequiredPositiveInt(payload, field);
7254
+ }
7080
7255
  function parsePositiveIntParam(value, name = "id") {
7081
7256
  const parsed = Number.parseInt(value, 10);
7082
7257
  if (!Number.isInteger(parsed) || parsed <= 0) {
@@ -7097,6 +7272,12 @@ class FormRequest {
7097
7272
  return await parseJsonBody(request, (payload) => this.parse(payload));
7098
7273
  }
7099
7274
  }
7275
+
7276
+ class QueryFormRequest {
7277
+ validate(request) {
7278
+ return this.parseQuery(request);
7279
+ }
7280
+ }
7100
7281
  // ../../src/core/http/authMiddleware.ts
7101
7282
  function createAuthMiddleware(auth) {
7102
7283
  return async (request, next) => {
@@ -7659,6 +7840,10 @@ function withErrorHandling(handler) {
7659
7840
  }
7660
7841
  };
7661
7842
  }
7843
+ // ../../src/core/http/route.ts
7844
+ function getRouteParams(request) {
7845
+ return request.params;
7846
+ }
7662
7847
  // ../../src/core/http/routeMiddleware.ts
7663
7848
  function withMiddleware(...middleware) {
7664
7849
  const wrap = composeMiddleware(...middleware);
@@ -7832,6 +8017,9 @@ function createLoginThrottleMiddleware(options) {
7832
8017
  }
7833
8018
  return createMemoryLoginThrottleMiddleware(options);
7834
8019
  }
8020
+ function resetMemoryLoginThrottleForTests() {
8021
+ memoryLoginBuckets.clear();
8022
+ }
7835
8023
  // ../../src/core/http/memoryThrottleMiddleware.ts
7836
8024
  var throttleBucketRegistries = new Set;
7837
8025
  function createMemoryThrottleMiddleware(options) {
@@ -8019,7 +8207,7 @@ function intendedUrlTtlSeconds() {
8019
8207
  return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_INTENDED_URL_TTL_SECONDS;
8020
8208
  }
8021
8209
  function cookieSecureFlag() {
8022
- return process.env.APP_ENV === "production" ? "; Secure" : "";
8210
+ return isProductionEnv() ? "; Secure" : "";
8023
8211
  }
8024
8212
  function pathnameOf(path) {
8025
8213
  const pathname = path.split("?")[0] ?? path;
@@ -8140,7 +8328,7 @@ function createSecurityHeadersMiddleware(options = {}) {
8140
8328
  // ../../src/core/http/signedUrl.ts
8141
8329
  import { createHmac as createHmac3 } from "crypto";
8142
8330
  function resolveSignedUrlSecret() {
8143
- return process.env.SIGNED_URL_SECRET?.trim() || process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || appDevSecret("signed-url-secret");
8331
+ return requireConfiguredSecret(["SIGNED_URL_SECRET", "SESSION_SECRET", "OAUTH_STATE_SECRET"], "signed-url-secret");
8144
8332
  }
8145
8333
  function resolveSignedUrlOrigin() {
8146
8334
  return (process.env.APP_URL ?? "http://localhost:3000").replace(/\/$/, "");
@@ -8339,6 +8527,11 @@ function installGracefulShutdownSignals(signals = ["SIGINT", "SIGTERM"]) {
8339
8527
  });
8340
8528
  }
8341
8529
  }
8530
+ function resetGracefulShutdownForTests() {
8531
+ shutdownHandlers.clear();
8532
+ shutdownInstalled = false;
8533
+ shuttingDown = false;
8534
+ }
8342
8535
  // ../../src/core/logging/requestLoggingMiddleware.ts
8343
8536
  function createRequestLoggingMiddleware() {
8344
8537
  return async (request, next) => {
@@ -8584,6 +8777,42 @@ class AsyncQueue {
8584
8777
  function createQueue(driver) {
8585
8778
  return driver === "async" ? new AsyncQueue : new SyncQueue;
8586
8779
  }
8780
+ // ../../src/core/queue/jobRegistry.ts
8781
+ class JobRegistry {
8782
+ factories = new Map;
8783
+ instances = new WeakMap;
8784
+ register(name, factory) {
8785
+ this.factories.set(name, factory);
8786
+ }
8787
+ resolveName(job) {
8788
+ return this.instances.get(job);
8789
+ }
8790
+ track(name, job) {
8791
+ this.instances.set(job, name);
8792
+ return job;
8793
+ }
8794
+ create(name) {
8795
+ const factory = this.factories.get(name);
8796
+ if (!factory) {
8797
+ return;
8798
+ }
8799
+ return factory();
8800
+ }
8801
+ names() {
8802
+ return [...this.factories.keys()];
8803
+ }
8804
+ }
8805
+ var JOB_REGISTRY_KEY = Symbol.for("@getstrata/jobRegistry");
8806
+ function readSharedJobRegistry() {
8807
+ const globalRegistry = globalThis[JOB_REGISTRY_KEY];
8808
+ if (globalRegistry) {
8809
+ return globalRegistry;
8810
+ }
8811
+ const registry = new JobRegistry;
8812
+ globalThis[JOB_REGISTRY_KEY] = registry;
8813
+ return registry;
8814
+ }
8815
+ var jobRegistry = readSharedJobRegistry();
8587
8816
  // ../../src/core/queue/failedJobTable.ts
8588
8817
  var failedJobTable = defineTable({
8589
8818
  name: "failed_job",
@@ -8644,43 +8873,6 @@ class FailedJobService {
8644
8873
  }
8645
8874
  var failedJobService_default = FailedJobService;
8646
8875
 
8647
- // ../../src/core/queue/jobRegistry.ts
8648
- class JobRegistry {
8649
- factories = new Map;
8650
- instances = new WeakMap;
8651
- register(name, factory) {
8652
- this.factories.set(name, factory);
8653
- }
8654
- resolveName(job) {
8655
- return this.instances.get(job);
8656
- }
8657
- track(name, job) {
8658
- this.instances.set(job, name);
8659
- return job;
8660
- }
8661
- create(name) {
8662
- const factory = this.factories.get(name);
8663
- if (!factory) {
8664
- return;
8665
- }
8666
- return factory();
8667
- }
8668
- names() {
8669
- return [...this.factories.keys()];
8670
- }
8671
- }
8672
- var JOB_REGISTRY_KEY = Symbol.for("@getstrata/jobRegistry");
8673
- function readSharedJobRegistry() {
8674
- const globalRegistry = globalThis[JOB_REGISTRY_KEY];
8675
- if (globalRegistry) {
8676
- return globalRegistry;
8677
- }
8678
- const registry = new JobRegistry;
8679
- globalThis[JOB_REGISTRY_KEY] = registry;
8680
- return registry;
8681
- }
8682
- var jobRegistry = readSharedJobRegistry();
8683
-
8684
8876
  // ../../src/core/queue/queueConfig.ts
8685
8877
  function resolveQueueConfig() {
8686
8878
  const driver = process.env.QUEUE_DRIVER;
@@ -9027,20 +9219,131 @@ function isPublicReadsEnabled() {
9027
9219
  function guestCanViewResource() {
9028
9220
  return isPublicReadsEnabled();
9029
9221
  }
9222
+ // ../../src/core/security/safeUrl.ts
9223
+ import { lookup as dnsLookupImpl } from "dns/promises";
9224
+ var dnsLookup = dnsLookupImpl;
9225
+ var BLOCKED_HOSTNAMES = new Set([
9226
+ "localhost",
9227
+ "127.0.0.1",
9228
+ "0.0.0.0",
9229
+ "::1",
9230
+ "metadata.google.internal"
9231
+ ]);
9232
+ function isPrivateIpv4(hostname) {
9233
+ const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
9234
+ if (!match) {
9235
+ return false;
9236
+ }
9237
+ const octets = match.slice(1, 5).map((part) => Number.parseInt(part, 10));
9238
+ if (octets.some((octet) => octet < 0 || octet > 255)) {
9239
+ return true;
9240
+ }
9241
+ const [a = 0, b = 0] = octets;
9242
+ if (a === 10) {
9243
+ return true;
9244
+ }
9245
+ if (a === 127) {
9246
+ return true;
9247
+ }
9248
+ if (a === 0) {
9249
+ return true;
9250
+ }
9251
+ if (a === 169 && b === 254) {
9252
+ return true;
9253
+ }
9254
+ if (a === 172 && b >= 16 && b <= 31) {
9255
+ return true;
9256
+ }
9257
+ if (a === 192 && b === 168) {
9258
+ return true;
9259
+ }
9260
+ return false;
9261
+ }
9262
+ function isBlockedHostname(hostname) {
9263
+ const normalized = hostname.trim().toLowerCase();
9264
+ if (normalized.length === 0) {
9265
+ return true;
9266
+ }
9267
+ if (BLOCKED_HOSTNAMES.has(normalized)) {
9268
+ return true;
9269
+ }
9270
+ if (normalized.endsWith(".local") || normalized.endsWith(".internal")) {
9271
+ return true;
9272
+ }
9273
+ if (normalized.includes(":")) {
9274
+ return true;
9275
+ }
9276
+ return isPrivateIpv4(normalized);
9277
+ }
9278
+ function assertSafeOutboundUrl(rawUrl, options = {}) {
9279
+ let parsed;
9280
+ try {
9281
+ parsed = new URL(rawUrl);
9282
+ } catch {
9283
+ throw new BadRequestError("Webhook URL is invalid.");
9284
+ }
9285
+ if (parsed.protocol !== "https:" && !(options.allowHttp && parsed.protocol === "http:")) {
9286
+ throw new BadRequestError("Webhook URL must use HTTPS.");
9287
+ }
9288
+ if (parsed.username || parsed.password) {
9289
+ throw new BadRequestError("Webhook URL must not include credentials.");
9290
+ }
9291
+ if (isBlockedHostname(parsed.hostname)) {
9292
+ throw new BadRequestError("Webhook URL targets a blocked host.");
9293
+ }
9294
+ return parsed;
9295
+ }
9296
+ function isBlockedIpAddress(address) {
9297
+ return isBlockedHostname(address.trim().toLowerCase());
9298
+ }
9299
+ async function assertSafeOutboundUrlResolved(rawUrl, options = {}) {
9300
+ const parsed = assertSafeOutboundUrl(rawUrl, options);
9301
+ if (options.resolveDns === false) {
9302
+ return parsed;
9303
+ }
9304
+ const hostname = parsed.hostname.trim().toLowerCase();
9305
+ const results = await dnsLookup(hostname, { all: true, verbatim: true });
9306
+ if (results.some((result) => isBlockedIpAddress(result.address))) {
9307
+ throw new BadRequestError("Webhook URL targets a blocked host.");
9308
+ }
9309
+ return parsed;
9310
+ }
9311
+ function setDnsLookupForTests(lookupFn) {
9312
+ dnsLookup = lookupFn;
9313
+ }
9314
+ function resetDnsLookupForTests() {
9315
+ dnsLookup = dnsLookupImpl;
9316
+ }
9030
9317
  // ../../src/core/tenant/tenantMiddleware.ts
9031
9318
  import { createHash } from "crypto";
9032
9319
 
9033
9320
  // ../../src/core/tenant/databaseTenantContext.ts
9321
+ async function applyBypassToTransaction(transaction, bypass) {
9322
+ await transaction.unsafe(`SELECT set_config('app.bypass_rls', $1, true)`, [
9323
+ bypass ? "true" : "false"
9324
+ ]);
9325
+ }
9034
9326
  async function runWithMigrationBypass(callback) {
9035
9327
  if (!isRlsTenancy()) {
9036
9328
  return await callback();
9037
9329
  }
9038
- await repositoryConnection`SELECT set_config('app.bypass_rls', 'true', false)`;
9039
- try {
9040
- return await callback();
9041
- } finally {
9042
- await repositoryConnection`SELECT set_config('app.bypass_rls', 'false', false)`;
9330
+ if (hasActiveDatabaseConnection()) {
9331
+ const activeConnection = getActiveDatabaseConnection(getDefaultDatabasePool());
9332
+ await applyBypassToTransaction(activeConnection, true);
9333
+ try {
9334
+ return await callback();
9335
+ } finally {
9336
+ await applyBypassToTransaction(activeConnection, false);
9337
+ }
9043
9338
  }
9339
+ const pool = getDefaultDatabasePool();
9340
+ if (typeof pool.begin !== "function") {
9341
+ throw new Error("RLS migration bypass requires a pool that supports begin(). Session-scoped set_config is not used on pooled connections.");
9342
+ }
9343
+ return await pool.begin(async (transaction) => {
9344
+ await applyBypassToTransaction(transaction, true);
9345
+ return await runWithDatabaseConnection(transaction, callback);
9346
+ });
9044
9347
  }
9045
9348
 
9046
9349
  // ../../src/core/tenant/tenantMiddleware.ts
@@ -9608,6 +9911,7 @@ export {
9608
9911
  CompositeGuard,
9609
9912
  ConfigStore,
9610
9913
  ConflictError,
9914
+ DEFAULT_PER_PAGE,
9611
9915
  DEFAULT_TENANT,
9612
9916
  DEFAULT_VIEWS_DIRECTORY,
9613
9917
  DatabaseTokenGuard,
@@ -9620,15 +9924,19 @@ export {
9620
9924
  ForeignIdColumnDefinition,
9621
9925
  FormRequest,
9622
9926
  GuestGuard,
9927
+ HTMX_2_0_4_INDICATOR_STYLE_HASH,
9623
9928
  HasManyRelationQuery,
9929
+ HasManyThroughRelationQuery,
9624
9930
  HasOneRelationQuery,
9625
9931
  HttpError,
9626
9932
  InternalServerError,
9627
9933
  Job,
9934
+ JobRegistry,
9628
9935
  JsonResource,
9629
9936
  JwtGuard,
9630
9937
  LocalStorageDriver,
9631
9938
  LogMailDriver,
9939
+ MAX_PER_PAGE,
9632
9940
  Mailer,
9633
9941
  membershipService_default as MembershipService,
9634
9942
  Model,
@@ -9646,6 +9954,7 @@ export {
9646
9954
  PostgresGrammar,
9647
9955
  PreconditionFailedError,
9648
9956
  PrometheusRegistry,
9957
+ QueryFormRequest,
9649
9958
  QueueWorker,
9650
9959
  RedisQueue,
9651
9960
  RepositoryQuery,
@@ -9664,7 +9973,15 @@ export {
9664
9973
  WebFormRequest,
9665
9974
  WhereBuilder,
9666
9975
  absoluteTemporarySignedUrl,
9976
+ apiPrefix,
9977
+ appCookieName,
9978
+ appDevSecret,
9979
+ appDisplayName,
9980
+ appEnv,
9981
+ appKeyPrefix,
9667
9982
  appSchedule,
9983
+ appUrl,
9984
+ appUserAgent,
9668
9985
  appendOrganizationScope,
9669
9986
  appendProjectScope,
9670
9987
  applyCasts,
@@ -9673,6 +9990,8 @@ export {
9673
9990
  assertIfMatch,
9674
9991
  assertOrganizationReadable,
9675
9992
  assertResourceInCurrentTenant,
9993
+ assertSafeOutboundUrl,
9994
+ assertSafeOutboundUrlResolved,
9676
9995
  assertValidSignature,
9677
9996
  auditChecksum,
9678
9997
  auth,
@@ -9682,13 +10001,29 @@ export {
9682
10001
  bindBunSql,
9683
10002
  bindDatabaseConnection,
9684
10003
  bindRouteModel,
10004
+ buildAdvancedWhereClause,
10005
+ buildCountQuery,
10006
+ buildDeleteByIdQuery,
10007
+ buildGroupedCountQuery,
10008
+ buildInsertQuery,
10009
+ buildJoinClause,
9685
10010
  buildMarkdownMailMessage,
10011
+ buildOrderByClause,
10012
+ buildPaginationMeta,
10013
+ buildProjectionQuery,
10014
+ buildQueryWhereClause,
9686
10015
  buildRequestCacheKey,
10016
+ buildRestoreByIdQuery,
10017
+ buildSelectQuery,
9687
10018
  buildSmtpPayload,
10019
+ buildSoftDeleteByIdQuery,
10020
+ buildUpdateQuery,
10021
+ buildWhereClause,
9688
10022
  cache,
9689
10023
  collectQueueMetrics,
9690
10024
  compileBlueprint,
9691
10025
  composeMiddleware,
10026
+ computeEtagFromJson,
9692
10027
  conditionalJsonResponse,
9693
10028
  config,
9694
10029
  configureContentSecurityPolicy,
@@ -9710,6 +10045,7 @@ export {
9710
10045
  createFlashMiddleware,
9711
10046
  createLoginThrottleMiddleware,
9712
10047
  createMembershipMiddleware,
10048
+ createMemoryLoginThrottleMiddleware,
9713
10049
  createMemoryThrottleMiddleware,
9714
10050
  createMetricsMiddleware,
9715
10051
  createMysqlConnection,
@@ -9750,10 +10086,14 @@ export {
9750
10086
  dialectFor,
9751
10087
  emailRule,
9752
10088
  emptyPaginateResult,
10089
+ envFlagEnabled,
10090
+ errorResponse,
9753
10091
  errorTemplateName,
9754
10092
  etagFromResource,
10093
+ etagValuesMatch,
9755
10094
  eventBus,
9756
10095
  events,
10096
+ expectObject,
9757
10097
  filterMassAssignable,
9758
10098
  foreignKeyFromTable,
9759
10099
  formatAdminValue,
@@ -9765,10 +10105,13 @@ export {
9765
10105
  getDefaultDatabaseQuery,
9766
10106
  getMigrationStatus,
9767
10107
  getNamedConnection,
10108
+ getQueryParams,
10109
+ getRouteParams,
9768
10110
  grammarForDriver,
9769
10111
  guestCanViewResource,
9770
10112
  hasActiveDatabaseConnection,
9771
10113
  hasMany,
10114
+ hasManyThrough,
9772
10115
  hasMinimumOrgRole2 as hasMinimumOrgRole,
9773
10116
  hasNamedConnection,
9774
10117
  hasOne,
@@ -9777,20 +10120,26 @@ export {
9777
10120
  htmlErrorResponse,
9778
10121
  htmlResponse,
9779
10122
  hydrateValue,
10123
+ ifMatchSatisfied,
10124
+ ifNoneMatchSatisfied,
9780
10125
  indexBelongsToManyRelation,
9781
10126
  indexBelongsToRelation,
9782
10127
  indexHasManyRelation,
10128
+ indexHasManyThroughRelation,
9783
10129
  indexHasOneRelation,
9784
10130
  indexMorphManyRelation,
9785
10131
  indexMorphOneRelation,
9786
10132
  indexMorphToRelation,
9787
10133
  inferReferencedTable,
9788
10134
  installGracefulShutdownSignals,
10135
+ isBlockedHostname,
10136
+ isBlockedIpAddress,
9789
10137
  isEtagEnabled,
9790
10138
  isGlobalAdmin,
9791
10139
  isHtmxRequest,
9792
10140
  isHttpErrorLike,
9793
10141
  isInsideTenantDatabaseScope,
10142
+ isProductionEnv,
9794
10143
  isPublicReadsEnabled,
9795
10144
  isRlsTenancy,
9796
10145
  isTenancyEnabled,
@@ -9801,32 +10150,54 @@ export {
9801
10150
  loadSeedersFromDirectory,
9802
10151
  log,
9803
10152
  logSecurityEvent,
10153
+ logServerError,
9804
10154
  loginRedirectLocation,
9805
10155
  mail,
9806
10156
  mailer,
10157
+ mapDatabaseError,
9807
10158
  markdownToHtml,
9808
10159
  maxLength,
10160
+ membershipContext,
10161
+ membershipRepository,
9809
10162
  migrateDatabase,
9810
10163
  minLength,
9811
10164
  modelEventName,
9812
10165
  morphMany,
9813
10166
  morphOne,
9814
10167
  morphTo,
10168
+ namespacedRedisKey,
9815
10169
  noContentResponse,
10170
+ normalizeFieldErrors,
9816
10171
  normalizeMetricPath,
9817
10172
  notFoundHtmlResponse,
10173
+ notModifiedResponse,
10174
+ otelServiceName,
9818
10175
  paginatedResponse,
10176
+ parseJsonBody,
9819
10177
  parseMultipartUpload,
10178
+ parseOptionalBooleanQueryParam,
10179
+ parseOptionalEnumQueryParam,
10180
+ parseOptionalPositiveIntQueryParam,
9820
10181
  parsePaginationQuery,
9821
10182
  parsePositiveIntParam,
10183
+ parseQualifiedColumn,
9822
10184
  pivotTableName,
9823
10185
  policyGate,
9824
10186
  prometheusRegistry,
10187
+ qualifyColumn,
9825
10188
  queue,
10189
+ quoteIdentifier,
9826
10190
  rateLimitMultiplierForPlan,
9827
10191
  readBunRequestCookie,
9828
10192
  readClientIp,
10193
+ readOptionalEnum,
10194
+ readOptionalPositiveInt,
10195
+ readOptionalString,
9829
10196
  readRequestCookie,
10197
+ readRequiredEnum,
10198
+ readRequiredPositiveInt,
10199
+ readRequiredString,
10200
+ readSharedEventBus,
9830
10201
  readSubmittedCsrfToken,
9831
10202
  readSubmittedCsrfTokenFromBody,
9832
10203
  readTenancyDriver,
@@ -9838,12 +10209,22 @@ export {
9838
10209
  registerShutdownHandler,
9839
10210
  renderKernelErrorChrome,
9840
10211
  renderMarkdownMail,
10212
+ renderWebErrorHtml,
9841
10213
  repositoryConnection,
9842
10214
  requestIdMiddleware,
10215
+ requestMetaContext,
10216
+ requireConfiguredSecret,
9843
10217
  required,
9844
10218
  resetBoundDatabaseConnection,
10219
+ resetContentSecurityPolicyForTests,
10220
+ resetDefaultDatabasePoolForTests,
9845
10221
  resetDefaultStorage,
10222
+ resetDnsLookupForTests,
10223
+ resetGracefulShutdownForTests,
10224
+ resetMembershipLookupForTests,
10225
+ resetMemoryLoginThrottleForTests,
9846
10226
  resetMemoryThrottleForTests,
10227
+ resetMysqlLoaderForTests,
9847
10228
  resetNamedConnections,
9848
10229
  resetSqlDialect,
9849
10230
  resolveApplicationAuth,
@@ -9859,11 +10240,15 @@ export {
9859
10240
  resolveCsrfTokenForRequest,
9860
10241
  resolveDatabaseDriver,
9861
10242
  resolveHtmlContentSecurityPolicy,
10243
+ resolveLoginEmail,
10244
+ resolveLoginIdentity,
9862
10245
  resolveMembershipLookup,
9863
10246
  resolveMembershipService,
9864
10247
  resolveOrganizationScope,
10248
+ resolveQualifiedColumn,
9865
10249
  resolveRepositoryConnection,
9866
10250
  resolveService,
10251
+ resolveSoftDeleteColumn,
9867
10252
  resolveUserId,
9868
10253
  resolveUserTenantId,
9869
10254
  resolveWebLayoutData,
@@ -9886,16 +10271,21 @@ export {
9886
10271
  safeInternalRedirectPath,
9887
10272
  sanitizeInternalPath,
9888
10273
  sanitizeMailHtml,
10274
+ sanitizeUploadFileName,
9889
10275
  scopedOrganizationIds,
10276
+ sdkClientClassName,
9890
10277
  securedBindRouteModel,
9891
10278
  securedBindRouteModelByKey,
9892
10279
  sendMarkdownMail,
9893
10280
  serializeDate,
9894
10281
  serverHtmxContentSecurityPolicy,
9895
10282
  setActiveApplicationContext,
10283
+ setDnsLookupForTests,
10284
+ siemEventType,
9896
10285
  signJwt,
9897
10286
  signedUrl,
9898
10287
  singularize,
10288
+ smtpEhloHost,
9899
10289
  spaContentSecurityPolicy,
9900
10290
  sqlTimestamp,
9901
10291
  storageFacade as storage,
@@ -9903,17 +10293,22 @@ export {
9903
10293
  stringRule,
9904
10294
  stripMarkdown,
9905
10295
  temporarySignedUrl,
10296
+ tenantContext,
9906
10297
  textResponse,
9907
10298
  toHttpError,
9908
10299
  toPaginatedResourceCollection,
9909
10300
  toResourceCollection,
10301
+ traceContextStorage,
9910
10302
  trustForwardedFor,
9911
10303
  unregisterNamedConnection,
9912
10304
  useSqlDialect,
9913
10305
  validateObject,
9914
10306
  verifyCsrfToken,
9915
10307
  verifyJwt,
10308
+ webErrorResponse,
10309
+ webhookSignatureHeader,
9916
10310
  whenLoaded,
10311
+ withDatabaseErrorHandling,
9917
10312
  withErrorHandling,
9918
10313
  withMiddleware,
9919
10314
  withMigrationLock,