@getstrata/bootstrap 0.2.65 → 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/dist/index.js CHANGED
@@ -52,7 +52,32 @@ import { isPublicReadsEnabled } from "@getstrata/core/security/publicReads";
52
52
  import { createTenantMiddleware } from "@getstrata/core/tenant/tenantMiddleware";
53
53
  import { createTracingMiddleware } from "@getstrata/core/tracing/tracingMiddleware";
54
54
 
55
- // ../../src/config/rateLimit.ts
55
+ // ../../src/bootstrap/config.ts
56
+ import {
57
+ CORE_ABILITY_CHECKER_TOKEN,
58
+ CORE_AUTH_TOKEN,
59
+ CORE_AUTH_USER_DIRECTORY_TOKEN,
60
+ CORE_CACHE_TOKEN,
61
+ CORE_CONFIG_TOKEN,
62
+ CORE_EVENT_BUS_TOKEN,
63
+ CORE_POLICY_GATE_TOKEN,
64
+ CORE_QUEUE_TOKEN,
65
+ CORE_TOKEN_SERVICE_TOKEN
66
+ } from "@getstrata/core/contracts/serviceTokens";
67
+ var APP_PORT_CONFIG_KEY = "app.port";
68
+ var CACHE_TTL_MS_CONFIG_KEY = "cache.ttlMs";
69
+ var CACHE_MAX_ENTRIES_CONFIG_KEY = "cache.maxEntries";
70
+ var CACHE_DRIVER_CONFIG_KEY = "cache.driver";
71
+ var REDIS_URL_CONFIG_KEY = "cache.redisUrl";
72
+ var DATABASE_URL_CONFIG_KEY = "database.url";
73
+ var DEFAULT_APP_PORT = 3000;
74
+ var DEFAULT_CACHE_TTL_MS = 3600000;
75
+ var DEFAULT_CACHE_MAX_ENTRIES = 100;
76
+ var DEFAULT_CACHE_DRIVER = "array";
77
+ var DEFAULT_API_TOKEN = "";
78
+ var DEFAULT_QUEUE_DRIVER = "sync";
79
+
80
+ // ../../src/bootstrap/rateLimit.ts
56
81
  var LOCAL_LOGIN_RATE_LIMIT = {
57
82
  maxAttempts: 100,
58
83
  decaySeconds: 60
@@ -98,31 +123,6 @@ function resolveRegisterRateLimit() {
98
123
  };
99
124
  }
100
125
 
101
- // ../../src/bootstrap/config.ts
102
- import {
103
- CORE_ABILITY_CHECKER_TOKEN,
104
- CORE_AUTH_TOKEN,
105
- CORE_AUTH_USER_DIRECTORY_TOKEN,
106
- CORE_CACHE_TOKEN,
107
- CORE_CONFIG_TOKEN,
108
- CORE_EVENT_BUS_TOKEN,
109
- CORE_POLICY_GATE_TOKEN,
110
- CORE_QUEUE_TOKEN,
111
- CORE_TOKEN_SERVICE_TOKEN
112
- } from "@getstrata/core/contracts/serviceTokens";
113
- var APP_PORT_CONFIG_KEY = "app.port";
114
- var CACHE_TTL_MS_CONFIG_KEY = "cache.ttlMs";
115
- var CACHE_MAX_ENTRIES_CONFIG_KEY = "cache.maxEntries";
116
- var CACHE_DRIVER_CONFIG_KEY = "cache.driver";
117
- var REDIS_URL_CONFIG_KEY = "cache.redisUrl";
118
- var DATABASE_URL_CONFIG_KEY = "database.url";
119
- var DEFAULT_APP_PORT = 3000;
120
- var DEFAULT_CACHE_TTL_MS = 3600000;
121
- var DEFAULT_CACHE_MAX_ENTRIES = 100;
122
- var DEFAULT_CACHE_DRIVER = "array";
123
- var DEFAULT_API_TOKEN = "";
124
- var DEFAULT_QUEUE_DRIVER = "sync";
125
-
126
126
  // ../../src/bootstrap/httpKernel.ts
