@getstrata/bootstrap 0.2.46 → 0.2.48
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/README.md +4 -0
- package/dist/_.._/_.._/index.html +174 -0
- package/dist/bootstrap/discoverModules.d.ts +2 -1
- package/dist/bootstrap/inProcessCron.d.ts +4 -0
- package/dist/bootstrap/metricsRoutes.d.ts +1 -1
- package/dist/bootstrap/public-api.d.ts +0 -1
- package/dist/entries/applicationRegistry.js +8 -8
- package/dist/entries/buildModuleRoutes.js +13 -7
- package/dist/entries/buildWebModuleRoutes.js +12 -6
- package/dist/entries/cache/modelCacheTags.js +9 -3
- package/dist/entries/config.js +19 -19
- package/dist/entries/context.js +33 -51
- package/dist/entries/contracts.js +4 -4
- package/dist/entries/createRoutes.js +50 -121
- package/dist/entries/createSpaRoutes.js +3 -3
- package/dist/entries/createWebRoutes.js +14 -8
- package/dist/entries/dependencies.js +32 -50
- package/dist/entries/discoverModules.js +10 -3
- package/dist/entries/health.js +3 -3
- package/dist/entries/http/securedRouteModelBinding.js +2 -2
- package/dist/entries/httpKernel.js +6 -6
- package/dist/entries/listeners/invalidateCacheOnModelWrite.js +7 -1
- package/dist/entries/metricsRoutes.js +31 -5
- package/dist/entries/providers/view.js +2 -2
- package/dist/entries/providers.js +7 -1
- package/dist/entries/routeRegistry.js +2 -2
- package/dist/entries/secretsGuard.js +23 -56
- package/dist/entries/web/forms.js +2 -2
- package/dist/entries/web/routing.js +10 -10
- package/dist/entries/web/server.js +2 -2
- package/dist/index.js +91 -177
- package/package.json +3 -3
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
var __jsonParse = (a) => JSON.parse(a);
|
|
3
3
|
|
|
4
4
|
// ../../src/bootstrap/createRoutes.ts
|
|
5
|
-
import { jsonResponse as jsonResponse4 } from "@getstrata/core/http";
|
|
6
5
|
import { applyMiddlewareToRoutes as applyMiddlewareToRoutes3 } from "@getstrata/core/http/middleware";
|
|
6
|
+
import { jsonResponse as jsonResponse4 } from "@getstrata/core/http/response";
|
|
7
7
|
import { htmlResponse as htmlResponse2 } from "@getstrata/core/view";
|
|
8
8
|
|
|
9
9
|
// ../../index.html
|
|
10
|
-
var strata_default = __jsonParse("{\"index\":\"
|
|
10
|
+
var strata_default = __jsonParse("{\"index\":\"_.._/_.._/index.html\",\"files\":[{\"input\":\"../../index.html\",\"path\":\"./index-sfreg6q3.js\",\"loader\":\"js\",\"isEntry\":true,\"headers\":{\"etag\":\"Oc_YLpCmV6M\",\"content-type\":\"text/javascript;charset=utf-8\"}},{\"input\":\"../../index.html\",\"path\":\"_.._/_.._/index.html\",\"loader\":\"html\",\"isEntry\":true,\"headers\":{\"etag\":\"rWY90LWIcx8\",\"content-type\":\"text/html;charset=utf-8\"}}]}");
|
|
11
11
|
|
|
12
12
|
// ../../src/config/app.ts
|
|
13
13
|
var appConfig = {
|
|
@@ -211,7 +211,7 @@ class HttpKernel {
|
|
|
211
211
|
return this.wrap(["api", "authenticated"], handler);
|
|
212
212
|
}
|
|
213
213
|
wrapWeb(handler) {
|
|
214
|
-
return handler;
|
|
214
|
+
return this.wrap("web", handler);
|
|
215
215
|
}
|
|
216
216
|
wrapWebPublicRead(handler) {
|
|
217
217
|
if (isPublicReadsEnabled()) {
|
|
@@ -221,19 +221,19 @@ class HttpKernel {
|
|
|
221
221
|
}
|
|
222
222
|
wrapWebAuthenticated(handler) {
|
|
223
223
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
224
|
-
return withMiddleware(createRequireWebAuthMiddleware(auth))(handler);
|
|
224
|
+
return this.wrap("web", withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
|
|
225
225
|
}
|
|
226
226
|
wrapWebAbility(ability, handler) {
|
|
227
227
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
228
228
|
const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
229
229
|
const requireAbility = createRequireAbilityMiddleware(abilityChecker);
|
|
230
230
|
const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
|
|
231
|
-
return withMiddleware(...middleware)(handler);
|
|
231
|
+
return this.wrap("web", withMiddleware(...middleware)(handler));
|
|
232
232
|
}
|
|
233
233
|
wrapWebGlobalAdmin(handler) {
|
|
234
234
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
235
235
|
const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
|
|
236
|
-
return withMiddleware(...middleware)(handler);
|
|
236
|
+
return this.wrap("web", withMiddleware(...middleware)(handler));
|
|
237
237
|
}
|
|
238
238
|
wrapAuthenticated(handler) {
|
|
239
239
|
return this.wrap("authenticated", handler);
|
|
@@ -332,7 +332,7 @@ function resolveModulesDirectory(options) {
|
|
|
332
332
|
if (state.configuredModulesDir) {
|
|
333
333
|
return state.configuredModulesDir;
|
|
334
334
|
}
|
|
335
|
-
|
|
335
|
+
throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
|
|
336
336
|
}
|
|
337
337
|
async function loadDiscoveredModules(options) {
|
|
338
338
|
const modulesDirectory = resolveModulesDirectory(options);
|
|
@@ -366,6 +366,12 @@ async function ensureModulesLoaded(options) {
|
|
|
366
366
|
function discoverModules() {
|
|
367
367
|
return readDiscoverModulesState().appModules;
|
|
368
368
|
}
|
|
369
|
+
function resetDiscoverModulesForTests() {
|
|
370
|
+
const state = readDiscoverModulesState();
|
|
371
|
+
state.configuredModulesDir = undefined;
|
|
372
|
+
state.appModules.length = 0;
|
|
373
|
+
state.modulesReady = undefined;
|
|
374
|
+
}
|
|
369
375
|
// ../../src/bootstrap/prefixRouteMap.ts
|
|
370
376
|
function prefixRouteMap(prefix, routes) {
|
|
371
377
|
const normalizedPrefix = prefix.replace(/\/$/, "");
|
|
@@ -449,7 +455,7 @@ function buildModuleRoutes(dependencies, options = {}) {
|
|
|
449
455
|
|
|
450
456
|
// ../../src/bootstrap/createSpaRoutes.ts
|
|
451
457
|
import { join as join2 } from "path";
|
|
452
|
-
import { jsonResponse } from "@getstrata/core/http";
|
|
458
|
+
import { jsonResponse } from "@getstrata/core/http/response";
|
|
453
459
|
var SPA_DIST_DIRECTORY = join2(process.cwd(), "frontend/dist");
|
|
454
460
|
var SPA_INDEX_FILE = join2(SPA_DIST_DIRECTORY, "index.html");
|
|
455
461
|
function createSpaRoutes(_dependencies) {
|
|
@@ -496,7 +502,7 @@ function buildWebModuleRoutes(dependencies, options = {}) {
|
|
|
496
502
|
routeRegistry.clear();
|
|
497
503
|
}
|
|
498
504
|
const kernel = createHttpKernel(dependencies);
|
|
499
|
-
const middleware =
|
|
505
|
+
const middleware = kernel.globalMiddleware();
|
|
500
506
|
const moduleRoutes = { ...seedRoutes };
|
|
501
507
|
for (const module of modules) {
|
|
502
508
|
if (!module.webRoutes) {
|
|
@@ -547,7 +553,7 @@ function mergeWebRoutes(dependencies, routes) {
|
|
|
547
553
|
}
|
|
548
554
|
|
|
549
555
|
// ../../src/bootstrap/health.ts
|
|
550
|
-
import { jsonResponse as jsonResponse2 } from "@getstrata/core/http";
|
|
556
|
+
import { jsonResponse as jsonResponse2 } from "@getstrata/core/http/response";
|
|
551
557
|
var {RedisClient } = globalThis.Bun;
|
|
552
558
|
|
|
553
559
|
// ../../src/config/database.ts
|
|
@@ -731,15 +737,41 @@ function createHealthRoutes(dependencies) {
|
|
|
731
737
|
}
|
|
732
738
|
|
|
733
739
|
// ../../src/bootstrap/metricsRoutes.ts
|
|
740
|
+
import { timingSafeEqual } from "crypto";
|
|
734
741
|
import { prometheusRegistry } from "@getstrata/core/metrics/prometheus";
|
|
742
|
+
function tokensMatch(left, right) {
|
|
743
|
+
const leftBuffer = Buffer.from(left);
|
|
744
|
+
const rightBuffer = Buffer.from(right);
|
|
745
|
+
if (leftBuffer.length !== rightBuffer.length) {
|
|
746
|
+
return false;
|
|
747
|
+
}
|
|
748
|
+
return timingSafeEqual(leftBuffer, rightBuffer);
|
|
749
|
+
}
|
|
750
|
+
function authorizeMetrics(request) {
|
|
751
|
+
const expected = process.env.METRICS_TOKEN?.trim();
|
|
752
|
+
const authorization = request.headers.get("authorization") ?? "";
|
|
753
|
+
const presented = authorization.startsWith("Bearer ") ? authorization.slice("Bearer ".length) : "";
|
|
754
|
+
if (expected) {
|
|
755
|
+
return presented.length > 0 && tokensMatch(presented, expected);
|
|
756
|
+
}
|
|
757
|
+
if ((process.env.APP_ENV ?? "local") === "production") {
|
|
758
|
+
return false;
|
|
759
|
+
}
|
|
760
|
+
return true;
|
|
761
|
+
}
|
|
735
762
|
function createMetricsRoutes() {
|
|
736
763
|
return {
|
|
737
|
-
"/metrics": async () =>
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
"content-type": "text/plain; version=0.0.4; charset=utf-8"
|
|
764
|
+
"/metrics": async (request) => {
|
|
765
|
+
if (!authorizeMetrics(request)) {
|
|
766
|
+
return new Response("Not Found", { status: 404 });
|
|
741
767
|
}
|
|
742
|
-
|
|
768
|
+
return new Response(prometheusRegistry.renderMetrics(), {
|
|
769
|
+
status: 200,
|
|
770
|
+
headers: {
|
|
771
|
+
"content-type": "text/plain; version=0.0.4; charset=utf-8"
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
}
|
|
743
775
|
};
|
|
744
776
|
}
|
|
745
777
|
|
|
@@ -749,16 +781,16 @@ import { withMiddleware as withMiddleware2 } from "@getstrata/core/http/routeMid
|
|
|
749
781
|
import { createScimThrottleMiddleware } from "@getstrata/core/http/scimThrottleMiddleware";
|
|
750
782
|
|
|
751
783
|
// ../../src/modules/scim/controller.ts
|
|
752
|
-
import { withErrorHandling } from "@getstrata/core/http";
|
|
784
|
+
import { withErrorHandling } from "@getstrata/core/http/response";
|
|
753
785
|
|
|
754
786
|
// ../../src/modules/scim/scimResponse.ts
|
|
755
|
-
import { jsonResponse as jsonResponse3 } from "@getstrata/core/http";
|
|
756
787
|
import {
|
|
757
788
|
applyConditionalGet,
|
|
758
789
|
assertIfMatch,
|
|
759
790
|
etagFromResource,
|
|
760
791
|
isEtagEnabled
|
|
761
792
|
} from "@getstrata/core/http/etag";
|
|
793
|
+
import { jsonResponse as jsonResponse3 } from "@getstrata/core/http/response";
|
|
762
794
|
function scimResponse(data, options = {}) {
|
|
763
795
|
const status = options.status ?? 200;
|
|
764
796
|
const response = jsonResponse3(data, {
|
|
@@ -797,8 +829,6 @@ import { NotFoundError as NotFoundError4 } from "@getstrata/core/errors/http";
|
|
|
797
829
|
import { currentTenantId as currentTenantId4 } from "@getstrata/core/tenant/tenantContext";
|
|
798
830
|
|
|
799
831
|
// ../../src/domain/scim.ts
|
|
800
|
-
var TEST_SCIM_BEARER_TOKEN = "workhub-scim-test-token";
|
|
801
|
-
var DEFAULT_SCIM_BEARER_TOKEN = TEST_SCIM_BEARER_TOKEN;
|
|
802
832
|
var SCIM_SCHEMAS = {
|
|
803
833
|
user: "urn:ietf:params:scim:schemas:core:2.0:User",
|
|
804
834
|
group: "urn:ietf:params:scim:schemas:core:2.0:Group",
|
|
@@ -942,7 +972,7 @@ import { resolveDefaultTokenExpiryDays } from "@getstrata/core/security/tokenExp
|
|
|
942
972
|
import { verifyTotp } from "@getstrata/core/security/totp";
|
|
943
973
|
import { currentTenantId as currentTenantId2 } from "@getstrata/core/tenant/tenantContext";
|
|
944
974
|
|
|
945
|
-
// ../../src/
|
|
975
|
+
// ../../src/core/auth/abilityCatalog.ts
|
|
946
976
|
var MEMBER_ABILITIES = [
|
|
947
977
|
"organizations:read",
|
|
948
978
|
"projects:read",
|
|
@@ -972,107 +1002,6 @@ var ADMIN_ABILITIES = [
|
|
|
972
1002
|
"webhooks:write",
|
|
973
1003
|
"audit:read"
|
|
974
1004
|
];
|
|
975
|
-
var PLATFORM_ADMIN_ABILITIES = ["*"];
|
|
976
|
-
function resolveAbilitiesForRole(role) {
|
|
977
|
-
if (role === "admin") {
|
|
978
|
-
return [...PLATFORM_ADMIN_ABILITIES];
|
|
979
|
-
}
|
|
980
|
-
return [...MEMBER_ABILITIES];
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
// ../../src/modules/user/authService.ts
|
|
984
|
-
class AuthService {
|
|
985
|
-
users;
|
|
986
|
-
tokens;
|
|
987
|
-
oauthIdentities;
|
|
988
|
-
oauthProviders = new Map;
|
|
989
|
-
constructor(users, tokens, oauthIdentities) {
|
|
990
|
-
this.users = users;
|
|
991
|
-
this.tokens = tokens;
|
|
992
|
-
this.oauthIdentities = oauthIdentities;
|
|
993
|
-
}
|
|
994
|
-
registerOAuthProvider(provider) {
|
|
995
|
-
this.oauthProviders.set(provider.name, provider);
|
|
996
|
-
}
|
|
997
|
-
getOAuthProvider(name) {
|
|
998
|
-
return this.oauthProviders.get(name);
|
|
999
|
-
}
|
|
1000
|
-
async loginWithPassword(email, password, options = {}) {
|
|
1001
|
-
const user = await this.users.findByEmail(email);
|
|
1002
|
-
if (!user?.password_hash) {
|
|
1003
|
-
logSecurityEvent("auth_login_failed", { reason: "unknown_user", email });
|
|
1004
|
-
throw new UnauthorizedError("Invalid credentials.");
|
|
1005
|
-
}
|
|
1006
|
-
const valid = await verifyPassword(password, user.password_hash);
|
|
1007
|
-
if (!valid) {
|
|
1008
|
-
logSecurityEvent("auth_login_failed", { reason: "invalid_password", user_id: user.id });
|
|
1009
|
-
throw new UnauthorizedError("Invalid credentials.");
|
|
1010
|
-
}
|
|
1011
|
-
if (isFeatureEnabled("emailVerification") && !user.email_verified_at) {
|
|
1012
|
-
logSecurityEvent("auth_login_blocked", { reason: "email_unverified", user_id: user.id });
|
|
1013
|
-
throw new UnauthorizedError("Email address is not verified.");
|
|
1014
|
-
}
|
|
1015
|
-
if (isFeatureEnabled("mfa") && user.mfa_enabled) {
|
|
1016
|
-
const mfaSecret = revealMfaSecret(user.mfa_secret);
|
|
1017
|
-
if (!mfaSecret || !options.mfaCode || !verifyTotp(mfaSecret, options.mfaCode)) {
|
|
1018
|
-
logSecurityEvent("auth_login_failed", { reason: "invalid_mfa", user_id: user.id });
|
|
1019
|
-
throw new UnauthorizedError("Invalid MFA code.");
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
logSecurityEvent("auth_login_success", { user_id: user.id, method: "password" });
|
|
1023
|
-
return await this.tokens.createToken(user.id, {
|
|
1024
|
-
name: "password-login",
|
|
1025
|
-
abilities: resolveAbilitiesForRole(user.role),
|
|
1026
|
-
expiresInDays: resolveDefaultTokenExpiryDays() ?? undefined
|
|
1027
|
-
});
|
|
1028
|
-
}
|
|
1029
|
-
async loginWithOAuth(providerName, code) {
|
|
1030
|
-
const provider = this.oauthProviders.get(providerName);
|
|
1031
|
-
if (!provider) {
|
|
1032
|
-
throw new UnauthorizedError("Unsupported OAuth provider.");
|
|
1033
|
-
}
|
|
1034
|
-
const profile = await provider.exchangeCode(code);
|
|
1035
|
-
const user = await this.findOrCreateOAuthUser(providerName, profile);
|
|
1036
|
-
logSecurityEvent("auth_login_success", { user_id: user.id, method: `oauth:${providerName}` });
|
|
1037
|
-
return await this.tokens.createToken(user.id, {
|
|
1038
|
-
name: `${providerName}-oauth`,
|
|
1039
|
-
abilities: resolveAbilitiesForRole(user.role),
|
|
1040
|
-
expiresInDays: resolveDefaultTokenExpiryDays() ?? undefined
|
|
1041
|
-
});
|
|
1042
|
-
}
|
|
1043
|
-
buildOAuthAuthorizationUrl(providerName, state) {
|
|
1044
|
-
const provider = this.oauthProviders.get(providerName);
|
|
1045
|
-
if (!provider) {
|
|
1046
|
-
throw new UnauthorizedError("Unsupported OAuth provider.");
|
|
1047
|
-
}
|
|
1048
|
-
return provider.getAuthorizationUrl(state);
|
|
1049
|
-
}
|
|
1050
|
-
async findOrCreateOAuthUser(providerName, profile) {
|
|
1051
|
-
const existingIdentity = await this.oauthIdentities.findByProviderUser(providerName, profile.providerUserId);
|
|
1052
|
-
if (existingIdentity) {
|
|
1053
|
-
return await this.users.findByIdOrThrow(existingIdentity.user_id);
|
|
1054
|
-
}
|
|
1055
|
-
const existingUser = await this.users.findByEmail(profile.email);
|
|
1056
|
-
const user = existingUser ?? await this.users.create({
|
|
1057
|
-
name: profile.name,
|
|
1058
|
-
email: profile.email,
|
|
1059
|
-
role: "member",
|
|
1060
|
-
tenant_id: currentTenantId2(),
|
|
1061
|
-
email_verified_at: new Date,
|
|
1062
|
-
created_at: new Date,
|
|
1063
|
-
updated_at: new Date
|
|
1064
|
-
});
|
|
1065
|
-
await this.oauthIdentities.create({
|
|
1066
|
-
user_id: user.id,
|
|
1067
|
-
provider: providerName,
|
|
1068
|
-
provider_user_id: profile.providerUserId,
|
|
1069
|
-
email: profile.email,
|
|
1070
|
-
created_at: new Date
|
|
1071
|
-
});
|
|
1072
|
-
return user;
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
1005
|
// ../../src/modules/user/notificationRepository.ts
|
|
1077
1006
|
import { BaseRepository as BaseRepository3 } from "@getstrata/core/database/baseRepository";
|
|
1078
1007
|
|
|
@@ -3,7 +3,7 @@ var __jsonParse = (a) => JSON.parse(a);
|
|
|
3
3
|
|
|
4
4
|
// ../../src/bootstrap/createSpaRoutes.ts
|
|
5
5
|
import { join } from "path";
|
|
6
|
-
import { jsonResponse } from "@getstrata/core/http";
|
|
6
|
+
import { jsonResponse } from "@getstrata/core/http/response";
|
|
7
7
|
|
|
8
8
|
// ../../src/config/frontend.ts
|
|
9
9
|
function readFrontendMode() {
|
|
@@ -58,7 +58,7 @@ function mergeSpaRoutes(dependencies, routes) {
|
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
export {
|
|
61
|
-
|
|
61
|
+
SPA_DIST_DIRECTORY,
|
|
62
62
|
createSpaRoutes,
|
|
63
|
-
|
|
63
|
+
mergeSpaRoutes
|
|
64
64
|
};
|
|
@@ -180,7 +180,7 @@ class HttpKernel {
|
|
|
180
180
|
return this.wrap(["api", "authenticated"], handler);
|
|
181
181
|
}
|
|
182
182
|
wrapWeb(handler) {
|
|
183
|
-
return handler;
|
|
183
|
+
return this.wrap("web", handler);
|
|
184
184
|
}
|
|
185
185
|
wrapWebPublicRead(handler) {
|
|
186
186
|
if (isPublicReadsEnabled()) {
|
|
@@ -190,19 +190,19 @@ class HttpKernel {
|
|
|
190
190
|
}
|
|
191
191
|
wrapWebAuthenticated(handler) {
|
|
192
192
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
193
|
-
return withMiddleware(createRequireWebAuthMiddleware(auth))(handler);
|
|
193
|
+
return this.wrap("web", withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
|
|
194
194
|
}
|
|
195
195
|
wrapWebAbility(ability, handler) {
|
|
196
196
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
197
197
|
const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
198
198
|
const requireAbility = createRequireAbilityMiddleware(abilityChecker);
|
|
199
199
|
const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
|
|
200
|
-
return withMiddleware(...middleware)(handler);
|
|
200
|
+
return this.wrap("web", withMiddleware(...middleware)(handler));
|
|
201
201
|
}
|
|
202
202
|
wrapWebGlobalAdmin(handler) {
|
|
203
203
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
204
204
|
const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
|
|
205
|
-
return withMiddleware(...middleware)(handler);
|
|
205
|
+
return this.wrap("web", withMiddleware(...middleware)(handler));
|
|
206
206
|
}
|
|
207
207
|
wrapAuthenticated(handler) {
|
|
208
208
|
return this.wrap("authenticated", handler);
|
|
@@ -301,7 +301,7 @@ function resolveModulesDirectory(options) {
|
|
|
301
301
|
if (state.configuredModulesDir) {
|
|
302
302
|
return state.configuredModulesDir;
|
|
303
303
|
}
|
|
304
|
-
|
|
304
|
+
throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
|
|
305
305
|
}
|
|
306
306
|
async function loadDiscoveredModules(options) {
|
|
307
307
|
const modulesDirectory = resolveModulesDirectory(options);
|
|
@@ -335,6 +335,12 @@ async function ensureModulesLoaded(options) {
|
|
|
335
335
|
function discoverModules() {
|
|
336
336
|
return readDiscoverModulesState().appModules;
|
|
337
337
|
}
|
|
338
|
+
function resetDiscoverModulesForTests() {
|
|
339
|
+
const state = readDiscoverModulesState();
|
|
340
|
+
state.configuredModulesDir = undefined;
|
|
341
|
+
state.appModules.length = 0;
|
|
342
|
+
state.modulesReady = undefined;
|
|
343
|
+
}
|
|
338
344
|
// ../../src/bootstrap/prefixRouteMap.ts
|
|
339
345
|
function prefixRouteMap(prefix, routes) {
|
|
340
346
|
const normalizedPrefix = prefix.replace(/\/$/, "");
|
|
@@ -423,7 +429,7 @@ function buildWebModuleRoutes(dependencies, options = {}) {
|
|
|
423
429
|
routeRegistry.clear();
|
|
424
430
|
}
|
|
425
431
|
const kernel = createHttpKernel(dependencies);
|
|
426
|
-
const middleware =
|
|
432
|
+
const middleware = kernel.globalMiddleware();
|
|
427
433
|
const moduleRoutes = { ...seedRoutes };
|
|
428
434
|
for (const module of modules) {
|
|
429
435
|
if (!module.webRoutes) {
|
|
@@ -473,6 +479,6 @@ function mergeWebRoutes(dependencies, routes) {
|
|
|
473
479
|
};
|
|
474
480
|
}
|
|
475
481
|
export {
|
|
476
|
-
|
|
477
|
-
|
|
482
|
+
createWebRoutes,
|
|
483
|
+
mergeWebRoutes
|
|
478
484
|
};
|
|
@@ -50,7 +50,7 @@ function resolveModulesDirectory(options) {
|
|
|
50
50
|
if (state.configuredModulesDir) {
|
|
51
51
|
return state.configuredModulesDir;
|
|
52
52
|
}
|
|
53
|
-
|
|
53
|
+
throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
|
|
54
54
|
}
|
|
55
55
|
async function loadDiscoveredModules(options) {
|
|
56
56
|
const modulesDirectory = resolveModulesDirectory(options);
|
|
@@ -84,6 +84,12 @@ async function ensureModulesLoaded(options) {
|
|
|
84
84
|
function discoverModules() {
|
|
85
85
|
return readDiscoverModulesState().appModules;
|
|
86
86
|
}
|
|
87
|
+
function resetDiscoverModulesForTests() {
|
|
88
|
+
const state = readDiscoverModulesState();
|
|
89
|
+
state.configuredModulesDir = undefined;
|
|
90
|
+
state.appModules.length = 0;
|
|
91
|
+
state.modulesReady = undefined;
|
|
92
|
+
}
|
|
87
93
|
// ../../src/bootstrap/providers/auth.ts
|
|
88
94
|
import {
|
|
89
95
|
AuthManager,
|
|
@@ -500,55 +506,28 @@ var coreProviders = [
|
|
|
500
506
|
viewProvider
|
|
501
507
|
];
|
|
502
508
|
|
|
503
|
-
// ../../src/config/features.ts
|
|
504
|
-
function readFeatureFlags() {
|
|
505
|
-
return {
|
|
506
|
-
webhooks: (process.env.FEATURE_WEBHOOKS ?? "true") !== "false",
|
|
507
|
-
fullTextSearch: (process.env.FEATURE_SEARCH ?? "true") !== "false",
|
|
508
|
-
auditLog: (process.env.FEATURE_AUDIT_LOG ?? "true") !== "false",
|
|
509
|
-
oauthLogin: (process.env.FEATURE_OAUTH ?? "true") !== "false",
|
|
510
|
-
samlLogin: (process.env.FEATURE_SAML ?? "false") === "true",
|
|
511
|
-
scim: (process.env.FEATURE_SCIM ?? "true") !== "false",
|
|
512
|
-
billing: (process.env.FEATURE_BILLING ?? "true") !== "false",
|
|
513
|
-
siemExport: (process.env.FEATURE_SIEM_EXPORT ?? "true") !== "false",
|
|
514
|
-
publicReads: (process.env.FEATURE_PUBLIC_READS ?? "true") !== "false",
|
|
515
|
-
emailVerification: (process.env.FEATURE_EMAIL_VERIFICATION ?? "false") === "true",
|
|
516
|
-
mfa: (process.env.FEATURE_MFA ?? "false") === "true"
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
var featureFlags = readFeatureFlags();
|
|
520
|
-
function isFeatureEnabled(feature) {
|
|
521
|
-
return readFeatureFlags()[feature];
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// ../../src/domain/auth.ts
|
|
525
|
-
var TEST_ADMIN_API_TOKEN = "workhub-admin-test-token";
|
|
526
|
-
var TEST_MEMBER_API_TOKEN = "workhub-member-test-token";
|
|
527
|
-
|
|
528
|
-
// ../../src/domain/scim.ts
|
|
529
|
-
var TEST_SCIM_BEARER_TOKEN = "workhub-scim-test-token";
|
|
530
|
-
var DEFAULT_SCIM_BEARER_TOKEN = TEST_SCIM_BEARER_TOKEN;
|
|
531
|
-
var SCIM_SCHEMAS = {
|
|
532
|
-
user: "urn:ietf:params:scim:schemas:core:2.0:User",
|
|
533
|
-
group: "urn:ietf:params:scim:schemas:core:2.0:Group",
|
|
534
|
-
listResponse: "urn:ietf:params:scim:api:messages:2.0:ListResponse",
|
|
535
|
-
patchOp: "urn:ietf:params:scim:api:messages:2.0:PatchOp",
|
|
536
|
-
serviceProviderConfig: "urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"
|
|
537
|
-
};
|
|
538
|
-
|
|
539
509
|
// ../../src/bootstrap/secretsGuard.ts
|
|
540
|
-
var
|
|
541
|
-
var
|
|
510
|
+
var DEFAULT_ADMIN_API_TOKEN = "workhub-admin-test-token";
|
|
511
|
+
var DEFAULT_MEMBER_API_TOKEN = "workhub-member-test-token";
|
|
512
|
+
var DEFAULT_SCIM_BEARER_TOKEN = "workhub-scim-test-token";
|
|
513
|
+
var DEFAULT_TOKENS = new Set([DEFAULT_ADMIN_API_TOKEN, DEFAULT_MEMBER_API_TOKEN]);
|
|
514
|
+
var DEFAULT_SCIM_TOKENS = new Set([DEFAULT_SCIM_BEARER_TOKEN]);
|
|
515
|
+
function isEnabled(value, defaultEnabled) {
|
|
516
|
+
if (value === undefined) {
|
|
517
|
+
return defaultEnabled;
|
|
518
|
+
}
|
|
519
|
+
return defaultEnabled ? value !== "false" : value === "true";
|
|
520
|
+
}
|
|
542
521
|
function assertProductionSecrets(env = process.env) {
|
|
543
|
-
const appEnv = env.APP_ENV ??
|
|
522
|
+
const appEnv = env.APP_ENV ?? "local";
|
|
544
523
|
if (appEnv !== "production") {
|
|
545
524
|
return;
|
|
546
525
|
}
|
|
547
|
-
const adminToken = env.ADMIN_API_TOKEN ??
|
|
548
|
-
const memberToken = env.MEMBER_API_TOKEN ??
|
|
526
|
+
const adminToken = env.ADMIN_API_TOKEN ?? DEFAULT_ADMIN_API_TOKEN;
|
|
527
|
+
const memberToken = env.MEMBER_API_TOKEN ?? DEFAULT_MEMBER_API_TOKEN;
|
|
549
528
|
const scimToken = env.SCIM_BEARER_TOKEN ?? DEFAULT_SCIM_BEARER_TOKEN;
|
|
550
|
-
const encryptionEnabled = env.FEATURE_FIELD_ENCRYPTION
|
|
551
|
-
const devHeadersEnabled = (env.AUTH_DEV_HEADERS
|
|
529
|
+
const encryptionEnabled = isEnabled(env.FEATURE_FIELD_ENCRYPTION, true);
|
|
530
|
+
const devHeadersEnabled = isEnabled(env.AUTH_DEV_HEADERS, true);
|
|
552
531
|
if (devHeadersEnabled) {
|
|
553
532
|
throw new Error("Production startup blocked: set AUTH_DEV_HEADERS=false to disable development auth headers.");
|
|
554
533
|
}
|
|
@@ -561,18 +540,17 @@ function assertProductionSecrets(env = process.env) {
|
|
|
561
540
|
if (encryptionEnabled && !env.KMS_ENCRYPTION_KEY?.trim()) {
|
|
562
541
|
throw new Error("Production startup blocked: set KMS_ENCRYPTION_KEY when field encryption is enabled.");
|
|
563
542
|
}
|
|
564
|
-
if (!env.SIEM_EXPORT_URL?.trim() &&
|
|
543
|
+
if (!env.SIEM_EXPORT_URL?.trim() && isEnabled(env.FEATURE_SIEM_EXPORT, true)) {
|
|
565
544
|
console.warn("[secrets] SIEM_EXPORT_URL is not configured; audit logs remain database-only.");
|
|
566
545
|
}
|
|
567
|
-
|
|
568
|
-
if (billingEnabled && !env.STRIPE_WEBHOOK_SECRET?.trim()) {
|
|
546
|
+
if (isEnabled(env.FEATURE_BILLING, true) && !env.STRIPE_WEBHOOK_SECRET?.trim()) {
|
|
569
547
|
throw new Error("Production startup blocked: set STRIPE_WEBHOOK_SECRET when billing webhooks are enabled.");
|
|
570
548
|
}
|
|
571
549
|
const corsOrigins = (env.CORS_ALLOWED_ORIGINS ?? "*").split(",").map((origin) => origin.trim());
|
|
572
550
|
if (corsOrigins.includes("*")) {
|
|
573
551
|
throw new Error("Production startup blocked: set explicit CORS_ALLOWED_ORIGINS instead of wildcard.");
|
|
574
552
|
}
|
|
575
|
-
if ((env.FEATURE_PUBLIC_READS
|
|
553
|
+
if (isEnabled(env.FEATURE_PUBLIC_READS, true)) {
|
|
576
554
|
throw new Error("Production startup blocked: set FEATURE_PUBLIC_READS=false for authenticated-only reads.");
|
|
577
555
|
}
|
|
578
556
|
if (!env.OAUTH_STATE_SECRET?.trim()) {
|
|
@@ -584,6 +562,10 @@ function assertProductionSecrets(env = process.env) {
|
|
|
584
562
|
if (!env.API_TOKEN_DEFAULT_EXPIRY_DAYS?.trim()) {
|
|
585
563
|
throw new Error("Production startup blocked: set API_TOKEN_DEFAULT_EXPIRY_DAYS to enforce token rotation.");
|
|
586
564
|
}
|
|
565
|
+
const frontendMode = (env.FRONTEND_MODE ?? "api").trim();
|
|
566
|
+
if (frontendMode === "server-htmx" && !env.SESSION_SECRET?.trim()) {
|
|
567
|
+
throw new Error("Production startup blocked: set SESSION_SECRET when FRONTEND_MODE=server-htmx.");
|
|
568
|
+
}
|
|
587
569
|
}
|
|
588
570
|
|
|
589
571
|
// ../../src/bootstrap/context.ts
|
|
@@ -641,6 +623,6 @@ function createAppDependencies() {
|
|
|
641
623
|
return createAppContext().dependencies;
|
|
642
624
|
}
|
|
643
625
|
export {
|
|
644
|
-
|
|
645
|
-
|
|
626
|
+
createAppContext,
|
|
627
|
+
createAppDependencies
|
|
646
628
|
};
|
|
@@ -26,7 +26,7 @@ function resolveModulesDirectory(options) {
|
|
|
26
26
|
if (state.configuredModulesDir) {
|
|
27
27
|
return state.configuredModulesDir;
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
|
|
30
30
|
}
|
|
31
31
|
async function loadDiscoveredModules(options) {
|
|
32
32
|
const modulesDirectory = resolveModulesDirectory(options);
|
|
@@ -60,8 +60,15 @@ async function ensureModulesLoaded(options) {
|
|
|
60
60
|
function discoverModules() {
|
|
61
61
|
return readDiscoverModulesState().appModules;
|
|
62
62
|
}
|
|
63
|
+
function resetDiscoverModulesForTests() {
|
|
64
|
+
const state = readDiscoverModulesState();
|
|
65
|
+
state.configuredModulesDir = undefined;
|
|
66
|
+
state.appModules.length = 0;
|
|
67
|
+
state.modulesReady = undefined;
|
|
68
|
+
}
|
|
63
69
|
export {
|
|
64
|
-
|
|
70
|
+
configureModulesDirectory,
|
|
65
71
|
discoverModules,
|
|
66
|
-
|
|
72
|
+
ensureModulesLoaded,
|
|
73
|
+
resetDiscoverModulesForTests
|
|
67
74
|
};
|
package/dist/entries/health.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
var __jsonParse = (a) => JSON.parse(a);
|
|
3
3
|
|
|
4
4
|
// ../../src/bootstrap/health.ts
|
|
5
|
-
import { jsonResponse } from "@getstrata/core/http";
|
|
5
|
+
import { jsonResponse } from "@getstrata/core/http/response";
|
|
6
6
|
var {RedisClient } = globalThis.Bun;
|
|
7
7
|
|
|
8
8
|
// ../../src/config/database.ts
|
|
@@ -209,7 +209,7 @@ function createHealthRoutes(dependencies) {
|
|
|
209
209
|
};
|
|
210
210
|
}
|
|
211
211
|
export {
|
|
212
|
-
|
|
212
|
+
checkDatabase,
|
|
213
213
|
checkRedis,
|
|
214
|
-
|
|
214
|
+
createHealthRoutes
|
|
215
215
|
};
|
|
@@ -169,7 +169,7 @@ class HttpKernel {
|
|
|
169
169
|
return this.wrap(["api", "authenticated"], handler);
|
|
170
170
|
}
|
|
171
171
|
wrapWeb(handler) {
|
|
172
|
-
return handler;
|
|
172
|
+
return this.wrap("web", handler);
|
|
173
173
|
}
|
|
174
174
|
wrapWebPublicRead(handler) {
|
|
175
175
|
if (isPublicReadsEnabled()) {
|
|
@@ -179,19 +179,19 @@ class HttpKernel {
|
|
|
179
179
|
}
|
|
180
180
|
wrapWebAuthenticated(handler) {
|
|
181
181
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
182
|
-
return withMiddleware(createRequireWebAuthMiddleware(auth))(handler);
|
|
182
|
+
return this.wrap("web", withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
|
|
183
183
|
}
|
|
184
184
|
wrapWebAbility(ability, handler) {
|
|
185
185
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
186
186
|
const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
187
187
|
const requireAbility = createRequireAbilityMiddleware(abilityChecker);
|
|
188
188
|
const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
|
|
189
|
-
return withMiddleware(...middleware)(handler);
|
|
189
|
+
return this.wrap("web", withMiddleware(...middleware)(handler));
|
|
190
190
|
}
|
|
191
191
|
wrapWebGlobalAdmin(handler) {
|
|
192
192
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
193
193
|
const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
|
|
194
|
-
return withMiddleware(...middleware)(handler);
|
|
194
|
+
return this.wrap("web", withMiddleware(...middleware)(handler));
|
|
195
195
|
}
|
|
196
196
|
wrapAuthenticated(handler) {
|
|
197
197
|
return this.wrap("authenticated", handler);
|
|
@@ -265,6 +265,6 @@ function createHttpKernel(dependencies) {
|
|
|
265
265
|
return new HttpKernel(dependencies);
|
|
266
266
|
}
|
|
267
267
|
export {
|
|
268
|
-
|
|
269
|
-
|
|
268
|
+
HttpKernel,
|
|
269
|
+
createHttpKernel
|
|
270
270
|
};
|
|
@@ -44,7 +44,7 @@ function resolveModulesDirectory(options) {
|
|
|
44
44
|
if (state.configuredModulesDir) {
|
|
45
45
|
return state.configuredModulesDir;
|
|
46
46
|
}
|
|
47
|
-
|
|
47
|
+
throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
|
|
48
48
|
}
|
|
49
49
|
async function loadDiscoveredModules(options) {
|
|
50
50
|
const modulesDirectory = resolveModulesDirectory(options);
|
|
@@ -78,6 +78,12 @@ async function ensureModulesLoaded(options) {
|
|
|
78
78
|
function discoverModules() {
|
|
79
79
|
return readDiscoverModulesState().appModules;
|
|
80
80
|
}
|
|
81
|
+
function resetDiscoverModulesForTests() {
|
|
82
|
+
const state = readDiscoverModulesState();
|
|
83
|
+
state.configuredModulesDir = undefined;
|
|
84
|
+
state.appModules.length = 0;
|
|
85
|
+
state.modulesReady = undefined;
|
|
86
|
+
}
|
|
81
87
|
|
|
82
88
|
// ../../src/bootstrap/cache/modelCacheTags.ts
|
|
83
89
|
function cacheTagsForModelWrite(tableName, action) {
|