@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
@@ -1,4 +1,24 @@
1
1
  // @bun
2
+ // ../../src/core/runtime/appEnv.ts
3
+ var NON_PRODUCTION_APP_ENVS = new Set(["local", "development", "dev", "test", "testing", "ci"]);
4
+ function normalizeEnvValue(value) {
5
+ return (value ?? "").trim().toLowerCase();
6
+ }
7
+ function isProductionEnv(env = process.env) {
8
+ const appEnv = normalizeEnvValue(env.APP_ENV);
9
+ const nodeEnv = normalizeEnvValue(env.NODE_ENV);
10
+ if (appEnv === "production" || nodeEnv === "production") {
11
+ return true;
12
+ }
13
+ if (appEnv === "") {
14
+ return false;
15
+ }
16
+ return !NON_PRODUCTION_APP_ENVS.has(appEnv);
17
+ }
18
+ function envFlagEnabled(value) {
19
+ return value === "true";
20
+ }
21
+
2
22
  // ../../src/core/runtime/appKeyPrefix.ts
3
23
  function appKeyPrefix() {
4
24
  return process.env.APP_KEY_PREFIX?.trim() || "strata";
@@ -9,6 +29,18 @@ function appCookieName(kind) {
9
29
  function appDevSecret(kind) {
10
30
  return `${appKeyPrefix()}-dev-${kind}`;
11
31
  }
32
+ function requireConfiguredSecret(names, devKind, env = process.env) {
33
+ for (const name of names) {
34
+ const value = env[name]?.trim();
35
+ if (value) {
36
+ return value;
37
+ }
38
+ }
39
+ if (isProductionEnv(env)) {
40
+ throw new Error(`${names[0]} must be set outside development. Do not derive secrets from the app name.`);
41
+ }
42
+ return appDevSecret(devKind);
43
+ }
12
44
  function namespacedRedisKey(kind) {
13
45
  return `${appKeyPrefix()}:${kind}`;
14
46
  }
@@ -26,9 +58,6 @@ function appUserAgent() {
26
58
  function otelServiceName() {
27
59
  return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
28
60
  }
29
- function webhookSignatureHeader() {
30
- return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
31
- }
32
61
  function appDisplayName() {
33
62
  return process.env.APP_NAME?.trim() || "Strata";
34
63
  }
@@ -156,6 +156,26 @@ async function runQueueJob(envelope, failedJobs) {
156
156
  // ../../src/core/queue/redisQueue.ts
157
157
  var {RedisClient } = globalThis.Bun;
158
158
 
159
+ // ../../src/core/runtime/appEnv.ts
160
+ var NON_PRODUCTION_APP_ENVS = new Set(["local", "development", "dev", "test", "testing", "ci"]);
161
+ function normalizeEnvValue(value) {
162
+ return (value ?? "").trim().toLowerCase();
163
+ }
164
+ function isProductionEnv(env = process.env) {
165
+ const appEnv = normalizeEnvValue(env.APP_ENV);
166
+ const nodeEnv = normalizeEnvValue(env.NODE_ENV);
167
+ if (appEnv === "production" || nodeEnv === "production") {
168
+ return true;
169
+ }
170
+ if (appEnv === "") {
171
+ return false;
172
+ }
173
+ return !NON_PRODUCTION_APP_ENVS.has(appEnv);
174
+ }
175
+ function envFlagEnabled(value) {
176
+ return value === "true";
177
+ }
178
+
159
179
  // ../../src/core/runtime/appKeyPrefix.ts
160
180
  function appKeyPrefix() {
161
181
  return process.env.APP_KEY_PREFIX?.trim() || "strata";
@@ -166,6 +186,18 @@ function appCookieName(kind) {
166
186
  function appDevSecret(kind) {
167
187
  return `${appKeyPrefix()}-dev-${kind}`;
168
188
  }
189
+ function requireConfiguredSecret(names, devKind, env = process.env) {
190
+ for (const name of names) {
191
+ const value = env[name]?.trim();
192
+ if (value) {
193
+ return value;
194
+ }
195
+ }
196
+ if (isProductionEnv(env)) {
197
+ throw new Error(`${names[0]} must be set outside development. Do not derive secrets from the app name.`);
198
+ }
199
+ return appDevSecret(devKind);
200
+ }
169
201
  function namespacedRedisKey(kind) {
170
202
  return `${appKeyPrefix()}:${kind}`;
171
203
  }
@@ -183,9 +215,6 @@ function appUserAgent() {
183
215
  function otelServiceName() {
184
216
  return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
185
217
  }
186
- function webhookSignatureHeader() {
187
- return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
188
- }
189
218
  function appDisplayName() {
190
219
  return process.env.APP_NAME?.trim() || "Strata";
191
220
  }
@@ -156,6 +156,26 @@ async function runQueueJob(envelope, failedJobs) {
156
156
  // ../../src/core/queue/redisQueue.ts
157
157
  var {RedisClient } = globalThis.Bun;
158
158
 
159
+ // ../../src/core/runtime/appEnv.ts
160
+ var NON_PRODUCTION_APP_ENVS = new Set(["local", "development", "dev", "test", "testing", "ci"]);
161
+ function normalizeEnvValue(value) {
162
+ return (value ?? "").trim().toLowerCase();
163
+ }
164
+ function isProductionEnv(env = process.env) {
165
+ const appEnv = normalizeEnvValue(env.APP_ENV);
166
+ const nodeEnv = normalizeEnvValue(env.NODE_ENV);
167
+ if (appEnv === "production" || nodeEnv === "production") {
168
+ return true;
169
+ }
170
+ if (appEnv === "") {
171
+ return false;
172
+ }
173
+ return !NON_PRODUCTION_APP_ENVS.has(appEnv);
174
+ }
175
+ function envFlagEnabled(value) {
176
+ return value === "true";
177
+ }
178
+
159
179
  // ../../src/core/runtime/appKeyPrefix.ts
160
180
  function appKeyPrefix() {
161
181
  return process.env.APP_KEY_PREFIX?.trim() || "strata";
@@ -166,6 +186,18 @@ function appCookieName(kind) {
166
186
  function appDevSecret(kind) {
167
187
  return `${appKeyPrefix()}-dev-${kind}`;
168
188
  }
189
+ function requireConfiguredSecret(names, devKind, env = process.env) {
190
+ for (const name of names) {
191
+ const value = env[name]?.trim();
192
+ if (value) {
193
+ return value;
194
+ }
195
+ }
196
+ if (isProductionEnv(env)) {
197
+ throw new Error(`${names[0]} must be set outside development. Do not derive secrets from the app name.`);
198
+ }
199
+ return appDevSecret(devKind);
200
+ }
169
201
  function namespacedRedisKey(kind) {
170
202
  return `${appKeyPrefix()}:${kind}`;
171
203
  }
@@ -183,9 +215,6 @@ function appUserAgent() {
183
215
  function otelServiceName() {
184
216
  return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
185
217
  }
186
- function webhookSignatureHeader() {
187
- return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
188
- }
189
218
  function appDisplayName() {
190
219
  return process.env.APP_NAME?.trim() || "Strata";
191
220
  }
@@ -159,6 +159,26 @@ async function runQueueJob(envelope, failedJobs) {
159
159
  // ../../src/core/queue/redisQueue.ts
160
160
  var {RedisClient } = globalThis.Bun;
161
161
 
162
+ // ../../src/core/runtime/appEnv.ts
163
+ var NON_PRODUCTION_APP_ENVS = new Set(["local", "development", "dev", "test", "testing", "ci"]);
164
+ function normalizeEnvValue(value) {
165
+ return (value ?? "").trim().toLowerCase();
166
+ }
167
+ function isProductionEnv(env = process.env) {
168
+ const appEnv = normalizeEnvValue(env.APP_ENV);
169
+ const nodeEnv = normalizeEnvValue(env.NODE_ENV);
170
+ if (appEnv === "production" || nodeEnv === "production") {
171
+ return true;
172
+ }
173
+ if (appEnv === "") {
174
+ return false;
175
+ }
176
+ return !NON_PRODUCTION_APP_ENVS.has(appEnv);
177
+ }
178
+ function envFlagEnabled(value) {
179
+ return value === "true";
180
+ }
181
+
162
182
  // ../../src/core/runtime/appKeyPrefix.ts
163
183
  function appKeyPrefix() {
164
184
  return process.env.APP_KEY_PREFIX?.trim() || "strata";
@@ -169,6 +189,18 @@ function appCookieName(kind) {
169
189
  function appDevSecret(kind) {
170
190
  return `${appKeyPrefix()}-dev-${kind}`;
171
191
  }
192
+ function requireConfiguredSecret(names, devKind, env = process.env) {
193
+ for (const name of names) {
194
+ const value = env[name]?.trim();
195
+ if (value) {
196
+ return value;
197
+ }
198
+ }
199
+ if (isProductionEnv(env)) {
200
+ throw new Error(`${names[0]} must be set outside development. Do not derive secrets from the app name.`);
201
+ }
202
+ return appDevSecret(devKind);
203
+ }
172
204
  function namespacedRedisKey(kind) {
173
205
  return `${appKeyPrefix()}:${kind}`;
174
206
  }
@@ -186,9 +218,6 @@ function appUserAgent() {
186
218
  function otelServiceName() {
187
219
  return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
188
220
  }
189
- function webhookSignatureHeader() {
190
- return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
191
- }
192
221
  function appDisplayName() {
193
222
  return process.env.APP_NAME?.trim() || "Strata";
194
223
  }
@@ -2,6 +2,26 @@
2
2
  // ../../src/core/queue/redisQueue.ts
3
3
  var {RedisClient } = globalThis.Bun;
4
4
 
5
+ // ../../src/core/runtime/appEnv.ts
6
+ var NON_PRODUCTION_APP_ENVS = new Set(["local", "development", "dev", "test", "testing", "ci"]);
7
+ function normalizeEnvValue(value) {
8
+ return (value ?? "").trim().toLowerCase();
9
+ }
10
+ function isProductionEnv(env = process.env) {
11
+ const appEnv = normalizeEnvValue(env.APP_ENV);
12
+ const nodeEnv = normalizeEnvValue(env.NODE_ENV);
13
+ if (appEnv === "production" || nodeEnv === "production") {
14
+ return true;
15
+ }
16
+ if (appEnv === "") {
17
+ return false;
18
+ }
19
+ return !NON_PRODUCTION_APP_ENVS.has(appEnv);
20
+ }
21
+ function envFlagEnabled(value) {
22
+ return value === "true";
23
+ }
24
+
5
25
  // ../../src/core/runtime/appKeyPrefix.ts
6
26
  function appKeyPrefix() {
7
27
  return process.env.APP_KEY_PREFIX?.trim() || "strata";
@@ -12,6 +32,18 @@ function appCookieName(kind) {
12
32
  function appDevSecret(kind) {
13
33
  return `${appKeyPrefix()}-dev-${kind}`;
14
34
  }
35
+ function requireConfiguredSecret(names, devKind, env = process.env) {
36
+ for (const name of names) {
37
+ const value = env[name]?.trim();
38
+ if (value) {
39
+ return value;
40
+ }
41
+ }
42
+ if (isProductionEnv(env)) {
43
+ throw new Error(`${names[0]} must be set outside development. Do not derive secrets from the app name.`);
44
+ }
45
+ return appDevSecret(devKind);
46
+ }
15
47
  function namespacedRedisKey(kind) {
16
48
  return `${appKeyPrefix()}:${kind}`;
17
49
  }
@@ -29,9 +61,6 @@ function appUserAgent() {
29
61
  function otelServiceName() {
30
62
  return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
31
63
  }
32
- function webhookSignatureHeader() {
33
- return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
34
- }
35
64
  function appDisplayName() {
36
65
  return process.env.APP_NAME?.trim() || "Strata";
37
66
  }
@@ -1,8 +1,25 @@
1
1
  // @bun
2
2
  // ../../src/core/runtime/appEnv.ts
3
+ var NON_PRODUCTION_APP_ENVS = new Set(["local", "development", "dev", "test", "testing", "ci"]);
4
+ function normalizeEnvValue(value) {
5
+ return (value ?? "").trim().toLowerCase();
6
+ }
3
7
  function isProductionEnv(env = process.env) {
4
- return env.APP_ENV === "production" || env.NODE_ENV === "production";
8
+ const appEnv = normalizeEnvValue(env.APP_ENV);
9
+ const nodeEnv = normalizeEnvValue(env.NODE_ENV);
10
+ if (appEnv === "production" || nodeEnv === "production") {
11
+ return true;
12
+ }
13
+ if (appEnv === "") {
14
+ return false;
15
+ }
16
+ return !NON_PRODUCTION_APP_ENVS.has(appEnv);
17
+ }
18
+ function envFlagEnabled(value) {
19
+ return value === "true";
5
20
  }
6
21
  export {
22
+ NON_PRODUCTION_APP_ENVS,
23
+ envFlagEnabled,
7
24
  isProductionEnv
8
25
  };
@@ -1,70 +1 @@
1
- // @bun
2
- // ../../src/core/runtime/appKeyPrefix.ts
3
- function appKeyPrefix() {
4
- return process.env.APP_KEY_PREFIX?.trim() || "strata";
5
- }
6
- function appCookieName(kind) {
7
- return `${appKeyPrefix()}_${kind}`;
8
- }
9
- function appDevSecret(kind) {
10
- return `${appKeyPrefix()}-dev-${kind}`;
11
- }
12
- function namespacedRedisKey(kind) {
13
- return `${appKeyPrefix()}:${kind}`;
14
- }
15
- function smtpEhloHost() {
16
- const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
17
- const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
18
- return safe || "strata.local";
19
- }
20
- function siemEventType() {
21
- return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
22
- }
23
- function appUserAgent() {
24
- return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
25
- }
26
- function otelServiceName() {
27
- return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
28
- }
29
- function webhookSignatureHeader() {
30
- return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
31
- }
32
- function appDisplayName() {
33
- return process.env.APP_NAME?.trim() || "Strata";
34
- }
35
- function appEnv() {
36
- return process.env.APP_ENV?.trim() || "local";
37
- }
38
- function appUrl() {
39
- return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
40
- }
41
- function apiPrefix() {
42
- const raw = process.env.API_PREFIX?.trim() || "/api/v1";
43
- const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
44
- const trimmed = withSlash.replace(/\/+$/, "");
45
- return trimmed || "/api/v1";
46
- }
47
- function sdkClientClassName() {
48
- const override = process.env.APP_SDK_CLASS?.trim();
49
- if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
50
- return override;
51
- }
52
- const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
53
- return fromName ? `${fromName}Client` : "AppClient";
54
- }
55
- export {
56
- apiPrefix,
57
- appCookieName,
58
- appDevSecret,
59
- appDisplayName,
60
- appEnv,
61
- appKeyPrefix,
62
- appUrl,
63
- appUserAgent,
64
- namespacedRedisKey,
65
- otelServiceName,
66
- sdkClientClassName,
67
- siemEventType,
68
- smtpEhloHost,
69
- webhookSignatureHeader
70
- };
1
+ export * from "../../index.js";
@@ -2,6 +2,26 @@
2
2
  // ../../src/core/security/oauthState.ts
3
3
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
4
4
 
5
+ // ../../src/core/runtime/appEnv.ts
6
+ var NON_PRODUCTION_APP_ENVS = new Set(["local", "development", "dev", "test", "testing", "ci"]);
7
+ function normalizeEnvValue(value) {
8
+ return (value ?? "").trim().toLowerCase();
9
+ }
10
+ function isProductionEnv(env = process.env) {
11
+ const appEnv = normalizeEnvValue(env.APP_ENV);
12
+ const nodeEnv = normalizeEnvValue(env.NODE_ENV);
13
+ if (appEnv === "production" || nodeEnv === "production") {
14
+ return true;
15
+ }
16
+ if (appEnv === "") {
17
+ return false;
18
+ }
19
+ return !NON_PRODUCTION_APP_ENVS.has(appEnv);
20
+ }
21
+ function envFlagEnabled(value) {
22
+ return value === "true";
23
+ }
24
+
5
25
  // ../../src/core/runtime/appKeyPrefix.ts
6
26
  function appKeyPrefix() {
7
27
  return process.env.APP_KEY_PREFIX?.trim() || "strata";
@@ -12,6 +32,18 @@ function appCookieName(kind) {
12
32
  function appDevSecret(kind) {
13
33
  return `${appKeyPrefix()}-dev-${kind}`;
14
34
  }
35
+ function requireConfiguredSecret(names, devKind, env = process.env) {
36
+ for (const name of names) {
37
+ const value = env[name]?.trim();
38
+ if (value) {
39
+ return value;
40
+ }
41
+ }
42
+ if (isProductionEnv(env)) {
43
+ throw new Error(`${names[0]} must be set outside development. Do not derive secrets from the app name.`);
44
+ }
45
+ return appDevSecret(devKind);
46
+ }
15
47
  function namespacedRedisKey(kind) {
16
48
  return `${appKeyPrefix()}:${kind}`;
17
49
  }
@@ -29,9 +61,6 @@ function appUserAgent() {
29
61
  function otelServiceName() {
30
62
  return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
31
63
  }
32
- function webhookSignatureHeader() {
33
- return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
34
- }
35
64
  function appDisplayName() {
36
65
  return process.env.APP_NAME?.trim() || "Strata";
37
66
  }
@@ -60,7 +89,7 @@ function sdkClientClassName() {
60
89
  var OAUTH_STATE_COOKIE = "oauth_state";
61
90
  var OAUTH_STATE_TTL_MS = 10 * 60 * 1000;
62
91
  function resolveOAuthStateSecret() {
63
- return process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || appDevSecret("oauth-state-secret");
92
+ return requireConfiguredSecret(["OAUTH_STATE_SECRET", "ADMIN_API_TOKEN"], "oauth-state-secret");
64
93
  }
65
94
  function signOAuthState(state, issuedAt) {
66
95
  const payload = `${state}.${issuedAt}`;
@@ -1,4 +1,24 @@
1
1
  // @bun
2
+ // ../../src/core/runtime/appEnv.ts
3
+ var NON_PRODUCTION_APP_ENVS = new Set(["local", "development", "dev", "test", "testing", "ci"]);
4
+ function normalizeEnvValue(value) {
5
+ return (value ?? "").trim().toLowerCase();
6
+ }
7
+ function isProductionEnv(env = process.env) {
8
+ const appEnv = normalizeEnvValue(env.APP_ENV);
9
+ const nodeEnv = normalizeEnvValue(env.NODE_ENV);
10
+ if (appEnv === "production" || nodeEnv === "production") {
11
+ return true;
12
+ }
13
+ if (appEnv === "") {
14
+ return false;
15
+ }
16
+ return !NON_PRODUCTION_APP_ENVS.has(appEnv);
17
+ }
18
+ function envFlagEnabled(value) {
19
+ return value === "true";
20
+ }
21
+
2
22
  // ../../src/core/runtime/appKeyPrefix.ts
3
23
  function appKeyPrefix() {
4
24
  return process.env.APP_KEY_PREFIX?.trim() || "strata";
@@ -9,6 +29,18 @@ function appCookieName(kind) {
9
29
  function appDevSecret(kind) {
10
30
  return `${appKeyPrefix()}-dev-${kind}`;
11
31
  }
32
+ function requireConfiguredSecret(names, devKind, env = process.env) {
33
+ for (const name of names) {
34
+ const value = env[name]?.trim();
35
+ if (value) {
36
+ return value;
37
+ }
38
+ }
39
+ if (isProductionEnv(env)) {
40
+ throw new Error(`${names[0]} must be set outside development. Do not derive secrets from the app name.`);
41
+ }
42
+ return appDevSecret(devKind);
43
+ }
12
44
  function namespacedRedisKey(kind) {
13
45
  return `${appKeyPrefix()}:${kind}`;
14
46
  }
@@ -26,9 +58,6 @@ function appUserAgent() {
26
58
  function otelServiceName() {
27
59
  return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
28
60
  }
29
- function webhookSignatureHeader() {
30
- return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
31
- }
32
61
  function appDisplayName() {
33
62
  return process.env.APP_NAME?.trim() || "Strata";
34
63
  }
@@ -143,12 +172,6 @@ async function assertSafeOutboundUrlResolved(rawUrl, options = {}) {
143
172
  }
144
173
  return parsed;
145
174
  }
146
- function setDnsLookupForTests(lookupFn) {
147
- dnsLookup = lookupFn;
148
- }
149
- function resetDnsLookupForTests() {
150
- dnsLookup = dnsLookupImpl;
151
- }
152
175
 
153
176
  // ../../src/core/security/safeFetch.ts
154
177
  var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
@@ -1,105 +1 @@
1
- // @bun
2
- // ../../src/core/security/safeUrl.ts
3
- import { lookup as dnsLookupImpl } from "dns/promises";
4
- import { BadRequestError } from "@getstrata/core/errors/http";
5
- var dnsLookup = dnsLookupImpl;
6
- var BLOCKED_HOSTNAMES = new Set([
7
- "localhost",
8
- "127.0.0.1",
9
- "0.0.0.0",
10
- "::1",
11
- "metadata.google.internal"
12
- ]);
13
- function isPrivateIpv4(hostname) {
14
- const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
15
- if (!match) {
16
- return false;
17
- }
18
- const octets = match.slice(1, 5).map((part) => Number.parseInt(part, 10));
19
- if (octets.some((octet) => octet < 0 || octet > 255)) {
20
- return true;
21
- }
22
- const [a = 0, b = 0] = octets;
23
- if (a === 10) {
24
- return true;
25
- }
26
- if (a === 127) {
27
- return true;
28
- }
29
- if (a === 0) {
30
- return true;
31
- }
32
- if (a === 169 && b === 254) {
33
- return true;
34
- }
35
- if (a === 172 && b >= 16 && b <= 31) {
36
- return true;
37
- }
38
- if (a === 192 && b === 168) {
39
- return true;
40
- }
41
- return false;
42
- }
43
- function isBlockedHostname(hostname) {
44
- const normalized = hostname.trim().toLowerCase();
45
- if (normalized.length === 0) {
46
- return true;
47
- }
48
- if (BLOCKED_HOSTNAMES.has(normalized)) {
49
- return true;
50
- }
51
- if (normalized.endsWith(".local") || normalized.endsWith(".internal")) {
52
- return true;
53
- }
54
- if (normalized.includes(":")) {
55
- return true;
56
- }
57
- return isPrivateIpv4(normalized);
58
- }
59
- function assertSafeOutboundUrl(rawUrl, options = {}) {
60
- let parsed;
61
- try {
62
- parsed = new URL(rawUrl);
63
- } catch {
64
- throw new BadRequestError("Webhook URL is invalid.");
65
- }
66
- if (parsed.protocol !== "https:" && !(options.allowHttp && parsed.protocol === "http:")) {
67
- throw new BadRequestError("Webhook URL must use HTTPS.");
68
- }
69
- if (parsed.username || parsed.password) {
70
- throw new BadRequestError("Webhook URL must not include credentials.");
71
- }
72
- if (isBlockedHostname(parsed.hostname)) {
73
- throw new BadRequestError("Webhook URL targets a blocked host.");
74
- }
75
- return parsed;
76
- }
77
- function isBlockedIpAddress(address) {
78
- return isBlockedHostname(address.trim().toLowerCase());
79
- }
80
- async function assertSafeOutboundUrlResolved(rawUrl, options = {}) {
81
- const parsed = assertSafeOutboundUrl(rawUrl, options);
82
- if (options.resolveDns === false) {
83
- return parsed;
84
- }
85
- const hostname = parsed.hostname.trim().toLowerCase();
86
- const results = await dnsLookup(hostname, { all: true, verbatim: true });
87
- if (results.some((result) => isBlockedIpAddress(result.address))) {
88
- throw new BadRequestError("Webhook URL targets a blocked host.");
89
- }
90
- return parsed;
91
- }
92
- function setDnsLookupForTests(lookupFn) {
93
- dnsLookup = lookupFn;
94
- }
95
- function resetDnsLookupForTests() {
96
- dnsLookup = dnsLookupImpl;
97
- }
98
- export {
99
- assertSafeOutboundUrl,
100
- assertSafeOutboundUrlResolved,
101
- isBlockedHostname,
102
- isBlockedIpAddress,
103
- resetDnsLookupForTests,
104
- setDnsLookupForTests
105
- };
1
+ export * from "../../index.js";
@@ -2,6 +2,26 @@
2
2
  // ../../src/core/security/totp.ts
3
3
  import { createHmac, randomBytes } from "crypto";
4
4
 
5
+ // ../../src/core/runtime/appEnv.ts
6
+ var NON_PRODUCTION_APP_ENVS = new Set(["local", "development", "dev", "test", "testing", "ci"]);
7
+ function normalizeEnvValue(value) {
8
+ return (value ?? "").trim().toLowerCase();
9
+ }
10
+ function isProductionEnv(env = process.env) {
11
+ const appEnv = normalizeEnvValue(env.APP_ENV);
12
+ const nodeEnv = normalizeEnvValue(env.NODE_ENV);
13
+ if (appEnv === "production" || nodeEnv === "production") {
14
+ return true;
15
+ }
16
+ if (appEnv === "") {
17
+ return false;
18
+ }
19
+ return !NON_PRODUCTION_APP_ENVS.has(appEnv);
20
+ }
21
+ function envFlagEnabled(value) {
22
+ return value === "true";
23
+ }
24
+
5
25
  // ../../src/core/runtime/appKeyPrefix.ts
6
26
  function appKeyPrefix() {
7
27
  return process.env.APP_KEY_PREFIX?.trim() || "strata";
@@ -12,6 +32,18 @@ function appCookieName(kind) {
12
32
  function appDevSecret(kind) {
13
33
  return `${appKeyPrefix()}-dev-${kind}`;
14
34
  }
35
+ function requireConfiguredSecret(names, devKind, env = process.env) {
36
+ for (const name of names) {
37
+ const value = env[name]?.trim();
38
+ if (value) {
39
+ return value;
40
+ }
41
+ }
42
+ if (isProductionEnv(env)) {
43
+ throw new Error(`${names[0]} must be set outside development. Do not derive secrets from the app name.`);
44
+ }
45
+ return appDevSecret(devKind);
46
+ }
15
47
  function namespacedRedisKey(kind) {
16
48
  return `${appKeyPrefix()}:${kind}`;
17
49
  }
@@ -29,9 +61,6 @@ function appUserAgent() {
29
61
  function otelServiceName() {
30
62
  return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
31
63
  }
32
- function webhookSignatureHeader() {
33
- return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
34
- }
35
64
  function appDisplayName() {
36
65
  return process.env.APP_NAME?.trim() || "Strata";
37
66
  }