@getstrata/bootstrap 0.2.66 → 0.4.2
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 +45 -10
- package/README.md +10 -10
- package/dist/_.._/_.._/index.html +2 -2
- package/dist/bootstrap/createSpaRoutes.d.ts +9 -2
- package/dist/bootstrap/createWebRoutes.d.ts +7 -3
- package/dist/bootstrap/dogfoodApp.d.ts +7 -0
- package/dist/bootstrap/httpKernel.d.ts +4 -4
- package/dist/bootstrap/public-api.d.ts +2 -1
- package/dist/bootstrap/schemaTarget.d.ts +5 -0
- package/dist/bootstrap/server.d.ts +1 -1
- package/dist/bootstrap/web/index.d.ts +1 -1
- package/dist/bootstrap/web/routing.d.ts +2 -2
- package/dist/bootstrap/web/session.d.ts +18 -3
- package/dist/entries/buildModuleRoutes.js +8 -3
- package/dist/entries/buildWebModuleRoutes.js +8 -3
- package/dist/entries/cache/modelCacheTags.js +6 -1
- package/dist/entries/context.js +52 -166
- package/dist/entries/createRoutes.js +83 -888
- package/dist/entries/createSpaRoutes.js +48 -24
- package/dist/entries/createWebRoutes.js +13 -7
- package/dist/entries/dependencies.js +52 -166
- package/dist/entries/discoverModules.js +6 -1
- package/dist/entries/httpKernel.js +2 -2
- package/dist/entries/listeners/invalidateCacheOnModelWrite.js +6 -1
- package/dist/entries/providers/view.js +5 -160
- package/dist/entries/providers.js +52 -166
- package/dist/entries/secretsGuard.js +13 -8
- package/dist/entries/web/routing.js +4 -3
- package/dist/entries/web/session.js +58 -25
- package/dist/index.js +132 -206
- package/package.json +5 -5
- package/dist/bootstrap/scimRoutes.d.ts +0 -24
|
@@ -4,37 +4,61 @@ var __jsonParse = (a) => JSON.parse(a);
|
|
|
4
4
|
// ../../src/bootstrap/createSpaRoutes.ts
|
|
5
5
|
import { join } from "path";
|
|
6
6
|
import { jsonResponse } from "@getstrata/core/http/response";
|
|
7
|
-
import { isSpaEnabled } from "@getstrata/core/runtime/frontendMode";
|
|
7
|
+
import { isSpaEnabled, isViewsEnabled, readSpaPrefix } from "@getstrata/core/runtime/frontendMode";
|
|
8
8
|
var SPA_DIST_DIRECTORY = join(process.cwd(), "frontend/dist");
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
9
|
+
function relativeSpaPath(pathname, prefix) {
|
|
10
|
+
if (pathname === prefix || pathname === `${prefix}/`) {
|
|
11
|
+
return "";
|
|
12
|
+
}
|
|
13
|
+
if (pathname.startsWith(`${prefix}/`)) {
|
|
14
|
+
return pathname.slice(prefix.length + 1);
|
|
15
|
+
}
|
|
16
|
+
return "";
|
|
17
|
+
}
|
|
18
|
+
function createSpaDocumentHandler(prefix, distDirectory) {
|
|
19
|
+
const indexFilePath = join(distDirectory, "index.html");
|
|
20
|
+
return async (request) => {
|
|
21
|
+
const pathname = new URL(request.url).pathname;
|
|
22
|
+
if (pathname.startsWith("/api/")) {
|
|
23
|
+
return new Response("Not found", { status: 404 });
|
|
24
|
+
}
|
|
25
|
+
const relativePath = relativeSpaPath(pathname, prefix);
|
|
26
|
+
const assetFile = Bun.file(join(distDirectory, relativePath));
|
|
27
|
+
if (relativePath.length > 0 && await assetFile.exists()) {
|
|
28
|
+
return new Response(assetFile);
|
|
29
|
+
}
|
|
30
|
+
const indexFile = Bun.file(indexFilePath);
|
|
31
|
+
if (await indexFile.exists()) {
|
|
32
|
+
return new Response(indexFile, {
|
|
33
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return jsonResponse({
|
|
37
|
+
error: "SPA build not found. Run `bun run frontend:build` in your app."
|
|
38
|
+
}, { status: 503 });
|
|
30
39
|
};
|
|
31
40
|
}
|
|
32
|
-
function
|
|
41
|
+
function createSpaRoutes(_dependencies, options = {}) {
|
|
42
|
+
const prefix = options.prefix ?? readSpaPrefix();
|
|
43
|
+
const distDirectory = options.distDirectory ?? SPA_DIST_DIRECTORY;
|
|
44
|
+
const wrap = options.wrap ?? ((handler2) => handler2);
|
|
45
|
+
const handler = wrap(createSpaDocumentHandler(prefix, distDirectory));
|
|
46
|
+
const routes = {
|
|
47
|
+
[prefix]: handler,
|
|
48
|
+
[`${prefix}/`]: handler,
|
|
49
|
+
[`${prefix}/*`]: handler
|
|
50
|
+
};
|
|
51
|
+
if (!isViewsEnabled()) {
|
|
52
|
+
routes["/"] = async () => Response.redirect(`${prefix}/`, 302);
|
|
53
|
+
}
|
|
54
|
+
return routes;
|
|
55
|
+
}
|
|
56
|
+
function mergeSpaRoutes(dependencies, routes, options) {
|
|
33
57
|
if (!isSpaEnabled()) {
|
|
34
58
|
return routes;
|
|
35
59
|
}
|
|
36
60
|
return {
|
|
37
|
-
...createSpaRoutes(dependencies),
|
|
61
|
+
...createSpaRoutes(dependencies, options),
|
|
38
62
|
...routes
|
|
39
63
|
};
|
|
40
64
|
}
|
|
@@ -36,7 +36,7 @@ import { createRequireGlobalAdminMiddleware } from "@getstrata/core/http/require
|
|
|
36
36
|
import { createRequirePasswordConfirmMiddleware } from "@getstrata/core/http/requirePasswordConfirmMiddleware";
|
|
37
37
|
import { createRequireVerifiedMiddleware } from "@getstrata/core/http/requireVerifiedMiddleware";
|
|
38
38
|
import { createRequireWebAuthMiddleware } from "@getstrata/core/http/requireWebAuthMiddleware";
|
|
39
|
-
import { withErrorHandling } from "@getstrata/core/http/response";
|
|
39
|
+
import { withErrorHandling, withJsonErrorHandling } from "@getstrata/core/http/response";
|
|
40
40
|
import { withMiddleware } from "@getstrata/core/http/routeMiddleware";
|
|
41
41
|
import { createSecurityHeadersMiddleware } from "@getstrata/core/http/securityHeadersMiddleware";
|
|
42
42
|
import { createValidateSignatureMiddleware } from "@getstrata/core/http/signedUrl";
|
|
@@ -184,7 +184,7 @@ class HttpKernel {
|
|
|
184
184
|
return withMiddleware(...middleware)(handler);
|
|
185
185
|
}
|
|
186
186
|
wrapApi(handler) {
|
|
187
|
-
return this.wrap(["api", "authenticated"], handler);
|
|
187
|
+
return withJsonErrorHandling(this.wrap(["api", "authenticated"], handler));
|
|
188
188
|
}
|
|
189
189
|
wrapWeb(handler) {
|
|
190
190
|
return withErrorHandling(this.wrap("web", handler));
|
|
@@ -349,7 +349,12 @@ function readDiscoverModulesState() {
|
|
|
349
349
|
return state;
|
|
350
350
|
}
|
|
351
351
|
function configureModulesDirectory(modulesDir) {
|
|
352
|
-
readDiscoverModulesState()
|
|
352
|
+
const state = readDiscoverModulesState();
|
|
353
|
+
if (state.configuredModulesDir !== modulesDir) {
|
|
354
|
+
state.appModules.length = 0;
|
|
355
|
+
state.modulesReady = undefined;
|
|
356
|
+
}
|
|
357
|
+
state.configuredModulesDir = modulesDir;
|
|
353
358
|
}
|
|
354
359
|
function resolveModulesDirectory(options) {
|
|
355
360
|
const state = readDiscoverModulesState();
|
|
@@ -506,9 +511,10 @@ function buildWebModuleRoutes(dependencies, options = {}) {
|
|
|
506
511
|
function registerRoute(method, path, middleware) {
|
|
507
512
|
routeRegistry.register({ method, path, middleware });
|
|
508
513
|
}
|
|
509
|
-
function createWebRoutes(dependencies) {
|
|
514
|
+
function createWebRoutes(dependencies, options = {}) {
|
|
510
515
|
const wrappedRoutes = buildWebModuleRoutes(dependencies, {
|
|
511
|
-
clearRegistry: false
|
|
516
|
+
clearRegistry: false,
|
|
517
|
+
modules: options.modules
|
|
512
518
|
});
|
|
513
519
|
registerRoute("GET", "/", ["global", "web"]);
|
|
514
520
|
wrappedRoutes["/assets/*"] = async (request) => {
|
|
@@ -524,12 +530,12 @@ function createWebRoutes(dependencies) {
|
|
|
524
530
|
registerRoute("GET", "/assets/*", ["global", "web"]);
|
|
525
531
|
return wrappedRoutes;
|
|
526
532
|
}
|
|
527
|
-
function mergeWebRoutes(dependencies, routes) {
|
|
533
|
+
function mergeWebRoutes(dependencies, routes, options = {}) {
|
|
528
534
|
if (!isViewsEnabled2()) {
|
|
529
535
|
return routes;
|
|
530
536
|
}
|
|
531
537
|
return {
|
|
532
|
-
...createWebRoutes(dependencies),
|
|
538
|
+
...createWebRoutes(dependencies, options),
|
|
533
539
|
...routes
|
|
534
540
|
};
|
|
535
541
|
}
|
|
@@ -40,7 +40,12 @@ function readDiscoverModulesState() {
|
|
|
40
40
|
return state;
|
|
41
41
|
}
|
|
42
42
|
function configureModulesDirectory(modulesDir) {
|
|
43
|
-
readDiscoverModulesState()
|
|
43
|
+
const state = readDiscoverModulesState();
|
|
44
|
+
if (state.configuredModulesDir !== modulesDir) {
|
|
45
|
+
state.appModules.length = 0;
|
|
46
|
+
state.modulesReady = undefined;
|
|
47
|
+
}
|
|
48
|
+
state.configuredModulesDir = modulesDir;
|
|
44
49
|
}
|
|
45
50
|
function resolveModulesDirectory(options) {
|
|
46
51
|
const state = readDiscoverModulesState();
|
|
@@ -91,17 +96,19 @@ function resetDiscoverModulesForTests() {
|
|
|
91
96
|
state.modulesReady = undefined;
|
|
92
97
|
}
|
|
93
98
|
// ../../src/bootstrap/providers/auth.ts
|
|
99
|
+
import { BasicAuthGuard } from "@getstrata/core/auth/basicAuthGuard";
|
|
94
100
|
import {
|
|
95
101
|
AuthManager,
|
|
96
102
|
CompositeGuard,
|
|
97
103
|
DatabaseTokenGuard,
|
|
98
104
|
GuestGuard
|
|
99
105
|
} from "@getstrata/core/auth/guard";
|
|
106
|
+
import { JwtGuard } from "@getstrata/core/auth/jwtGuard";
|
|
100
107
|
import { SessionGuard } from "@getstrata/core/auth/sessionGuard";
|
|
101
108
|
|
|
102
109
|
// ../../src/config/auth.ts
|
|
103
110
|
var authConfig = {
|
|
104
|
-
allowDevHeaders:
|
|
111
|
+
allowDevHeaders: process.env.AUTH_DEV_HEADERS === "true",
|
|
105
112
|
tokenDefaultAbilities: ["*"]
|
|
106
113
|
};
|
|
107
114
|
|
|
@@ -136,11 +143,22 @@ var authProvider = {
|
|
|
136
143
|
name: "core.auth",
|
|
137
144
|
register({ container, config }) {
|
|
138
145
|
config.set("auth.allowDevHeaders", authConfig.allowDevHeaders);
|
|
139
|
-
const
|
|
146
|
+
const apiGuard = new DatabaseTokenGuard(container);
|
|
147
|
+
const sessionGuard = new SessionGuard(container);
|
|
148
|
+
const jwtGuard = new JwtGuard;
|
|
149
|
+
const basicGuard = new BasicAuthGuard(container);
|
|
150
|
+
const guards = [apiGuard, jwtGuard, basicGuard, sessionGuard];
|
|
140
151
|
if (authConfig.allowDevHeaders) {
|
|
141
152
|
guards.push(new GuestGuard);
|
|
142
153
|
}
|
|
143
|
-
|
|
154
|
+
const auth = new AuthManager(new CompositeGuard(guards));
|
|
155
|
+
auth.registerGuard("api", apiGuard);
|
|
156
|
+
auth.registerGuard("access_token", apiGuard);
|
|
157
|
+
auth.registerGuard("jwt", jwtGuard);
|
|
158
|
+
auth.registerGuard("basic", basicGuard);
|
|
159
|
+
auth.registerGuard("web", sessionGuard);
|
|
160
|
+
auth.registerGuard("session", sessionGuard);
|
|
161
|
+
container.set(CORE_AUTH_TOKEN, auth);
|
|
144
162
|
}
|
|
145
163
|
};
|
|
146
164
|
var auth_default = authProvider;
|
|
@@ -201,8 +219,12 @@ var queueConfig = {
|
|
|
201
219
|
};
|
|
202
220
|
// ../../src/bootstrap/env.ts
|
|
203
221
|
import { defineEnvSchema } from "@getstrata/core/config/envSchema";
|
|
222
|
+
import { DEFAULT_SPA_PREFIX, FRONTEND_MODE_PATTERN } from "@getstrata/core/runtime/frontendMode";
|
|
204
223
|
var appEnvSchema = defineEnvSchema({
|
|
205
|
-
DATABASE_URL: {
|
|
224
|
+
DATABASE_URL: {
|
|
225
|
+
required: true,
|
|
226
|
+
pattern: /^(postgres(ql)?|mysql|sqlite):\/\//i
|
|
227
|
+
},
|
|
206
228
|
PORT: {
|
|
207
229
|
integer: true,
|
|
208
230
|
minimum: 1,
|
|
@@ -230,9 +252,21 @@ var appEnvSchema = defineEnvSchema({
|
|
|
230
252
|
pattern: /^(sync|async|redis)$/
|
|
231
253
|
},
|
|
232
254
|
AUTH_DEV_HEADERS: {
|
|
233
|
-
default: "
|
|
255
|
+
default: "false",
|
|
234
256
|
pattern: /^(true|false|0|1)$/
|
|
235
257
|
},
|
|
258
|
+
DB_CONNECTION: {
|
|
259
|
+
default: "",
|
|
260
|
+
pattern: /^(pgsql|postgres|postgresql|mysql|mariadb|sqlite)?$/i
|
|
261
|
+
},
|
|
262
|
+
AUTH_DEFAULT_GUARD: {
|
|
263
|
+
default: "web"
|
|
264
|
+
},
|
|
265
|
+
JWT_TTL_SECONDS: {
|
|
266
|
+
integer: true,
|
|
267
|
+
minimum: 60,
|
|
268
|
+
default: "3600"
|
|
269
|
+
},
|
|
236
270
|
APP_ENV: {
|
|
237
271
|
default: "local"
|
|
238
272
|
},
|
|
@@ -242,6 +276,13 @@ var appEnvSchema = defineEnvSchema({
|
|
|
242
276
|
APP_URL: {
|
|
243
277
|
default: "http://localhost:3000"
|
|
244
278
|
},
|
|
279
|
+
FRONTEND_MODE: {
|
|
280
|
+
default: "api",
|
|
281
|
+
pattern: FRONTEND_MODE_PATTERN
|
|
282
|
+
},
|
|
283
|
+
SPA_PREFIX: {
|
|
284
|
+
default: DEFAULT_SPA_PREFIX
|
|
285
|
+
},
|
|
245
286
|
API_PREFIX: {
|
|
246
287
|
default: "/api/v1"
|
|
247
288
|
},
|
|
@@ -480,7 +521,6 @@ var storageProvider = {
|
|
|
480
521
|
var storage_default = storageProvider;
|
|
481
522
|
|
|
482
523
|
// ../../src/bootstrap/providers/view.ts
|
|
483
|
-
import { resolveMembershipLookup } from "@getstrata/core/auth/membershipContext";
|
|
484
524
|
import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
|
|
485
525
|
import { appDisplayName } from "@getstrata/core/runtime/appKeyPrefix";
|
|
486
526
|
import { isViewsEnabled } from "@getstrata/core/runtime/frontendMode";
|
|
@@ -492,148 +532,6 @@ import {
|
|
|
492
532
|
errorTemplateName,
|
|
493
533
|
resolveWebLayoutData
|
|
494
534
|
} from "@getstrata/core/view";
|
|
495
|
-
|
|
496
|
-
// ../../src/modules/organization/repository.ts
|
|
497
|
-
import { BaseRepository } from "@getstrata/core/database/baseRepository";
|
|
498
|
-
import { NotFoundError } from "@getstrata/core/errors/http";
|
|
499
|
-
import { currentTenantId } from "@getstrata/core/tenant/tenantContext";
|
|
500
|
-
|
|
501
|
-
// ../../src/modules/organization/table.ts
|
|
502
|
-
import { defineTable } from "@getstrata/core/database/table";
|
|
503
|
-
|
|
504
|
-
// ../../src/domain/workhub.ts
|
|
505
|
-
var ORGANIZATION_TABLE = "organization";
|
|
506
|
-
|
|
507
|
-
// ../../src/modules/organization/table.ts
|
|
508
|
-
var organizationTable = defineTable({
|
|
509
|
-
name: ORGANIZATION_TABLE,
|
|
510
|
-
primaryKey: "id",
|
|
511
|
-
columns: ["id", "tenant_id", "name", "slug", "created_at", "updated_at", "deleted_at"],
|
|
512
|
-
softDeletes: true,
|
|
513
|
-
defaultOrderBy: { column: "id", direction: "ASC" }
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
// ../../src/modules/organization/repository.ts
|
|
517
|
-
class OrganizationRepository extends BaseRepository {
|
|
518
|
-
constructor() {
|
|
519
|
-
super(organizationTable);
|
|
520
|
-
}
|
|
521
|
-
async findBySlug(slug) {
|
|
522
|
-
return await this.firstOrNull({ slug });
|
|
523
|
-
}
|
|
524
|
-
async listForTenant(options) {
|
|
525
|
-
return await this.findAll({
|
|
526
|
-
limit: options.limit,
|
|
527
|
-
offset: options.offset,
|
|
528
|
-
where: { tenant_id: options.tenantId ?? currentTenantId() }
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
|
-
async countForTenant(tenantId = currentTenantId()) {
|
|
532
|
-
return await this.countWhere({ tenant_id: tenantId });
|
|
533
|
-
}
|
|
534
|
-
async findForTenantOrThrow(id, tenantId = currentTenantId()) {
|
|
535
|
-
const organization = await this.findById(id);
|
|
536
|
-
if (!organization || organization.tenant_id !== tenantId) {
|
|
537
|
-
throw new NotFoundError(`SCIM group ${id} not found.`);
|
|
538
|
-
}
|
|
539
|
-
return organization;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
var repository_default = OrganizationRepository;
|
|
543
|
-
|
|
544
|
-
// ../../src/modules/user/repository.ts
|
|
545
|
-
import {
|
|
546
|
-
emailLookupForQuery,
|
|
547
|
-
protectEmail,
|
|
548
|
-
revealEmail
|
|
549
|
-
} from "@getstrata/core/crypto/fieldEncryption";
|
|
550
|
-
import { revealMfaSecret } from "@getstrata/core/crypto/mfaSecret";
|
|
551
|
-
import { BaseRepository as BaseRepository2 } from "@getstrata/core/database/baseRepository";
|
|
552
|
-
import { currentTenantId as currentTenantId2 } from "@getstrata/core/tenant/tenantContext";
|
|
553
|
-
|
|
554
|
-
// ../../src/modules/user/table.ts
|
|
555
|
-
import { defineTable as defineTable2 } from "@getstrata/core/database/table";
|
|
556
|
-
var userTable = defineTable2({
|
|
557
|
-
name: "users",
|
|
558
|
-
primaryKey: "id",
|
|
559
|
-
columns: [
|
|
560
|
-
"id",
|
|
561
|
-
"name",
|
|
562
|
-
"email",
|
|
563
|
-
"email_lookup",
|
|
564
|
-
"role",
|
|
565
|
-
"tenant_id",
|
|
566
|
-
"password_hash",
|
|
567
|
-
"email_verified_at",
|
|
568
|
-
"mfa_secret",
|
|
569
|
-
"mfa_enabled",
|
|
570
|
-
"mfa_recovery_codes",
|
|
571
|
-
"profile_photo_path",
|
|
572
|
-
"session_valid_after",
|
|
573
|
-
"current_organization_id",
|
|
574
|
-
"created_at",
|
|
575
|
-
"updated_at"
|
|
576
|
-
],
|
|
577
|
-
defaultOrderBy: { column: "id", direction: "ASC" }
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
// ../../src/modules/user/repository.ts
|
|
581
|
-
class UserRepository extends BaseRepository2 {
|
|
582
|
-
constructor() {
|
|
583
|
-
super(userTable);
|
|
584
|
-
}
|
|
585
|
-
decode(record) {
|
|
586
|
-
return {
|
|
587
|
-
...record,
|
|
588
|
-
email: revealEmail(record.email),
|
|
589
|
-
mfa_secret: revealMfaSecret(record.mfa_secret)
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
|
-
async findById(id) {
|
|
593
|
-
const record = await super.findById(id);
|
|
594
|
-
return record ? this.decode(record) : null;
|
|
595
|
-
}
|
|
596
|
-
async findAll(options = {}) {
|
|
597
|
-
const records = await super.findAll(options);
|
|
598
|
-
return records.map((record) => this.decode(record));
|
|
599
|
-
}
|
|
600
|
-
async create(values) {
|
|
601
|
-
const email = values.email;
|
|
602
|
-
if (!email) {
|
|
603
|
-
throw new Error("Email is required.");
|
|
604
|
-
}
|
|
605
|
-
const protectedEmail = protectEmail(email);
|
|
606
|
-
const record = await super.create({
|
|
607
|
-
...values,
|
|
608
|
-
tenant_id: values.tenant_id ?? currentTenantId2(),
|
|
609
|
-
email: protectedEmail.storedEmail,
|
|
610
|
-
email_lookup: protectedEmail.emailLookup,
|
|
611
|
-
password_hash: values.password_hash ?? ""
|
|
612
|
-
});
|
|
613
|
-
return this.decode(record);
|
|
614
|
-
}
|
|
615
|
-
async updateByIdOrThrow(id, values, errorFactory) {
|
|
616
|
-
const changes = { ...values };
|
|
617
|
-
if (values.email !== undefined) {
|
|
618
|
-
const protectedEmail = protectEmail(values.email);
|
|
619
|
-
changes.email = protectedEmail.storedEmail;
|
|
620
|
-
changes.email_lookup = protectedEmail.emailLookup;
|
|
621
|
-
}
|
|
622
|
-
const record = await super.updateByIdOrThrow(id, changes, errorFactory);
|
|
623
|
-
return this.decode(record);
|
|
624
|
-
}
|
|
625
|
-
async findByEmail(email) {
|
|
626
|
-
const records = await this.findWhere({ email_lookup: emailLookupForQuery(email) }, { limit: 1 });
|
|
627
|
-
const record = records[0];
|
|
628
|
-
return record ? this.decode(record) : null;
|
|
629
|
-
}
|
|
630
|
-
async countForTenant(tenantId = currentTenantId2()) {
|
|
631
|
-
return await this.countWhere({ tenant_id: tenantId });
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
var repository_default2 = UserRepository;
|
|
635
|
-
|
|
636
|
-
// ../../src/bootstrap/providers/view.ts
|
|
637
535
|
var CORE_VIEW_TOKEN = "core.view";
|
|
638
536
|
var VIEW_DIRECTORY_CONFIG_KEY = "view.directory";
|
|
639
537
|
var viewProvider = {
|
|
@@ -647,23 +545,11 @@ var viewProvider = {
|
|
|
647
545
|
const engine = new EtaViewEngine(viewsDirectory, (request) => resolveWebLayoutData(container, request ?? currentRequestMeta().request));
|
|
648
546
|
container.set(CORE_VIEW_TOKEN, engine);
|
|
649
547
|
configureWebLayoutData({
|
|
650
|
-
extra: async (
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
try {
|
|
656
|
-
const record = await new repository_default2().findByIdOrThrow(user.id);
|
|
657
|
-
const memberships = await resolveMembershipLookup().listForUser(record.id);
|
|
658
|
-
const organizationsRepo = new repository_default;
|
|
659
|
-
const organizations = (await Promise.all(memberships.map((membership) => organizationsRepo.findById(membership.organization_id)))).filter((organization) => Boolean(organization && !organization.deleted_at));
|
|
660
|
-
const currentId = record.current_organization_id ?? null;
|
|
661
|
-
const currentOrganization = organizations.find((organization) => organization.id === currentId) ?? organizations[0] ?? null;
|
|
662
|
-
return { appName, currentOrganization, organizations };
|
|
663
|
-
} catch {
|
|
664
|
-
return { appName, currentOrganization: null, organizations: [] };
|
|
665
|
-
}
|
|
666
|
-
}
|
|
548
|
+
extra: async () => ({
|
|
549
|
+
appName: appDisplayName(),
|
|
550
|
+
currentOrganization: null,
|
|
551
|
+
organizations: []
|
|
552
|
+
})
|
|
667
553
|
});
|
|
668
554
|
configureWebErrorView({
|
|
669
555
|
render: async (input) => engine.render(errorTemplateName(input.status), {
|
|
@@ -16,7 +16,12 @@ function readDiscoverModulesState() {
|
|
|
16
16
|
return state;
|
|
17
17
|
}
|
|
18
18
|
function configureModulesDirectory(modulesDir) {
|
|
19
|
-
readDiscoverModulesState()
|
|
19
|
+
const state = readDiscoverModulesState();
|
|
20
|
+
if (state.configuredModulesDir !== modulesDir) {
|
|
21
|
+
state.appModules.length = 0;
|
|
22
|
+
state.modulesReady = undefined;
|
|
23
|
+
}
|
|
24
|
+
state.configuredModulesDir = modulesDir;
|
|
20
25
|
}
|
|
21
26
|
function resolveModulesDirectory(options) {
|
|
22
27
|
const state = readDiscoverModulesState();
|
|
@@ -24,7 +24,7 @@ import { createRequireGlobalAdminMiddleware } from "@getstrata/core/http/require
|
|
|
24
24
|
import { createRequirePasswordConfirmMiddleware } from "@getstrata/core/http/requirePasswordConfirmMiddleware";
|
|
25
25
|
import { createRequireVerifiedMiddleware } from "@getstrata/core/http/requireVerifiedMiddleware";
|
|
26
26
|
import { createRequireWebAuthMiddleware } from "@getstrata/core/http/requireWebAuthMiddleware";
|
|
27
|
-
import { withErrorHandling } from "@getstrata/core/http/response";
|
|
27
|
+
import { withErrorHandling, withJsonErrorHandling } from "@getstrata/core/http/response";
|
|
28
28
|
import { withMiddleware } from "@getstrata/core/http/routeMiddleware";
|
|
29
29
|
import { createSecurityHeadersMiddleware } from "@getstrata/core/http/securityHeadersMiddleware";
|
|
30
30
|
import { createValidateSignatureMiddleware } from "@getstrata/core/http/signedUrl";
|
|
@@ -172,7 +172,7 @@ class HttpKernel {
|
|
|
172
172
|
return withMiddleware(...middleware)(handler);
|
|
173
173
|
}
|
|
174
174
|
wrapApi(handler) {
|
|
175
|
-
return this.wrap(["api", "authenticated"], handler);
|
|
175
|
+
return withJsonErrorHandling(this.wrap(["api", "authenticated"], handler));
|
|
176
176
|
}
|
|
177
177
|
wrapWeb(handler) {
|
|
178
178
|
return withErrorHandling(this.wrap("web", handler));
|
|
@@ -34,7 +34,12 @@ function readDiscoverModulesState() {
|
|
|
34
34
|
return state;
|
|
35
35
|
}
|
|
36
36
|
function configureModulesDirectory(modulesDir) {
|
|
37
|
-
readDiscoverModulesState()
|
|
37
|
+
const state = readDiscoverModulesState();
|
|
38
|
+
if (state.configuredModulesDir !== modulesDir) {
|
|
39
|
+
state.appModules.length = 0;
|
|
40
|
+
state.modulesReady = undefined;
|
|
41
|
+
}
|
|
42
|
+
state.configuredModulesDir = modulesDir;
|
|
38
43
|
}
|
|
39
44
|
function resolveModulesDirectory(options) {
|
|
40
45
|
const state = readDiscoverModulesState();
|
|
@@ -2,7 +2,6 @@
|
|
|
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";
|
|
6
5
|
import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
|
|
7
6
|
import { appDisplayName } from "@getstrata/core/runtime/appKeyPrefix";
|
|
8
7
|
import { isViewsEnabled } from "@getstrata/core/runtime/frontendMode";
|
|
@@ -14,148 +13,6 @@ import {
|
|
|
14
13
|
errorTemplateName,
|
|
15
14
|
resolveWebLayoutData
|
|
16
15
|
} 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
|
|
159
16
|
var CORE_VIEW_TOKEN = "core.view";
|
|
160
17
|
var VIEW_DIRECTORY_CONFIG_KEY = "view.directory";
|
|
161
18
|
var viewProvider = {
|
|
@@ -169,23 +26,11 @@ var viewProvider = {
|
|
|
169
26
|
const engine = new EtaViewEngine(viewsDirectory, (request) => resolveWebLayoutData(container, request ?? currentRequestMeta().request));
|
|
170
27
|
container.set(CORE_VIEW_TOKEN, engine);
|
|
171
28
|
configureWebLayoutData({
|
|
172
|
-
extra: async (
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
}
|
|
29
|
+
extra: async () => ({
|
|
30
|
+
appName: appDisplayName(),
|
|
31
|
+
currentOrganization: null,
|
|
32
|
+
organizations: []
|
|
33
|
+
})
|
|
189
34
|
});
|
|
190
35
|
configureWebErrorView({
|
|
191
36
|
render: async (input) => engine.render(errorTemplateName(input.status), {
|