@getstrata/core 1.0.2 → 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.
- package/CHANGELOG.md +16 -0
- package/README.md +1 -1
- package/dist/core/database/index.d.ts +2 -1
- package/dist/core/database/mysqlConnection.d.ts +8 -4
- package/dist/core/runtime/appEnv.d.ts +16 -2
- package/dist/core/runtime/appKeyPrefix.d.ts +8 -1
- package/dist/core/runtime/optionalPeer.d.ts +2 -0
- package/dist/core/view/etaViewEngine.d.ts +5 -1
- package/dist/entries/audit/exportAuditLogs.js +83 -18
- package/dist/entries/audit/siemFormatter.js +32 -3
- package/dist/entries/auth/intendedUrlCookie.js +33 -4
- package/dist/entries/auth/jwt.js +36 -4
- package/dist/entries/auth/jwtGuard.js +36 -4
- package/dist/entries/auth/oauth/providers.js +32 -3
- package/dist/entries/auth/oauth/samlProvider.js +32 -3
- package/dist/entries/auth/passwordConfirmCookie.js +35 -6
- package/dist/entries/auth/sessionCookie.js +35 -6
- package/dist/entries/auth/sessionGuard.js +35 -6
- package/dist/entries/auth/tokenHash.js +33 -4
- package/dist/entries/cache/createCacheStore.js +32 -3
- package/dist/entries/database/mysqlConnection.js +86 -5
- package/dist/entries/facades.js +32 -3
- package/dist/entries/http/corsMiddleware.js +16 -1
- package/dist/entries/http/csrfMiddleware.js +34 -5
- package/dist/entries/http/csrfToken.js +34 -5
- package/dist/entries/http/flashMiddleware.js +33 -4
- package/dist/entries/http/flashSession.js +33 -4
- package/dist/entries/http/loginThrottleMiddleware.js +1 -373
- package/dist/entries/http/memoryThrottleMiddleware.js +32 -58
- package/dist/entries/http/response.js +16 -56
- package/dist/entries/http/scimThrottleMiddleware.js +32 -3
- package/dist/entries/http/securityHeadersMiddleware.js +32 -3
- package/dist/entries/http/signedUrl.js +33 -4
- package/dist/entries/http/throttleMiddleware.js +32 -58
- package/dist/entries/http/webErrorResponse.js +1 -401
- package/dist/entries/jobs/exportAuditLogsJob.js +83 -18
- package/dist/entries/lifecycle/gracefulShutdown.js +1 -50
- package/dist/entries/mail/mailer.js +32 -3
- package/dist/entries/openapi/generator.js +32 -3
- package/dist/entries/queue/createAppQueue.js +32 -3
- package/dist/entries/queue/publicQueue.js +32 -3
- package/dist/entries/queue/queueMetrics.js +32 -3
- package/dist/entries/queue/redisQueue.js +32 -3
- package/dist/entries/runtime/appEnv.js +18 -1
- package/dist/entries/runtime/appKeyPrefix.js +1 -70
- package/dist/entries/security/oauthState.js +33 -4
- package/dist/entries/security/safeFetch.js +32 -9
- package/dist/entries/security/safeUrl.js +1 -105
- package/dist/entries/security/totp.js +32 -3
- package/dist/entries/tenant/databaseTenantContext.js +48 -6
- package/dist/entries/tracing/tracingMiddleware.js +32 -3
- package/dist/entries/view.js +1 -748
- package/dist/framework/public-api.d.ts +28 -18
- package/dist/index.js +555 -68
- package/package.json +12 -6
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
// ../../src/core/auth/passwordConfirmCookie.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
|
}
|
|
@@ -67,7 +96,7 @@ function passwordConfirmTtlSeconds() {
|
|
|
67
96
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_PASSWORD_CONFIRM_TTL_SECONDS;
|
|
68
97
|
}
|
|
69
98
|
function resolvePasswordConfirmSecret() {
|
|
70
|
-
return
|
|
99
|
+
return requireConfiguredSecret(["SESSION_SECRET", "OAUTH_STATE_SECRET"], "session-secret");
|
|
71
100
|
}
|
|
72
101
|
function signPasswordConfirm(userId, confirmedAt) {
|
|
73
102
|
const payload = `${userId}.${confirmedAt}`;
|
|
@@ -119,11 +148,11 @@ function hasFreshPasswordConfirmation(request, userId) {
|
|
|
119
148
|
function createPasswordConfirmCookie(userId) {
|
|
120
149
|
const confirmedAt = Date.now();
|
|
121
150
|
const value = signPasswordConfirm(userId, confirmedAt);
|
|
122
|
-
const secure =
|
|
151
|
+
const secure = isProductionEnv() ? "; Secure" : "";
|
|
123
152
|
return `${passwordConfirmCookieName()}=${encodeURIComponent(value)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${passwordConfirmTtlSeconds()}${secure}`;
|
|
124
153
|
}
|
|
125
154
|
function clearPasswordConfirmCookie() {
|
|
126
|
-
const secure =
|
|
155
|
+
const secure = isProductionEnv() ? "; Secure" : "";
|
|
127
156
|
return `${passwordConfirmCookieName()}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0${secure}`;
|
|
128
157
|
}
|
|
129
158
|
export {
|
|
@@ -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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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,6 +1,58 @@
|
|
|
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
|
+
|
|
2
9
|
// ../../src/core/database/mysqlConnection.ts
|
|
3
|
-
|
|
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
|
+
}
|
|
4
56
|
function rowsFromResult(result) {
|
|
5
57
|
if (Array.isArray(result)) {
|
|
6
58
|
return result;
|
|
@@ -23,7 +75,6 @@ function createMysqlConnectionFromPool(pool) {
|
|
|
23
75
|
}
|
|
24
76
|
};
|
|
25
77
|
}
|
|
26
|
-
var MYSQL_SESSION_UTC = "SET time_zone = '+00:00'";
|
|
27
78
|
function pinSessionToUtc(connection) {
|
|
28
79
|
connection.query(MYSQL_SESSION_UTC, (error) => {
|
|
29
80
|
if (error) {
|
|
@@ -31,21 +82,51 @@ function pinSessionToUtc(connection) {
|
|
|
31
82
|
}
|
|
32
83
|
});
|
|
33
84
|
}
|
|
34
|
-
function
|
|
85
|
+
function createPoolFromModule(mysql, url) {
|
|
35
86
|
const pool = mysql.createPool({ uri: url, timezone: "Z" });
|
|
36
87
|
pool.on("connection", (connection) => {
|
|
37
88
|
pinSessionToUtc(connection);
|
|
38
89
|
});
|
|
39
90
|
return pool;
|
|
40
91
|
}
|
|
92
|
+
async function createMysqlPool(url) {
|
|
93
|
+
return createPoolFromModule(await loadMysql(), url);
|
|
94
|
+
}
|
|
41
95
|
function createMysqlConnection(url) {
|
|
42
96
|
if (!url.trim()) {
|
|
43
97
|
throw new Error("MYSQL_URL is not configured. Set url before creating a MySQL pool.");
|
|
44
98
|
}
|
|
45
|
-
|
|
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
|
+
};
|
|
46
126
|
}
|
|
47
127
|
export {
|
|
48
128
|
createMysqlConnection,
|
|
49
129
|
createMysqlConnectionFromPool,
|
|
50
|
-
createMysqlPool
|
|
130
|
+
createMysqlPool,
|
|
131
|
+
resetMysqlLoaderForTests
|
|
51
132
|
};
|
package/dist/entries/facades.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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 =
|
|
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}`
|