@getstrata/bootstrap 0.2.66 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -41,7 +41,7 @@ import { createRequireGlobalAdminMiddleware } from "@getstrata/core/http/require
41
41
  import { createRequirePasswordConfirmMiddleware } from "@getstrata/core/http/requirePasswordConfirmMiddleware";
42
42
  import { createRequireVerifiedMiddleware } from "@getstrata/core/http/requireVerifiedMiddleware";
43
43
  import { createRequireWebAuthMiddleware } from "@getstrata/core/http/requireWebAuthMiddleware";
44
- import { withErrorHandling } from "@getstrata/core/http/response";
44
+ import { withErrorHandling, withJsonErrorHandling } from "@getstrata/core/http/response";
45
45
  import { withMiddleware } from "@getstrata/core/http/routeMiddleware";
46
46
  import { createSecurityHeadersMiddleware } from "@getstrata/core/http/securityHeadersMiddleware";
47
47
  import { createValidateSignatureMiddleware } from "@getstrata/core/http/signedUrl";
@@ -188,7 +188,7 @@ class HttpKernel {
188
188
  return withMiddleware(...middleware)(handler);
189
189
  }
190
190
  wrapApi(handler) {
191
- return this.wrap(["api", "authenticated"], handler);
191
+ return withJsonErrorHandling(this.wrap(["api", "authenticated"], handler));
192
192
  }
