@getstrata/core 0.5.63 → 0.5.65

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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # @getstrata/core changelog
2
2
 
3
+ ## 0.5.65
4
+
5
+ - Identity helpers on `@getstrata/core/runtime/appKeyPrefix` (`smtpEhloHost`, `siemEventType`, `appUserAgent`, `otelServiceName`, `appDisplayName`, `webhookSignatureHeader`) so sibling apps are not stuck with WorkHub SMTP/SIEM/OTEL/OAuth names.
6
+ - SIEM `event_type` and CEF vendor follow `SIEM_EVENT_TYPE` / `APP_NAME` (defaults stay `workhub.audit` / `WorkHub`).
7
+
8
+ ## 0.5.64
9
+
10
+ - `APP_KEY_PREFIX` (default `workhub`) namespaces Redis cache, queue, and throttle keys so sibling apps do not share WorkHub’s keyspace.
11
+ - `DispatchWebhookJob` implementation lives in the WorkHub webhook module. `@getstrata/core/jobs/dispatchWebhookJob` remains a compatibility re-export.
12
+
3
13
  ## 0.5.63
4
14
 
5
15
  - Flash cookies honor `FLASH_COOKIE_NAME` (default `workhub_flash`) so sibling apps do not inherit WorkHub’s cookie name.
