@getstrata/bootstrap 0.2.61 → 0.2.63

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.
@@ -475,15 +475,160 @@ var storageProvider = {
475
475
  var storage_default = storageProvider;
476
476
 
477
477
  // ../../src/bootstrap/providers/view.ts
478
+ import { resolveMembershipLookup } from "@getstrata/core/auth/membershipContext";
478
479
  import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
480
+ import { appDisplayName } from "@getstrata/core/runtime/appKeyPrefix";
479
481
  import { isViewsEnabled } from "@getstrata/core/runtime/frontendMode";
480
482
  import {
481
483
  configureWebErrorView,
484
+ configureWebLayoutData,
482
485
  DEFAULT_VIEWS_DIRECTORY,
483
486
  EtaViewEngine,
484
487
  errorTemplateName,
485
488
  resolveWebLayoutData
486
489
  } from "@getstrata/core/view";
490
+
491
+ // ../../src/modules/organization/repository.ts
492
+ import { BaseRepository } from "@getstrata/core/database/baseRepository";
493
+ import { NotFoundError } from "@getstrata/core/errors/http";
494
+ import { currentTenantId } from "@getstrata/core/tenant/tenantContext";
495
+
496
+ // ../../src/modules/organization/table.ts
497
+ import { defineTable } from "@getstrata/core/database/table";
498
+
499
+ // ../../src/domain/workhub.ts
500
+ var ORGANIZATION_TABLE = "organization";
501
+
502
+ // ../../src/modules/organization/table.ts
503
+ var organizationTable = defineTable({
504
+ name: ORGANIZATION_TABLE,
505
+ primaryKey: "id",
506
+ columns: ["id", "tenant_id", "name", "slug", "created_at", "updated_at", "deleted_at"],
507
+ softDeletes: true,
508
+ defaultOrderBy: { column: "id", direction: "ASC" }
509
+ });
510
+
511
+ // ../../src/modules/organization/repository.ts
512
+ class OrganizationRepository extends BaseRepository {
513
+ constructor() {
514
+ super(organizationTable);
515
+ }
516
+ async findBySlug(slug) {
517
+ return await this.firstOrNull({ slug });
518
+ }
519
+ async listForTenant(options) {
520
+ return await this.findAll({
521
+ limit: options.limit,
522
+ offset: options.offset,
523
+ where: { tenant_id: options.tenantId ?? currentTenantId() }
524
+ });
525
+ }
526
+ async countForTenant(tenantId = currentTenantId()) {
527
+ return await this.countWhere({ tenant_id: tenantId });
528
+ }
529
+ async findForTenantOrThrow(id, tenantId = currentTenantId()) {
530
+ const organization = await this.findById(id);
531
+ if (!organization || organization.tenant_id !== tenantId) {
532
+ throw new NotFoundError(`SCIM group ${id} not found.`);
533
+ }
534
+ return organization;
535
+ }
536
+ }
537
+ var repository_default = OrganizationRepository;
538
+
539
+ // ../../src/modules/user/repository.ts
540
+ import {
541
+ emailLookupForQuery,
542
+ protectEmail,
543
+ revealEmail
544
+ } from "@getstrata/core/crypto/fieldEncryption";
545
+ import { revealMfaSecret } from "@getstrata/core/crypto/mfaSecret";
546
+ import { BaseRepository as BaseRepository2 } from "@getstrata/core/database/baseRepository";
547
+ import { currentTenantId as currentTenantId2 } from "@getstrata/core/tenant/tenantContext";
548
+
549
+ // ../../src/modules/user/table.ts
550
+ import { defineTable as defineTable2 } from "@getstrata/core/database/table";
551
+ var userTable = defineTable2({
552
+ name: "users",
553
+ primaryKey: "id",
554
+ columns: [
555
+ "id",
556
+ "name",
557
+ "email",
558
+ "email_lookup",
559
+ "role",
560
+ "tenant_id",
561
+ "password_hash",
562
+ "email_verified_at",
563
+ "mfa_secret",
564
+ "mfa_enabled",
565
+ "mfa_recovery_codes",
566
+ "profile_photo_path",
567
+ "session_valid_after",
568
+ "current_organization_id",
569
+ "created_at",
570
+ "updated_at"
571
+ ],
572
+ defaultOrderBy: { column: "id", direction: "ASC" }
573
+ });
574
+
575
+ // ../../src/modules/user/repository.ts
576
+ class UserRepository extends BaseRepository2 {
577
+ constructor() {
578
+ super(userTable);
579
+ }
580
+ decode(record) {
581
+ return {
582
+ ...record,
583
+ email: revealEmail(record.email),
584
+ mfa_secret: revealMfaSecret(record.mfa_secret)
585
+ };
586
+ }
587
+ async findById(id) {
588
+ const record = await super.findById(id);
589
+ return record ? this.decode(record) : null;
590
+ }
591
+ async findAll(options = {}) {
592
+ const records = await super.findAll(options);
593
+ return records.map((record) => this.decode(record));
594
+ }
595
+ async create(values) {
596
+ const email = values.email;
597
+ if (!email) {
598
+ throw new Error("Email is required.");
599
+ }
600
+ const protectedEmail = protectEmail(email);
601
+ const record = await super.create({
602
+ ...values,
603
+ tenant_id: values.tenant_id ?? currentTenantId2(),
604
+ email: protectedEmail.storedEmail,
605
+ email_lookup: protectedEmail.emailLookup,
606
+ password_hash: values.password_hash ?? ""
607
+ });
608
+ return this.decode(record);
609
+ }
610
+ async updateByIdOrThrow(id, values, errorFactory) {
611
+ const changes = { ...values };
612
+ if (values.email !== undefined) {
613
+ const protectedEmail = protectEmail(values.email);
614
+ changes.email = protectedEmail.storedEmail;
615
+ changes.email_lookup = protectedEmail.emailLookup;
616
+ }
617
+ const record = await super.updateByIdOrThrow(id, changes, errorFactory);
618
+ return this.decode(record);
619
+ }
620
+ async findByEmail(email) {
621
+ const records = await this.findWhere({ email_lookup: emailLookupForQuery(email) }, { limit: 1 });
622
+ const record = records[0];
623
+ return record ? this.decode(record) : null;
624
+ }
625
+ async countForTenant(tenantId = currentTenantId2()) {
626
+ return await this.countWhere({ tenant_id: tenantId });
627
+ }
628
+ }
629
+ var repository_default2 = UserRepository;
630
+
631
+ // ../../src/bootstrap/providers/view.ts
487
632
  var CORE_VIEW_TOKEN = "core.view";
488
633
  var VIEW_DIRECTORY_CONFIG_KEY = "view.directory";
489
634
  var viewProvider = {
@@ -496,6 +641,25 @@ var viewProvider = {
496
641
  config.set(VIEW_DIRECTORY_CONFIG_KEY, viewsDirectory);
497
642
  const engine = new EtaViewEngine(viewsDirectory, (request) => resolveWebLayoutData(container, request ?? currentRequestMeta().request));
498
643
  container.set(CORE_VIEW_TOKEN, engine);
644
+ configureWebLayoutData({
645
+ extra: async (user) => {
646
+ const appName = appDisplayName();
647
+ if (!user || typeof user.id !== "number") {
648
+ return { appName, currentOrganization: null, organizations: [] };
649
+ }
650
+ try {
651
+ const record = await new repository_default2().findByIdOrThrow(user.id);
652
+ const memberships = await resolveMembershipLookup().listForUser(record.id);
653
+ const organizationsRepo = new repository_default;
654
+ const organizations = (await Promise.all(memberships.map((membership) => organizationsRepo.findById(membership.organization_id)))).filter((organization) => Boolean(organization && !organization.deleted_at));
655
+ const currentId = record.current_organization_id ?? null;
656
+ const currentOrganization = organizations.find((organization) => organization.id === currentId) ?? organizations[0] ?? null;
657
+ return { appName, currentOrganization, organizations };
658
+ } catch {
659
+ return { appName, currentOrganization: null, organizations: [] };
660
+ }
661
+ }
662
+ });
499
663
  configureWebErrorView({
500
664
  render: async (input) => engine.render(errorTemplateName(input.status), {
501
665
  title: input.title,
@@ -21,6 +21,7 @@ import { requestIdMiddleware } from "@getstrata/core/http/middleware";
21
21
  import { createRequireAbilityMiddleware } from "@getstrata/core/http/requireAbilityMiddleware";
22
22
  import { createRequireAuthMiddleware } from "@getstrata/core/http/requireAuthMiddleware";
23
23
  import { createRequireGlobalAdminMiddleware } from "@getstrata/core/http/requireGlobalAdminMiddleware";
24
+ import { createRequirePasswordConfirmMiddleware } from "@getstrata/core/http/requirePasswordConfirmMiddleware";
24
25
  import { createRequireVerifiedMiddleware } from "@getstrata/core/http/requireVerifiedMiddleware";
25
26
  import { createRequireWebAuthMiddleware } from "@getstrata/core/http/requireWebAuthMiddleware";
26
27
  import { withErrorHandling } from "@getstrata/core/http/response";
@@ -184,7 +185,8 @@ class HttpKernel {
184
185
  if (isEmailVerificationRequired() && !hasVerifiedEmail(user)) {
185
186
  return Response.redirect("/email/verify", 302);
186
187
  }
187
- return Response.redirect(home, 302);
188
+ const location = typeof home === "function" ? await home(user) : home;
189
+ return Response.redirect(location, 302);
188
190
  }
189
191
  return handler(request);
190
192
  });
@@ -204,6 +206,11 @@ class HttpKernel {
204
206
  wrapWebVerified(handler) {
205
207
  return this.wrapWebAuth(handler, { verified: true });
206
208
  }
209
+ wrapWebPasswordConfirm(handler) {
210
+ return this.wrapWebAuth(withMiddleware(createRequirePasswordConfirmMiddleware())(handler), {
211
+ verified: true
212
+ });
213
+ }
207
214
  wrapWebAbility(ability, handler) {
208
215
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
209
216
  const abilityChecker = resolveAbilityChecker(this.dependencies.container);
@@ -2,15 +2,160 @@
2
2
  var __jsonParse = (a) => JSON.parse(a);
3
3
 
4
4
  // ../../src/bootstrap/providers/view.ts
5
+ import { resolveMembershipLookup } from "@getstrata/core/auth/membershipContext";
5
6
  import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
7
+ import { appDisplayName } from "@getstrata/core/runtime/appKeyPrefix";
6
8
  import { isViewsEnabled } from "@getstrata/core/runtime/frontendMode";
7
9
  import {
8
10
  configureWebErrorView,
11
+ configureWebLayoutData,
9
12
  DEFAULT_VIEWS_DIRECTORY,
10
13
  EtaViewEngine,
11
14
  errorTemplateName,
12
15
  resolveWebLayoutData
13
16
  } from "@getstrata/core/view";
17
+
18
+ // ../../src/modules/organization/repository.ts
19
+ import { BaseRepository } from "@getstrata/core/database/baseRepository";
20
+ import { NotFoundError } from "@getstrata/core/errors/http";
21
+ import { currentTenantId } from "@getstrata/core/tenant/tenantContext";
22
+
23
+ // ../../src/modules/organization/table.ts
24
+ import { defineTable } from "@getstrata/core/database/table";
25
+
26
+ // ../../src/domain/workhub.ts
27
+ var ORGANIZATION_TABLE = "organization";
28
+
29
+ // ../../src/modules/organization/table.ts
30
+ var organizationTable = defineTable({
31
+ name: ORGANIZATION_TABLE,
32
+ primaryKey: "id",
33
+ columns: ["id", "tenant_id", "name", "slug", "created_at", "updated_at", "deleted_at"],
34
+ softDeletes: true,
35
+ defaultOrderBy: { column: "id", direction: "ASC" }
36
+ });
37
+
38
+ // ../../src/modules/organization/repository.ts
39
+ class OrganizationRepository extends BaseRepository {
40
+ constructor() {
41
+ super(organizationTable);
42
+ }
43
+ async findBySlug(slug) {
44
+ return await this.firstOrNull({ slug });
45
+ }
46
+ async listForTenant(options) {
47
+ return await this.findAll({
48
+ limit: options.limit,
49
+ offset: options.offset,
50
+ where: { tenant_id: options.tenantId ?? currentTenantId() }
51
+ });
52
+ }
53
+ async countForTenant(tenantId = currentTenantId()) {
54
+ return await this.countWhere({ tenant_id: tenantId });
55
+ }
56
+ async findForTenantOrThrow(id, tenantId = currentTenantId()) {
57
+ const organization = await this.findById(id);
58
+ if (!organization || organization.tenant_id !== tenantId) {
59
+ throw new NotFoundError(`SCIM group ${id} not found.`);
60
+ }
61
+ return organization;
62
+ }
63
+ }
64
+ var repository_default = OrganizationRepository;
65
+
66
+ // ../../src/modules/user/repository.ts
67
+ import {
68
+ emailLookupForQuery,
69
+ protectEmail,
70
+ revealEmail
71
+ } from "@getstrata/core/crypto/fieldEncryption";
72
+ import { revealMfaSecret } from "@getstrata/core/crypto/mfaSecret";
73
+ import { BaseRepository as BaseRepository2 } from "@getstrata/core/database/baseRepository";
74
+ import { currentTenantId as currentTenantId2 } from "@getstrata/core/tenant/tenantContext";
75
+
76
+ // ../../src/modules/user/table.ts
77
+ import { defineTable as defineTable2 } from "@getstrata/core/database/table";
78
+ var userTable = defineTable2({
79
+ name: "users",
80
+ primaryKey: "id",
81
+ columns: [
82
+ "id",
83
+ "name",
84
+ "email",
85
+ "email_lookup",
86
+ "role",
87
+ "tenant_id",
88
+ "password_hash",
89
+ "email_verified_at",
90
+ "mfa_secret",
91
+ "mfa_enabled",
92
+ "mfa_recovery_codes",
93
+ "profile_photo_path",
94
+ "session_valid_after",
95
+ "current_organization_id",
96
+ "created_at",
97
+ "updated_at"
98
+ ],
99
+ defaultOrderBy: { column: "id", direction: "ASC" }
100
+ });
101
+
102
+ // ../../src/modules/user/repository.ts
103
+ class UserRepository extends BaseRepository2 {
104
+ constructor() {
105
+ super(userTable);
106
+ }
107
+ decode(record) {
108
+ return {
109
+ ...record,
110
+ email: revealEmail(record.email),
111
+ mfa_secret: revealMfaSecret(record.mfa_secret)
112
+ };
113
+ }
114
+ async findById(id) {
115
+ const record = await super.findById(id);
116
+ return record ? this.decode(record) : null;
117
+ }
118
+ async findAll(options = {}) {
119
+ const records = await super.findAll(options);
120
+ return records.map((record) => this.decode(record));
121
+ }
122
+ async create(values) {
123
+ const email = values.email;
124
+ if (!email) {
125
+ throw new Error("Email is required.");
126
+ }
127
+ const protectedEmail = protectEmail(email);
128
+ const record = await super.create({
129
+ ...values,
130
+ tenant_id: values.tenant_id ?? currentTenantId2(),
131
+ email: protectedEmail.storedEmail,
132
+ email_lookup: protectedEmail.emailLookup,
133
+ password_hash: values.password_hash ?? ""
134
+ });
135
+ return this.decode(record);
136
+ }
137
+ async updateByIdOrThrow(id, values, errorFactory) {
138
+ const changes = { ...values };
139
+ if (values.email !== undefined) {
140
+ const protectedEmail = protectEmail(values.email);
141
+ changes.email = protectedEmail.storedEmail;
142
+ changes.email_lookup = protectedEmail.emailLookup;
143
+ }
144
+ const record = await super.updateByIdOrThrow(id, changes, errorFactory);
145
+ return this.decode(record);
146
+ }
147
+ async findByEmail(email) {
148
+ const records = await this.findWhere({ email_lookup: emailLookupForQuery(email) }, { limit: 1 });
149
+ const record = records[0];
150
+ return record ? this.decode(record) : null;
151
+ }
152
+ async countForTenant(tenantId = currentTenantId2()) {
153
+ return await this.countWhere({ tenant_id: tenantId });
154
+ }
155
+ }
156
+ var repository_default2 = UserRepository;
157
+
158
+ // ../../src/bootstrap/providers/view.ts
14
159
  var CORE_VIEW_TOKEN = "core.view";
15
160
  var VIEW_DIRECTORY_CONFIG_KEY = "view.directory";
16
161
  var viewProvider = {
@@ -23,6 +168,25 @@ var viewProvider = {
23
168
  config.set(VIEW_DIRECTORY_CONFIG_KEY, viewsDirectory);
24
169
  const engine = new EtaViewEngine(viewsDirectory, (request) => resolveWebLayoutData(container, request ?? currentRequestMeta().request));
25
170
  container.set(CORE_VIEW_TOKEN, engine);
171
+ configureWebLayoutData({
172
+ extra: async (user) => {
173
+ const appName = appDisplayName();
174
+ if (!user || typeof user.id !== "number") {
175
+ return { appName, currentOrganization: null, organizations: [] };
176
+ }
177
+ try {
178
+ const record = await new repository_default2().findByIdOrThrow(user.id);
179
+ const memberships = await resolveMembershipLookup().listForUser(record.id);
180
+ const organizationsRepo = new repository_default;
181
+ const organizations = (await Promise.all(memberships.map((membership) => organizationsRepo.findById(membership.organization_id)))).filter((organization) => Boolean(organization && !organization.deleted_at));
182
+ const currentId = record.current_organization_id ?? null;
183
+ const currentOrganization = organizations.find((organization) => organization.id === currentId) ?? organizations[0] ?? null;
184
+ return { appName, currentOrganization, organizations };
185
+ } catch {
186
+ return { appName, currentOrganization: null, organizations: [] };
187
+ }
188
+ }
189
+ });
26
190
  configureWebErrorView({
27
191
  render: async (input) => engine.render(errorTemplateName(input.status), {
28
192
  title: input.title,
@@ -465,15 +465,160 @@ var storageProvider = {
465
465
  var storage_default = storageProvider;
466
466
 
467
467
  // ../../src/bootstrap/providers/view.ts
468
+ import { resolveMembershipLookup } from "@getstrata/core/auth/membershipContext";
468
469
  import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
470
+ import { appDisplayName } from "@getstrata/core/runtime/appKeyPrefix";
469
471
  import { isViewsEnabled } from "@getstrata/core/runtime/frontendMode";
470
472
  import {
471
473
  configureWebErrorView,
474
+ configureWebLayoutData,
472
475
  DEFAULT_VIEWS_DIRECTORY,
473
476
  EtaViewEngine,
474
477
  errorTemplateName,
475
478
  resolveWebLayoutData
476
479
  } from "@getstrata/core/view";
480
+
481
+ // ../../src/modules/organization/repository.ts
482
+ import { BaseRepository } from "@getstrata/core/database/baseRepository";
483
+ import { NotFoundError } from "@getstrata/core/errors/http";
484
+ import { currentTenantId } from "@getstrata/core/tenant/tenantContext";
485
+
486
+ // ../../src/modules/organization/table.ts
487
+ import { defineTable } from "@getstrata/core/database/table";
488
+
489
+ // ../../src/domain/workhub.ts
490
+ var ORGANIZATION_TABLE = "organization";
491
+
492
+ // ../../src/modules/organization/table.ts
493
+ var organizationTable = defineTable({
494
+ name: ORGANIZATION_TABLE,
495
+ primaryKey: "id",
496
+ columns: ["id", "tenant_id", "name", "slug", "created_at", "updated_at", "deleted_at"],
497
+ softDeletes: true,
498
+ defaultOrderBy: { column: "id", direction: "ASC" }
499
+ });
500
+
501
+ // ../../src/modules/organization/repository.ts
502
+ class OrganizationRepository extends BaseRepository {
503
+ constructor() {
504
+ super(organizationTable);
505
+ }
506
+ async findBySlug(slug) {
507
+ return await this.firstOrNull({ slug });
508
+ }
509
+ async listForTenant(options) {
510
+ return await this.findAll({
511
+ limit: options.limit,
512
+ offset: options.offset,
513
+ where: { tenant_id: options.tenantId ?? currentTenantId() }
514
+ });
515
+ }
516
+ async countForTenant(tenantId = currentTenantId()) {
517
+ return await this.countWhere({ tenant_id: tenantId });
518
+ }
519
+ async findForTenantOrThrow(id, tenantId = currentTenantId()) {
520
+ const organization = await this.findById(id);
521
+ if (!organization || organization.tenant_id !== tenantId) {
522
+ throw new NotFoundError(`SCIM group ${id} not found.`);
523
+ }
524
+ return organization;
525
+ }
526
+ }
527
+ var repository_default = OrganizationRepository;
528
+
529
+ // ../../src/modules/user/repository.ts
530
+ import {
531
+ emailLookupForQuery,
532
+ protectEmail,
533
+ revealEmail
534
+ } from "@getstrata/core/crypto/fieldEncryption";
535
+ import { revealMfaSecret } from "@getstrata/core/crypto/mfaSecret";
536
+ import { BaseRepository as BaseRepository2 } from "@getstrata/core/database/baseRepository";
537
+ import { currentTenantId as currentTenantId2 } from "@getstrata/core/tenant/tenantContext";
538
+
539
+ // ../../src/modules/user/table.ts
540
+ import { defineTable as defineTable2 } from "@getstrata/core/database/table";
541
+ var userTable = defineTable2({
542
+ name: "users",
543
+ primaryKey: "id",
544
+ columns: [
545
+ "id",
546
+ "name",
547
+ "email",
548
+ "email_lookup",
549
+ "role",
550
+ "tenant_id",
551
+ "password_hash",
552
+ "email_verified_at",
553
+ "mfa_secret",
554
+ "mfa_enabled",
555
+ "mfa_recovery_codes",
556
+ "profile_photo_path",
557
+ "session_valid_after",
558
+ "current_organization_id",
559
+ "created_at",
560
+ "updated_at"
561
+ ],
562
+ defaultOrderBy: { column: "id", direction: "ASC" }
563
+ });
564
+
565
+ // ../../src/modules/user/repository.ts
566
+ class UserRepository extends BaseRepository2 {
567
+ constructor() {
568
+ super(userTable);
569
+ }
570
+ decode(record) {
571
+ return {
572
+ ...record,
573
+ email: revealEmail(record.email),
574
+ mfa_secret: revealMfaSecret(record.mfa_secret)
575
+ };
576
+ }
577
+ async findById(id) {
578
+ const record = await super.findById(id);
579
+ return record ? this.decode(record) : null;
580
+ }
581
+ async findAll(options = {}) {
582
+ const records = await super.findAll(options);
583
+ return records.map((record) => this.decode(record));
584
+ }
585
+ async create(values) {
586
+ const email = values.email;
587
+ if (!email) {
588
+ throw new Error("Email is required.");
589
+ }
590
+ const protectedEmail = protectEmail(email);
591
+ const record = await super.create({
592
+ ...values,
593
+ tenant_id: values.tenant_id ?? currentTenantId2(),
594
+ email: protectedEmail.storedEmail,
595
+ email_lookup: protectedEmail.emailLookup,
596
+ password_hash: values.password_hash ?? ""
597
+ });
598
+ return this.decode(record);
599
+ }
600
+ async updateByIdOrThrow(id, values, errorFactory) {
601
+ const changes = { ...values };
602
+ if (values.email !== undefined) {
603
+ const protectedEmail = protectEmail(values.email);
604
+ changes.email = protectedEmail.storedEmail;
605
+ changes.email_lookup = protectedEmail.emailLookup;
606
+ }
607
+ const record = await super.updateByIdOrThrow(id, changes, errorFactory);
608
+ return this.decode(record);
609
+ }
610
+ async findByEmail(email) {
611
+ const records = await this.findWhere({ email_lookup: emailLookupForQuery(email) }, { limit: 1 });
612
+ const record = records[0];
613
+ return record ? this.decode(record) : null;
614
+ }
615
+ async countForTenant(tenantId = currentTenantId2()) {
616
+ return await this.countWhere({ tenant_id: tenantId });
617
+ }
618
+ }
619
+ var repository_default2 = UserRepository;
620
+
621
+ // ../../src/bootstrap/providers/view.ts
477
622
  var CORE_VIEW_TOKEN = "core.view";
478
623
  var VIEW_DIRECTORY_CONFIG_KEY = "view.directory";
479
624
  var viewProvider = {
@@ -486,6 +631,25 @@ var viewProvider = {
486
631
  config.set(VIEW_DIRECTORY_CONFIG_KEY, viewsDirectory);
487
632
  const engine = new EtaViewEngine(viewsDirectory, (request) => resolveWebLayoutData(container, request ?? currentRequestMeta().request));
488
633
  container.set(CORE_VIEW_TOKEN, engine);
634
+ configureWebLayoutData({
635
+ extra: async (user) => {
636
+ const appName = appDisplayName();
637
+ if (!user || typeof user.id !== "number") {
638
+ return { appName, currentOrganization: null, organizations: [] };
639
+ }
640
+ try {
641
+ const record = await new repository_default2().findByIdOrThrow(user.id);
642
+ const memberships = await resolveMembershipLookup().listForUser(record.id);
643
+ const organizationsRepo = new repository_default;
644
+ const organizations = (await Promise.all(memberships.map((membership) => organizationsRepo.findById(membership.organization_id)))).filter((organization) => Boolean(organization && !organization.deleted_at));
645
+ const currentId = record.current_organization_id ?? null;
646
+ const currentOrganization = organizations.find((organization) => organization.id === currentId) ?? organizations[0] ?? null;
647
+ return { appName, currentOrganization, organizations };
648
+ } catch {
649
+ return { appName, currentOrganization: null, organizations: [] };
650
+ }
651
+ }
652
+ });
489
653
  configureWebErrorView({
490
654
  render: async (input) => engine.render(errorTemplateName(input.status), {
491
655
  title: input.title,
@@ -30,6 +30,7 @@ import { requestIdMiddleware } from "@getstrata/core/http/middleware";
30
30
  import { createRequireAbilityMiddleware } from "@getstrata/core/http/requireAbilityMiddleware";
31
31
  import { createRequireAuthMiddleware } from "@getstrata/core/http/requireAuthMiddleware";
32
32
  import { createRequireGlobalAdminMiddleware } from "@getstrata/core/http/requireGlobalAdminMiddleware";
33
+ import { createRequirePasswordConfirmMiddleware } from "@getstrata/core/http/requirePasswordConfirmMiddleware";
33
34
  import { createRequireVerifiedMiddleware } from "@getstrata/core/http/requireVerifiedMiddleware";
34
35
  import { createRequireWebAuthMiddleware } from "@getstrata/core/http/requireWebAuthMiddleware";
35
36
  import { withErrorHandling } from "@getstrata/core/http/response";
@@ -193,7 +194,8 @@ class HttpKernel {
193
194
  if (isEmailVerificationRequired() && !hasVerifiedEmail(user)) {
194
195
  return Response.redirect("/email/verify", 302);
195
196
  }
196
- return Response.redirect(home, 302);
197
+ const location = typeof home === "function" ? await home(user) : home;
198
+ return Response.redirect(location, 302);
197
199
  }
198
200
  return handler(request);
199
201
  });
@@ -213,6 +215,11 @@ class HttpKernel {
213
215
  wrapWebVerified(handler) {
214
216
  return this.wrapWebAuth(handler, { verified: true });
215
217
  }
218
+ wrapWebPasswordConfirm(handler) {
219
+ return this.wrapWebAuth(withMiddleware(createRequirePasswordConfirmMiddleware())(handler), {
220
+ verified: true
221
+ });
222
+ }
216
223
  wrapWebAbility(ability, handler) {
217
224
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
218
225
  const abilityChecker = resolveAbilityChecker(this.dependencies.container);