@getstrata/core 1.0.3 → 1.0.4

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 (49) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/core/runtime/appEnv.d.ts +16 -2
  3. package/dist/core/runtime/appKeyPrefix.d.ts +8 -1
  4. package/dist/entries/audit/exportAuditLogs.js +83 -18
  5. package/dist/entries/audit/siemFormatter.js +32 -3
  6. package/dist/entries/auth/intendedUrlCookie.js +33 -4
  7. package/dist/entries/auth/jwt.js +36 -4
  8. package/dist/entries/auth/jwtGuard.js +36 -4
  9. package/dist/entries/auth/oauth/providers.js +32 -3
  10. package/dist/entries/auth/oauth/samlProvider.js +32 -3
  11. package/dist/entries/auth/passwordConfirmCookie.js +35 -6
  12. package/dist/entries/auth/sessionCookie.js +35 -6
  13. package/dist/entries/auth/sessionGuard.js +35 -6
  14. package/dist/entries/auth/tokenHash.js +33 -4
  15. package/dist/entries/cache/createCacheStore.js +32 -3
  16. package/dist/entries/facades.js +32 -3
  17. package/dist/entries/http/corsMiddleware.js +16 -1
  18. package/dist/entries/http/csrfMiddleware.js +34 -5
  19. package/dist/entries/http/csrfToken.js +34 -5
  20. package/dist/entries/http/flashMiddleware.js +33 -4
  21. package/dist/entries/http/flashSession.js +33 -4
  22. package/dist/entries/http/loginThrottleMiddleware.js +1 -373
  23. package/dist/entries/http/memoryThrottleMiddleware.js +32 -58
  24. package/dist/entries/http/response.js +16 -56
  25. package/dist/entries/http/scimThrottleMiddleware.js +32 -3
  26. package/dist/entries/http/securityHeadersMiddleware.js +32 -3
  27. package/dist/entries/http/signedUrl.js +33 -4
  28. package/dist/entries/http/throttleMiddleware.js +32 -58
  29. package/dist/entries/http/webErrorResponse.js +1 -401
  30. package/dist/entries/jobs/exportAuditLogsJob.js +83 -18
  31. package/dist/entries/lifecycle/gracefulShutdown.js +1 -50
  32. package/dist/entries/mail/mailer.js +32 -3
  33. package/dist/entries/openapi/generator.js +32 -3
  34. package/dist/entries/queue/createAppQueue.js +32 -3
  35. package/dist/entries/queue/publicQueue.js +32 -3
  36. package/dist/entries/queue/queueMetrics.js +32 -3
  37. package/dist/entries/queue/redisQueue.js +32 -3
  38. package/dist/entries/runtime/appEnv.js +18 -1
  39. package/dist/entries/runtime/appKeyPrefix.js +1 -70
  40. package/dist/entries/security/oauthState.js +33 -4
  41. package/dist/entries/security/safeFetch.js +32 -9
  42. package/dist/entries/security/safeUrl.js +1 -105
  43. package/dist/entries/security/totp.js +32 -3
  44. package/dist/entries/tenant/databaseTenantContext.js +48 -6
  45. package/dist/entries/tracing/tracingMiddleware.js +32 -3
  46. package/dist/entries/view.js +1 -778
  47. package/dist/framework/public-api.d.ts +27 -18
  48. package/dist/index.js +442 -53
  49. 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);