127
127
  class HttpKernel {
128
128
  dependencies;
@@ -193,7 +193,7 @@ class HttpKernel {
193
193
  wrapWeb(handler) {
194
194
  return withErrorHandling(this.wrap("web", handler));
195
195
  }
196
- wrapWebGuest(handler, home = "/organizations") {
196
+ wrapWebGuest(handler, home = "/") {
197
197
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
198
198
  return this.wrapWeb(async (request) => {
199
199
  const user = await auth.resolve(request);
@@ -353,7 +353,12 @@ function readDiscoverModulesState() {
353
353
  return state;
354
354
  }
355
355
  function configureModulesDirectory(modulesDir) {
356
- readDiscoverModulesState().configuredModulesDir = modulesDir;
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();
@@ -363,7 +368,7 @@ function resolveModulesDirectory(options) {
363
368
  if (state.configuredModulesDir) {
364
369
  return state.configuredModulesDir;
365
370
  }
366
- throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
371
+ throw new Error("configureModulesDirectory() must be called before discovering modules. The app preload (or starter) should call it.");
367
372
  }
368
373
  async function loadDiscoveredModules(options) {
369
374
  const modulesDirectory = resolveModulesDirectory(options);
@@ -575,7 +580,7 @@ import { validateEnv } from "@getstrata/core/config/envSchema";
575
580
 
576
581
  // ../../src/config/app.ts
577
582
  var appConfig = {
578
- name: process.env.APP_NAME?.trim() || "WorkHub",
583
+ name: process.env.APP_NAME?.trim() || "Strata",
579
584
  env: process.env.APP_ENV ?? "local",
580
585
  debug: (process.env.APP_DEBUG ?? "true") !== "false",
581
586
  url: process.env.APP_URL ?? "http://localhost:3000",
@@ -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 (user) => {
1042
- const appName = appDisplayName();
1043
- if (!user || typeof user.id !== "number") {
1044
- return { appName, currentOrganization: null, organizations: [] };
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,12 +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
973
  clearRegistry: false,
1124
- seedRoutes: {
1125
- "/": () => Response.redirect("/organizations", 302)
1126
- }
974
+ modules: options.modules
1127
975
  });
1128
976
  registerRoute("GET", "/", ["global", "web"]);
1129
977
  wrappedRoutes["/assets/*"] = async (request) => {
@@ -1139,12 +987,12 @@ function createWebRoutes(dependencies) {
1139
987
  registerRoute("GET", "/assets/*", ["global", "web"]);
1140
988
  return wrappedRoutes;
1141
989
  }
1142
- function mergeWebRoutes(dependencies, routes) {
990
+ function mergeWebRoutes(dependencies, routes, options = {}) {
1143
991
  if (!isViewsEnabled3()) {
1144
992
  return routes;
1145
993
  }
1146
994
  return {
1147
- ...createWebRoutes(dependencies),
995
+ ...createWebRoutes(dependencies, options),
1148
996
  ...routes
1149
997
  };
1150
998
  }
@@ -1255,12 +1103,15 @@ import {
1255
1103
  // ../../src/bootstrap/membershipService.ts
1256
1104
  import { resolveMembershipService } from "@getstrata/core/auth/membershipService";
1257
1105
  // ../../src/bootstrap/secretsGuard.ts
1258
- var DEFAULT_ADMIN_API_TOKEN = "workhub-admin-test-token";
1259
- var DEFAULT_MEMBER_API_TOKEN = "workhub-member-test-token";
1260
- var DEFAULT_SCIM_BEARER_TOKEN = "workhub-scim-test-token";
1106
+ var PUBLISHED_TEST_ADMIN_API_TOKEN = "workhub-admin-test-token";
1107
+ var PUBLISHED_TEST_MEMBER_API_TOKEN = "workhub-member-test-token";
1108
+ var PUBLISHED_TEST_SCIM_BEARER_TOKEN = "workhub-scim-test-token";
1261
1109
  var MIN_SESSION_SECRET_LENGTH = 32;
1262
- var DEFAULT_TOKENS = new Set([DEFAULT_ADMIN_API_TOKEN, DEFAULT_MEMBER_API_TOKEN]);
1263
- var DEFAULT_SCIM_TOKENS = new Set([DEFAULT_SCIM_BEARER_TOKEN]);
1110
+ var PUBLISHED_TEST_TOKENS = new Set([
1111
+ PUBLISHED_TEST_ADMIN_API_TOKEN,
1112
+ PUBLISHED_TEST_MEMBER_API_TOKEN
1113
+ ]);
1114
+ var PUBLISHED_TEST_SCIM_TOKENS = new Set([PUBLISHED_TEST_SCIM_BEARER_TOKEN]);
1264
1115
  function isEnabled(value, defaultEnabled) {
1265
1116
  if (value === undefined) {
1266
1117
  return defaultEnabled;
@@ -1287,36 +1138,19 @@ function assertSessionSecret(env) {
1287
1138
  throw new Error("Production startup blocked: set SESSION_SECRET when FRONTEND_MODE=server-htmx (32+ characters).");
1288
1139
  }
1289
1140
  }
1290
- function assertWorkHubProductionSecrets(env) {
1291
- const adminToken = env.ADMIN_API_TOKEN ?? DEFAULT_ADMIN_API_TOKEN;
1292
- const memberToken = env.MEMBER_API_TOKEN ?? DEFAULT_MEMBER_API_TOKEN;
1293
- const scimToken = env.SCIM_BEARER_TOKEN ?? DEFAULT_SCIM_BEARER_TOKEN;
1294
- const encryptionEnabled = isEnabled(env.FEATURE_FIELD_ENCRYPTION, true);
1295
- if (DEFAULT_TOKENS.has(adminToken) || DEFAULT_TOKENS.has(memberToken)) {
1296
- throw new Error("Production startup blocked: rotate ADMIN_API_TOKEN and MEMBER_API_TOKEN away from default WorkHub test values.");
1297
- }
1298
- if (DEFAULT_SCIM_TOKENS.has(scimToken)) {
1299
- throw new Error("Production startup blocked: rotate SCIM_BEARER_TOKEN away from default WorkHub test values.");
1300
- }
1301
- if (encryptionEnabled && !env.KMS_ENCRYPTION_KEY?.trim()) {
1302
- throw new Error("Production startup blocked: set KMS_ENCRYPTION_KEY when field encryption is enabled.");
1303
- }
1304
- if (!env.SIEM_EXPORT_URL?.trim() && isEnabled(env.FEATURE_SIEM_EXPORT, true)) {
1305
- console.warn("[secrets] SIEM_EXPORT_URL is not configured; audit logs remain database-only.");
1306
- }
1307
- if (isEnabled(env.FEATURE_BILLING, true) && !env.STRIPE_WEBHOOK_SECRET?.trim()) {
1308
- throw new Error("Production startup blocked: set STRIPE_WEBHOOK_SECRET when billing webhooks are enabled.");
1309
- }
1310
- const corsOrigins = (env.CORS_ALLOWED_ORIGINS ?? "*").split(",").map((origin) => origin.trim());
1311
- if (corsOrigins.includes("*")) {
1312
- throw new Error("Production startup blocked: set explicit CORS_ALLOWED_ORIGINS instead of wildcard.");
1141
+ function assertPublishedTestTokensRotated(env) {
1142
+ const adminToken = env.ADMIN_API_TOKEN ?? "";
1143
+ const memberToken = env.MEMBER_API_TOKEN ?? "";
1144
+ const scimToken = env.SCIM_BEARER_TOKEN ?? "";
1145
+ if (PUBLISHED_TEST_TOKENS.has(adminToken) || PUBLISHED_TEST_TOKENS.has(memberToken)) {
1146
+ throw new Error("Production startup blocked: rotate ADMIN_API_TOKEN and MEMBER_API_TOKEN away from published test defaults.");
1313
1147
  }
1314
- if (isEnabled(env.FEATURE_PUBLIC_READS, true)) {
1315
- throw new Error("Production startup blocked: set FEATURE_PUBLIC_READS=false for authenticated-only reads.");
1316
- }
1317
- if (!env.OAUTH_STATE_SECRET?.trim()) {
1318
- throw new Error("Production startup blocked: set OAUTH_STATE_SECRET for OAuth CSRF protection.");
1148
+ if (scimToken && PUBLISHED_TEST_SCIM_TOKENS.has(scimToken)) {
1149
+ throw new Error("Production startup blocked: rotate SCIM_BEARER_TOKEN away from published test defaults.");
1319
1150
  }
1151
+ }
1152
+ function assertTokenAuthProductionSecrets(env) {
1153
+ assertPublishedTestTokensRotated(env);
1320
1154
  if (!env.TOKEN_HASH_PEPPER?.trim()) {
1321
1155
  throw new Error("Production startup blocked: set TOKEN_HASH_PEPPER for API token hashing.");
1322
1156
  }
@@ -1324,11 +1158,11 @@ function assertWorkHubProductionSecrets(env) {
1324
1158
  throw new Error("Production startup blocked: set API_TOKEN_DEFAULT_EXPIRY_DAYS to enforce token rotation.");
1325
1159
  }
1326
1160
  }
1327
- function assertSiblingProductionSecrets(env) {
1161
+ function assertFeatureProductionSecrets(env) {
1328
1162
  if (isEnabled(env.FEATURE_SCIM, false)) {
1329
- const scimToken = env.SCIM_BEARER_TOKEN ?? DEFAULT_SCIM_BEARER_TOKEN;
1330
- if (DEFAULT_SCIM_TOKENS.has(scimToken) || !env.SCIM_BEARER_TOKEN?.trim()) {
1331
- throw new Error("Production startup blocked: rotate SCIM_BEARER_TOKEN away from default WorkHub test values.");
1163
+ const scimToken = env.SCIM_BEARER_TOKEN ?? PUBLISHED_TEST_SCIM_BEARER_TOKEN;
1164
+ if (PUBLISHED_TEST_SCIM_TOKENS.has(scimToken) || !env.SCIM_BEARER_TOKEN?.trim()) {
1165
+ throw new Error("Production startup blocked: rotate SCIM_BEARER_TOKEN away from published test defaults.");
1332
1166
  }
1333
1167
  }
1334
1168
  if (isEnabled(env.FEATURE_FIELD_ENCRYPTION, false) && !env.KMS_ENCRYPTION_KEY?.trim()) {
@@ -1360,10 +1194,9 @@ function assertProductionSecrets(env = process.env) {
1360
1194
  }
1361
1195
  assertAuthDevHeadersDisabled(env);
1362
1196
  if (isTokenAuthEnabled(env)) {
1363
- assertWorkHubProductionSecrets(env);
1364
- } else {
1365
- assertSiblingProductionSecrets(env);
1197
+ assertTokenAuthProductionSecrets(env);
1366
1198
  }
1199
+ assertFeatureProductionSecrets(env);
1367
1200
  const frontendMode = (env.FRONTEND_MODE ?? "api").trim();
1368
1201
  if (frontendMode === "server-htmx") {
1369
1202
  assertSessionSecret(env);
@@ -1610,19 +1443,33 @@ class CookieSessionStore {
1610
1443
  sql() {
1611
1444
  return resolveSql(this.sqlSource);
1612
1445
  }
1613
- async create(user) {
1446
+ async create(user, meta = {}) {
1614
1447
  const id = randomBytes(32).toString("hex");
1615
1448
  const expires = new Date(Date.now() + this.maxAgeSeconds * 1000);
1616
- await this.sql().unsafe(`INSERT INTO sessions (id, user_id, expires_at) VALUES ($1, $2, $3)`, [
1617
- id,
1618
- user.id,
1619
- expires
1620
- ]);
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]);
1621
1451
  return id;
1622
1452
  }
1623
1453
  async destroy(sessionId) {
1624
1454
  await this.sql().unsafe(`DELETE FROM sessions WHERE id = $1`, [sessionId]);
1625
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
+ }
1626
1473
  async read(request) {
1627
1474
  const sessionId = this.sessionIdFromRequest(request);
1628
1475
  if (!sessionId)
@@ -1656,8 +1503,8 @@ class CookieSessionAuthManager extends AuthManager2 {
1656
1503
  super(new CookieSessionGuard(store, mapUser));
1657
1504
  this.store = store;
1658
1505
  }
1659
- async signIn(user) {
1660
- const sessionId = await this.store.create(user);
1506
+ async signIn(user, meta = {}) {
1507
+ const sessionId = await this.store.create(user, meta);
1661
1508
  return { sessionId, setCookie: this.store.cookieHeader(user, sessionId) };
1662
1509
  }
1663
1510
  async signOut(request) {
@@ -1667,8 +1514,8 @@ class CookieSessionAuthManager extends AuthManager2 {
1667
1514
  }
1668
1515
  return { setCookie: this.store.clearCookieHeader() };
1669
1516
  }
1670
- async signInRedirect(user, location, status = 302) {
1671
- const { setCookie } = await this.signIn(user);
1517
+ async signInRedirect(user, location, status = 302, meta = {}) {
1518
+ const { setCookie } = await this.signIn(user, meta);
1672
1519
  return redirectWithCookie(location, setCookie, status);
1673
1520
  }
1674
1521
  async signOutRedirect(request, location, status = 302) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getstrata/bootstrap",
3
- "version": "0.2.65",
3
+ "version": "0.2.68",
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/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",
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/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": {
@@ -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 };