@getstrata/core 0.5.59 → 0.5.61

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.
Files changed (31) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +2 -2
  3. package/dist/core/database/bindConnection.d.ts +1 -3
  4. package/dist/core/database/bunSql.d.ts +13 -0
  5. package/dist/core/http/contentSecurityPolicy.d.ts +27 -0
  6. package/dist/core/http/memoryThrottleMiddleware.d.ts +2 -1
  7. package/dist/core/http/requestMetaContext.d.ts +1 -0
  8. package/dist/core/http/safeInternalPath.d.ts +4 -0
  9. package/dist/core/http/securedRouteModelBinding.d.ts +2 -0
  10. package/dist/core/http/securityHeadersMiddleware.d.ts +5 -2
  11. package/dist/core/http/webErrorResponse.d.ts +1 -1
  12. package/dist/core/view/etaViewEngine.d.ts +3 -2
  13. package/dist/core/view/htmlResponse.d.ts +5 -2
  14. package/dist/core/view/index.d.ts +4 -1
  15. package/dist/core/view/viewEngine.d.ts +1 -0
  16. package/dist/core/view/webErrorView.d.ts +21 -0
  17. package/dist/entries/database/bunSql.js +1 -0
  18. package/dist/entries/http/contentSecurityPolicy.js +1 -0
  19. package/dist/entries/http/memoryThrottleMiddleware.js +10 -2
  20. package/dist/entries/http/requireWebAuthMiddleware.js +34 -2
  21. package/dist/entries/http/response.js +133 -555
  22. package/dist/entries/http/safeInternalPath.js +38 -0
  23. package/dist/entries/http/securedRouteModelBinding.js +27 -5
  24. package/dist/entries/http/securityHeadersMiddleware.js +33 -60
  25. package/dist/entries/http/webErrorResponse.js +132 -554
  26. package/dist/entries/logging/requestLoggingMiddleware.js +2 -1
  27. package/dist/entries/view.js +96 -9
  28. package/dist/framework/public-api.d.ts +7 -3
  29. package/dist/index.js +665 -384
  30. package/package.json +17 -2
  31. package/dist/config/contentSecurityPolicy.d.ts +0 -5
@@ -1,6 +1,6 @@
1
1
  // @bun
2
2
  // ../../src/core/logging/requestLoggingMiddleware.ts
3
- import { runWithRequestMeta } from "@getstrata/core/http/requestMetaContext";
3
+ import { currentRequestMeta, runWithRequestMeta } from "@getstrata/core/http/requestMetaContext";
4
4
 
5
5
  // ../../src/core/http/clientIp.ts
