@getstrata/core 0.5.65 → 0.5.67
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/README.md +1 -1
- package/dist/core/http/signedUrl.d.ts +3 -1
- package/dist/core/openapi/generator.d.ts +2 -2
- package/dist/core/runtime/appKeyPrefix.d.ts +5 -1
- package/dist/entries/audit/exportAuditLogs.js +50 -39
- package/dist/entries/audit/siemFormatter.js +20 -0
- package/dist/entries/auth/oauth/providers.js +20 -0
- package/dist/entries/auth/oauth/samlProvider.js +20 -0
- package/dist/entries/cache/createCacheStore.js +20 -0
- package/dist/entries/facades.js +20 -0
- package/dist/entries/http/loginThrottleMiddleware.js +20 -0
- package/dist/entries/http/memoryThrottleMiddleware.js +20 -0
- package/dist/entries/http/scimThrottleMiddleware.js +20 -0
- package/dist/entries/http/securityHeadersMiddleware.js +48 -9
- package/dist/entries/http/signedUrl.js +7 -0
- package/dist/entries/http/throttleMiddleware.js +20 -0
- package/dist/entries/jobs/exportAuditLogsJob.js +50 -39
- package/dist/entries/mail/mailer.js +20 -0
- package/dist/entries/openapi/generator.js +58 -19
- package/dist/entries/queue/createAppQueue.js +20 -0
- package/dist/entries/queue/publicQueue.js +20 -0
- package/dist/entries/queue/queueMetrics.js +20 -0
- package/dist/entries/queue/redisQueue.js +20 -0
- package/dist/entries/runtime/appKeyPrefix.js +24 -0
- package/dist/entries/security/safeFetch.js +48 -9
- package/dist/entries/tracing/tracingMiddleware.js +20 -0
- package/dist/framework/public-api.d.ts +1 -1
- package/dist/index.js +11 -10
- package/package.json +1 -1
- package/dist/config/app.d.ts +0 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @getstrata/core changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.67
|
|
4
|
+
|
|
5
|
+
- OpenAPI title, server URLs, and generated SDK class name come from `APP_NAME` / `APP_URL` / `API_PREFIX` / `APP_SDK_CLASS` instead of importing WorkHub `src/config/app`.
|
|
6
|
+
- `appEnv()`, `appUrl()`, `apiPrefix()`, and `sdkClientClassName()` live on `@getstrata/core/runtime/appKeyPrefix`. SIEM export, HSTS, and `safeFetch` DNS resolve use `appEnv()` instead of WorkHub `appConfig`.
|
|
7
|
+
|
|
8
|
+
## 0.5.66
|
|
9
|
+
|
|
10
|
+
- `createValidateSignatureMiddleware()` on `@getstrata/core/http/signedUrl` is Laravel’s `signed` / `ValidateSignature` middleware. Invalid or expired links throw `ForbiddenError`.
|
|
11
|
+
|
|
3
12
|
## 0.5.65
|
|
4
13
|
|
|
5
14
|
- 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.
|
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.
|
|
85
|
+
2. Tag a release: `git tag v0.5.69 && git push origin v0.5.69`
|
|
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,3 +1,4 @@
|
|
|
1
|
+
import type { Middleware } from "./middleware";
|
|
1
2
|
interface TemporarySignedUrlOptions {
|
|
2
3
|
expiresInSeconds?: number;
|
|
3
4
|
query?: Record<string, string | number>;
|
|
@@ -7,5 +8,6 @@ declare function temporarySignedUrl(path: string, expiresInSeconds: number, quer
|
|
|
7
8
|
declare function absoluteTemporarySignedUrl(path: string, expiresInSeconds: number, query?: Record<string, string | number>, origin?: string): string;
|
|
8
9
|
declare function hasValidSignature(input: Request | URL | string): boolean;
|
|
9
10
|
declare function assertValidSignature(input: Request | URL | string): void;
|
|
11
|
+
declare function createValidateSignatureMiddleware(): Middleware;
|
|
10
12
|
export type { TemporarySignedUrlOptions };
|
|
11
|
-
export { absoluteTemporarySignedUrl, assertValidSignature, hasValidSignature, signedUrl, temporarySignedUrl, };
|
|
13
|
+
export { absoluteTemporarySignedUrl, assertValidSignature, createValidateSignatureMiddleware, hasValidSignature, signedUrl, temporarySignedUrl, };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RegisteredRoute } from "
|
|
1
|
+
import type { RegisteredRoute } from "./registeredRoute";
|
|
2
2
|
interface OpenApiSpec {
|
|
3
3
|
openapi: string;
|
|
4
4
|
info: {
|
|
@@ -17,6 +17,6 @@ interface OpenApiSpec {
|
|
|
17
17
|
}
|
|
18
18
|
declare function generateOpenApiSpec(routes: RegisteredRoute[]): OpenApiSpec;
|
|
19
19
|
declare function renderOpenApiDocument(spec: OpenApiSpec): string;
|
|
20
|
-
declare function renderTypeScriptSdk(spec: OpenApiSpec,
|
|
20
|
+
declare function renderTypeScriptSdk(spec: OpenApiSpec, prefix?: string): string;
|
|
21
21
|
export type { OpenApiSpec };
|
|
22
22
|
export { generateOpenApiSpec, renderOpenApiDocument, renderTypeScriptSdk };
|
|
@@ -6,4 +6,8 @@ declare function appUserAgent(): string;
|
|
|
6
6
|
declare function otelServiceName(): string;
|
|
7
7
|
declare function webhookSignatureHeader(): string;
|
|
8
8
|
declare function appDisplayName(): string;
|
|
9
|
-
|
|
9
|
+
declare function appEnv(): string;
|
|
10
|
+
declare function appUrl(): string;
|
|
11
|
+
declare function apiPrefix(): string;
|
|
12
|
+
declare function sdkClientClassName(): string;
|
|
13
|
+
export { apiPrefix, appDisplayName, appEnv, appKeyPrefix, appUrl, appUserAgent, namespacedRedisKey, otelServiceName, sdkClientClassName, siemEventType, smtpEhloHost, webhookSignatureHeader, };
|
|
@@ -2,14 +2,53 @@
|
|
|
2
2
|
// ../../src/core/audit/exportAuditLogs.ts
|
|
3
3
|
import { repositoryConnection as db2 } from "@getstrata/core/database/repositoryConnection";
|
|
4
4
|
|
|
5
|
-
// ../../src/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
+
function appEnv() {
|
|
33
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
34
|
+
}
|
|
35
|
+
function appUrl() {
|
|
36
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
37
|
+
}
|
|
38
|
+
function apiPrefix() {
|
|
39
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
40
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
41
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
42
|
+
return trimmed || "/api/v1";
|
|
43
|
+
}
|
|
44
|
+
function sdkClientClassName() {
|
|
45
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
46
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
47
|
+
return override;
|
|
48
|
+
}
|
|
49
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
50
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
51
|
+
}
|
|
13
52
|
|
|
14
53
|
// ../../src/core/security/safeUrl.ts
|
|
15
54
|
import { lookup as dnsLookupImpl } from "dns/promises";
|
|
@@ -113,7 +152,7 @@ var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
|
|
|
113
152
|
async function safeFetch(input, init = {}, options = {}) {
|
|
114
153
|
const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
|
|
115
154
|
const maxRedirects = options.maxRedirects ?? 0;
|
|
116
|
-
const resolveDns = options.resolveDns ??
|
|
155
|
+
const resolveDns = options.resolveDns ?? appEnv() === "production";
|
|
117
156
|
const urlOptions = { allowHttp: options.allowHttp, resolveDns };
|
|
118
157
|
const controller = new AbortController;
|
|
119
158
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -153,34 +192,6 @@ async function runWithMigrationBypass(callback) {
|
|
|
153
192
|
}
|
|
154
193
|
}
|
|
155
194
|
|
|
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
|
-
|
|
184
195
|
// ../../src/core/audit/siemFormatter.ts
|
|
185
196
|
function formatSiemAuditEvent(input) {
|
|
186
197
|
return {
|
|
@@ -220,7 +231,7 @@ function resolveAuditExportConfig() {
|
|
|
220
231
|
if (!endpoint) {
|
|
221
232
|
return null;
|
|
222
233
|
}
|
|
223
|
-
assertSafeOutboundUrl(endpoint, { allowHttp:
|
|
234
|
+
assertSafeOutboundUrl(endpoint, { allowHttp: appEnv() !== "production" });
|
|
224
235
|
const batchSize = Number(process.env.SIEM_EXPORT_BATCH_SIZE ?? "100");
|
|
225
236
|
return {
|
|
226
237
|
endpoint,
|
|
@@ -278,7 +289,7 @@ async function exportPendingAuditLogs() {
|
|
|
278
289
|
...process.env.SIEM_EXPORT_TOKEN ? { authorization: `Bearer ${process.env.SIEM_EXPORT_TOKEN}` } : {}
|
|
279
290
|
},
|
|
280
291
|
body
|
|
281
|
-
}, { allowHttp:
|
|
292
|
+
}, { allowHttp: appEnv() !== "production" });
|
|
282
293
|
if (!response.ok) {
|
|
283
294
|
throw new Error(`SIEM export failed with status ${response.status}.`);
|
|
284
295
|
}
|
|
@@ -26,6 +26,26 @@ function webhookSignatureHeader() {
|
|
|
26
26
|
function appDisplayName() {
|
|
27
27
|
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
28
28
|
}
|
|
29
|
+
function appEnv() {
|
|
30
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
31
|
+
}
|
|
32
|
+
function appUrl() {
|
|
33
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
34
|
+
}
|
|
35
|
+
function apiPrefix() {
|
|
36
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
37
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
38
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
39
|
+
return trimmed || "/api/v1";
|
|
40
|
+
}
|
|
41
|
+
function sdkClientClassName() {
|
|
42
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
43
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
44
|
+
return override;
|
|
45
|
+
}
|
|
46
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
47
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
48
|
+
}
|
|
29
49
|
|
|
30
50
|
// ../../src/core/audit/siemFormatter.ts
|
|
31
51
|
function formatSiemAuditEvent(input) {
|
|
@@ -26,6 +26,26 @@ function webhookSignatureHeader() {
|
|
|
26
26
|
function appDisplayName() {
|
|
27
27
|
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
28
28
|
}
|
|
29
|
+
function appEnv() {
|
|
30
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
31
|
+
}
|
|
32
|
+
function appUrl() {
|
|
33
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
34
|
+
}
|
|
35
|
+
function apiPrefix() {
|
|
36
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
37
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
38
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
39
|
+
return trimmed || "/api/v1";
|
|
40
|
+
}
|
|
41
|
+
function sdkClientClassName() {
|
|
42
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
43
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
44
|
+
return override;
|
|
45
|
+
}
|
|
46
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
47
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
48
|
+
}
|
|
29
49
|
|
|
30
50
|
// ../../src/core/auth/oauth/providers.ts
|
|
31
51
|
class GitHubOAuthProvider {
|
|
@@ -26,6 +26,26 @@ function webhookSignatureHeader() {
|
|
|
26
26
|
function appDisplayName() {
|
|
27
27
|
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
28
28
|
}
|
|
29
|
+
function appEnv() {
|
|
30
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
31
|
+
}
|
|
32
|
+
function appUrl() {
|
|
33
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
34
|
+
}
|
|
35
|
+
function apiPrefix() {
|
|
36
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
37
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
38
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
39
|
+
return trimmed || "/api/v1";
|
|
40
|
+
}
|
|
41
|
+
function sdkClientClassName() {
|
|
42
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
43
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
44
|
+
return override;
|
|
45
|
+
}
|
|
46
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
47
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
48
|
+
}
|
|
29
49
|
|
|
30
50
|
// ../../src/core/auth/oauth/samlProvider.ts
|
|
31
51
|
class SamlProvider {
|
|
@@ -29,6 +29,26 @@ function webhookSignatureHeader() {
|
|
|
29
29
|
function appDisplayName() {
|
|
30
30
|
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
31
31
|
}
|
|
32
|
+
function appEnv() {
|
|
33
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
34
|
+
}
|
|
35
|
+
function appUrl() {
|
|
36
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
37
|
+
}
|
|
38
|
+
function apiPrefix() {
|
|
39
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
40
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
41
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
42
|
+
return trimmed || "/api/v1";
|
|
43
|
+
}
|
|
44
|
+
function sdkClientClassName() {
|
|
45
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
46
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
47
|
+
return override;
|
|
48
|
+
}
|
|
49
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
50
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
51
|
+
}
|
|
32
52
|
|
|
33
53
|
// ../../src/core/cache/redisCacheStore.ts
|
|
34
54
|
function cacheKeyPrefix() {
|
package/dist/entries/facades.js
CHANGED
|
@@ -38,6 +38,26 @@ function webhookSignatureHeader() {
|
|
|
38
38
|
function appDisplayName() {
|
|
39
39
|
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
40
40
|
}
|
|
41
|
+
function appEnv() {
|
|
42
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
43
|
+
}
|
|
44
|
+
function appUrl() {
|
|
45
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
46
|
+
}
|
|
47
|
+
function apiPrefix() {
|
|
48
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
49
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
50
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
51
|
+
return trimmed || "/api/v1";
|
|
52
|
+
}
|
|
53
|
+
function sdkClientClassName() {
|
|
54
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
55
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
56
|
+
return override;
|
|
57
|
+
}
|
|
58
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
59
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
60
|
+
}
|
|
41
61
|
|
|
42
62
|
// ../../src/core/mail/mailer.ts
|
|
43
63
|
function resolveSmtpConfig() {
|
|
@@ -29,6 +29,26 @@ function webhookSignatureHeader() {
|
|
|
29
29
|
function appDisplayName() {
|
|
30
30
|
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
31
31
|
}
|
|
32
|
+
function appEnv() {
|
|
33
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
34
|
+
}
|
|
35
|
+
function appUrl() {
|
|
36
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
37
|
+
}
|
|
38
|
+
function apiPrefix() {
|
|
39
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
40
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
41
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
42
|
+
return trimmed || "/api/v1";
|
|
43
|
+
}
|
|
44
|
+
function sdkClientClassName() {
|
|
45
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
46
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
47
|
+
return override;
|
|
48
|
+
}
|
|
49
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
50
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
51
|
+
}
|
|
32
52
|
|
|
33
53
|
// ../../src/core/http/clientIp.ts
|
|
34
54
|
function trustForwardedFor(env = process.env) {
|
|
@@ -26,6 +26,26 @@ function webhookSignatureHeader() {
|
|
|
26
26
|
function appDisplayName() {
|
|
27
27
|
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
28
28
|
}
|
|
29
|
+
function appEnv() {
|
|
30
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
31
|
+
}
|
|
32
|
+
function appUrl() {
|
|
33
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
34
|
+
}
|
|
35
|
+
function apiPrefix() {
|
|
36
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
37
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
38
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
39
|
+
return trimmed || "/api/v1";
|
|
40
|
+
}
|
|
41
|
+
function sdkClientClassName() {
|
|
42
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
43
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
44
|
+
return override;
|
|
45
|
+
}
|
|
46
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
47
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
48
|
+
}
|
|
29
49
|
|
|
30
50
|
// ../../src/core/http/clientIp.ts
|
|
31
51
|
function trustForwardedFor(env = process.env) {
|
|
@@ -29,6 +29,26 @@ function webhookSignatureHeader() {
|
|
|
29
29
|
function appDisplayName() {
|
|
30
30
|
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
31
31
|
}
|
|
32
|
+
function appEnv() {
|
|
33
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
34
|
+
}
|
|
35
|
+
function appUrl() {
|
|
36
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
37
|
+
}
|
|
38
|
+
function apiPrefix() {
|
|
39
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
40
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
41
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
42
|
+
return trimmed || "/api/v1";
|
|
43
|
+
}
|
|
44
|
+
function sdkClientClassName() {
|
|
45
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
46
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
47
|
+
return override;
|
|
48
|
+
}
|
|
49
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
50
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
51
|
+
}
|
|
32
52
|
|
|
33
53
|
// ../../src/core/http/clientIp.ts
|
|
34
54
|
function trustForwardedFor(env = process.env) {
|
|
@@ -7,14 +7,53 @@ import {
|
|
|
7
7
|
} from "@getstrata/core/http/contentSecurityPolicy";
|
|
8
8
|
import { currentRequestMeta, runWithRequestMeta } from "@getstrata/core/http/requestMetaContext";
|
|
9
9
|
|
|
10
|
-
// ../../src/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
// ../../src/core/runtime/appKeyPrefix.ts
|
|
11
|
+
function appKeyPrefix() {
|
|
12
|
+
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
13
|
+
}
|
|
14
|
+
function namespacedRedisKey(kind) {
|
|
15
|
+
return `${appKeyPrefix()}:${kind}`;
|
|
16
|
+
}
|
|
17
|
+
function smtpEhloHost() {
|
|
18
|
+
const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
|
|
19
|
+
const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
|
|
20
|
+
return safe || "strata.local";
|
|
21
|
+
}
|
|
22
|
+
function siemEventType() {
|
|
23
|
+
return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
|
|
24
|
+
}
|
|
25
|
+
function appUserAgent() {
|
|
26
|
+
return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
|
|
27
|
+
}
|
|
28
|
+
function otelServiceName() {
|
|
29
|
+
return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
|
|
30
|
+
}
|
|
31
|
+
function webhookSignatureHeader() {
|
|
32
|
+
return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
|
|
33
|
+
}
|
|
34
|
+
function appDisplayName() {
|
|
35
|
+
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
36
|
+
}
|
|
37
|
+
function appEnv() {
|
|
38
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
39
|
+
}
|
|
40
|
+
function appUrl() {
|
|
41
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
42
|
+
}
|
|
43
|
+
function apiPrefix() {
|
|
44
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
45
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
46
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
47
|
+
return trimmed || "/api/v1";
|
|
48
|
+
}
|
|
49
|
+
function sdkClientClassName() {
|
|
50
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
51
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
52
|
+
return override;
|
|
53
|
+
}
|
|
54
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
55
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
56
|
+
}
|
|
18
57
|
|
|
19
58
|
// ../../src/core/http/securityHeadersMiddleware.ts
|
|
20
59
|
function createSecurityHeadersMiddleware(options = {}) {
|
|
@@ -33,7 +72,7 @@ function createSecurityHeadersMiddleware(options = {}) {
|
|
|
33
72
|
headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
34
73
|
headers.set("X-XSS-Protection", "0");
|
|
35
74
|
headers.set("Content-Security-Policy", resolveContentSecurityPolicy(response, { ...options, nonce }));
|
|
36
|
-
if (
|
|
75
|
+
if (appEnv() === "production") {
|
|
37
76
|
headers.set("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
|
|
38
77
|
}
|
|
39
78
|
return new Response(response.body, {
|
|
@@ -88,9 +88,16 @@ function assertValidSignature(input) {
|
|
|
88
88
|
throw new ForbiddenError("Invalid or expired signed URL.");
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
|
+
function createValidateSignatureMiddleware() {
|
|
92
|
+
return async (request, next) => {
|
|
93
|
+
assertValidSignature(request);
|
|
94
|
+
return await next();
|
|
95
|
+
};
|
|
96
|
+
}
|
|
91
97
|
export {
|
|
92
98
|
absoluteTemporarySignedUrl,
|
|
93
99
|
assertValidSignature,
|
|
100
|
+
createValidateSignatureMiddleware,
|
|
94
101
|
hasValidSignature,
|
|
95
102
|
signedUrl,
|
|
96
103
|
temporarySignedUrl
|
|
@@ -31,6 +31,26 @@ function webhookSignatureHeader() {
|
|
|
31
31
|
function appDisplayName() {
|
|
32
32
|
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
33
33
|
}
|
|
34
|
+
function appEnv() {
|
|
35
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
36
|
+
}
|
|
37
|
+
function appUrl() {
|
|
38
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
39
|
+
}
|
|
40
|
+
function apiPrefix() {
|
|
41
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
42
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
43
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
44
|
+
return trimmed || "/api/v1";
|
|
45
|
+
}
|
|
46
|
+
function sdkClientClassName() {
|
|
47
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
48
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
49
|
+
return override;
|
|
50
|
+
}
|
|
51
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
52
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
53
|
+
}
|
|
34
54
|
|
|
35
55
|
// ../../src/core/http/clientIp.ts
|
|
36
56
|
function trustForwardedFor(env = process.env) {
|
|
@@ -2,14 +2,53 @@
|
|
|
2
2
|
// ../../src/core/audit/exportAuditLogs.ts
|
|
3
3
|
import { repositoryConnection as db2 } from "@getstrata/core/database/repositoryConnection";
|
|
4
4
|
|
|
5
|
-
// ../../src/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
+
function appEnv() {
|
|
33
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
34
|
+
}
|
|
35
|
+
function appUrl() {
|
|
36
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
37
|
+
}
|
|
38
|
+
function apiPrefix() {
|
|
39
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
40
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
41
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
42
|
+
return trimmed || "/api/v1";
|
|
43
|
+
}
|
|
44
|
+
function sdkClientClassName() {
|
|
45
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
46
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
47
|
+
return override;
|
|
48
|
+
}
|
|
49
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
50
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
51
|
+
}
|
|
13
52
|
|
|
14
53
|
// ../../src/core/security/safeUrl.ts
|
|
15
54
|
import { lookup as dnsLookupImpl } from "dns/promises";
|
|
@@ -113,7 +152,7 @@ var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
|
|
|
113
152
|
async function safeFetch(input, init = {}, options = {}) {
|
|
114
153
|
const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
|
|
115
154
|
const maxRedirects = options.maxRedirects ?? 0;
|
|
116
|
-
const resolveDns = options.resolveDns ??
|
|
155
|
+
const resolveDns = options.resolveDns ?? appEnv() === "production";
|
|
117
156
|
const urlOptions = { allowHttp: options.allowHttp, resolveDns };
|
|
118
157
|
const controller = new AbortController;
|
|
119
158
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -153,34 +192,6 @@ async function runWithMigrationBypass(callback) {
|
|
|
153
192
|
}
|
|
154
193
|
}
|
|
155
194
|
|
|
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
|
-
|
|
184
195
|
// ../../src/core/audit/siemFormatter.ts
|
|
185
196
|
function formatSiemAuditEvent(input) {
|
|
186
197
|
return {
|
|
@@ -220,7 +231,7 @@ function resolveAuditExportConfig() {
|
|
|
220
231
|
if (!endpoint) {
|
|
221
232
|
return null;
|
|
222
233
|
}
|
|
223
|
-
assertSafeOutboundUrl(endpoint, { allowHttp:
|
|
234
|
+
assertSafeOutboundUrl(endpoint, { allowHttp: appEnv() !== "production" });
|
|
224
235
|
const batchSize = Number(process.env.SIEM_EXPORT_BATCH_SIZE ?? "100");
|
|
225
236
|
return {
|
|
226
237
|
endpoint,
|
|
@@ -278,7 +289,7 @@ async function exportPendingAuditLogs() {
|
|
|
278
289
|
...process.env.SIEM_EXPORT_TOKEN ? { authorization: `Bearer ${process.env.SIEM_EXPORT_TOKEN}` } : {}
|
|
279
290
|
},
|
|
280
291
|
body
|
|
281
|
-
}, { allowHttp:
|
|
292
|
+
}, { allowHttp: appEnv() !== "production" });
|
|
282
293
|
if (!response.ok) {
|
|
283
294
|
throw new Error(`SIEM export failed with status ${response.status}.`);
|
|
284
295
|
}
|
|
@@ -26,6 +26,26 @@ function webhookSignatureHeader() {
|
|
|
26
26
|
function appDisplayName() {
|
|
27
27
|
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
28
28
|
}
|
|
29
|
+
function appEnv() {
|
|
30
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
31
|
+
}
|
|
32
|
+
function appUrl() {
|
|
33
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
34
|
+
}
|
|
35
|
+
function apiPrefix() {
|
|
36
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
37
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
38
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
39
|
+
return trimmed || "/api/v1";
|
|
40
|
+
}
|
|
41
|
+
function sdkClientClassName() {
|
|
42
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
43
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
44
|
+
return override;
|
|
45
|
+
}
|
|
46
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
47
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
48
|
+
}
|
|
29
49
|
|
|
30
50
|
// ../../src/core/mail/mailer.ts
|
|
31
51
|
function resolveSmtpConfig() {
|
|
@@ -1,12 +1,51 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
// ../../src/
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
+
function appEnv() {
|
|
30
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
31
|
+
}
|
|
32
|
+
function appUrl() {
|
|
33
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
34
|
+
}
|
|
35
|
+
function apiPrefix() {
|
|
36
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
37
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
38
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
39
|
+
return trimmed || "/api/v1";
|
|
40
|
+
}
|
|
41
|
+
function sdkClientClassName() {
|
|
42
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
43
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
44
|
+
return override;
|
|
45
|
+
}
|
|
46
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
47
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
48
|
+
}
|
|
10
49
|
|
|
11
50
|
// ../../src/core/openapi/generator.ts
|
|
12
51
|
var PUBLIC_ROUTE_DESCRIPTIONS = {
|
|
@@ -84,12 +123,12 @@ function generateOpenApiSpec(routes) {
|
|
|
84
123
|
return {
|
|
85
124
|
openapi: "3.1.0",
|
|
86
125
|
info: {
|
|
87
|
-
title:
|
|
126
|
+
title: `${appDisplayName()} API`,
|
|
88
127
|
version: "1.0.0"
|
|
89
128
|
},
|
|
90
129
|
servers: [
|
|
91
|
-
{ url: `${
|
|
92
|
-
{ url:
|
|
130
|
+
{ url: `${appUrl()}${apiPrefix()}`, description: `${appDisplayName()} API` },
|
|
131
|
+
{ url: appUrl(), description: "Root (health, metrics, SCIM)" }
|
|
93
132
|
],
|
|
94
133
|
paths,
|
|
95
134
|
components: {
|
|
@@ -142,17 +181,17 @@ function renderOpenApiDocument(spec) {
|
|
|
142
181
|
return `${JSON.stringify(spec, null, 2)}
|
|
143
182
|
`;
|
|
144
183
|
}
|
|
145
|
-
function toMethodName(method, path,
|
|
146
|
-
const relativePath = path.startsWith(
|
|
184
|
+
function toMethodName(method, path, apiPrefix2) {
|
|
185
|
+
const relativePath = path.startsWith(apiPrefix2) ? path.slice(apiPrefix2.length) || "/" : path;
|
|
147
186
|
const segments = relativePath.replace(/\{|\}/g, "").split("/").filter(Boolean).flatMap((segment) => segment.split("-")).map((segment) => segment.replace(/[^a-zA-Z0-9]/g, "")).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1));
|
|
148
187
|
return `${method.toLowerCase()}${segments.join("")}`;
|
|
149
188
|
}
|
|
150
|
-
function toRequestPath(path,
|
|
151
|
-
return path.startsWith(
|
|
189
|
+
function toRequestPath(path, apiPrefix2) {
|
|
190
|
+
return path.startsWith(apiPrefix2) ? path.slice(apiPrefix2.length) || "/" : path;
|
|
152
191
|
}
|
|
153
|
-
function renderTypeScriptSdk(spec,
|
|
192
|
+
function renderTypeScriptSdk(spec, prefix = apiPrefix()) {
|
|
154
193
|
const lines = [
|
|
155
|
-
|
|
194
|
+
`export class ${sdkClientClassName()} {`,
|
|
156
195
|
` constructor(private readonly baseUrl = "${spec.servers[0]?.url ?? ""}") {}`,
|
|
157
196
|
"",
|
|
158
197
|
" private async request(path: string, init: RequestInit = {}): Promise<Response> {",
|
|
@@ -161,9 +200,9 @@ function renderTypeScriptSdk(spec, apiPrefix = "/api/v1") {
|
|
|
161
200
|
""
|
|
162
201
|
];
|
|
163
202
|
for (const [path, methods] of Object.entries(spec.paths)) {
|
|
164
|
-
const requestPath = toRequestPath(path,
|
|
203
|
+
const requestPath = toRequestPath(path, prefix);
|
|
165
204
|
for (const method of Object.keys(methods)) {
|
|
166
|
-
const functionName = toMethodName(method, path,
|
|
205
|
+
const functionName = toMethodName(method, path, prefix);
|
|
167
206
|
lines.push(` async ${functionName}(init: RequestInit = {}): Promise<Response> {`, ` return await this.request("${requestPath}", { ...init, method: "${method.toUpperCase()}" });`, " }", "");
|
|
168
207
|
}
|
|
169
208
|
}
|
|
@@ -181,6 +181,26 @@ function webhookSignatureHeader() {
|
|
|
181
181
|
function appDisplayName() {
|
|
182
182
|
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
183
183
|
}
|
|
184
|
+
function appEnv() {
|
|
185
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
186
|
+
}
|
|
187
|
+
function appUrl() {
|
|
188
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
189
|
+
}
|
|
190
|
+
function apiPrefix() {
|
|
191
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
192
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
193
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
194
|
+
return trimmed || "/api/v1";
|
|
195
|
+
}
|
|
196
|
+
function sdkClientClassName() {
|
|
197
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
198
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
199
|
+
return override;
|
|
200
|
+
}
|
|
201
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
202
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
203
|
+
}
|
|
184
204
|
|
|
185
205
|
// ../../src/core/queue/redisQueue.ts
|
|
186
206
|
function queueListKey() {
|
|
@@ -181,6 +181,26 @@ function webhookSignatureHeader() {
|
|
|
181
181
|
function appDisplayName() {
|
|
182
182
|
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
183
183
|
}
|
|
184
|
+
function appEnv() {
|
|
185
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
186
|
+
}
|
|
187
|
+
function appUrl() {
|
|
188
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
189
|
+
}
|
|
190
|
+
function apiPrefix() {
|
|
191
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
192
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
193
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
194
|
+
return trimmed || "/api/v1";
|
|
195
|
+
}
|
|
196
|
+
function sdkClientClassName() {
|
|
197
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
198
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
199
|
+
return override;
|
|
200
|
+
}
|
|
201
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
202
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
203
|
+
}
|
|
184
204
|
|
|
185
205
|
// ../../src/core/queue/redisQueue.ts
|
|
186
206
|
function queueListKey() {
|
|
@@ -184,6 +184,26 @@ function webhookSignatureHeader() {
|
|
|
184
184
|
function appDisplayName() {
|
|
185
185
|
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
186
186
|
}
|
|
187
|
+
function appEnv() {
|
|
188
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
189
|
+
}
|
|
190
|
+
function appUrl() {
|
|
191
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
192
|
+
}
|
|
193
|
+
function apiPrefix() {
|
|
194
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
195
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
196
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
197
|
+
return trimmed || "/api/v1";
|
|
198
|
+
}
|
|
199
|
+
function sdkClientClassName() {
|
|
200
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
201
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
202
|
+
return override;
|
|
203
|
+
}
|
|
204
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
205
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
206
|
+
}
|
|
187
207
|
|
|
188
208
|
// ../../src/core/queue/redisQueue.ts
|
|
189
209
|
function queueListKey() {
|
|
@@ -29,6 +29,26 @@ function webhookSignatureHeader() {
|
|
|
29
29
|
function appDisplayName() {
|
|
30
30
|
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
31
31
|
}
|
|
32
|
+
function appEnv() {
|
|
33
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
34
|
+
}
|
|
35
|
+
function appUrl() {
|
|
36
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
37
|
+
}
|
|
38
|
+
function apiPrefix() {
|
|
39
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
40
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
41
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
42
|
+
return trimmed || "/api/v1";
|
|
43
|
+
}
|
|
44
|
+
function sdkClientClassName() {
|
|
45
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
46
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
47
|
+
return override;
|
|
48
|
+
}
|
|
49
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
50
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
51
|
+
}
|
|
32
52
|
|
|
33
53
|
// ../../src/core/queue/jobRegistry.ts
|
|
34
54
|
class JobRegistry {
|
|
@@ -26,12 +26,36 @@ function webhookSignatureHeader() {
|
|
|
26
26
|
function appDisplayName() {
|
|
27
27
|
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
28
28
|
}
|
|
29
|
+
function appEnv() {
|
|
30
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
31
|
+
}
|
|
32
|
+
function appUrl() {
|
|
33
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
34
|
+
}
|
|
35
|
+
function apiPrefix() {
|
|
36
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
37
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
38
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
39
|
+
return trimmed || "/api/v1";
|
|
40
|
+
}
|
|
41
|
+
function sdkClientClassName() {
|
|
42
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
43
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
44
|
+
return override;
|
|
45
|
+
}
|
|
46
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
47
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
48
|
+
}
|
|
29
49
|
export {
|
|
50
|
+
apiPrefix,
|
|
30
51
|
appDisplayName,
|
|
52
|
+
appEnv,
|
|
31
53
|
appKeyPrefix,
|
|
54
|
+
appUrl,
|
|
32
55
|
appUserAgent,
|
|
33
56
|
namespacedRedisKey,
|
|
34
57
|
otelServiceName,
|
|
58
|
+
sdkClientClassName,
|
|
35
59
|
siemEventType,
|
|
36
60
|
smtpEhloHost,
|
|
37
61
|
webhookSignatureHeader
|
|
@@ -1,12 +1,51 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
// ../../src/
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
+
function appEnv() {
|
|
30
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
31
|
+
}
|
|
32
|
+
function appUrl() {
|
|
33
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
34
|
+
}
|
|
35
|
+
function apiPrefix() {
|
|
36
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
37
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
38
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
39
|
+
return trimmed || "/api/v1";
|
|
40
|
+
}
|
|
41
|
+
function sdkClientClassName() {
|
|
42
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
43
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
44
|
+
return override;
|
|
45
|
+
}
|
|
46
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
47
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
48
|
+
}
|
|
10
49
|
|
|
11
50
|
// ../../src/core/security/safeUrl.ts
|
|
12
51
|
import { lookup as dnsLookupImpl } from "dns/promises";
|
|
@@ -110,7 +149,7 @@ var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
|
|
|
110
149
|
async function safeFetch(input, init = {}, options = {}) {
|
|
111
150
|
const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
|
|
112
151
|
const maxRedirects = options.maxRedirects ?? 0;
|
|
113
|
-
const resolveDns = options.resolveDns ??
|
|
152
|
+
const resolveDns = options.resolveDns ?? appEnv() === "production";
|
|
114
153
|
const urlOptions = { allowHttp: options.allowHttp, resolveDns };
|
|
115
154
|
const controller = new AbortController;
|
|
116
155
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -29,6 +29,26 @@ function webhookSignatureHeader() {
|
|
|
29
29
|
function appDisplayName() {
|
|
30
30
|
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
31
31
|
}
|
|
32
|
+
function appEnv() {
|
|
33
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
34
|
+
}
|
|
35
|
+
function appUrl() {
|
|
36
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
37
|
+
}
|
|
38
|
+
function apiPrefix() {
|
|
39
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
40
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
41
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
42
|
+
return trimmed || "/api/v1";
|
|
43
|
+
}
|
|
44
|
+
function sdkClientClassName() {
|
|
45
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
46
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
47
|
+
return override;
|
|
48
|
+
}
|
|
49
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
50
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
51
|
+
}
|
|
32
52
|
|
|
33
53
|
// ../../src/core/tracing/otel.ts
|
|
34
54
|
function randomHex(bytes) {
|
|
@@ -83,7 +83,7 @@ export type { RouteRequest } from "../core/http/route.ts";
|
|
|
83
83
|
export { loginRedirectLocation, safeInternalRedirectPath, sanitizeInternalPath, } from "../core/http/safeInternalPath.ts";
|
|
84
84
|
export { createScimThrottleMiddleware } from "../core/http/scimThrottleMiddleware.ts";
|
|
85
85
|
export { createSecurityHeadersMiddleware } from "../core/http/securityHeadersMiddleware.ts";
|
|
86
|
-
export { absoluteTemporarySignedUrl, assertValidSignature, hasValidSignature, signedUrl, temporarySignedUrl, } from "../core/http/signedUrl.ts";
|
|
86
|
+
export { absoluteTemporarySignedUrl, assertValidSignature, createValidateSignatureMiddleware, hasValidSignature, signedUrl, temporarySignedUrl, } from "../core/http/signedUrl.ts";
|
|
87
87
|
export { createThrottleMiddleware } from "../core/http/throttleMiddleware.ts";
|
|
88
88
|
export { WebFormRequest } from "../core/http/webFormRequest.ts";
|
|
89
89
|
export { installGracefulShutdownSignals, registerShutdownHandler, runGracefulShutdown, } from "../core/lifecycle/gracefulShutdown.ts";
|
package/dist/index.js
CHANGED
|
@@ -982,6 +982,9 @@ function smtpEhloHost() {
|
|
|
982
982
|
function otelServiceName() {
|
|
983
983
|
return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
|
|
984
984
|
}
|
|
985
|
+
function appEnv() {
|
|
986
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
987
|
+
}
|
|
985
988
|
|
|
986
989
|
// ../../src/core/cache/redisCacheStore.ts
|
|
987
990
|
function cacheKeyPrefix() {
|
|
@@ -5666,15 +5669,6 @@ function createScimThrottleMiddleware(options) {
|
|
|
5666
5669
|
return await next();
|
|
5667
5670
|
};
|
|
5668
5671
|
}
|
|
5669
|
-
// ../../src/config/app.ts
|
|
5670
|
-
var appConfig = {
|
|
5671
|
-
name: process.env.APP_NAME?.trim() || "WorkHub",
|
|
5672
|
-
env: process.env.APP_ENV ?? "local",
|
|
5673
|
-
debug: (process.env.APP_DEBUG ?? "true") !== "false",
|
|
5674
|
-
url: process.env.APP_URL ?? "http://localhost:3000",
|
|
5675
|
-
apiPrefix: process.env.API_PREFIX ?? "/api/v1"
|
|
5676
|
-
};
|
|
5677
|
-
|
|
5678
5672
|
// ../../src/core/http/securityHeadersMiddleware.ts
|
|
5679
5673
|
function createSecurityHeadersMiddleware(options = {}) {
|
|
5680
5674
|
return async (request, next) => {
|
|
@@ -5692,7 +5686,7 @@ function createSecurityHeadersMiddleware(options = {}) {
|
|
|
5692
5686
|
headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
5693
5687
|
headers.set("X-XSS-Protection", "0");
|
|
5694
5688
|
headers.set("Content-Security-Policy", resolveContentSecurityPolicy(response, { ...options, nonce }));
|
|
5695
|
-
if (
|
|
5689
|
+
if (appEnv() === "production") {
|
|
5696
5690
|
headers.set("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
|
|
5697
5691
|
}
|
|
5698
5692
|
return new Response(response.body, {
|
|
@@ -5790,6 +5784,12 @@ function assertValidSignature(input) {
|
|
|
5790
5784
|
throw new ForbiddenError("Invalid or expired signed URL.");
|
|
5791
5785
|
}
|
|
5792
5786
|
}
|
|
5787
|
+
function createValidateSignatureMiddleware() {
|
|
5788
|
+
return async (request, next) => {
|
|
5789
|
+
assertValidSignature(request);
|
|
5790
|
+
return await next();
|
|
5791
|
+
};
|
|
5792
|
+
}
|
|
5793
5793
|
// ../../src/core/http/throttleMiddleware.ts
|
|
5794
5794
|
var {RedisClient: RedisClient4 } = globalThis.Bun;
|
|
5795
5795
|
function resolveThrottleIdentity(request) {
|
|
@@ -7223,6 +7223,7 @@ export {
|
|
|
7223
7223
|
createThrottleMiddleware,
|
|
7224
7224
|
createTracingMiddleware,
|
|
7225
7225
|
createTrackedJob,
|
|
7226
|
+
createValidateSignatureMiddleware,
|
|
7226
7227
|
createdResponse,
|
|
7227
7228
|
currentAuthUser,
|
|
7228
7229
|
currentOrgRole,
|
package/package.json
CHANGED