@getstrata/core 0.5.5 → 0.5.6

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
@@ -1487,6 +1487,14 @@ var baseRepository_default = BaseRepository;
1487
1487
  function bindDatabaseConnection2(connection) {
1488
1488
  bindDatabaseConnection(connection);
1489
1489
  }
1490
+ // ../../src/core/database/connection.ts
1491
+ function createDatabaseConnection2(source) {
1492
+ return {
1493
+ async unsafe(query, params = []) {
1494
+ return await source.unsafe(query, params);
1495
+ }
1496
+ };
1497
+ }
1490
1498
  // ../../src/core/database/migrations/advisoryLock.ts
1491
1499
  var MIGRATION_LOCK_KEY = 42424242;
1492
1500
  async function withMigrationLock(db2, callback, lockKey = MIGRATION_LOCK_KEY) {
@@ -1739,26 +1747,26 @@ class Model {
1739
1747
  throw new Error(`${this.constructor.name}.primaryKey() is not implemented.`);
1740
1748
  }
1741
1749
  static primaryKeyField() {
1742
- return resolveModelRepository(this).getTable().primaryKey;
1750
+ return resolveModelRepository(Model).getTable().primaryKey;
1743
1751
  }
1744
1752
  static hydrateAttributes(attributes) {
1745
- const casts = modelStatics(this).$casts ?? {};
1753
+ const casts = modelStatics(Model).$casts ?? {};
1746
1754
  return applyCasts(attributes, casts, "hydrate");
1747
1755
  }
1748
1756
  static dehydrateAttributes(attributes) {
1749
- const casts = modelStatics(this).$casts ?? {};
1757
+ const casts = modelStatics(Model).$casts ?? {};
1750
1758
  return applyCasts(attributes, casts, "dehydrate");
1751
1759
  }
1752
1760
  static fromRecord(record, repository, exists = true) {
1753
- const statics = modelStatics(this);
1761
+ const statics = modelStatics(Model);
1754
1762
  const hydrated = statics.hydrateAttributes(record);
1755
1763
  return new statics(hydrated, repository, exists);
1756
1764
  }
1757
1765
  static boot() {}
1758
1766
  static addGlobalScope(_name, scope) {
1759
- ensureBooted(this);
1760
- const existing = modelGlobalScopes.get(this) ?? [];
1761
- modelGlobalScopes.set(this, [
1767
+ ensureBooted(Model);
1768
+ const existing = modelGlobalScopes.get(Model) ?? [];
1769
+ modelGlobalScopes.set(Model, [
1762
1770
  ...existing,
1763
1771
  scope
1764
1772
  ]);
@@ -1768,17 +1776,17 @@ class Model {
1768
1776
  }
1769
1777
  static query() {
1770
1778
  ensureBooted(this);
1771
- const repository = resolveModelRepository(this);
1779
+ const repository = resolveModelRepository(Model);
1772
1780
  let query = repository.query();
1773
- for (const scope of getGlobalScopes(this)) {
1781
+ for (const scope of getGlobalScopes(Model)) {
1774
1782
  query = scope(query);
1775
1783
  }
1776
1784
  return query;
1777
1785
  }
1778
1786
  static async create(attributes) {
1779
1787
  const statics = modelStatics(this);
1780
- ensureBooted(this);
1781
- const repository = resolveModelRepository(this);
1788
+ ensureBooted(Model);
1789
+ const repository = resolveModelRepository(Model);
1782
1790
  const table = repository.getTable();
1783
1791
  const timestamps = statics.$timestamps ?? true;
1784
1792
  const assignable = filterMassAssignable(statics.$fillable, statics.$guarded, attributes);
@@ -1788,23 +1796,23 @@ class Model {
1788
1796
  return statics.fromRecord(record, repository, true);
1789
1797
  }
1790
1798
  static async find(id) {
1791
- const statics = modelStatics(this);
1792
- const repository = resolveModelRepository(this);
1799
+ const statics = modelStatics(Model);
1800
+ const repository = resolveModelRepository(Model);
1793
1801
  const primaryKey = repository.getTable().primaryKey;
1794
- const record = await Model.query.call(this).where({ [primaryKey]: id }).first();
1802
+ const record = await Model.query.call(Model).where({ [primaryKey]: id }).first();
1795
1803
  return record ? statics.fromRecord(record, repository, true) : null;
1796
1804
  }
1797
1805
  static async findOrFail(id, errorFactory) {
1798
- const model = await Model.find.call(this, id);
1806
+ const model = await Model.find.call(Model, id);
1799
1807
  if (model) {
1800
1808
  return model;
1801
1809
  }
1802
- throw errorFactory?.(id) ?? new NotFoundError(`${this.name} ${String(id)} not found.`);
1810
+ throw errorFactory?.(id) ?? new NotFoundError(`${Model.name} ${String(id)} not found.`);
1803
1811
  }
1804
1812
  static async all(options = {}) {
1805
1813
  const statics = modelStatics(this);
1806
- const repository = resolveModelRepository(this);
1807
- let query = Model.query.call(this);
1814
+ const repository = resolveModelRepository(Model);
1815
+ let query = Model.query.call(Model);
1808
1816
  if (options.orderBy) {
1809
1817
  query = query.orderBy(options.orderBy);
1810
1818
  }
@@ -1816,8 +1824,8 @@ class Model {
1816
1824
  }
1817
1825
  static async firstWhere(where, options = {}) {
1818
1826
  const statics = modelStatics(this);
1819
- const repository = resolveModelRepository(this);
1820
- let query = Model.query.call(this).where(where);
1827
+ const repository = resolveModelRepository(Model);
1828
+ let query = Model.query.call(Model).where(where);
1821
1829
  if (options.orderBy) {
1822
1830
  query = query.orderBy(options.orderBy);
1823
1831
  }
@@ -2564,15 +2572,6 @@ async function runSeedersFromDirectory(directory, db2, options) {
2564
2572
  function defineTable(definition) {
2565
2573
  return definition;
2566
2574
  }
2567
- // ../../src/core/database/connection.ts
2568
- function createDatabaseConnection2(source) {
2569
- return {
2570
- async unsafe(query, params = []) {
2571
- return await source.unsafe(query, params);
2572
- }
2573
- };
2574
- }
2575
-
2576
2575
  // ../../src/core/database/transaction.ts
2577
2576
  async function runInTransaction(operation) {
2578
2577
  return await connection_default.begin(async (transaction) => {
@@ -2927,8 +2926,35 @@ function createBodySizeLimitMiddleware(maxBytes = resolveMaxBodyBytes()) {
2927
2926
  return await next();
2928
2927
  };
2929
2928
  }
2929
+ // ../../src/core/http/cookies.ts
2930
+ function readRequestCookie(request, name) {
2931
+ const cookies = request.cookies;
2932
+ if (cookies && typeof cookies.get === "function") {
2933
+ const value = cookies.get(name);
2934
+ if (value) {
2935
+ return value;
2936
+ }
2937
+ }
2938
+ const header = request.headers.get("cookie");
2939
+ if (!header) {
2940
+ return null;
2941
+ }
2942
+ for (const part of header.split(";")) {
2943
+ const idx = part.indexOf("=");
2944
+ if (idx === -1)
2945
+ continue;
2946
+ const cookieName = part.slice(0, idx).trim();
2947
+ if (cookieName !== name)
2948
+ continue;
2949
+ return decodeURIComponent(part.slice(idx + 1).trim());
2950
+ }
2951
+ return null;
2952
+ }
2953
+ function readBunRequestCookie(request, name) {
2954
+ return request.cookies.get(name) ?? readRequestCookie(request, name);
2955
+ }
2930
2956
  // ../../src/core/http/csrfToken.ts
2931
- import { createHmac, randomBytes, timingSafeEqual } from "crypto";
2957
+ import { timingSafeEqual } from "crypto";
2932
2958
 
2933
2959
  // ../../src/core/http/requestMetaContext.ts
2934
2960
  import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
@@ -2949,70 +2975,28 @@ var CSRF_TTL_MS = 60 * 60 * 1000;
2949
2975
  function resolveCsrfSecret() {
2950
2976
  return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || "workhub-dev-csrf-secret";
2951
2977
  }
2952
- function signCsrfToken(token, issuedAt) {
2953
- const payload = `${token}.${issuedAt}`;
2954
- const signature = createHmac("sha256", resolveCsrfSecret()).update(payload).digest("hex");
2955
- return `${payload}.${signature}`;
2978
+ function csrfVerifyOptions() {
2979
+ return { secret: resolveCsrfSecret(), maxAge: CSRF_TTL_MS };
2956
2980
  }
2957
- function readCsrfCookie(request) {
2958
- const cookieHeader = request.headers.get("cookie");
2959
- if (!cookieHeader) {
2960
- return null;
2961
- }
2962
- for (const part of cookieHeader.split(";")) {
2963
- const [name, ...rest] = part.trim().split("=");
2964
- if (name === CSRF_COOKIE) {
2965
- return decodeURIComponent(rest.join("="));
2966
- }
2967
- }
2968
- return null;
2969
- }
2970
- function parseSignedCsrfValue(cookieValue) {
2971
- const parts = cookieValue.split(".");
2972
- if (parts.length !== 3) {
2973
- return null;
2974
- }
2975
- const [token, issuedAtRaw, cookieSignature] = parts;
2976
- if (!token || !issuedAtRaw || !cookieSignature) {
2977
- return null;
2978
- }
2979
- const issuedAt = Number.parseInt(issuedAtRaw, 10);
2980
- if (!Number.isFinite(issuedAt)) {
2981
- return null;
2982
- }
2983
- if (Date.now() - issuedAt > CSRF_TTL_MS) {
2984
- return null;
2985
- }
2986
- const expectedSignature = signCsrfToken(token, issuedAt).split(".").pop();
2987
- if (!expectedSignature) {
2988
- return null;
2989
- }
2990
- const expectedBuffer = Buffer.from(expectedSignature);
2991
- const actualBuffer = Buffer.from(cookieSignature);
2992
- if (expectedBuffer.length !== actualBuffer.length) {
2993
- return null;
2994
- }
2995
- if (!timingSafeEqual(expectedBuffer, actualBuffer)) {
2996
- return null;
2981
+ function tokensMatch(left, right) {
2982
+ const leftBuffer = Buffer.from(left);
2983
+ const rightBuffer = Buffer.from(right);
2984
+ if (leftBuffer.length !== rightBuffer.length) {
2985
+ return false;
2997
2986
  }
2998
- return { token, issuedAt };
2987
+ return timingSafeEqual(leftBuffer, rightBuffer);
2999
2988
  }
3000
2989
  function createCsrfTokenCookie() {
3001
- const token = randomBytes(24).toString("hex");
3002
- const issuedAt = Date.now();
3003
- const value = signCsrfToken(token, issuedAt);
2990
+ const token = Bun.CSRF.generate(resolveCsrfSecret(), { expiresIn: CSRF_TTL_MS });
3004
2991
  return {
3005
2992
  token,
3006
- cookie: `${CSRF_COOKIE}=${encodeURIComponent(value)}; Path=/; SameSite=Lax; Max-Age=3600`
2993
+ cookie: `${CSRF_COOKIE}=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Max-Age=${Math.floor(CSRF_TTL_MS / 1000)}`
3007
2994
  };
3008
2995
  }
3009
2996
  function resolveCsrfToken(request) {
3010
- const cookieValue = readCsrfCookie(request);
3011
- if (cookieValue) {
3012
- const parsed = parseSignedCsrfValue(cookieValue);
3013
- if (parsed) {
3014
- return { token: parsed.token };
3015
- }
2997
+ const cookieValue = readRequestCookie(request, CSRF_COOKIE);
2998
+ if (cookieValue && Bun.CSRF.verify(cookieValue, csrfVerifyOptions())) {
2999
+ return { token: cookieValue };
3016
3000
  }
3017
3001
  return createCsrfTokenCookie();
3018
3002
  }
@@ -3035,6 +3019,10 @@ async function readSubmittedCsrfTokenFromBody(request) {
3035
3019
  if (typeof field === "string" && field.trim().length > 0) {
3036
3020
  return field.trim();
3037
3021
  }
3022
+ const legacyField = formData.get("_csrf");
3023
+ if (typeof legacyField === "string" && legacyField.trim().length > 0) {
3024
+ return legacyField.trim();
3025
+ }
3038
3026
  }
3039
3027
  return null;
3040
3028
  }
@@ -3042,20 +3030,14 @@ function verifyCsrfToken(request, submittedToken) {
3042
3030
  if (!submittedToken) {
3043
3031
  return false;
3044
3032
  }
3045
- const cookieValue = readCsrfCookie(request);
3033
+ const cookieValue = readRequestCookie(request, CSRF_COOKIE);
3046
3034
  if (!cookieValue) {
3047
3035
  return false;
3048
3036
  }
3049
- const parsed = parseSignedCsrfValue(cookieValue);
3050
- if (!parsed) {
3037
+ if (!tokensMatch(submittedToken, cookieValue)) {
3051
3038
  return false;
3052
3039
  }
3053
- const submittedBuffer = Buffer.from(submittedToken);
3054
- const expectedBuffer = Buffer.from(parsed.token);
3055
- if (submittedBuffer.length !== expectedBuffer.length) {
3056
- return false;
3057
- }
3058
- return timingSafeEqual(submittedBuffer, expectedBuffer);
3040
+ return Bun.CSRF.verify(submittedToken, csrfVerifyOptions());
3059
3041
  }
3060
3042
  function resolveCsrfTokenForRequest(request) {
3061
3043
  const metaToken = currentRequestMeta().csrfToken;
@@ -3096,8 +3078,30 @@ function createCsrfMiddleware() {
3096
3078
  return await next();
3097
3079
  };
3098
3080
  }
3081
+ // ../../src/core/http/csrfProtection.ts
3082
+ var DEFAULT_CSRF_TTL_MS = 60 * 60 * 1000;
3083
+ function createCsrfProtection(secret, options = {}) {
3084
+ const expiresIn = options.expiresIn ?? DEFAULT_CSRF_TTL_MS;
3085
+ const maxAge = options.maxAge ?? expiresIn;
3086
+ return {
3087
+ generate(_sessionKey) {
3088
+ return Bun.CSRF.generate(secret, { expiresIn });
3089
+ },
3090
+ verify(token, _sessionKey) {
3091
+ if (!token) {
3092
+ return false;
3093
+ }
3094
+ return Bun.CSRF.verify(token, { secret, maxAge });
3095
+ },
3096
+ secret
3097
+ };
3098
+ }
3099
+ // ../../src/core/crypto/nonCryptographicHash.ts
3100
+ function nonCryptographicDigest(input) {
3101
+ return Bun.hash(input).toString(16);
3102
+ }
3103
+
3099
3104
  // ../../src/core/http/etag.ts
3100
- import { createHash } from "crypto";
3101
3105
  function isEtagEnabled() {
3102
3106
  return (process.env.FEATURE_ETAG ?? "true") !== "false";
3103
3107
  }
@@ -3105,13 +3109,13 @@ function formatWeakEtag(digest) {
3105
3109
  return `W/"${digest}"`;
3106
3110
  }
3107
3111
  function computeEtagFromJson(data) {
3108
- const digest = createHash("sha256").update(JSON.stringify(data)).digest("hex").slice(0, 32);
3112
+ const digest = nonCryptographicDigest(JSON.stringify(data)).slice(0, 32);
3109
3113
  return formatWeakEtag(digest);
3110
3114
  }
3111
3115
  function etagFromResource(resource) {
3112
3116
  const version = resource.updated_at ?? resource.created_at ?? "";
3113
3117
  const versionText = version instanceof Date ? version.toISOString() : version ? String(version) : "0";
3114
- const digest = createHash("sha256").update(`${String(resource.id ?? "0")}:${versionText}`).digest("hex").slice(0, 32);
3118
+ const digest = nonCryptographicDigest(`${String(resource.id ?? "0")}:${versionText}`).slice(0, 32);
3115
3119
  return formatWeakEtag(digest);
3116
3120
  }
3117
3121
  function normalizeEtag(value) {
@@ -3598,7 +3602,7 @@ async function verifyPassword(password, passwordHash) {
3598
3602
  }
3599
3603
 
3600
3604
  // ../../src/core/crypto/fieldEncryption.ts
3601
- import { createCipheriv, createDecipheriv, createHmac as createHmac2, randomBytes as randomBytes2 } from "crypto";
3605
+ import { createCipheriv, createDecipheriv, createHmac, randomBytes } from "crypto";
3602
3606
  var ENCRYPTION_PREFIX = "enc:v1:";
3603
3607
  var IV_LENGTH = 12;
3604
3608
  var TAG_LENGTH = 16;
@@ -3627,7 +3631,7 @@ function isFieldEncryptionEnabled() {
3627
3631
  return (process.env.APP_ENV ?? "local") === "production";
3628
3632
  }
3629
3633
  function encryptField(plaintext, key) {
3630
- const iv = randomBytes2(IV_LENGTH);
3634
+ const iv = randomBytes(IV_LENGTH);
3631
3635
  const cipher = createCipheriv("aes-256-gcm", key, iv);
3632
3636
  const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
3633
3637
  const tag = cipher.getAuthTag();
@@ -3647,7 +3651,7 @@ function decryptField(value, key) {
3647
3651
  return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
3648
3652
  }
3649
3653
  function hashLookupValue(normalizedValue, key) {
3650
- return createHmac2("sha256", key).update(normalizedValue).digest("hex");
3654
+ return createHmac("sha256", key).update(normalizedValue).digest("hex");
3651
3655
  }
3652
3656
  function normalizeEmail(email) {
3653
3657
  return email.trim().toLowerCase();
@@ -3727,7 +3731,7 @@ function resolveDefaultTokenExpiryDays() {
3727
3731
  }
3728
3732
 
3729
3733
  // ../../src/core/security/totp.ts
3730
- import { createHmac as createHmac3 } from "crypto";
3734
+ import { createHmac as createHmac2 } from "crypto";
3731
3735
  function decodeBase32(input) {
3732
3736
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
3733
3737
  const normalized = input.replace(/=+$/u, "").toUpperCase();
@@ -3749,7 +3753,7 @@ function generateTotp(secret, counter, digits = 6) {
3749
3753
  const key = decodeBase32(secret);
3750
3754
  const buffer = Buffer.alloc(8);
3751
3755
  buffer.writeBigUInt64BE(BigInt(counter));
3752
- const digest = createHmac3("sha1", key).update(buffer).digest();
3756
+ const digest = createHmac2("sha1", key).update(buffer).digest();
3753
3757
  const lastByte = digest[digest.length - 1] ?? 0;
3754
3758
  const offset = lastByte & 15;
3755
3759
  const b0 = digest[offset] ?? 0;
@@ -3941,30 +3945,30 @@ var userTable = defineTable({
3941
3945
  });
3942
3946
 
3943
3947
  // ../../src/core/auth/tokenHash.ts
3944
- import { createHash as createHash2, createHmac as createHmac4 } from "crypto";
3948
+ import { createHash, createHmac as createHmac3 } from "crypto";
3945
3949
  function resolveTokenPepper() {
3946
3950
  return process.env.TOKEN_HASH_PEPPER?.trim() ?? "workhub-dev-token-pepper";
3947
3951
  }
3948
3952
  function hashApiToken(token) {
3949
3953
  const pepper = resolveTokenPepper();
3950
3954
  if (pepper && pepper !== "workhub-dev-token-pepper") {
3951
- return createHmac4("sha256", pepper).update(token).digest("hex");
3955
+ return createHmac3("sha256", pepper).update(token).digest("hex");
3952
3956
  }
3953
- return createHash2("sha256").update(token).digest("hex");
3957
+ return createHash("sha256").update(token).digest("hex");
3954
3958
  }
3955
3959
 
3956
3960
  // ../../src/modules/user/provider.ts
3957
3961
  var tokenServiceToken = CORE_TOKEN_SERVICE_TOKEN;
3958
3962
 
3959
3963
  // ../../src/core/http/flashSession.ts
3960
- import { createHmac as createHmac5, timingSafeEqual as timingSafeEqual2 } from "crypto";
3964
+ import { createHmac as createHmac4, timingSafeEqual as timingSafeEqual2 } from "crypto";
3961
3965
  var FLASH_COOKIE = "workhub_flash";
3962
3966
  var FLASH_TTL_MS = 60 * 1000;
3963
3967
  function resolveFlashSecret() {
3964
3968
  return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || "workhub-dev-flash-secret";
3965
3969
  }
3966
3970
  function signFlashPayload(payload, issuedAt) {
3967
- const signature = createHmac5("sha256", resolveFlashSecret()).update(`${payload}.${issuedAt}`).digest("hex");
3971
+ const signature = createHmac4("sha256", resolveFlashSecret()).update(`${payload}.${issuedAt}`).digest("hex");
3968
3972
  return `${payload}.${issuedAt}.${signature}`;
3969
3973
  }
3970
3974
  function readFlashCookie(request) {
@@ -5362,6 +5366,7 @@ export {
5362
5366
  withMigrationLock,
5363
5367
  withMiddleware,
5364
5368
  withErrorHandling,
5369
+ verifyCsrfToken,
5365
5370
  validateObject,
5366
5371
  toResourceCollection,
5367
5372
  toPaginatedResourceCollection,
@@ -5381,6 +5386,8 @@ export {
5381
5386
  resolveWebLayoutData,
5382
5387
  resolveService,
5383
5388
  resolveDatabaseDriver,
5389
+ resolveCsrfTokenForRequest,
5390
+ resolveCsrfToken,
5384
5391
  resolveApplicationQueue,
5385
5392
  resolveApplicationPolicyGate,
5386
5393
  resolveApplicationLogger,
@@ -5391,6 +5398,10 @@ export {
5391
5398
  required,
5392
5399
  registerShutdownHandler,
5393
5400
  registerModelRepository,
5401
+ readSubmittedCsrfTokenFromBody,
5402
+ readSubmittedCsrfToken,
5403
+ readRequestCookie,
5404
+ readBunRequestCookie,
5394
5405
  queue,
5395
5406
  prometheusRegistry,
5396
5407
  policyGate,
@@ -5445,6 +5456,9 @@ export {
5445
5456
  createMemoryThrottleMiddleware,
5446
5457
  createLoginThrottleMiddleware,
5447
5458
  createFailedJobService,
5459
+ createDatabaseConnection2 as createDatabaseConnection,
5460
+ createCsrfTokenCookie,
5461
+ createCsrfProtection,
5448
5462
  createCsrfMiddleware,
5449
5463
  createBodySizeLimitMiddleware,
5450
5464
  createAuthorizeMiddleware,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getstrata/core",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
4
4
  "description": "Strata — Laravel-inspired Bun framework public API",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -317,6 +317,9 @@
317
317
  "publishConfig": {
318
318
  "access": "public"
319
319
  },
320
+ "dependencies": {
321
+ "eta": "^4.6.0"
322
+ },
320
323
  "peerDependencies": {
321
324
  "typescript": "^5.9.0"
322
325
  }