@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.
- package/CHANGELOG.md +8 -0
- package/README.md +1 -1
- package/dist/bootstrap/httpKernel.d.ts +6 -2
- package/dist/entries/buildModuleRoutes.js +8 -1
- package/dist/entries/buildWebModuleRoutes.js +8 -1
- package/dist/entries/context.js +164 -0
- package/dist/entries/createRoutes.js +99 -8
- package/dist/entries/createWebRoutes.js +8 -1
- package/dist/entries/dependencies.js +164 -0
- package/dist/entries/httpKernel.js +8 -1
- package/dist/entries/providers/view.js +164 -0
- package/dist/entries/providers.js +164 -0
- package/dist/entries/web/routing.js +8 -1
- package/dist/index.js +172 -1
- package/package.json +3 -3
|
@@ -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
|
-
|
|
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
|
-
|
|
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);
|