@getstrata/core 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- 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/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 +27 -18
- package/dist/index.js +442 -53
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @getstrata/core changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.4
|
|
4
|
+
|
|
5
|
+
- `isProductionEnv()` treats `APP_ENV` or `NODE_ENV` `production` as production (case-insensitive), treats `staging` as production, and default-denies unrecognized `APP_ENV` values such as `prod`.
|
|
6
|
+
- `envFlagEnabled()` is true only for the exact string `true`.
|
|
7
|
+
- JWT, session, CSRF, flash, signed-URL, OAuth-state, and token-pepper resolvers throw outside development when the secret is unset. They no longer derive a secret from the app name.
|
|
8
|
+
- `runWithMigrationBypass()` uses a transaction and `SET LOCAL` so `app.bypass_rls` cannot leak across pooled connections.
|
|
9
|
+
- `@getstrata/core/view` (and related singleton subpaths) re-export the main bundle so `configureWebErrorView` is shared.
|
|
10
|
+
- Shared subpath shims re-export the barrel. Subpath-only helpers such as `assertSafeOutboundUrl`, `webErrorResponse`, and `renderWebErrorHtml` are now on the public API so those imports work at runtime, not only in `.d.ts` files.
|
|
11
|
+
|
|
3
12
|
## 1.0.3
|
|
4
13
|
|
|
5
14
|
- `eta` and `mysql2` are optional peers, loaded with `import()` on first use. SQLite and Postgres apps no longer install `mysql2` through core. If a package is missing, the error names `bun add` for that package.
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
type EnvRecord = Record<string, string | undefined>;
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* App environments that may boot without production secret checks.
|
|
4
|
+
* Staging is not in this set: it is typically internet-exposed with
|
|
5
|
+
* production-like data, so it must use real secrets.
|
|
6
|
+
*/
|
|
7
|
+
declare const NON_PRODUCTION_APP_ENVS: Set<string>;
|
|
8
|
+
/**
|
|
9
|
+
* Production when APP_ENV or NODE_ENV says so, including `staging`.
|
|
10
|
+
* Unrecognized APP_ENV values (for example "prod" or "Production" after
|
|
11
|
+
* case-folding fails to match "production") default to production so secrets
|
|
12
|
+
* checks cannot be skipped by capitalization or a typo.
|
|
13
|
+
*/
|
|
3
14
|
declare function isProductionEnv(env?: EnvRecord): boolean;
|
|
4
|
-
|
|
15
|
+
/** True only for the exact string "true". Unset, "false", "0", "FALSE", and "off" are off. */
|
|
16
|
+
declare function envFlagEnabled(value: string | undefined): boolean;
|
|
17
|
+
export type { EnvRecord };
|
|
18
|
+
export { envFlagEnabled, isProductionEnv, NON_PRODUCTION_APP_ENVS };
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
import { type EnvRecord } from "./appEnv";
|
|
1
2
|
declare function appKeyPrefix(): string;
|
|
2
3
|
declare function appCookieName(kind: string): string;
|
|
3
4
|
declare function appDevSecret(kind: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Use a configured secret, or a predictable local fallback. Production,
|
|
7
|
+
* staging, and unrecognized APP_ENV values treated as production must set a
|
|
8
|
+
* real secret; never derive one from the app name.
|
|
9
|
+
*/
|
|
10
|
+
declare function requireConfiguredSecret(names: readonly string[], devKind: string, env?: EnvRecord): string;
|
|
4
11
|
declare function namespacedRedisKey(kind: string): string;
|
|
5
12
|
declare function smtpEhloHost(): string;
|
|
6
13
|
declare function siemEventType(): string;
|
|
@@ -12,4 +19,4 @@ declare function appEnv(): string;
|
|
|
12
19
|
declare function appUrl(): string;
|
|
13
20
|
declare function apiPrefix(): string;
|
|
14
21
|
declare function sdkClientClassName(): string;
|
|
15
|
-
export { apiPrefix, appCookieName, appDevSecret, appDisplayName, appEnv, appKeyPrefix, appUrl, appUserAgent, namespacedRedisKey, otelServiceName, sdkClientClassName, siemEventType, smtpEhloHost, webhookSignatureHeader, };
|
|
22
|
+
export { apiPrefix, appCookieName, appDevSecret, appDisplayName, appEnv, appKeyPrefix, appUrl, appUserAgent, namespacedRedisKey, otelServiceName, requireConfiguredSecret, sdkClientClassName, siemEventType, smtpEhloHost, webhookSignatureHeader, };
|
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// ../../src/core/audit/exportAuditLogs.ts
|
|
3
|
-
import { repositoryConnection as
|
|
3
|
+
import { repositoryConnection as db } from "@getstrata/core/database/repositoryConnection";
|
|
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
|
+
}
|
|
4
24
|
|
|
5
25
|
// ../../src/core/runtime/appKeyPrefix.ts
|
|
6
26
|
function appKeyPrefix() {
|
|
@@ -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
|
}
|
|
@@ -146,12 +175,6 @@ async function assertSafeOutboundUrlResolved(rawUrl, options = {}) {
|
|
|
146
175
|
}
|
|
147
176
|
return parsed;
|
|
148
177
|
}
|
|
149
|
-
function setDnsLookupForTests(lookupFn) {
|
|
150
|
-
dnsLookup = lookupFn;
|
|
151
|
-
}
|
|
152
|
-
function resetDnsLookupForTests() {
|
|
153
|
-
dnsLookup = dnsLookupImpl;
|
|
154
|
-
}
|
|
155
178
|
|
|
156
179
|
// ../../src/core/security/safeFetch.ts
|
|
157
180
|
var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
|
|
@@ -188,7 +211,33 @@ async function safeFetch(input, init = {}, options = {}) {
|
|
|
188
211
|
}
|
|
189
212
|
|
|
190
213
|
// ../../src/core/tenant/databaseTenantContext.ts
|
|
191
|
-
import {
|
|
214
|
+
import { getDefaultDatabasePool } from "@getstrata/core/database/defaultConnection";
|
|
215
|
+
|
|
216
|
+
// ../../src/core/runtime/asyncContextStore.ts
|
|
217
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
218
|
+
function createAsyncContextStore(key) {
|
|
219
|
+
const symbol = Symbol.for(key);
|
|
220
|
+
const globalRecord = globalThis;
|
|
221
|
+
const existing = globalRecord[symbol];
|
|
222
|
+
if (existing) {
|
|
223
|
+
return existing;
|
|
224
|
+
}
|
|
225
|
+
const store = new AsyncLocalStorage;
|
|
226
|
+
globalRecord[symbol] = store;
|
|
227
|
+
return store;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ../../src/core/database/connectionContext.ts
|
|
231
|
+
var activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
|
|
232
|
+
function runWithDatabaseConnection(connection, callback) {
|
|
233
|
+
return activeConnection.run(connection, callback);
|
|
234
|
+
}
|
|
235
|
+
function getActiveDatabaseConnection(fallback) {
|
|
236
|
+
return activeConnection.getStore() ?? fallback;
|
|
237
|
+
}
|
|
238
|
+
function hasActiveDatabaseConnection() {
|
|
239
|
+
return activeConnection.getStore() !== undefined;
|
|
240
|
+
}
|
|
192
241
|
|
|
193
242
|
// ../../src/core/tenant/tenancyConfig.ts
|
|
194
243
|
var TENANCY_DRIVERS = ["rls", "column", "none"];
|
|
@@ -210,16 +259,32 @@ function isRlsTenancy(env = process.env) {
|
|
|
210
259
|
}
|
|
211
260
|
|
|
212
261
|
// ../../src/core/tenant/databaseTenantContext.ts
|
|
262
|
+
async function applyBypassToTransaction(transaction, bypass) {
|
|
263
|
+
await transaction.unsafe(`SELECT set_config('app.bypass_rls', $1, true)`, [
|
|
264
|
+
bypass ? "true" : "false"
|
|
265
|
+
]);
|
|
266
|
+
}
|
|
213
267
|
async function runWithMigrationBypass(callback) {
|
|
214
268
|
if (!isRlsTenancy()) {
|
|
215
269
|
return await callback();
|
|
216
270
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
271
|
+
if (hasActiveDatabaseConnection()) {
|
|
272
|
+
const activeConnection = getActiveDatabaseConnection(getDefaultDatabasePool());
|
|
273
|
+
await applyBypassToTransaction(activeConnection, true);
|
|
274
|
+
try {
|
|
275
|
+
return await callback();
|
|
276
|
+
} finally {
|
|
277
|
+
await applyBypassToTransaction(activeConnection, false);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const pool = getDefaultDatabasePool();
|
|
281
|
+
if (typeof pool.begin !== "function") {
|
|
282
|
+
throw new Error("RLS migration bypass requires a pool that supports begin(). Session-scoped set_config is not used on pooled connections.");
|
|
222
283
|
}
|
|
284
|
+
return await pool.begin(async (transaction) => {
|
|
285
|
+
await applyBypassToTransaction(transaction, true);
|
|
286
|
+
return await runWithDatabaseConnection(transaction, callback);
|
|
287
|
+
});
|
|
223
288
|
}
|
|
224
289
|
|
|
225
290
|
// ../../src/core/audit/siemFormatter.ts
|
|
@@ -275,7 +340,7 @@ async function exportPendingAuditLogs() {
|
|
|
275
340
|
return 0;
|
|
276
341
|
}
|
|
277
342
|
return await runWithMigrationBypass(async () => {
|
|
278
|
-
const rows = await
|
|
343
|
+
const rows = await db`
|
|
279
344
|
SELECT
|
|
280
345
|
id,
|
|
281
346
|
user_id,
|
|
@@ -325,7 +390,7 @@ async function exportPendingAuditLogs() {
|
|
|
325
390
|
}
|
|
326
391
|
const ids = rows.map((row) => row.id);
|
|
327
392
|
for (const id of ids) {
|
|
328
|
-
await
|
|
393
|
+
await db`UPDATE audit_log SET exported_at = NOW() WHERE id = ${id}`;
|
|
329
394
|
}
|
|
330
395
|
return rows.length;
|
|
331
396
|
});
|
|
@@ -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
|
}
|
|
@@ -32,6 +32,26 @@ function loginRedirectLocation(request) {
|
|
|
32
32
|
return `/login?redirect=${encodeURIComponent(safeInternalRedirectPath(request))}`;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
// ../../src/core/runtime/appEnv.ts
|
|
36
|
+
var NON_PRODUCTION_APP_ENVS = new Set(["local", "development", "dev", "test", "testing", "ci"]);
|
|
37
|
+
function normalizeEnvValue(value) {
|
|
38
|
+
return (value ?? "").trim().toLowerCase();
|
|
39
|
+
}
|
|
40
|
+
function isProductionEnv(env = process.env) {
|
|
41
|
+
const appEnv = normalizeEnvValue(env.APP_ENV);
|
|
42
|
+
const nodeEnv = normalizeEnvValue(env.NODE_ENV);
|
|
43
|
+
if (appEnv === "production" || nodeEnv === "production") {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
if (appEnv === "") {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
return !NON_PRODUCTION_APP_ENVS.has(appEnv);
|
|
50
|
+
}
|
|
51
|
+
function envFlagEnabled(value) {
|
|
52
|
+
return value === "true";
|
|
53
|
+
}
|
|
54
|
+
|
|
35
55
|
// ../../src/core/runtime/appKeyPrefix.ts
|
|
36
56
|
function appKeyPrefix() {
|
|
37
57
|
return process.env.APP_KEY_PREFIX?.trim() || "strata";
|
|
@@ -42,6 +62,18 @@ function appCookieName(kind) {
|
|
|
42
62
|
function appDevSecret(kind) {
|
|
43
63
|
return `${appKeyPrefix()}-dev-${kind}`;
|
|
44
64
|
}
|
|
65
|
+
function requireConfiguredSecret(names, devKind, env = process.env) {
|
|
66
|
+
for (const name of names) {
|
|
67
|
+
const value = env[name]?.trim();
|
|
68
|
+
if (value) {
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (isProductionEnv(env)) {
|
|
73
|
+
throw new Error(`${names[0]} must be set outside development. Do not derive secrets from the app name.`);
|
|
74
|
+
}
|
|
75
|
+
return appDevSecret(devKind);
|
|
76
|
+
}
|
|
45
77
|
function namespacedRedisKey(kind) {
|
|
46
78
|
return `${appKeyPrefix()}:${kind}`;
|
|
47
79
|
}
|
|
@@ -59,9 +91,6 @@ function appUserAgent() {
|
|
|
59
91
|
function otelServiceName() {
|
|
60
92
|
return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
|
|
61
93
|
}
|
|
62
|
-
function webhookSignatureHeader() {
|
|
63
|
-
return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
|
|
64
|
-
}
|
|
65
94
|
function appDisplayName() {
|
|
66
95
|
return process.env.APP_NAME?.trim() || "Strata";
|
|
67
96
|
}
|
|
@@ -109,7 +138,7 @@ function intendedUrlTtlSeconds() {
|
|
|
109
138
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_INTENDED_URL_TTL_SECONDS;
|
|
110
139
|
}
|
|
111
140
|
function cookieSecureFlag() {
|
|
112
|
-
return
|
|
141
|
+
return isProductionEnv() ? "; Secure" : "";
|
|
113
142
|
}
|
|
114
143
|
function pathnameOf(path) {
|
|
115
144
|
const pathname = path.split("?")[0] ?? path;
|
package/dist/entries/auth/jwt.js
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
// ../../src/core/auth/jwt.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
|
}
|
|
@@ -58,7 +87,10 @@ function sdkClientClassName() {
|
|
|
58
87
|
|
|
59
88
|
// ../../src/core/auth/jwt.ts
|
|
60
89
|
function resolveJwtSecret(secret) {
|
|
61
|
-
|
|
90
|
+
if (secret?.trim()) {
|
|
91
|
+
return secret.trim();
|
|
92
|
+
}
|
|
93
|
+
return requireConfiguredSecret(["JWT_SECRET", "SESSION_SECRET"], "jwt-secret");
|
|
62
94
|
}
|
|
63
95
|
function jwtTtlSeconds(override) {
|
|
64
96
|
if (typeof override === "number" && Number.isInteger(override) && override > 0) {
|
|
@@ -44,6 +44,26 @@ function readBasicCredentials(request) {
|
|
|
44
44
|
// ../../src/core/auth/jwt.ts
|
|
45
45
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
46
46
|
|
|
47
|
+
// ../../src/core/runtime/appEnv.ts
|
|
48
|
+
var NON_PRODUCTION_APP_ENVS = new Set(["local", "development", "dev", "test", "testing", "ci"]);
|
|
49
|
+
function normalizeEnvValue(value) {
|
|
50
|
+
return (value ?? "").trim().toLowerCase();
|
|
51
|
+
}
|
|
52
|
+
function isProductionEnv(env = process.env) {
|
|
53
|
+
const appEnv = normalizeEnvValue(env.APP_ENV);
|
|
54
|
+
const nodeEnv = normalizeEnvValue(env.NODE_ENV);
|
|
55
|
+
if (appEnv === "production" || nodeEnv === "production") {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
if (appEnv === "") {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return !NON_PRODUCTION_APP_ENVS.has(appEnv);
|
|
62
|
+
}
|
|
63
|
+
function envFlagEnabled(value) {
|
|
64
|
+
return value === "true";
|
|
65
|
+
}
|
|
66
|
+
|
|
47
67
|
// ../../src/core/runtime/appKeyPrefix.ts
|
|
48
68
|
function appKeyPrefix() {
|
|
49
69
|
return process.env.APP_KEY_PREFIX?.trim() || "strata";
|
|
@@ -54,6 +74,18 @@ function appCookieName(kind) {
|
|
|
54
74
|
function appDevSecret(kind) {
|
|
55
75
|
return `${appKeyPrefix()}-dev-${kind}`;
|
|
56
76
|
}
|
|
77
|
+
function requireConfiguredSecret(names, devKind, env = process.env) {
|
|
78
|
+
for (const name of names) {
|
|
79
|
+
const value = env[name]?.trim();
|
|
80
|
+
if (value) {
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (isProductionEnv(env)) {
|
|
85
|
+
throw new Error(`${names[0]} must be set outside development. Do not derive secrets from the app name.`);
|
|
86
|
+
}
|
|
87
|
+
return appDevSecret(devKind);
|
|
88
|
+
}
|
|
57
89
|
function namespacedRedisKey(kind) {
|
|
58
90
|
return `${appKeyPrefix()}:${kind}`;
|
|
59
91
|
}
|
|
@@ -71,9 +103,6 @@ function appUserAgent() {
|
|
|
71
103
|
function otelServiceName() {
|
|
72
104
|
return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
|
|
73
105
|
}
|
|
74
|
-
function webhookSignatureHeader() {
|
|
75
|
-
return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
|
|
76
|
-
}
|
|
77
106
|
function appDisplayName() {
|
|
78
107
|
return process.env.APP_NAME?.trim() || "Strata";
|
|
79
108
|
}
|
|
@@ -100,7 +129,10 @@ function sdkClientClassName() {
|
|
|
100
129
|
|
|
101
130
|
// ../../src/core/auth/jwt.ts
|
|
102
131
|
function resolveJwtSecret(secret) {
|
|
103
|
-
|
|
132
|
+
if (secret?.trim()) {
|
|
133
|
+
return secret.trim();
|
|
134
|
+
}
|
|
135
|
+
return requireConfiguredSecret(["JWT_SECRET", "SESSION_SECRET"], "jwt-secret");
|
|
104
136
|
}
|
|
105
137
|
function jwtTtlSeconds(override) {
|
|
106
138
|
if (typeof override === "number" && Number.isInteger(override) && override > 0) {
|
|
@@ -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
|
}
|
|
@@ -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
|
}
|
|
@@ -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 {
|