@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.
Files changed (49) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/core/runtime/appEnv.d.ts +16 -2
  3. package/dist/core/runtime/appKeyPrefix.d.ts +8 -1
  4. package/dist/entries/audit/exportAuditLogs.js +83 -18
  5. package/dist/entries/audit/siemFormatter.js +32 -3
  6. package/dist/entries/auth/intendedUrlCookie.js +33 -4
  7. package/dist/entries/auth/jwt.js +36 -4
  8. package/dist/entries/auth/jwtGuard.js +36 -4
  9. package/dist/entries/auth/oauth/providers.js +32 -3
  10. package/dist/entries/auth/oauth/samlProvider.js +32 -3
  11. package/dist/entries/auth/passwordConfirmCookie.js +35 -6
  12. package/dist/entries/auth/sessionCookie.js +35 -6
  13. package/dist/entries/auth/sessionGuard.js +35 -6
  14. package/dist/entries/auth/tokenHash.js +33 -4
  15. package/dist/entries/cache/createCacheStore.js +32 -3
  16. package/dist/entries/facades.js +32 -3
  17. package/dist/entries/http/corsMiddleware.js +16 -1
  18. package/dist/entries/http/csrfMiddleware.js +34 -5
  19. package/dist/entries/http/csrfToken.js +34 -5
  20. package/dist/entries/http/flashMiddleware.js +33 -4
  21. package/dist/entries/http/flashSession.js +33 -4
  22. package/dist/entries/http/loginThrottleMiddleware.js +1 -373
  23. package/dist/entries/http/memoryThrottleMiddleware.js +32 -58
  24. package/dist/entries/http/response.js +16 -56
  25. package/dist/entries/http/scimThrottleMiddleware.js +32 -3
  26. package/dist/entries/http/securityHeadersMiddleware.js +32 -3
  27. package/dist/entries/http/signedUrl.js +33 -4
  28. package/dist/entries/http/throttleMiddleware.js +32 -58
  29. package/dist/entries/http/webErrorResponse.js +1 -401
  30. package/dist/entries/jobs/exportAuditLogsJob.js +83 -18
  31. package/dist/entries/lifecycle/gracefulShutdown.js +1 -50
  32. package/dist/entries/mail/mailer.js +32 -3
  33. package/dist/entries/openapi/generator.js +32 -3
  34. package/dist/entries/queue/createAppQueue.js +32 -3
  35. package/dist/entries/queue/publicQueue.js +32 -3
  36. package/dist/entries/queue/queueMetrics.js +32 -3
  37. package/dist/entries/queue/redisQueue.js +32 -3
  38. package/dist/entries/runtime/appEnv.js +18 -1
  39. package/dist/entries/runtime/appKeyPrefix.js +1 -70
  40. package/dist/entries/security/oauthState.js +33 -4
  41. package/dist/entries/security/safeFetch.js +32 -9
  42. package/dist/entries/security/safeUrl.js +1 -105
  43. package/dist/entries/security/totp.js +32 -3
  44. package/dist/entries/tenant/databaseTenantContext.js +48 -6
  45. package/dist/entries/tracing/tracingMiddleware.js +32 -3
  46. package/dist/entries/view.js +1 -778
  47. package/dist/framework/public-api.d.ts +27 -18
  48. package/dist/index.js +442 -53
  49. package/package.json +2 -2
@@ -2,6 +2,26 @@
2
2
  // ../../src/core/security/oauthState.ts
3
3
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
4
4
 
5
+ // ../../src/core/runtime/appEnv.ts
6
+ var NON_PRODUCTION_APP_ENVS = new Set(["local", "development", "dev", "test", "testing", "ci"]);
7
+ function normalizeEnvValue(value) {
8
+ return (value ?? "").trim().toLowerCase();
9
+ }
10
+ function isProductionEnv(env = process.env) {
11
+ const appEnv = normalizeEnvValue(env.APP_ENV);
12
+ const nodeEnv = normalizeEnvValue(env.NODE_ENV);
13
+ if (appEnv === "production" || nodeEnv === "production") {
14
+ return true;
15
+ }
16
+ if (appEnv === "") {
17
+ return false;
18
+ }
19
+ return !NON_PRODUCTION_APP_ENVS.has(appEnv);
20
+ }
21
+ function envFlagEnabled(value) {
22
+ return value === "true";
23
+ }
24
+
5
25
  // ../../src/core/runtime/appKeyPrefix.ts
