@getstrata/bootstrap 0.2.66 → 0.2.68
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 +11 -0
- package/dist/bootstrap/createWebRoutes.d.ts +7 -3
- package/dist/bootstrap/dogfoodApp.d.ts +7 -0
- package/dist/bootstrap/public-api.d.ts +1 -0
- package/dist/bootstrap/web/session.d.ts +18 -3
- package/dist/entries/buildModuleRoutes.js +6 -1
- package/dist/entries/buildWebModuleRoutes.js +6 -1
- package/dist/entries/cache/modelCacheTags.js +6 -1
- package/dist/entries/context.js +11 -161
- package/dist/entries/createRoutes.js +31 -856
- package/dist/entries/createWebRoutes.js +11 -5
- package/dist/entries/dependencies.js +11 -161
- package/dist/entries/discoverModules.js +6 -1
- package/dist/entries/listeners/invalidateCacheOnModelWrite.js +6 -1
- package/dist/entries/providers/view.js +5 -160
- package/dist/entries/providers.js +11 -161
- package/dist/entries/web/session.js +24 -10
- package/dist/index.js +40 -175
- package/package.json +1 -1
- package/dist/bootstrap/scimRoutes.d.ts +0 -24
|
@@ -96,19 +96,33 @@ class CookieSessionStore {
|
|
|
96
96
|
sql() {
|
|
97
97
|
return resolveSql(this.sqlSource);
|
|
98
98
|
}
|
|
99
|
-
async create(user) {
|
|
99
|
+
async create(user, meta = {}) {
|
|
100
100
|
const id = randomBytes(32).toString("hex");
|
|
101
101
|
const expires = new Date(Date.now() + this.maxAgeSeconds * 1000);
|
|
102
|
-
await this.sql().unsafe(`INSERT INTO sessions (id, user_id, expires_at
|
|
103
|
-
|
|
104
|
-
user.id,
|
|
105
|
-
expires
|
|
106
|
-
]);
|
|
102
|
+
await this.sql().unsafe(`INSERT INTO sessions (id, user_id, expires_at, user_agent, ip_address, last_active_at)
|
|
103
|
+
VALUES ($1, $2, $3, $4, $5, NOW())`, [id, user.id, expires, meta.userAgent ?? null, meta.ipAddress ?? null]);
|
|
107
104
|
return id;
|
|
108
105
|
}
|
|
109
106
|
async destroy(sessionId) {
|
|
110
107
|
await this.sql().unsafe(`DELETE FROM sessions WHERE id = $1`, [sessionId]);
|
|
111
108
|
}
|
|
109
|
+
async destroyOtherSessions(userId, keepSessionId) {
|
|
110
|
+
await this.sql().unsafe(`DELETE FROM sessions WHERE user_id = $1 AND id <> $2`, [
|
|
111
|
+
userId,
|
|
112
|
+
keepSessionId
|
|
113
|
+
]);
|
|
114
|
+
}
|
|
115
|
+
async listForUser(userId) {
|
|
116
|
+
return this.sql().unsafe(`SELECT id, user_id, user_agent, ip_address, last_active_at, expires_at
|
|
117
|
+
FROM sessions
|
|
118
|
+
WHERE user_id = $1 AND expires_at > NOW()
|
|
119
|
+
ORDER BY last_active_at DESC NULLS LAST, expires_at DESC`, [userId]);
|
|
120
|
+
}
|
|
121
|
+
async touch(sessionId) {
|
|
122
|
+
await this.sql().unsafe(`UPDATE sessions SET last_active_at = NOW() WHERE id = $1`, [
|
|
123
|
+
sessionId
|
|
124
|
+
]);
|
|
125
|
+
}
|
|
112
126
|
async read(request) {
|
|
113
127
|
const sessionId = this.sessionIdFromRequest(request);
|
|
114
128
|
if (!sessionId)
|
|
@@ -142,8 +156,8 @@ class CookieSessionAuthManager extends AuthManager {
|
|
|
142
156
|
super(new CookieSessionGuard(store, mapUser));
|
|
143
157
|
this.store = store;
|
|
144
158
|
}
|
|
145
|
-
async signIn(user) {
|
|
146
|
-
const sessionId = await this.store.create(user);
|
|
159
|
+
async signIn(user, meta = {}) {
|
|
160
|
+
const sessionId = await this.store.create(user, meta);
|
|
147
161
|
return { sessionId, setCookie: this.store.cookieHeader(user, sessionId) };
|
|
148
162
|
}
|
|
149
163
|
async signOut(request) {
|
|
@@ -153,8 +167,8 @@ class CookieSessionAuthManager extends AuthManager {
|
|
|
153
167
|
}
|
|
154
168
|
return { setCookie: this.store.clearCookieHeader() };
|
|
155
169
|
}
|
|
156
|
-
async signInRedirect(user, location, status = 302) {
|
|
157
|
-
const { setCookie } = await this.signIn(user);
|
|
170
|
+
async signInRedirect(user, location, status = 302, meta = {}) {
|
|
171
|
+
const { setCookie } = await this.signIn(user, meta);
|
|
158
172
|
return redirectWithCookie(location, setCookie, status);
|
|
159
173
|
}
|
|
160
174
|
async signOutRedirect(request, location, status = 302) {
|
package/dist/index.js
CHANGED
|
@@ -353,7 +353,12 @@ function readDiscoverModulesState() {
|
|
|
353
353
|
return state;
|
|
354
354
|
}
|
|
355
355
|
function configureModulesDirectory(modulesDir) {
|
|
356
|
-
readDiscoverModulesState()
|
|
356
|
+
const state = readDiscoverModulesState();
|
|
357
|
+
if (state.configuredModulesDir !== modulesDir) {
|
|
358
|
+
state.appModules.length = 0;
|
|
359
|
+
state.modulesReady = undefined;
|
|
360
|
+
}
|
|
361
|
+
state.configuredModulesDir = modulesDir;
|
|
357
362
|
}
|
|
358
363
|
function resolveModulesDirectory(options) {
|
|
359
364
|
const state = readDiscoverModulesState();
|
|
@@ -871,7 +876,6 @@ var storageProvider = {
|
|
|
871
876
|
var storage_default = storageProvider;
|
|
872
877
|
|
|
873
878
|
// ../../src/bootstrap/providers/view.ts
|
|
874
|
-
import { resolveMembershipLookup } from "@getstrata/core/auth/membershipContext";
|
|
875
879
|
import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
|
|
876
880
|
import { appDisplayName } from "@getstrata/core/runtime/appKeyPrefix";
|
|
877
881
|
import { isViewsEnabled as isViewsEnabled2 } from "@getstrata/core/runtime/frontendMode";
|
|
@@ -883,148 +887,6 @@ import {
|
|
|
883
887
|
errorTemplateName,
|
|
884
888
|
resolveWebLayoutData
|
|
885
889
|
} from "@getstrata/core/view";
|
|
886
|
-
|
|
887
|
-
// ../../src/modules/organization/repository.ts
|
|
888
|
-
import { BaseRepository } from "@getstrata/core/database/baseRepository";
|
|
889
|
-
import { NotFoundError } from "@getstrata/core/errors/http";
|
|
890
|
-
import { currentTenantId } from "@getstrata/core/tenant/tenantContext";
|
|
891
|
-
|
|
892
|
-
// ../../src/modules/organization/table.ts
|
|
893
|
-
import { defineTable } from "@getstrata/core/database/table";
|
|
894
|
-
|
|
895
|
-
// ../../src/domain/workhub.ts
|
|
896
|
-
var ORGANIZATION_TABLE = "organization";
|
|
897
|
-
|
|
898
|
-
// ../../src/modules/organization/table.ts
|
|
899
|
-
var organizationTable = defineTable({
|
|
900
|
-
name: ORGANIZATION_TABLE,
|
|
901
|
-
primaryKey: "id",
|
|
902
|
-
columns: ["id", "tenant_id", "name", "slug", "created_at", "updated_at", "deleted_at"],
|
|
903
|
-
softDeletes: true,
|
|
904
|
-
defaultOrderBy: { column: "id", direction: "ASC" }
|
|
905
|
-
});
|
|
906
|
-
|
|
907
|
-
// ../../src/modules/organization/repository.ts
|
|
908
|
-
class OrganizationRepository extends BaseRepository {
|
|
909
|
-
constructor() {
|
|
910
|
-
super(organizationTable);
|
|
911
|
-
}
|
|
912
|
-
async findBySlug(slug) {
|
|
913
|
-
return await this.firstOrNull({ slug });
|
|
914
|
-
}
|
|
915
|
-
async listForTenant(options) {
|
|
916
|
-
return await this.findAll({
|
|
917
|
-
limit: options.limit,
|
|
918
|
-
offset: options.offset,
|
|
919
|
-
where: { tenant_id: options.tenantId ?? currentTenantId() }
|
|
920
|
-
});
|
|
921
|
-
}
|
|
922
|
-
async countForTenant(tenantId = currentTenantId()) {
|
|
923
|
-
return await this.countWhere({ tenant_id: tenantId });
|
|
924
|
-
}
|
|
925
|
-
async findForTenantOrThrow(id, tenantId = currentTenantId()) {
|
|
926
|
-
const organization = await this.findById(id);
|
|
927
|
-
if (!organization || organization.tenant_id !== tenantId) {
|
|
928
|
-
throw new NotFoundError(`SCIM group ${id} not found.`);
|
|
929
|
-
}
|
|
930
|
-
return organization;
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
var repository_default = OrganizationRepository;
|
|
934
|
-
|
|
935
|
-
// ../../src/modules/user/repository.ts
|
|
936
|
-
import {
|
|
937
|
-
emailLookupForQuery,
|
|
938
|
-
protectEmail,
|
|
939
|
-
revealEmail
|
|
940
|
-
} from "@getstrata/core/crypto/fieldEncryption";
|
|
941
|
-
import { revealMfaSecret } from "@getstrata/core/crypto/mfaSecret";
|
|
942
|
-
import { BaseRepository as BaseRepository2 } from "@getstrata/core/database/baseRepository";
|
|
943
|
-
import { currentTenantId as currentTenantId2 } from "@getstrata/core/tenant/tenantContext";
|
|
944
|
-
|
|
945
|
-
// ../../src/modules/user/table.ts
|
|
946
|
-
import { defineTable as defineTable2 } from "@getstrata/core/database/table";
|
|
947
|
-
var userTable = defineTable2({
|
|
948
|
-
name: "users",
|
|
949
|
-
primaryKey: "id",
|
|
950
|
-
columns: [
|
|
951
|
-
"id",
|
|
952
|
-
"name",
|
|
953
|
-
"email",
|
|
954
|
-
"email_lookup",
|
|
955
|
-
"role",
|
|
956
|
-
"tenant_id",
|
|
957
|
-
"password_hash",
|
|
958
|
-
"email_verified_at",
|
|
959
|
-
"mfa_secret",
|
|
960
|
-
"mfa_enabled",
|
|
961
|
-
"mfa_recovery_codes",
|
|
962
|
-
"profile_photo_path",
|
|
963
|
-
"session_valid_after",
|
|
964
|
-
"current_organization_id",
|
|
965
|
-
"created_at",
|
|
966
|
-
"updated_at"
|
|
967
|
-
],
|
|
968
|
-
defaultOrderBy: { column: "id", direction: "ASC" }
|
|
969
|
-
});
|
|
970
|
-
|
|
971
|
-
// ../../src/modules/user/repository.ts
|
|
972
|
-
class UserRepository extends BaseRepository2 {
|
|
973
|
-
constructor() {
|
|
974
|
-
super(userTable);
|
|
975
|
-
}
|
|
976
|
-
decode(record) {
|
|
977
|
-
return {
|
|
978
|
-
...record,
|
|
979
|
-
email: revealEmail(record.email),
|
|
980
|
-
mfa_secret: revealMfaSecret(record.mfa_secret)
|
|
981
|
-
};
|
|
982
|
-
}
|
|
983
|
-
async findById(id) {
|
|
984
|
-
const record = await super.findById(id);
|
|
985
|
-
return record ? this.decode(record) : null;
|
|
986
|
-
}
|
|
987
|
-
async findAll(options = {}) {
|
|
988
|
-
const records = await super.findAll(options);
|
|
989
|
-
return records.map((record) => this.decode(record));
|
|
990
|
-
}
|
|
991
|
-
async create(values) {
|
|
992
|
-
const email = values.email;
|
|
993
|
-
if (!email) {
|
|
994
|
-
throw new Error("Email is required.");
|
|
995
|
-
}
|
|
996
|
-
const protectedEmail = protectEmail(email);
|
|
997
|
-
const record = await super.create({
|
|
998
|
-
...values,
|
|
999
|
-
tenant_id: values.tenant_id ?? currentTenantId2(),
|
|
1000
|
-
email: protectedEmail.storedEmail,
|
|
1001
|
-
email_lookup: protectedEmail.emailLookup,
|
|
1002
|
-
password_hash: values.password_hash ?? ""
|
|
1003
|
-
});
|
|
1004
|
-
return this.decode(record);
|
|
1005
|
-
}
|
|
1006
|
-
async updateByIdOrThrow(id, values, errorFactory) {
|
|
1007
|
-
const changes = { ...values };
|
|
1008
|
-
if (values.email !== undefined) {
|
|
1009
|
-
const protectedEmail = protectEmail(values.email);
|
|
1010
|
-
changes.email = protectedEmail.storedEmail;
|
|
1011
|
-
changes.email_lookup = protectedEmail.emailLookup;
|
|
1012
|
-
}
|
|
1013
|
-
const record = await super.updateByIdOrThrow(id, changes, errorFactory);
|
|
1014
|
-
return this.decode(record);
|
|
1015
|
-
}
|
|
1016
|
-
async findByEmail(email) {
|
|
1017
|
-
const records = await this.findWhere({ email_lookup: emailLookupForQuery(email) }, { limit: 1 });
|
|
1018
|
-
const record = records[0];
|
|
1019
|
-
return record ? this.decode(record) : null;
|
|
1020
|
-
}
|
|
1021
|
-
async countForTenant(tenantId = currentTenantId2()) {
|
|
1022
|
-
return await this.countWhere({ tenant_id: tenantId });
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
var repository_default2 = UserRepository;
|
|
1026
|
-
|
|
1027
|
-
// ../../src/bootstrap/providers/view.ts
|
|
1028
890
|
var CORE_VIEW_TOKEN = "core.view";
|
|
1029
891
|
var VIEW_DIRECTORY_CONFIG_KEY = "view.directory";
|
|
1030
892
|
var viewProvider = {
|
|
@@ -1038,23 +900,11 @@ var viewProvider = {
|
|
|
1038
900
|
const engine = new EtaViewEngine(viewsDirectory, (request) => resolveWebLayoutData(container, request ?? currentRequestMeta().request));
|
|
1039
901
|
container.set(CORE_VIEW_TOKEN, engine);
|
|
1040
902
|
configureWebLayoutData({
|
|
1041
|
-
extra: async (
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
try {
|
|
1047
|
-
const record = await new repository_default2().findByIdOrThrow(user.id);
|
|
1048
|
-
const memberships = await resolveMembershipLookup().listForUser(record.id);
|
|
1049
|
-
const organizationsRepo = new repository_default;
|
|
1050
|
-
const organizations = (await Promise.all(memberships.map((membership) => organizationsRepo.findById(membership.organization_id)))).filter((organization) => Boolean(organization && !organization.deleted_at));
|
|
1051
|
-
const currentId = record.current_organization_id ?? null;
|
|
1052
|
-
const currentOrganization = organizations.find((organization) => organization.id === currentId) ?? organizations[0] ?? null;
|
|
1053
|
-
return { appName, currentOrganization, organizations };
|
|
1054
|
-
} catch {
|
|
1055
|
-
return { appName, currentOrganization: null, organizations: [] };
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
903
|
+
extra: async () => ({
|
|
904
|
+
appName: appDisplayName(),
|
|
905
|
+
currentOrganization: null,
|
|
906
|
+
organizations: []
|
|
907
|
+
})
|
|
1058
908
|
});
|
|
1059
909
|
configureWebErrorView({
|
|
1060
910
|
render: async (input) => engine.render(errorTemplateName(input.status), {
|
|
@@ -1118,9 +968,10 @@ import { notFoundHtmlResponse } from "@getstrata/core/view";
|
|
|
1118
968
|
function registerRoute(method, path, middleware) {
|
|
1119
969
|
routeRegistry.register({ method, path, middleware });
|
|
1120
970
|
}
|
|
1121
|
-
function createWebRoutes(dependencies) {
|
|
971
|
+
function createWebRoutes(dependencies, options = {}) {
|
|
1122
972
|
const wrappedRoutes = buildWebModuleRoutes(dependencies, {
|
|
1123
|
-
clearRegistry: false
|
|
973
|
+
clearRegistry: false,
|
|
974
|
+
modules: options.modules
|
|
1124
975
|
});
|
|
1125
976
|
registerRoute("GET", "/", ["global", "web"]);
|
|
1126
977
|
wrappedRoutes["/assets/*"] = async (request) => {
|
|
@@ -1136,12 +987,12 @@ function createWebRoutes(dependencies) {
|
|
|
1136
987
|
registerRoute("GET", "/assets/*", ["global", "web"]);
|
|
1137
988
|
return wrappedRoutes;
|
|
1138
989
|
}
|
|
1139
|
-
function mergeWebRoutes(dependencies, routes) {
|
|
990
|
+
function mergeWebRoutes(dependencies, routes, options = {}) {
|
|
1140
991
|
if (!isViewsEnabled3()) {
|
|
1141
992
|
return routes;
|
|
1142
993
|
}
|
|
1143
994
|
return {
|
|
1144
|
-
...createWebRoutes(dependencies),
|
|
995
|
+
...createWebRoutes(dependencies, options),
|
|
1145
996
|
...routes
|
|
1146
997
|
};
|
|
1147
998
|
}
|
|
@@ -1592,19 +1443,33 @@ class CookieSessionStore {
|
|
|
1592
1443
|
sql() {
|
|
1593
1444
|
return resolveSql(this.sqlSource);
|
|
1594
1445
|
}
|
|
1595
|
-
async create(user) {
|
|
1446
|
+
async create(user, meta = {}) {
|
|
1596
1447
|
const id = randomBytes(32).toString("hex");
|
|
1597
1448
|
const expires = new Date(Date.now() + this.maxAgeSeconds * 1000);
|
|
1598
|
-
await this.sql().unsafe(`INSERT INTO sessions (id, user_id, expires_at
|
|
1599
|
-
|
|
1600
|
-
user.id,
|
|
1601
|
-
expires
|
|
1602
|
-
]);
|
|
1449
|
+
await this.sql().unsafe(`INSERT INTO sessions (id, user_id, expires_at, user_agent, ip_address, last_active_at)
|
|
1450
|
+
VALUES ($1, $2, $3, $4, $5, NOW())`, [id, user.id, expires, meta.userAgent ?? null, meta.ipAddress ?? null]);
|
|
1603
1451
|
return id;
|
|
1604
1452
|
}
|
|
1605
1453
|
async destroy(sessionId) {
|
|
1606
1454
|
await this.sql().unsafe(`DELETE FROM sessions WHERE id = $1`, [sessionId]);
|
|
1607
1455
|
}
|
|
1456
|
+
async destroyOtherSessions(userId, keepSessionId) {
|
|
1457
|
+
await this.sql().unsafe(`DELETE FROM sessions WHERE user_id = $1 AND id <> $2`, [
|
|
1458
|
+
userId,
|
|
1459
|
+
keepSessionId
|
|
1460
|
+
]);
|
|
1461
|
+
}
|
|
1462
|
+
async listForUser(userId) {
|
|
1463
|
+
return this.sql().unsafe(`SELECT id, user_id, user_agent, ip_address, last_active_at, expires_at
|
|
1464
|
+
FROM sessions
|
|
1465
|
+
WHERE user_id = $1 AND expires_at > NOW()
|
|
1466
|
+
ORDER BY last_active_at DESC NULLS LAST, expires_at DESC`, [userId]);
|
|
1467
|
+
}
|
|
1468
|
+
async touch(sessionId) {
|
|
1469
|
+
await this.sql().unsafe(`UPDATE sessions SET last_active_at = NOW() WHERE id = $1`, [
|
|
1470
|
+
sessionId
|
|
1471
|
+
]);
|
|
1472
|
+
}
|
|
1608
1473
|
async read(request) {
|
|
1609
1474
|
const sessionId = this.sessionIdFromRequest(request);
|
|
1610
1475
|
if (!sessionId)
|
|
@@ -1638,8 +1503,8 @@ class CookieSessionAuthManager extends AuthManager2 {
|
|
|
1638
1503
|
super(new CookieSessionGuard(store, mapUser));
|
|
1639
1504
|
this.store = store;
|
|
1640
1505
|
}
|
|
1641
|
-
async signIn(user) {
|
|
1642
|
-
const sessionId = await this.store.create(user);
|
|
1506
|
+
async signIn(user, meta = {}) {
|
|
1507
|
+
const sessionId = await this.store.create(user, meta);
|
|
1643
1508
|
return { sessionId, setCookie: this.store.cookieHeader(user, sessionId) };
|
|
1644
1509
|
}
|
|
1645
1510
|
async signOut(request) {
|
|
@@ -1649,8 +1514,8 @@ class CookieSessionAuthManager extends AuthManager2 {
|
|
|
1649
1514
|
}
|
|
1650
1515
|
return { setCookie: this.store.clearCookieHeader() };
|
|
1651
1516
|
}
|
|
1652
|
-
async signInRedirect(user, location, status = 302) {
|
|
1653
|
-
const { setCookie } = await this.signIn(user);
|
|
1517
|
+
async signInRedirect(user, location, status = 302, meta = {}) {
|
|
1518
|
+
const { setCookie } = await this.signIn(user, meta);
|
|
1654
1519
|
return redirectWithCookie(location, setCookie, status);
|
|
1655
1520
|
}
|
|
1656
1521
|
async signOutRedirect(request, location, status = 302) {
|
package/package.json
CHANGED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { RouteHandler } from "@getstrata/core/http/middleware";
|
|
2
|
-
import type { AppDependencies } from "./contracts";
|
|
3
|
-
declare function createScimRoutes(dependencies: AppDependencies): {
|
|
4
|
-
"/scim/v2/ServiceProviderConfig": {
|
|
5
|
-
GET: RouteHandler;
|
|
6
|
-
};
|
|
7
|
-
"/scim/v2/Users": {
|
|
8
|
-
GET: RouteHandler;
|
|
9
|
-
POST: RouteHandler;
|
|
10
|
-
};
|
|
11
|
-
"/scim/v2/Users/:id": {
|
|
12
|
-
GET: RouteHandler;
|
|
13
|
-
PATCH: RouteHandler;
|
|
14
|
-
DELETE: RouteHandler;
|
|
15
|
-
};
|
|
16
|
-
"/scim/v2/Groups": {
|
|
17
|
-
GET: RouteHandler;
|
|
18
|
-
};
|
|
19
|
-
"/scim/v2/Groups/:id": {
|
|
20
|
-
GET: RouteHandler;
|
|
21
|
-
PATCH: RouteHandler;
|
|
22
|
-
};
|
|
23
|
-
};
|
|
24
|
-
export { createScimRoutes };
|