@getstrata/bootstrap 0.2.62 → 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 +4 -0
- package/README.md +1 -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 -0
- package/dist/entries/createRoutes.js +93 -8
- package/dist/entries/createWebRoutes.js +2 -1
- package/dist/entries/dependencies.js +164 -0
- package/dist/entries/httpKernel.js +2 -1
- package/dist/entries/providers/view.js +164 -0
- package/dist/entries/providers.js +164 -0
- package/dist/entries/web/routing.js +2 -1
- package/dist/index.js +166 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# @getstrata/bootstrap changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.63
|
|
4
|
+
|
|
5
|
+
- `HttpKernel.wrapWebGuest()` `home` accepts `string | ((user) => string | Promise<string>)` so signed-in guest redirects can follow the current team. Default remains `/organizations`.
|
|
6
|
+
|
|
3
7
|
## 0.2.62
|
|
4
8
|
|
|
5
9
|
- `HttpKernel.wrapWebPasswordConfirm()` is Laravel `password.confirm` for HTML routes. Peer `@getstrata/core` `^0.5.75`.
|
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ import { createHttpKernel, createAppContext, coreProviders } from "@getstrata/bo
|
|
|
32
32
|
|
|
33
33
|
Sibling HTMX apps should bind `createCookieSessionAuthManager` from `@getstrata/bootstrap/web/session` instead of HMAC `SessionGuard`. Use `signIn` / `signOut` (or the redirect helpers) instead of calling `CookieSessionStore` from controllers. Pass `mapUser` to map roles in the app. Pass `loadSessionUser` when the default `learn_subscriber` / `is_admin` SELECT does not match your schema. WorkHub’s table is `sessions` (`0031_create_sessions`); its loader is `loadWorkhubSessionUser` (maps `users.role`, decrypts email). WorkHub web login itself stays on HMAC `SessionGuard`.
|
|
34
34
|
|
|
35
|
-
`wrapWeb` applies the web group (CSRF + flash) and `withErrorHandling`, so CSRF `ForbiddenError` becomes an HTML 403 in `FRONTEND_MODE=server-htmx`. `wrapWebGuest` is Laravel `guest` / `RedirectIfAuthenticated` (signed-in users go to `/organizations` by default). `wrapWebLogin` / `wrapWebRegister` include that web group plus throttle — do not wrap them with `wrapWeb` again. The throttle callback should return an HTML form at 429 (WorkHub’s `/login` and `/register` do).
|
|
35
|
+
`wrapWeb` applies the web group (CSRF + flash) and `withErrorHandling`, so CSRF `ForbiddenError` becomes an HTML 403 in `FRONTEND_MODE=server-htmx`. `wrapWebGuest` is Laravel `guest` / `RedirectIfAuthenticated` (signed-in users go to `/organizations` by default; pass a string or `(user) => path` to override). `wrapWebLogin` / `wrapWebRegister` include that web group plus throttle — do not wrap them with `wrapWeb` again. The throttle callback should return an HTML form at 429 (WorkHub’s `/login` and `/register` do).
|
|
36
36
|
|
|
37
37
|
These subpaths remain WorkHub-oriented and are not a generic starter API: `@getstrata/bootstrap/createRoutes` (includes SCIM), `@getstrata/bootstrap/schedule`, and `@getstrata/bootstrap/createWebRoutes` (redirects `/` to `/organizations`).
|
|
38
38
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import type { AuthUser } from "@getstrata/core/auth/authContext";
|
|
1
2
|
import type { Policy } from "@getstrata/core/auth/policy";
|
|
2
3
|
import type { Middleware, RouteHandler } from "@getstrata/core/http/middleware";
|
|
3
4
|
import type { AppDependencies } from "./contracts";
|
|
4
5
|
type MiddlewareGroupName = "api" | "authenticated" | "web";
|
|
6
|
+
type WebGuestHome = string | ((user: AuthUser) => string | Promise<string>);
|
|
5
7
|
declare class HttpKernel {
|
|
6
8
|
private readonly dependencies;
|
|
7
9
|
constructor(dependencies: AppDependencies);
|
|
@@ -11,7 +13,7 @@ declare class HttpKernel {
|
|
|
11
13
|
wrapApi(handler: RouteHandler): RouteHandler;
|
|
12
14
|
wrapWeb(handler: RouteHandler): RouteHandler;
|
|
13
15
|
/** Laravel `guest` / `RedirectIfAuthenticated` — signed-in users go to `home`. */
|
|
14
|
-
wrapWebGuest(handler: RouteHandler, home?:
|
|
16
|
+
wrapWebGuest(handler: RouteHandler, home?: WebGuestHome): RouteHandler;
|
|
15
17
|
wrapWebPublicRead(handler: RouteHandler): RouteHandler;
|
|
16
18
|
wrapWebAuthenticated(handler: RouteHandler): RouteHandler;
|
|
17
19
|
/** Signed-in HTML without Laravel `verified` (logout, verification notice). */
|
|
@@ -36,5 +38,5 @@ declare class HttpKernel {
|
|
|
36
38
|
private wrapThrottle;
|
|
37
39
|
}
|
|
38
40
|
declare function createHttpKernel(dependencies: AppDependencies): HttpKernel;
|
|
39
|
-
export type { MiddlewareGroupName };
|
|
41
|
+
export type { MiddlewareGroupName, WebGuestHome };
|
|
40
42
|
export { createHttpKernel, HttpKernel };
|
|
@@ -189,7 +189,8 @@ class HttpKernel {
|
|
|
189
189
|
if (isEmailVerificationRequired() && !hasVerifiedEmail(user)) {
|
|
190
190
|
return Response.redirect("/email/verify", 302);
|
|
191
191
|
}
|
|
192
|
-
|
|
192
|
+
const location = typeof home === "function" ? await home(user) : home;
|
|
193
|
+
return Response.redirect(location, 302);
|
|
193
194
|
}
|
|
194
195
|
return handler(request);
|
|
195
196
|
});
|
|
@@ -192,7 +192,8 @@ class HttpKernel {
|
|
|
192
192
|
if (isEmailVerificationRequired() && !hasVerifiedEmail(user)) {
|
|
193
193
|
return Response.redirect("/email/verify", 302);
|
|
194
194
|
}
|
|
195
|
-
|
|
195
|
+
const location = typeof home === "function" ? await home(user) : home;
|
|
196
|
+
return Response.redirect(location, 302);
|
|
196
197
|
}
|
|
197
198
|
return handler(request);
|
|
198
199
|
});
|
package/dist/entries/context.js
CHANGED
|
@@ -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,
|
|
@@ -229,7 +229,8 @@ class HttpKernel {
|
|
|
229
229
|
if (isEmailVerificationRequired() && !hasVerifiedEmail(user)) {
|
|
230
230
|
return Response.redirect("/email/verify", 302);
|
|
231
231
|
}
|
|
232
|
-
|
|
232
|
+
const location = typeof home === "function" ? await home(user) : home;
|
|
233
|
+
return Response.redirect(location, 302);
|
|
233
234
|
}
|
|
234
235
|
return handler(request);
|
|
235
236
|
});
|
|
@@ -797,7 +798,7 @@ function assertScimIfMatch(request, etagSource) {
|
|
|
797
798
|
// ../../src/modules/scim/service.ts
|
|
798
799
|
import { hashPassword as hashPassword3 } from "@getstrata/core/auth/password";
|
|
799
800
|
import { resolveService } from "@getstrata/core/contracts/di";
|
|
800
|
-
import { NotFoundError as
|
|
801
|
+
import { NotFoundError as NotFoundError7 } from "@getstrata/core/errors/http";
|
|
801
802
|
import { currentTenantId as currentTenantId4 } from "@getstrata/core/tenant/tenantContext";
|
|
802
803
|
|
|
803
804
|
// ../../src/domain/scim.ts
|
|
@@ -1050,6 +1051,7 @@ import {
|
|
|
1050
1051
|
import { OidcProvider } from "@getstrata/core/auth/oauth/oidcProvider";
|
|
1051
1052
|
import { GitHubOAuthProvider, MockOAuthProvider } from "@getstrata/core/auth/oauth/providers";
|
|
1052
1053
|
import { SamlProvider } from "@getstrata/core/auth/oauth/samlProvider";
|
|
1054
|
+
import { getRequiredDependency } from "@getstrata/core/contracts/di";
|
|
1053
1055
|
|
|
1054
1056
|
// ../../src/modules/user/apiTokenRepository.ts
|
|
1055
1057
|
import { BaseRepository as BaseRepository2 } from "@getstrata/core/database/baseRepository";
|
|
@@ -1073,9 +1075,16 @@ var apiTokenTable = defineTable2({
|
|
|
1073
1075
|
});
|
|
1074
1076
|
|
|
1075
1077
|
// ../../src/modules/user/authService.ts
|
|
1078
|
+
import { currentAuthUser } from "@getstrata/core/auth/authContext";
|
|
1076
1079
|
import { hashPassword, verifyPassword } from "@getstrata/core/auth/password";
|
|
1080
|
+
import { normalizeEmail } from "@getstrata/core/crypto/fieldEncryption";
|
|
1077
1081
|
import { protectMfaSecret, revealMfaSecret } from "@getstrata/core/crypto/mfaSecret";
|
|
1078
|
-
import { UnauthorizedError, ValidationError } from "@getstrata/core/errors/http";
|
|
1082
|
+
import { UnauthorizedError as UnauthorizedError2, ValidationError } from "@getstrata/core/errors/http";
|
|
1083
|
+
import {
|
|
1084
|
+
generateRecoveryCodes,
|
|
1085
|
+
hashRecoveryCode,
|
|
1086
|
+
recoveryCodeMatches
|
|
1087
|
+
} from "@getstrata/core/security/recoveryCodes";
|
|
1079
1088
|
import { logSecurityEvent } from "@getstrata/core/security/securityEvents";
|
|
1080
1089
|
import { resolveDefaultTokenExpiryDays } from "@getstrata/core/security/tokenExpiry";
|
|
1081
1090
|
import { buildOtpauthUrl, generateTotpSecret, verifyTotp } from "@getstrata/core/security/totp";
|
|
@@ -1084,6 +1093,7 @@ import { currentTenantId as currentTenantId2 } from "@getstrata/core/tenant/tena
|
|
|
1084
1093
|
// ../../src/core/auth/abilityCatalog.ts
|
|
1085
1094
|
var MEMBER_ABILITIES = [
|
|
1086
1095
|
"organizations:read",
|
|
1096
|
+
"organizations:create",
|
|
1087
1097
|
"projects:read",
|
|
1088
1098
|
"projects:create",
|
|
1089
1099
|
"tasks:read",
|
|
@@ -1093,7 +1103,8 @@ var MEMBER_ABILITIES = [
|
|
|
1093
1103
|
"attachments:read",
|
|
1094
1104
|
"attachments:create",
|
|
1095
1105
|
"auth:tokens:read",
|
|
1096
|
-
"auth:tokens:write"
|
|
1106
|
+
"auth:tokens:write",
|
|
1107
|
+
"auth:tokens:delete"
|
|
1097
1108
|
];
|
|
1098
1109
|
var ADMIN_ABILITIES = [
|
|
1099
1110
|
...MEMBER_ABILITIES,
|
|
@@ -1111,6 +1122,14 @@ var ADMIN_ABILITIES = [
|
|
|
1111
1122
|
"webhooks:write",
|
|
1112
1123
|
"audit:read"
|
|
1113
1124
|
];
|
|
1125
|
+
// ../../src/modules/user/mfaRequiredError.ts
|
|
1126
|
+
import { UnauthorizedError } from "@getstrata/core/errors/http";
|
|
1127
|
+
|
|
1128
|
+
// ../../src/modules/user/currentOrganizationService.ts
|
|
1129
|
+
import { assertResourceInCurrentTenant } from "@getstrata/core/auth/membershipScope";
|
|
1130
|
+
import { resolveMembershipService } from "@getstrata/core/auth/membershipService";
|
|
1131
|
+
import { NotFoundError as NotFoundError3 } from "@getstrata/core/errors/http";
|
|
1132
|
+
|
|
1114
1133
|
// ../../src/modules/user/notificationRepository.ts
|
|
1115
1134
|
import { BaseRepository as BaseRepository3 } from "@getstrata/core/database/baseRepository";
|
|
1116
1135
|
|
|
@@ -1124,7 +1143,7 @@ var notificationTable = defineTable3({
|
|
|
1124
1143
|
});
|
|
1125
1144
|
|
|
1126
1145
|
// ../../src/modules/user/notificationService.ts
|
|
1127
|
-
import { NotFoundError as
|
|
1146
|
+
import { NotFoundError as NotFoundError4 } from "@getstrata/core/errors/http";
|
|
1128
1147
|
|
|
1129
1148
|
// ../../src/modules/user/oauthIdentityRepository.ts
|
|
1130
1149
|
import { BaseRepository as BaseRepository4 } from "@getstrata/core/database/baseRepository";
|
|
@@ -1143,11 +1162,16 @@ import { ValidationError as ValidationError2 } from "@getstrata/core/errors/http
|
|
|
1143
1162
|
import { absoluteTemporarySignedUrl } from "@getstrata/core/http/signedUrl";
|
|
1144
1163
|
import { mailer } from "@getstrata/core/mail/mailer";
|
|
1145
1164
|
import { sendMarkdownMail } from "@getstrata/core/mail/markdownMailable";
|
|
1165
|
+
import { appDisplayName } from "@getstrata/core/runtime/appKeyPrefix";
|
|
1146
1166
|
import { logSecurityEvent as logSecurityEvent2 } from "@getstrata/core/security/securityEvents";
|
|
1147
1167
|
import { timingSafeCompareString } from "@getstrata/core/security/timingSafeCompare";
|
|
1148
1168
|
var RESET_TTL_SECONDS = 60 * 60;
|
|
1149
1169
|
var VERIFY_TTL_SECONDS = 60 * 60 * 24;
|
|
1150
1170
|
|
|
1171
|
+
// ../../src/modules/user/profilePhotoService.ts
|
|
1172
|
+
import { BadRequestError, NotFoundError as NotFoundError5 } from "@getstrata/core/errors/http";
|
|
1173
|
+
import { isImageMimeType, resizeImageContents } from "@getstrata/core/media/imageTransform";
|
|
1174
|
+
|
|
1151
1175
|
// ../../src/modules/user/repository.ts
|
|
1152
1176
|
import {
|
|
1153
1177
|
emailLookupForQuery,
|
|
@@ -1174,15 +1198,76 @@ var userTable = defineTable5({
|
|
|
1174
1198
|
"email_verified_at",
|
|
1175
1199
|
"mfa_secret",
|
|
1176
1200
|
"mfa_enabled",
|
|
1201
|
+
"mfa_recovery_codes",
|
|
1202
|
+
"profile_photo_path",
|
|
1203
|
+
"session_valid_after",
|
|
1204
|
+
"current_organization_id",
|
|
1177
1205
|
"created_at",
|
|
1178
1206
|
"updated_at"
|
|
1179
1207
|
],
|
|
1180
1208
|
defaultOrderBy: { column: "id", direction: "ASC" }
|
|
1181
1209
|
});
|
|
1182
1210
|
|
|
1211
|
+
// ../../src/modules/user/repository.ts
|
|
1212
|
+
class UserRepository extends BaseRepository5 {
|
|
1213
|
+
constructor() {
|
|
1214
|
+
super(userTable);
|
|
1215
|
+
}
|
|
1216
|
+
decode(record) {
|
|
1217
|
+
return {
|
|
1218
|
+
...record,
|
|
1219
|
+
email: revealEmail(record.email),
|
|
1220
|
+
mfa_secret: revealMfaSecret2(record.mfa_secret)
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
async findById(id) {
|
|
1224
|
+
const record = await super.findById(id);
|
|
1225
|
+
return record ? this.decode(record) : null;
|
|
1226
|
+
}
|
|
1227
|
+
async findAll(options = {}) {
|
|
1228
|
+
const records = await super.findAll(options);
|
|
1229
|
+
return records.map((record) => this.decode(record));
|
|
1230
|
+
}
|
|
1231
|
+
async create(values) {
|
|
1232
|
+
const email = values.email;
|
|
1233
|
+
if (!email) {
|
|
1234
|
+
throw new Error("Email is required.");
|
|
1235
|
+
}
|
|
1236
|
+
const protectedEmail = protectEmail(email);
|
|
1237
|
+
const record = await super.create({
|
|
1238
|
+
...values,
|
|
1239
|
+
tenant_id: values.tenant_id ?? currentTenantId3(),
|
|
1240
|
+
email: protectedEmail.storedEmail,
|
|
1241
|
+
email_lookup: protectedEmail.emailLookup,
|
|
1242
|
+
password_hash: values.password_hash ?? ""
|
|
1243
|
+
});
|
|
1244
|
+
return this.decode(record);
|
|
1245
|
+
}
|
|
1246
|
+
async updateByIdOrThrow(id, values, errorFactory) {
|
|
1247
|
+
const changes = { ...values };
|
|
1248
|
+
if (values.email !== undefined) {
|
|
1249
|
+
const protectedEmail = protectEmail(values.email);
|
|
1250
|
+
changes.email = protectedEmail.storedEmail;
|
|
1251
|
+
changes.email_lookup = protectedEmail.emailLookup;
|
|
1252
|
+
}
|
|
1253
|
+
const record = await super.updateByIdOrThrow(id, changes, errorFactory);
|
|
1254
|
+
return this.decode(record);
|
|
1255
|
+
}
|
|
1256
|
+
async findByEmail(email) {
|
|
1257
|
+
const records = await this.findWhere({ email_lookup: emailLookupForQuery(email) }, { limit: 1 });
|
|
1258
|
+
const record = records[0];
|
|
1259
|
+
return record ? this.decode(record) : null;
|
|
1260
|
+
}
|
|
1261
|
+
async countForTenant(tenantId = currentTenantId3()) {
|
|
1262
|
+
return await this.countWhere({ tenant_id: tenantId });
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
var repository_default2 = UserRepository;
|
|
1266
|
+
|
|
1183
1267
|
// ../../src/modules/user/tokenService.ts
|
|
1268
|
+
import { ADMIN_ABILITIES as ADMIN_ABILITIES2 } from "@getstrata/core/auth/abilityCatalog";
|
|
1184
1269
|
import { hashApiToken as hashApiToken2 } from "@getstrata/core/auth/tokenHash";
|
|
1185
|
-
import { ForbiddenError, NotFoundError as
|
|
1270
|
+
import { ForbiddenError, NotFoundError as NotFoundError6, ValidationError as ValidationError3 } from "@getstrata/core/errors/http";
|
|
1186
1271
|
import { resolveDefaultTokenExpiryDays as resolveDefaultTokenExpiryDays2 } from "@getstrata/core/security/tokenExpiry";
|
|
1187
1272
|
|
|
1188
1273
|
// ../../src/modules/user/provider.ts
|
|
@@ -1240,7 +1325,7 @@ class ScimService {
|
|
|
1240
1325
|
async findUserRecord(id) {
|
|
1241
1326
|
const user = await this.users.findById(id);
|
|
1242
1327
|
if (!user || user.tenant_id !== currentTenantId4()) {
|
|
1243
|
-
throw new
|
|
1328
|
+
throw new NotFoundError7(`SCIM user ${id} not found.`);
|
|
1244
1329
|
}
|
|
1245
1330
|
return user;
|
|
1246
1331
|
}
|
|
@@ -1282,7 +1367,7 @@ class ScimService {
|
|
|
1282
1367
|
const user = await this.findUserRecord(id);
|
|
1283
1368
|
const deleted = await this.users.deleteById(id);
|
|
1284
1369
|
if (!deleted) {
|
|
1285
|
-
throw new
|
|
1370
|
+
throw new NotFoundError7(`SCIM user ${id} not found.`);
|
|
1286
1371
|
}
|
|
1287
1372
|
return { id: user.id, updated_at: user.updated_at };
|
|
1288
1373
|
}
|
|
@@ -197,7 +197,8 @@ class HttpKernel {
|
|
|
197
197
|
if (isEmailVerificationRequired() && !hasVerifiedEmail(user)) {
|
|
198
198
|
return Response.redirect("/email/verify", 302);
|
|
199
199
|
}
|
|
200
|
-
|
|
200
|
+
const location = typeof home === "function" ? await home(user) : home;
|
|
201
|
+
return Response.redirect(location, 302);
|
|
201
202
|
}
|
|
202
203
|
return handler(request);
|
|
203
204
|
});
|
|
@@ -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,
|
|
@@ -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,
|
|
@@ -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,
|
|
@@ -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
|
});
|
package/dist/index.js
CHANGED
|
@@ -201,7 +201,8 @@ class HttpKernel {
|
|
|
201
201
|
if (isEmailVerificationRequired() && !hasVerifiedEmail(user)) {
|
|
202
202
|
return Response.redirect("/email/verify", 302);
|
|
203
203
|
}
|
|
204
|
-
|
|
204
|
+
const location = typeof home === "function" ? await home(user) : home;
|
|
205
|
+
return Response.redirect(location, 302);
|
|
205
206
|
}
|
|
206
207
|
return handler(request);
|
|
207
208
|
});
|
|
@@ -865,15 +866,160 @@ var storageProvider = {
|
|
|
865
866
|
var storage_default = storageProvider;
|
|
866
867
|
|
|
867
868
|
// ../../src/bootstrap/providers/view.ts
|
|
869
|
+
import { resolveMembershipLookup } from "@getstrata/core/auth/membershipContext";
|
|
868
870
|
import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
|
|
871
|
+
import { appDisplayName } from "@getstrata/core/runtime/appKeyPrefix";
|
|
869
872
|
import { isViewsEnabled as isViewsEnabled2 } from "@getstrata/core/runtime/frontendMode";
|
|
870
873
|
import {
|
|
871
874
|
configureWebErrorView,
|
|
875
|
+
configureWebLayoutData,
|
|
872
876
|
DEFAULT_VIEWS_DIRECTORY,
|
|
873
877
|
EtaViewEngine,
|
|
874
878
|
errorTemplateName,
|
|
875
879
|
resolveWebLayoutData
|
|
876
880
|
} from "@getstrata/core/view";
|
|
881
|
+
|
|
882
|
+
// ../../src/modules/organization/repository.ts
|
|
883
|
+
import { BaseRepository } from "@getstrata/core/database/baseRepository";
|
|
884
|
+
import { NotFoundError } from "@getstrata/core/errors/http";
|
|
885
|
+
import { currentTenantId } from "@getstrata/core/tenant/tenantContext";
|
|
886
|
+
|
|
887
|
+
// ../../src/modules/organization/table.ts
|
|
888
|
+
import { defineTable } from "@getstrata/core/database/table";
|
|
889
|
+
|
|
890
|
+
// ../../src/domain/workhub.ts
|
|
891
|
+
var ORGANIZATION_TABLE = "organization";
|
|
892
|
+
|
|
893
|
+
// ../../src/modules/organization/table.ts
|
|
894
|
+
var organizationTable = defineTable({
|
|
895
|
+
name: ORGANIZATION_TABLE,
|
|
896
|
+
primaryKey: "id",
|
|
897
|
+
columns: ["id", "tenant_id", "name", "slug", "created_at", "updated_at", "deleted_at"],
|
|
898
|
+
softDeletes: true,
|
|
899
|
+
defaultOrderBy: { column: "id", direction: "ASC" }
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
// ../../src/modules/organization/repository.ts
|
|
903
|
+
class OrganizationRepository extends BaseRepository {
|
|
904
|
+
constructor() {
|
|
905
|
+
super(organizationTable);
|
|
906
|
+
}
|
|
907
|
+
async findBySlug(slug) {
|
|
908
|
+
return await this.firstOrNull({ slug });
|
|
909
|
+
}
|
|
910
|
+
async listForTenant(options) {
|
|
911
|
+
return await this.findAll({
|
|
912
|
+
limit: options.limit,
|
|
913
|
+
offset: options.offset,
|
|
914
|
+
where: { tenant_id: options.tenantId ?? currentTenantId() }
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
async countForTenant(tenantId = currentTenantId()) {
|
|
918
|
+
return await this.countWhere({ tenant_id: tenantId });
|
|
919
|
+
}
|
|
920
|
+
async findForTenantOrThrow(id, tenantId = currentTenantId()) {
|
|
921
|
+
const organization = await this.findById(id);
|
|
922
|
+
if (!organization || organization.tenant_id !== tenantId) {
|
|
923
|
+
throw new NotFoundError(`SCIM group ${id} not found.`);
|
|
924
|
+
}
|
|
925
|
+
return organization;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
var repository_default = OrganizationRepository;
|
|
929
|
+
|
|
930
|
+
// ../../src/modules/user/repository.ts
|
|
931
|
+
import {
|
|
932
|
+
emailLookupForQuery,
|
|
933
|
+
protectEmail,
|
|
934
|
+
revealEmail
|
|
935
|
+
} from "@getstrata/core/crypto/fieldEncryption";
|
|
936
|
+
import { revealMfaSecret } from "@getstrata/core/crypto/mfaSecret";
|
|
937
|
+
import { BaseRepository as BaseRepository2 } from "@getstrata/core/database/baseRepository";
|
|
938
|
+
import { currentTenantId as currentTenantId2 } from "@getstrata/core/tenant/tenantContext";
|
|
939
|
+
|
|
940
|
+
// ../../src/modules/user/table.ts
|
|
941
|
+
import { defineTable as defineTable2 } from "@getstrata/core/database/table";
|
|
942
|
+
var userTable = defineTable2({
|
|
943
|
+
name: "users",
|
|
944
|
+
primaryKey: "id",
|
|
945
|
+
columns: [
|
|
946
|
+
"id",
|
|
947
|
+
"name",
|
|
948
|
+
"email",
|
|
949
|
+
"email_lookup",
|
|
950
|
+
"role",
|
|
951
|
+
"tenant_id",
|
|
952
|
+
"password_hash",
|
|
953
|
+
"email_verified_at",
|
|
954
|
+
"mfa_secret",
|
|
955
|
+
"mfa_enabled",
|
|
956
|
+
"mfa_recovery_codes",
|
|
957
|
+
"profile_photo_path",
|
|
958
|
+
"session_valid_after",
|
|
959
|
+
"current_organization_id",
|
|
960
|
+
"created_at",
|
|
961
|
+
"updated_at"
|
|
962
|
+
],
|
|
963
|
+
defaultOrderBy: { column: "id", direction: "ASC" }
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
// ../../src/modules/user/repository.ts
|
|
967
|
+
class UserRepository extends BaseRepository2 {
|
|
968
|
+
constructor() {
|
|
969
|
+
super(userTable);
|
|
970
|
+
}
|
|
971
|
+
decode(record) {
|
|
972
|
+
return {
|
|
973
|
+
...record,
|
|
974
|
+
email: revealEmail(record.email),
|
|
975
|
+
mfa_secret: revealMfaSecret(record.mfa_secret)
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
async findById(id) {
|
|
979
|
+
const record = await super.findById(id);
|
|
980
|
+
return record ? this.decode(record) : null;
|
|
981
|
+
}
|
|
982
|
+
async findAll(options = {}) {
|
|
983
|
+
const records = await super.findAll(options);
|
|
984
|
+
return records.map((record) => this.decode(record));
|
|
985
|
+
}
|
|
986
|
+
async create(values) {
|
|
987
|
+
const email = values.email;
|
|
988
|
+
if (!email) {
|
|
989
|
+
throw new Error("Email is required.");
|
|
990
|
+
}
|
|
991
|
+
const protectedEmail = protectEmail(email);
|
|
992
|
+
const record = await super.create({
|
|
993
|
+
...values,
|
|
994
|
+
tenant_id: values.tenant_id ?? currentTenantId2(),
|
|
995
|
+
email: protectedEmail.storedEmail,
|
|
996
|
+
email_lookup: protectedEmail.emailLookup,
|
|
997
|
+
password_hash: values.password_hash ?? ""
|
|
998
|
+
});
|
|
999
|
+
return this.decode(record);
|
|
1000
|
+
}
|
|
1001
|
+
async updateByIdOrThrow(id, values, errorFactory) {
|
|
1002
|
+
const changes = { ...values };
|
|
1003
|
+
if (values.email !== undefined) {
|
|
1004
|
+
const protectedEmail = protectEmail(values.email);
|
|
1005
|
+
changes.email = protectedEmail.storedEmail;
|
|
1006
|
+
changes.email_lookup = protectedEmail.emailLookup;
|
|
1007
|
+
}
|
|
1008
|
+
const record = await super.updateByIdOrThrow(id, changes, errorFactory);
|
|
1009
|
+
return this.decode(record);
|
|
1010
|
+
}
|
|
1011
|
+
async findByEmail(email) {
|
|
1012
|
+
const records = await this.findWhere({ email_lookup: emailLookupForQuery(email) }, { limit: 1 });
|
|
1013
|
+
const record = records[0];
|
|
1014
|
+
return record ? this.decode(record) : null;
|
|
1015
|
+
}
|
|
1016
|
+
async countForTenant(tenantId = currentTenantId2()) {
|
|
1017
|
+
return await this.countWhere({ tenant_id: tenantId });
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
var repository_default2 = UserRepository;
|
|
1021
|
+
|
|
1022
|
+
// ../../src/bootstrap/providers/view.ts
|
|
877
1023
|
var CORE_VIEW_TOKEN = "core.view";
|
|
878
1024
|
var VIEW_DIRECTORY_CONFIG_KEY = "view.directory";
|
|
879
1025
|
var viewProvider = {
|
|
@@ -886,6 +1032,25 @@ var viewProvider = {
|
|
|
886
1032
|
config.set(VIEW_DIRECTORY_CONFIG_KEY, viewsDirectory);
|
|
887
1033
|
const engine = new EtaViewEngine(viewsDirectory, (request) => resolveWebLayoutData(container, request ?? currentRequestMeta().request));
|
|
888
1034
|
container.set(CORE_VIEW_TOKEN, engine);
|
|
1035
|
+
configureWebLayoutData({
|
|
1036
|
+
extra: async (user) => {
|
|
1037
|
+
const appName = appDisplayName();
|
|
1038
|
+
if (!user || typeof user.id !== "number") {
|
|
1039
|
+
return { appName, currentOrganization: null, organizations: [] };
|
|
1040
|
+
}
|
|
1041
|
+
try {
|
|
1042
|
+
const record = await new repository_default2().findByIdOrThrow(user.id);
|
|
1043
|
+
const memberships = await resolveMembershipLookup().listForUser(record.id);
|
|
1044
|
+
const organizationsRepo = new repository_default;
|
|
1045
|
+
const organizations = (await Promise.all(memberships.map((membership) => organizationsRepo.findById(membership.organization_id)))).filter((organization) => Boolean(organization && !organization.deleted_at));
|
|
1046
|
+
const currentId = record.current_organization_id ?? null;
|
|
1047
|
+
const currentOrganization = organizations.find((organization) => organization.id === currentId) ?? organizations[0] ?? null;
|
|
1048
|
+
return { appName, currentOrganization, organizations };
|
|
1049
|
+
} catch {
|
|
1050
|
+
return { appName, currentOrganization: null, organizations: [] };
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
});
|
|
889
1054
|
configureWebErrorView({
|
|
890
1055
|
render: async (input) => engine.render(errorTemplateName(input.status), {
|
|
891
1056
|
title: input.title,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getstrata/bootstrap",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.63",
|
|
4
4
|
"description": "Strata application bootstrap — HttpKernel, DI, web session helpers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -173,7 +173,7 @@
|
|
|
173
173
|
"build:bundle": "bun build index.ts --outdir dist --target bun --external bun --external eta --external @getstrata/core",
|
|
174
174
|
"build:types": "tsc -p tsconfig.types.json && bun ../../scripts/prune-bootstrap-dist-types.ts",
|
|
175
175
|
"prepublishOnly": "bun run build",
|
|
176
|
-
"build:subpaths": "bun build entries/applicationRegistry.ts entries/buildModuleRoutes.ts entries/buildWebModuleRoutes.ts entries/cache/modelCacheTags.ts entries/config.ts entries/context.ts entries/contracts.ts entries/createWebRoutes.ts entries/createRoutes.ts entries/createSpaRoutes.ts entries/dependencies.ts entries/discoverModules.ts entries/health.ts entries/http/securedRouteModelBinding.ts entries/httpKernel.ts entries/listeners/invalidateCacheOnModelWrite.ts entries/membershipService.ts entries/metricsRoutes.ts entries/queue/defaultJobs.ts entries/providers.ts entries/providers/view.ts entries/routeRegistry.ts entries/schedule.ts entries/secretsGuard.ts entries/web/forms.ts entries/web/routing.ts entries/web/server.ts entries/web/session.ts entries/web/slug.ts --outdir dist --root . --target bun --external bun --external eta --external @getstrata/core --external @getstrata/bootstrap --external @getstrata/bootstrap/applicationRegistry --external @getstrata/bootstrap/buildModuleRoutes --external @getstrata/bootstrap/buildWebModuleRoutes --external @getstrata/bootstrap/cache/modelCacheTags --external @getstrata/bootstrap/config --external @getstrata/bootstrap/context --external @getstrata/bootstrap/contracts --external @getstrata/bootstrap/createWebRoutes --external @getstrata/bootstrap/createRoutes --external @getstrata/bootstrap/createSpaRoutes --external @getstrata/bootstrap/dependencies --external @getstrata/bootstrap/discoverModules --external @getstrata/bootstrap/health --external @getstrata/bootstrap/http/securedRouteModelBinding --external @getstrata/bootstrap/httpKernel --external @getstrata/bootstrap/listeners/invalidateCacheOnModelWrite --external @getstrata/bootstrap/membershipService --external @getstrata/bootstrap/metricsRoutes --external @getstrata/bootstrap/queue/defaultJobs --external @getstrata/bootstrap/providers --external @getstrata/bootstrap/providers/view --external @getstrata/bootstrap/routeRegistry --external @getstrata/bootstrap/schedule --external @getstrata/bootstrap/secretsGuard --external @getstrata/bootstrap/web/forms --external @getstrata/bootstrap/web/routing --external @getstrata/bootstrap/web/server --external @getstrata/bootstrap/web/session --external @getstrata/bootstrap/web/slug --external @getstrata/core/auth/accessControl --external @getstrata/core/auth/abilityChecker --external @getstrata/core/auth/authContext --external @getstrata/core/auth/emailVerification --external @getstrata/core/auth/guard --external @getstrata/core/auth/membershipContext --external @getstrata/core/auth/membershipMiddleware --external @getstrata/core/auth/membershipScope --external @getstrata/core/auth/membershipService --external @getstrata/core/auth/oauth/oidcProvider --external @getstrata/core/auth/oauth/providers --external @getstrata/core/auth/oauth/samlProvider --external @getstrata/core/auth/oauth/types --external @getstrata/core/auth/password --external @getstrata/core/auth/passwordConfirmCookie --external @getstrata/core/auth/policy --external @getstrata/core/auth/scimAuthMiddleware --external @getstrata/core/auth/sessionCookie --external @getstrata/core/auth/sessionGuard --external @getstrata/core/auth/tokenHash --external @getstrata/core/audit/exportAuditLogs --external @getstrata/core/audit/siemFormatter --external @getstrata/core/admin/formatValue --external @getstrata/core/admin/registry --external @getstrata/core/admin/types --external @getstrata/core/cache/tags --external @getstrata/core/cache/createCacheStore --external @getstrata/core/cache/repository --external @getstrata/core/cache/simpleCache --external @getstrata/core/cache/simpleCacheStore --external @getstrata/core/config/envSchema --external @getstrata/core/contracts/serviceTokens --external @getstrata/core/contracts/container --external @getstrata/core/contracts/di --external @getstrata/core/crypto/fieldEncryption --external @getstrata/core/crypto/mfaSecret --external @getstrata/core/database --external @getstrata/core/database/baseRepository --external @getstrata/core/database/bindConnection --external @getstrata/core/database/boundConnection --external @getstrata/core/database/bunSql --external @getstrata/core/database/connection --external @getstrata/core/database/defaultConnection --external @getstrata/core/database/errors --external @getstrata/core/database/factory --external @getstrata/core/database/migrations --external @getstrata/core/database/migrations/types --external @getstrata/core/database/model --external @getstrata/core/database/query --external @getstrata/core/database/relationships --external @getstrata/core/database/repositoryConnection --external @getstrata/core/database/repositoryQuery --external @getstrata/core/database/seeders --external @getstrata/core/database/seeders/types --external @getstrata/core/database/schema --external @getstrata/core/database/table --external @getstrata/core/database/transaction --external @getstrata/core/database/types --external @getstrata/core/database/whereBuilder --external @getstrata/core/errors/http --external @getstrata/core/events --external @getstrata/core/facades --external @getstrata/core/http --external @getstrata/core/http/authMiddleware --external @getstrata/core/http/authorizeMiddleware --external @getstrata/core/http/bodySizeLimitMiddleware --external @getstrata/core/http/clientIp --external @getstrata/core/http/cookies --external @getstrata/core/http/contentNegotiation --external @getstrata/core/http/contentSecurityPolicy --external @getstrata/core/http/conditionalResponse --external @getstrata/core/http/corsMiddleware --external @getstrata/core/http/csrfMiddleware --external @getstrata/core/http/csrfProtection --external @getstrata/core/http/csrfToken --external @getstrata/core/http/etag --external @getstrata/core/http/flashSession --external @getstrata/core/http/flashMiddleware --external @getstrata/core/http/formRequest --external @getstrata/core/http/middleware --external @getstrata/core/http/metricsMiddleware --external @getstrata/core/http/loginThrottleMiddleware --external @getstrata/core/http/memoryThrottleMiddleware --external @getstrata/core/http/pagination --external @getstrata/core/http/parseFormBody --external @getstrata/core/http/parseMultipartUpload --external @getstrata/core/http/requireAbilityMiddleware --external @getstrata/core/http/requireAuthMiddleware --external @getstrata/core/http/requireGlobalAdminMiddleware --external @getstrata/core/http/requireWebAuthMiddleware --external @getstrata/core/http/requireVerifiedMiddleware --external @getstrata/core/http/requirePasswordConfirmMiddleware --external @getstrata/core/http/resources --external @getstrata/core/http/response --external @getstrata/core/http/route --external @getstrata/core/http/routeMiddleware --external @getstrata/core/http/routeModelBinding --external @getstrata/core/http/safeInternalPath --external @getstrata/core/http/signedUrl --external @getstrata/core/http/scimThrottleMiddleware --external @getstrata/core/http/securityHeadersMiddleware --external @getstrata/core/http/securedRouteModelBinding --external @getstrata/core/http/requestMetaContext --external @getstrata/core/http/webErrorResponse --external @getstrata/core/http/webFormRequest --external @getstrata/core/http/throttleMiddleware --external @getstrata/core/http/validation --external @getstrata/core/jobs/dispatchWebhookJob --external @getstrata/core/jobs/exportAuditLogsJob --external @getstrata/core/jobs/invalidateCacheTagsJob --external @getstrata/core/lifecycle/gracefulShutdown --external @getstrata/core/logging/logger --external @getstrata/core/logging/requestLoggingMiddleware --external @getstrata/core/mail/mailer --external @getstrata/core/mail/markdownMail --external @getstrata/core/mail/markdownMailable --external @getstrata/core/mail/sanitizeMailHtml --external @getstrata/core/media/imageTransform --external @getstrata/core/metrics/prometheus --external @getstrata/core/notifications --external @getstrata/core/openapi/generator --external @getstrata/core/openapi/registeredRoute --external @getstrata/core/openapi/validate --external @getstrata/core/pagination --external @getstrata/core/queue --external @getstrata/core/queue/createAppQueue --external @getstrata/core/queue/failedJobRepository --external @getstrata/core/queue/failedJobService --external @getstrata/core/queue/jobRegistry --external @getstrata/core/queue/jobRunner --external @getstrata/core/queue/queueMetrics --external @getstrata/core/queue/publicQueue --external @getstrata/core/queue/redisQueue --external @getstrata/core/queue/types --external @getstrata/core/runtime/appKeyPrefix --external @getstrata/core/runtime/frontendMode --external @getstrata/core/runtime/applicationRegistry --external @getstrata/core/runtime/asyncContextStore --external @getstrata/core/scheduler/schedule --external @getstrata/core/scheduler/osCron --external @getstrata/core/security/oauthState --external @getstrata/core/security/publicReads --external @getstrata/core/security/safeFetch --external @getstrata/core/security/safeUrl --external @getstrata/core/security/scimTenantTokens --external @getstrata/core/security/securityEvents --external @getstrata/core/security/stripeWebhook --external @getstrata/core/security/timingSafeCompare --external @getstrata/core/security/tokenExpiry --external @getstrata/core/security/totp --external @getstrata/core/storage/storage --external @getstrata/core/tenant/tenancyConfig --external @getstrata/core/tenant/tenantContext --external @getstrata/core/tenant/tenantDatabaseScope --external @getstrata/core/tenant/databaseTenantContext --external @getstrata/core/tenant/tenantMiddleware --external @getstrata/core/terminal/runShell --external @getstrata/core/tracing/traceContext --external @getstrata/core/tracing/tracingMiddleware --external @getstrata/core/validation/rules --external @getstrata/core/view",
|
|
176
|
+
"build:subpaths": "bun build entries/applicationRegistry.ts entries/buildModuleRoutes.ts entries/buildWebModuleRoutes.ts entries/cache/modelCacheTags.ts entries/config.ts entries/context.ts entries/contracts.ts entries/createWebRoutes.ts entries/createRoutes.ts entries/createSpaRoutes.ts entries/dependencies.ts entries/discoverModules.ts entries/health.ts entries/http/securedRouteModelBinding.ts entries/httpKernel.ts entries/listeners/invalidateCacheOnModelWrite.ts entries/membershipService.ts entries/metricsRoutes.ts entries/queue/defaultJobs.ts entries/providers.ts entries/providers/view.ts entries/routeRegistry.ts entries/schedule.ts entries/secretsGuard.ts entries/web/forms.ts entries/web/routing.ts entries/web/server.ts entries/web/session.ts entries/web/slug.ts --outdir dist --root . --target bun --external bun --external eta --external @getstrata/core --external @getstrata/bootstrap --external @getstrata/bootstrap/applicationRegistry --external @getstrata/bootstrap/buildModuleRoutes --external @getstrata/bootstrap/buildWebModuleRoutes --external @getstrata/bootstrap/cache/modelCacheTags --external @getstrata/bootstrap/config --external @getstrata/bootstrap/context --external @getstrata/bootstrap/contracts --external @getstrata/bootstrap/createWebRoutes --external @getstrata/bootstrap/createRoutes --external @getstrata/bootstrap/createSpaRoutes --external @getstrata/bootstrap/dependencies --external @getstrata/bootstrap/discoverModules --external @getstrata/bootstrap/health --external @getstrata/bootstrap/http/securedRouteModelBinding --external @getstrata/bootstrap/httpKernel --external @getstrata/bootstrap/listeners/invalidateCacheOnModelWrite --external @getstrata/bootstrap/membershipService --external @getstrata/bootstrap/metricsRoutes --external @getstrata/bootstrap/queue/defaultJobs --external @getstrata/bootstrap/providers --external @getstrata/bootstrap/providers/view --external @getstrata/bootstrap/routeRegistry --external @getstrata/bootstrap/schedule --external @getstrata/bootstrap/secretsGuard --external @getstrata/bootstrap/web/forms --external @getstrata/bootstrap/web/routing --external @getstrata/bootstrap/web/server --external @getstrata/bootstrap/web/session --external @getstrata/bootstrap/web/slug --external @getstrata/core/auth/accessControl --external @getstrata/core/auth/abilityChecker --external @getstrata/core/auth/authContext --external @getstrata/core/auth/emailVerification --external @getstrata/core/auth/guard --external @getstrata/core/auth/membershipContext --external @getstrata/core/auth/membershipMiddleware --external @getstrata/core/auth/membershipScope --external @getstrata/core/auth/membershipService --external @getstrata/core/auth/oauth/oidcProvider --external @getstrata/core/auth/oauth/providers --external @getstrata/core/auth/oauth/samlProvider --external @getstrata/core/auth/oauth/types --external @getstrata/core/auth/password --external @getstrata/core/auth/intendedUrlCookie --external @getstrata/core/auth/passwordConfirmCookie --external @getstrata/core/auth/policy --external @getstrata/core/auth/scimAuthMiddleware --external @getstrata/core/auth/sessionCookie --external @getstrata/core/auth/sessionGuard --external @getstrata/core/auth/tokenHash --external @getstrata/core/audit/exportAuditLogs --external @getstrata/core/audit/siemFormatter --external @getstrata/core/admin/formatValue --external @getstrata/core/admin/registry --external @getstrata/core/admin/types --external @getstrata/core/cache/tags --external @getstrata/core/cache/createCacheStore --external @getstrata/core/cache/repository --external @getstrata/core/cache/simpleCache --external @getstrata/core/cache/simpleCacheStore --external @getstrata/core/config/envSchema --external @getstrata/core/contracts/serviceTokens --external @getstrata/core/contracts/container --external @getstrata/core/contracts/di --external @getstrata/core/crypto/fieldEncryption --external @getstrata/core/crypto/mfaSecret --external @getstrata/core/database --external @getstrata/core/database/baseRepository --external @getstrata/core/database/bindConnection --external @getstrata/core/database/boundConnection --external @getstrata/core/database/bunSql --external @getstrata/core/database/connection --external @getstrata/core/database/defaultConnection --external @getstrata/core/database/errors --external @getstrata/core/database/factory --external @getstrata/core/database/migrations --external @getstrata/core/database/migrations/types --external @getstrata/core/database/model --external @getstrata/core/database/query --external @getstrata/core/database/relationships --external @getstrata/core/database/repositoryConnection --external @getstrata/core/database/repositoryQuery --external @getstrata/core/database/seeders --external @getstrata/core/database/seeders/types --external @getstrata/core/database/schema --external @getstrata/core/database/table --external @getstrata/core/database/transaction --external @getstrata/core/database/types --external @getstrata/core/database/whereBuilder --external @getstrata/core/errors/http --external @getstrata/core/events --external @getstrata/core/facades --external @getstrata/core/http --external @getstrata/core/http/authMiddleware --external @getstrata/core/http/authorizeMiddleware --external @getstrata/core/http/bodySizeLimitMiddleware --external @getstrata/core/http/clientIp --external @getstrata/core/http/cookies --external @getstrata/core/http/contentNegotiation --external @getstrata/core/http/contentSecurityPolicy --external @getstrata/core/http/conditionalResponse --external @getstrata/core/http/corsMiddleware --external @getstrata/core/http/csrfMiddleware --external @getstrata/core/http/csrfProtection --external @getstrata/core/http/csrfToken --external @getstrata/core/http/etag --external @getstrata/core/http/flashSession --external @getstrata/core/http/flashMiddleware --external @getstrata/core/http/formRequest --external @getstrata/core/http/middleware --external @getstrata/core/http/metricsMiddleware --external @getstrata/core/http/loginThrottleMiddleware --external @getstrata/core/http/memoryThrottleMiddleware --external @getstrata/core/http/pagination --external @getstrata/core/http/parseFormBody --external @getstrata/core/http/parseMultipartUpload --external @getstrata/core/http/requireAbilityMiddleware --external @getstrata/core/http/requireAuthMiddleware --external @getstrata/core/http/requireGlobalAdminMiddleware --external @getstrata/core/http/requireWebAuthMiddleware --external @getstrata/core/http/requireVerifiedMiddleware --external @getstrata/core/http/requirePasswordConfirmMiddleware --external @getstrata/core/http/resources --external @getstrata/core/http/response --external @getstrata/core/http/route --external @getstrata/core/http/routeMiddleware --external @getstrata/core/http/routeModelBinding --external @getstrata/core/http/safeInternalPath --external @getstrata/core/http/signedUrl --external @getstrata/core/http/scimThrottleMiddleware --external @getstrata/core/http/securityHeadersMiddleware --external @getstrata/core/http/securedRouteModelBinding --external @getstrata/core/http/requestMetaContext --external @getstrata/core/http/webErrorResponse --external @getstrata/core/http/webFormRequest --external @getstrata/core/http/throttleMiddleware --external @getstrata/core/http/validation --external @getstrata/core/jobs/dispatchWebhookJob --external @getstrata/core/jobs/exportAuditLogsJob --external @getstrata/core/jobs/invalidateCacheTagsJob --external @getstrata/core/lifecycle/gracefulShutdown --external @getstrata/core/logging/logger --external @getstrata/core/logging/requestLoggingMiddleware --external @getstrata/core/mail/mailer --external @getstrata/core/mail/markdownMail --external @getstrata/core/mail/markdownMailable --external @getstrata/core/mail/sanitizeMailHtml --external @getstrata/core/media/imageTransform --external @getstrata/core/metrics/prometheus --external @getstrata/core/notifications --external @getstrata/core/openapi/generator --external @getstrata/core/openapi/registeredRoute --external @getstrata/core/openapi/validate --external @getstrata/core/pagination --external @getstrata/core/queue --external @getstrata/core/queue/createAppQueue --external @getstrata/core/queue/failedJobRepository --external @getstrata/core/queue/failedJobService --external @getstrata/core/queue/jobRegistry --external @getstrata/core/queue/jobRunner --external @getstrata/core/queue/queueMetrics --external @getstrata/core/queue/publicQueue --external @getstrata/core/queue/redisQueue --external @getstrata/core/queue/types --external @getstrata/core/runtime/appKeyPrefix --external @getstrata/core/runtime/frontendMode --external @getstrata/core/runtime/applicationRegistry --external @getstrata/core/runtime/asyncContextStore --external @getstrata/core/scheduler/schedule --external @getstrata/core/scheduler/osCron --external @getstrata/core/security/oauthState --external @getstrata/core/security/publicReads --external @getstrata/core/security/recoveryCodes --external @getstrata/core/security/safeFetch --external @getstrata/core/security/safeUrl --external @getstrata/core/security/scimTenantTokens --external @getstrata/core/security/securityEvents --external @getstrata/core/security/stripeWebhook --external @getstrata/core/security/timingSafeCompare --external @getstrata/core/security/tokenExpiry --external @getstrata/core/security/totp --external @getstrata/core/storage/storage --external @getstrata/core/tenant/tenancyConfig --external @getstrata/core/tenant/tenantContext --external @getstrata/core/tenant/tenantDatabaseScope --external @getstrata/core/tenant/databaseTenantContext --external @getstrata/core/tenant/tenantMiddleware --external @getstrata/core/terminal/runShell --external @getstrata/core/tracing/traceContext --external @getstrata/core/tracing/tracingMiddleware --external @getstrata/core/validation/rules --external @getstrata/core/view",
|
|
177
177
|
"build:shims": "true"
|
|
178
178
|
},
|
|
179
179
|
"publishConfig": {
|