@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
@@ -2,6 +2,26 @@
2
2
  // ../../src/core/auth/sessionCookie.ts
3
3
  import { createHmac, 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
  }
@@ -74,7 +103,7 @@ function sessionRememberTtlSeconds() {
74
103
  return parsePositiveSeconds(process.env.SESSION_REMEMBER_TTL_SECONDS, SESSION_REMEMBER_TTL_SECONDS);
75
104
  }
76
105
  function resolveSessionSecret() {
77
- return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || appDevSecret("session-secret");
106
+ return requireConfiguredSecret(["SESSION_SECRET", "OAUTH_STATE_SECRET", "ADMIN_API_TOKEN"], "session-secret");
78
107
  }
79
108
  function signSession(userId, issuedAt, ttlSeconds) {
80
109
  const payload = ttlSeconds === undefined ? `${userId}.${issuedAt}` : `${userId}.${issuedAt}.${ttlSeconds}`;
@@ -153,7 +182,7 @@ function createSessionCookieDetails(userId, options = {}) {
153
182
  const issuedAt = Date.now();
154
183
  const ttlSeconds = options.remember ? sessionRememberTtlSeconds() : sessionTtlSeconds();
155
184
  const value = options.remember ? signSession(userId, issuedAt, ttlSeconds) : signSession(userId, issuedAt);
156
- const secure = process.env.APP_ENV === "production" ? "; Secure" : "";
185
+ const secure = isProductionEnv() ? "; Secure" : "";
157
186
  return {
158
187
  header: `${sessionCookieName()}=${encodeURIComponent(value)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${ttlSeconds}${secure}`,
159
188
  userId,
@@ -165,7 +194,7 @@ function createSessionCookie(userId, options = {}) {
165
194
  return createSessionCookieDetails(userId, options).header;
166
195
  }
167
196
  function clearSessionCookie() {
168
- const secure = process.env.APP_ENV === "production" ? "; Secure" : "";
197
+ const secure = isProductionEnv() ? "; Secure" : "";
169
198
  return `${sessionCookieName()}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0${secure}`;
170
199
  }
171
200
  export {
@@ -69,6 +69,26 @@ function resolveAbilitiesForRole(role) {
69
69
  // ../../src/core/auth/sessionCookie.ts
70
70
  import { createHmac, timingSafeEqual } from "crypto";
71
71
 
72
+ // ../../src/core/runtime/appEnv.ts
73
+ var NON_PRODUCTION_APP_ENVS = new Set(["local", "development", "dev", "test", "testing", "ci"]);
74
+ function normalizeEnvValue(value) {
75
+ return (value ?? "").trim().toLowerCase();
76
+ }
77
+ function isProductionEnv(env = process.env) {
78
+ const appEnv = normalizeEnvValue(env.APP_ENV);
79
+ const nodeEnv = normalizeEnvValue(env.NODE_ENV);
80
+ if (appEnv === "production" || nodeEnv === "production") {
81
+ return true;
82
+ }
83
+ if (appEnv === "") {
84
+ return false;
85
+ }
86
+ return !NON_PRODUCTION_APP_ENVS.has(appEnv);
87
+ }
88
+ function envFlagEnabled(value) {
89
+ return value === "true";
90
+ }
91
+
72
92
  // ../../src/core/runtime/appKeyPrefix.ts
73
93
  function appKeyPrefix() {
74
94
  return process.env.APP_KEY_PREFIX?.trim() || "strata";
@@ -79,6 +99,18 @@ function appCookieName(kind) {
79
99
  function appDevSecret(kind) {
80
100
  return `${appKeyPrefix()}-dev-${kind}`;
81
101
  }
102
+ function requireConfiguredSecret(names, devKind, env = process.env) {
103
+ for (const name of names) {
104
+ const value = env[name]?.trim();
105
+ if (value) {
106
+ return value;
107
+ }
108
+ }
109
+ if (isProductionEnv(env)) {
110
+ throw new Error(`${names[0]} must be set outside development. Do not derive secrets from the app name.`);
111
+ }
112
+ return appDevSecret(devKind);
113
+ }
82
114
  function namespacedRedisKey(kind) {
83
115
  return `${appKeyPrefix()}:${kind}`;
84
116
  }
@@ -96,9 +128,6 @@ function appUserAgent() {
96
128
  function otelServiceName() {
97
129
  return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
98
130
  }
99
- function webhookSignatureHeader() {
100
- return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
101
- }
102
131
  function appDisplayName() {
103
132
  return process.env.APP_NAME?.trim() || "Strata";
104
133
  }
@@ -141,7 +170,7 @@ function sessionRememberTtlSeconds() {
141
170
  return parsePositiveSeconds(process.env.SESSION_REMEMBER_TTL_SECONDS, SESSION_REMEMBER_TTL_SECONDS);
142
171
  }
143
172
  function resolveSessionSecret() {
144
- return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || appDevSecret("session-secret");
173
+ return requireConfiguredSecret(["SESSION_SECRET", "OAUTH_STATE_SECRET", "ADMIN_API_TOKEN"], "session-secret");
145
174
  }
146
175
  function signSession(userId, issuedAt, ttlSeconds) {
147
176
  const payload = ttlSeconds === undefined ? `${userId}.${issuedAt}` : `${userId}.${issuedAt}.${ttlSeconds}`;
@@ -220,7 +249,7 @@ function createSessionCookieDetails(userId, options = {}) {
220
249
  const issuedAt = Date.now();
221
250
  const ttlSeconds = options.remember ? sessionRememberTtlSeconds() : sessionTtlSeconds();
222
251
  const value = options.remember ? signSession(userId, issuedAt, ttlSeconds) : signSession(userId, issuedAt);
223
- const secure = process.env.APP_ENV === "production" ? "; Secure" : "";
252
+ const secure = isProductionEnv() ? "; Secure" : "";
224
253
  return {
225
254
  header: `${sessionCookieName()}=${encodeURIComponent(value)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${ttlSeconds}${secure}`,
226
255
  userId,
@@ -232,7 +261,7 @@ function createSessionCookie(userId, options = {}) {
232
261
  return createSessionCookieDetails(userId, options).header;
233
262
  }
234
263
  function clearSessionCookie() {
235
- const secure = process.env.APP_ENV === "production" ? "; Secure" : "";
264
+ const secure = isProductionEnv() ? "; Secure" : "";
236
265
  return `${sessionCookieName()}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0${secure}`;
237
266
  }
238
267
 
@@ -2,6 +2,26 @@
2
2
  // ../../src/core/auth/tokenHash.ts
3
3
  import { createHash, createHmac } 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
  }
@@ -58,7 +87,7 @@ function sdkClientClassName() {
58
87
 
59
88
  // ../../src/core/auth/tokenHash.ts
60
89
  function resolveTokenPepper() {
61
- return process.env.TOKEN_HASH_PEPPER?.trim() ?? appDevSecret("token-pepper");
90
+ return requireConfiguredSecret(["TOKEN_HASH_PEPPER"], "token-pepper");
62
91
  }
63
92
  function hashApiToken(token) {
64
93
  const pepper = resolveTokenPepper();
@@ -2,6 +2,26 @@
2
2
  // ../../src/core/cache/redisCacheStore.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,132 +1 @@
1
- // @bun
2
- // ../../src/core/runtime/optionalPeer.ts
3
- function missingOptionalPeer(packageName, reason, error) {
4
- return new Error(`Install ${packageName} ${reason} (\`bun add ${packageName}\`).`, {
5
- cause: error
6
- });
7
- }
8
-
9
- // ../../src/core/database/mysqlConnection.ts
10
- var MYSQL_SESSION_UTC = "SET time_zone = '+00:00'";
11
- var mysqlModule;
12
- var mysqlPending;
13
- var importMysql = defaultImportMysql;
14
- async function defaultImportMysql() {
15
- return import("mysql2/promise");
16
- }
17
- function resetMysqlLoaderForTests(importer) {
18
- mysqlModule = undefined;
19
- mysqlPending = undefined;
20
- importMysql = importer ? async () => await importer() : defaultImportMysql;
21
- }
22
- function mysqlApi(mod) {
23
- if (typeof mod.createPool === "function") {
24
- return mod;
25
- }
26
- const withDefault = mod;
27
- if (typeof withDefault.default?.createPool === "function") {
28
- return withDefault.default;
29
- }
30
- throw new Error("mysql2/promise did not export createPool.");
31
- }
32
- async function loadMysql() {
33
- if (mysqlModule) {
34
- return mysqlModule;
35
- }
36
- if (!mysqlPending) {
37
- mysqlPending = (async () => {
38
- let mod;
39
- try {
40
- mod = await importMysql();
41
- } catch (error) {
42
- mysqlPending = undefined;
43
- throw missingOptionalPeer("mysql2", "to open a MySQL connection", error);
44
- }
45
- try {
46
- mysqlModule = mysqlApi(mod);
47
- return mysqlModule;
48
- } catch (error) {
49
- mysqlPending = undefined;
50
- throw error;
51
- }
52
- })();
53
- }
54
- return mysqlPending;
55
- }
56
- function rowsFromResult(result) {
57
- if (Array.isArray(result)) {
58
- return result;
59
- }
60
- if (result && typeof result === "object") {
61
- return [result];
62
- }
63
- return [];
64
- }
65
- function createMysqlConnectionFromPool(pool) {
66
- return {
67
- async unsafe(query, params = []) {
68
- const [result] = await pool.execute(query, [...params]);
69
- return rowsFromResult(result);
70
- },
71
- async close() {
72
- if (typeof pool.end === "function") {
73
- await pool.end();
74
- }
75
- }
76
- };
77
- }
78
- function pinSessionToUtc(connection) {
79
- connection.query(MYSQL_SESSION_UTC, (error) => {
80
- if (error) {
81
- console.warn(`[mysql] Could not set the session time zone to UTC; DATETIME comparisons may drift: ${error instanceof Error ? error.message : String(error)}`);
82
- }
83
- });
84
- }
85
- function createPoolFromModule(mysql, url) {
86
- const pool = mysql.createPool({ uri: url, timezone: "Z" });
87
- pool.on("connection", (connection) => {
88
- pinSessionToUtc(connection);
89
- });
90
- return pool;
91
- }
92
- async function createMysqlPool(url) {
93
- return createPoolFromModule(await loadMysql(), url);
94
- }
95
- function createMysqlConnection(url) {
96
- if (!url.trim()) {
97
- throw new Error("MYSQL_URL is not configured. Set url before creating a MySQL pool.");
98
- }
99
- let poolPending;
100
- function ensurePool() {
101
- if (!poolPending) {
102
- poolPending = createMysqlPool(url).catch((error) => {
103
- poolPending = undefined;
104
- throw error;
105
- });
106
- }
107
- return poolPending;
108
- }
109
- return {
110
- async unsafe(query, params = []) {
111
- const [result] = await (await ensurePool()).execute(query, [...params]);
112
- return rowsFromResult(result);
113
- },
114
- async close() {
115
- if (!poolPending) {
116
- return;
117
- }
118
- const pending = poolPending;
119
- poolPending = undefined;
120
- const pool = await pending.catch(() => {
121
- return;
122
- });
123
- await pool?.end();
124
- }
125
- };
126
- }
127
- export {
128
- createMysqlConnection,
129
- createMysqlConnectionFromPool,
130
- createMysqlPool,
131
- resetMysqlLoaderForTests
132
- };
1
+ export * from "../../index.js";
@@ -11,6 +11,26 @@ import {
11
11
  resolveApplicationQueue
12
12
  } from "@getstrata/core/runtime/applicationRegistry";
13
13
 
14
+ // ../../src/core/runtime/appEnv.ts
15
+ var NON_PRODUCTION_APP_ENVS = new Set(["local", "development", "dev", "test", "testing", "ci"]);
16
+ function normalizeEnvValue(value) {
17
+ return (value ?? "").trim().toLowerCase();
18
+ }
19
+ function isProductionEnv(env = process.env) {
20
+ const appEnv = normalizeEnvValue(env.APP_ENV);
21
+ const nodeEnv = normalizeEnvValue(env.NODE_ENV);
22
+ if (appEnv === "production" || nodeEnv === "production") {
23
+ return true;
24
+ }
25
+ if (appEnv === "") {
26
+ return false;
27
+ }
28
+ return !NON_PRODUCTION_APP_ENVS.has(appEnv);
29
+ }
30
+ function envFlagEnabled(value) {
31
+ return value === "true";
32
+ }
33
+
14
34
  // ../../src/core/runtime/appKeyPrefix.ts
15
35
  function appKeyPrefix() {
16
36
  return process.env.APP_KEY_PREFIX?.trim() || "strata";
@@ -21,6 +41,18 @@ function appCookieName(kind) {
21
41
  function appDevSecret(kind) {
22
42
  return `${appKeyPrefix()}-dev-${kind}`;
23
43
  }
44
+ function requireConfiguredSecret(names, devKind, env = process.env) {
45
+ for (const name of names) {
46
+ const value = env[name]?.trim();
47
+ if (value) {
48
+ return value;
49
+ }
50
+ }
51
+ if (isProductionEnv(env)) {
52
+ throw new Error(`${names[0]} must be set outside development. Do not derive secrets from the app name.`);
53
+ }
54
+ return appDevSecret(devKind);
55
+ }
24
56
  function namespacedRedisKey(kind) {
25
57
  return `${appKeyPrefix()}:${kind}`;
26
58
  }
@@ -38,9 +70,6 @@ function appUserAgent() {
38
70
  function otelServiceName() {
39
71
  return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
40
72
  }
41
- function webhookSignatureHeader() {
42
- return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
43
- }
44
73
  function appDisplayName() {
45
74
  return process.env.APP_NAME?.trim() || "Strata";
46
75
  }
@@ -1,7 +1,22 @@
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
 
7
22
  // ../../src/core/http/corsMiddleware.ts
@@ -5,6 +5,26 @@ import { ForbiddenError } from "@getstrata/core/errors/http";
5
5
  // ../../src/core/http/csrfToken.ts
6
6
  import { timingSafeEqual } from "crypto";
7
7
 
8
+ // ../../src/core/runtime/appEnv.ts
9
+ var NON_PRODUCTION_APP_ENVS = new Set(["local", "development", "dev", "test", "testing", "ci"]);
10
+ function normalizeEnvValue(value) {
11
+ return (value ?? "").trim().toLowerCase();
12
+ }
13
+ function isProductionEnv(env = process.env) {
14
+ const appEnv = normalizeEnvValue(env.APP_ENV);
15
+ const nodeEnv = normalizeEnvValue(env.NODE_ENV);
16
+ if (appEnv === "production" || nodeEnv === "production") {
17
+ return true;
18
+ }
19
+ if (appEnv === "") {
20
+ return false;
21
+ }
22
+ return !NON_PRODUCTION_APP_ENVS.has(appEnv);
23
+ }
24
+ function envFlagEnabled(value) {
25
+ return value === "true";
26
+ }
27
+
8
28
  // ../../src/core/runtime/appKeyPrefix.ts
9
29
  function appKeyPrefix() {
10
30
  return process.env.APP_KEY_PREFIX?.trim() || "strata";
@@ -15,6 +35,18 @@ function appCookieName(kind) {
15
35
  function appDevSecret(kind) {
16
36
  return `${appKeyPrefix()}-dev-${kind}`;
17
37
  }
38
+ function requireConfiguredSecret(names, devKind, env = process.env) {
39
+ for (const name of names) {
40
+ const value = env[name]?.trim();
41
+ if (value) {
42
+ return value;
43
+ }
44
+ }
45
+ if (isProductionEnv(env)) {
46
+ throw new Error(`${names[0]} must be set outside development. Do not derive secrets from the app name.`);
47
+ }
48
+ return appDevSecret(devKind);
49
+ }
18
50
  function namespacedRedisKey(kind) {
19
51
  return `${appKeyPrefix()}:${kind}`;
20
52
  }
@@ -32,9 +64,6 @@ function appUserAgent() {
32
64
  function otelServiceName() {
33
65
  return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
34
66
  }
35
- function webhookSignatureHeader() {
36
- return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
37
- }
38
67
  function appDisplayName() {
39
68
  return process.env.APP_NAME?.trim() || "Strata";
40
69
  }
@@ -120,7 +149,7 @@ function csrfCookieName() {
120
149
  return process.env.CSRF_COOKIE_NAME?.trim() || appCookieName("csrf");
121
150
  }
122
151
  function resolveCsrfSecret() {
123
- return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || appDevSecret("csrf-secret");
152
+ return requireConfiguredSecret(["SESSION_SECRET", "OAUTH_STATE_SECRET", "ADMIN_API_TOKEN"], "csrf-secret");
124
153
  }
125
154
  function csrfVerifyOptions() {
126
155
  return { secret: resolveCsrfSecret(), maxAge: CSRF_TTL_MS };
@@ -135,7 +164,7 @@ function tokensMatch(left, right) {
135
164
  }
136
165
  function createCsrfTokenCookie() {
137
166
  const token = Bun.CSRF.generate(resolveCsrfSecret(), { expiresIn: CSRF_TTL_MS });
138
- const secure = process.env.APP_ENV === "production" ? "; Secure" : "";
167
+ const secure = isProductionEnv() ? "; Secure" : "";
139
168
  return {
140
169
  token,
141
170
  cookie: `${csrfCookieName()}=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Max-Age=${Math.floor(CSRF_TTL_MS / 1000)}${secure}`
@@ -2,6 +2,26 @@
2
2
  // ../../src/core/http/csrfToken.ts
3
3
  import { 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
  }
@@ -117,7 +146,7 @@ function csrfCookieName() {
117
146
  return process.env.CSRF_COOKIE_NAME?.trim() || appCookieName("csrf");
118
147
  }
119
148
  function resolveCsrfSecret() {
120
- return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || appDevSecret("csrf-secret");
149
+ return requireConfiguredSecret(["SESSION_SECRET", "OAUTH_STATE_SECRET", "ADMIN_API_TOKEN"], "csrf-secret");
121
150
  }
122
151
  function csrfVerifyOptions() {
123
152
  return { secret: resolveCsrfSecret(), maxAge: CSRF_TTL_MS };
@@ -132,7 +161,7 @@ function tokensMatch(left, right) {
132
161
  }
133
162
  function createCsrfTokenCookie() {
134
163
  const token = Bun.CSRF.generate(resolveCsrfSecret(), { expiresIn: CSRF_TTL_MS });
135
- const secure = process.env.APP_ENV === "production" ? "; Secure" : "";
164
+ const secure = isProductionEnv() ? "; Secure" : "";
136
165
  return {
137
166
  token,
138
167
  cookie: `${csrfCookieName()}=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Max-Age=${Math.floor(CSRF_TTL_MS / 1000)}${secure}`