@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.
- package/CHANGELOG.md +14 -0
- package/README.md +1 -1
- package/dist/core/runtime/appEnv.d.ts +16 -2
- package/dist/core/runtime/appKeyPrefix.d.ts +8 -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 +1 -132
- 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 -778
- package/dist/framework/public-api.d.ts +28 -19
- package/dist/index.js +448 -53
- 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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
}
|