@getstrata/core 0.5.66 → 0.5.68
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/http/corsMiddleware.d.ts +8 -1
- package/dist/core/openapi/generator.d.ts +2 -2
- package/dist/{config/queue.d.ts → core/queue/queueConfig.d.ts} +2 -1
- 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/corsMiddleware.js +20 -18
- package/dist/entries/http/loginThrottleMiddleware.js +20 -0
- package/dist/entries/http/memoryThrottleMiddleware.js +20 -0
- package/dist/entries/http/parseMultipartUpload.js +1 -1
- package/dist/entries/http/requireAbilityMiddleware.js +4 -1
- package/dist/entries/http/response.js +4 -1
- package/dist/entries/http/scimThrottleMiddleware.js +20 -0
- package/dist/entries/http/securityHeadersMiddleware.js +48 -9
- package/dist/entries/http/throttleMiddleware.js +20 -0
- package/dist/entries/http/webErrorResponse.js +18 -15
- 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 +40 -18
- package/dist/entries/queue/jobRunner.js +23 -21
- package/dist/entries/queue/publicQueue.js +40 -18
- package/dist/entries/queue/queueMetrics.js +43 -21
- package/dist/entries/queue/redisQueue.js +40 -18
- package/dist/entries/runtime/appKeyPrefix.js +24 -0
- package/dist/entries/runtime/frontendMode.js +23 -0
- package/dist/entries/security/publicReads.js +1 -22
- package/dist/entries/security/safeFetch.js +48 -9
- package/dist/entries/tracing/tracingMiddleware.js +20 -0
- package/dist/index.js +45 -57
- package/package.json +7 -2
- package/dist/bootstrap/config.d.ts +0 -15
- package/dist/config/app.d.ts +0 -10
- package/dist/config/cors.d.ts +0 -9
- package/dist/config/features.d.ts +0 -18
- /package/dist/{config → core/http}/uploads.d.ts +0 -0
- /package/dist/{config/frontend.d.ts → core/runtime/frontendMode.d.ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @getstrata/core changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.68
|
|
4
|
+
|
|
5
|
+
- Published core no longer imports WorkHub `src/config`. Frontend mode, queue retries, CORS, and upload limits read env (`FRONTEND_MODE`, `QUEUE_*`, `CORS_ALLOWED_ORIGINS`, `MAX_UPLOAD_BYTES`).
|
|
6
|
+
- `@getstrata/core/runtime/frontendMode` exports `readFrontendMode` / `isViewsEnabled` / `isSpaEnabled`.
|
|
7
|
+
|
|
8
|
+
## 0.5.67
|
|
9
|
+
|
|
10
|
+
- 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`.
|
|
11
|
+
- `appEnv()`, `appUrl()`, `apiPrefix()`, and `sdkClientClassName()` live on `@getstrata/core/runtime/appKeyPrefix`. SIEM export, HSTS, and `safeFetch` DNS resolve use `appEnv()` instead of WorkHub `appConfig`.
|
|
12
|
+
|
|
3
13
|
## 0.5.66
|
|
4
14
|
|
|
5
15
|
- `createValidateSignatureMiddleware()` on `@getstrata/core/http/signedUrl` is Laravel’s `signed` / `ValidateSignature` middleware. Invalid or expired links throw `ForbiddenError`.
|
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.70 && git push origin v0.5.70`
|
|
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,10 @@
|
|
|
1
1
|
import type { Middleware } from "./middleware";
|
|
2
|
+
interface CorsConfig {
|
|
3
|
+
allowedOrigins: string[];
|
|
4
|
+
allowedMethods: string[];
|
|
5
|
+
allowedHeaders: string[];
|
|
6
|
+
maxAgeSeconds: number;
|
|
7
|
+
}
|
|
8
|
+
declare function resolveCorsConfig(): CorsConfig;
|
|
2
9
|
declare function createCorsMiddleware(): Middleware;
|
|
3
|
-
export { createCorsMiddleware };
|
|
10
|
+
export { createCorsMiddleware, resolveCorsConfig };
|
|
@@ -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 };
|
|
@@ -3,6 +3,7 @@ interface QueueConfig {
|
|
|
3
3
|
maxAttempts: number;
|
|
4
4
|
backoffMs: number;
|
|
5
5
|
}
|
|
6
|
+
declare function resolveQueueConfig(): QueueConfig;
|
|
6
7
|
declare const queueConfig: QueueConfig;
|
|
7
8
|
export type { QueueConfig };
|
|
8
|
-
export { queueConfig };
|
|
9
|
+
export { queueConfig, resolveQueueConfig };
|
|
@@ -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() {
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
// ../../src/config/cors.ts
|
|
3
|
-
var corsConfig = {
|
|
4
|
-
allowedOrigins: (process.env.CORS_ALLOWED_ORIGINS ?? "*").split(",").map((origin) => origin.trim()).filter(Boolean),
|
|
5
|
-
allowedMethods: ["GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"],
|
|
6
|
-
allowedHeaders: [
|
|
7
|
-
"Authorization",
|
|
8
|
-
"Content-Type",
|
|
9
|
-
"X-Request-Id",
|
|
10
|
-
"X-Tenant-Id",
|
|
11
|
-
"X-Authenticated-User-Id",
|
|
12
|
-
"X-Authenticated-User-Role",
|
|
13
|
-
"If-Match",
|
|
14
|
-
"If-None-Match"
|
|
15
|
-
],
|
|
16
|
-
maxAgeSeconds: 86400
|
|
17
|
-
};
|
|
18
|
-
|
|
19
2
|
// ../../src/core/http/corsMiddleware.ts
|
|
3
|
+
function resolveCorsConfig() {
|
|
4
|
+
return {
|
|
5
|
+
allowedOrigins: (process.env.CORS_ALLOWED_ORIGINS ?? "*").split(",").map((origin) => origin.trim()).filter(Boolean),
|
|
6
|
+
allowedMethods: ["GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"],
|
|
7
|
+
allowedHeaders: [
|
|
8
|
+
"Authorization",
|
|
9
|
+
"Content-Type",
|
|
10
|
+
"X-Request-Id",
|
|
11
|
+
"X-Tenant-Id",
|
|
12
|
+
"X-Authenticated-User-Id",
|
|
13
|
+
"X-Authenticated-User-Role",
|
|
14
|
+
"If-Match",
|
|
15
|
+
"If-None-Match"
|
|
16
|
+
],
|
|
17
|
+
maxAgeSeconds: 86400
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
20
|
function createCorsMiddleware() {
|
|
21
21
|
return async (request, next) => {
|
|
22
22
|
if (request.method === "OPTIONS") {
|
|
@@ -40,6 +40,7 @@ function createCorsMiddleware() {
|
|
|
40
40
|
function buildCorsHeaders(request) {
|
|
41
41
|
const headers = new Headers;
|
|
42
42
|
const origin = request.headers.get("origin");
|
|
43
|
+
const corsConfig = resolveCorsConfig();
|
|
43
44
|
const allowedOrigins = corsConfig.allowedOrigins;
|
|
44
45
|
const allowOrigin = allowedOrigins.includes("*") || origin && allowedOrigins.includes(origin) ? origin ?? "*" : allowedOrigins[0] ?? "*";
|
|
45
46
|
headers.set("Access-Control-Allow-Origin", allowOrigin);
|
|
@@ -50,5 +51,6 @@ function buildCorsHeaders(request) {
|
|
|
50
51
|
return headers;
|
|
51
52
|
}
|
|
52
53
|
export {
|
|
53
|
-
createCorsMiddleware
|
|
54
|
+
createCorsMiddleware,
|
|
55
|
+
resolveCorsConfig
|
|
54
56
|
};
|
|
@@ -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) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// ../../src/core/http/parseMultipartUpload.ts
|
|
3
3
|
import { BadRequestError, PayloadTooLargeError } from "@getstrata/core/errors/http";
|
|
4
4
|
|
|
5
|
-
// ../../src/
|
|
5
|
+
// ../../src/core/http/uploads.ts
|
|
6
6
|
var DEFAULT_MAX_UPLOAD_BYTES = 5 * 1024 * 1024;
|
|
7
7
|
var ALLOWED_UPLOAD_MIME_TYPES = new Set([
|
|
8
8
|
"application/pdf",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { currentAuthUser } from "@getstrata/core/auth/authContext";
|
|
4
4
|
import { ForbiddenError } from "@getstrata/core/errors/http";
|
|
5
5
|
|
|
6
|
-
// ../../src/
|
|
6
|
+
// ../../src/core/runtime/frontendMode.ts
|
|
7
7
|
function readFrontendMode() {
|
|
8
8
|
const mode = (process.env.FRONTEND_MODE ?? "api").trim();
|
|
9
9
|
if (mode === "server-htmx") {
|
|
@@ -17,6 +17,9 @@ function readFrontendMode() {
|
|
|
17
17
|
function isViewsEnabled() {
|
|
18
18
|
return readFrontendMode() === "server-htmx";
|
|
19
19
|
}
|
|
20
|
+
function isSpaEnabled() {
|
|
21
|
+
return readFrontendMode() === "spa-react";
|
|
22
|
+
}
|
|
20
23
|
|
|
21
24
|
// ../../src/core/http/contentNegotiation.ts
|
|
22
25
|
function requestPrefersJson(request) {
|
|
@@ -68,7 +68,7 @@ async function withDatabaseErrorHandling(operation) {
|
|
|
68
68
|
// ../../src/core/http/webErrorResponse.ts
|
|
69
69
|
import { HttpError as HttpError2, UnauthorizedError, ValidationError } from "@getstrata/core/errors/http";
|
|
70
70
|
|
|
71
|
-
// ../../src/
|
|
71
|
+
// ../../src/core/runtime/frontendMode.ts
|
|
72
72
|
function readFrontendMode() {
|
|
73
73
|
const mode = (process.env.FRONTEND_MODE ?? "api").trim();
|
|
74
74
|
if (mode === "server-htmx") {
|
|
@@ -82,6 +82,9 @@ function readFrontendMode() {
|
|
|
82
82
|
function isViewsEnabled() {
|
|
83
83
|
return readFrontendMode() === "server-htmx";
|
|
84
84
|
}
|
|
85
|
+
function isSpaEnabled() {
|
|
86
|
+
return readFrontendMode() === "spa-react";
|
|
87
|
+
}
|
|
85
88
|
|
|
86
89
|
// ../../src/core/view/webErrorView.ts
|
|
87
90
|
import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
|
|
@@ -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, {
|
|
@@ -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) {
|