@getstrata/bootstrap 1.0.8 → 1.1.0
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/dist/bootstrap/secretsGuard.d.ts +13 -1
- package/dist/bootstrap/web/session.d.ts +1 -0
- package/dist/entries/buildModuleRoutes.js +34 -38
- package/dist/entries/buildWebModuleRoutes.js +34 -38
- package/dist/entries/context.js +3 -3
- package/dist/entries/createSpaRoutes.js +10 -3
- package/dist/entries/createWebRoutes.js +43 -42
- package/dist/entries/dependencies.js +3 -3
- package/dist/entries/health.js +3 -2
- package/dist/entries/httpKernel.js +28 -37
- package/dist/entries/metricsRoutes.js +2 -1
- package/dist/entries/providers.js +3 -3
- package/dist/entries/schedule.js +2 -2
- package/dist/entries/secretsGuard.js +100 -5
- package/dist/entries/web/routing.js +28 -37
- package/dist/entries/web/server.js +9 -3
- package/dist/entries/web/session.js +68 -18
- package/dist/index.js +205 -79
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @getstrata/bootstrap changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
Lockstep with `@getstrata/core` 1.1.0. Cookie sessions select `s.created_at AS session_created_at` so `users.created_at` from `u.*` cannot clobber the session issued-at. API group always includes session-mutating CSRF. `buildModuleRoutes` maps thrown API middleware errors (including CSRF `ForbiddenError`) to JSON. `HttpKernel.wrap("api")` does the same for routes used outside that builder. Path confinement on the web server and safer production defaults. The rls role denylist checks the runtime URL (`APP_DATABASE_URL` when set, otherwise `DATABASE_URL`) so fixture admin `DATABASE_URL` can stay `postgres`.
|
|
6
|
+
|
|
7
|
+
## 1.0.9
|
|
8
|
+
|
|
9
|
+
Label HiroApp as internal e2e dogfood and seed notes via Model
|
|
10
|
+
|
|
3
11
|
## 1.0.8
|
|
4
12
|
|
|
5
13
|
- Container image starts and ships production dependencies only.
|
|
@@ -1,2 +1,14 @@
|
|
|
1
|
+
type LivePostgresRoleInspector = () => Promise<{
|
|
2
|
+
rolname: string;
|
|
3
|
+
rolsuper: boolean;
|
|
4
|
+
rolbypassrls: boolean;
|
|
5
|
+
} | null>;
|
|
6
|
+
declare function setLivePostgresRoleInspectorForTests(inspect: LivePostgresRoleInspector | null): void;
|
|
7
|
+
/**
|
|
8
|
+
* After the runtime pool is open, refuse rolsuper / rolbypassrls whenever
|
|
9
|
+
* TENANCY_DRIVER=rls, including local. Fail closed if the live query cannot run.
|
|
10
|
+
* MIGRATION_DATABASE_URL may stay a superuser.
|
|
11
|
+
*/
|
|
12
|
+
declare function assertRlsLiveDatabaseRole(env?: Record<string, string | undefined>, inspect?: LivePostgresRoleInspector): Promise<void>;
|
|
1
13
|
declare function assertProductionSecrets(env?: Record<string, string | undefined>): void;
|
|
2
|
-
export { assertProductionSecrets };
|
|
14
|
+
export { assertProductionSecrets, assertRlsLiveDatabaseRole, setLivePostgresRoleInspectorForTests };
|
|
@@ -42,6 +42,7 @@ export declare class CookieSessionStore {
|
|
|
42
42
|
private sql;
|
|
43
43
|
create(user: SessionUser, meta?: SessionCreateMeta): Promise<string>;
|
|
44
44
|
destroy(sessionId: string): Promise<void>;
|
|
45
|
+
destroyAllSessions(userId: number): Promise<void>;
|
|
45
46
|
destroyOtherSessions(userId: number, keepSessionId: string): Promise<void>;
|
|
46
47
|
listForUser(userId: number): Promise<BrowserSessionRecord[]>;
|
|
47
48
|
touch(sessionId: string): Promise<void>;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// ../../src/bootstrap/buildModuleRoutes.ts
|
|
3
3
|
import { conditionalJsonResponse } from "@getstrata/core/http/conditionalResponse";
|
|
4
4
|
import { applyMiddlewareToRoutes } from "@getstrata/core/http/middleware";
|
|
5
|
+
import { createJsonErrorMiddleware } from "@getstrata/core/http/response";
|
|
5
6
|
|
|
6
7
|
// ../../src/bootstrap/httpKernel.ts
|
|
7
8
|
import {
|
|
@@ -138,8 +139,9 @@ class HttpKernel {
|
|
|
138
139
|
case "web":
|
|
139
140
|
return isViewsEnabled() ? [createFlashMiddleware(), createCsrfMiddleware()] : [];
|
|
140
141
|
case "api": {
|
|
142
|
+
const csrf = createCsrfMiddleware();
|
|
141
143
|
if (!this.dependencies.container.has(CORE_CONFIG_TOKEN)) {
|
|
142
|
-
return [];
|
|
144
|
+
return [csrf];
|
|
143
145
|
}
|
|
144
146
|
const config = this.dependencies.container.resolve(CORE_CONFIG_TOKEN);
|
|
145
147
|
const redisUrl = config.get(REDIS_URL_CONFIG_KEY)?.trim() ?? "";
|
|
@@ -149,7 +151,8 @@ class HttpKernel {
|
|
|
149
151
|
createMemoryThrottleMiddleware({
|
|
150
152
|
maxAttempts: Number.isFinite(maxAttempts) ? maxAttempts : 120,
|
|
151
153
|
decaySeconds: 60
|
|
152
|
-
})
|
|
154
|
+
}),
|
|
155
|
+
csrf
|
|
153
156
|
];
|
|
154
157
|
}
|
|
155
158
|
const maxAttempts = Number(process.env.RATE_LIMIT_PER_MINUTE ?? "120");
|
|
@@ -158,7 +161,8 @@ class HttpKernel {
|
|
|
158
161
|
redisUrl,
|
|
159
162
|
maxAttempts: Number.isFinite(maxAttempts) ? maxAttempts : 120,
|
|
160
163
|
decaySeconds: 60
|
|
161
|
-
})
|
|
164
|
+
}),
|
|
165
|
+
csrf
|
|
162
166
|
];
|
|
163
167
|
}
|
|
164
168
|
default:
|
|
@@ -168,13 +172,14 @@ class HttpKernel {
|
|
|
168
172
|
wrap(groups, handler) {
|
|
169
173
|
const names = Array.isArray(groups) ? groups : [groups];
|
|
170
174
|
const middleware = names.flatMap((name) => this.group(name));
|
|
171
|
-
|
|
172
|
-
|
|
175
|
+
const wrapped = middleware.length === 0 ? handler : withMiddleware(...middleware)(handler);
|
|
176
|
+
if (names.includes("api")) {
|
|
177
|
+
return withJsonErrorHandling(wrapped);
|
|
173
178
|
}
|
|
174
|
-
return
|
|
179
|
+
return wrapped;
|
|
175
180
|
}
|
|
176
181
|
wrapApi(handler) {
|
|
177
|
-
return
|
|
182
|
+
return this.wrap(["api", "authenticated"], handler);
|
|
178
183
|
}
|
|
179
184
|
wrapWeb(handler) {
|
|
180
185
|
return withErrorHandling(this.wrap("web", handler));
|
|
@@ -283,41 +288,28 @@ class HttpKernel {
|
|
|
283
288
|
return [createRequireVerifiedMiddleware(auth)];
|
|
284
289
|
}
|
|
285
290
|
wrapThrottle(scope, rateLimit, handler) {
|
|
286
|
-
const middleware = [];
|
|
287
291
|
const memoryKeyPrefix = scope === "login" ? "login-throttle:" : "register-throttle:";
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
decaySeconds: rateLimit.decaySeconds,
|
|
300
|
-
keyPrefix: memoryKeyPrefix
|
|
301
|
-
});
|
|
302
|
-
middleware.push(throttle);
|
|
303
|
-
} else {
|
|
304
|
-
middleware.push(createMemoryThrottleMiddleware({
|
|
305
|
-
maxAttempts: rateLimit.maxAttempts,
|
|
306
|
-
decaySeconds: rateLimit.decaySeconds,
|
|
307
|
-
keyPrefix: memoryKeyPrefix
|
|
308
|
-
}));
|
|
309
|
-
}
|
|
310
|
-
} else {
|
|
311
|
-
middleware.push(createMemoryThrottleMiddleware({
|
|
292
|
+
const redisUrl = this.dependencies.container.has(CORE_CONFIG_TOKEN) ? this.dependencies.container.resolve(CORE_CONFIG_TOKEN).get(REDIS_URL_CONFIG_KEY)?.trim() ?? "" : "";
|
|
293
|
+
if (scope === "login") {
|
|
294
|
+
return withMiddleware(createLoginThrottleMiddleware({
|
|
295
|
+
...redisUrl ? { redisUrl } : {},
|
|
296
|
+
maxAttempts: rateLimit.maxAttempts,
|
|
297
|
+
decaySeconds: rateLimit.decaySeconds
|
|
298
|
+
}))(handler);
|
|
299
|
+
}
|
|
300
|
+
if (redisUrl) {
|
|
301
|
+
return withMiddleware(createThrottleMiddleware({
|
|
302
|
+
redisUrl,
|
|
312
303
|
maxAttempts: rateLimit.maxAttempts,
|
|
313
304
|
decaySeconds: rateLimit.decaySeconds,
|
|
314
305
|
keyPrefix: memoryKeyPrefix
|
|
315
|
-
}));
|
|
316
|
-
}
|
|
317
|
-
if (middleware.length === 0) {
|
|
318
|
-
return handler;
|
|
306
|
+
}))(handler);
|
|
319
307
|
}
|
|
320
|
-
return withMiddleware(
|
|
308
|
+
return withMiddleware(createMemoryThrottleMiddleware({
|
|
309
|
+
maxAttempts: rateLimit.maxAttempts,
|
|
310
|
+
decaySeconds: rateLimit.decaySeconds,
|
|
311
|
+
keyPrefix: memoryKeyPrefix
|
|
312
|
+
}))(handler);
|
|
321
313
|
}
|
|
322
314
|
}
|
|
323
315
|
function createHttpKernel(dependencies) {
|
|
@@ -462,7 +454,11 @@ function buildModuleRoutes(dependencies, options = {}) {
|
|
|
462
454
|
routeRegistry.clear();
|
|
463
455
|
}
|
|
464
456
|
const kernel = createHttpKernel(dependencies);
|
|
465
|
-
const middleware = [
|
|
457
|
+
const middleware = [
|
|
458
|
+
...kernel.globalMiddleware(),
|
|
459
|
+
createJsonErrorMiddleware(),
|
|
460
|
+
...kernel.group("api")
|
|
461
|
+
];
|
|
466
462
|
const cachedJson = createCachedJson(dependencies);
|
|
467
463
|
const moduleRoutes = {};
|
|
468
464
|
for (const module of modules) {
|
|
@@ -5,6 +5,7 @@ import { applyMiddlewareToRoutes as applyMiddlewareToRoutes2 } from "@getstrata/
|
|
|
5
5
|
// ../../src/bootstrap/buildModuleRoutes.ts
|
|
6
6
|
import { conditionalJsonResponse } from "@getstrata/core/http/conditionalResponse";
|
|
7
7
|
import { applyMiddlewareToRoutes } from "@getstrata/core/http/middleware";
|
|
8
|
+
import { createJsonErrorMiddleware } from "@getstrata/core/http/response";
|
|
8
9
|
|
|
9
10
|
// ../../src/bootstrap/httpKernel.ts
|
|
10
11
|
import {
|
|
@@ -141,8 +142,9 @@ class HttpKernel {
|
|
|
141
142
|
case "web":
|
|
142
143
|
return isViewsEnabled() ? [createFlashMiddleware(), createCsrfMiddleware()] : [];
|
|
143
144
|
case "api": {
|
|
145
|
+
const csrf = createCsrfMiddleware();
|
|
144
146
|
if (!this.dependencies.container.has(CORE_CONFIG_TOKEN)) {
|
|
145
|
-
return [];
|
|
147
|
+
return [csrf];
|
|
146
148
|
}
|
|
147
149
|
const config = this.dependencies.container.resolve(CORE_CONFIG_TOKEN);
|
|
148
150
|
const redisUrl = config.get(REDIS_URL_CONFIG_KEY)?.trim() ?? "";
|
|
@@ -152,7 +154,8 @@ class HttpKernel {
|
|
|
152
154
|
createMemoryThrottleMiddleware({
|
|
153
155
|
maxAttempts: Number.isFinite(maxAttempts) ? maxAttempts : 120,
|
|
154
156
|
decaySeconds: 60
|
|
155
|
-
})
|
|
157
|
+
}),
|
|
158
|
+
csrf
|
|
156
159
|
];
|
|
157
160
|
}
|
|
158
161
|
const maxAttempts = Number(process.env.RATE_LIMIT_PER_MINUTE ?? "120");
|
|
@@ -161,7 +164,8 @@ class HttpKernel {
|
|
|
161
164
|
redisUrl,
|
|
162
165
|
maxAttempts: Number.isFinite(maxAttempts) ? maxAttempts : 120,
|
|
163
166
|
decaySeconds: 60
|
|
164
|
-
})
|
|
167
|
+
}),
|
|
168
|
+
csrf
|
|
165
169
|
];
|
|
166
170
|
}
|
|
167
171
|
default:
|
|
@@ -171,13 +175,14 @@ class HttpKernel {
|
|
|
171
175
|
wrap(groups, handler) {
|
|
172
176
|
const names = Array.isArray(groups) ? groups : [groups];
|
|
173
177
|
const middleware = names.flatMap((name) => this.group(name));
|
|
174
|
-
|
|
175
|
-
|
|
178
|
+
const wrapped = middleware.length === 0 ? handler : withMiddleware(...middleware)(handler);
|
|
179
|
+
if (names.includes("api")) {
|
|
180
|
+
return withJsonErrorHandling(wrapped);
|
|
176
181
|
}
|
|
177
|
-
return
|
|
182
|
+
return wrapped;
|
|
178
183
|
}
|
|
179
184
|
wrapApi(handler) {
|
|
180
|
-
return
|
|
185
|
+
return this.wrap(["api", "authenticated"], handler);
|
|
181
186
|
}
|
|
182
187
|
wrapWeb(handler) {
|
|
183
188
|
return withErrorHandling(this.wrap("web", handler));
|
|
@@ -286,41 +291,28 @@ class HttpKernel {
|
|
|
286
291
|
return [createRequireVerifiedMiddleware(auth)];
|
|
287
292
|
}
|
|
288
293
|
wrapThrottle(scope, rateLimit, handler) {
|
|
289
|
-
const middleware = [];
|
|
290
294
|
const memoryKeyPrefix = scope === "login" ? "login-throttle:" : "register-throttle:";
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
decaySeconds: rateLimit.decaySeconds,
|
|
303
|
-
keyPrefix: memoryKeyPrefix
|
|
304
|
-
});
|
|
305
|
-
middleware.push(throttle);
|
|
306
|
-
} else {
|
|
307
|
-
middleware.push(createMemoryThrottleMiddleware({
|
|
308
|
-
maxAttempts: rateLimit.maxAttempts,
|
|
309
|
-
decaySeconds: rateLimit.decaySeconds,
|
|
310
|
-
keyPrefix: memoryKeyPrefix
|
|
311
|
-
}));
|
|
312
|
-
}
|
|
313
|
-
} else {
|
|
314
|
-
middleware.push(createMemoryThrottleMiddleware({
|
|
295
|
+
const redisUrl = this.dependencies.container.has(CORE_CONFIG_TOKEN) ? this.dependencies.container.resolve(CORE_CONFIG_TOKEN).get(REDIS_URL_CONFIG_KEY)?.trim() ?? "" : "";
|
|
296
|
+
if (scope === "login") {
|
|
297
|
+
return withMiddleware(createLoginThrottleMiddleware({
|
|
298
|
+
...redisUrl ? { redisUrl } : {},
|
|
299
|
+
maxAttempts: rateLimit.maxAttempts,
|
|
300
|
+
decaySeconds: rateLimit.decaySeconds
|
|
301
|
+
}))(handler);
|
|
302
|
+
}
|
|
303
|
+
if (redisUrl) {
|
|
304
|
+
return withMiddleware(createThrottleMiddleware({
|
|
305
|
+
redisUrl,
|
|
315
306
|
maxAttempts: rateLimit.maxAttempts,
|
|
316
307
|
decaySeconds: rateLimit.decaySeconds,
|
|
317
308
|
keyPrefix: memoryKeyPrefix
|
|
318
|
-
}));
|
|
319
|
-
}
|
|
320
|
-
if (middleware.length === 0) {
|
|
321
|
-
return handler;
|
|
309
|
+
}))(handler);
|
|
322
310
|
}
|
|
323
|
-
return withMiddleware(
|
|
311
|
+
return withMiddleware(createMemoryThrottleMiddleware({
|
|
312
|
+
maxAttempts: rateLimit.maxAttempts,
|
|
313
|
+
decaySeconds: rateLimit.decaySeconds,
|
|
314
|
+
keyPrefix: memoryKeyPrefix
|
|
315
|
+
}))(handler);
|
|
324
316
|
}
|
|
325
317
|
}
|
|
326
318
|
function createHttpKernel(dependencies) {
|
|
@@ -465,7 +457,11 @@ function buildModuleRoutes(dependencies, options = {}) {
|
|
|
465
457
|
routeRegistry.clear();
|
|
466
458
|
}
|
|
467
459
|
const kernel = createHttpKernel(dependencies);
|
|
468
|
-
const middleware = [
|
|
460
|
+
const middleware = [
|
|
461
|
+
...kernel.globalMiddleware(),
|
|
462
|
+
createJsonErrorMiddleware(),
|
|
463
|
+
...kernel.group("api")
|
|
464
|
+
];
|
|
469
465
|
const cachedJson = createCachedJson(dependencies);
|
|
470
466
|
const moduleRoutes = {};
|
|
471
467
|
for (const module of modules) {
|
package/dist/entries/context.js
CHANGED
|
@@ -108,7 +108,7 @@ import { SessionGuard } from "@getstrata/core/auth/sessionGuard";
|
|
|
108
108
|
import { envFlagEnabled } from "@getstrata/core/runtime/appEnv";
|
|
109
109
|
var authConfig = {
|
|
110
110
|
allowDevHeaders: envFlagEnabled(process.env.AUTH_DEV_HEADERS),
|
|
111
|
-
tokenDefaultAbilities: [
|
|
111
|
+
tokenDefaultAbilities: []
|
|
112
112
|
};
|
|
113
113
|
|
|
114
114
|
// ../../src/bootstrap/config.ts
|
|
@@ -144,7 +144,7 @@ var authProvider = {
|
|
|
144
144
|
config.set("auth.allowDevHeaders", authConfig.allowDevHeaders);
|
|
145
145
|
const apiGuard = new DatabaseTokenGuard(container);
|
|
146
146
|
const sessionGuard = new SessionGuard(container);
|
|
147
|
-
const jwtGuard = new JwtGuard;
|
|
147
|
+
const jwtGuard = new JwtGuard(container);
|
|
148
148
|
const basicGuard = new BasicAuthGuard(container);
|
|
149
149
|
const guards = [apiGuard, jwtGuard, basicGuard, sessionGuard];
|
|
150
150
|
if (authConfig.allowDevHeaders) {
|
|
@@ -189,7 +189,7 @@ import { validateEnv } from "@getstrata/core/config/envSchema";
|
|
|
189
189
|
var appConfig = {
|
|
190
190
|
name: process.env.APP_NAME?.trim() || "Strata",
|
|
191
191
|
env: process.env.APP_ENV ?? "local",
|
|
192
|
-
debug: (process.env.APP_DEBUG ?? "
|
|
192
|
+
debug: (process.env.APP_DEBUG ?? "false") === "true",
|
|
193
193
|
url: process.env.APP_URL ?? "http://localhost:3000",
|
|
194
194
|
apiPrefix: process.env.API_PREFIX ?? "/api/v1"
|
|
195
195
|
};
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { jsonResponse } from "@getstrata/core/http/response";
|
|
5
5
|
import { isSpaEnabled, isViewsEnabled, readSpaPrefix } from "@getstrata/core/runtime/frontendMode";
|
|
6
|
+
import { assertPathUnderRoot } from "@getstrata/core/security/safePath";
|
|
6
7
|
var SPA_DIST_DIRECTORY = join(process.cwd(), "frontend/dist");
|
|
7
8
|
function relativeSpaPath(pathname, prefix) {
|
|
8
9
|
if (pathname === prefix || pathname === `${prefix}/`) {
|
|
@@ -21,9 +22,15 @@ function createSpaDocumentHandler(prefix, distDirectory) {
|
|
|
21
22
|
return new Response("Not found", { status: 404 });
|
|
22
23
|
}
|
|
23
24
|
const relativePath = relativeSpaPath(pathname, prefix);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
if (relativePath.length > 0) {
|
|
26
|
+
try {
|
|
27
|
+
const assetFile = Bun.file(assertPathUnderRoot(distDirectory, relativePath));
|
|
28
|
+
if (await assetFile.exists()) {
|
|
29
|
+
return new Response(assetFile);
|
|
30
|
+
}
|
|
31
|
+
} catch {
|
|
32
|
+
return new Response("Not found", { status: 404 });
|
|
33
|
+
}
|
|
27
34
|
}
|
|
28
35
|
const indexFile = Bun.file(indexFilePath);
|
|
29
36
|
if (await indexFile.exists()) {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// ../../src/bootstrap/createWebRoutes.ts
|
|
3
3
|
import { join as join2 } from "path";
|
|
4
4
|
import { isViewsEnabled as isViewsEnabled2 } from "@getstrata/core/runtime/frontendMode";
|
|
5
|
+
import { assertUrlPathUnderRoot } from "@getstrata/core/security/safePath";
|
|
5
6
|
import { notFoundHtmlResponse } from "@getstrata/core/view";
|
|
6
7
|
|
|
7
8
|
// ../../src/bootstrap/buildWebModuleRoutes.ts
|
|
@@ -10,6 +11,7 @@ import { applyMiddlewareToRoutes as applyMiddlewareToRoutes2 } from "@getstrata/
|
|
|
10
11
|
// ../../src/bootstrap/buildModuleRoutes.ts
|
|
11
12
|
import { conditionalJsonResponse } from "@getstrata/core/http/conditionalResponse";
|
|
12
13
|
import { applyMiddlewareToRoutes } from "@getstrata/core/http/middleware";
|
|
14
|
+
import { createJsonErrorMiddleware } from "@getstrata/core/http/response";
|
|
13
15
|
|
|
14
16
|
// ../../src/bootstrap/httpKernel.ts
|
|
15
17
|
import {
|
|
@@ -146,8 +148,9 @@ class HttpKernel {
|
|
|
146
148
|
case "web":
|
|
147
149
|
return isViewsEnabled() ? [createFlashMiddleware(), createCsrfMiddleware()] : [];
|
|
148
150
|
case "api": {
|
|
151
|
+
const csrf = createCsrfMiddleware();
|
|
149
152
|
if (!this.dependencies.container.has(CORE_CONFIG_TOKEN)) {
|
|
150
|
-
return [];
|
|
153
|
+
return [csrf];
|
|
151
154
|
}
|
|
152
155
|
const config = this.dependencies.container.resolve(CORE_CONFIG_TOKEN);
|
|
153
156
|
const redisUrl = config.get(REDIS_URL_CONFIG_KEY)?.trim() ?? "";
|
|
@@ -157,7 +160,8 @@ class HttpKernel {
|
|
|
157
160
|
createMemoryThrottleMiddleware({
|
|
158
161
|
maxAttempts: Number.isFinite(maxAttempts) ? maxAttempts : 120,
|
|
159
162
|
decaySeconds: 60
|
|
160
|
-
})
|
|
163
|
+
}),
|
|
164
|
+
csrf
|
|
161
165
|
];
|
|
162
166
|
}
|
|
163
167
|
const maxAttempts = Number(process.env.RATE_LIMIT_PER_MINUTE ?? "120");
|
|
@@ -166,7 +170,8 @@ class HttpKernel {
|
|
|
166
170
|
redisUrl,
|
|
167
171
|
maxAttempts: Number.isFinite(maxAttempts) ? maxAttempts : 120,
|
|
168
172
|
decaySeconds: 60
|
|
169
|
-
})
|
|
173
|
+
}),
|
|
174
|
+
csrf
|
|
170
175
|
];
|
|
171
176
|
}
|
|
172
177
|
default:
|
|
@@ -176,13 +181,14 @@ class HttpKernel {
|
|
|
176
181
|
wrap(groups, handler) {
|
|
177
182
|
const names = Array.isArray(groups) ? groups : [groups];
|
|
178
183
|
const middleware = names.flatMap((name) => this.group(name));
|
|
179
|
-
|
|
180
|
-
|
|
184
|
+
const wrapped = middleware.length === 0 ? handler : withMiddleware(...middleware)(handler);
|
|
185
|
+
if (names.includes("api")) {
|
|
186
|
+
return withJsonErrorHandling(wrapped);
|
|
181
187
|
}
|
|
182
|
-
return
|
|
188
|
+
return wrapped;
|
|
183
189
|
}
|
|
184
190
|
wrapApi(handler) {
|
|
185
|
-
return
|
|
191
|
+
return this.wrap(["api", "authenticated"], handler);
|
|
186
192
|
}
|
|
187
193
|
wrapWeb(handler) {
|
|
188
194
|
return withErrorHandling(this.wrap("web", handler));
|
|
@@ -291,41 +297,28 @@ class HttpKernel {
|
|
|
291
297
|
return [createRequireVerifiedMiddleware(auth)];
|
|
292
298
|
}
|
|
293
299
|
wrapThrottle(scope, rateLimit, handler) {
|
|
294
|
-
const middleware = [];
|
|
295
300
|
const memoryKeyPrefix = scope === "login" ? "login-throttle:" : "register-throttle:";
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
decaySeconds: rateLimit.decaySeconds,
|
|
308
|
-
keyPrefix: memoryKeyPrefix
|
|
309
|
-
});
|
|
310
|
-
middleware.push(throttle);
|
|
311
|
-
} else {
|
|
312
|
-
middleware.push(createMemoryThrottleMiddleware({
|
|
313
|
-
maxAttempts: rateLimit.maxAttempts,
|
|
314
|
-
decaySeconds: rateLimit.decaySeconds,
|
|
315
|
-
keyPrefix: memoryKeyPrefix
|
|
316
|
-
}));
|
|
317
|
-
}
|
|
318
|
-
} else {
|
|
319
|
-
middleware.push(createMemoryThrottleMiddleware({
|
|
301
|
+
const redisUrl = this.dependencies.container.has(CORE_CONFIG_TOKEN) ? this.dependencies.container.resolve(CORE_CONFIG_TOKEN).get(REDIS_URL_CONFIG_KEY)?.trim() ?? "" : "";
|
|
302
|
+
if (scope === "login") {
|
|
303
|
+
return withMiddleware(createLoginThrottleMiddleware({
|
|
304
|
+
...redisUrl ? { redisUrl } : {},
|
|
305
|
+
maxAttempts: rateLimit.maxAttempts,
|
|
306
|
+
decaySeconds: rateLimit.decaySeconds
|
|
307
|
+
}))(handler);
|
|
308
|
+
}
|
|
309
|
+
if (redisUrl) {
|
|
310
|
+
return withMiddleware(createThrottleMiddleware({
|
|
311
|
+
redisUrl,
|
|
320
312
|
maxAttempts: rateLimit.maxAttempts,
|
|
321
313
|
decaySeconds: rateLimit.decaySeconds,
|
|
322
314
|
keyPrefix: memoryKeyPrefix
|
|
323
|
-
}));
|
|
315
|
+
}))(handler);
|
|
324
316
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
317
|
+
return withMiddleware(createMemoryThrottleMiddleware({
|
|
318
|
+
maxAttempts: rateLimit.maxAttempts,
|
|
319
|
+
decaySeconds: rateLimit.decaySeconds,
|
|
320
|
+
keyPrefix: memoryKeyPrefix
|
|
321
|
+
}))(handler);
|
|
329
322
|
}
|
|
330
323
|
}
|
|
331
324
|
function createHttpKernel(dependencies) {
|
|
@@ -470,7 +463,11 @@ function buildModuleRoutes(dependencies, options = {}) {
|
|
|
470
463
|
routeRegistry.clear();
|
|
471
464
|
}
|
|
472
465
|
const kernel = createHttpKernel(dependencies);
|
|
473
|
-
const middleware = [
|
|
466
|
+
const middleware = [
|
|
467
|
+
...kernel.globalMiddleware(),
|
|
468
|
+
createJsonErrorMiddleware(),
|
|
469
|
+
...kernel.group("api")
|
|
470
|
+
];
|
|
474
471
|
const cachedJson = createCachedJson(dependencies);
|
|
475
472
|
const moduleRoutes = {};
|
|
476
473
|
for (const module of modules) {
|
|
@@ -518,12 +515,16 @@ function createWebRoutes(dependencies, options = {}) {
|
|
|
518
515
|
wrappedRoutes["/assets/*"] = async (request) => {
|
|
519
516
|
registerRoute("GET", "/assets/*", ["global", "web"]);
|
|
520
517
|
const pathname = new URL(request.url).pathname;
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
518
|
+
try {
|
|
519
|
+
const filePath = assertUrlPathUnderRoot(join2(process.cwd(), "public"), pathname);
|
|
520
|
+
const file = Bun.file(filePath);
|
|
521
|
+
if (!await file.exists()) {
|
|
522
|
+
return notFoundHtmlResponse();
|
|
523
|
+
}
|
|
524
|
+
return new Response(file);
|
|
525
|
+
} catch {
|
|
524
526
|
return notFoundHtmlResponse();
|
|
525
527
|
}
|
|
526
|
-
return new Response(file);
|
|
527
528
|
};
|
|
528
529
|
registerRoute("GET", "/assets/*", ["global", "web"]);
|
|
529
530
|
return wrappedRoutes;
|
|
@@ -108,7 +108,7 @@ import { SessionGuard } from "@getstrata/core/auth/sessionGuard";
|
|
|
108
108
|
import { envFlagEnabled } from "@getstrata/core/runtime/appEnv";
|
|
109
109
|
var authConfig = {
|
|
110
110
|
allowDevHeaders: envFlagEnabled(process.env.AUTH_DEV_HEADERS),
|
|
111
|
-
tokenDefaultAbilities: [
|
|
111
|
+
tokenDefaultAbilities: []
|
|
112
112
|
};
|
|
113
113
|
|
|
114
114
|
// ../../src/bootstrap/config.ts
|
|
@@ -144,7 +144,7 @@ var authProvider = {
|
|
|
144
144
|
config.set("auth.allowDevHeaders", authConfig.allowDevHeaders);
|
|
145
145
|
const apiGuard = new DatabaseTokenGuard(container);
|
|
146
146
|
const sessionGuard = new SessionGuard(container);
|
|
147
|
-
const jwtGuard = new JwtGuard;
|
|
147
|
+
const jwtGuard = new JwtGuard(container);
|
|
148
148
|
const basicGuard = new BasicAuthGuard(container);
|
|
149
149
|
const guards = [apiGuard, jwtGuard, basicGuard, sessionGuard];
|
|
150
150
|
if (authConfig.allowDevHeaders) {
|
|
@@ -189,7 +189,7 @@ import { validateEnv } from "@getstrata/core/config/envSchema";
|
|
|
189
189
|
var appConfig = {
|
|
190
190
|
name: process.env.APP_NAME?.trim() || "Strata",
|
|
191
191
|
env: process.env.APP_ENV ?? "local",
|
|
192
|
-
debug: (process.env.APP_DEBUG ?? "
|
|
192
|
+
debug: (process.env.APP_DEBUG ?? "false") === "true",
|
|
193
193
|
url: process.env.APP_URL ?? "http://localhost:3000",
|
|
194
194
|
apiPrefix: process.env.API_PREFIX ?? "/api/v1"
|
|
195
195
|
};
|
package/dist/entries/health.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { getBoundDatabaseConnection } from "@getstrata/core/database/boundConnection";
|
|
4
4
|
import { getDefaultDatabasePool } from "@getstrata/core/database/defaultConnection";
|
|
5
5
|
import { jsonResponse } from "@getstrata/core/http/response";
|
|
6
|
+
import { envFlagEnabled } from "@getstrata/core/runtime/appEnv";
|
|
6
7
|
var {RedisClient } = globalThis.Bun;
|
|
7
8
|
|
|
8
9
|
// ../../src/bootstrap/config.ts
|
|
@@ -114,10 +115,10 @@ function createHealthRoutes(dependencies, options = {}) {
|
|
|
114
115
|
"/ready": async () => {
|
|
115
116
|
const extra = await resolveExtraFields(options.extra);
|
|
116
117
|
const { checks, ready } = await collectDependencyChecks(dependencies);
|
|
118
|
+
const debug = envFlagEnabled(process.env.APP_DEBUG);
|
|
117
119
|
return jsonResponse({
|
|
118
120
|
status: ready ? "ready" : "not_ready",
|
|
119
|
-
checks,
|
|
120
|
-
...extra
|
|
121
|
+
...debug ? { checks, ...extra } : {}
|
|
121
122
|
}, { status: ready ? 200 : 503 });
|
|
122
123
|
}
|
|
123
124
|
};
|
|
@@ -134,8 +134,9 @@ class HttpKernel {
|
|
|
134
134
|
case "web":
|
|
135
135
|
return isViewsEnabled() ? [createFlashMiddleware(), createCsrfMiddleware()] : [];
|
|
136
136
|
case "api": {
|
|
137
|
+
const csrf = createCsrfMiddleware();
|
|
137
138
|
if (!this.dependencies.container.has(CORE_CONFIG_TOKEN)) {
|
|
138
|
-
return [];
|
|
139
|
+
return [csrf];
|
|
139
140
|
}
|
|
140
141
|
const config = this.dependencies.container.resolve(CORE_CONFIG_TOKEN);
|
|
141
142
|
const redisUrl = config.get(REDIS_URL_CONFIG_KEY)?.trim() ?? "";
|
|
@@ -145,7 +146,8 @@ class HttpKernel {
|
|
|
145
146
|
createMemoryThrottleMiddleware({
|
|
146
147
|
maxAttempts: Number.isFinite(maxAttempts) ? maxAttempts : 120,
|
|
147
148
|
decaySeconds: 60
|
|
148
|
-
})
|
|
149
|
+
}),
|
|
150
|
+
csrf
|
|
149
151
|
];
|
|
150
152
|
}
|
|
151
153
|
const maxAttempts = Number(process.env.RATE_LIMIT_PER_MINUTE ?? "120");
|
|
@@ -154,7 +156,8 @@ class HttpKernel {
|
|
|
154
156
|
redisUrl,
|
|
155
157
|
maxAttempts: Number.isFinite(maxAttempts) ? maxAttempts : 120,
|
|
156
158
|
decaySeconds: 60
|
|
157
|
-
})
|
|
159
|
+
}),
|
|
160
|
+
csrf
|
|
158
161
|
];
|
|
159
162
|
}
|
|
160
163
|
default:
|
|
@@ -164,13 +167,14 @@ class HttpKernel {
|
|
|
164
167
|
wrap(groups, handler) {
|
|
165
168
|
const names = Array.isArray(groups) ? groups : [groups];
|
|
166
169
|
const middleware = names.flatMap((name) => this.group(name));
|
|
167
|
-
|
|
168
|
-
|
|
170
|
+
const wrapped = middleware.length === 0 ? handler : withMiddleware(...middleware)(handler);
|
|
171
|
+
if (names.includes("api")) {
|
|
172
|
+
return withJsonErrorHandling(wrapped);
|
|
169
173
|
}
|
|
170
|
-
return
|
|
174
|
+
return wrapped;
|
|
171
175
|
}
|
|
172
176
|
wrapApi(handler) {
|
|
173
|
-
return
|
|
177
|
+
return this.wrap(["api", "authenticated"], handler);
|
|
174
178
|
}
|
|
175
179
|
wrapWeb(handler) {
|
|
176
180
|
return withErrorHandling(this.wrap("web", handler));
|
|
@@ -279,41 +283,28 @@ class HttpKernel {
|
|
|
279
283
|
return [createRequireVerifiedMiddleware(auth)];
|
|
280
284
|
}
|
|
281
285
|
wrapThrottle(scope, rateLimit, handler) {
|
|
282
|
-
const middleware = [];
|
|
283
286
|
const memoryKeyPrefix = scope === "login" ? "login-throttle:" : "register-throttle:";
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
decaySeconds: rateLimit.decaySeconds,
|
|
296
|
-
keyPrefix: memoryKeyPrefix
|
|
297
|
-
});
|
|
298
|
-
middleware.push(throttle);
|
|
299
|
-
} else {
|
|
300
|
-
middleware.push(createMemoryThrottleMiddleware({
|
|
301
|
-
maxAttempts: rateLimit.maxAttempts,
|
|
302
|
-
decaySeconds: rateLimit.decaySeconds,
|
|
303
|
-
keyPrefix: memoryKeyPrefix
|
|
304
|
-
}));
|
|
305
|
-
}
|
|
306
|
-
} else {
|
|
307
|
-
middleware.push(createMemoryThrottleMiddleware({
|
|
287
|
+
const redisUrl = this.dependencies.container.has(CORE_CONFIG_TOKEN) ? this.dependencies.container.resolve(CORE_CONFIG_TOKEN).get(REDIS_URL_CONFIG_KEY)?.trim() ?? "" : "";
|
|
288
|
+
if (scope === "login") {
|
|
289
|
+
return withMiddleware(createLoginThrottleMiddleware({
|
|
290
|
+
...redisUrl ? { redisUrl } : {},
|
|
291
|
+
maxAttempts: rateLimit.maxAttempts,
|
|
292
|
+
decaySeconds: rateLimit.decaySeconds
|
|
293
|
+
}))(handler);
|
|
294
|
+
}
|
|
295
|
+
if (redisUrl) {
|
|
296
|
+
return withMiddleware(createThrottleMiddleware({
|
|
297
|
+
redisUrl,
|
|
308
298
|
maxAttempts: rateLimit.maxAttempts,
|
|
309
299
|
decaySeconds: rateLimit.decaySeconds,
|
|
310
300
|
keyPrefix: memoryKeyPrefix
|
|
311
|
-
}));
|
|
312
|
-
}
|
|
313
|
-
if (middleware.length === 0) {
|
|
314
|
-
return handler;
|
|
301
|
+
}))(handler);
|
|
315
302
|
}
|
|
316
|
-
return withMiddleware(
|
|
303
|
+
return withMiddleware(createMemoryThrottleMiddleware({
|
|
304
|
+
maxAttempts: rateLimit.maxAttempts,
|
|
305
|
+
decaySeconds: rateLimit.decaySeconds,
|
|
306
|
+
keyPrefix: memoryKeyPrefix
|
|
307
|
+
}))(handler);
|
|
317
308
|
}
|
|
318
309
|
}
|
|
319
310
|
function createHttpKernel(dependencies) {
|