@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 +10 -0
- package/README.md +1 -1
- package/dist/core/jobs/dispatchWebhookJob.d.ts +1 -16
- package/dist/core/queue/redisQueue.d.ts +3 -3
- package/dist/core/runtime/appKeyPrefix.d.ts +9 -0
- package/dist/entries/audit/exportAuditLogs.js +30 -2
- package/dist/entries/audit/siemFormatter.js +30 -2
- package/dist/entries/auth/oauth/providers.js +29 -1
- package/dist/entries/auth/oauth/samlProvider.js +30 -2
- package/dist/entries/cache/createCacheStore.js +44 -10
- package/dist/entries/facades.js +29 -1
- package/dist/entries/http/loginThrottleMiddleware.js +29 -1
- package/dist/entries/http/memoryThrottleMiddleware.js +29 -1
- package/dist/entries/http/scimThrottleMiddleware.js +29 -1
- package/dist/entries/http/throttleMiddleware.js +29 -1
- package/dist/entries/jobs/dispatchWebhookJob.js +11 -266
- package/dist/entries/jobs/exportAuditLogsJob.js +30 -2
- package/dist/entries/mail/mailer.js +29 -1
- package/dist/entries/queue/createAppQueue.js +45 -6
- package/dist/entries/queue/publicQueue.js +45 -6
- package/dist/entries/queue/queueMetrics.js +45 -6
- package/dist/entries/queue/redisQueue.js +43 -6
- package/dist/entries/runtime/appKeyPrefix.js +38 -0
- package/dist/entries/tracing/tracingMiddleware.js +31 -1
- package/dist/index.js +53 -22
- package/dist/modules/webhook/dispatchWebhookJob.d.ts +16 -0
- package/package.json +7 -2
|
@@ -1,269 +1,14 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
// ../../src/
|
|
2
|
+
// ../../src/modules/webhook/dispatchWebhookJob.ts
|
|
3
3
|
import { createHmac } from "crypto";
|
|
4
|
-
import { repositoryConnection as db2 } from "@getstrata/core/database/repositoryConnection";
|
|
5
|
-
|
|
6
|
-
// ../../src/core/queue/index.ts
|
|
7
|
-
class Job {
|
|
8
|
-
maxAttempts;
|
|
9
|
-
backoffMs;
|
|
10
|
-
priority;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
class SyncQueue {
|
|
14
|
-
async dispatch(job, payload) {
|
|
15
|
-
await job.handle(payload);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
class AsyncQueue {
|
|
20
|
-
async dispatch(job, payload) {
|
|
21
|
-
setTimeout(() => {
|
|
22
|
-
job.handle(payload).catch((error) => {
|
|
23
|
-
console.error("[AsyncQueue] Job failed:", error);
|
|
24
|
-
});
|
|
25
|
-
}, 0);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
function createQueue(driver) {
|
|
29
|
-
return driver === "async" ? new AsyncQueue : new SyncQueue;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// ../../src/config/app.ts
|
|
33
|
-
var appConfig = {
|
|
34
|
-
name: process.env.APP_NAME?.trim() || "WorkHub",
|
|
35
|
-
env: process.env.APP_ENV ?? "local",
|
|
36
|
-
debug: (process.env.APP_DEBUG ?? "true") !== "false",
|
|
37
|
-
url: process.env.APP_URL ?? "http://localhost:3000",
|
|
38
|
-
apiPrefix: process.env.API_PREFIX ?? "/api/v1"
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
// ../../src/core/security/safeUrl.ts
|
|
42
|
-
import { lookup as dnsLookupImpl } from "dns/promises";
|
|
43
|
-
import { BadRequestError } from "@getstrata/core/errors/http";
|
|
44
|
-
var dnsLookup = dnsLookupImpl;
|
|
45
|
-
var BLOCKED_HOSTNAMES = new Set([
|
|
46
|
-
"localhost",
|
|
47
|
-
"127.0.0.1",
|
|
48
|
-
"0.0.0.0",
|
|
49
|
-
"::1",
|
|
50
|
-
"metadata.google.internal"
|
|
51
|
-
]);
|
|
52
|
-
function isPrivateIpv4(hostname) {
|
|
53
|
-
const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
|
|
54
|
-
if (!match) {
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
const octets = match.slice(1, 5).map((part) => Number.parseInt(part, 10));
|
|
58
|
-
if (octets.some((octet) => octet < 0 || octet > 255)) {
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
61
|
-
const [a = 0, b = 0] = octets;
|
|
62
|
-
if (a === 10) {
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
if (a === 127) {
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
if (a === 0) {
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
71
|
-
if (a === 169 && b === 254) {
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
if (a === 172 && b >= 16 && b <= 31) {
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
if (a === 192 && b === 168) {
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
function isBlockedHostname(hostname) {
|
|
83
|
-
const normalized = hostname.trim().toLowerCase();
|
|
84
|
-
if (normalized.length === 0) {
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
if (BLOCKED_HOSTNAMES.has(normalized)) {
|
|
88
|
-
return true;
|
|
89
|
-
}
|
|
90
|
-
if (normalized.endsWith(".local") || normalized.endsWith(".internal")) {
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
if (normalized.includes(":")) {
|
|
94
|
-
return true;
|
|
95
|
-
}
|
|
96
|
-
return isPrivateIpv4(normalized);
|
|
97
|
-
}
|
|
98
|
-
function assertSafeOutboundUrl(rawUrl, options = {}) {
|
|
99
|
-
let parsed;
|
|
100
|
-
try {
|
|
101
|
-
parsed = new URL(rawUrl);
|
|
102
|
-
} catch {
|
|
103
|
-
throw new BadRequestError("Webhook URL is invalid.");
|
|
104
|
-
}
|
|
105
|
-
if (parsed.protocol !== "https:" && !(options.allowHttp && parsed.protocol === "http:")) {
|
|
106
|
-
throw new BadRequestError("Webhook URL must use HTTPS.");
|
|
107
|
-
}
|
|
108
|
-
if (parsed.username || parsed.password) {
|
|
109
|
-
throw new BadRequestError("Webhook URL must not include credentials.");
|
|
110
|
-
}
|
|
111
|
-
if (isBlockedHostname(parsed.hostname)) {
|
|
112
|
-
throw new BadRequestError("Webhook URL targets a blocked host.");
|
|
113
|
-
}
|
|
114
|
-
return parsed;
|
|
115
|
-
}
|
|
116
|
-
function isBlockedIpAddress(address) {
|
|
117
|
-
return isBlockedHostname(address.trim().toLowerCase());
|
|
118
|
-
}
|
|
119
|
-
async function assertSafeOutboundUrlResolved(rawUrl, options = {}) {
|
|
120
|
-
const parsed = assertSafeOutboundUrl(rawUrl, options);
|
|
121
|
-
if (options.resolveDns === false) {
|
|
122
|
-
return parsed;
|
|
123
|
-
}
|
|
124
|
-
const hostname = parsed.hostname.trim().toLowerCase();
|
|
125
|
-
const results = await dnsLookup(hostname, { all: true, verbatim: true });
|
|
126
|
-
if (results.some((result) => isBlockedIpAddress(result.address))) {
|
|
127
|
-
throw new BadRequestError("Webhook URL targets a blocked host.");
|
|
128
|
-
}
|
|
129
|
-
return parsed;
|
|
130
|
-
}
|
|
131
|
-
function setDnsLookupForTests(lookupFn) {
|
|
132
|
-
dnsLookup = lookupFn;
|
|
133
|
-
}
|
|
134
|
-
function resetDnsLookupForTests() {
|
|
135
|
-
dnsLookup = dnsLookupImpl;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ../../src/core/security/safeFetch.ts
|
|
139
|
-
var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
|
|
140
|
-
async function safeFetch(input, init = {}, options = {}) {
|
|
141
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
|
|
142
|
-
const maxRedirects = options.maxRedirects ?? 0;
|
|
143
|
-
const resolveDns = options.resolveDns ?? appConfig.env === "production";
|
|
144
|
-
const urlOptions = { allowHttp: options.allowHttp, resolveDns };
|
|
145
|
-
const controller = new AbortController;
|
|
146
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
147
|
-
try {
|
|
148
|
-
let currentUrl = (await assertSafeOutboundUrlResolved(input, urlOptions)).toString();
|
|
149
|
-
let redirectCount = 0;
|
|
150
|
-
while (true) {
|
|
151
|
-
const response = await fetch(currentUrl, {
|
|
152
|
-
...init,
|
|
153
|
-
signal: controller.signal,
|
|
154
|
-
redirect: "manual"
|
|
155
|
-
});
|
|
156
|
-
if (response.status >= 300 && response.status < 400) {
|
|
157
|
-
const location = response.headers.get("location");
|
|
158
|
-
if (!location || redirectCount >= maxRedirects) {
|
|
159
|
-
return response;
|
|
160
|
-
}
|
|
161
|
-
currentUrl = (await assertSafeOutboundUrlResolved(new URL(location, currentUrl).toString(), urlOptions)).toString();
|
|
162
|
-
redirectCount += 1;
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
return response;
|
|
166
|
-
}
|
|
167
|
-
} finally {
|
|
168
|
-
clearTimeout(timeout);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// ../../src/core/tenant/resolveTenant.ts
|
|
173
4
|
import { repositoryConnection as db } from "@getstrata/core/database/repositoryConnection";
|
|
5
|
+
import { Job } from "@getstrata/core/queue";
|
|
6
|
+
import { webhookSignatureHeader } from "@getstrata/core/runtime/appKeyPrefix";
|
|
7
|
+
import { safeFetch } from "@getstrata/core/security/safeFetch";
|
|
8
|
+
import { assertSafeOutboundUrl } from "@getstrata/core/security/safeUrl";
|
|
9
|
+
import { resolveTenant } from "@getstrata/core/tenant/resolveTenant";
|
|
10
|
+
import { runWithTenantDatabase } from "@getstrata/core/tenant/tenantDatabaseScope";
|
|
174
11
|
|
|
175
|
-
// ../../src/core/tenant/tenancyConfig.ts
|
|
176
|
-
function readTenancyDriver(env = process.env) {
|
|
177
|
-
return env.TENANCY_DRIVER === "none" ? "none" : "rls";
|
|
178
|
-
}
|
|
179
|
-
function isTenancyEnabled(env = process.env) {
|
|
180
|
-
return readTenancyDriver(env) !== "none";
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// ../../src/core/tenant/resolveTenant.ts
|
|
184
|
-
async function resolveTenant(tenantId) {
|
|
185
|
-
if (!isTenancyEnabled()) {
|
|
186
|
-
return {
|
|
187
|
-
id: tenantId,
|
|
188
|
-
slug: "default",
|
|
189
|
-
plan: "free",
|
|
190
|
-
region: "eu"
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
const rows = await db`
|
|
194
|
-
SELECT id, slug, plan, region
|
|
195
|
-
FROM tenant
|
|
196
|
-
WHERE id = ${tenantId}
|
|
197
|
-
LIMIT 1
|
|
198
|
-
`;
|
|
199
|
-
const row = rows[0];
|
|
200
|
-
return row ? { id: row.id, slug: row.slug, plan: row.plan, region: row.region ?? "eu" } : null;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// ../../src/core/tenant/tenantDatabaseScope.ts
|
|
204
|
-
import { getDefaultDatabasePool } from "@getstrata/core/database/defaultConnection";
|
|
205
|
-
|
|
206
|
-
// ../../src/core/runtime/asyncContextStore.ts
|
|
207
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
208
|
-
function createAsyncContextStore(key) {
|
|
209
|
-
const symbol = Symbol.for(key);
|
|
210
|
-
const globalRecord = globalThis;
|
|
211
|
-
const existing = globalRecord[symbol];
|
|
212
|
-
if (existing) {
|
|
213
|
-
return existing;
|
|
214
|
-
}
|
|
215
|
-
const store = new AsyncLocalStorage;
|
|
216
|
-
globalRecord[symbol] = store;
|
|
217
|
-
return store;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// ../../src/core/database/connectionContext.ts
|
|
221
|
-
var activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
|
|
222
|
-
function runWithDatabaseConnection(connection, callback) {
|
|
223
|
-
return activeConnection.run(connection, callback);
|
|
224
|
-
}
|
|
225
|
-
function getActiveDatabaseConnection(fallback) {
|
|
226
|
-
return activeConnection.getStore() ?? fallback;
|
|
227
|
-
}
|
|
228
|
-
function hasActiveDatabaseConnection() {
|
|
229
|
-
return activeConnection.getStore() !== undefined;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// ../../src/core/tenant/tenantContext.ts
|
|
233
|
-
var tenantContext = createAsyncContextStore("@getstrata/tenantContext");
|
|
234
|
-
function runWithTenant(tenant, callback) {
|
|
235
|
-
return tenantContext.run(tenant, callback);
|
|
236
|
-
}
|
|
237
|
-
function currentTenant() {
|
|
238
|
-
return tenantContext.getStore() ?? null;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// ../../src/core/tenant/tenantDatabaseScope.ts
|
|
242
|
-
async function applyTenantContextToTransaction(transaction, tenantId) {
|
|
243
|
-
await transaction.unsafe(`SELECT set_config('app.tenant_id', $1, true)`, [String(tenantId)]);
|
|
244
|
-
await transaction.unsafe(`SELECT set_config('app.bypass_rls', $1, true)`, ["false"]);
|
|
245
|
-
}
|
|
246
|
-
async function runWithTenantDatabase(tenant, callback) {
|
|
247
|
-
if (!isTenancyEnabled()) {
|
|
248
|
-
return await runWithTenant(tenant, callback);
|
|
249
|
-
}
|
|
250
|
-
if (hasActiveDatabaseConnection()) {
|
|
251
|
-
const activeConnection2 = getActiveDatabaseConnection(getDefaultDatabasePool());
|
|
252
|
-
await applyTenantContextToTransaction(activeConnection2, tenant.id);
|
|
253
|
-
return await runWithTenant(tenant, callback);
|
|
254
|
-
}
|
|
255
|
-
return await getDefaultDatabasePool().begin(async (transaction) => {
|
|
256
|
-
await applyTenantContextToTransaction(transaction, tenant.id);
|
|
257
|
-
return await runWithDatabaseConnection(transaction, async () => {
|
|
258
|
-
return await runWithTenant(tenant, callback);
|
|
259
|
-
});
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
function isInsideTenantDatabaseScope(tenantId = currentTenant()?.id) {
|
|
263
|
-
return hasActiveDatabaseConnection() && currentTenant()?.id === tenantId;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// ../../src/core/jobs/dispatchWebhookJob.ts
|
|
267
12
|
class DispatchWebhookJob extends Job {
|
|
268
13
|
maxAttempts = 3;
|
|
269
14
|
backoffMs = 2000;
|
|
@@ -280,7 +25,7 @@ class DispatchWebhookJob extends Job {
|
|
|
280
25
|
}
|
|
281
26
|
}
|
|
282
27
|
async deliver(payload) {
|
|
283
|
-
const rows = await
|
|
28
|
+
const rows = await db`
|
|
284
29
|
SELECT id, url, secret
|
|
285
30
|
FROM webhook
|
|
286
31
|
WHERE id = ${payload.webhookId} AND active = TRUE
|
|
@@ -293,7 +38,7 @@ class DispatchWebhookJob extends Job {
|
|
|
293
38
|
const body = JSON.stringify({ event: payload.event, payload: payload.payload });
|
|
294
39
|
const signature = createHmac("sha256", webhook.secret).update(body).digest("hex");
|
|
295
40
|
const allowHttp = (process.env.APP_ENV ?? "local") !== "production";
|
|
296
|
-
const signatureHeader =
|
|
41
|
+
const signatureHeader = webhookSignatureHeader();
|
|
297
42
|
assertSafeOutboundUrl(webhook.url, { allowHttp });
|
|
298
43
|
let responseStatus = null;
|
|
299
44
|
let errorMessage = null;
|
|
@@ -312,7 +57,7 @@ class DispatchWebhookJob extends Job {
|
|
|
312
57
|
}
|
|
313
58
|
} catch (error) {
|
|
314
59
|
errorMessage = error instanceof Error ? error.message : String(error);
|
|
315
|
-
await
|
|
60
|
+
await db`
|
|
316
61
|
INSERT INTO webhook_delivery (webhook_id, event, payload, response_status, error)
|
|
317
62
|
VALUES (
|
|
318
63
|
${webhook.id},
|
|
@@ -324,7 +69,7 @@ class DispatchWebhookJob extends Job {
|
|
|
324
69
|
`;
|
|
325
70
|
return error instanceof Error ? error : new Error(errorMessage);
|
|
326
71
|
}
|
|
327
|
-
await
|
|
72
|
+
await db`
|
|
328
73
|
INSERT INTO webhook_delivery (webhook_id, event, payload, response_status)
|
|
329
74
|
VALUES (
|
|
330
75
|
${webhook.id},
|
|
@@ -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:
|
|
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|
|
|
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,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/mail/mailer.ts
|
|
3
31
|
function resolveSmtpConfig() {
|
|
4
32
|
const host = process.env.MAIL_HOST?.trim();
|
|
@@ -89,7 +117,7 @@ async function defaultSmtpTransport(config, message) {
|
|
|
89
117
|
const { socket, readResponse } = await openSmtpConnection(config);
|
|
90
118
|
try {
|
|
91
119
|
await waitForSmtpResponse(readResponse, ["220"]);
|
|
92
|
-
await socket.write(`EHLO
|
|
120
|
+
await socket.write(`EHLO ${smtpEhloHost()}\r
|
|
93
121
|
`);
|
|
94
122
|
await waitForSmtpResponse(readResponse, ["250"]);
|
|
95
123
|
if (config.username && config.password) {
|
|
@@ -153,18 +153,57 @@ async function runQueueJob(envelope, failedJobs) {
|
|
|
153
153
|
|
|
154
154
|
// ../../src/core/queue/redisQueue.ts
|
|
155
155
|
var {RedisClient } = globalThis.Bun;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
|
|
157
|
+
// ../../src/core/runtime/appKeyPrefix.ts
|
|
158
|
+
function appKeyPrefix() {
|
|
159
|
+
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
160
|
+
}
|
|
161
|
+
function namespacedRedisKey(kind) {
|
|
162
|
+
return `${appKeyPrefix()}:${kind}`;
|
|
163
|
+
}
|
|
164
|
+
function smtpEhloHost() {
|
|
165
|
+
const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
|
|
166
|
+
const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
|
|
167
|
+
return safe || "strata.local";
|
|
168
|
+
}
|
|
169
|
+
function siemEventType() {
|
|
170
|
+
return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
|
|
171
|
+
}
|
|
172
|
+
function appUserAgent() {
|
|
173
|
+
return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
|
|
174
|
+
}
|
|
175
|
+
function otelServiceName() {
|
|
176
|
+
return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
|
|
177
|
+
}
|
|
178
|
+
function webhookSignatureHeader() {
|
|
179
|
+
return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
|
|
180
|
+
}
|
|
181
|
+
function appDisplayName() {
|
|
182
|
+
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ../../src/core/queue/redisQueue.ts
|
|
186
|
+
function queueListKey() {
|
|
187
|
+
return namespacedRedisKey("queue:default");
|
|
188
|
+
}
|
|
189
|
+
function queueHighKey() {
|
|
190
|
+
return namespacedRedisKey("queue:high");
|
|
191
|
+
}
|
|
192
|
+
function queueLowKey() {
|
|
193
|
+
return namespacedRedisKey("queue:low");
|
|
194
|
+
}
|
|
195
|
+
var QUEUE_LIST_KEY = queueListKey();
|
|
196
|
+
var QUEUE_HIGH_KEY = queueHighKey();
|
|
197
|
+
var QUEUE_LOW_KEY = queueLowKey();
|
|
159
198
|
var QUEUE_KEYS = [QUEUE_HIGH_KEY, QUEUE_LIST_KEY, QUEUE_LOW_KEY];
|
|
160
199
|
function queueKeyForPriority(priority = "default") {
|
|
161
200
|
switch (priority) {
|
|
162
201
|
case "high":
|
|
163
|
-
return
|
|
202
|
+
return queueHighKey();
|
|
164
203
|
case "low":
|
|
165
|
-
return
|
|
204
|
+
return queueLowKey();
|
|
166
205
|
default:
|
|
167
|
-
return
|
|
206
|
+
return queueListKey();
|
|
168
207
|
}
|
|
169
208
|
}
|
|
170
209
|
function parseQueueJobEnvelope(rawPayload) {
|
|
@@ -153,18 +153,57 @@ async function runQueueJob(envelope, failedJobs) {
|
|
|
153
153
|
|
|
154
154
|
// ../../src/core/queue/redisQueue.ts
|
|
155
155
|
var {RedisClient } = globalThis.Bun;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
|
|
157
|
+
// ../../src/core/runtime/appKeyPrefix.ts
|
|
158
|
+
function appKeyPrefix() {
|
|
159
|
+
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
160
|
+
}
|
|
161
|
+
function namespacedRedisKey(kind) {
|
|
162
|
+
return `${appKeyPrefix()}:${kind}`;
|
|
163
|
+
}
|
|
164
|
+
function smtpEhloHost() {
|
|
165
|
+
const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
|
|
166
|
+
const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
|
|
167
|
+
return safe || "strata.local";
|
|
168
|
+
}
|
|
169
|
+
function siemEventType() {
|
|
170
|
+
return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
|
|
171
|
+
}
|
|
172
|
+
function appUserAgent() {
|
|
173
|
+
return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
|
|
174
|
+
}
|
|
175
|
+
function otelServiceName() {
|
|
176
|
+
return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
|
|
177
|
+
}
|
|
178
|
+
function webhookSignatureHeader() {
|
|
179
|
+
return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
|
|
180
|
+
}
|
|
181
|
+
function appDisplayName() {
|
|
182
|
+
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ../../src/core/queue/redisQueue.ts
|
|
186
|
+
function queueListKey() {
|
|
187
|
+
return namespacedRedisKey("queue:default");
|
|
188
|
+
}
|
|
189
|
+
function queueHighKey() {
|
|
190
|
+
return namespacedRedisKey("queue:high");
|
|
191
|
+
}
|
|
192
|
+
function queueLowKey() {
|
|
193
|
+
return namespacedRedisKey("queue:low");
|
|
194
|
+
}
|
|
195
|
+
var QUEUE_LIST_KEY = queueListKey();
|
|
196
|
+
var QUEUE_HIGH_KEY = queueHighKey();
|
|
197
|
+
var QUEUE_LOW_KEY = queueLowKey();
|
|
159
198
|
var QUEUE_KEYS = [QUEUE_HIGH_KEY, QUEUE_LIST_KEY, QUEUE_LOW_KEY];
|
|
160
199
|
function queueKeyForPriority(priority = "default") {
|
|
161
200
|
switch (priority) {
|
|
162
201
|
case "high":
|
|
163
|
-
return
|
|
202
|
+
return queueHighKey();
|
|
164
203
|
case "low":
|
|
165
|
-
return
|
|
204
|
+
return queueLowKey();
|
|
166
205
|
default:
|
|
167
|
-
return
|
|
206
|
+
return queueListKey();
|
|
168
207
|
}
|
|
169
208
|
}
|
|
170
209
|
function parseQueueJobEnvelope(rawPayload) {
|
|
@@ -156,18 +156,57 @@ async function runQueueJob(envelope, failedJobs) {
|
|
|
156
156
|
|
|
157
157
|
// ../../src/core/queue/redisQueue.ts
|
|
158
158
|
var {RedisClient } = globalThis.Bun;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
|
|
160
|
+
// ../../src/core/runtime/appKeyPrefix.ts
|
|
161
|
+
function appKeyPrefix() {
|
|
162
|
+
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
163
|
+
}
|
|
164
|
+
function namespacedRedisKey(kind) {
|
|
165
|
+
return `${appKeyPrefix()}:${kind}`;
|
|
166
|
+
}
|
|
167
|
+
function smtpEhloHost() {
|
|
168
|
+
const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
|
|
169
|
+
const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
|
|
170
|
+
return safe || "strata.local";
|
|
171
|
+
}
|
|
172
|
+
function siemEventType() {
|
|
173
|
+
return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
|
|
174
|
+
}
|
|
175
|
+
function appUserAgent() {
|
|
176
|
+
return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
|
|
177
|
+
}
|
|
178
|
+
function otelServiceName() {
|
|
179
|
+
return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
|
|
180
|
+
}
|
|
181
|
+
function webhookSignatureHeader() {
|
|
182
|
+
return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
|
|
183
|
+
}
|
|
184
|
+
function appDisplayName() {
|
|
185
|
+
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ../../src/core/queue/redisQueue.ts
|
|
189
|
+
function queueListKey() {
|
|
190
|
+
return namespacedRedisKey("queue:default");
|
|
191
|
+
}
|
|
192
|
+
function queueHighKey() {
|
|
193
|
+
return namespacedRedisKey("queue:high");
|
|
194
|
+
}
|
|
195
|
+
function queueLowKey() {
|
|
196
|
+
return namespacedRedisKey("queue:low");
|
|
197
|
+
}
|
|
198
|
+
var QUEUE_LIST_KEY = queueListKey();
|
|
199
|
+
var QUEUE_HIGH_KEY = queueHighKey();
|
|
200
|
+
var QUEUE_LOW_KEY = queueLowKey();
|
|
162
201
|
var QUEUE_KEYS = [QUEUE_HIGH_KEY, QUEUE_LIST_KEY, QUEUE_LOW_KEY];
|
|
163
202
|
function queueKeyForPriority(priority = "default") {
|
|
164
203
|
switch (priority) {
|
|
165
204
|
case "high":
|
|
166
|
-
return
|
|
205
|
+
return queueHighKey();
|
|
167
206
|
case "low":
|
|
168
|
-
return
|
|
207
|
+
return queueLowKey();
|
|
169
208
|
default:
|
|
170
|
-
return
|
|
209
|
+
return queueListKey();
|
|
171
210
|
}
|
|
172
211
|
}
|
|
173
212
|
function parseQueueJobEnvelope(rawPayload) {
|
|
@@ -2,6 +2,34 @@
|
|
|
2
2
|
// ../../src/core/queue/redisQueue.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/queue/jobRegistry.ts
|
|
6
34
|
class JobRegistry {
|
|
7
35
|
factories = new Map;
|
|
@@ -91,18 +119,27 @@ async function runQueueJob(envelope, failedJobs) {
|
|
|
91
119
|
}
|
|
92
120
|
|
|
93
121
|
// ../../src/core/queue/redisQueue.ts
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
122
|
+
function queueListKey() {
|
|
123
|
+
return namespacedRedisKey("queue:default");
|
|
124
|
+
}
|
|
125
|
+
function queueHighKey() {
|
|
126
|
+
return namespacedRedisKey("queue:high");
|
|
127
|
+
}
|
|
128
|
+
function queueLowKey() {
|
|
129
|
+
return namespacedRedisKey("queue:low");
|
|
130
|
+
}
|
|
131
|
+
var QUEUE_LIST_KEY = queueListKey();
|
|
132
|
+
var QUEUE_HIGH_KEY = queueHighKey();
|
|
133
|
+
var QUEUE_LOW_KEY = queueLowKey();
|
|
97
134
|
var QUEUE_KEYS = [QUEUE_HIGH_KEY, QUEUE_LIST_KEY, QUEUE_LOW_KEY];
|
|
98
135
|
function queueKeyForPriority(priority = "default") {
|
|
99
136
|
switch (priority) {
|
|
100
137
|
case "high":
|
|
101
|
-
return
|
|
138
|
+
return queueHighKey();
|
|
102
139
|
case "low":
|
|
103
|
-
return
|
|
140
|
+
return queueLowKey();
|
|
104
141
|
default:
|
|
105
|
-
return
|
|
142
|
+
return queueListKey();
|
|
106
143
|
}
|
|
107
144
|
}
|
|
108
145
|
function parseQueueJobEnvelope(rawPayload) {
|