6
26
  function appKeyPrefix() {
7
27
  return process.env.APP_KEY_PREFIX?.trim() || "strata";
@@ -12,6 +32,18 @@ function appCookieName(kind) {
12
32
  function appDevSecret(kind) {
13
33
  return `${appKeyPrefix()}-dev-${kind}`;
14
34
  }
35
+ function requireConfiguredSecret(names, devKind, env = process.env) {
36
+ for (const name of names) {
37
+ const value = env[name]?.trim();
38
+ if (value) {
39
+ return value;
40
+ }
41
+ }
42
+ if (isProductionEnv(env)) {
43
+ throw new Error(`${names[0]} must be set outside development. Do not derive secrets from the app name.`);
44
+ }
45
+ return appDevSecret(devKind);
46
+ }
15
47
  function namespacedRedisKey(kind) {
16
48
  return `${appKeyPrefix()}:${kind}`;
17
49
  }
@@ -29,9 +61,6 @@ function appUserAgent() {
29
61
  function otelServiceName() {
30
62
  return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
31
63
  }
32
- function webhookSignatureHeader() {
33
- return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
34
- }
35
64
  function appDisplayName() {
36
65
  return process.env.APP_NAME?.trim() || "Strata";
37
66
  }
@@ -60,7 +89,7 @@ function sdkClientClassName() {
60
89
  var OAUTH_STATE_COOKIE = "oauth_state";
61
90
  var OAUTH_STATE_TTL_MS = 10 * 60 * 1000;
62
91
  function resolveOAuthStateSecret() {
63
- return process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || appDevSecret("oauth-state-secret");
92
+ return requireConfiguredSecret(["OAUTH_STATE_SECRET", "ADMIN_API_TOKEN"], "oauth-state-secret");
64
93
  }
65
94
  function signOAuthState(state, issuedAt) {
66
95
  const payload = `${state}.${issuedAt}`;
@@ -1,4 +1,24 @@
1
1
  // @bun
2
+ // ../../src/core/runtime/appEnv.ts
3
+ var NON_PRODUCTION_APP_ENVS = new Set(["local", "development", "dev", "test", "testing", "ci"]);
4
+ function normalizeEnvValue(value) {
5
+ return (value ?? "").trim().toLowerCase();
6
+ }
7
+ function isProductionEnv(env = process.env) {
8
+ const appEnv = normalizeEnvValue(env.APP_ENV);
9
+ const nodeEnv = normalizeEnvValue(env.NODE_ENV);
10
+ if (appEnv === "production" || nodeEnv === "production") {
11
+ return true;
12
+ }
13
+ if (appEnv === "") {
14
+ return false;
15
+ }
16
+ return !NON_PRODUCTION_APP_ENVS.has(appEnv);
17
+ }
18
+ function envFlagEnabled(value) {
19
+ return value === "true";
20
+ }
21
+
2
22
  // ../../src/core/runtime/appKeyPrefix.ts
3
23
  function appKeyPrefix() {
4
24
  return process.env.APP_KEY_PREFIX?.trim() || "strata";
@@ -9,6 +29,18 @@ function appCookieName(kind) {
9
29
  function appDevSecret(kind) {
10
30
  return `${appKeyPrefix()}-dev-${kind}`;
11
31
  }
32
+ function requireConfiguredSecret(names, devKind, env = process.env) {
33
+ for (const name of names) {
34
+ const value = env[name]?.trim();
35
+ if (value) {
36
+ return value;
37
+ }
38
+ }
39
+ if (isProductionEnv(env)) {
40
+ throw new Error(`${names[0]} must be set outside development. Do not derive secrets from the app name.`);
41
+ }
42
+ return appDevSecret(devKind);
43
+ }
12
44
  function namespacedRedisKey(kind) {
13
45
  return `${appKeyPrefix()}:${kind}`;
14
46
  }
@@ -26,9 +58,6 @@ function appUserAgent() {
26
58
  function otelServiceName() {
27
59
  return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
28
60
  }
29
- function webhookSignatureHeader() {
30
- return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
31
- }
32
61
  function appDisplayName() {
33
62
  return process.env.APP_NAME?.trim() || "Strata";
34
63
  }
@@ -143,12 +172,6 @@ async function assertSafeOutboundUrlResolved(rawUrl, options = {}) {
143
172
  }
144
173
  return parsed;
145
174
  }
146
- function setDnsLookupForTests(lookupFn) {
147
- dnsLookup = lookupFn;
148
- }
149
- function resetDnsLookupForTests() {
150
- dnsLookup = dnsLookupImpl;
151
- }
152
175
 
153
176
  // ../../src/core/security/safeFetch.ts
154
177
  var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
@@ -1,105 +1 @@
1
- // @bun
2
- // ../../src/core/security/safeUrl.ts
3
- import { lookup as dnsLookupImpl } from "dns/promises";
4
- import { BadRequestError } from "@getstrata/core/errors/http";
5
- var dnsLookup = dnsLookupImpl;
6
- var BLOCKED_HOSTNAMES = new Set([
7
- "localhost",
8
- "127.0.0.1",
9
- "0.0.0.0",
10
- "::1",
11
- "metadata.google.internal"
12
- ]);
13
- function isPrivateIpv4(hostname) {
14
- const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
15
- if (!match) {
16
- return false;
17
- }
18
- const octets = match.slice(1, 5).map((part) => Number.parseInt(part, 10));
19
- if (octets.some((octet) => octet < 0 || octet > 255)) {
20
- return true;
21
- }
22
- const [a = 0, b = 0] = octets;
23
- if (a === 10) {
24
- return true;
25
- }
26
- if (a === 127) {
27
- return true;
28
- }
29
- if (a === 0) {
30
- return true;
31
- }
32
- if (a === 169 && b === 254) {
33
- return true;
34
- }
35
- if (a === 172 && b >= 16 && b <= 31) {
36
- return true;
37
- }
38
- if (a === 192 && b === 168) {
39
- return true;
40
- }
41
- return false;
42
- }
43
- function isBlockedHostname(hostname) {
44
- const normalized = hostname.trim().toLowerCase();
45
- if (normalized.length === 0) {
46
- return true;
47
- }
48
- if (BLOCKED_HOSTNAMES.has(normalized)) {
49
- return true;
50
- }
51
- if (normalized.endsWith(".local") || normalized.endsWith(".internal")) {
52
- return true;
53
- }
54
- if (normalized.includes(":")) {
55
- return true;
56
- }
57
- return isPrivateIpv4(normalized);
58
- }
59
- function assertSafeOutboundUrl(rawUrl, options = {}) {
60
- let parsed;
61
- try {
62
- parsed = new URL(rawUrl);
63
- } catch {
64
- throw new BadRequestError("Webhook URL is invalid.");
65
- }
66
- if (parsed.protocol !== "https:" && !(options.allowHttp && parsed.protocol === "http:")) {
67
- throw new BadRequestError("Webhook URL must use HTTPS.");
68
- }
69
- if (parsed.username || parsed.password) {
70
- throw new BadRequestError("Webhook URL must not include credentials.");
71
- }
72
- if (isBlockedHostname(parsed.hostname)) {
73
- throw new BadRequestError("Webhook URL targets a blocked host.");
74
- }
75
- return parsed;
76
- }
77
- function isBlockedIpAddress(address) {
78
- return isBlockedHostname(address.trim().toLowerCase());
79
- }
80
- async function assertSafeOutboundUrlResolved(rawUrl, options = {}) {
81
- const parsed = assertSafeOutboundUrl(rawUrl, options);
82
- if (options.resolveDns === false) {
83
- return parsed;
84
- }
85
- const hostname = parsed.hostname.trim().toLowerCase();
86
- const results = await dnsLookup(hostname, { all: true, verbatim: true });
87
- if (results.some((result) => isBlockedIpAddress(result.address))) {
88
- throw new BadRequestError("Webhook URL targets a blocked host.");
89
- }
90
- return parsed;
91
- }
92
- function setDnsLookupForTests(lookupFn) {
93
- dnsLookup = lookupFn;
94
- }
95
- function resetDnsLookupForTests() {
96
- dnsLookup = dnsLookupImpl;
97
- }
98
- export {
99
- assertSafeOutboundUrl,
100
- assertSafeOutboundUrlResolved,
101
- isBlockedHostname,
102
- isBlockedIpAddress,
103
- resetDnsLookupForTests,
104
- setDnsLookupForTests
105
- };
1
+ export * from "../../index.js";
@@ -2,6 +2,26 @@
2
2
  // ../../src/core/security/totp.ts
3
3
  import { createHmac, randomBytes } from "crypto";
4
4
 
5
+ // ../../src/core/runtime/appEnv.ts
6
+ var NON_PRODUCTION_APP_ENVS = new Set(["local", "development", "dev", "test", "testing", "ci"]);
7
+ function normalizeEnvValue(value) {
8
+ return (value ?? "").trim().toLowerCase();
9
+ }
10
+ function isProductionEnv(env = process.env) {
11
+ const appEnv = normalizeEnvValue(env.APP_ENV);
12
+ const nodeEnv = normalizeEnvValue(env.NODE_ENV);
13
+ if (appEnv === "production" || nodeEnv === "production") {
14
+ return true;
15
+ }
16
+ if (appEnv === "") {
17
+ return false;
18
+ }
19
+ return !NON_PRODUCTION_APP_ENVS.has(appEnv);
20
+ }
21
+ function envFlagEnabled(value) {
22
+ return value === "true";
23
+ }
24
+
5
25
  // ../../src/core/runtime/appKeyPrefix.ts
6
26
  function appKeyPrefix() {
7
27
  return process.env.APP_KEY_PREFIX?.trim() || "strata";
@@ -12,6 +32,18 @@ function appCookieName(kind) {
12
32
  function appDevSecret(kind) {
13
33
  return `${appKeyPrefix()}-dev-${kind}`;
14
34
  }
35
+ function requireConfiguredSecret(names, devKind, env = process.env) {
36
+ for (const name of names) {
37
+ const value = env[name]?.trim();
38
+ if (value) {
39
+ return value;
40
+ }
41
+ }
42
+ if (isProductionEnv(env)) {
43
+ throw new Error(`${names[0]} must be set outside development. Do not derive secrets from the app name.`);
44
+ }
45
+ return appDevSecret(devKind);
46
+ }
15
47
  function namespacedRedisKey(kind) {
16
48
  return `${appKeyPrefix()}:${kind}`;
17
49
  }
@@ -29,9 +61,6 @@ function appUserAgent() {
29
61
  function otelServiceName() {
30
62
  return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
31
63
  }
32
- function webhookSignatureHeader() {
33
- return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
34
- }
35
64
  function appDisplayName() {
36
65
  return process.env.APP_NAME?.trim() || "Strata";
37
66
  }
@@ -1,6 +1,32 @@
1
1
  // @bun
2
2
  // ../../src/core/tenant/databaseTenantContext.ts
3
- import { repositoryConnection as db } from "@getstrata/core/database/repositoryConnection";
3
+ import { getDefaultDatabasePool } from "@getstrata/core/database/defaultConnection";
4
+
5
+ // ../../src/core/runtime/asyncContextStore.ts
6
+ import { AsyncLocalStorage } from "async_hooks";
7
+ function createAsyncContextStore(key) {
8
+ const symbol = Symbol.for(key);
9
+ const globalRecord = globalThis;
10
+ const existing = globalRecord[symbol];
11
+ if (existing) {
12
+ return existing;
13
+ }
14
+ const store = new AsyncLocalStorage;
15
+ globalRecord[symbol] = store;
16
+ return store;
17
+ }
18
+
19
+ // ../../src/core/database/connectionContext.ts
20
+ var activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
21
+ function runWithDatabaseConnection(connection, callback) {
22
+ return activeConnection.run(connection, callback);
23
+ }
24
+ function getActiveDatabaseConnection(fallback) {
25
+ return activeConnection.getStore() ?? fallback;
26
+ }
27
+ function hasActiveDatabaseConnection() {
28
+ return activeConnection.getStore() !== undefined;
29
+ }
4
30
 
5
31
  // ../../src/core/tenant/tenancyConfig.ts
6
32
  var TENANCY_DRIVERS = ["rls", "column", "none"];
@@ -22,16 +48,32 @@ function isRlsTenancy(env = process.env) {
22
48
  }
23
49
 
24
50
  // ../../src/core/tenant/databaseTenantContext.ts
51
+ async function applyBypassToTransaction(transaction, bypass) {
52
+ await transaction.unsafe(`SELECT set_config('app.bypass_rls', $1, true)`, [
53
+ bypass ? "true" : "false"
54
+ ]);
55
+ }
25
56
  async function runWithMigrationBypass(callback) {
26
57
  if (!isRlsTenancy()) {
27
58
  return await callback();
28
59
  }
29
- await db`SELECT set_config('app.bypass_rls', 'true', false)`;
30
- try {
31
- return await callback();
32
- } finally {
33
- await db`SELECT set_config('app.bypass_rls', 'false', false)`;
60
+ if (hasActiveDatabaseConnection()) {
61
+ const activeConnection = getActiveDatabaseConnection(getDefaultDatabasePool());
62
+ await applyBypassToTransaction(activeConnection, true);
63
+ try {
64
+ return await callback();
65
+ } finally {
66
+ await applyBypassToTransaction(activeConnection, false);
67
+ }
68
+ }
69
+ const pool = getDefaultDatabasePool();
70
+ if (typeof pool.begin !== "function") {
71
+ throw new Error("RLS migration bypass requires a pool that supports begin(). Session-scoped set_config is not used on pooled connections.");
34
72
  }
73
+ return await pool.begin(async (transaction) => {
74
+ await applyBypassToTransaction(transaction, true);
75
+ return await runWithDatabaseConnection(transaction, callback);
76
+ });
35
77
  }
36
78
  export {
37
79
  runWithMigrationBypass
@@ -2,6 +2,26 @@
2
2
  // ../../src/core/tracing/otel.ts
3
3
  import { 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
  }