@getstrata/core 0.5.5 → 0.5.7

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.
Files changed (49) hide show
  1. package/README.md +3 -0
  2. package/dist/bootstrap/discoverModules.d.ts +4 -0
  3. package/dist/bootstrap/modules.d.ts +1 -0
  4. package/dist/bootstrap/routeRegistry.d.ts +14 -0
  5. package/dist/core/audit/exportAuditLogs.d.ts +8 -0
  6. package/dist/core/audit/siemFormatter.d.ts +30 -0
  7. package/dist/core/auth/membershipScope.d.ts +16 -0
  8. package/dist/core/auth/membershipService.d.ts +20 -0
  9. package/dist/core/auth/scimAuthMiddleware.d.ts +3 -0
  10. package/dist/core/auth/sessionCookie.d.ts +6 -0
  11. package/dist/core/auth/sessionGuard.d.ts +9 -0
  12. package/dist/core/cache/createCacheStore.d.ts +11 -0
  13. package/dist/core/cache/keys.d.ts +2 -0
  14. package/dist/core/cache/modelCacheTags.d.ts +3 -0
  15. package/dist/core/cache/redisCacheStore.d.ts +23 -0
  16. package/dist/core/cache/simpleCache.d.ts +23 -0
  17. package/dist/core/cache/simpleCacheStore.d.ts +16 -0
  18. package/dist/core/config/envSchema.d.ts +12 -0
  19. package/dist/core/crypto/nonCryptographicHash.d.ts +2 -0
  20. package/dist/core/database/factory.d.ts +6 -0
  21. package/dist/core/http/cookies.d.ts +4 -0
  22. package/dist/core/http/csrfProtection.d.ts +12 -0
  23. package/dist/core/http/scimThrottleMiddleware.d.ts +9 -0
  24. package/dist/core/jobs/dispatchWebhookJob.d.ts +14 -0
  25. package/dist/core/jobs/invalidateCacheTagsJob.d.ts +12 -0
  26. package/dist/core/openapi/generator.d.ts +22 -0
  27. package/dist/core/openapi/validate.d.ts +3 -0
  28. package/dist/core/queue/createAppQueue.d.ts +6 -0
  29. package/dist/core/queue/queueMetrics.d.ts +15 -0
  30. package/dist/core/security/oauthState.d.ts +8 -0
  31. package/dist/core/security/safeFetch.d.ts +8 -0
  32. package/dist/core/security/safeUrl.d.ts +5 -0
  33. package/dist/core/security/scimTenantTokens.d.ts +3 -0
  34. package/dist/core/security/stripeWebhook.d.ts +2 -0
  35. package/dist/core/security/timingSafeCompare.d.ts +2 -0
  36. package/dist/domain/scim.d.ts +9 -0
  37. package/dist/entries/auth/guard.js +20 -20
  38. package/dist/entries/database.js +20 -20
  39. package/dist/entries/http/csrfToken.js +49 -65
  40. package/dist/entries/http/etag.js +6 -4
  41. package/dist/entries/http/webErrorResponse.js +73 -87
  42. package/dist/entries/http.js +80 -90
  43. package/dist/entries/queue/createAppQueue.js +20 -20
  44. package/dist/entries/queue/publicQueue.js +20 -20
  45. package/dist/entries/queue/queueMetrics.js +20 -20
  46. package/dist/entries/view.js +73 -87
  47. package/dist/framework/public-api.d.ts +4 -0
  48. package/dist/index.js +121 -107
  49. package/package.json +61 -58
@@ -1562,26 +1562,26 @@ class Model {
1562
1562
  throw new Error(`${this.constructor.name}.primaryKey() is not implemented.`);
1563
1563
  }
1564
1564
  static primaryKeyField() {
1565
- return resolveModelRepository(this).getTable().primaryKey;
1565
+ return resolveModelRepository(Model).getTable().primaryKey;
1566
1566
  }