193
193
  wrapWeb(handler) {
194
194
  return withErrorHandling(this.wrap("web", handler));
@@ -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();
@@ -522,17 +527,19 @@ import {
522
527
  } from "@getstrata/core/contracts/di";
523
528
 
524
529
  // ../../src/bootstrap/providers/auth.ts
530
+ import { BasicAuthGuard } from "@getstrata/core/auth/basicAuthGuard";
525
531
  import {
526
532
  AuthManager,
527
533
  CompositeGuard,
528
534
  DatabaseTokenGuard,
529
535
  GuestGuard
530
536
  } from "@getstrata/core/auth/guard";
537
+ import { JwtGuard } from "@getstrata/core/auth/jwtGuard";
531
538
  import { SessionGuard } from "@getstrata/core/auth/sessionGuard";
532
539
 
533
540
  // ../../src/config/auth.ts
534
541
  var authConfig = {
535
- allowDevHeaders: (process.env.AUTH_DEV_HEADERS ?? "true") !== "false",
542
+ allowDevHeaders: process.env.AUTH_DEV_HEADERS === "true",
536
543
  tokenDefaultAbilities: ["*"]
537
544
  };
538
545
 
@@ -541,11 +548,22 @@ var authProvider = {
541
548
  name: "core.auth",
542
549
  register({ container, config }) {
543
550
  config.set("auth.allowDevHeaders", authConfig.allowDevHeaders);
544
- const guards = [new DatabaseTokenGuard(container), new SessionGuard(container)];
551
+ const apiGuard = new DatabaseTokenGuard(container);
552
+ const sessionGuard = new SessionGuard(container);
553
+ const jwtGuard = new JwtGuard;
554
+ const basicGuard = new BasicAuthGuard(container);
555
+ const guards = [apiGuard, jwtGuard, basicGuard, sessionGuard];
545
556
  if (authConfig.allowDevHeaders) {
546
557
  guards.push(new GuestGuard);
547
558
  }
548
- container.set(CORE_AUTH_TOKEN, new AuthManager(new CompositeGuard(guards)));
559
+ const auth = new AuthManager(new CompositeGuard(guards));
560
+ auth.registerGuard("api", apiGuard);
561
+ auth.registerGuard("access_token", apiGuard);
562
+ auth.registerGuard("jwt", jwtGuard);
563
+ auth.registerGuard("basic", basicGuard);
564
+ auth.registerGuard("web", sessionGuard);
565
+ auth.registerGuard("session", sessionGuard);
566
+ container.set(CORE_AUTH_TOKEN, auth);
549
567
  }
550
568
  };
551
569
  var auth_default = authProvider;
@@ -606,8 +624,12 @@ var queueConfig = {
606
624
  };
607
625
  // ../../src/bootstrap/env.ts
608
626
  import { defineEnvSchema } from "@getstrata/core/config/envSchema";
627
+ import { DEFAULT_SPA_PREFIX, FRONTEND_MODE_PATTERN } from "@getstrata/core/runtime/frontendMode";
609
628
  var appEnvSchema = defineEnvSchema({
610
- DATABASE_URL: { required: true, pattern: /^postgres(ql)?:\/\// },
629
+ DATABASE_URL: {
630
+ required: true,
631
+ pattern: /^(postgres(ql)?|mysql|sqlite):\/\//i
632
+ },
611
633
  PORT: {
612
634
  integer: true,
613
635
  minimum: 1,
@@ -635,9 +657,21 @@ var appEnvSchema = defineEnvSchema({
635
657
  pattern: /^(sync|async|redis)$/
636
658
  },
637
659
  AUTH_DEV_HEADERS: {
638
- default: "true",
660
+ default: "false",
639
661
  pattern: /^(true|false|0|1)$/
640
662
  },
663
+ DB_CONNECTION: {
664
+ default: "",
665
+ pattern: /^(pgsql|postgres|postgresql|mysql|mariadb|sqlite)?$/i
666
+ },
667
+ AUTH_DEFAULT_GUARD: {
668
+ default: "web"
669
+ },
670
+ JWT_TTL_SECONDS: {
671
+ integer: true,
672
+ minimum: 60,
673
+ default: "3600"
674
+ },
641
675
  APP_ENV: {
642
676
  default: "local"
643
677
  },
@@ -647,6 +681,13 @@ var appEnvSchema = defineEnvSchema({
647
681
  APP_URL: {
648
682
  default: "http://localhost:3000"
649
683
  },
684
+ FRONTEND_MODE: {
685
+ default: "api",
686
+ pattern: FRONTEND_MODE_PATTERN
687
+ },
688
+ SPA_PREFIX: {
689
+ default: DEFAULT_SPA_PREFIX
690
+ },
650
691
  API_PREFIX: {
651
692
  default: "/api/v1"
652
693
  },
@@ -871,7 +912,6 @@ var storageProvider = {
871
912
  var storage_default = storageProvider;
872
913
 
873
914
  // ../../src/bootstrap/providers/view.ts
874
- import { resolveMembershipLookup } from "@getstrata/core/auth/membershipContext";
875
915
  import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
876
916
  import { appDisplayName } from "@getstrata/core/runtime/appKeyPrefix";
877
917
  import { isViewsEnabled as isViewsEnabled2 } from "@getstrata/core/runtime/frontendMode";
@@ -883,148 +923,6 @@ import {
883
923
  errorTemplateName,
884
924
  resolveWebLayoutData
885
925
  } 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
926
  var CORE_VIEW_TOKEN = "core.view";
1029
927
  var VIEW_DIRECTORY_CONFIG_KEY = "view.directory";
1030
928
  var viewProvider = {
@@ -1038,23 +936,11 @@ var viewProvider = {
1038
936
  const engine = new EtaViewEngine(viewsDirectory, (request) => resolveWebLayoutData(container, request ?? currentRequestMeta().request));
1039
937
  container.set(CORE_VIEW_TOKEN, engine);
1040
938
  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
- }
939
+ extra: async () => ({
940
+ appName: appDisplayName(),
941
+ currentOrganization: null,
942
+ organizations: []
943
+ })
1058
944
  });
1059
945
  configureWebErrorView({
1060
946
  render: async (input) => engine.render(errorTemplateName(input.status), {
@@ -1118,9 +1004,10 @@ import { notFoundHtmlResponse } from "@getstrata/core/view";
1118
1004
  function registerRoute(method, path, middleware) {
1119
1005
  routeRegistry.register({ method, path, middleware });
1120
1006
  }
1121
- function createWebRoutes(dependencies) {
1007
+ function createWebRoutes(dependencies, options = {}) {
1122
1008
  const wrappedRoutes = buildWebModuleRoutes(dependencies, {
1123
- clearRegistry: false
1009
+ clearRegistry: false,
1010
+ modules: options.modules
1124
1011
  });
1125
1012
  registerRoute("GET", "/", ["global", "web"]);
1126
1013
  wrappedRoutes["/assets/*"] = async (request) => {
@@ -1136,12 +1023,12 @@ function createWebRoutes(dependencies) {
1136
1023
  registerRoute("GET", "/assets/*", ["global", "web"]);
1137
1024
  return wrappedRoutes;
1138
1025
  }
1139
- function mergeWebRoutes(dependencies, routes) {
1026
+ function mergeWebRoutes(dependencies, routes, options = {}) {
1140
1027
  if (!isViewsEnabled3()) {
1141
1028
  return routes;
1142
1029
  }
1143
1030
  return {
1144
- ...createWebRoutes(dependencies),
1031
+ ...createWebRoutes(dependencies, options),
1145
1032
  ...routes
1146
1033
  };
1147
1034
  }
@@ -1252,15 +1139,21 @@ import {
1252
1139
  // ../../src/bootstrap/membershipService.ts
1253
1140
  import { resolveMembershipService } from "@getstrata/core/auth/membershipService";
1254
1141
  // ../../src/bootstrap/secretsGuard.ts
1255
- var PUBLISHED_TEST_ADMIN_API_TOKEN = "workhub-admin-test-token";
1256
- var PUBLISHED_TEST_MEMBER_API_TOKEN = "workhub-member-test-token";
1257
- var PUBLISHED_TEST_SCIM_BEARER_TOKEN = "workhub-scim-test-token";
1142
+ import { isViewsMode, parseFrontendMode } from "@getstrata/core/runtime/frontendMode";
1143
+ var PUBLISHED_TEST_ADMIN_API_TOKEN = "strata-admin-test-token";
1144
+ var PUBLISHED_TEST_MEMBER_API_TOKEN = "strata-member-test-token";
1145
+ var PUBLISHED_TEST_SCIM_BEARER_TOKEN = "strata-scim-test-token";
1258
1146
  var MIN_SESSION_SECRET_LENGTH = 32;
1259
1147
  var PUBLISHED_TEST_TOKENS = new Set([
1260
1148
  PUBLISHED_TEST_ADMIN_API_TOKEN,
1261
- PUBLISHED_TEST_MEMBER_API_TOKEN
1149
+ PUBLISHED_TEST_MEMBER_API_TOKEN,
1150
+ "workhub-admin-test-token",
1151
+ "workhub-member-test-token"
1152
+ ]);
1153
+ var PUBLISHED_TEST_SCIM_TOKENS = new Set([
1154
+ PUBLISHED_TEST_SCIM_BEARER_TOKEN,
1155
+ "workhub-scim-test-token"
1262
1156
  ]);
1263
- var PUBLISHED_TEST_SCIM_TOKENS = new Set([PUBLISHED_TEST_SCIM_BEARER_TOKEN]);
1264
1157
  function isEnabled(value, defaultEnabled) {
1265
1158
  if (value === undefined) {
1266
1159
  return defaultEnabled;
@@ -1284,7 +1177,7 @@ function assertAuthDevHeadersDisabled(env) {
1284
1177
  function assertSessionSecret(env) {
1285
1178
  const secret = env.SESSION_SECRET?.trim() ?? "";
1286
1179
  if (secret.length < MIN_SESSION_SECRET_LENGTH) {
1287
- throw new Error("Production startup blocked: set SESSION_SECRET when FRONTEND_MODE=server-htmx (32+ characters).");
1180
+ throw new Error("Production startup blocked: set SESSION_SECRET when FRONTEND_MODE=server-htmx or hybrid (32+ characters).");
1288
1181
  }
1289
1182
  }
1290
1183
  function assertPublishedTestTokensRotated(env) {
@@ -1346,8 +1239,7 @@ function assertProductionSecrets(env = process.env) {
1346
1239
  assertTokenAuthProductionSecrets(env);
1347
1240
  }
1348
1241
  assertFeatureProductionSecrets(env);
1349
- const frontendMode = (env.FRONTEND_MODE ?? "api").trim();
1350
- if (frontendMode === "server-htmx") {
1242
+ if (isViewsMode(parseFrontendMode(env.FRONTEND_MODE))) {
1351
1243
  assertSessionSecret(env);
1352
1244
  }
1353
1245
  }
@@ -1384,6 +1276,7 @@ async function parseFormBody(request) {
1384
1276
  return { fields, files };
1385
1277
  }
1386
1278
  // ../../src/bootstrap/web/routing.ts
1279
+ import { requestPrefersJson } from "@getstrata/core/http/contentNegotiation";
1387
1280
  import { withErrorHandling as withErrorHandling2 } from "@getstrata/core/http/response";
1388
1281
  function routeParams(request) {
1389
1282
  const normalized = {};
@@ -1419,7 +1312,7 @@ function wrapWebThrottle(kernel, scope, handler, onThrottled) {
1419
1312
  const throttled = scope === "login" ? kernel.wrapLogin(handler) : kernel.wrapRegister(handler);
1420
1313
  return async (request) => {
1421
1314
  const response = await throttled(request);
1422
- if (response.status === 429) {
1315
+ if (response.status === 429 && !requestPrefersJson(request)) {
1423
1316
  return onThrottled(request);
1424
1317
  }
1425
1318
  return response;
@@ -1498,11 +1391,19 @@ function createWebServer(options) {
1498
1391
  });
1499
1392
  }
1500
1393
  // ../../src/bootstrap/web/session.ts
1501
- import { createHash, randomBytes } from "crypto";
1394
+ import { createHmac, randomBytes } from "crypto";
1502
1395
  import { AuthManager as AuthManager2 } from "@getstrata/core/auth/guard";
1503
1396
  import { getBoundDatabaseConnection as getBoundDatabaseConnection2 } from "@getstrata/core/database/boundConnection";
1504
1397
  import { getDefaultDatabasePool as getDefaultDatabasePool2 } from "@getstrata/core/database/defaultConnection";
1398
+ import { currentSqlDialect } from "@getstrata/core/database/dialect";
1505
1399
  import { readRequestCookie } from "@getstrata/core/http/cookies";
1400
+ import { timingSafeCompareString } from "@getstrata/core/security/timingSafeCompare";
1401
+ function sqlPlaceholder(index) {
1402
+ return currentSqlDialect().placeholder(index);
1403
+ }
1404
+ function sqlNow() {
1405
+ return currentSqlDialect().nowExpression();
1406
+ }
1506
1407
  function isSqlClient(value) {
1507
1408
  return typeof value.unsafe === "function";
1508
1409
  }
@@ -1525,22 +1426,34 @@ function defaultMapSessionUser(user) {
1525
1426
  role: user.is_admin ? "admin" : "member"
1526
1427
  };
1527
1428
  }
1429
+ function sessionDisplayName(row, email) {
1430
+ if (typeof row.name === "string" && row.name.trim() !== "") {
1431
+ return row.name;
1432
+ }
1433
+ const first = typeof row.first_name === "string" ? row.first_name.trim() : "";
1434
+ const last = typeof row.last_name === "string" ? row.last_name.trim() : "";
1435
+ const composed = `${first} ${last}`.trim();
1436
+ return composed || email;
1437
+ }
1438
+ function mapSessionUserRow(row) {
1439
+ const email = typeof row.email === "string" ? row.email : "";
1440
+ return {
1441
+ id: Number(row.user_id ?? row.id),
1442
+ name: sessionDisplayName(row, email),
1443
+ email,
1444
+ learn_subscriber: Boolean(row.learn_subscriber),
1445
+ is_admin: Boolean(row.is_admin)
1446
+ };
1447
+ }
1528
1448
  async function defaultLoadSessionUser(sql, sessionId) {
1529
- const rows = await sql.unsafe(`SELECT s.id, s.user_id, s.expires_at, u.name, u.email, u.learn_subscriber,
1530
- COALESCE(u.is_admin, false) AS is_admin
1449
+ const rows = await sql.unsafe(`SELECT s.user_id, s.expires_at, u.*
1531
1450
  FROM sessions s
1532
1451
  INNER JOIN users u ON u.id = s.user_id
1533
- WHERE s.id = $1 AND s.expires_at > NOW()`, [sessionId]);
1452
+ WHERE s.id = ${sqlPlaceholder(1)} AND s.expires_at > ${sqlNow()}`, [sessionId]);
1534
1453
  const row = rows[0];
1535
1454
  if (!row)
1536
1455
  return null;
1537
- return {
1538
- id: row.user_id,
1539
- name: row.name,
1540
- email: row.email,
1541
- learn_subscriber: row.learn_subscriber,
1542
- is_admin: row.is_admin
1543
- };
1456
+ return mapSessionUserRow(row);
1544
1457
  }
1545
1458
  function redirectWithCookie(location, setCookie, status) {
1546
1459
  return new Response(null, {
@@ -1578,7 +1491,7 @@ class CookieSessionStore {
1578
1491
  if (!raw)
1579
1492
  return null;
1580
1493
  const [sessionId, signature] = raw.split(".");
1581
- if (!sessionId || !signature || signature !== this.sign(sessionId)) {
1494
+ if (!sessionId || !signature || !timingSafeCompareString(signature, this.sign(sessionId))) {
1582
1495
  return null;
1583
1496
  }
1584
1497
  return sessionId;
@@ -1592,18 +1505,28 @@ class CookieSessionStore {
1592
1505
  sql() {
1593
1506
  return resolveSql(this.sqlSource);
1594
1507
  }
1595
- async create(user) {
1508
+ async create(user, meta = {}) {
1596
1509
  const id = randomBytes(32).toString("hex");
1597
1510
  const expires = new Date(Date.now() + this.maxAgeSeconds * 1000);
1598
- await this.sql().unsafe(`INSERT INTO sessions (id, user_id, expires_at) VALUES ($1, $2, $3)`, [
1599
- id,
1600
- user.id,
1601
- expires
1602
- ]);
1511
+ await this.sql().unsafe(`INSERT INTO sessions (id, user_id, expires_at, user_agent, ip_address, last_active_at)
1512
+ VALUES (${sqlPlaceholder(1)}, ${sqlPlaceholder(2)}, ${sqlPlaceholder(3)}, ${sqlPlaceholder(4)}, ${sqlPlaceholder(5)}, ${sqlNow()})`, [id, user.id, expires, meta.userAgent ?? null, meta.ipAddress ?? null]);
1603
1513
  return id;
1604
1514
  }
1605
1515
  async destroy(sessionId) {
1606
- await this.sql().unsafe(`DELETE FROM sessions WHERE id = $1`, [sessionId]);
1516
+ await this.sql().unsafe(`DELETE FROM sessions WHERE id = ${sqlPlaceholder(1)}`, [sessionId]);
1517
+ }
1518
+ async destroyOtherSessions(userId, keepSessionId) {
1519
+ await this.sql().unsafe(`DELETE FROM sessions WHERE user_id = ${sqlPlaceholder(1)} AND id <> ${sqlPlaceholder(2)}`, [userId, keepSessionId]);
1520
+ }
1521
+ async listForUser(userId) {
1522
+ const dialect = currentSqlDialect();
1523
+ return this.sql().unsafe(`SELECT id, user_id, user_agent, ip_address, last_active_at, expires_at
1524
+ FROM sessions
1525
+ WHERE user_id = ${dialect.placeholder(1)} AND expires_at > ${dialect.nowExpression()}
1526
+ ORDER BY last_active_at DESC${dialect.nullsLastSuffix()}, expires_at DESC`, [userId]);
1527
+ }
1528
+ async touch(sessionId) {
1529
+ await this.sql().unsafe(`UPDATE sessions SET last_active_at = ${sqlNow()} WHERE id = ${sqlPlaceholder(1)}`, [sessionId]);
1607
1530
  }
1608
1531
  async read(request) {
1609
1532
  const sessionId = this.sessionIdFromRequest(request);
@@ -1612,7 +1535,7 @@ class CookieSessionStore {
1612
1535
  return this.loadSessionUser(this.sql(), sessionId);
1613
1536
  }
1614
1537
  sign(value) {
1615
- return createHash("sha256").update(`${value}.${this.secret}`).digest("hex").slice(0, 32);
1538
+ return createHmac("sha256", this.secret).update(value).digest("hex").slice(0, 32);
1616
1539
  }
1617
1540
  }
1618
1541
 
@@ -1635,11 +1558,14 @@ class CookieSessionGuard {
1635
1558
  class CookieSessionAuthManager extends AuthManager2 {
1636
1559
  store;
1637
1560
  constructor(store, mapUser = defaultMapSessionUser) {
1638
- super(new CookieSessionGuard(store, mapUser));
1561
+ const guard = new CookieSessionGuard(store, mapUser);
1562
+ super(guard);
1639
1563
  this.store = store;
1564
+ this.registerGuard("web", guard);
1565
+ this.registerGuard("session", guard);
1640
1566
  }
1641
- async signIn(user) {
1642
- const sessionId = await this.store.create(user);
1567
+ async signIn(user, meta = {}) {
1568
+ const sessionId = await this.store.create(user, meta);
1643
1569
  return { sessionId, setCookie: this.store.cookieHeader(user, sessionId) };
1644
1570
  }
1645
1571
  async signOut(request) {
@@ -1649,8 +1575,8 @@ class CookieSessionAuthManager extends AuthManager2 {
1649
1575
  }
1650
1576
  return { setCookie: this.store.clearCookieHeader() };
1651
1577
  }
1652
- async signInRedirect(user, location, status = 302) {
1653
- const { setCookie } = await this.signIn(user);
1578
+ async signInRedirect(user, location, status = 302, meta = {}) {
1579
+ const { setCookie } = await this.signIn(user, meta);
1654
1580
  return redirectWithCookie(location, setCookie, status);
1655
1581
  }
1656
1582
  async signOutRedirect(request, location, status = 302) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@getstrata/bootstrap",
3
- "version": "0.2.66",
4
- "description": "Strata application bootstrap HttpKernel, DI, web session helpers",
3
+ "version": "0.4.2",
4
+ "description": "Strata application bootstrap: HttpKernel, DI, web session helpers",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -170,17 +170,17 @@
170
170
  ],
171
171
  "scripts": {
172
172
  "build": "bun run build:bundle && bun run build:shims && bun run build:subpaths && bun run build:types",
173
- "build:bundle": "bun build index.ts --outdir dist --target bun --external bun --external eta --external @getstrata/core",
173
+ "build:bundle": "bun build index.ts --outdir dist --target bun --external bun --external eta --external mysql2 --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/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 mysql2 --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/jwt --external @getstrata/core/auth/jwtGuard --external @getstrata/core/auth/basicAuthGuard --external @getstrata/core/auth/tokenAbilityChecker --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/connectionContext --external @getstrata/core/database/defaultConnection --external @getstrata/core/database/dialect --external @getstrata/core/database/mysqlConnection --external @getstrata/core/database/namedConnections --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/sqliteConnection --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/statelessAuth --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": {
180
180
  "access": "public"
181
181
  },
182
182
  "peerDependencies": {
183
- "@getstrata/core": "^0.5.75",
183
+ "@getstrata/core": "^0.7.2",
184
184
  "typescript": "^5.9.0"
185
185
  }
186
186
  }
@@ -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 };