@getstrata/core 0.5.58 → 0.5.60

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.
@@ -297,6 +297,9 @@ class EtaViewEngine {
297
297
  }
298
298
  }
299
299
  // ../../src/core/view/htmlResponse.ts
300
+ function withCharset(contentType) {
301
+ return contentType.includes("charset=") ? contentType : `${contentType}; charset=utf-8`;
302
+ }
300
303
  function htmlResponse(html, init = {}) {
301
304
  return new Response(html, {
302
305
  status: init.status ?? 200,
@@ -309,6 +312,36 @@ function htmlResponse(html, init = {}) {
309
312
  function isHtmxRequest(request) {
310
313
  return request.headers.get("HX-Request") === "true";
311
314
  }
315
+ function redirectResponse(location, status = 302) {
316
+ return new Response(null, {
317
+ status,
318
+ headers: {
319
+ Location: location
320
+ }
321
+ });
322
+ }
323
+ function notFoundHtmlResponse(body = "Not Found") {
324
+ return htmlResponse(body, { status: 404 });
325
+ }
326
+ function textResponse(body, init = {}) {
327
+ return new Response(body, {
328
+ status: init.status ?? 200,
329
+ headers: {
330
+ "Content-Type": "text/plain; charset=utf-8"
331
+ }
332
+ });
333
+ }
334
+ function xmlResponse(body, init = {}) {
335
+ return new Response(body, {
336
+ status: init.status ?? 200,
337
+ headers: {
338
+ "Content-Type": withCharset(init.contentType ?? "application/xml")
339
+ }
340
+ });
341
+ }
342
+ function rssResponse(body, init = {}) {
343
+ return xmlResponse(body, { ...init, contentType: "application/rss+xml" });
344
+ }
312
345
  // ../../src/core/view/webLayoutData.ts
313
346
  import { currentAuthUser } from "@getstrata/core/auth/authContext";
314
347
  import { currentRequestMeta as currentRequestMeta2 } from "@getstrata/core/http/requestMetaContext";
@@ -321,6 +354,42 @@ var CORE_EVENT_BUS_TOKEN = "core.eventBus";
321
354
  var CORE_POLICY_GATE_TOKEN = "core.policyGate";
322
355
  var CORE_AUTH_TOKEN = "core.auth";
323
356
  var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
357
+ var CORE_ABILITY_CHECKER_TOKEN = "core.abilityChecker";
358
+ var CORE_AUTH_USER_DIRECTORY_TOKEN = "core.authUserDirectory";
359
+ function isAbilityChecker(value) {
360
+ if (!value || typeof value !== "object") {
361
+ return false;
362
+ }
363
+ const candidate = value;
364
+ return typeof candidate.tokenCan === "function" && typeof candidate.requireAbility === "function";
365
+ }
366
+ function isAuthUserDirectory(value) {
367
+ if (!value || typeof value !== "object") {
368
+ return false;
369
+ }
370
+ return typeof value.findByIdOrThrow === "function";
371
+ }
372
+ function resolveAbilityChecker(container) {
373
+ if (container.has(CORE_ABILITY_CHECKER_TOKEN)) {
374
+ return container.resolve(CORE_ABILITY_CHECKER_TOKEN);
375
+ }
376
+ return container.resolve(CORE_TOKEN_SERVICE_TOKEN);
377
+ }
378
+ function resolveAuthUserDirectory(container) {
379
+ if (container.has(CORE_AUTH_USER_DIRECTORY_TOKEN)) {
380
+ const directory = container.resolve(CORE_AUTH_USER_DIRECTORY_TOKEN);
381
+ if (isAuthUserDirectory(directory)) {
382
+ return directory;
383
+ }
384
+ }
385
+ if (container.has(CORE_TOKEN_SERVICE_TOKEN)) {
386
+ const tokenService = container.resolve(CORE_TOKEN_SERVICE_TOKEN);
387
+ if (isAuthUserDirectory(tokenService)) {
388
+ return tokenService;
389
+ }
390
+ }
391
+ return null;
392
+ }
324
393
 
325
394
  // ../../src/core/http/csrfToken.ts
326
395
  import { timingSafeEqual } from "crypto";
@@ -382,6 +451,9 @@ function currentRequestMeta() {
382
451
  // ../../src/core/http/csrfToken.ts
383
452
  var CSRF_COOKIE = "workhub_csrf";
384
453
  var CSRF_TTL_MS = 60 * 60 * 1000;
454
+ function csrfCookieName() {
455
+ return process.env.CSRF_COOKIE_NAME?.trim() || CSRF_COOKIE;
456
+ }
385
457
  function resolveCsrfSecret() {
386
458
  return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || "workhub-dev-csrf-secret";
387
459
  }
@@ -401,11 +473,11 @@ function createCsrfTokenCookie() {
401
473
  const secure = process.env.APP_ENV === "production" ? "; Secure" : "";
402
474
  return {
403
475
  token,
404
- cookie: `${CSRF_COOKIE}=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Max-Age=${Math.floor(CSRF_TTL_MS / 1000)}${secure}`
476
+ cookie: `${csrfCookieName()}=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Max-Age=${Math.floor(CSRF_TTL_MS / 1000)}${secure}`
405
477
  };
406
478
  }
407
479
  function resolveCsrfToken(request) {
408
- const cookieValue = readRequestCookie(request, CSRF_COOKIE);
480
+ const cookieValue = readRequestCookie(request, csrfCookieName());
409
481
  if (cookieValue && Bun.CSRF.verify(cookieValue, csrfVerifyOptions())) {
410
482
  return { token: cookieValue };
411
483
  }
@@ -441,7 +513,7 @@ function verifyCsrfToken(request, submittedToken) {
441
513
  if (!submittedToken) {
442
514
  return false;
443
515
  }
444
- const cookieValue = readRequestCookie(request, CSRF_COOKIE);
516
+ const cookieValue = readRequestCookie(request, csrfCookieName());
445
517
  if (!cookieValue) {
446
518
  return false;
447
519
  }
@@ -558,44 +630,56 @@ function withFlashClear(response) {
558
630
  }
559
631
 
560
632
  // ../../src/core/view/webLayoutData.ts
561
- async function resolveWebLayoutData(container, request) {
562
- const csrfToken = request ? resolveCsrfTokenForRequest(request) : "";
563
- const flash = request ? currentRequestMeta2().flash ?? pullFlash(request) : null;
633
+ var configuredLayoutOptions = {};
634
+ function configureWebLayoutData(options) {
635
+ configuredLayoutOptions = { ...options };
636
+ }
637
+ function layoutUserKey(options) {
638
+ return options.userKey ?? "authUser";
639
+ }
640
+ async function defaultLoadLayoutUser(container, _request) {
564
641
  const authUser = currentAuthUser();
565
642
  if (!authUser) {
566
- return { authUser: null, csrfToken, flash };
643
+ return null;
567
644
  }
568
645
  const userId = Number(authUser.id);
569
646
  if (!Number.isInteger(userId) || userId <= 0) {
570
- return { authUser: null, csrfToken, flash };
647
+ return null;
571
648
  }
572
- if (!container.has(CORE_TOKEN_SERVICE_TOKEN)) {
649
+ const directory = resolveAuthUserDirectory(container);
650
+ if (!directory) {
573
651
  return {
574
- authUser: {
575
- id: userId,
576
- email: "",
577
- role: authUser.role ?? "member"
578
- },
579
- csrfToken,
580
- flash
652
+ id: userId,
653
+ email: "",
654
+ role: authUser.role ?? "member"
581
655
  };
582
656
  }
583
- const tokenService = container.resolve(CORE_TOKEN_SERVICE_TOKEN);
584
657
  try {
585
- const user = await tokenService.findByIdOrThrow(userId);
658
+ const user = await directory.findByIdOrThrow(userId);
586
659
  return {
587
- authUser: {
588
- id: userId,
589
- email: user.email ?? "",
590
- role: authUser.role ?? user.role ?? "member"
591
- },
592
- csrfToken,
593
- flash
660
+ id: userId,
661
+ email: user.email ?? "",
662
+ role: authUser.role ?? user.role ?? "member"
594
663
  };
595
664
  } catch {
596
- return { authUser: null, csrfToken, flash };
665
+ return null;
597
666
  }
598
667
  }
668
+ async function resolveWebLayoutData(container, request, options = {}) {
669
+ const resolved = { ...configuredLayoutOptions, ...options };
670
+ const csrfToken = request ? resolveCsrfTokenForRequest(request) : "";
671
+ const flash = request ? currentRequestMeta2().flash ?? pullFlash(request) : null;
672
+ const userKey = layoutUserKey(resolved);
673
+ const loadUser = resolved.loadUser ?? defaultLoadLayoutUser;
674
+ const user = await loadUser(container, request);
675
+ const extra = typeof resolved.extra === "function" ? await resolved.extra(user) : resolved.extra ?? {};
676
+ return {
677
+ [userKey]: user,
678
+ csrfToken,
679
+ flash,
680
+ ...extra
681
+ };
682
+ }
599
683
  // ../../src/core/http/contentNegotiation.ts
600
684
  function requestPrefersJson(request) {
601
685
  if (!request) {
@@ -294,6 +294,9 @@ class EtaViewEngine {
294
294
  }
295
295
  }
296
296
  // ../../src/core/view/htmlResponse.ts
297
+ function withCharset(contentType) {
298
+ return contentType.includes("charset=") ? contentType : `${contentType}; charset=utf-8`;
299
+ }
297
300
  function htmlResponse(html, init = {}) {
298
301
  return new Response(html, {
299
302
  status: init.status ?? 200,
@@ -306,6 +309,36 @@ function htmlResponse(html, init = {}) {
306
309
  function isHtmxRequest(request) {
307
310
  return request.headers.get("HX-Request") === "true";
308
311
  }
312
+ function redirectResponse(location, status = 302) {
313
+ return new Response(null, {
314
+ status,
315
+ headers: {
316
+ Location: location
317
+ }
318
+ });
319
+ }
320
+ function notFoundHtmlResponse(body = "Not Found") {
321
+ return htmlResponse(body, { status: 404 });
322
+ }
323
+ function textResponse(body, init = {}) {
324
+ return new Response(body, {
325
+ status: init.status ?? 200,
326
+ headers: {
327
+ "Content-Type": "text/plain; charset=utf-8"
328
+ }
329
+ });
330
+ }
331
+ function xmlResponse(body, init = {}) {
332
+ return new Response(body, {
333
+ status: init.status ?? 200,
334
+ headers: {
335
+ "Content-Type": withCharset(init.contentType ?? "application/xml")
336
+ }
337
+ });
338
+ }
339
+ function rssResponse(body, init = {}) {
340
+ return xmlResponse(body, { ...init, contentType: "application/rss+xml" });
341
+ }
309
342
  // ../../src/core/view/webLayoutData.ts
310
343
  import { currentAuthUser } from "@getstrata/core/auth/authContext";
311
344
  import { currentRequestMeta as currentRequestMeta2 } from "@getstrata/core/http/requestMetaContext";
@@ -318,6 +351,42 @@ var CORE_EVENT_BUS_TOKEN = "core.eventBus";
318
351
  var CORE_POLICY_GATE_TOKEN = "core.policyGate";
319
352
  var CORE_AUTH_TOKEN = "core.auth";
320
353
  var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
354
+ var CORE_ABILITY_CHECKER_TOKEN = "core.abilityChecker";
355
+ var CORE_AUTH_USER_DIRECTORY_TOKEN = "core.authUserDirectory";
356
+ function isAbilityChecker(value) {
357
+ if (!value || typeof value !== "object") {
358
+ return false;
359
+ }
360
+ const candidate = value;
361
+ return typeof candidate.tokenCan === "function" && typeof candidate.requireAbility === "function";
362
+ }
363
+ function isAuthUserDirectory(value) {
364
+ if (!value || typeof value !== "object") {
365
+ return false;
366
+ }
367
+ return typeof value.findByIdOrThrow === "function";
368
+ }
369
+ function resolveAbilityChecker(container) {
370
+ if (container.has(CORE_ABILITY_CHECKER_TOKEN)) {
371
+ return container.resolve(CORE_ABILITY_CHECKER_TOKEN);
372
+ }
373
+ return container.resolve(CORE_TOKEN_SERVICE_TOKEN);
374
+ }
375
+ function resolveAuthUserDirectory(container) {
376
+ if (container.has(CORE_AUTH_USER_DIRECTORY_TOKEN)) {
377
+ const directory = container.resolve(CORE_AUTH_USER_DIRECTORY_TOKEN);
378
+ if (isAuthUserDirectory(directory)) {
379
+ return directory;
380
+ }
381
+ }
382
+ if (container.has(CORE_TOKEN_SERVICE_TOKEN)) {
383
+ const tokenService = container.resolve(CORE_TOKEN_SERVICE_TOKEN);
384
+ if (isAuthUserDirectory(tokenService)) {
385
+ return tokenService;
386
+ }
387
+ }
388
+ return null;
389
+ }
321
390
 
322
391
  // ../../src/core/http/csrfToken.ts
323
392
  import { timingSafeEqual } from "crypto";
@@ -379,6 +448,9 @@ function currentRequestMeta() {
379
448
  // ../../src/core/http/csrfToken.ts
380
449
  var CSRF_COOKIE = "workhub_csrf";
381
450
  var CSRF_TTL_MS = 60 * 60 * 1000;
451
+ function csrfCookieName() {
452
+ return process.env.CSRF_COOKIE_NAME?.trim() || CSRF_COOKIE;
453
+ }
382
454
  function resolveCsrfSecret() {
383
455
  return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || "workhub-dev-csrf-secret";
384
456
  }
@@ -398,11 +470,11 @@ function createCsrfTokenCookie() {
398
470
  const secure = process.env.APP_ENV === "production" ? "; Secure" : "";
399
471
  return {
400
472
  token,
401
- cookie: `${CSRF_COOKIE}=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Max-Age=${Math.floor(CSRF_TTL_MS / 1000)}${secure}`
473
+ cookie: `${csrfCookieName()}=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Max-Age=${Math.floor(CSRF_TTL_MS / 1000)}${secure}`
402
474
  };
403
475
  }
404
476
  function resolveCsrfToken(request) {
405
- const cookieValue = readRequestCookie(request, CSRF_COOKIE);
477
+ const cookieValue = readRequestCookie(request, csrfCookieName());
406
478
  if (cookieValue && Bun.CSRF.verify(cookieValue, csrfVerifyOptions())) {
407
479
  return { token: cookieValue };
408
480
  }
@@ -438,7 +510,7 @@ function verifyCsrfToken(request, submittedToken) {
438
510
  if (!submittedToken) {
439
511
  return false;
440
512
  }
441
- const cookieValue = readRequestCookie(request, CSRF_COOKIE);
513
+ const cookieValue = readRequestCookie(request, csrfCookieName());
442
514
  if (!cookieValue) {
443
515
  return false;
444
516
  }
@@ -555,44 +627,56 @@ function withFlashClear(response) {
555
627
  }
556
628
 
557
629
  // ../../src/core/view/webLayoutData.ts
558
- async function resolveWebLayoutData(container, request) {
559
- const csrfToken = request ? resolveCsrfTokenForRequest(request) : "";
560
- const flash = request ? currentRequestMeta2().flash ?? pullFlash(request) : null;
630
+ var configuredLayoutOptions = {};
631
+ function configureWebLayoutData(options) {
632
+ configuredLayoutOptions = { ...options };
633
+ }
634
+ function layoutUserKey(options) {
635
+ return options.userKey ?? "authUser";
636
+ }
637
+ async function defaultLoadLayoutUser(container, _request) {
561
638
  const authUser = currentAuthUser();
562
639
  if (!authUser) {
563
- return { authUser: null, csrfToken, flash };
640
+ return null;
564
641
  }
565
642
  const userId = Number(authUser.id);
566
643
  if (!Number.isInteger(userId) || userId <= 0) {
567
- return { authUser: null, csrfToken, flash };
644
+ return null;
568
645
  }
569
- if (!container.has(CORE_TOKEN_SERVICE_TOKEN)) {
646
+ const directory = resolveAuthUserDirectory(container);
647
+ if (!directory) {
570
648
  return {
571
- authUser: {
572
- id: userId,
573
- email: "",
574
- role: authUser.role ?? "member"
575
- },
576
- csrfToken,
577
- flash
649
+ id: userId,
650
+ email: "",
651
+ role: authUser.role ?? "member"
578
652
  };
579
653
  }
580
- const tokenService = container.resolve(CORE_TOKEN_SERVICE_TOKEN);
581
654
  try {
582
- const user = await tokenService.findByIdOrThrow(userId);
655
+ const user = await directory.findByIdOrThrow(userId);
583
656
  return {
584
- authUser: {
585
- id: userId,
586
- email: user.email ?? "",
587
- role: authUser.role ?? user.role ?? "member"
588
- },
589
- csrfToken,
590
- flash
657
+ id: userId,
658
+ email: user.email ?? "",
659
+ role: authUser.role ?? user.role ?? "member"
591
660
  };
592
661
  } catch {
593
- return { authUser: null, csrfToken, flash };
662
+ return null;
594
663
  }
595
664
  }
665
+ async function resolveWebLayoutData(container, request, options = {}) {
666
+ const resolved = { ...configuredLayoutOptions, ...options };
667
+ const csrfToken = request ? resolveCsrfTokenForRequest(request) : "";
668
+ const flash = request ? currentRequestMeta2().flash ?? pullFlash(request) : null;
669
+ const userKey = layoutUserKey(resolved);
670
+ const loadUser = resolved.loadUser ?? defaultLoadLayoutUser;
671
+ const user = await loadUser(container, request);
672
+ const extra = typeof resolved.extra === "function" ? await resolved.extra(user) : resolved.extra ?? {};
673
+ return {
674
+ [userKey]: user,
675
+ csrfToken,
676
+ flash,
677
+ ...extra
678
+ };
679
+ }
596
680
  // ../../src/core/http/contentNegotiation.ts
597
681
  function requestPrefersJson(request) {
598
682
  if (!request) {
@@ -102,7 +102,9 @@ var jobRegistry = readSharedJobRegistry();
102
102
 
103
103
  // ../../src/bootstrap/config.ts
104
104
  import {
105
+ CORE_ABILITY_CHECKER_TOKEN,
105
106
  CORE_AUTH_TOKEN,
107
+ CORE_AUTH_USER_DIRECTORY_TOKEN,
106
108
  CORE_CACHE_TOKEN,
107
109
  CORE_CONFIG_TOKEN,
108
110
  CORE_EVENT_BUS_TOKEN,
@@ -1,7 +1,9 @@
1
1
  // @bun
2
2
  // ../../src/bootstrap/config.ts
3
3
  import {
4
+ CORE_ABILITY_CHECKER_TOKEN,
4
5
  CORE_AUTH_TOKEN,
6
+ CORE_AUTH_USER_DIRECTORY_TOKEN,
5
7
  CORE_CACHE_TOKEN,
6
8
  CORE_CONFIG_TOKEN,
7
9
  CORE_EVENT_BUS_TOKEN,
@@ -102,7 +102,9 @@ var jobRegistry = readSharedJobRegistry();
102
102
 
103
103
  // ../../src/bootstrap/config.ts
104
104
  import {
105
+ CORE_ABILITY_CHECKER_TOKEN,
105
106
  CORE_AUTH_TOKEN,
107
+ CORE_AUTH_USER_DIRECTORY_TOKEN,
106
108
  CORE_CACHE_TOKEN,
107
109
  CORE_CONFIG_TOKEN,
108
110
  CORE_EVENT_BUS_TOKEN,
@@ -4,7 +4,9 @@ var {RedisClient: RedisClient2 } = globalThis.Bun;
4
4
 
5
5
  // ../../src/bootstrap/config.ts
6
6
  import {
7
+ CORE_ABILITY_CHECKER_TOKEN,
7
8
  CORE_AUTH_TOKEN,
9
+ CORE_AUTH_USER_DIRECTORY_TOKEN,
8
10
  CORE_CACHE_TOKEN,
9
11
  CORE_CONFIG_TOKEN,
10
12
  CORE_EVENT_BUS_TOKEN,
@@ -41,7 +41,9 @@ var jobRegistry = readSharedJobRegistry();
41
41
 
42
42
  // ../../src/bootstrap/config.ts
43
43
  import {
44
+ CORE_ABILITY_CHECKER_TOKEN,
44
45
  CORE_AUTH_TOKEN,
46
+ CORE_AUTH_USER_DIRECTORY_TOKEN,
45
47
  CORE_CACHE_TOKEN,
46
48
  CORE_CONFIG_TOKEN,
47
49
  CORE_EVENT_BUS_TOKEN,
@@ -213,6 +213,9 @@ class EtaViewEngine {
213
213
  }
214
214
  }
215
215
  // ../../src/core/view/htmlResponse.ts
216
+ function withCharset(contentType) {
217
+ return contentType.includes("charset=") ? contentType : `${contentType}; charset=utf-8`;
218
+ }
216
219
  function htmlResponse(html, init = {}) {
217
220
  return new Response(html, {
218
221
  status: init.status ?? 200,
@@ -225,6 +228,36 @@ function htmlResponse(html, init = {}) {
225
228
  function isHtmxRequest(request) {
226
229
  return request.headers.get("HX-Request") === "true";
227
230
  }
231
+ function redirectResponse(location, status = 302) {
232
+ return new Response(null, {
233
+ status,
234
+ headers: {
235
+ Location: location
236
+ }
237
+ });
238
+ }
239
+ function notFoundHtmlResponse(body = "Not Found") {
240
+ return htmlResponse(body, { status: 404 });
241
+ }
242
+ function textResponse(body, init = {}) {
243
+ return new Response(body, {
244
+ status: init.status ?? 200,
245
+ headers: {
246
+ "Content-Type": "text/plain; charset=utf-8"
247
+ }
248
+ });
249
+ }
250
+ function xmlResponse(body, init = {}) {
251
+ return new Response(body, {
252
+ status: init.status ?? 200,
253
+ headers: {
254
+ "Content-Type": withCharset(init.contentType ?? "application/xml")
255
+ }
256
+ });
257
+ }
258
+ function rssResponse(body, init = {}) {
259
+ return xmlResponse(body, { ...init, contentType: "application/rss+xml" });
260
+ }
228
261
  // ../../src/core/view/webLayoutData.ts
229
262
  import { currentAuthUser } from "@getstrata/core/auth/authContext";
230
263
  import { currentRequestMeta as currentRequestMeta2 } from "@getstrata/core/http/requestMetaContext";
@@ -237,6 +270,42 @@ var CORE_EVENT_BUS_TOKEN = "core.eventBus";
237
270
  var CORE_POLICY_GATE_TOKEN = "core.policyGate";
238
271
  var CORE_AUTH_TOKEN = "core.auth";
239
272
  var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
273
+ var CORE_ABILITY_CHECKER_TOKEN = "core.abilityChecker";
274
+ var CORE_AUTH_USER_DIRECTORY_TOKEN = "core.authUserDirectory";
275
+ function isAbilityChecker(value) {
276
+ if (!value || typeof value !== "object") {
277
+ return false;
278
+ }
279
+ const candidate = value;
280
+ return typeof candidate.tokenCan === "function" && typeof candidate.requireAbility === "function";
281
+ }
282
+ function isAuthUserDirectory(value) {
283
+ if (!value || typeof value !== "object") {
284
+ return false;
285
+ }
286
+ return typeof value.findByIdOrThrow === "function";
287
+ }
288
+ function resolveAbilityChecker(container) {
289
+ if (container.has(CORE_ABILITY_CHECKER_TOKEN)) {
290
+ return container.resolve(CORE_ABILITY_CHECKER_TOKEN);
291
+ }
292
+ return container.resolve(CORE_TOKEN_SERVICE_TOKEN);
293
+ }
294
+ function resolveAuthUserDirectory(container) {
295
+ if (container.has(CORE_AUTH_USER_DIRECTORY_TOKEN)) {
296
+ const directory = container.resolve(CORE_AUTH_USER_DIRECTORY_TOKEN);
297
+ if (isAuthUserDirectory(directory)) {
298
+ return directory;
299
+ }
300
+ }
301
+ if (container.has(CORE_TOKEN_SERVICE_TOKEN)) {
302
+ const tokenService = container.resolve(CORE_TOKEN_SERVICE_TOKEN);
303
+ if (isAuthUserDirectory(tokenService)) {
304
+ return tokenService;
305
+ }
306
+ }
307
+ return null;
308
+ }
240
309
 
241
310
  // ../../src/core/http/csrfToken.ts
242
311
  import { timingSafeEqual } from "crypto";
@@ -298,6 +367,9 @@ function currentRequestMeta() {
298
367
  // ../../src/core/http/csrfToken.ts
299
368
  var CSRF_COOKIE = "workhub_csrf";
300
369
  var CSRF_TTL_MS = 60 * 60 * 1000;
370
+ function csrfCookieName() {
371
+ return process.env.CSRF_COOKIE_NAME?.trim() || CSRF_COOKIE;
372
+ }
301
373
  function resolveCsrfSecret() {
302
374
  return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || "workhub-dev-csrf-secret";
303
375
  }
@@ -317,11 +389,11 @@ function createCsrfTokenCookie() {
317
389
  const secure = process.env.APP_ENV === "production" ? "; Secure" : "";
318
390
  return {
319
391
  token,
320
- cookie: `${CSRF_COOKIE}=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Max-Age=${Math.floor(CSRF_TTL_MS / 1000)}${secure}`
392
+ cookie: `${csrfCookieName()}=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Max-Age=${Math.floor(CSRF_TTL_MS / 1000)}${secure}`
321
393
  };
322
394
  }
323
395
  function resolveCsrfToken(request) {
324
- const cookieValue = readRequestCookie(request, CSRF_COOKIE);
396
+ const cookieValue = readRequestCookie(request, csrfCookieName());
325
397
  if (cookieValue && Bun.CSRF.verify(cookieValue, csrfVerifyOptions())) {
326
398
  return { token: cookieValue };
327
399
  }
@@ -357,7 +429,7 @@ function verifyCsrfToken(request, submittedToken) {
357
429
  if (!submittedToken) {
358
430
  return false;
359
431
  }
360
- const cookieValue = readRequestCookie(request, CSRF_COOKIE);
432
+ const cookieValue = readRequestCookie(request, csrfCookieName());
361
433
  if (!cookieValue) {
362
434
  return false;
363
435
  }
@@ -474,48 +546,66 @@ function withFlashClear(response) {
474
546
  }
475
547
 
476
548
  // ../../src/core/view/webLayoutData.ts
477
- async function resolveWebLayoutData(container, request) {
478
- const csrfToken = request ? resolveCsrfTokenForRequest(request) : "";
479
- const flash = request ? currentRequestMeta2().flash ?? pullFlash(request) : null;
549
+ var configuredLayoutOptions = {};
550
+ function configureWebLayoutData(options) {
551
+ configuredLayoutOptions = { ...options };
552
+ }
553
+ function layoutUserKey(options) {
554
+ return options.userKey ?? "authUser";
555
+ }
556
+ async function defaultLoadLayoutUser(container, _request) {
480
557
  const authUser = currentAuthUser();
481
558
  if (!authUser) {
482
- return { authUser: null, csrfToken, flash };
559
+ return null;
483
560
  }
484
561
  const userId = Number(authUser.id);
485
562
  if (!Number.isInteger(userId) || userId <= 0) {
486
- return { authUser: null, csrfToken, flash };
563
+ return null;
487
564
  }
488
- if (!container.has(CORE_TOKEN_SERVICE_TOKEN)) {
565
+ const directory = resolveAuthUserDirectory(container);
566
+ if (!directory) {
489
567
  return {
490
- authUser: {
491
- id: userId,
492
- email: "",
493
- role: authUser.role ?? "member"
494
- },
495
- csrfToken,
496
- flash
568
+ id: userId,
569
+ email: "",
570
+ role: authUser.role ?? "member"
497
571
  };
498
572
  }
499
- const tokenService = container.resolve(CORE_TOKEN_SERVICE_TOKEN);
500
573
  try {
501
- const user = await tokenService.findByIdOrThrow(userId);
574
+ const user = await directory.findByIdOrThrow(userId);
502
575
  return {
503
- authUser: {
504
- id: userId,
505
- email: user.email ?? "",
506
- role: authUser.role ?? user.role ?? "member"
507
- },
508
- csrfToken,
509
- flash
576
+ id: userId,
577
+ email: user.email ?? "",
578
+ role: authUser.role ?? user.role ?? "member"
510
579
  };
511
580
  } catch {
512
- return { authUser: null, csrfToken, flash };
581
+ return null;
513
582
  }
514
583
  }
584
+ async function resolveWebLayoutData(container, request, options = {}) {
585
+ const resolved = { ...configuredLayoutOptions, ...options };
586
+ const csrfToken = request ? resolveCsrfTokenForRequest(request) : "";
587
+ const flash = request ? currentRequestMeta2().flash ?? pullFlash(request) : null;
588
+ const userKey = layoutUserKey(resolved);
589
+ const loadUser = resolved.loadUser ?? defaultLoadLayoutUser;
590
+ const user = await loadUser(container, request);
591
+ const extra = typeof resolved.extra === "function" ? await resolved.extra(user) : resolved.extra ?? {};
592
+ return {
593
+ [userKey]: user,
594
+ csrfToken,
595
+ flash,
596
+ ...extra
597
+ };
598
+ }
515
599
  export {
516
600
  DEFAULT_VIEWS_DIRECTORY,
517
601
  EtaViewEngine,
602
+ configureWebLayoutData,
518
603
  htmlResponse,
519
604
  isHtmxRequest,
520
- resolveWebLayoutData
605
+ notFoundHtmlResponse,
606
+ redirectResponse,
607
+ resolveWebLayoutData,
608
+ rssResponse,
609
+ textResponse,
610
+ xmlResponse
521
611
  };