@getstrata/bootstrap 0.2.48 → 0.2.50
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 +8 -0
- package/README.md +5 -1
- package/dist/bootstrap/config.d.ts +1 -1
- package/dist/bootstrap/health.d.ts +8 -2
- package/dist/bootstrap/public-api.d.ts +4 -2
- package/dist/bootstrap/web/index.d.ts +1 -1
- package/dist/bootstrap/web/routing.d.ts +2 -2
- package/dist/bootstrap/web/session.d.ts +25 -3
- package/dist/entries/buildModuleRoutes.js +24 -8
- package/dist/entries/buildWebModuleRoutes.js +24 -8
- package/dist/entries/config.js +4 -0
- package/dist/entries/context.js +2 -63
- package/dist/entries/createRoutes.js +218 -169
- package/dist/entries/createSpaRoutes.js +1 -1
- package/dist/entries/createWebRoutes.js +24 -8
- package/dist/entries/dependencies.js +2 -63
- package/dist/entries/health.js +69 -152
- package/dist/entries/httpKernel.js +24 -8
- package/dist/entries/providers.js +2 -0
- package/dist/entries/secretsGuard.js +64 -10
- package/dist/entries/web/routing.js +28 -12
- package/dist/entries/web/session.js +59 -7
- package/dist/index.js +300 -81
- package/package.json +5 -4
|
@@ -63,6 +63,7 @@ import { applyMiddlewareToRoutes } from "@getstrata/core/http/middleware";
|
|
|
63
63
|
|
|
64
64
|
// ../../src/bootstrap/httpKernel.ts
|
|
65
65
|
import { createMembershipMiddleware } from "@getstrata/core/auth/membershipMiddleware";
|
|
66
|
+
import { resolveAbilityChecker } from "@getstrata/core/contracts/serviceTokens";
|
|
66
67
|
import { createAuthMiddleware } from "@getstrata/core/http/authMiddleware";
|
|
67
68
|
import { createAuthorizeMiddleware } from "@getstrata/core/http/authorizeMiddleware";
|
|
68
69
|
import { createBodySizeLimitMiddleware } from "@getstrata/core/http/bodySizeLimitMiddleware";
|
|
@@ -77,6 +78,7 @@ import { createRequireAbilityMiddleware } from "@getstrata/core/http/requireAbil
|
|
|
77
78
|
import { createRequireAuthMiddleware } from "@getstrata/core/http/requireAuthMiddleware";
|
|
78
79
|
import { createRequireGlobalAdminMiddleware } from "@getstrata/core/http/requireGlobalAdminMiddleware";
|
|
79
80
|
import { createRequireWebAuthMiddleware } from "@getstrata/core/http/requireWebAuthMiddleware";
|
|
81
|
+
import { withErrorHandling } from "@getstrata/core/http/response";
|
|
80
82
|
import { withMiddleware } from "@getstrata/core/http/routeMiddleware";
|
|
81
83
|
import { createSecurityHeadersMiddleware } from "@getstrata/core/http/securityHeadersMiddleware";
|
|
82
84
|
import { createThrottleMiddleware } from "@getstrata/core/http/throttleMiddleware";
|
|
@@ -104,24 +106,38 @@ function parsePositiveInt(value, fallback) {
|
|
|
104
106
|
}
|
|
105
107
|
return Math.trunc(parsed);
|
|
106
108
|
}
|
|
109
|
+
function parseWindowSeconds(secondsValue, msValue, fallback) {
|
|
110
|
+
if (secondsValue !== undefined && secondsValue.trim() !== "") {
|
|
111
|
+
return parsePositiveInt(secondsValue, fallback);
|
|
112
|
+
}
|
|
113
|
+
if (msValue !== undefined && msValue.trim() !== "") {
|
|
114
|
+
const parsedMs = Number(msValue);
|
|
115
|
+
if (Number.isFinite(parsedMs) && parsedMs > 0) {
|
|
116
|
+
return Math.max(1, Math.trunc(parsedMs / 1000));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return fallback;
|
|
120
|
+
}
|
|
107
121
|
function resolveLoginRateLimit() {
|
|
108
122
|
const defaults = isLocalAppEnv() ? LOCAL_LOGIN_RATE_LIMIT : PRODUCTION_LOGIN_RATE_LIMIT;
|
|
109
123
|
return {
|
|
110
124
|
maxAttempts: parsePositiveInt(process.env.LOGIN_RATE_LIMIT_PER_WINDOW, defaults.maxAttempts),
|
|
111
|
-
decaySeconds:
|
|
125
|
+
decaySeconds: parseWindowSeconds(process.env.LOGIN_RATE_LIMIT_WINDOW_SECONDS, process.env.LOGIN_RATE_LIMIT_WINDOW_MS, defaults.decaySeconds)
|
|
112
126
|
};
|
|
113
127
|
}
|
|
114
128
|
function resolveRegisterRateLimit() {
|
|
115
129
|
const defaults = isLocalAppEnv() ? { maxAttempts: 100, decaySeconds: 60 } : { maxAttempts: 10, decaySeconds: 60 };
|
|
116
130
|
return {
|
|
117
131
|
maxAttempts: parsePositiveInt(process.env.REGISTER_RATE_LIMIT_PER_WINDOW, defaults.maxAttempts),
|
|
118
|
-
decaySeconds:
|
|
132
|
+
decaySeconds: parseWindowSeconds(process.env.REGISTER_RATE_LIMIT_WINDOW_SECONDS, process.env.REGISTER_RATE_LIMIT_WINDOW_MS, defaults.decaySeconds)
|
|
119
133
|
};
|
|
120
134
|
}
|
|
121
135
|
|
|
122
136
|
// ../../src/bootstrap/config.ts
|
|
123
137
|
import {
|
|
138
|
+
CORE_ABILITY_CHECKER_TOKEN,
|
|
124
139
|
CORE_AUTH_TOKEN,
|
|
140
|
+
CORE_AUTH_USER_DIRECTORY_TOKEN,
|
|
125
141
|
CORE_CACHE_TOKEN,
|
|
126
142
|
CORE_CONFIG_TOKEN,
|
|
127
143
|
CORE_EVENT_BUS_TOKEN,
|
|
@@ -211,7 +227,7 @@ class HttpKernel {
|
|
|
211
227
|
return this.wrap(["api", "authenticated"], handler);
|
|
212
228
|
}
|
|
213
229
|
wrapWeb(handler) {
|
|
214
|
-
return this.wrap("web", handler);
|
|
230
|
+
return withErrorHandling(this.wrap("web", handler));
|
|
215
231
|
}
|
|
216
232
|
wrapWebPublicRead(handler) {
|
|
217
233
|
if (isPublicReadsEnabled()) {
|
|
@@ -221,19 +237,19 @@ class HttpKernel {
|
|
|
221
237
|
}
|
|
222
238
|
wrapWebAuthenticated(handler) {
|
|
223
239
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
224
|
-
return this.
|
|
240
|
+
return this.wrapWeb(withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
|
|
225
241
|
}
|
|
226
242
|
wrapWebAbility(ability, handler) {
|
|
227
243
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
228
|
-
const abilityChecker = this.dependencies.container
|
|
244
|
+
const abilityChecker = resolveAbilityChecker(this.dependencies.container);
|
|
229
245
|
const requireAbility = createRequireAbilityMiddleware(abilityChecker);
|
|
230
246
|
const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
|
|
231
|
-
return this.
|
|
247
|
+
return this.wrapWeb(withMiddleware(...middleware)(handler));
|
|
232
248
|
}
|
|
233
249
|
wrapWebGlobalAdmin(handler) {
|
|
234
250
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
235
251
|
const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
|
|
236
|
-
return this.
|
|
252
|
+
return this.wrapWeb(withMiddleware(...middleware)(handler));
|
|
237
253
|
}
|
|
238
254
|
wrapAuthenticated(handler) {
|
|
239
255
|
return this.wrap("authenticated", handler);
|
|
@@ -249,7 +265,7 @@ class HttpKernel {
|
|
|
249
265
|
return withMiddleware(...middleware)(handler);
|
|
250
266
|
}
|
|
251
267
|
wrapAbility(ability, handler) {
|
|
252
|
-
const abilityChecker = this.dependencies.container
|
|
268
|
+
const abilityChecker = resolveAbilityChecker(this.dependencies.container);
|
|
253
269
|
const requireAbility = createRequireAbilityMiddleware(abilityChecker);
|
|
254
270
|
const middleware = [...this.group("authenticated"), requireAbility(ability)];
|
|
255
271
|
return withMiddleware(...middleware)(handler);
|
|
@@ -474,7 +490,7 @@ function createSpaRoutes(_dependencies) {
|
|
|
474
490
|
});
|
|
475
491
|
}
|
|
476
492
|
return jsonResponse({
|
|
477
|
-
error: "SPA build not found. Run `
|
|
493
|
+
error: "SPA build not found. Run `bun run build:frontend`."
|
|
478
494
|
}, { status: 503 });
|
|
479
495
|
},
|
|
480
496
|
"/": async () => Response.redirect("/app/", 302)
|
|
@@ -553,157 +569,46 @@ function mergeWebRoutes(dependencies, routes) {
|
|
|
553
569
|
}
|
|
554
570
|
|
|
555
571
|
// ../../src/bootstrap/health.ts
|
|
572
|
+
import { getBoundDatabaseConnection } from "@getstrata/core/database/boundConnection";
|
|
573
|
+
import { getDefaultDatabasePool } from "@getstrata/core/database/defaultConnection";
|
|
556
574
|
import { jsonResponse as jsonResponse2 } from "@getstrata/core/http/response";
|
|
557
575
|
var {RedisClient } = globalThis.Bun;
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
const parsed = Number.parseInt(process.env[name] ?? String(fallback), 10);
|
|
562
|
-
return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
|
|
563
|
-
}
|
|
564
|
-
var databaseConfig = {
|
|
565
|
-
url: process.env.DATABASE_URL ?? "",
|
|
566
|
-
poolMax: readInteger("DB_POOL_MAX", 10),
|
|
567
|
-
idleTimeoutSeconds: readInteger("DB_POOL_IDLE_TIMEOUT", 30),
|
|
568
|
-
maxLifetimeSeconds: readInteger("DB_POOL_MAX_LIFETIME", 3600),
|
|
569
|
-
connectionTimeoutSeconds: readInteger("DB_CONNECTION_TIMEOUT", 10)
|
|
570
|
-
};
|
|
571
|
-
|
|
572
|
-
// ../../src/core/runtime/asyncContextStore.ts
|
|
573
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
574
|
-
function createAsyncContextStore(key) {
|
|
575
|
-
const symbol = Symbol.for(key);
|
|
576
|
-
const globalRecord = globalThis;
|
|
577
|
-
const existing = globalRecord[symbol];
|
|
578
|
-
if (existing) {
|
|
579
|
-
return existing;
|
|
580
|
-
}
|
|
581
|
-
const store = new AsyncLocalStorage;
|
|
582
|
-
globalRecord[symbol] = store;
|
|
583
|
-
return store;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
// ../../src/core/database/connectionContext.ts
|
|
587
|
-
var activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
|
|
588
|
-
function getActiveDatabaseConnection(fallback) {
|
|
589
|
-
return activeConnection.getStore() ?? fallback;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
// ../../src/core/database/queryProxy.ts
|
|
593
|
-
var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
|
|
594
|
-
function createDatabaseQueryProxy(pool) {
|
|
595
|
-
function resolveDatabase() {
|
|
596
|
-
return getActiveDatabaseConnection(pool);
|
|
597
|
-
}
|
|
598
|
-
function resolveDatabaseForProperty(property) {
|
|
599
|
-
if (typeof property === "string" && POOL_CONNECTION_METHODS.has(property)) {
|
|
600
|
-
return pool;
|
|
601
|
-
}
|
|
602
|
-
return resolveDatabase();
|
|
603
|
-
}
|
|
604
|
-
return new Proxy(function database() {}, {
|
|
605
|
-
apply(_target, _thisArg, args) {
|
|
606
|
-
return resolveDatabase()(...args);
|
|
607
|
-
},
|
|
608
|
-
get(_target, property) {
|
|
609
|
-
const connection = resolveDatabaseForProperty(property);
|
|
610
|
-
const value = connection[property];
|
|
611
|
-
return typeof value === "function" ? value.bind(connection) : value;
|
|
612
|
-
}
|
|
613
|
-
});
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// ../../src/core/database/defaultConnection.ts
|
|
617
|
-
var defaultPool = {
|
|
618
|
-
connection: null
|
|
619
|
-
};
|
|
620
|
-
var defaultQuery = {
|
|
621
|
-
connection: null
|
|
622
|
-
};
|
|
623
|
-
function registerDefaultDatabasePool(connection) {
|
|
624
|
-
defaultPool.connection = connection;
|
|
625
|
-
defaultQuery.connection = createDatabaseQueryProxy(connection);
|
|
626
|
-
}
|
|
627
|
-
function getDefaultDatabaseQuery() {
|
|
628
|
-
if (!defaultQuery.connection) {
|
|
629
|
-
throw new Error("Default database query handle is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
|
|
576
|
+
function resolveRedisUrl(dependencies) {
|
|
577
|
+
if (!dependencies.container.has(CORE_CONFIG_TOKEN)) {
|
|
578
|
+
return process.env.REDIS_URL?.trim() || undefined;
|
|
630
579
|
}
|
|
631
|
-
|
|
580
|
+
const config = dependencies.container.resolve(CORE_CONFIG_TOKEN);
|
|
581
|
+
const redisUrl = config.get(REDIS_URL_CONFIG_KEY)?.trim();
|
|
582
|
+
return redisUrl || undefined;
|
|
632
583
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
if (!config.url) {
|
|
638
|
-
throw new Error("DATABASE_URL is not configured. Set DATABASE_URL before starting the app or running integration tests.");
|
|
584
|
+
function resolveHealthDatabase() {
|
|
585
|
+
const bound = getBoundDatabaseConnection();
|
|
586
|
+
if (bound) {
|
|
587
|
+
return bound;
|
|
639
588
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
maxLifetime: config.maxLifetimeSeconds,
|
|
645
|
-
connectionTimeout: config.connectionTimeoutSeconds
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
// ../../src/db/connection/index.ts
|
|
650
|
-
var connectionHolder = {
|
|
651
|
-
connection: null
|
|
652
|
-
};
|
|
653
|
-
function getDatabase() {
|
|
654
|
-
if (!connectionHolder.connection) {
|
|
655
|
-
connectionHolder.connection = createDatabaseConnection(databaseConfig);
|
|
656
|
-
registerDefaultDatabasePool(connectionHolder.connection);
|
|
589
|
+
try {
|
|
590
|
+
return getDefaultDatabasePool();
|
|
591
|
+
} catch {
|
|
592
|
+
return null;
|
|
657
593
|
}
|
|
658
|
-
return connectionHolder.connection;
|
|
659
594
|
}
|
|
660
|
-
function
|
|
661
|
-
getDatabase();
|
|
662
|
-
return getDefaultDatabaseQuery();
|
|
663
|
-
}
|
|
664
|
-
async function pingDatabase(connection = getDatabase()) {
|
|
595
|
+
async function pingDatabaseClient(connection) {
|
|
665
596
|
try {
|
|
666
|
-
await connection
|
|
597
|
+
await connection.unsafe("SELECT 1");
|
|
667
598
|
return true;
|
|
668
599
|
} catch {
|
|
669
600
|
return false;
|
|
670
601
|
}
|
|
671
602
|
}
|
|
672
|
-
async function
|
|
673
|
-
|
|
674
|
-
|
|
603
|
+
async function checkDatabase() {
|
|
604
|
+
const connection = resolveHealthDatabase();
|
|
605
|
+
if (!connection) {
|
|
606
|
+
return false;
|
|
675
607
|
}
|
|
676
|
-
await
|
|
677
|
-
return;
|
|
678
|
-
});
|
|
679
|
-
connectionHolder.connection = createDatabaseConnection(databaseConfig);
|
|
680
|
-
registerDefaultDatabasePool(connectionHolder.connection);
|
|
681
|
-
return getDatabase();
|
|
608
|
+
return await pingDatabaseClient(connection);
|
|
682
609
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
return getDb()(...args);
|
|
686
|
-
},
|
|
687
|
-
get(_target, property) {
|
|
688
|
-
const connection = getDb();
|
|
689
|
-
const value = connection[property];
|
|
690
|
-
return typeof value === "function" ? value.bind(connection) : value;
|
|
691
|
-
}
|
|
692
|
-
});
|
|
693
|
-
var connection_default = db;
|
|
694
|
-
|
|
695
|
-
// ../../src/bootstrap/health.ts
|
|
696
|
-
function resolveRedisUrl(dependencies) {
|
|
697
|
-
if (!dependencies.container.has(CORE_CONFIG_TOKEN)) {
|
|
698
|
-
return process.env.REDIS_URL?.trim() || undefined;
|
|
699
|
-
}
|
|
700
|
-
const config = dependencies.container.resolve(CORE_CONFIG_TOKEN);
|
|
701
|
-
const redisUrl = config.get(REDIS_URL_CONFIG_KEY)?.trim();
|
|
702
|
-
return redisUrl || undefined;
|
|
703
|
-
}
|
|
704
|
-
async function checkDatabase() {
|
|
705
|
-
await ensureDatabaseConnection();
|
|
706
|
-
return await pingDatabase();
|
|
610
|
+
async function pingDatabase() {
|
|
611
|
+
return await checkDatabase();
|
|
707
612
|
}
|
|
708
613
|
async function checkRedis(redisUrl) {
|
|
709
614
|
try {
|
|
@@ -714,23 +619,46 @@ async function checkRedis(redisUrl) {
|
|
|
714
619
|
return false;
|
|
715
620
|
}
|
|
716
621
|
}
|
|
717
|
-
function
|
|
622
|
+
async function resolveExtraFields(extra) {
|
|
623
|
+
if (!extra) {
|
|
624
|
+
return {};
|
|
625
|
+
}
|
|
626
|
+
return typeof extra === "function" ? await extra() : extra;
|
|
627
|
+
}
|
|
628
|
+
async function collectDependencyChecks(dependencies) {
|
|
629
|
+
const checks = {
|
|
630
|
+
database: await checkDatabase() ? "ok" : "error"
|
|
631
|
+
};
|
|
632
|
+
const redisUrl = resolveRedisUrl(dependencies);
|
|
633
|
+
if (redisUrl) {
|
|
634
|
+
checks.redis = await checkRedis(redisUrl) ? "ok" : "error";
|
|
635
|
+
} else {
|
|
636
|
+
checks.redis = "skipped";
|
|
637
|
+
}
|
|
638
|
+
const ready = checks.database === "ok" && (checks.redis === "ok" || checks.redis === "skipped");
|
|
639
|
+
return { checks, ready };
|
|
640
|
+
}
|
|
641
|
+
function createHealthRoutes(dependencies, options = {}) {
|
|
718
642
|
return {
|
|
719
|
-
"/health": async () =>
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
};
|
|
724
|
-
const redisUrl = resolveRedisUrl(dependencies);
|
|
725
|
-
if (redisUrl) {
|
|
726
|
-
checks.redis = await checkRedis(redisUrl) ? "ok" : "error";
|
|
727
|
-
} else {
|
|
728
|
-
checks.redis = "skipped";
|
|
643
|
+
"/health": async () => {
|
|
644
|
+
const extra = await resolveExtraFields(options.extra);
|
|
645
|
+
if (!options.pingOnHealth) {
|
|
646
|
+
return jsonResponse2({ status: "ok", ...extra });
|
|
729
647
|
}
|
|
730
|
-
const
|
|
648
|
+
const { checks, ready } = await collectDependencyChecks(dependencies);
|
|
649
|
+
return jsonResponse2({
|
|
650
|
+
status: ready ? "ok" : "error",
|
|
651
|
+
checks,
|
|
652
|
+
...extra
|
|
653
|
+
}, { status: ready ? 200 : 503 });
|
|
654
|
+
},
|
|
655
|
+
"/ready": async () => {
|
|
656
|
+
const extra = await resolveExtraFields(options.extra);
|
|
657
|
+
const { checks, ready } = await collectDependencyChecks(dependencies);
|
|
731
658
|
return jsonResponse2({
|
|
732
659
|
status: ready ? "ready" : "not_ready",
|
|
733
|
-
checks
|
|
660
|
+
checks,
|
|
661
|
+
...extra
|
|
734
662
|
}, { status: ready ? 200 : 503 });
|
|
735
663
|
}
|
|
736
664
|
};
|
|
@@ -781,7 +709,7 @@ import { withMiddleware as withMiddleware2 } from "@getstrata/core/http/routeMid
|
|
|
781
709
|
import { createScimThrottleMiddleware } from "@getstrata/core/http/scimThrottleMiddleware";
|
|
782
710
|
|
|
783
711
|
// ../../src/modules/scim/controller.ts
|
|
784
|
-
import { withErrorHandling } from "@getstrata/core/http/response";
|
|
712
|
+
import { withErrorHandling as withErrorHandling2 } from "@getstrata/core/http/response";
|
|
785
713
|
|
|
786
714
|
// ../../src/modules/scim/scimResponse.ts
|
|
787
715
|
import {
|
|
@@ -837,6 +765,123 @@ var SCIM_SCHEMAS = {
|
|
|
837
765
|
serviceProviderConfig: "urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"
|
|
838
766
|
};
|
|
839
767
|
|
|
768
|
+
// ../../src/config/database.ts
|
|
769
|
+
function readInteger(name, fallback) {
|
|
770
|
+
const parsed = Number.parseInt(process.env[name] ?? String(fallback), 10);
|
|
771
|
+
return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
|
|
772
|
+
}
|
|
773
|
+
var databaseConfig = {
|
|
774
|
+
url: process.env.DATABASE_URL ?? "",
|
|
775
|
+
poolMax: readInteger("DB_POOL_MAX", 10),
|
|
776
|
+
idleTimeoutSeconds: readInteger("DB_POOL_IDLE_TIMEOUT", 30),
|
|
777
|
+
maxLifetimeSeconds: readInteger("DB_POOL_MAX_LIFETIME", 3600),
|
|
778
|
+
connectionTimeoutSeconds: readInteger("DB_CONNECTION_TIMEOUT", 10)
|
|
779
|
+
};
|
|
780
|
+
|
|
781
|
+
// ../../src/core/runtime/asyncContextStore.ts
|
|
782
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
783
|
+
function createAsyncContextStore(key) {
|
|
784
|
+
const symbol = Symbol.for(key);
|
|
785
|
+
const globalRecord = globalThis;
|
|
786
|
+
const existing = globalRecord[symbol];
|
|
787
|
+
if (existing) {
|
|
788
|
+
return existing;
|
|
789
|
+
}
|
|
790
|
+
const store = new AsyncLocalStorage;
|
|
791
|
+
globalRecord[symbol] = store;
|
|
792
|
+
return store;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// ../../src/core/database/connectionContext.ts
|
|
796
|
+
var activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
|
|
797
|
+
function getActiveDatabaseConnection(fallback) {
|
|
798
|
+
return activeConnection.getStore() ?? fallback;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// ../../src/core/database/queryProxy.ts
|
|
802
|
+
var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
|
|
803
|
+
function createDatabaseQueryProxy(pool) {
|
|
804
|
+
function resolveDatabase() {
|
|
805
|
+
return getActiveDatabaseConnection(pool);
|
|
806
|
+
}
|
|
807
|
+
function resolveDatabaseForProperty(property) {
|
|
808
|
+
if (typeof property === "string" && POOL_CONNECTION_METHODS.has(property)) {
|
|
809
|
+
return pool;
|
|
810
|
+
}
|
|
811
|
+
return resolveDatabase();
|
|
812
|
+
}
|
|
813
|
+
return new Proxy(function database() {}, {
|
|
814
|
+
apply(_target, _thisArg, args) {
|
|
815
|
+
return resolveDatabase()(...args);
|
|
816
|
+
},
|
|
817
|
+
get(_target, property) {
|
|
818
|
+
const connection = resolveDatabaseForProperty(property);
|
|
819
|
+
const value = connection[property];
|
|
820
|
+
return typeof value === "function" ? value.bind(connection) : value;
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// ../../src/core/database/defaultConnection.ts
|
|
826
|
+
var defaultPool = {
|
|
827
|
+
connection: null
|
|
828
|
+
};
|
|
829
|
+
var defaultQuery = {
|
|
830
|
+
connection: null
|
|
831
|
+
};
|
|
832
|
+
function registerDefaultDatabasePool(connection) {
|
|
833
|
+
defaultPool.connection = connection;
|
|
834
|
+
defaultQuery.connection = createDatabaseQueryProxy(connection);
|
|
835
|
+
}
|
|
836
|
+
function getDefaultDatabaseQuery() {
|
|
837
|
+
if (!defaultQuery.connection) {
|
|
838
|
+
throw new Error("Default database query handle is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
|
|
839
|
+
}
|
|
840
|
+
return defaultQuery.connection;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// ../../src/db/connection/createConnection.ts
|
|
844
|
+
var {SQL } = globalThis.Bun;
|
|
845
|
+
function createDatabaseConnection(config) {
|
|
846
|
+
if (!config.url) {
|
|
847
|
+
throw new Error("DATABASE_URL is not configured. Set DATABASE_URL before starting the app or running integration tests.");
|
|
848
|
+
}
|
|
849
|
+
return new SQL({
|
|
850
|
+
url: config.url,
|
|
851
|
+
max: config.poolMax,
|
|
852
|
+
idleTimeout: config.idleTimeoutSeconds,
|
|
853
|
+
maxLifetime: config.maxLifetimeSeconds,
|
|
854
|
+
connectionTimeout: config.connectionTimeoutSeconds
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// ../../src/db/connection/index.ts
|
|
859
|
+
var connectionHolder = {
|
|
860
|
+
connection: null
|
|
861
|
+
};
|
|
862
|
+
function getDatabase() {
|
|
863
|
+
if (!connectionHolder.connection) {
|
|
864
|
+
connectionHolder.connection = createDatabaseConnection(databaseConfig);
|
|
865
|
+
registerDefaultDatabasePool(connectionHolder.connection);
|
|
866
|
+
}
|
|
867
|
+
return connectionHolder.connection;
|
|
868
|
+
}
|
|
869
|
+
function getDb() {
|
|
870
|
+
getDatabase();
|
|
871
|
+
return getDefaultDatabaseQuery();
|
|
872
|
+
}
|
|
873
|
+
var db = new Proxy(function database() {}, {
|
|
874
|
+
apply(_target, _thisArg, args) {
|
|
875
|
+
return getDb()(...args);
|
|
876
|
+
},
|
|
877
|
+
get(_target, property) {
|
|
878
|
+
const connection = getDb();
|
|
879
|
+
const value = connection[property];
|
|
880
|
+
return typeof value === "function" ? value.bind(connection) : value;
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
var connection_default = db;
|
|
884
|
+
|
|
840
885
|
// ../../src/modules/organization/memberRepository.ts
|
|
841
886
|
class OrganizationMemberRepository {
|
|
842
887
|
constructor() {}
|
|
@@ -937,7 +982,11 @@ class OrganizationRepository extends BaseRepository {
|
|
|
937
982
|
var repository_default = OrganizationRepository;
|
|
938
983
|
|
|
939
984
|
// ../../src/modules/user/provider.ts
|
|
940
|
-
import {
|
|
985
|
+
import {
|
|
986
|
+
CORE_ABILITY_CHECKER_TOKEN as CORE_ABILITY_CHECKER_TOKEN2,
|
|
987
|
+
CORE_AUTH_USER_DIRECTORY_TOKEN as CORE_AUTH_USER_DIRECTORY_TOKEN2,
|
|
988
|
+
CORE_TOKEN_SERVICE_TOKEN as CORE_TOKEN_SERVICE_TOKEN2
|
|
989
|
+
} from "@getstrata/bootstrap/config";
|
|
941
990
|
import { OidcProvider } from "@getstrata/core/auth/oauth/oidcProvider";
|
|
942
991
|
import { GitHubOAuthProvider, MockOAuthProvider } from "@getstrata/core/auth/oauth/providers";
|
|
943
992
|
import { SamlProvider } from "@getstrata/core/auth/oauth/samlProvider";
|
|
@@ -1246,30 +1295,30 @@ class ScimController {
|
|
|
1246
1295
|
constructor(dependencies, service = createScimService(dependencies)) {
|
|
1247
1296
|
this.service = service;
|
|
1248
1297
|
}
|
|
1249
|
-
serviceProviderConfig =
|
|
1298
|
+
serviceProviderConfig = withErrorHandling2(async () => {
|
|
1250
1299
|
return scimResponse(this.service.serviceProviderConfig());
|
|
1251
1300
|
});
|
|
1252
|
-
listUsers =
|
|
1301
|
+
listUsers = withErrorHandling2(async (request) => {
|
|
1253
1302
|
const url = new URL(request.url);
|
|
1254
1303
|
const startIndex = Number.parseInt(url.searchParams.get("startIndex") ?? "1", 10);
|
|
1255
1304
|
const count = Number.parseInt(url.searchParams.get("count") ?? "100", 10);
|
|
1256
1305
|
return scimResponse(await this.service.listUsers(startIndex, count));
|
|
1257
1306
|
});
|
|
1258
|
-
createUser =
|
|
1307
|
+
createUser = withErrorHandling2(async (request) => {
|
|
1259
1308
|
const payload = await request.json();
|
|
1260
1309
|
const user = await this.service.createUser(payload);
|
|
1261
1310
|
const id = Number.parseInt(user.id, 10);
|
|
1262
1311
|
const record = await this.service.findUserRecord(id);
|
|
1263
1312
|
return scimResponse(user, { status: 201, etagSource: record });
|
|
1264
1313
|
});
|
|
1265
|
-
showUser =
|
|
1314
|
+
showUser = withErrorHandling2(async (request) => {
|
|
1266
1315
|
const params = request.params;
|
|
1267
1316
|
const id = Number.parseInt(params?.id ?? "", 10);
|
|
1268
1317
|
const record = await this.service.findUserRecord(id);
|
|
1269
1318
|
const user = await this.service.getUser(id);
|
|
1270
1319
|
return scimResponse(user, { request, etagSource: record });
|
|
1271
1320
|
});
|
|
1272
|
-
patchUser =
|
|
1321
|
+
patchUser = withErrorHandling2(async (request) => {
|
|
1273
1322
|
const params = request.params;
|
|
1274
1323
|
const id = Number.parseInt(params?.id ?? "", 10);
|
|
1275
1324
|
const record = await this.service.findUserRecord(id);
|
|
@@ -1279,7 +1328,7 @@ class ScimController {
|
|
|
1279
1328
|
const updated = await this.service.findUserRecord(id);
|
|
1280
1329
|
return scimResponse(user, { etagSource: updated });
|
|
1281
1330
|
});
|
|
1282
|
-
deleteUser =
|
|
1331
|
+
deleteUser = withErrorHandling2(async (request) => {
|
|
1283
1332
|
const params = request.params;
|
|
1284
1333
|
const id = Number.parseInt(params?.id ?? "", 10);
|
|
1285
1334
|
const record = await this.service.findUserRecord(id);
|
|
@@ -1287,20 +1336,20 @@ class ScimController {
|
|
|
1287
1336
|
await this.service.deleteUser(id);
|
|
1288
1337
|
return new Response(null, { status: 204 });
|
|
1289
1338
|
});
|
|
1290
|
-
listGroups =
|
|
1339
|
+
listGroups = withErrorHandling2(async (request) => {
|
|
1291
1340
|
const url = new URL(request.url);
|
|
1292
1341
|
const startIndex = Number.parseInt(url.searchParams.get("startIndex") ?? "1", 10);
|
|
1293
1342
|
const count = Number.parseInt(url.searchParams.get("count") ?? "100", 10);
|
|
1294
1343
|
return scimResponse(await this.service.listGroups(startIndex, count));
|
|
1295
1344
|
});
|
|
1296
|
-
showGroup =
|
|
1345
|
+
showGroup = withErrorHandling2(async (request) => {
|
|
1297
1346
|
const params = request.params;
|
|
1298
1347
|
const id = Number.parseInt(params?.id ?? "", 10);
|
|
1299
1348
|
const record = await this.service.findOrganizationRecord(id);
|
|
1300
1349
|
const group = await this.service.getGroup(id);
|
|
1301
1350
|
return scimResponse(group, { request, etagSource: record });
|
|
1302
1351
|
});
|
|
1303
|
-
patchGroup =
|
|
1352
|
+
patchGroup = withErrorHandling2(async (request) => {
|
|
1304
1353
|
const params = request.params;
|
|
1305
1354
|
const id = Number.parseInt(params?.id ?? "", 10);
|
|
1306
1355
|
const record = await this.service.findOrganizationRecord(id);
|
|
@@ -42,7 +42,7 @@ function createSpaRoutes(_dependencies) {
|
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
return jsonResponse({
|
|
45
|
-
error: "SPA build not found. Run `
|
|
45
|
+
error: "SPA build not found. Run `bun run build:frontend`."
|
|
46
46
|
}, { status: 503 });
|
|
47
47
|
},
|
|
48
48
|
"/": async () => Response.redirect("/app/", 302)
|
|
@@ -32,6 +32,7 @@ import { applyMiddlewareToRoutes } from "@getstrata/core/http/middleware";
|
|
|
32
32
|
|
|
33
33
|
// ../../src/bootstrap/httpKernel.ts
|
|
34
34
|
import { createMembershipMiddleware } from "@getstrata/core/auth/membershipMiddleware";
|
|
35
|
+
import { resolveAbilityChecker } from "@getstrata/core/contracts/serviceTokens";
|
|
35
36
|
import { createAuthMiddleware } from "@getstrata/core/http/authMiddleware";
|
|
36
37
|
import { createAuthorizeMiddleware } from "@getstrata/core/http/authorizeMiddleware";
|
|
37
38
|
import { createBodySizeLimitMiddleware } from "@getstrata/core/http/bodySizeLimitMiddleware";
|
|
@@ -46,6 +47,7 @@ import { createRequireAbilityMiddleware } from "@getstrata/core/http/requireAbil
|
|
|
46
47
|
import { createRequireAuthMiddleware } from "@getstrata/core/http/requireAuthMiddleware";
|
|
47
48
|
import { createRequireGlobalAdminMiddleware } from "@getstrata/core/http/requireGlobalAdminMiddleware";
|
|
48
49
|
import { createRequireWebAuthMiddleware } from "@getstrata/core/http/requireWebAuthMiddleware";
|
|
50
|
+
import { withErrorHandling } from "@getstrata/core/http/response";
|
|
49
51
|
import { withMiddleware } from "@getstrata/core/http/routeMiddleware";
|
|
50
52
|
import { createSecurityHeadersMiddleware } from "@getstrata/core/http/securityHeadersMiddleware";
|
|
51
53
|
import { createThrottleMiddleware } from "@getstrata/core/http/throttleMiddleware";
|
|
@@ -73,24 +75,38 @@ function parsePositiveInt(value, fallback) {
|
|
|
73
75
|
}
|
|
74
76
|
return Math.trunc(parsed);
|
|
75
77
|
}
|
|
78
|
+
function parseWindowSeconds(secondsValue, msValue, fallback) {
|
|
79
|
+
if (secondsValue !== undefined && secondsValue.trim() !== "") {
|
|
80
|
+
return parsePositiveInt(secondsValue, fallback);
|
|
81
|
+
}
|
|
82
|
+
if (msValue !== undefined && msValue.trim() !== "") {
|
|
83
|
+
const parsedMs = Number(msValue);
|
|
84
|
+
if (Number.isFinite(parsedMs) && parsedMs > 0) {
|
|
85
|
+
return Math.max(1, Math.trunc(parsedMs / 1000));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return fallback;
|
|
89
|
+
}
|
|
76
90
|
function resolveLoginRateLimit() {
|
|
77
91
|
const defaults = isLocalAppEnv() ? LOCAL_LOGIN_RATE_LIMIT : PRODUCTION_LOGIN_RATE_LIMIT;
|
|
78
92
|
return {
|
|
79
93
|
maxAttempts: parsePositiveInt(process.env.LOGIN_RATE_LIMIT_PER_WINDOW, defaults.maxAttempts),
|
|
80
|
-
decaySeconds:
|
|
94
|
+
decaySeconds: parseWindowSeconds(process.env.LOGIN_RATE_LIMIT_WINDOW_SECONDS, process.env.LOGIN_RATE_LIMIT_WINDOW_MS, defaults.decaySeconds)
|
|
81
95
|
};
|
|
82
96
|
}
|
|
83
97
|
function resolveRegisterRateLimit() {
|
|
84
98
|
const defaults = isLocalAppEnv() ? { maxAttempts: 100, decaySeconds: 60 } : { maxAttempts: 10, decaySeconds: 60 };
|
|
85
99
|
return {
|
|
86
100
|
maxAttempts: parsePositiveInt(process.env.REGISTER_RATE_LIMIT_PER_WINDOW, defaults.maxAttempts),
|
|
87
|
-
decaySeconds:
|
|
101
|
+
decaySeconds: parseWindowSeconds(process.env.REGISTER_RATE_LIMIT_WINDOW_SECONDS, process.env.REGISTER_RATE_LIMIT_WINDOW_MS, defaults.decaySeconds)
|
|
88
102
|
};
|
|
89
103
|
}
|
|
90
104
|
|
|
91
105
|
// ../../src/bootstrap/config.ts
|
|
92
106
|
import {
|
|
107
|
+
CORE_ABILITY_CHECKER_TOKEN,
|
|
93
108
|
CORE_AUTH_TOKEN,
|
|
109
|
+
CORE_AUTH_USER_DIRECTORY_TOKEN,
|
|
94
110
|
CORE_CACHE_TOKEN,
|
|
95
111
|
CORE_CONFIG_TOKEN,
|
|
96
112
|
CORE_EVENT_BUS_TOKEN,
|
|
@@ -180,7 +196,7 @@ class HttpKernel {
|
|
|
180
196
|
return this.wrap(["api", "authenticated"], handler);
|
|
181
197
|
}
|
|
182
198
|
wrapWeb(handler) {
|
|
183
|
-
return this.wrap("web", handler);
|
|
199
|
+
return withErrorHandling(this.wrap("web", handler));
|
|
184
200
|
}
|
|
185
201
|
wrapWebPublicRead(handler) {
|
|
186
202
|
if (isPublicReadsEnabled()) {
|
|
@@ -190,19 +206,19 @@ class HttpKernel {
|
|
|
190
206
|
}
|
|
191
207
|
wrapWebAuthenticated(handler) {
|
|
192
208
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
193
|
-
return this.
|
|
209
|
+
return this.wrapWeb(withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
|
|
194
210
|
}
|
|
195
211
|
wrapWebAbility(ability, handler) {
|
|
196
212
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
197
|
-
const abilityChecker = this.dependencies.container
|
|
213
|
+
const abilityChecker = resolveAbilityChecker(this.dependencies.container);
|
|
198
214
|
const requireAbility = createRequireAbilityMiddleware(abilityChecker);
|
|
199
215
|
const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
|
|
200
|
-
return this.
|
|
216
|
+
return this.wrapWeb(withMiddleware(...middleware)(handler));
|
|
201
217
|
}
|
|
202
218
|
wrapWebGlobalAdmin(handler) {
|
|
203
219
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
204
220
|
const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
|
|
205
|
-
return this.
|
|
221
|
+
return this.wrapWeb(withMiddleware(...middleware)(handler));
|
|
206
222
|
}
|
|
207
223
|
wrapAuthenticated(handler) {
|
|
208
224
|
return this.wrap("authenticated", handler);
|
|
@@ -218,7 +234,7 @@ class HttpKernel {
|
|
|
218
234
|
return withMiddleware(...middleware)(handler);
|
|
219
235
|
}
|
|
220
236
|
wrapAbility(ability, handler) {
|
|
221
|
-
const abilityChecker = this.dependencies.container
|
|
237
|
+
const abilityChecker = resolveAbilityChecker(this.dependencies.container);
|
|
222
238
|
const requireAbility = createRequireAbilityMiddleware(abilityChecker);
|
|
223
239
|
const middleware = [...this.group("authenticated"), requireAbility(ability)];
|
|
224
240
|
return withMiddleware(...middleware)(handler);
|