package/README.md CHANGED
@@ -82,7 +82,7 @@ import type { Migration } from "@getstrata/core/database/migrations/types";
82
82
  Package name: **`@getstrata/core`** (npm org [`@getstrata`](https://www.npmjs.com/org/getstrata)).
83
83
 
84
84
  1. Add `NPM_TOKEN` to GitHub repository secrets.
85
- 2. Tag a release: `git tag v0.5.64 && git push origin v0.5.64`
85
+ 2. Tag a release: `git tag v0.5.67 && git push origin v0.5.67`
86
86
  3. [Release workflow](../../.github/workflows/release.yml) builds and runs `npm publish --access public`.
87
87
 
88
88
  Previously published as `@eyk-workhub/framework@0.1.0`, deprecated in favor of this package.
@@ -1,16 +1 @@
1
- import { Job } from "../queue";
2
- interface DispatchWebhookPayload {
3
- webhookId: number;
4
- tenantId: number;
5
- event: string;
6
- payload: Record<string, unknown>;
7
- }
8
- declare class DispatchWebhookJob extends Job<DispatchWebhookPayload> {
9
- readonly maxAttempts = 3;
10
- readonly backoffMs = 2000;
11
- handle(payload: DispatchWebhookPayload): Promise<void>;
12
- private deliver;
13
- }
14
- export { DispatchWebhookJob };
15
- export default DispatchWebhookJob;
16
- export type { DispatchWebhookPayload };
1
+ export { DispatchWebhookJob, type DispatchWebhookPayload, default, } from "../../modules/webhook/dispatchWebhookJob";
@@ -1,9 +1,9 @@
1
1
  import type FailedJobService from "./failedJobService";
2
2
  import type { Job, Queue, QueuePriority } from "./index";
3
3
  import { type QueueJobEnvelope } from "./jobRunner";
4
- declare const QUEUE_LIST_KEY = "workhub:queue:default";
5
- declare const QUEUE_HIGH_KEY = "workhub:queue:high";
6
- declare const QUEUE_LOW_KEY = "workhub:queue:low";
4
+ declare const QUEUE_LIST_KEY: string;
5
+ declare const QUEUE_HIGH_KEY: string;
6
+ declare const QUEUE_LOW_KEY: string;
7
7
  declare function queueKeyForPriority(priority?: QueuePriority): string;
8
8
  declare function parseQueueJobEnvelope(rawPayload: string): QueueJobEnvelope | null;
9
9
  declare class RedisQueue implements Queue {
@@ -0,0 +1,9 @@
1
+ declare function appKeyPrefix(): string;
2
+ declare function namespacedRedisKey(kind: string): string;
3
+ declare function smtpEhloHost(): string;
4
+ declare function siemEventType(): string;
5
+ declare function appUserAgent(): string;
6
+ declare function otelServiceName(): string;
7
+ declare function webhookSignatureHeader(): string;
8
+ declare function appDisplayName(): string;
9
+ export { appDisplayName, appKeyPrefix, appUserAgent, namespacedRedisKey, otelServiceName, siemEventType, smtpEhloHost, webhookSignatureHeader, };
@@ -153,11 +153,39 @@ async function runWithMigrationBypass(callback) {
153
153
  }
154
154
  }
155
155
 
156
+ // ../../src/core/runtime/appKeyPrefix.ts
157
+ function appKeyPrefix() {
158
+ return process.env.APP_KEY_PREFIX?.trim() || "workhub";
159
+ }
160
+ function namespacedRedisKey(kind) {
161
+ return `${appKeyPrefix()}:${kind}`;
162
+ }
163
+ function smtpEhloHost() {
164
+ const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
165
+ const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
166
+ return safe || "strata.local";
167
+ }
168
+ function siemEventType() {
169
+ return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
170
+ }
171
+ function appUserAgent() {
172
+ return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
173
+ }
174
+ function otelServiceName() {
175
+ return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
176
+ }
177
+ function webhookSignatureHeader() {
178
+ return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
179
+ }
180
+ function appDisplayName() {
181
+ return process.env.APP_NAME?.trim() || "WorkHub";
182
+ }
183
+
156
184
  // ../../src/core/audit/siemFormatter.ts
157
185
  function formatSiemAuditEvent(input) {
158
186
  return {
159
187
  timestamp: input.created_at.toISOString(),
160
- event_type: "workhub.audit",
188
+ event_type: siemEventType(),
161
189
  actor_user_id: input.user_id,
162
190
  tenant_id: input.tenant_id ?? null,
163
191
  trace_id: input.trace_id ?? null,
@@ -183,7 +211,7 @@ function formatCefLine(event) {
183
211
  `src=${event.ip_address ?? ""}`,
184
212
  `request=${event.trace_id ?? ""}`
185
213
  ].join(" ");
186
- return `CEF:0|WorkHub|API|1.0|${event.action}|${event.subject_type}|5|${extension}`;
214
+ return `CEF:0|${appDisplayName()}|API|1.0|${event.action}|${event.subject_type}|5|${extension}`;
187
215
  }
188
216
 
189
217
  // ../../src/core/audit/exportAuditLogs.ts
@@ -1,9 +1,37 @@
1
1
  // @bun
2
+ // ../../src/core/runtime/appKeyPrefix.ts
3
+ function appKeyPrefix() {
4
+ return process.env.APP_KEY_PREFIX?.trim() || "workhub";
5
+ }
6
+ function namespacedRedisKey(kind) {
7
+ return `${appKeyPrefix()}:${kind}`;
8
+ }
9
+ function smtpEhloHost() {
10
+ const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
11
+ const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
12
+ return safe || "strata.local";
13
+ }
14
+ function siemEventType() {
15
+ return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
16
+ }
17
+ function appUserAgent() {
18
+ return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
19
+ }
20
+ function otelServiceName() {
21
+ return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
22
+ }
23
+ function webhookSignatureHeader() {
24
+ return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
25
+ }
26
+ function appDisplayName() {
27
+ return process.env.APP_NAME?.trim() || "WorkHub";
28
+ }
29
+
2
30
  // ../../src/core/audit/siemFormatter.ts
3
31
  function formatSiemAuditEvent(input) {
4
32
  return {
5
33
  timestamp: input.created_at.toISOString(),
6
- event_type: "workhub.audit",
34
+ event_type: siemEventType(),
7
35
  actor_user_id: input.user_id,
8
36
  tenant_id: input.tenant_id ?? null,
9
37
  trace_id: input.trace_id ?? null,
@@ -29,7 +57,7 @@ function formatCefLine(event) {
29
57
  `src=${event.ip_address ?? ""}`,
30
58
  `request=${event.trace_id ?? ""}`
31
59
  ].join(" ");
32
- return `CEF:0|WorkHub|API|1.0|${event.action}|${event.subject_type}|5|${extension}`;
60
+ return `CEF:0|${appDisplayName()}|API|1.0|${event.action}|${event.subject_type}|5|${extension}`;
33
61
  }
34
62
  export {
35
63
  formatCefLine,
@@ -1,4 +1,32 @@
1
1
  // @bun
2
+ // ../../src/core/runtime/appKeyPrefix.ts
3
+ function appKeyPrefix() {
4
+ return process.env.APP_KEY_PREFIX?.trim() || "workhub";
5
+ }
6
+ function namespacedRedisKey(kind) {
7
+ return `${appKeyPrefix()}:${kind}`;
8
+ }
9
+ function smtpEhloHost() {
10
+ const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
11
+ const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
12
+ return safe || "strata.local";
13
+ }
14
+ function siemEventType() {
15
+ return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
16
+ }
17
+ function appUserAgent() {
18
+ return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
19
+ }
20
+ function otelServiceName() {
21
+ return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
22
+ }
23
+ function webhookSignatureHeader() {
24
+ return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
25
+ }
26
+ function appDisplayName() {
27
+ return process.env.APP_NAME?.trim() || "WorkHub";
28
+ }
29
+
2
30
  // ../../src/core/auth/oauth/providers.ts
3
31
  class GitHubOAuthProvider {
4
32
  options;
@@ -37,7 +65,7 @@ class GitHubOAuthProvider {
37
65
  headers: {
38
66
  authorization: `Bearer ${tokenBody.access_token}`,
39
67
  accept: "application/json",
40
- "user-agent": "workhub"
68
+ "user-agent": appUserAgent()
41
69
  }
42
70
  });
43
71
  const profile = await profileResponse.json();
@@ -1,4 +1,32 @@
1
1
  // @bun
2
+ // ../../src/core/runtime/appKeyPrefix.ts
3
+ function appKeyPrefix() {
4
+ return process.env.APP_KEY_PREFIX?.trim() || "workhub";
5
+ }
6
+ function namespacedRedisKey(kind) {
7
+ return `${appKeyPrefix()}:${kind}`;
8
+ }
9
+ function smtpEhloHost() {
10
+ const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
11
+ const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
12
+ return safe || "strata.local";
13
+ }
14
+ function siemEventType() {
15
+ return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
16
+ }
17
+ function appUserAgent() {
18
+ return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
19
+ }
20
+ function otelServiceName() {
21
+ return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
22
+ }
23
+ function webhookSignatureHeader() {
24
+ return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
25
+ }
26
+ function appDisplayName() {
27
+ return process.env.APP_NAME?.trim() || "WorkHub";
28
+ }
29
+
2
30
  // ../../src/core/auth/oauth/samlProvider.ts
3
31
  class SamlProvider {
4
32
  loginUrl;
@@ -15,8 +43,8 @@ class SamlProvider {
15
43
  }
16
44
  const [, email, name] = code.split(":");
17
45
  return {
18
- providerUserId: email ?? "saml-user",
19
- email: email ?? "saml-user@workhub.test",
46
+ providerUserId: email || "saml-user",
47
+ email: email || `saml-user@${appKeyPrefix()}.test`,
20
48
  name: name ?? "SAML User"
21
49
  };
22
50
  }
@@ -1,8 +1,42 @@
1
1
  // @bun
2
2
  // ../../src/core/cache/redisCacheStore.ts
3
3
  var {RedisClient } = globalThis.Bun;
4
- var KEY_PREFIX = "workhub:cache:";
5
- var TAG_PREFIX = "workhub:cache:tag:";
4
+
5
+ // ../../src/core/runtime/appKeyPrefix.ts
6
+ function appKeyPrefix() {
7
+ return process.env.APP_KEY_PREFIX?.trim() || "workhub";
8
+ }
9
+ function namespacedRedisKey(kind) {
10
+ return `${appKeyPrefix()}:${kind}`;
11
+ }
12
+ function smtpEhloHost() {
13
+ const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
14
+ const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
15
+ return safe || "strata.local";
16
+ }
17
+ function siemEventType() {
18
+ return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
19
+ }
20
+ function appUserAgent() {
21
+ return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
22
+ }
23
+ function otelServiceName() {
24
+ return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
25
+ }
26
+ function webhookSignatureHeader() {
27
+ return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
28
+ }
29
+ function appDisplayName() {
30
+ return process.env.APP_NAME?.trim() || "WorkHub";
31
+ }
32
+
33
+ // ../../src/core/cache/redisCacheStore.ts
34
+ function cacheKeyPrefix() {
35
+ return namespacedRedisKey("cache:");
36
+ }
37
+ function cacheTagPrefix() {
38
+ return namespacedRedisKey("cache:tag:");
39
+ }
6
40
 
7
41
  class RedisCacheStore {
8
42
  ttlMs;
@@ -89,10 +123,10 @@ class RedisCacheStore {
89
123
  return deleted > 0;
90
124
  }
91
125
  async invalidateByPrefix(prefix) {
92
- const keys = await this.client.keys(`${KEY_PREFIX}*`);
126
+ const keys = await this.client.keys(`${cacheKeyPrefix()}*`);
93
127
  let removed = 0;
94
128
  for (const storageKey of keys) {
95
- const key = storageKey.slice(KEY_PREFIX.length);
129
+ const key = storageKey.slice(cacheKeyPrefix().length);
96
130
  if (key === prefix || key.startsWith(`${prefix}?`)) {
97
131
  if (await this.invalidate(key)) {
98
132
  removed += 1;
@@ -102,11 +136,11 @@ class RedisCacheStore {
102
136
  return removed;
103
137
  }
104
138
  async clear() {
105
- const keys = await this.client.keys(`${KEY_PREFIX}*`);
139
+ const keys = await this.client.keys(`${cacheKeyPrefix()}*`);
106
140
  if (keys.length > 0) {
107
141
  await this.client.del(...keys);
108
142
  }
109
- const tagKeys = await this.client.keys(`${TAG_PREFIX}*`);
143
+ const tagKeys = await this.client.keys(`${cacheTagPrefix()}*`);
110
144
  if (tagKeys.length > 0) {
111
145
  await this.client.del(...tagKeys);
112
146
  }
@@ -114,14 +148,14 @@ class RedisCacheStore {
114
148
  this.keyTags.clear();
115
149
  }
116
150
  async size() {
117
- const keys = await this.client.keys(`${KEY_PREFIX}*`);
151
+ const keys = await this.client.keys(`${cacheKeyPrefix()}*`);
118
152
  return keys.length;
119
153
  }
120
154
  storageKey(key) {
121
- return `${KEY_PREFIX}${key}`;
155
+ return `${cacheKeyPrefix()}${key}`;
122
156
  }
123
157
  tagKey(tag) {
124
- return `${TAG_PREFIX}${tag}`;
158
+ return `${cacheTagPrefix()}${tag}`;
125
159
  }
126
160
  async detachKeyFromTags(key) {
127
161
  const tags = this.keyTags.get(key);
@@ -134,7 +168,7 @@ class RedisCacheStore {
134
168
  this.keyTags.delete(key);
135
169
  }
136
170
  async enforceMaxEntries() {
137
- const keys = await this.client.keys(`${KEY_PREFIX}*`);
171
+ const keys = await this.client.keys(`${cacheKeyPrefix()}*`);
138
172
  if (keys.length <= this.maxEntries) {
139
173
  return;
140
174
  }
@@ -11,6 +11,34 @@ import {
11
11
  resolveApplicationQueue
12
12
  } from "@getstrata/core/runtime/applicationRegistry";
13
13
 
14
+ // ../../src/core/runtime/appKeyPrefix.ts
15
+ function appKeyPrefix() {
16
+ return process.env.APP_KEY_PREFIX?.trim() || "workhub";
17
+ }
18
+ function namespacedRedisKey(kind) {
19
+ return `${appKeyPrefix()}:${kind}`;
20
+ }
21
+ function smtpEhloHost() {
22
+ const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
23
+ const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
24
+ return safe || "strata.local";
25
+ }
26
+ function siemEventType() {
27
+ return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
28
+ }
29
+ function appUserAgent() {
30
+ return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
31
+ }
32
+ function otelServiceName() {
33
+ return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
34
+ }
35
+ function webhookSignatureHeader() {
36
+ return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
37
+ }
38
+ function appDisplayName() {
39
+ return process.env.APP_NAME?.trim() || "WorkHub";
40
+ }
41
+
14
42
  // ../../src/core/mail/mailer.ts
15
43
  function resolveSmtpConfig() {
16
44
  const host = process.env.MAIL_HOST?.trim();
@@ -101,7 +129,7 @@ async function defaultSmtpTransport(config, message) {
101
129
  const { socket, readResponse } = await openSmtpConnection(config);
102
130
  try {
103
131
  await waitForSmtpResponse(readResponse, ["220"]);
104
- await socket.write(`EHLO workhub.local\r
132
+ await socket.write(`EHLO ${smtpEhloHost()}\r
105
133
  `);
106
134
  await waitForSmtpResponse(readResponse, ["250"]);
107
135
  if (config.username && config.password) {
@@ -2,6 +2,34 @@
2
2
  // ../../src/core/http/loginThrottleMiddleware.ts
3
3
  var {RedisClient } = globalThis.Bun;
4
4
 
5
+ // ../../src/core/runtime/appKeyPrefix.ts
6
+ function appKeyPrefix() {
7
+ return process.env.APP_KEY_PREFIX?.trim() || "workhub";
8
+ }
9
+ function namespacedRedisKey(kind) {
10
+ return `${appKeyPrefix()}:${kind}`;
11
+ }
12
+ function smtpEhloHost() {
13
+ const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
14
+ const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
15
+ return safe || "strata.local";
16
+ }
17
+ function siemEventType() {
18
+ return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
19
+ }
20
+ function appUserAgent() {
21
+ return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
22
+ }
23
+ function otelServiceName() {
24
+ return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
25
+ }
26
+ function webhookSignatureHeader() {
27
+ return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
28
+ }
29
+ function appDisplayName() {
30
+ return process.env.APP_NAME?.trim() || "WorkHub";
31
+ }
32
+
5
33
  // ../../src/core/http/clientIp.ts
6
34
  function trustForwardedFor(env = process.env) {
7
35
  return (env.TRUST_FORWARDED_FOR ?? "false") === "true";
@@ -37,7 +65,7 @@ async function resolveLoginEmail(request) {
37
65
  }
38
66
  function createLoginThrottleMiddleware(options) {
39
67
  const client = new RedisClient(options.redisUrl);
40
- const prefix = options.keyPrefix ?? "workhub:login-throttle:";
68
+ const prefix = options.keyPrefix ?? namespacedRedisKey("login-throttle:");
41
69
  return async (request, next) => {
42
70
  const identity = resolveLoginIdentity(request);
43
71
  const email = await resolveLoginEmail(request);
@@ -1,4 +1,32 @@
1
1
  // @bun
2
+ // ../../src/core/runtime/appKeyPrefix.ts
3
+ function appKeyPrefix() {
4
+ return process.env.APP_KEY_PREFIX?.trim() || "workhub";
5
+ }
6
+ function namespacedRedisKey(kind) {
7
+ return `${appKeyPrefix()}:${kind}`;
8
+ }
9
+ function smtpEhloHost() {
10
+ const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
11
+ const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
12
+ return safe || "strata.local";
13
+ }
14
+ function siemEventType() {
15
+ return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
16
+ }
17
+ function appUserAgent() {
18
+ return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
19
+ }
20
+ function otelServiceName() {
21
+ return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
22
+ }
23
+ function webhookSignatureHeader() {
24
+ return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
25
+ }
26
+ function appDisplayName() {
27
+ return process.env.APP_NAME?.trim() || "WorkHub";
28
+ }
29
+
2
30
  // ../../src/core/http/clientIp.ts
3
31
  function trustForwardedFor(env = process.env) {
4
32
  return (env.TRUST_FORWARDED_FOR ?? "false") === "true";
@@ -17,7 +45,7 @@ function readClientIp(request, env = process.env) {
17
45
  // ../../src/core/http/memoryThrottleMiddleware.ts
18
46
  var throttleBucketRegistries = new Set;
19
47
  function createMemoryThrottleMiddleware(options) {
20
- const prefix = options.keyPrefix ?? "workhub:memory-throttle:";
48
+ const prefix = options.keyPrefix ?? namespacedRedisKey("memory-throttle:");
21
49
  const buckets = new Map;
22
50
  throttleBucketRegistries.add(buckets);
23
51
  return async (request, next) => {
@@ -2,6 +2,34 @@
2
2
  // ../../src/core/http/scimThrottleMiddleware.ts
3
3
  var {RedisClient } = globalThis.Bun;
4
4
 
5
+ // ../../src/core/runtime/appKeyPrefix.ts
6
+ function appKeyPrefix() {
7
+ return process.env.APP_KEY_PREFIX?.trim() || "workhub";
8
+ }
9
+ function namespacedRedisKey(kind) {
10
+ return `${appKeyPrefix()}:${kind}`;
11
+ }
12
+ function smtpEhloHost() {
13
+ const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
14
+ const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
15
+ return safe || "strata.local";
16
+ }
17
+ function siemEventType() {
18
+ return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
19
+ }
20
+ function appUserAgent() {
21
+ return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
22
+ }
23
+ function otelServiceName() {
24
+ return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
25
+ }
26
+ function webhookSignatureHeader() {
27
+ return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
28
+ }
29
+ function appDisplayName() {
30
+ return process.env.APP_NAME?.trim() || "WorkHub";
31
+ }
32
+
5
33
  // ../../src/core/http/clientIp.ts
6
34
  function trustForwardedFor(env = process.env) {
7
35
  return (env.TRUST_FORWARDED_FOR ?? "false") === "true";
@@ -39,7 +67,7 @@ function createScimThrottleMiddleware(options) {
39
67
  const client = options.redisUrl ? new RedisClient(options.redisUrl) : null;
40
68
  return async (request, next) => {
41
69
  const identity = resolveScimIdentity(request);
42
- const key = `workhub:scim-throttle:${identity}`;
70
+ const key = `${namespacedRedisKey("scim-throttle:")}${identity}`;
43
71
  if (client) {
44
72
  const attempts = Number(await client.incr(key));
45
73
  if (attempts === 1) {
@@ -4,6 +4,34 @@ import { currentAuthUser } from "@getstrata/core/auth/authContext";
4
4
  import { currentTenant, rateLimitMultiplierForPlan } from "@getstrata/core/tenant/tenantContext";
5
5
  var {RedisClient } = globalThis.Bun;
6
6
 
7
+ // ../../src/core/runtime/appKeyPrefix.ts
8
+ function appKeyPrefix() {
9
+ return process.env.APP_KEY_PREFIX?.trim() || "workhub";
10
+ }
11
+ function namespacedRedisKey(kind) {
12
+ return `${appKeyPrefix()}:${kind}`;
13
+ }
14
+ function smtpEhloHost() {
15
+ const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
16
+ const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
17
+ return safe || "strata.local";
18
+ }
19
+ function siemEventType() {
20
+ return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
21
+ }
22
+ function appUserAgent() {
23
+ return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
24
+ }
25
+ function otelServiceName() {
26
+ return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
27
+ }
28
+ function webhookSignatureHeader() {
29
+ return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
30
+ }
31
+ function appDisplayName() {
32
+ return process.env.APP_NAME?.trim() || "WorkHub";
33
+ }
34
+
7
35
  // ../../src/core/http/clientIp.ts
8
36
  function trustForwardedFor(env = process.env) {
9
37
  return (env.TRUST_FORWARDED_FOR ?? "false") === "true";
@@ -32,7 +60,7 @@ function resolveThrottleIdentity(request) {
32
60
  }
33
61
  function createThrottleMiddleware(options) {
34
62
  const client = new RedisClient(options.redisUrl);
35
- const prefix = options.keyPrefix ?? "workhub:throttle:";
63
+ const prefix = options.keyPrefix ?? namespacedRedisKey("throttle:");
36
64
  return async (request, next) => {
37
65
  const identity = resolveThrottleIdentity(request);
38
66
  const path = new URL(request.url).pathname;