@@ -6643,6 +6723,9 @@ function generateCspNonce() {
6643
6723
  function configureContentSecurityPolicy(options) {
6644
6724
  configuredHtmlOptions = { ...options };
6645
6725
  }
6726
+ function resetContentSecurityPolicyForTests() {
6727
+ configuredHtmlOptions = {};
6728
+ }
6646
6729
  function cloneDirectives(source) {
6647
6730
  const copy = {};
6648
6731
  for (const [directive, values] of Object.entries(source)) {
@@ -6753,11 +6836,6 @@ function readRequestCookie(request, name) {
6753
6836
  function readBunRequestCookie(request, name) {
6754
6837
  return request.cookies.get(name) ?? readRequestCookie(request, name);
6755
6838
  }
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
6839
  // ../../src/core/http/corsMiddleware.ts
6762
6840
  function defaultAllowedOrigins() {
6763
6841
  return isProductionEnv() ? "" : "*";
@@ -6823,7 +6901,7 @@ function csrfCookieName() {
6823
6901
  return process.env.CSRF_COOKIE_NAME?.trim() || appCookieName("csrf");
6824
6902
  }
6825
6903
  function resolveCsrfSecret() {
6826
- return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || appDevSecret("csrf-secret");
6904
+ return requireConfiguredSecret(["SESSION_SECRET", "OAUTH_STATE_SECRET", "ADMIN_API_TOKEN"], "csrf-secret");
6827
6905
  }
6828
6906
  function csrfVerifyOptions() {
6829
6907
  return { secret: resolveCsrfSecret(), maxAge: CSRF_TTL_MS };
@@ -6838,7 +6916,7 @@ function tokensMatch(left, right) {
6838
6916
  }
6839
6917
  function createCsrfTokenCookie() {
6840
6918
  const token = Bun.CSRF.generate(resolveCsrfSecret(), { expiresIn: CSRF_TTL_MS });
6841
- const secure = process.env.APP_ENV === "production" ? "; Secure" : "";
6919
+ const secure = isProductionEnv() ? "; Secure" : "";
6842
6920
  return {
6843
6921
  token,
6844
6922
  cookie: `${csrfCookieName()}=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Max-Age=${Math.floor(CSRF_TTL_MS / 1000)}${secure}`
@@ -6958,7 +7036,7 @@ function flashCookieName() {
6958
7036
  return process.env.FLASH_COOKIE_NAME?.trim() || appCookieName("flash");
6959
7037
  }
6960
7038
  function resolveFlashSecret() {
6961
- return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || appDevSecret("flash-secret");
7039
+ return requireConfiguredSecret(["SESSION_SECRET", "OAUTH_STATE_SECRET"], "flash-secret");
6962
7040
  }
6963
7041
  function signFlashPayload(payload, issuedAt) {
6964
7042
  const signature = createHmac2("sha256", resolveFlashSecret()).update(`${payload}.${issuedAt}`).digest("hex");
@@ -7068,6 +7146,49 @@ function getQueryParams(request) {
7068
7146
  }
7069
7147
  return new URL(request.url).searchParams;
7070
7148
  }
7149
+ function parseOptionalPositiveIntQueryParam(params, name) {
7150
+ const value = params.get(name);
7151
+ if (value === null || value.trim() === "") {
7152
+ return;
7153
+ }
7154
+ const parsed = Number.parseInt(value, 10);
7155
+ if (!Number.isInteger(parsed) || parsed <= 0) {
7156
+ throw new BadRequestError(`Invalid query parameter "${name}". Expected a positive integer.`);
7157
+ }
7158
+ return parsed;
7159
+ }
7160
+ function parseOptionalBooleanQueryParam(params, name) {
7161
+ const value = params.get(name);
7162
+ if (value === null || value.trim() === "") {
7163
+ return;
7164
+ }
7165
+ switch (value.toLowerCase()) {
7166
+ case "true":
7167
+ case "1":
7168
+ return true;
7169
+ case "false":
7170
+ case "0":
7171
+ return false;
7172
+ default:
7173
+ throw new BadRequestError(`Invalid query parameter "${name}". Expected a boolean.`);
7174
+ }
7175
+ }
7176
+ function parseOptionalEnumQueryParam(params, name, allowedValues) {
7177
+ const value = params.get(name);
7178
+ if (value === null || value.trim() === "") {
7179
+ return;
7180
+ }
7181
+ if (!allowedValues.includes(value)) {
7182
+ throw new BadRequestError(`Invalid query parameter "${name}". Expected one of: ${allowedValues.join(", ")}.`);
7183
+ }
7184
+ return value;
7185
+ }
7186
+ function expectObject(value, label = "request body") {
7187
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
7188
+ throw new BadRequestError(`${label} must be a JSON object.`);
7189
+ }
7190
+ return value;
7191
+ }
7071
7192
  async function parseJsonBody(request, validator) {
7072
7193
  let payload;
7073
7194
  try {
@@ -7077,6 +7198,55 @@ async function parseJsonBody(request, validator) {
7077
7198
  }
7078
7199
  return validator(payload);
7079
7200
  }
7201
+ function readRequiredString(payload, field, options = {}) {
7202
+ const value = payload[field];
7203
+ if (typeof value !== "string" || value.trim() === "") {
7204
+ throw new BadRequestError(`"${field}" is required and must be a string.`);
7205
+ }
7206
+ const trimmed = value.trim();
7207
+ if (options.minLength !== undefined && trimmed.length < options.minLength) {
7208
+ throw new BadRequestError(`"${field}" must be at least ${options.minLength} characters.`);
7209
+ }
7210
+ if (options.maxLength !== undefined && trimmed.length > options.maxLength) {
7211
+ throw new BadRequestError(`"${field}" must be at most ${options.maxLength} characters.`);
7212
+ }
7213
+ if (options.pattern && !options.pattern.test(trimmed)) {
7214
+ throw new BadRequestError(`"${field}" has an invalid format.`);
7215
+ }
7216
+ return trimmed;
7217
+ }
7218
+ function readOptionalString(payload, field, options = {}) {
7219
+ if (!(field in payload) || payload[field] === undefined) {
7220
+ return;
7221
+ }
7222
+ return readRequiredString(payload, field, options);
7223
+ }
7224
+ function readRequiredEnum(payload, field, allowedValues) {
7225
+ const value = readRequiredString(payload, field);
7226
+ if (!allowedValues.includes(value)) {
7227
+ throw new BadRequestError(`"${field}" must be one of: ${allowedValues.join(", ")}.`);
7228
+ }
7229
+ return value;
7230
+ }
7231
+ function readOptionalEnum(payload, field, allowedValues) {
7232
+ if (!(field in payload) || payload[field] === undefined) {
7233
+ return;
7234
+ }
7235
+ return readRequiredEnum(payload, field, allowedValues);
7236
+ }
7237
+ function readRequiredPositiveInt(payload, field) {
7238
+ const value = payload[field];
7239
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
7240
+ throw new BadRequestError(`"${field}" is required and must be a positive integer.`);
7241
+ }
7242
+ return value;
7243
+ }
7244
+ function readOptionalPositiveInt(payload, field) {
7245
+ if (!(field in payload) || payload[field] === undefined) {
7246
+ return;
7247
+ }
7248
+ return readRequiredPositiveInt(payload, field);
7249
+ }
7080
7250
  function parsePositiveIntParam(value, name = "id") {
7081
7251
  const parsed = Number.parseInt(value, 10);
7082
7252
  if (!Number.isInteger(parsed) || parsed <= 0) {
@@ -7097,6 +7267,12 @@ class FormRequest {
7097
7267
  return await parseJsonBody(request, (payload) => this.parse(payload));
7098
7268
  }
7099
7269
  }
7270
+
7271
+ class QueryFormRequest {
7272
+ validate(request) {
7273
+ return this.parseQuery(request);
7274
+ }
7275
+ }
7100
7276
  // ../../src/core/http/authMiddleware.ts
7101
7277
  function createAuthMiddleware(auth) {
7102
7278
  return async (request, next) => {
@@ -7659,6 +7835,10 @@ function withErrorHandling(handler) {
7659
7835
  }
7660
7836
  };
7661
7837
  }
7838
+ // ../../src/core/http/route.ts
7839
+ function getRouteParams(request) {
7840
+ return request.params;
7841
+ }
7662
7842
  // ../../src/core/http/routeMiddleware.ts
7663
7843
  function withMiddleware(...middleware) {
7664
7844
  const wrap = composeMiddleware(...middleware);
@@ -7832,6 +8012,9 @@ function createLoginThrottleMiddleware(options) {
7832
8012
  }
7833
8013
  return createMemoryLoginThrottleMiddleware(options);
7834
8014
  }
8015
+ function resetMemoryLoginThrottleForTests() {
8016
+ memoryLoginBuckets.clear();
8017
+ }
7835
8018
  // ../../src/core/http/memoryThrottleMiddleware.ts
7836
8019
  var throttleBucketRegistries = new Set;
7837
8020
  function createMemoryThrottleMiddleware(options) {
@@ -8019,7 +8202,7 @@ function intendedUrlTtlSeconds() {
8019
8202
  return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_INTENDED_URL_TTL_SECONDS;
8020
8203
  }
8021
8204
  function cookieSecureFlag() {
8022
- return process.env.APP_ENV === "production" ? "; Secure" : "";
8205
+ return isProductionEnv() ? "; Secure" : "";
8023
8206
  }
8024
8207
  function pathnameOf(path) {
8025
8208
  const pathname = path.split("?")[0] ?? path;
@@ -8140,7 +8323,7 @@ function createSecurityHeadersMiddleware(options = {}) {
8140
8323
  // ../../src/core/http/signedUrl.ts
8141
8324
  import { createHmac as createHmac3 } from "crypto";
8142
8325
  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");
8326
+ return requireConfiguredSecret(["SIGNED_URL_SECRET", "SESSION_SECRET", "OAUTH_STATE_SECRET"], "signed-url-secret");
8144
8327
  }
8145
8328
  function resolveSignedUrlOrigin() {
8146
8329
  return (process.env.APP_URL ?? "http://localhost:3000").replace(/\/$/, "");
@@ -8339,6 +8522,11 @@ function installGracefulShutdownSignals(signals = ["SIGINT", "SIGTERM"]) {
8339
8522
  });
8340
8523
  }
8341
8524
  }
8525
+ function resetGracefulShutdownForTests() {
8526
+ shutdownHandlers.clear();
8527
+ shutdownInstalled = false;
8528
+ shuttingDown = false;
8529
+ }
8342
8530
  // ../../src/core/logging/requestLoggingMiddleware.ts
8343
8531
  function createRequestLoggingMiddleware() {
8344
8532
  return async (request, next) => {
@@ -8584,6 +8772,42 @@ class AsyncQueue {
8584
8772
  function createQueue(driver) {
8585
8773
  return driver === "async" ? new AsyncQueue : new SyncQueue;
8586
8774
  }
8775
+ // ../../src/core/queue/jobRegistry.ts
8776
+ class JobRegistry {
8777
+ factories = new Map;
8778
+ instances = new WeakMap;
8779
+ register(name, factory) {
8780
+ this.factories.set(name, factory);
8781
+ }
8782
+ resolveName(job) {
8783
+ return this.instances.get(job);
8784
+ }
8785
+ track(name, job) {
8786
+ this.instances.set(job, name);
8787
+ return job;
8788
+ }
8789
+ create(name) {
8790
+ const factory = this.factories.get(name);
8791
+ if (!factory) {
8792
+ return;
8793
+ }
8794
+ return factory();
8795
+ }
8796
+ names() {
8797
+ return [...this.factories.keys()];
8798
+ }
8799
+ }
8800
+ var JOB_REGISTRY_KEY = Symbol.for("@getstrata/jobRegistry");
8801
+ function readSharedJobRegistry() {
8802
+ const globalRegistry = globalThis[JOB_REGISTRY_KEY];
8803
+ if (globalRegistry) {
8804
+ return globalRegistry;
8805
+ }
8806
+ const registry = new JobRegistry;
8807
+ globalThis[JOB_REGISTRY_KEY] = registry;
8808
+ return registry;
8809
+ }
8810
+ var jobRegistry = readSharedJobRegistry();
8587
8811
  // ../../src/core/queue/failedJobTable.ts
8588
8812
  var failedJobTable = defineTable({
8589
8813
  name: "failed_job",
@@ -8644,43 +8868,6 @@ class FailedJobService {
8644
8868
  }
8645
8869
  var failedJobService_default = FailedJobService;
8646
8870
 
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
8871
  // ../../src/core/queue/queueConfig.ts
8685
8872
  function resolveQueueConfig() {
8686
8873
  const driver = process.env.QUEUE_DRIVER;
@@ -9027,20 +9214,131 @@ function isPublicReadsEnabled() {
9027
9214
  function guestCanViewResource() {
9028
9215
  return isPublicReadsEnabled();
9029
9216
  }
9217
+ // ../../src/core/security/safeUrl.ts
9218
+ import { lookup as dnsLookupImpl } from "dns/promises";
9219
+ var dnsLookup = dnsLookupImpl;
9220
+ var BLOCKED_HOSTNAMES = new Set([
9221
+ "localhost",
9222
+ "127.0.0.1",
9223
+ "0.0.0.0",
9224
+ "::1",
9225
+ "metadata.google.internal"
9226
+ ]);
9227
+ function isPrivateIpv4(hostname) {
9228
+ const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
9229
+ if (!match) {
9230
+ return false;
9231
+ }
9232
+ const octets = match.slice(1, 5).map((part) => Number.parseInt(part, 10));
9233
+ if (octets.some((octet) => octet < 0 || octet > 255)) {
9234
+ return true;
9235
+ }
9236
+ const [a = 0, b = 0] = octets;
9237
+ if (a === 10) {
9238
+ return true;
9239
+ }
9240
+ if (a === 127) {
9241
+ return true;
9242
+ }
9243
+ if (a === 0) {
9244
+ return true;
9245
+ }
9246
+ if (a === 169 && b === 254) {
9247
+ return true;
9248
+ }
9249
+ if (a === 172 && b >= 16 && b <= 31) {
9250
+ return true;
9251
+ }
9252
+ if (a === 192 && b === 168) {
9253
+ return true;
9254
+ }
9255
+ return false;
9256
+ }
9257
+ function isBlockedHostname(hostname) {
9258
+ const normalized = hostname.trim().toLowerCase();
9259
+ if (normalized.length === 0) {
9260
+ return true;
9261
+ }
9262
+ if (BLOCKED_HOSTNAMES.has(normalized)) {
9263
+ return true;
9264
+ }
9265
+ if (normalized.endsWith(".local") || normalized.endsWith(".internal")) {
9266
+ return true;
9267
+ }
9268
+ if (normalized.includes(":")) {
9269
+ return true;
9270
+ }
9271
+ return isPrivateIpv4(normalized);
9272
+ }
9273
+ function assertSafeOutboundUrl(rawUrl, options = {}) {
9274
+ let parsed;
9275
+ try {
9276
+ parsed = new URL(rawUrl);
9277
+ } catch {
9278
+ throw new BadRequestError("Webhook URL is invalid.");
9279
+ }
9280
+ if (parsed.protocol !== "https:" && !(options.allowHttp && parsed.protocol === "http:")) {
9281
+ throw new BadRequestError("Webhook URL must use HTTPS.");
9282
+ }
9283
+ if (parsed.username || parsed.password) {
9284
+ throw new BadRequestError("Webhook URL must not include credentials.");
9285
+ }
9286
+ if (isBlockedHostname(parsed.hostname)) {
9287
+ throw new BadRequestError("Webhook URL targets a blocked host.");
9288
+ }
9289
+ return parsed;
9290
+ }
9291
+ function isBlockedIpAddress(address) {
9292
+ return isBlockedHostname(address.trim().toLowerCase());
9293
+ }
9294
+ async function assertSafeOutboundUrlResolved(rawUrl, options = {}) {
9295
+ const parsed = assertSafeOutboundUrl(rawUrl, options);
9296
+ if (options.resolveDns === false) {
9297
+ return parsed;
9298
+ }
9299
+ const hostname = parsed.hostname.trim().toLowerCase();
9300
+ const results = await dnsLookup(hostname, { all: true, verbatim: true });
9301
+ if (results.some((result) => isBlockedIpAddress(result.address))) {
9302
+ throw new BadRequestError("Webhook URL targets a blocked host.");
9303
+ }
9304
+ return parsed;
9305
+ }
9306
+ function setDnsLookupForTests(lookupFn) {
9307
+ dnsLookup = lookupFn;
9308
+ }
9309
+ function resetDnsLookupForTests() {
9310
+ dnsLookup = dnsLookupImpl;
9311
+ }
9030
9312
  // ../../src/core/tenant/tenantMiddleware.ts
9031
9313
  import { createHash } from "crypto";
9032
9314
 
9033
9315
  // ../../src/core/tenant/databaseTenantContext.ts
9316
+ async function applyBypassToTransaction(transaction, bypass) {
9317
+ await transaction.unsafe(`SELECT set_config('app.bypass_rls', $1, true)`, [
9318
+ bypass ? "true" : "false"
9319
+ ]);
9320
+ }
9034
9321
  async function runWithMigrationBypass(callback) {
9035
9322
  if (!isRlsTenancy()) {
9036
9323
  return await callback();
9037
9324
  }
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)`;
9325
+ if (hasActiveDatabaseConnection()) {
9326
+ const activeConnection = getActiveDatabaseConnection(getDefaultDatabasePool());
9327
+ await applyBypassToTransaction(activeConnection, true);
9328
+ try {
9329
+ return await callback();
9330
+ } finally {
9331
+ await applyBypassToTransaction(activeConnection, false);
9332
+ }
9043
9333
  }
9334
+ const pool = getDefaultDatabasePool();
9335
+ if (typeof pool.begin !== "function") {
9336
+ throw new Error("RLS migration bypass requires a pool that supports begin(). Session-scoped set_config is not used on pooled connections.");
9337
+ }
9338
+ return await pool.begin(async (transaction) => {
9339
+ await applyBypassToTransaction(transaction, true);
9340
+ return await runWithDatabaseConnection(transaction, callback);
9341
+ });
9044
9342
  }
9045
9343
 
9046
9344
  // ../../src/core/tenant/tenantMiddleware.ts
@@ -9608,6 +9906,7 @@ export {
9608
9906
  CompositeGuard,
9609
9907
  ConfigStore,
9610
9908
  ConflictError,
9909
+ DEFAULT_PER_PAGE,
9611
9910
  DEFAULT_TENANT,
9612
9911
  DEFAULT_VIEWS_DIRECTORY,
9613
9912
  DatabaseTokenGuard,
@@ -9620,15 +9919,19 @@ export {
9620
9919
  ForeignIdColumnDefinition,
9621
9920
  FormRequest,
9622
9921
  GuestGuard,
9922
+ HTMX_2_0_4_INDICATOR_STYLE_HASH,
9623
9923
  HasManyRelationQuery,
9924
+ HasManyThroughRelationQuery,
9624
9925
  HasOneRelationQuery,
9625
9926
  HttpError,
9626
9927
  InternalServerError,
9627
9928
  Job,
9929
+ JobRegistry,
9628
9930
  JsonResource,
9629
9931
  JwtGuard,
9630
9932
  LocalStorageDriver,
9631
9933
  LogMailDriver,
9934
+ MAX_PER_PAGE,
9632
9935
  Mailer,
9633
9936
  membershipService_default as MembershipService,
9634
9937
  Model,
@@ -9646,6 +9949,7 @@ export {
9646
9949
  PostgresGrammar,
9647
9950
  PreconditionFailedError,
9648
9951
  PrometheusRegistry,
9952
+ QueryFormRequest,
9649
9953
  QueueWorker,
9650
9954
  RedisQueue,
9651
9955
  RepositoryQuery,
@@ -9664,7 +9968,15 @@ export {
9664
9968
  WebFormRequest,
9665
9969
  WhereBuilder,
9666
9970
  absoluteTemporarySignedUrl,
9971
+ apiPrefix,
9972
+ appCookieName,
9973
+ appDevSecret,
9974
+ appDisplayName,
9975
+ appEnv,
9976
+ appKeyPrefix,
9667
9977
  appSchedule,
9978
+ appUrl,
9979
+ appUserAgent,
9668
9980
  appendOrganizationScope,
9669
9981
  appendProjectScope,
9670
9982
  applyCasts,
@@ -9673,6 +9985,8 @@ export {
9673
9985
  assertIfMatch,
9674
9986
  assertOrganizationReadable,
9675
9987
  assertResourceInCurrentTenant,
9988
+ assertSafeOutboundUrl,
9989
+ assertSafeOutboundUrlResolved,
9676
9990
  assertValidSignature,
9677
9991
  auditChecksum,
9678
9992
  auth,
@@ -9682,13 +9996,29 @@ export {
9682
9996
  bindBunSql,
9683
9997
  bindDatabaseConnection,
9684
9998
  bindRouteModel,
9999
+ buildAdvancedWhereClause,
10000
+ buildCountQuery,
10001
+ buildDeleteByIdQuery,
10002
+ buildGroupedCountQuery,
10003
+ buildInsertQuery,
10004
+ buildJoinClause,
9685
10005
  buildMarkdownMailMessage,
10006
+ buildOrderByClause,
10007
+ buildPaginationMeta,
10008
+ buildProjectionQuery,
10009
+ buildQueryWhereClause,
9686
10010
  buildRequestCacheKey,
10011
+ buildRestoreByIdQuery,
10012
+ buildSelectQuery,
9687
10013
  buildSmtpPayload,
10014
+ buildSoftDeleteByIdQuery,
10015
+ buildUpdateQuery,
10016
+ buildWhereClause,
9688
10017
  cache,
9689
10018
  collectQueueMetrics,
9690
10019
  compileBlueprint,
9691
10020
  composeMiddleware,
10021
+ computeEtagFromJson,
9692
10022
  conditionalJsonResponse,
9693
10023
  config,
9694
10024
  configureContentSecurityPolicy,
@@ -9710,6 +10040,7 @@ export {
9710
10040
  createFlashMiddleware,
9711
10041
  createLoginThrottleMiddleware,
9712
10042
  createMembershipMiddleware,
10043
+ createMemoryLoginThrottleMiddleware,
9713
10044
  createMemoryThrottleMiddleware,
9714
10045
  createMetricsMiddleware,
9715
10046
  createMysqlConnection,
@@ -9750,10 +10081,14 @@ export {
9750
10081
  dialectFor,
9751
10082
  emailRule,
9752
10083
  emptyPaginateResult,
10084
+ envFlagEnabled,
10085
+ errorResponse,
9753
10086
  errorTemplateName,
9754
10087
  etagFromResource,
10088
+ etagValuesMatch,
9755
10089
  eventBus,
9756
10090
  events,
10091
+ expectObject,
9757
10092
  filterMassAssignable,
9758
10093
  foreignKeyFromTable,
9759
10094
  formatAdminValue,
@@ -9765,10 +10100,13 @@ export {
9765
10100
  getDefaultDatabaseQuery,
9766
10101
  getMigrationStatus,
9767
10102
  getNamedConnection,
10103
+ getQueryParams,
10104
+ getRouteParams,
9768
10105
  grammarForDriver,
9769
10106
  guestCanViewResource,
9770
10107
  hasActiveDatabaseConnection,
9771
10108
  hasMany,
10109
+ hasManyThrough,
9772
10110
  hasMinimumOrgRole2 as hasMinimumOrgRole,
9773
10111
  hasNamedConnection,
9774
10112
  hasOne,
@@ -9777,20 +10115,26 @@ export {
9777
10115
  htmlErrorResponse,
9778
10116
  htmlResponse,
9779
10117
  hydrateValue,
10118
+ ifMatchSatisfied,
10119
+ ifNoneMatchSatisfied,
9780
10120
  indexBelongsToManyRelation,
9781
10121
  indexBelongsToRelation,
9782
10122
  indexHasManyRelation,
10123
+ indexHasManyThroughRelation,
9783
10124
  indexHasOneRelation,
9784
10125
  indexMorphManyRelation,
9785
10126
  indexMorphOneRelation,
9786
10127
  indexMorphToRelation,
9787
10128
  inferReferencedTable,
9788
10129
  installGracefulShutdownSignals,
10130
+ isBlockedHostname,
10131
+ isBlockedIpAddress,
9789
10132
  isEtagEnabled,
9790
10133
  isGlobalAdmin,
9791
10134
  isHtmxRequest,
9792
10135
  isHttpErrorLike,
9793
10136
  isInsideTenantDatabaseScope,
10137
+ isProductionEnv,
9794
10138
  isPublicReadsEnabled,
9795
10139
  isRlsTenancy,
9796
10140
  isTenancyEnabled,
@@ -9801,32 +10145,54 @@ export {
9801
10145
  loadSeedersFromDirectory,
9802
10146
  log,
9803
10147
  logSecurityEvent,
10148
+ logServerError,
9804
10149
  loginRedirectLocation,
9805
10150
  mail,
9806
10151
  mailer,
10152
+ mapDatabaseError,
9807
10153
  markdownToHtml,
9808
10154
  maxLength,
10155
+ membershipContext,
10156
+ membershipRepository,
9809
10157
  migrateDatabase,
9810
10158
  minLength,
9811
10159
  modelEventName,
9812
10160
  morphMany,
9813
10161
  morphOne,
9814
10162
  morphTo,
10163
+ namespacedRedisKey,
9815
10164
  noContentResponse,
10165
+ normalizeFieldErrors,
9816
10166
  normalizeMetricPath,
9817
10167
  notFoundHtmlResponse,
10168
+ notModifiedResponse,
10169
+ otelServiceName,
9818
10170
  paginatedResponse,
10171
+ parseJsonBody,
9819
10172
  parseMultipartUpload,
10173
+ parseOptionalBooleanQueryParam,
10174
+ parseOptionalEnumQueryParam,
10175
+ parseOptionalPositiveIntQueryParam,
9820
10176
  parsePaginationQuery,
9821
10177
  parsePositiveIntParam,
10178
+ parseQualifiedColumn,
9822
10179
  pivotTableName,
9823
10180
  policyGate,
9824
10181
  prometheusRegistry,
10182
+ qualifyColumn,
9825
10183
  queue,
10184
+ quoteIdentifier,
9826
10185
  rateLimitMultiplierForPlan,
9827
10186
  readBunRequestCookie,
9828
10187
  readClientIp,
10188
+ readOptionalEnum,
10189
+ readOptionalPositiveInt,
10190
+ readOptionalString,
9829
10191
  readRequestCookie,
10192
+ readRequiredEnum,
10193
+ readRequiredPositiveInt,
10194
+ readRequiredString,
10195
+ readSharedEventBus,
9830
10196
  readSubmittedCsrfToken,
9831
10197
  readSubmittedCsrfTokenFromBody,
9832
10198
  readTenancyDriver,
@@ -9838,11 +10204,20 @@ export {
9838
10204
  registerShutdownHandler,
9839
10205
  renderKernelErrorChrome,
9840
10206
  renderMarkdownMail,
10207
+ renderWebErrorHtml,
9841
10208
  repositoryConnection,
9842
10209
  requestIdMiddleware,
10210
+ requestMetaContext,
10211
+ requireConfiguredSecret,
9843
10212
  required,
9844
10213
  resetBoundDatabaseConnection,
10214
+ resetContentSecurityPolicyForTests,
10215
+ resetDefaultDatabasePoolForTests,
9845
10216
  resetDefaultStorage,
10217
+ resetDnsLookupForTests,
10218
+ resetGracefulShutdownForTests,
10219
+ resetMembershipLookupForTests,
10220
+ resetMemoryLoginThrottleForTests,
9846
10221
  resetMemoryThrottleForTests,
9847
10222
  resetNamedConnections,
9848
10223
  resetSqlDialect,
@@ -9859,11 +10234,15 @@ export {
9859
10234
  resolveCsrfTokenForRequest,
9860
10235
  resolveDatabaseDriver,
9861
10236
  resolveHtmlContentSecurityPolicy,
10237
+ resolveLoginEmail,
10238
+ resolveLoginIdentity,
9862
10239
  resolveMembershipLookup,
9863
10240
  resolveMembershipService,
9864
10241
  resolveOrganizationScope,
10242
+ resolveQualifiedColumn,
9865
10243
  resolveRepositoryConnection,
9866
10244
  resolveService,
10245
+ resolveSoftDeleteColumn,
9867
10246
  resolveUserId,
9868
10247
  resolveUserTenantId,
9869
10248
  resolveWebLayoutData,
@@ -9886,16 +10265,21 @@ export {
9886
10265
  safeInternalRedirectPath,
9887
10266
  sanitizeInternalPath,
9888
10267
  sanitizeMailHtml,
10268
+ sanitizeUploadFileName,
9889
10269
  scopedOrganizationIds,
10270
+ sdkClientClassName,
9890
10271
  securedBindRouteModel,
9891
10272
  securedBindRouteModelByKey,
9892
10273
  sendMarkdownMail,
9893
10274
  serializeDate,
9894
10275
  serverHtmxContentSecurityPolicy,
9895
10276
  setActiveApplicationContext,
10277
+ setDnsLookupForTests,
10278
+ siemEventType,
9896
10279
  signJwt,
9897
10280
  signedUrl,
9898
10281
  singularize,
10282
+ smtpEhloHost,
9899
10283
  spaContentSecurityPolicy,
9900
10284
  sqlTimestamp,
9901
10285
  storageFacade as storage,
@@ -9903,17 +10287,22 @@ export {
9903
10287
  stringRule,
9904
10288
  stripMarkdown,
9905
10289
  temporarySignedUrl,
10290
+ tenantContext,
9906
10291
  textResponse,
9907
10292
  toHttpError,
9908
10293
  toPaginatedResourceCollection,
9909
10294
  toResourceCollection,
10295
+ traceContextStorage,
9910
10296
  trustForwardedFor,
9911
10297
  unregisterNamedConnection,
9912
10298
  useSqlDialect,
9913
10299
  validateObject,
9914
10300
  verifyCsrfToken,
9915
10301
  verifyJwt,
10302
+ webErrorResponse,
10303
+ webhookSignatureHeader,
9916
10304
  whenLoaded,
10305
+ withDatabaseErrorHandling,
9917
10306
  withErrorHandling,
9918
10307
  withMiddleware,
9919
10308
  withMigrationLock,