@getstrata/core 1.0.3 → 1.0.4

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 (49) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/core/runtime/appEnv.d.ts +16 -2
  3. package/dist/core/runtime/appKeyPrefix.d.ts +8 -1
  4. package/dist/entries/audit/exportAuditLogs.js +83 -18
  5. package/dist/entries/audit/siemFormatter.js +32 -3
  6. package/dist/entries/auth/intendedUrlCookie.js +33 -4
  7. package/dist/entries/auth/jwt.js +36 -4
  8. package/dist/entries/auth/jwtGuard.js +36 -4
  9. package/dist/entries/auth/oauth/providers.js +32 -3
  10. package/dist/entries/auth/oauth/samlProvider.js +32 -3
  11. package/dist/entries/auth/passwordConfirmCookie.js +35 -6
  12. package/dist/entries/auth/sessionCookie.js +35 -6
  13. package/dist/entries/auth/sessionGuard.js +35 -6
  14. package/dist/entries/auth/tokenHash.js +33 -4
  15. package/dist/entries/cache/createCacheStore.js +32 -3
  16. package/dist/entries/facades.js +32 -3
  17. package/dist/entries/http/corsMiddleware.js +16 -1
  18. package/dist/entries/http/csrfMiddleware.js +34 -5
  19. package/dist/entries/http/csrfToken.js +34 -5
  20. package/dist/entries/http/flashMiddleware.js +33 -4
  21. package/dist/entries/http/flashSession.js +33 -4
  22. package/dist/entries/http/loginThrottleMiddleware.js +1 -373
  23. package/dist/entries/http/memoryThrottleMiddleware.js +32 -58
  24. package/dist/entries/http/response.js +16 -56
  25. package/dist/entries/http/scimThrottleMiddleware.js +32 -3
  26. package/dist/entries/http/securityHeadersMiddleware.js +32 -3
  27. package/dist/entries/http/signedUrl.js +33 -4
  28. package/dist/entries/http/throttleMiddleware.js +32 -58
  29. package/dist/entries/http/webErrorResponse.js +1 -401
  30. package/dist/entries/jobs/exportAuditLogsJob.js +83 -18
  31. package/dist/entries/lifecycle/gracefulShutdown.js +1 -50
  32. package/dist/entries/mail/mailer.js +32 -3
  33. package/dist/entries/openapi/generator.js +32 -3
  34. package/dist/entries/queue/createAppQueue.js +32 -3
  35. package/dist/entries/queue/publicQueue.js +32 -3
  36. package/dist/entries/queue/queueMetrics.js +32 -3
  37. package/dist/entries/queue/redisQueue.js +32 -3
  38. package/dist/entries/runtime/appEnv.js +18 -1
  39. package/dist/entries/runtime/appKeyPrefix.js +1 -70
  40. package/dist/entries/security/oauthState.js +33 -4
  41. package/dist/entries/security/safeFetch.js +32 -9
  42. package/dist/entries/security/safeUrl.js +1 -105
  43. package/dist/entries/security/totp.js +32 -3
  44. package/dist/entries/tenant/databaseTenantContext.js +48 -6
  45. package/dist/entries/tracing/tracingMiddleware.js +32 -3
  46. package/dist/entries/view.js +1 -778
  47. package/dist/framework/public-api.d.ts +27 -18
  48. package/dist/index.js +442 -53
  49. package/package.json +2 -2