6
6
  function trustForwardedFor(env = process.env) {
@@ -57,6 +57,7 @@ var appLogger = new Logger("app");
57
57
  function createRequestLoggingMiddleware() {
58
58
  return async (request, next) => {
59
59
  return await runWithRequestMeta({
60
+ ...currentRequestMeta(),
60
61
  ipAddress: readClientIp(request) ?? null,
61
62
  userAgent: request.headers.get("user-agent"),
62
63
  request
@@ -1,6 +1,7 @@
1
1
  // @bun
2
2
  // ../../src/core/view/etaViewEngine.ts
3
3
  import { join, relative } from "path";
4
+ import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
4
5
  import { Eta } from "eta";
5
6
 
6
7
  // ../../src/core/view/assertEtaHtmlSource.ts
@@ -198,7 +199,8 @@ class EtaViewEngine {
198
199
  }
199
200
  async render(name, data = {}, options = {}) {
200
201
  const template = name.endsWith(".eta") ? name : `${name}.eta`;
201
- const layoutData = this.resolveLayoutData ? await this.resolveLayoutData() : {};
202
+ const request = options.request ?? currentRequestMeta().request;
203
+ const layoutData = this.resolveLayoutData ? await this.resolveLayoutData(request) : {};
202
204
  const mergedData = { ...layoutData, ...data };
203
205
  const body = await this.eta.renderAsync(template, mergedData);
204
206
  const layout = options.layout ?? DEFAULT_LAYOUT;
@@ -213,6 +215,9 @@ class EtaViewEngine {
213
215
  }
214
216
  }
215
217
  // ../../src/core/view/htmlResponse.ts
218
+ function withCharset(contentType) {
219
+ return contentType.includes("charset=") ? contentType : `${contentType}; charset=utf-8`;
220
+ }
216
221
  function htmlResponse(html, init = {}) {
217
222
  return new Response(html, {
218
223
  status: init.status ?? 200,
@@ -233,9 +238,6 @@ function redirectResponse(location, status = 302) {
233
238
  }
234
239
  });
235
240
  }
236
- function notFoundHtmlResponse(body = "Not Found") {
237
- return htmlResponse(body, { status: 404 });
238
- }
239
241
  function textResponse(body, init = {}) {
240
242
  return new Response(body, {
241
243
  status: init.status ?? 200,
@@ -248,13 +250,91 @@ function xmlResponse(body, init = {}) {
248
250
  return new Response(body, {
249
251
  status: init.status ?? 200,
250
252
  headers: {
251
- "Content-Type": "application/xml; charset=utf-8"
253
+ "Content-Type": withCharset(init.contentType ?? "application/xml")
252
254
  }
253
255
  });
254
256
  }
257
+ function rssResponse(body, init = {}) {
258
+ return xmlResponse(body, { ...init, contentType: "application/rss+xml" });
259
+ }
260
+ // ../../src/core/view/webErrorView.ts
261
+ import { currentRequestMeta as currentRequestMeta2 } from "@getstrata/core/http/requestMetaContext";
262
+ var configuredErrorView = {};
263
+ function configureWebErrorView(options) {
264
+ configuredErrorView = { ...options };
265
+ }
266
+ function escapeHtml(value) {
267
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
268
+ }
269
+ function renderKernelErrorChrome(input) {
270
+ const title = escapeHtml(input.title);
271
+ const message = escapeHtml(input.message);
272
+ const errorLines = Object.entries(input.errors ?? {}).flatMap(([field, messages]) => messages.map((item) => `${field}: ${item}`)).map((line) => `<li>${escapeHtml(line)}</li>`).join("");
273
+ const details = errorLines ? `<ul class="error-list">${errorLines}</ul>` : "";
274
+ const goBack = input.status === 422 ? `<p><a href="javascript:history.back()">Go back</a></p>` : "";
275
+ return `<!doctype html>
276
+ <html lang="en">
277
+ <head>
278
+ <meta charset="UTF-8" />
279
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
280
+ <title>${title}</title>
281
+ <link rel="stylesheet" href="/assets/app.css" />
282
+ </head>
283
+ <body>
284
+ <header class="site-header">
285
+ <a class="brand" href="/">Home</a>
286
+ </header>
287
+ <main class="site-main">
288
+ <section class="page-header">
289
+ <h1>${title}</h1>
290
+ <p>${message}</p>
291
+ ${details}
292
+ ${goBack}
293
+ </section>
294
+ </main>
295
+ </body>
296
+ </html>
297
+ `;
298
+ }
299
+ function errorTemplateName(status) {
300
+ if (status === 404) {
301
+ return "errors/not-found";
302
+ }
303
+ if (status === 403) {
304
+ return "errors/forbidden";
305
+ }
306
+ return "errors/error";
307
+ }
308
+ async function renderWebErrorHtml(input) {
309
+ const render = configuredErrorView.render;
310
+ if (!render) {
311
+ return renderKernelErrorChrome(input);
312
+ }
313
+ try {
314
+ return await render({
315
+ ...input,
316
+ request: input.request ?? currentRequestMeta2().request
317
+ });
318
+ } catch {
319
+ return renderKernelErrorChrome(input);
320
+ }
321
+ }
322
+ async function htmlErrorResponse(input) {
323
+ return htmlResponse(await renderWebErrorHtml(input), { status: input.status });
324
+ }
325
+ async function notFoundHtmlResponse(body) {
326
+ if (body !== undefined) {
327
+ return htmlResponse(body, { status: 404 });
328
+ }
329
+ return htmlErrorResponse({
330
+ status: 404,
331
+ title: "Not Found",
332
+ message: "The page you requested was not found."
333
+ });
334
+ }
255
335
  // ../../src/core/view/webLayoutData.ts
256
336
  import { currentAuthUser } from "@getstrata/core/auth/authContext";
257
- import { currentRequestMeta as currentRequestMeta2 } from "@getstrata/core/http/requestMetaContext";
337
+ import { currentRequestMeta as currentRequestMeta4 } from "@getstrata/core/http/requestMetaContext";
258
338
 
259
339
  // ../../src/core/contracts/serviceTokens.ts
260
340
  var CORE_CONFIG_TOKEN = "core.config";
@@ -351,7 +431,7 @@ var requestMetaContext = createAsyncContextStore("@getstrata/requestMetaContext"
351
431
  function runWithRequestMeta(meta, callback) {
352
432
  return requestMetaContext.run(meta, callback);
353
433
  }
354
- function currentRequestMeta() {
434
+ function currentRequestMeta3() {
355
435
  return requestMetaContext.getStore() ?? {
356
436
  ipAddress: null,
357
437
  userAgent: null
@@ -433,7 +513,7 @@ function verifyCsrfToken(request, submittedToken) {
433
513
  return Bun.CSRF.verify(submittedToken, csrfVerifyOptions());
434
514
  }
435
515
  function resolveCsrfTokenForRequest(request) {
436
- const metaToken = currentRequestMeta().csrfToken;
516
+ const metaToken = currentRequestMeta3().csrfToken;
437
517
  if (metaToken) {
438
518
  return metaToken;
439
519
  }
@@ -578,7 +658,7 @@ async function defaultLoadLayoutUser(container, _request) {
578
658
  async function resolveWebLayoutData(container, request, options = {}) {
579
659
  const resolved = { ...configuredLayoutOptions, ...options };
580
660
  const csrfToken = request ? resolveCsrfTokenForRequest(request) : "";
581
- const flash = request ? currentRequestMeta2().flash ?? pullFlash(request) : null;
661
+ const flash = request ? currentRequestMeta4().flash ?? pullFlash(request) : null;
582
662
  const userKey = layoutUserKey(resolved);
583
663
  const loadUser = resolved.loadUser ?? defaultLoadLayoutUser;
584
664
  const user = await loadUser(container, request);
@@ -587,18 +667,25 @@ async function resolveWebLayoutData(container, request, options = {}) {
587
667
  [userKey]: user,
588
668
  csrfToken,
589
669
  flash,
670
+ cspNonce: currentRequestMeta4().cspNonce ?? "",
590
671
  ...extra
591
672
  };
592
673
  }
593
674
  export {
594
675
  DEFAULT_VIEWS_DIRECTORY,
595
676
  EtaViewEngine,
677
+ configureWebErrorView,
596
678
  configureWebLayoutData,
679
+ errorTemplateName,
680
+ htmlErrorResponse,
597
681
  htmlResponse,
598
682
  isHtmxRequest,
599
683
  notFoundHtmlResponse,
600
684
  redirectResponse,
685
+ renderKernelErrorChrome,
686
+ renderWebErrorHtml,
601
687
  resolveWebLayoutData,
688
+ rssResponse,
602
689
  textResponse,
603
690
  xmlResponse
604
691
  };
@@ -24,7 +24,8 @@ export type { ServiceProvider } from "../core/contracts/di.ts";
24
24
  export { resolveService } from "../core/contracts/di.ts";
25
25
  export type { DatabaseConnection } from "../core/database/baseRepository.ts";
26
26
  export { default as BaseRepository } from "../core/database/baseRepository.ts";
27
- export { bindDatabaseConnection } from "../core/database/bindConnection.ts";
27
+ export { bindDatabaseConnection, getBoundDatabaseConnection, resetBoundDatabaseConnection, } from "../core/database/bindConnection.ts";
28
+ export { bindBunSql, createBunSqlPool } from "../core/database/bunSql.ts";
28
29
  export { createDatabaseConnection } from "../core/database/connection.ts";
29
30
  export { getActiveDatabaseConnection, runWithDatabaseConnection, } from "../core/database/connectionContext.ts";
30
31
  export { getDefaultDatabasePool, getDefaultDatabaseQuery, registerDefaultDatabasePool, } from "../core/database/defaultConnection.ts";
@@ -55,6 +56,8 @@ export { auth, cache, config, events, log, mail, policyGate, queue, storage, } f
55
56
  export { createBodySizeLimitMiddleware } from "../core/http/bodySizeLimitMiddleware.ts";
56
57
  export { readClientIp, trustForwardedFor } from "../core/http/clientIp.ts";
57
58
  export { conditionalJsonResponse } from "../core/http/conditionalResponse.ts";
59
+ export type { ContentSecurityPolicyDirectives, ContentSecurityPolicyOptions, } from "../core/http/contentSecurityPolicy.ts";
60
+ export { configureContentSecurityPolicy, generateCspNonce, resolveContentSecurityPolicy, resolveHtmlContentSecurityPolicy, serverHtmxContentSecurityPolicy, spaContentSecurityPolicy, strictApiContentSecurityPolicy, } from "../core/http/contentSecurityPolicy.ts";
58
61
  export { readBunRequestCookie, readRequestCookie } from "../core/http/cookies.ts";
59
62
  export { createCorsMiddleware } from "../core/http/corsMiddleware.ts";
60
63
  export { createCsrfMiddleware } from "../core/http/csrfMiddleware.ts";
@@ -65,7 +68,7 @@ export { createFlashMiddleware } from "../core/http/flashMiddleware.ts";
65
68
  export { FormRequest } from "../core/http/formRequest.ts";
66
69
  export { applyMiddlewareToRoutes, bindRouteModel, buildRequestCacheKey, composeMiddleware, createAuthMiddleware, createAuthorizeMiddleware, createRequireAuthMiddleware, paginatedResponse, parsePaginationQuery, parsePositiveIntParam, requestIdMiddleware, securedBindRouteModel, securedBindRouteModelByKey, withMiddleware, wrapRouteHandler, } from "../core/http/index.ts";
67
70
  export { createLoginThrottleMiddleware } from "../core/http/loginThrottleMiddleware.ts";
68
- export { createMemoryThrottleMiddleware } from "../core/http/memoryThrottleMiddleware.ts";
71
+ export { createMemoryThrottleMiddleware, resetMemoryThrottleForTests, } from "../core/http/memoryThrottleMiddleware.ts";
69
72
  export { createMetricsMiddleware, normalizeMetricPath } from "../core/http/metricsMiddleware.ts";
70
73
  export type { Middleware, RouteHandler } from "../core/http/middleware.ts";
71
74
  export { type ParsedUpload, parseMultipartUpload } from "../core/http/parseMultipartUpload.ts";
@@ -77,6 +80,7 @@ export { createRequireWebAuthMiddleware } from "../core/http/requireWebAuthMiddl
77
80
  export { serializeDate, toPaginatedResourceCollection, toResourceCollection, } from "../core/http/resources.ts";
78
81
  export { createdResponse, jsonResponse, noContentResponse, withErrorHandling, } from "../core/http/response.ts";
79
82
  export type { RouteRequest } from "../core/http/route.ts";
83
+ export { loginRedirectLocation, safeInternalRedirectPath, sanitizeInternalPath, } from "../core/http/safeInternalPath.ts";
80
84
  export { createScimThrottleMiddleware } from "../core/http/scimThrottleMiddleware.ts";
81
85
  export { createSecurityHeadersMiddleware } from "../core/http/securityHeadersMiddleware.ts";
82
86
  export { createThrottleMiddleware } from "../core/http/throttleMiddleware.ts";
@@ -118,6 +122,6 @@ export { currentTraceId, runWithTraceContext } from "../core/tracing/traceContex
118
122
  export { createTracingMiddleware } from "../core/tracing/tracingMiddleware.ts";
119
123
  export type { ValidationRule, ValidationSchema } from "../core/validation/rules.ts";
120
124
  export { emailRule, maxLength, minLength, required, stringRule, validateObject, } from "../core/validation/rules.ts";
121
- export { configureWebLayoutData, DEFAULT_VIEWS_DIRECTORY, EtaViewEngine, htmlResponse, isHtmxRequest, notFoundHtmlResponse, redirectResponse, resolveWebLayoutData, textResponse, xmlResponse, } from "../core/view/index.ts";
125
+ export { configureWebErrorView, configureWebLayoutData, DEFAULT_VIEWS_DIRECTORY, EtaViewEngine, errorTemplateName, htmlErrorResponse, htmlResponse, isHtmxRequest, notFoundHtmlResponse, redirectResponse, renderKernelErrorChrome, resolveWebLayoutData, rssResponse, textResponse, xmlResponse, } from "../core/view/index.ts";
122
126
  export type { ViewEngine } from "../core/view/viewEngine.ts";
123
127
  export type { WebLayoutAuthUser, WebLayoutData, WebLayoutDataOptions, WebLayoutUserKey, } from "../core/view/webLayoutData.ts";