1567
1567
  static hydrateAttributes(attributes) {
1568
- const casts = modelStatics(this).$casts ?? {};
1568
+ const casts = modelStatics(Model).$casts ?? {};
1569
1569
  return applyCasts(attributes, casts, "hydrate");
1570
1570
  }
1571
1571
  static dehydrateAttributes(attributes) {
1572
- const casts = modelStatics(this).$casts ?? {};
1572
+ const casts = modelStatics(Model).$casts ?? {};
1573
1573
  return applyCasts(attributes, casts, "dehydrate");
1574
1574
  }
1575
1575
  static fromRecord(record, repository, exists = true) {
1576
- const statics = modelStatics(this);
1576
+ const statics = modelStatics(Model);
1577
1577
  const hydrated = statics.hydrateAttributes(record);
1578
1578
  return new statics(hydrated, repository, exists);
1579
1579
  }
1580
1580
  static boot() {}
1581
1581
  static addGlobalScope(_name, scope) {
1582
- ensureBooted(this);
1583
- const existing = modelGlobalScopes.get(this) ?? [];
1584
- modelGlobalScopes.set(this, [
1582
+ ensureBooted(Model);
1583
+ const existing = modelGlobalScopes.get(Model) ?? [];
1584
+ modelGlobalScopes.set(Model, [
1585
1585
  ...existing,
1586
1586
  scope
1587
1587
  ]);
@@ -1591,17 +1591,17 @@ class Model {
1591
1591
  }
1592
1592
  static query() {
1593
1593
  ensureBooted(this);
1594
- const repository = resolveModelRepository(this);
1594
+ const repository = resolveModelRepository(Model);
1595
1595
  let query = repository.query();
1596
- for (const scope of getGlobalScopes(this)) {
1596
+ for (const scope of getGlobalScopes(Model)) {
1597
1597
  query = scope(query);
1598
1598
  }
1599
1599
  return query;
1600
1600
  }
1601
1601
  static async create(attributes) {
1602
1602
  const statics = modelStatics(this);
1603
- ensureBooted(this);
1604
- const repository = resolveModelRepository(this);
1603
+ ensureBooted(Model);
1604
+ const repository = resolveModelRepository(Model);
1605
1605
  const table = repository.getTable();
1606
1606
  const timestamps = statics.$timestamps ?? true;
1607
1607
  const assignable = filterMassAssignable(statics.$fillable, statics.$guarded, attributes);
@@ -1611,23 +1611,23 @@ class Model {
1611
1611
  return statics.fromRecord(record, repository, true);
1612
1612
  }
1613
1613
  static async find(id) {
1614
- const statics = modelStatics(this);
1615
- const repository = resolveModelRepository(this);
1614
+ const statics = modelStatics(Model);
1615
+ const repository = resolveModelRepository(Model);
1616
1616
  const primaryKey = repository.getTable().primaryKey;
1617
- const record = await Model.query.call(this).where({ [primaryKey]: id }).first();
1617
+ const record = await Model.query.call(Model).where({ [primaryKey]: id }).first();
1618
1618
  return record ? statics.fromRecord(record, repository, true) : null;
1619
1619
  }
1620
1620
  static async findOrFail(id, errorFactory) {
1621
- const model = await Model.find.call(this, id);
1621
+ const model = await Model.find.call(Model, id);
1622
1622
  if (model) {
1623
1623
  return model;
1624
1624
  }
1625
- throw errorFactory?.(id) ?? new NotFoundError(`${this.name} ${String(id)} not found.`);
1625
+ throw errorFactory?.(id) ?? new NotFoundError(`${Model.name} ${String(id)} not found.`);
1626
1626
  }
1627
1627
  static async all(options = {}) {
1628
1628
  const statics = modelStatics(this);
1629
- const repository = resolveModelRepository(this);
1630
- let query = Model.query.call(this);
1629
+ const repository = resolveModelRepository(Model);
1630
+ let query = Model.query.call(Model);
1631
1631
  if (options.orderBy) {
1632
1632
  query = query.orderBy(options.orderBy);
1633
1633
  }
@@ -1639,8 +1639,8 @@ class Model {
1639
1639
  }
1640
1640
  static async firstWhere(where, options = {}) {
1641
1641
  const statics = modelStatics(this);
1642
- const repository = resolveModelRepository(this);
1643
- let query = Model.query.call(this).where(where);
1642
+ const repository = resolveModelRepository(Model);
1643
+ let query = Model.query.call(Model).where(where);
1644
1644
  if (options.orderBy) {
1645
1645
  query = query.orderBy(options.orderBy);
1646
1646
  }
@@ -2802,76 +2802,64 @@ function hashApiToken(token) {
2802
2802
  var tokenServiceToken = CORE_TOKEN_SERVICE_TOKEN;
2803
2803
 
2804
2804
  // ../../src/core/http/csrfToken.ts
2805
- import { createHmac as createHmac4, randomBytes as randomBytes2, timingSafeEqual } from "crypto";
2805
+ import { timingSafeEqual } from "crypto";
2806
+
2807
+ // ../../src/core/http/cookies.ts
2808
+ function readRequestCookie(request, name) {
2809
+ const cookies = request.cookies;
2810
+ if (cookies && typeof cookies.get === "function") {
2811
+ const value = cookies.get(name);
2812
+ if (value) {
2813
+ return value;
2814
+ }
2815
+ }
2816
+ const header = request.headers.get("cookie");
2817
+ if (!header) {
2818
+ return null;
2819
+ }
2820
+ for (const part of header.split(";")) {
2821
+ const idx = part.indexOf("=");
2822
+ if (idx === -1)
2823
+ continue;
2824
+ const cookieName = part.slice(0, idx).trim();
2825
+ if (cookieName !== name)
2826
+ continue;
2827
+ return decodeURIComponent(part.slice(idx + 1).trim());
2828
+ }
2829
+ return null;
2830
+ }
2831
+ function readBunRequestCookie(request, name) {
2832
+ return request.cookies.get(name) ?? readRequestCookie(request, name);
2833
+ }
2834
+
2835
+ // ../../src/core/http/csrfToken.ts
2806
2836
  var CSRF_COOKIE = "workhub_csrf";
2807
2837
  var CSRF_TTL_MS = 60 * 60 * 1000;
2808
2838
  function resolveCsrfSecret() {
2809
2839
  return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || "workhub-dev-csrf-secret";
2810
2840
  }
2811
- function signCsrfToken(token, issuedAt) {
2812
- const payload = `${token}.${issuedAt}`;
2813
- const signature = createHmac4("sha256", resolveCsrfSecret()).update(payload).digest("hex");
2814
- return `${payload}.${signature}`;
2841
+ function csrfVerifyOptions() {
2842
+ return { secret: resolveCsrfSecret(), maxAge: CSRF_TTL_MS };
2815
2843
  }
2816
- function readCsrfCookie(request) {
2817
- const cookieHeader = request.headers.get("cookie");
2818
- if (!cookieHeader) {
2819
- return null;
2820
- }
2821
- for (const part of cookieHeader.split(";")) {
2822
- const [name, ...rest] = part.trim().split("=");
2823
- if (name === CSRF_COOKIE) {
2824
- return decodeURIComponent(rest.join("="));
2825
- }
2826
- }
2827
- return null;
2828
- }
2829
- function parseSignedCsrfValue(cookieValue) {
2830
- const parts = cookieValue.split(".");
2831
- if (parts.length !== 3) {
2832
- return null;
2833
- }
2834
- const [token, issuedAtRaw, cookieSignature] = parts;
2835
- if (!token || !issuedAtRaw || !cookieSignature) {
2836
- return null;
2837
- }
2838
- const issuedAt = Number.parseInt(issuedAtRaw, 10);
2839
- if (!Number.isFinite(issuedAt)) {
2840
- return null;
2841
- }
2842
- if (Date.now() - issuedAt > CSRF_TTL_MS) {
2843
- return null;
2844
- }
2845
- const expectedSignature = signCsrfToken(token, issuedAt).split(".").pop();
2846
- if (!expectedSignature) {
2847
- return null;
2848
- }
2849
- const expectedBuffer = Buffer.from(expectedSignature);
2850
- const actualBuffer = Buffer.from(cookieSignature);
2851
- if (expectedBuffer.length !== actualBuffer.length) {
2852
- return null;
2853
- }
2854
- if (!timingSafeEqual(expectedBuffer, actualBuffer)) {
2855
- return null;
2844
+ function tokensMatch(left, right) {
2845
+ const leftBuffer = Buffer.from(left);
2846
+ const rightBuffer = Buffer.from(right);
2847
+ if (leftBuffer.length !== rightBuffer.length) {
2848
+ return false;
2856
2849
  }
2857
- return { token, issuedAt };
2850
+ return timingSafeEqual(leftBuffer, rightBuffer);
2858
2851
  }
2859
2852
  function createCsrfTokenCookie() {
2860
- const token = randomBytes2(24).toString("hex");
2861
- const issuedAt = Date.now();
2862
- const value = signCsrfToken(token, issuedAt);
2853
+ const token = Bun.CSRF.generate(resolveCsrfSecret(), { expiresIn: CSRF_TTL_MS });
2863
2854
  return {
2864
2855
  token,
2865
- cookie: `${CSRF_COOKIE}=${encodeURIComponent(value)}; Path=/; SameSite=Lax; Max-Age=3600`
2856
+ cookie: `${CSRF_COOKIE}=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Max-Age=${Math.floor(CSRF_TTL_MS / 1000)}`
2866
2857
  };
2867
2858
  }
2868
2859
  function resolveCsrfToken(request) {
2869
- const cookieValue = readCsrfCookie(request);
2870
- if (cookieValue) {
2871
- const parsed = parseSignedCsrfValue(cookieValue);
2872
- if (parsed) {
2873
- return { token: parsed.token };
2874
- }
2860
+ const cookieValue = readRequestCookie(request, CSRF_COOKIE);
2861
+ if (cookieValue && Bun.CSRF.verify(cookieValue, csrfVerifyOptions())) {
2862
+ return { token: cookieValue };
2875
2863
  }
2876
2864
  return createCsrfTokenCookie();
2877
2865
  }
@@ -2894,6 +2882,10 @@ async function readSubmittedCsrfTokenFromBody(request) {
2894
2882
  if (typeof field === "string" && field.trim().length > 0) {
2895
2883
  return field.trim();
2896
2884
  }
2885
+ const legacyField = formData.get("_csrf");
2886
+ if (typeof legacyField === "string" && legacyField.trim().length > 0) {
2887
+ return legacyField.trim();
2888
+ }
2897
2889
  }
2898
2890
  return null;
2899
2891
  }
@@ -2901,20 +2893,14 @@ function verifyCsrfToken(request, submittedToken) {
2901
2893
  if (!submittedToken) {
2902
2894
  return false;
2903
2895
  }
2904
- const cookieValue = readCsrfCookie(request);
2896
+ const cookieValue = readRequestCookie(request, CSRF_COOKIE);
2905
2897
  if (!cookieValue) {
2906
2898
  return false;
2907
2899
  }
2908
- const parsed = parseSignedCsrfValue(cookieValue);
2909
- if (!parsed) {
2910
- return false;
2911
- }
2912
- const submittedBuffer = Buffer.from(submittedToken);
2913
- const expectedBuffer = Buffer.from(parsed.token);
2914
- if (submittedBuffer.length !== expectedBuffer.length) {
2900
+ if (!tokensMatch(submittedToken, cookieValue)) {
2915
2901
  return false;
2916
2902
  }
2917
- return timingSafeEqual(submittedBuffer, expectedBuffer);
2903
+ return Bun.CSRF.verify(submittedToken, csrfVerifyOptions());
2918
2904
  }
2919
2905
  function resolveCsrfTokenForRequest(request) {
2920
2906
  const metaToken = currentRequestMeta().csrfToken;
@@ -2925,14 +2911,14 @@ function resolveCsrfTokenForRequest(request) {
2925
2911
  }
2926
2912
 
2927
2913
  // ../../src/core/http/flashSession.ts
2928
- import { createHmac as createHmac5, timingSafeEqual as timingSafeEqual2 } from "crypto";
2914
+ import { createHmac as createHmac4, timingSafeEqual as timingSafeEqual2 } from "crypto";
2929
2915
  var FLASH_COOKIE = "workhub_flash";
2930
2916
  var FLASH_TTL_MS = 60 * 1000;
2931
2917
  function resolveFlashSecret() {
2932
2918
  return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || "workhub-dev-flash-secret";
2933
2919
  }
2934
2920
  function signFlashPayload(payload, issuedAt) {
2935
- const signature = createHmac5("sha256", resolveFlashSecret()).update(`${payload}.${issuedAt}`).digest("hex");
2921
+ const signature = createHmac4("sha256", resolveFlashSecret()).update(`${payload}.${issuedAt}`).digest("hex");
2936
2922
  return `${payload}.${issuedAt}.${signature}`;
2937
2923
  }
2938
2924
  function readFlashCookie(request) {
@@ -14,6 +14,7 @@ export { CACHE_TAGS } from "../core/cache/tags.ts";
14
14
  export type { DatabaseConnection } from "../core/database/baseRepository.ts";
15
15
  export { default as BaseRepository } from "../core/database/baseRepository.ts";
16
16
  export { bindDatabaseConnection } from "../core/database/bindConnection.ts";
17
+ export { createDatabaseConnection } from "../core/database/connection.ts";
17
18
  export { getActiveDatabaseConnection, runWithDatabaseConnection, } from "../core/database/connectionContext.ts";
18
19
  export { withMigrationLock } from "../core/database/migrations/advisoryLock.ts";
19
20
  export { freshDatabase, getMigrationStatus, loadMigrationsFromDirectory, migrateDatabase, rollbackDatabase, } from "../core/database/migrations/runner.ts";
@@ -36,7 +37,10 @@ export { BadRequestError, ConflictError, ForbiddenError, NotFoundError, Precondi
36
37
  export { EventBus } from "../core/events/eventBus.ts";
37
38
  export { auth, cache, config, events, log, mail, policyGate, queue, storage, } from "../core/facades/index.ts";
38
39
  export { createBodySizeLimitMiddleware } from "../core/http/bodySizeLimitMiddleware.ts";
40
+ export { readBunRequestCookie, readRequestCookie } from "../core/http/cookies.ts";
39
41
  export { createCsrfMiddleware } from "../core/http/csrfMiddleware.ts";
42
+ export { createCsrfProtection } from "../core/http/csrfProtection.ts";
43
+ export { createCsrfTokenCookie, readSubmittedCsrfToken, readSubmittedCsrfTokenFromBody, resolveCsrfToken, resolveCsrfTokenForRequest, verifyCsrfToken, } from "../core/http/csrfToken.ts";
40
44
  export { assertIfMatch, etagFromResource, isEtagEnabled, } from "../core/http/etag.ts";
41
45
  export { FormRequest } from "../core/http/formRequest.ts";
42
46
  export { applyMiddlewareToRoutes, composeMiddleware, createAuthMiddleware, createAuthorizeMiddleware, createdResponse, createRequireAuthMiddleware, jsonResponse, noContentResponse, paginatedResponse, parsePaginationQuery, securedBindRouteModel, securedBindRouteModelByKey, withErrorHandling, withMiddleware, } from "../core/http/index.ts";