@getstrata/bootstrap 0.2.62 → 0.2.64
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 +3 -1
- package/dist/bootstrap/httpKernel.d.ts +4 -2
- package/dist/entries/buildModuleRoutes.js +2 -1
- package/dist/entries/buildWebModuleRoutes.js +2 -1
- package/dist/entries/context.js +164 -2
- package/dist/entries/createRoutes.js +93 -8
- package/dist/entries/createWebRoutes.js +2 -1
- package/dist/entries/dependencies.js +164 -2
- package/dist/entries/httpKernel.js +2 -1
- package/dist/entries/providers/view.js +164 -0
- package/dist/entries/providers.js +164 -2
- package/dist/entries/queue/defaultJobs.js +0 -2
- package/dist/entries/web/routing.js +2 -1
- package/dist/index.js +166 -3
- package/package.json +2 -2
|
@@ -437,7 +437,6 @@ import {
|
|
|
437
437
|
} from "@getstrata/core/queue/createAppQueue";
|
|
438
438
|
|
|
439
439
|
// ../../src/bootstrap/queue/defaultJobs.ts
|
|
440
|
-
import { DispatchWebhookJob } from "@getstrata/core/jobs/dispatchWebhookJob";
|
|
441
440
|
import { ExportAuditLogsJob } from "@getstrata/core/jobs/exportAuditLogsJob";
|
|
442
441
|
import { InvalidateCacheTagsJob as InvalidateCacheTagsJob2 } from "@getstrata/core/jobs/invalidateCacheTagsJob";
|
|
443
442
|
import { jobRegistry } from "@getstrata/core/queue/jobRegistry";
|
|
@@ -446,7 +445,6 @@ function registerDefaultJobs() {
|
|
|
446
445
|
jobRegistry.register("cache.invalidate-tags", () => {
|
|
447
446
|
return new InvalidateCacheTagsJob2(resolveApplicationCache2());
|
|
448
447
|
});
|
|
449
|
-
jobRegistry.register("webhook.dispatch", () => new DispatchWebhookJob);
|
|
450
448
|
jobRegistry.register("audit.export", () => new ExportAuditLogsJob);
|
|
451
449
|
}
|
|
452
450
|
|
|
@@ -475,15 +473,160 @@ var storageProvider = {
|
|
|
475
473
|
var storage_default = storageProvider;
|
|
476
474
|
|
|
477
475
|
// ../../src/bootstrap/providers/view.ts
|
|
476
|
+
import { resolveMembershipLookup } from "@getstrata/core/auth/membershipContext";
|
|
478
477
|
import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
|
|
478
|
+
import { appDisplayName } from "@getstrata/core/runtime/appKeyPrefix";
|
|
479
479
|
import { isViewsEnabled } from "@getstrata/core/runtime/frontendMode";
|
|
480
480
|
import {
|
|
481
481
|
configureWebErrorView,
|
|
482
|
+
configureWebLayoutData,
|
|
482
483
|
DEFAULT_VIEWS_DIRECTORY,
|
|
483
484
|
EtaViewEngine,
|
|
484
485
|
errorTemplateName,
|
|
485
486
|
resolveWebLayoutData
|
|
486
487
|
} from "@getstrata/core/view";
|
|
488
|
+
|
|
489
|
+
// ../../src/modules/organization/repository.ts
|
|
490
|
+
import { BaseRepository } from "@getstrata/core/database/baseRepository";
|
|
491
|
+
import { NotFoundError } from "@getstrata/core/errors/http";
|
|
492
|
+
import { currentTenantId } from "@getstrata/core/tenant/tenantContext";
|
|
493
|
+
|
|
494
|
+
// ../../src/modules/organization/table.ts
|
|
495
|
+
import { defineTable } from "@getstrata/core/database/table";
|
|
496
|
+
|
|
497
|
+
// ../../src/domain/workhub.ts
|
|
498
|
+
var ORGANIZATION_TABLE = "organization";
|
|
499
|
+
|
|
500
|
+
// ../../src/modules/organization/table.ts
|
|
501
|
+
var organizationTable = defineTable({
|
|
502
|
+
name: ORGANIZATION_TABLE,
|
|
503
|
+
primaryKey: "id",
|
|
504
|
+
columns: ["id", "tenant_id", "name", "slug", "created_at", "updated_at", "deleted_at"],
|
|
505
|
+
softDeletes: true,
|
|
506
|
+
defaultOrderBy: { column: "id", direction: "ASC" }
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
// ../../src/modules/organization/repository.ts
|
|
510
|
+
class OrganizationRepository extends BaseRepository {
|
|
511
|
+
constructor() {
|
|
512
|
+
super(organizationTable);
|
|
513
|
+
}
|
|
514
|
+
async findBySlug(slug) {
|
|
515
|
+
return await this.firstOrNull({ slug });
|
|
516
|
+
}
|
|
517
|
+
async listForTenant(options) {
|
|
518
|
+
return await this.findAll({
|
|
519
|
+
limit: options.limit,
|
|
520
|
+
offset: options.offset,
|
|
521
|
+
where: { tenant_id: options.tenantId ?? currentTenantId() }
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
async countForTenant(tenantId = currentTenantId()) {
|
|
525
|
+
return await this.countWhere({ tenant_id: tenantId });
|
|
526
|
+
}
|
|
527
|
+
async findForTenantOrThrow(id, tenantId = currentTenantId()) {
|
|
528
|
+
const organization = await this.findById(id);
|
|
529
|
+
if (!organization || organization.tenant_id !== tenantId) {
|
|
530
|
+
throw new NotFoundError(`SCIM group ${id} not found.`);
|
|
531
|
+
}
|
|
532
|
+
return organization;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
var repository_default = OrganizationRepository;
|
|
536
|
+
|
|
537
|
+
// ../../src/modules/user/repository.ts
|
|
538
|
+
import {
|
|
539
|
+
emailLookupForQuery,
|
|
540
|
+
protectEmail,
|
|
541
|
+
revealEmail
|
|
542
|
+
} from "@getstrata/core/crypto/fieldEncryption";
|
|
543
|
+
import { revealMfaSecret } from "@getstrata/core/crypto/mfaSecret";
|
|
544
|
+
import { BaseRepository as BaseRepository2 } from "@getstrata/core/database/baseRepository";
|
|
545
|
+
import { currentTenantId as currentTenantId2 } from "@getstrata/core/tenant/tenantContext";
|
|
546
|
+
|
|
547
|
+
// ../../src/modules/user/table.ts
|
|
548
|
+
import { defineTable as defineTable2 } from "@getstrata/core/database/table";
|
|
549
|
+
var userTable = defineTable2({
|
|
550
|
+
name: "users",
|
|
551
|
+
primaryKey: "id",
|
|
552
|
+
columns: [
|
|
553
|
+
"id",
|
|
554
|
+
"name",
|
|
555
|
+
"email",
|
|
556
|
+
"email_lookup",
|
|
557
|
+
"role",
|
|
558
|
+
"tenant_id",
|
|
559
|
+
"password_hash",
|
|
560
|
+
"email_verified_at",
|
|
561
|
+
"mfa_secret",
|
|
562
|
+
"mfa_enabled",
|
|
563
|
+
"mfa_recovery_codes",
|
|
564
|
+
"profile_photo_path",
|
|
565
|
+
"session_valid_after",
|
|
566
|
+
"current_organization_id",
|
|
567
|
+
"created_at",
|
|
568
|
+
"updated_at"
|
|
569
|
+
],
|
|
570
|
+
defaultOrderBy: { column: "id", direction: "ASC" }
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
// ../../src/modules/user/repository.ts
|
|
574
|
+
class UserRepository extends BaseRepository2 {
|
|
575
|
+
constructor() {
|
|
576
|
+
super(userTable);
|
|
577
|
+
}
|
|
578
|
+
decode(record) {
|
|
579
|
+
return {
|
|
580
|
+
...record,
|
|
581
|
+
email: revealEmail(record.email),
|
|
582
|
+
mfa_secret: revealMfaSecret(record.mfa_secret)
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
async findById(id) {
|
|
586
|
+
const record = await super.findById(id);
|
|
587
|
+
return record ? this.decode(record) : null;
|
|
588
|
+
}
|
|
589
|
+
async findAll(options = {}) {
|
|
590
|
+
const records = await super.findAll(options);
|
|
591
|
+
return records.map((record) => this.decode(record));
|
|
592
|
+
}
|
|
593
|
+
async create(values) {
|
|
594
|
+
const email = values.email;
|
|
595
|
+
if (!email) {
|
|
596
|
+
throw new Error("Email is required.");
|
|
597
|
+
}
|
|
598
|
+
const protectedEmail = protectEmail(email);
|
|
599
|
+
const record = await super.create({
|
|
600
|
+
...values,
|
|
601
|
+
tenant_id: values.tenant_id ?? currentTenantId2(),
|
|
602
|
+
email: protectedEmail.storedEmail,
|
|
603
|
+
email_lookup: protectedEmail.emailLookup,
|
|
604
|
+
password_hash: values.password_hash ?? ""
|
|
605
|
+
});
|
|
606
|
+
return this.decode(record);
|
|
607
|
+
}
|
|
608
|
+
async updateByIdOrThrow(id, values, errorFactory) {
|
|
609
|
+
const changes = { ...values };
|
|
610
|
+
if (values.email !== undefined) {
|
|
611
|
+
const protectedEmail = protectEmail(values.email);
|
|
612
|
+
changes.email = protectedEmail.storedEmail;
|
|
613
|
+
changes.email_lookup = protectedEmail.emailLookup;
|
|
614
|
+
}
|
|
615
|
+
const record = await super.updateByIdOrThrow(id, changes, errorFactory);
|
|
616
|
+
return this.decode(record);
|
|
617
|
+
}
|
|
618
|
+
async findByEmail(email) {
|
|
619
|
+
const records = await this.findWhere({ email_lookup: emailLookupForQuery(email) }, { limit: 1 });
|
|
620
|
+
const record = records[0];
|
|
621
|
+
return record ? this.decode(record) : null;
|
|
622
|
+
}
|
|
623
|
+
async countForTenant(tenantId = currentTenantId2()) {
|
|
624
|
+
return await this.countWhere({ tenant_id: tenantId });
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
var repository_default2 = UserRepository;
|
|
628
|
+
|
|
629
|
+
// ../../src/bootstrap/providers/view.ts
|
|
487
630
|
var CORE_VIEW_TOKEN = "core.view";
|
|
488
631
|
var VIEW_DIRECTORY_CONFIG_KEY = "view.directory";
|
|
489
632
|
var viewProvider = {
|
|
@@ -496,6 +639,25 @@ var viewProvider = {
|
|
|
496
639
|
config.set(VIEW_DIRECTORY_CONFIG_KEY, viewsDirectory);
|
|
497
640
|
const engine = new EtaViewEngine(viewsDirectory, (request) => resolveWebLayoutData(container, request ?? currentRequestMeta().request));
|
|
498
641
|
container.set(CORE_VIEW_TOKEN, engine);
|
|
642
|
+
configureWebLayoutData({
|
|
643
|
+
extra: async (user) => {
|
|
644
|
+
const appName = appDisplayName();
|
|
645
|
+
if (!user || typeof user.id !== "number") {
|
|
646
|
+
return { appName, currentOrganization: null, organizations: [] };
|
|
647
|
+
}
|
|
648
|
+
try {
|
|
649
|
+
const record = await new repository_default2().findByIdOrThrow(user.id);
|
|
650
|
+
const memberships = await resolveMembershipLookup().listForUser(record.id);
|
|
651
|
+
const organizationsRepo = new repository_default;
|
|
652
|
+
const organizations = (await Promise.all(memberships.map((membership) => organizationsRepo.findById(membership.organization_id)))).filter((organization) => Boolean(organization && !organization.deleted_at));
|
|
653
|
+
const currentId = record.current_organization_id ?? null;
|
|
654
|
+
const currentOrganization = organizations.find((organization) => organization.id === currentId) ?? organizations[0] ?? null;
|
|
655
|
+
return { appName, currentOrganization, organizations };
|
|
656
|
+
} catch {
|
|
657
|
+
return { appName, currentOrganization: null, organizations: [] };
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
});
|
|
499
661
|
configureWebErrorView({
|
|
500
662
|
render: async (input) => engine.render(errorTemplateName(input.status), {
|
|
501
663
|
title: input.title,
|
|
@@ -185,7 +185,8 @@ class HttpKernel {
|
|
|
185
185
|
if (isEmailVerificationRequired() && !hasVerifiedEmail(user)) {
|
|
186
186
|
return Response.redirect("/email/verify", 302);
|
|
187
187
|
}
|
|
188
|
-
|
|
188
|
+
const location = typeof home === "function" ? await home(user) : home;
|
|
189
|
+
return Response.redirect(location, 302);
|
|
189
190
|
}
|
|
190
191
|
return handler(request);
|
|
191
192
|
});
|
|
@@ -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,
|
|
@@ -427,7 +427,6 @@ import {
|
|
|
427
427
|
} from "@getstrata/core/queue/createAppQueue";
|
|
428
428
|
|
|
429
429
|
// ../../src/bootstrap/queue/defaultJobs.ts
|
|
430
|
-
import { DispatchWebhookJob } from "@getstrata/core/jobs/dispatchWebhookJob";
|
|
431
430
|
import { ExportAuditLogsJob } from "@getstrata/core/jobs/exportAuditLogsJob";
|
|
432
431
|
import { InvalidateCacheTagsJob as InvalidateCacheTagsJob2 } from "@getstrata/core/jobs/invalidateCacheTagsJob";
|
|
433
432
|
import { jobRegistry } from "@getstrata/core/queue/jobRegistry";
|
|
@@ -436,7 +435,6 @@ function registerDefaultJobs() {
|
|
|
436
435
|
jobRegistry.register("cache.invalidate-tags", () => {
|
|
437
436
|
return new InvalidateCacheTagsJob2(resolveApplicationCache2());
|
|
438
437
|
});
|
|
439
|
-
jobRegistry.register("webhook.dispatch", () => new DispatchWebhookJob);
|
|
440
438
|
jobRegistry.register("audit.export", () => new ExportAuditLogsJob);
|
|
441
439
|
}
|
|
442
440
|
|
|
@@ -465,15 +463,160 @@ var storageProvider = {
|
|
|
465
463
|
var storage_default = storageProvider;
|
|
466
464
|
|
|
467
465
|
// ../../src/bootstrap/providers/view.ts
|
|
466
|
+
import { resolveMembershipLookup } from "@getstrata/core/auth/membershipContext";
|
|
468
467
|
import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
|
|
468
|
+
import { appDisplayName } from "@getstrata/core/runtime/appKeyPrefix";
|
|
469
469
|
import { isViewsEnabled } from "@getstrata/core/runtime/frontendMode";
|
|
470
470
|
import {
|
|
471
471
|
configureWebErrorView,
|
|
472
|
+
configureWebLayoutData,
|
|
472
473
|
DEFAULT_VIEWS_DIRECTORY,
|
|
473
474
|
EtaViewEngine,
|
|
474
475
|
errorTemplateName,
|
|
475
476
|
resolveWebLayoutData
|
|
476
477
|
} from "@getstrata/core/view";
|
|
478
|
+
|
|
479
|
+
// ../../src/modules/organization/repository.ts
|
|
480
|
+
import { BaseRepository } from "@getstrata/core/database/baseRepository";
|
|
481
|
+
import { NotFoundError } from "@getstrata/core/errors/http";
|
|
482
|
+
import { currentTenantId } from "@getstrata/core/tenant/tenantContext";
|
|
483
|
+
|
|
484
|
+
// ../../src/modules/organization/table.ts
|
|
485
|
+
import { defineTable } from "@getstrata/core/database/table";
|
|
486
|
+
|
|
487
|
+
// ../../src/domain/workhub.ts
|
|
488
|
+
var ORGANIZATION_TABLE = "organization";
|
|
489
|
+
|
|
490
|
+
// ../../src/modules/organization/table.ts
|
|
491
|
+
var organizationTable = defineTable({
|
|
492
|
+
name: ORGANIZATION_TABLE,
|
|
493
|
+
primaryKey: "id",
|
|
494
|
+
columns: ["id", "tenant_id", "name", "slug", "created_at", "updated_at", "deleted_at"],
|
|
495
|
+
softDeletes: true,
|
|
496
|
+
defaultOrderBy: { column: "id", direction: "ASC" }
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
// ../../src/modules/organization/repository.ts
|
|
500
|
+
class OrganizationRepository extends BaseRepository {
|
|
501
|
+
constructor() {
|
|
502
|
+
super(organizationTable);
|
|
503
|
+
}
|
|
504
|
+
async findBySlug(slug) {
|
|
505
|
+
return await this.firstOrNull({ slug });
|
|
506
|
+
}
|
|
507
|
+
async listForTenant(options) {
|
|
508
|
+
return await this.findAll({
|
|
509
|
+
limit: options.limit,
|
|
510
|
+
offset: options.offset,
|
|
511
|
+
where: { tenant_id: options.tenantId ?? currentTenantId() }
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
async countForTenant(tenantId = currentTenantId()) {
|
|
515
|
+
return await this.countWhere({ tenant_id: tenantId });
|
|
516
|
+
}
|
|
517
|
+
async findForTenantOrThrow(id, tenantId = currentTenantId()) {
|
|
518
|
+
const organization = await this.findById(id);
|
|
519
|
+
if (!organization || organization.tenant_id !== tenantId) {
|
|
520
|
+
throw new NotFoundError(`SCIM group ${id} not found.`);
|
|
521
|
+
}
|
|
522
|
+
return organization;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
var repository_default = OrganizationRepository;
|
|
526
|
+
|
|
527
|
+
// ../../src/modules/user/repository.ts
|
|
528
|
+
import {
|
|
529
|
+
emailLookupForQuery,
|
|
530
|
+
protectEmail,
|
|
531
|
+
revealEmail
|
|
532
|
+
} from "@getstrata/core/crypto/fieldEncryption";
|
|
533
|
+
import { revealMfaSecret } from "@getstrata/core/crypto/mfaSecret";
|
|
534
|
+
import { BaseRepository as BaseRepository2 } from "@getstrata/core/database/baseRepository";
|
|
535
|
+
import { currentTenantId as currentTenantId2 } from "@getstrata/core/tenant/tenantContext";
|
|
536
|
+
|
|
537
|
+
// ../../src/modules/user/table.ts
|
|
538
|
+
import { defineTable as defineTable2 } from "@getstrata/core/database/table";
|
|
539
|
+
var userTable = defineTable2({
|
|
540
|
+
name: "users",
|
|
541
|
+
primaryKey: "id",
|
|
542
|
+
columns: [
|
|
543
|
+
"id",
|
|
544
|
+
"name",
|
|
545
|
+
"email",
|
|
546
|
+
"email_lookup",
|
|
547
|
+
"role",
|
|
548
|
+
"tenant_id",
|
|
549
|
+
"password_hash",
|
|
550
|
+
"email_verified_at",
|
|
551
|
+
"mfa_secret",
|
|
552
|
+
"mfa_enabled",
|
|
553
|
+
"mfa_recovery_codes",
|
|
554
|
+
"profile_photo_path",
|
|
555
|
+
"session_valid_after",
|
|
556
|
+
"current_organization_id",
|
|
557
|
+
"created_at",
|
|
558
|
+
"updated_at"
|
|
559
|
+
],
|
|
560
|
+
defaultOrderBy: { column: "id", direction: "ASC" }
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
// ../../src/modules/user/repository.ts
|
|
564
|
+
class UserRepository extends BaseRepository2 {
|
|
565
|
+
constructor() {
|
|
566
|
+
super(userTable);
|
|
567
|
+
}
|
|
568
|
+
decode(record) {
|
|
569
|
+
return {
|
|
570
|
+
...record,
|
|
571
|
+
email: revealEmail(record.email),
|
|
572
|
+
mfa_secret: revealMfaSecret(record.mfa_secret)
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
async findById(id) {
|
|
576
|
+
const record = await super.findById(id);
|
|
577
|
+
return record ? this.decode(record) : null;
|
|
578
|
+
}
|
|
579
|
+
async findAll(options = {}) {
|
|
580
|
+
const records = await super.findAll(options);
|
|
581
|
+
return records.map((record) => this.decode(record));
|
|
582
|
+
}
|
|
583
|
+
async create(values) {
|
|
584
|
+
const email = values.email;
|
|
585
|
+
if (!email) {
|
|
586
|
+
throw new Error("Email is required.");
|
|
587
|
+
}
|
|
588
|
+
const protectedEmail = protectEmail(email);
|
|
589
|
+
const record = await super.create({
|
|
590
|
+
...values,
|
|
591
|
+
tenant_id: values.tenant_id ?? currentTenantId2(),
|
|
592
|
+
email: protectedEmail.storedEmail,
|
|
593
|
+
email_lookup: protectedEmail.emailLookup,
|
|
594
|
+
password_hash: values.password_hash ?? ""
|
|
595
|
+
});
|
|
596
|
+
return this.decode(record);
|
|
597
|
+
}
|
|
598
|
+
async updateByIdOrThrow(id, values, errorFactory) {
|
|
599
|
+
const changes = { ...values };
|
|
600
|
+
if (values.email !== undefined) {
|
|
601
|
+
const protectedEmail = protectEmail(values.email);
|
|
602
|
+
changes.email = protectedEmail.storedEmail;
|
|
603
|
+
changes.email_lookup = protectedEmail.emailLookup;
|
|
604
|
+
}
|
|
605
|
+
const record = await super.updateByIdOrThrow(id, changes, errorFactory);
|
|
606
|
+
return this.decode(record);
|
|
607
|
+
}
|
|
608
|
+
async findByEmail(email) {
|
|
609
|
+
const records = await this.findWhere({ email_lookup: emailLookupForQuery(email) }, { limit: 1 });
|
|
610
|
+
const record = records[0];
|
|
611
|
+
return record ? this.decode(record) : null;
|
|
612
|
+
}
|
|
613
|
+
async countForTenant(tenantId = currentTenantId2()) {
|
|
614
|
+
return await this.countWhere({ tenant_id: tenantId });
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
var repository_default2 = UserRepository;
|
|
618
|
+
|
|
619
|
+
// ../../src/bootstrap/providers/view.ts
|
|
477
620
|
var CORE_VIEW_TOKEN = "core.view";
|
|
478
621
|
var VIEW_DIRECTORY_CONFIG_KEY = "view.directory";
|
|
479
622
|
var viewProvider = {
|
|
@@ -486,6 +629,25 @@ var viewProvider = {
|
|
|
486
629
|
config.set(VIEW_DIRECTORY_CONFIG_KEY, viewsDirectory);
|
|
487
630
|
const engine = new EtaViewEngine(viewsDirectory, (request) => resolveWebLayoutData(container, request ?? currentRequestMeta().request));
|
|
488
631
|
container.set(CORE_VIEW_TOKEN, engine);
|
|
632
|
+
configureWebLayoutData({
|
|
633
|
+
extra: async (user) => {
|
|
634
|
+
const appName = appDisplayName();
|
|
635
|
+
if (!user || typeof user.id !== "number") {
|
|
636
|
+
return { appName, currentOrganization: null, organizations: [] };
|
|
637
|
+
}
|
|
638
|
+
try {
|
|
639
|
+
const record = await new repository_default2().findByIdOrThrow(user.id);
|
|
640
|
+
const memberships = await resolveMembershipLookup().listForUser(record.id);
|
|
641
|
+
const organizationsRepo = new repository_default;
|
|
642
|
+
const organizations = (await Promise.all(memberships.map((membership) => organizationsRepo.findById(membership.organization_id)))).filter((organization) => Boolean(organization && !organization.deleted_at));
|
|
643
|
+
const currentId = record.current_organization_id ?? null;
|
|
644
|
+
const currentOrganization = organizations.find((organization) => organization.id === currentId) ?? organizations[0] ?? null;
|
|
645
|
+
return { appName, currentOrganization, organizations };
|
|
646
|
+
} catch {
|
|
647
|
+
return { appName, currentOrganization: null, organizations: [] };
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
});
|
|
489
651
|
configureWebErrorView({
|
|
490
652
|
render: async (input) => engine.render(errorTemplateName(input.status), {
|
|
491
653
|
title: input.title,
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
var __jsonParse = (a) => JSON.parse(a);
|
|
3
3
|
|
|
4
4
|
// ../../src/bootstrap/queue/defaultJobs.ts
|
|
5
|
-
import { DispatchWebhookJob } from "@getstrata/core/jobs/dispatchWebhookJob";
|
|
6
5
|
import { ExportAuditLogsJob } from "@getstrata/core/jobs/exportAuditLogsJob";
|
|
7
6
|
import { InvalidateCacheTagsJob } from "@getstrata/core/jobs/invalidateCacheTagsJob";
|
|
8
7
|
import { jobRegistry } from "@getstrata/core/queue/jobRegistry";
|
|
@@ -11,7 +10,6 @@ function registerDefaultJobs() {
|
|
|
11
10
|
jobRegistry.register("cache.invalidate-tags", () => {
|
|
12
11
|
return new InvalidateCacheTagsJob(resolveApplicationCache());
|
|
13
12
|
});
|
|
14
|
-
jobRegistry.register("webhook.dispatch", () => new DispatchWebhookJob);
|
|
15
13
|
jobRegistry.register("audit.export", () => new ExportAuditLogsJob);
|
|
16
14
|
}
|
|
17
15
|
export {
|
|
@@ -194,7 +194,8 @@ class HttpKernel {
|
|
|
194
194
|
if (isEmailVerificationRequired() && !hasVerifiedEmail(user)) {
|
|
195
195
|
return Response.redirect("/email/verify", 302);
|
|
196
196
|
}
|
|
197
|
-
|
|
197
|
+
const location = typeof home === "function" ? await home(user) : home;
|
|
198
|
+
return Response.redirect(location, 302);
|
|
198
199
|
}
|
|
199
200
|
return handler(request);
|
|
200
201
|
});
|