@@ -1,778 +1 @@
1
- // @bun
2
- // ../../src/core/view/etaViewEngine.ts
3
- import { join, relative } from "path";
4
- import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
5
-
6
- // ../../src/core/runtime/optionalPeer.ts
7
- function missingOptionalPeer(packageName, reason, error) {
8
- return new Error(`Install ${packageName} ${reason} (\`bun add ${packageName}\`).`, {
9
- cause: error
10
- });
11
- }
12
-
13
- // ../../src/core/view/assertEtaHtmlSource.ts
14
- var HTML_TAGS = new Set([
15
- "a",
16
- "abbr",
17
- "address",
18
- "article",
19
- "aside",
20
- "audio",
21
- "b",
22
- "blockquote",
23
- "body",
24
- "button",
25
- "canvas",
26
- "caption",
27
- "cite",
28
- "code",
29
- "col",
30
- "colgroup",
31
- "data",
32
- "datalist",
33
- "dd",
34
- "del",
35
- "details",
36
- "dfn",
37
- "dialog",
38
- "div",
39
- "dl",
40
- "dt",
41
- "em",
42
- "fieldset",
43
- "figcaption",
44
- "figure",
45
- "footer",
46
- "form",
47
- "h1",
48
- "h2",
49
- "h3",
50
- "h4",
51
- "h5",
52
- "h6",
53
- "header",
54
- "hgroup",
55
- "hr",
56
- "html",
57
- "i",
58
- "iframe",
59
- "img",
60
- "input",
61
- "ins",
62
- "kbd",
63
- "label",
64
- "legend",
65
- "li",
66
- "main",
67
- "map",
68
- "mark",
69
- "menu",
70
- "meter",
71
- "nav",
72
- "object",
73
- "ol",
74
- "optgroup",
75
- "option",
76
- "output",
77
- "p",
78
- "picture",
79
- "pre",
80
- "progress",
81
- "q",
82
- "rp",
83
- "rt",
84
- "ruby",
85
- "s",
86
- "samp",
87
- "script",
88
- "search",
89
- "section",
90
- "select",
91
- "slot",
92
- "small",
93
- "source",
94
- "span",
95
- "strong",
96
- "style",
97
- "sub",
98
- "summary",
99
- "sup",
100
- "table",
101
- "tbody",
102
- "td",
103
- "template",
104
- "textarea",
105
- "tfoot",
106
- "th",
107
- "thead",
108
- "time",
109
- "title",
110
- "tr",
111
- "track",
112
- "u",
113
- "ul",
114
- "var",
115
- "video",
116
- "wbr"
117
- ]);
118
- var TAG_PATTERN = "([a-z][a-z0-9]*)";
119
- var CLASS_OR_ID_PATTERN = "[.#][\\w-]+";
120
- var CLASS_ID_SHORTHAND = new RegExp(`^${TAG_PATTERN}(?:${CLASS_OR_ID_PATTERN})+`);
121
- var ATTRIBUTE_SHORTHAND = new RegExp(`^${TAG_PATTERN}(?:${CLASS_OR_ID_PATTERN})*\\s+[\\w:-]+=`);
122
- var TAG_SHORTHAND = new RegExp(`^${TAG_PATTERN}(?:>|\\s+\\S|$)`);
123
- function htmlTagName(match) {
124
- const tag = match?.[1];
125
- return tag && HTML_TAGS.has(tag) ? tag : undefined;
126
- }
127
- function describePugLikeLine(line) {
128
- if (/^\|(?:\s|$)/.test(line)) {
129
- return "piped text";
130
- }
131
- if (htmlTagName(line.match(CLASS_ID_SHORTHAND))) {
132
- return "class/id shorthand";
133
- }
134
- if (htmlTagName(line.match(ATTRIBUTE_SHORTHAND))) {
135
- return "attribute shorthand";
136
- }
137
- if (htmlTagName(line.match(TAG_SHORTHAND))) {
138
- return "tag shorthand";
139
- }
140
- return;
141
- }
142
- function updateSkipBlock(lower, skipBlock) {
143
- if (skipBlock) {
144
- if (skipBlock === "comment" && lower.includes("-->")) {
145
- return null;
146
- }
147
- if (skipBlock !== "comment" && lower.includes(`</${skipBlock}`)) {
148
- return null;
149
- }
150
- return skipBlock;
151
- }
152
- if (lower.startsWith("<!--") && !lower.includes("-->")) {
153
- return "comment";
154
- }
155
- if (lower.startsWith("<script") && !lower.includes("</script")) {
156
- return "script";
157
- }
158
- if (lower.startsWith("<style") && !lower.includes("</style")) {
159
- return "style";
160
- }
161
- return null;
162
- }
163
- function assertEtaHtmlSource(templateName, source) {
164
- let skipBlock = null;
165
- for (const rawLine of source.split(/\r?\n/)) {
166
- const trimmed = rawLine.trim();
167
- if (!trimmed) {
168
- continue;
169
- }
170
- const lower = trimmed.toLowerCase();
171
- const nextSkipBlock = updateSkipBlock(lower, skipBlock);
172
- if (skipBlock || nextSkipBlock) {
173
- skipBlock = nextSkipBlock;
174
- continue;
175
- }
176
- if (trimmed.startsWith("<") || trimmed.startsWith("<%")) {
177
- continue;
178
- }
179
- const kind = describePugLikeLine(trimmed);
180
- if (kind) {
181
- throw new Error(`Eta view "${templateName}" contains Pug-like markup (${kind}: ${trimmed}). Eta views must be HTML + <% %> tags, not Pug.`);
182
- }
183
- }
184
- }
185
-
186
- // ../../src/core/view/etaViewEngine.ts
187
- var DEFAULT_VIEWS_DIRECTORY = join(process.cwd(), "resources/views");
188
- var DEFAULT_LAYOUT = "layouts/app.eta";
189
-
190
- class EtaViewEngine {
191
- eta = null;
192
- etaPending = null;
193
- viewsDirectory;
194
- resolveLayoutData;
195
- constructor(viewsDirectory = DEFAULT_VIEWS_DIRECTORY, resolveLayoutData) {
196
- this.viewsDirectory = viewsDirectory;
197
- this.resolveLayoutData = resolveLayoutData;
198
- }
199
- async getEta() {
200
- if (this.eta) {
201
- return this.eta;
202
- }
203
- if (!this.etaPending) {
204
- this.etaPending = this.createEta();
205
- }
206
- return this.etaPending;
207
- }
208
- async createEta() {
209
- let eta;
210
- try {
211
- const mod = await import("eta");
212
- eta = new mod.Eta({
213
- views: this.viewsDirectory,
214
- autoTrim: false
215
- });
216
- } catch (error) {
217
- this.etaPending = null;
218
- throw missingOptionalPeer("eta", "to render HTML views", error);
219
- }
220
- const readFile = eta.readFile?.bind(eta);
221
- eta.readFile = (path) => {
222
- const source = readFile ? readFile(path) : "";
223
- assertEtaHtmlSource(relative(this.viewsDirectory, path) || path, source);
224
- return source;
225
- };
226
- this.eta = eta;
227
- return eta;
228
- }
229
- async render(name, data = {}, options = {}) {
230
- const template = name.endsWith(".eta") ? name : `${name}.eta`;
231
- const request = options.request ?? currentRequestMeta().request;
232
- const layoutData = this.resolveLayoutData ? await this.resolveLayoutData(request) : {};
233
- const mergedData = { ...layoutData, ...data };
234
- const eta = await this.getEta();
235
- const body = await eta.renderAsync(template, mergedData);
236
- const layout = options.layout ?? DEFAULT_LAYOUT;
237
- if (layout === false) {
238
- return body;
239
- }
240
- const layoutTemplate = layout.endsWith(".eta") ? layout : `${layout}.eta`;
241
- return await eta.renderAsync(layoutTemplate, {
242
- ...mergedData,
243
- body
244
- });
245
- }
246
- }
247
- // ../../src/core/view/htmlResponse.ts
248
- function withCharset(contentType) {
249
- return contentType.includes("charset=") ? contentType : `${contentType}; charset=utf-8`;
250
- }
251
- function htmlResponse(html, init = {}) {
252
- return new Response(html, {
253
- status: init.status ?? 200,
254
- statusText: init.statusText,
255
- headers: {
256
- "Content-Type": "text/html; charset=utf-8"
257
- }
258
- });
259
- }
260
- function isHtmxRequest(request) {
261
- return request.headers.get("HX-Request") === "true";
262
- }
263
- function redirectResponse(location, status = 302) {
264
- return new Response(null, {
265
- status,
266
- headers: {
267
- Location: location
268
- }
269
- });
270
- }
271
- function textResponse(body, init = {}) {
272
- return new Response(body, {
273
- status: init.status ?? 200,
274
- headers: {
275
- "Content-Type": "text/plain; charset=utf-8"
276
- }
277
- });
278
- }
279
- function xmlResponse(body, init = {}) {
280
- return new Response(body, {
281
- status: init.status ?? 200,
282
- headers: {
283
- "Content-Type": withCharset(init.contentType ?? "application/xml")
284
- }
285
- });
286
- }
287
- function rssResponse(body, init = {}) {
288
- return xmlResponse(body, { ...init, contentType: "application/rss+xml" });
289
- }
290
- // ../../src/core/view/webErrorView.ts
291
- import { currentRequestMeta as currentRequestMeta2 } from "@getstrata/core/http/requestMetaContext";
292
- var configuredErrorView = {};
293
- function configureWebErrorView(options) {
294
- configuredErrorView = { ...options };
295
- }
296
- function escapeHtml(value) {
297
- return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
298
- }
299
- function renderKernelErrorChrome(input) {
300
- const title = escapeHtml(input.title);
301
- const message = escapeHtml(input.message);
302
- const errorLines = Object.entries(input.errors ?? {}).flatMap(([field, messages]) => messages.map((item) => `${field}: ${item}`)).map((line) => `<li>${escapeHtml(line)}</li>`).join("");
303
- const details = errorLines ? `<ul class="error-list">${errorLines}</ul>` : "";
304
- const goBack = input.status === 422 ? `<p><a href="javascript:history.back()">Go back</a></p>` : "";
305
- return `<!doctype html>
306
- <html lang="en">
307
- <head>
308
- <meta charset="UTF-8" />
309
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
310
- <title>${title}</title>
311
- <link rel="stylesheet" href="/assets/app.css" />
312
- </head>
313
- <body>
314
- <header class="site-header">
315
- <a class="brand" href="/">Home</a>
316
- </header>
317
- <main class="site-main">
318
- <section class="page-header">
319
- <h1>${title}</h1>
320
- <p>${message}</p>
321
- ${details}
322
- ${goBack}
323
- </section>
324
- </main>
325
- </body>
326
- </html>
327
- `;
328
- }
329
- function errorTemplateName(status) {
330
- if (status === 404) {
331
- return "errors/not-found";
332
- }
333
- if (status === 403) {
334
- return "errors/forbidden";
335
- }
336
- return "errors/error";
337
- }
338
- async function renderWebErrorHtml(input) {
339
- const render = configuredErrorView.render;
340
- if (!render) {
341
- return renderKernelErrorChrome(input);
342
- }
343
- try {
344
- return await render({
345
- ...input,
346
- request: input.request ?? currentRequestMeta2().request
347
- });
348
- } catch {
349
- return renderKernelErrorChrome(input);
350
- }
351
- }
352
- async function htmlErrorResponse(input) {
353
- return htmlResponse(await renderWebErrorHtml(input), { status: input.status });
354
- }
355
- async function notFoundHtmlResponse(body) {
356
- if (body !== undefined) {
357
- return htmlResponse(body, { status: 404 });
358
- }
359
- return htmlErrorResponse({
360
- status: 404,
361
- title: "Not Found",
362
- message: "The page you requested was not found."
363
- });
364
- }
365
- // ../../src/core/view/webLayoutData.ts
366
- import { currentAuthUser } from "@getstrata/core/auth/authContext";
367
- import { currentRequestMeta as currentRequestMeta4 } from "@getstrata/core/http/requestMetaContext";
368
-
369
- // ../../src/core/contracts/serviceTokens.ts
370
- var CORE_CONFIG_TOKEN = "core.config";
371
- var CORE_CACHE_TOKEN = "core.cache";
372
- var CORE_QUEUE_TOKEN = "core.queue";
373
- var CORE_EVENT_BUS_TOKEN = "core.eventBus";
374
- var CORE_POLICY_GATE_TOKEN = "core.policyGate";
375
- var CORE_AUTH_TOKEN = "core.auth";
376
- var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
377
- var CORE_ABILITY_CHECKER_TOKEN = "core.abilityChecker";
378
- var CORE_AUTH_USER_DIRECTORY_TOKEN = "core.authUserDirectory";
379
- function isAbilityChecker(value) {
380
- if (!value || typeof value !== "object") {
381
- return false;
382
- }
383
- const candidate = value;
384
- return typeof candidate.tokenCan === "function" && typeof candidate.requireAbility === "function";
385
- }
386
- function isAuthUserDirectory(value) {
387
- if (!value || typeof value !== "object") {
388
- return false;
389
- }
390
- return typeof value.findByIdOrThrow === "function";
391
- }
392
- function resolveAbilityChecker(container) {
393
- if (container.has(CORE_ABILITY_CHECKER_TOKEN)) {
394
- return container.resolve(CORE_ABILITY_CHECKER_TOKEN);
395
- }
396
- return container.resolve(CORE_TOKEN_SERVICE_TOKEN);
397
- }
398
- function resolveAuthUserDirectory(container) {
399
- if (container.has(CORE_AUTH_USER_DIRECTORY_TOKEN)) {
400
- const directory = container.resolve(CORE_AUTH_USER_DIRECTORY_TOKEN);
401
- if (isAuthUserDirectory(directory)) {
402
- return directory;
403
- }
404
- }
405
- if (container.has(CORE_TOKEN_SERVICE_TOKEN)) {
406
- const tokenService = container.resolve(CORE_TOKEN_SERVICE_TOKEN);
407
- if (isAuthUserDirectory(tokenService)) {
408
- return tokenService;
409
- }
410
- }
411
- return null;
412
- }
413
-
414
- // ../../src/core/http/csrfToken.ts
415
- import { timingSafeEqual } from "crypto";
416
-
417
- // ../../src/core/runtime/appKeyPrefix.ts
418
- function appKeyPrefix() {
419
- return process.env.APP_KEY_PREFIX?.trim() || "strata";
420
- }
421
- function appCookieName(kind) {
422
- return `${appKeyPrefix()}_${kind}`;
423
- }
424
- function appDevSecret(kind) {
425
- return `${appKeyPrefix()}-dev-${kind}`;
426
- }
427
- function namespacedRedisKey(kind) {
428
- return `${appKeyPrefix()}:${kind}`;
429
- }
430
- function smtpEhloHost() {
431
- const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
432
- const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
433
- return safe || "strata.local";
434
- }
435
- function siemEventType() {
436
- return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
437
- }
438
- function appUserAgent() {
439
- return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
440
- }
441
- function otelServiceName() {
442
- return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
443
- }
444
- function webhookSignatureHeader() {
445
- return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
446
- }
447
- function appDisplayName() {
448
- return process.env.APP_NAME?.trim() || "Strata";
449
- }
450
- function appEnv() {
451
- return process.env.APP_ENV?.trim() || "local";
452
- }
453
- function appUrl() {
454
- return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
455
- }
456
- function apiPrefix() {
457
- const raw = process.env.API_PREFIX?.trim() || "/api/v1";
458
- const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
459
- const trimmed = withSlash.replace(/\/+$/, "");
460
- return trimmed || "/api/v1";
461
- }
462
- function sdkClientClassName() {
463
- const override = process.env.APP_SDK_CLASS?.trim();
464
- if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
465
- return override;
466
- }
467
- const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
468
- return fromName ? `${fromName}Client` : "AppClient";
469
- }
470
-
471
- // ../../src/core/http/cookies.ts
472
- function readRequestCookie(request, name) {
473
- const cookies = request.cookies;
474
- if (cookies && typeof cookies.get === "function") {
475
- const value = cookies.get(name);
476
- if (value) {
477
- return value;
478
- }
479
- }
480
- const header = request.headers.get("cookie");
481
- if (!header) {
482
- return null;
483
- }
484
- for (const part of header.split(";")) {
485
- const idx = part.indexOf("=");
486
- if (idx === -1)
487
- continue;
488
- const cookieName = part.slice(0, idx).trim();
489
- if (cookieName !== name)
490
- continue;
491
- return decodeURIComponent(part.slice(idx + 1).trim());
492
- }
493
- return null;
494
- }
495
- function readBunRequestCookie(request, name) {
496
- return request.cookies.get(name) ?? readRequestCookie(request, name);
497
- }
498
-
499
- // ../../src/core/runtime/asyncContextStore.ts
500
- import { AsyncLocalStorage } from "async_hooks";
501
- function createAsyncContextStore(key) {
502
- const symbol = Symbol.for(key);
503
- const globalRecord = globalThis;
504
- const existing = globalRecord[symbol];
505
- if (existing) {
506
- return existing;
507
- }
508
- const store = new AsyncLocalStorage;
509
- globalRecord[symbol] = store;
510
- return store;
511
- }
512
-
513
- // ../../src/core/http/requestMetaContext.ts
514
- var requestMetaContext = createAsyncContextStore("@getstrata/requestMetaContext");
515
- function runWithRequestMeta(meta, callback) {
516
- return requestMetaContext.run(meta, callback);
517
- }
518
- function currentRequestMeta3() {
519
- return requestMetaContext.getStore() ?? {
520
- ipAddress: null,
521
- userAgent: null
522
- };
523
- }
524
-
525
- // ../../src/core/http/csrfToken.ts
526
- var CSRF_COOKIE = appCookieName("csrf");
527
- var CSRF_TTL_MS = 60 * 60 * 1000;
528
- function csrfCookieName() {
529
- return process.env.CSRF_COOKIE_NAME?.trim() || appCookieName("csrf");
530
- }
531
- function resolveCsrfSecret() {
532
- return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || appDevSecret("csrf-secret");
533
- }
534
- function csrfVerifyOptions() {
535
- return { secret: resolveCsrfSecret(), maxAge: CSRF_TTL_MS };
536
- }
537
- function tokensMatch(left, right) {
538
- const leftBuffer = Buffer.from(left);
539
- const rightBuffer = Buffer.from(right);
540
- if (leftBuffer.length !== rightBuffer.length) {
541
- return false;
542
- }
543
- return timingSafeEqual(leftBuffer, rightBuffer);
544
- }
545
- function createCsrfTokenCookie() {
546
- const token = Bun.CSRF.generate(resolveCsrfSecret(), { expiresIn: CSRF_TTL_MS });
547
- const secure = process.env.APP_ENV === "production" ? "; Secure" : "";
548
- return {
549
- token,
550
- cookie: `${csrfCookieName()}=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Max-Age=${Math.floor(CSRF_TTL_MS / 1000)}${secure}`
551
- };
552
- }
553
- function resolveCsrfToken(request) {
554
- const cookieValue = readRequestCookie(request, csrfCookieName());
555
- if (cookieValue && Bun.CSRF.verify(cookieValue, csrfVerifyOptions())) {
556
- return { token: cookieValue };
557
- }
558
- return createCsrfTokenCookie();
559
- }
560
- function readSubmittedCsrfToken(request) {
561
- const headerToken = request.headers.get("x-csrf-token")?.trim();
562
- if (headerToken) {
563
- return headerToken;
564
- }
565
- return null;
566
- }
567
- async function readSubmittedCsrfTokenFromBody(request) {
568
- const headerToken = readSubmittedCsrfToken(request);
569
- if (headerToken) {
570
- return headerToken;
571
- }
572
- const contentType = request.headers.get("content-type")?.toLowerCase() ?? "";
573
- if (contentType.includes("application/x-www-form-urlencoded") || contentType.includes("multipart/form-data")) {
574
- const formData = await request.clone().formData();
575
- const field = formData.get("_token");
576
- if (typeof field === "string" && field.trim().length > 0) {
577
- return field.trim();
578
- }
579
- const legacyField = formData.get("_csrf");
580
- if (typeof legacyField === "string" && legacyField.trim().length > 0) {
581
- return legacyField.trim();
582
- }
583
- }
584
- return null;
585
- }
586
- function verifyCsrfToken(request, submittedToken) {
587
- if (!submittedToken) {
588
- return false;
589
- }
590
- const cookieValue = readRequestCookie(request, csrfCookieName());
591
- if (!cookieValue) {
592
- return false;
593
- }
594
- if (!tokensMatch(submittedToken, cookieValue)) {
595
- return false;
596
- }
597
- return Bun.CSRF.verify(submittedToken, csrfVerifyOptions());
598
- }
599
- function resolveCsrfTokenForRequest(request) {
600
- const metaToken = currentRequestMeta3().csrfToken;
601
- if (metaToken) {
602
- return metaToken;
603
- }
604
- return resolveCsrfToken(request).token;
605
- }
606
-
607
- // ../../src/core/http/flashSession.ts
608
- import { createHmac, timingSafeEqual as timingSafeEqual2 } from "crypto";
609
- var FLASH_COOKIE = appCookieName("flash");
610
- var FLASH_TTL_MS = 60 * 1000;
611
- function flashCookieName() {
612
- return process.env.FLASH_COOKIE_NAME?.trim() || appCookieName("flash");
613
- }
614
- function resolveFlashSecret() {
615
- return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || appDevSecret("flash-secret");
616
- }
617
- function signFlashPayload(payload, issuedAt) {
618
- const signature = createHmac("sha256", resolveFlashSecret()).update(`${payload}.${issuedAt}`).digest("hex");
619
- return `${payload}.${issuedAt}.${signature}`;
620
- }
621
- function readFlashCookie(request) {
622
- const cookieHeader = request.headers.get("cookie");
623
- if (!cookieHeader) {
624
- return null;
625
- }
626
- for (const part of cookieHeader.split(";")) {
627
- const [name, ...rest] = part.trim().split("=");
628
- if (name === flashCookieName()) {
629
- return decodeURIComponent(rest.join("="));
630
- }
631
- }
632
- return null;
633
- }
634
- function parseFlashCookie(cookieValue) {
635
- const parts = cookieValue.split(".");
636
- if (parts.length < 3) {
637
- return null;
638
- }
639
- const signature = parts.pop();
640
- const issuedAtRaw = parts.pop();
641
- const payload = parts.join(".");
642
- if (!signature || !issuedAtRaw || !payload) {
643
- return null;
644
- }
645
- const issuedAt = Number.parseInt(issuedAtRaw, 10);
646
- if (!Number.isFinite(issuedAt) || Date.now() - issuedAt > FLASH_TTL_MS) {
647
- return null;
648
- }
649
- const expectedSignature = signFlashPayload(payload, issuedAt).split(".").pop();
650
- if (!expectedSignature) {
651
- return null;
652
- }
653
- const expectedBuffer = Buffer.from(expectedSignature);
654
- const actualBuffer = Buffer.from(signature);
655
- if (expectedBuffer.length !== actualBuffer.length) {
656
- return null;
657
- }
658
- if (!timingSafeEqual2(expectedBuffer, actualBuffer)) {
659
- return null;
660
- }
661
- try {
662
- const parsed = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
663
- if (!parsed?.message || typeof parsed.message !== "string") {
664
- return null;
665
- }
666
- if (parsed.level !== "success" && parsed.level !== "error" && parsed.level !== "info") {
667
- return null;
668
- }
669
- return parsed;
670
- } catch {
671
- return null;
672
- }
673
- }
674
- function createFlashCookie(message) {
675
- const payload = Buffer.from(JSON.stringify(message), "utf8").toString("base64url");
676
- const issuedAt = Date.now();
677
- const value = signFlashPayload(payload, issuedAt);
678
- return `${flashCookieName()}=${encodeURIComponent(value)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=60`;
679
- }
680
- function clearFlashCookie() {
681
- return `${flashCookieName()}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`;
682
- }
683
- function pullFlash(request) {
684
- const cookieValue = readFlashCookie(request);
685
- if (!cookieValue) {
686
- return null;
687
- }
688
- return parseFlashCookie(cookieValue);
689
- }
690
- function flashResponse(response, message) {
691
- const headers = new Headers(response.headers);
692
- headers.append("set-cookie", createFlashCookie(message));
693
- return new Response(response.body, {
694
- status: response.status,
695
- statusText: response.statusText,
696
- headers
697
- });
698
- }
699
- function withFlashClear(response) {
700
- const headers = new Headers(response.headers);
701
- headers.append("set-cookie", clearFlashCookie());
702
- return new Response(response.body, {
703
- status: response.status,
704
- statusText: response.statusText,
705
- headers
706
- });
707
- }
708
-
709
- // ../../src/core/view/webLayoutData.ts
710
- var configuredLayoutOptions = {};
711
- function configureWebLayoutData(options) {
712
- configuredLayoutOptions = { ...options };
713
- }
714
- function layoutUserKey(options) {
715
- return options.userKey ?? "authUser";
716
- }
717
- async function defaultLoadLayoutUser(container, _request) {
718
- const authUser = currentAuthUser();
719
- if (!authUser) {
720
- return null;
721
- }
722
- const userId = Number(authUser.id);
723
- if (!Number.isInteger(userId) || userId <= 0) {
724
- return null;
725
- }
726
- const directory = resolveAuthUserDirectory(container);
727
- if (!directory) {
728
- return {
729
- id: userId,
730
- email: "",
731
- role: authUser.role ?? "member"
732
- };
733
- }
734
- try {
735
- const user = await directory.findByIdOrThrow(userId);
736
- return {
737
- id: userId,
738
- email: user.email ?? "",
739
- role: authUser.role ?? user.role ?? "member"
740
- };
741
- } catch {
742
- return null;
743
- }
744
- }
745
- async function resolveWebLayoutData(container, request, options = {}) {
746
- const resolved = { ...configuredLayoutOptions, ...options };
747
- const csrfToken = request ? resolveCsrfTokenForRequest(request) : "";
748
- const flash = request ? currentRequestMeta4().flash ?? pullFlash(request) : null;
749
- const userKey = layoutUserKey(resolved);
750
- const loadUser = resolved.loadUser ?? defaultLoadLayoutUser;
751
- const user = await loadUser(container, request);
752
- const extra = typeof resolved.extra === "function" ? await resolved.extra(user) : resolved.extra ?? {};
753
- return {
754
- [userKey]: user,
755
- csrfToken,
756
- flash,
757
- cspNonce: currentRequestMeta4().cspNonce ?? "",
758
- ...extra
759
- };
760
- }
761
- export {
762
- DEFAULT_VIEWS_DIRECTORY,
763
- EtaViewEngine,
764
- configureWebErrorView,
765
- configureWebLayoutData,
766
- errorTemplateName,
767
- htmlErrorResponse,
768
- htmlResponse,
769
- isHtmxRequest,
770
- notFoundHtmlResponse,
771
- redirectResponse,
772
- renderKernelErrorChrome,
773
- renderWebErrorHtml,
774
- resolveWebLayoutData,
775
- rssResponse,
776
- textResponse,
777
- xmlResponse
778
- };
1
+ export * from "../index.js";