@getstrata/core 0.5.4 → 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
@@ -1,4 +1,47 @@
1
1
  // @bun
2
+ // ../../src/core/logging/logger.ts
3
+ class Logger {
4
+ channel;
5
+ constructor(channel = "app") {
6
+ this.channel = channel;
7
+ }
8
+ write(level, message, context = {}) {
9
+ const entry = {
10
+ level,
11
+ channel: this.channel,
12
+ message,
13
+ timestamp: new Date().toISOString(),
14
+ ...context
15
+ };
16
+ const line = JSON.stringify(entry);
17
+ if (level === "error") {
18
+ console.error(line);
19
+ return;
20
+ }
21
+ console.log(line);
22
+ }
23
+ debug(message, context) {
24
+ this.write("debug", message, context);
25
+ }
26
+ info(message, context) {
27
+ this.write("info", message, context);
28
+ }
29
+ warn(message, context) {
30
+ this.write("warn", message, context);
31
+ }
32
+ error(message, context) {
33
+ this.write("error", message, context);
34
+ }
35
+ }
36
+ var appLogger = new Logger("app");
37
+
38
+ // ../../src/bootstrap/config.ts
39
+ var CORE_QUEUE_TOKEN = "core.queue";
40
+ var CORE_POLICY_GATE_TOKEN = "core.policyGate";
41
+ var CORE_AUTH_TOKEN = "core.auth";
42
+ var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
43
+ var DEFAULT_QUEUE_DRIVER = "sync";
44
+
2
45
  // ../../src/bootstrap/contracts.ts
3
46
  class ServiceContainer {
4
47
  services = new Map;
@@ -73,6 +116,39 @@ function getRequiredDependency(dependencies, key) {
73
116
  function resolveService(dependencies, token) {
74
117
  return dependencies.container.resolve(token);
75
118
  }
119
+
120
+ // ../../src/bootstrap/applicationRegistry.ts
121
+ var activeContext;
122
+ function setActiveApplicationContext(context) {
123
+ activeContext = context;
124
+ }
125
+ function requireActiveApplicationContext() {
126
+ if (!activeContext) {
127
+ throw new Error("The application context has not been bootstrapped.");
128
+ }
129
+ return activeContext;
130
+ }
131
+ function resolveApplicationCache() {
132
+ return getRequiredDependency(requireActiveApplicationContext().dependencies, "cache");
133
+ }
134
+ function resolveApplicationQueue() {
135
+ return requireActiveApplicationContext().container.resolve(CORE_QUEUE_TOKEN);
136
+ }
137
+ function resolveApplicationAuth() {
138
+ return requireActiveApplicationContext().container.resolve(CORE_AUTH_TOKEN);
139
+ }
140
+ function resolveApplicationPolicyGate() {
141
+ return requireActiveApplicationContext().container.resolve(CORE_POLICY_GATE_TOKEN);
142
+ }
143
+ function resolveApplicationConfig() {
144
+ return requireActiveApplicationContext().config;
145
+ }
146
+ function resolveApplicationLogger() {
147
+ return appLogger;
148
+ }
149
+ function resolveApplicationDependencies() {
150
+ return requireActiveApplicationContext().dependencies;
151
+ }
76
152
  // ../../src/core/auth/authContext.ts
77
153
  import { AsyncLocalStorage } from "async_hooks";
78
154
  var authContext = new AsyncLocalStorage;
@@ -1411,6 +1487,14 @@ var baseRepository_default = BaseRepository;
1411
1487
  function bindDatabaseConnection2(connection) {
1412
1488
  bindDatabaseConnection(connection);
1413
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
+ }
1414
1498
  // ../../src/core/database/migrations/advisoryLock.ts
1415
1499
  var MIGRATION_LOCK_KEY = 42424242;
1416
1500
  async function withMigrationLock(db2, callback, lockKey = MIGRATION_LOCK_KEY) {
@@ -1663,26 +1747,26 @@ class Model {
1663
1747
  throw new Error(`${this.constructor.name}.primaryKey() is not implemented.`);
1664
1748
  }
1665
1749
  static primaryKeyField() {
1666
- return resolveModelRepository(this).getTable().primaryKey;
1750
+ return resolveModelRepository(Model).getTable().primaryKey;
1667
1751
  }
1668
1752
  static hydrateAttributes(attributes) {
1669
- const casts = modelStatics(this).$casts ?? {};
1753
+ const casts = modelStatics(Model).$casts ?? {};
1670
1754
  return applyCasts(attributes, casts, "hydrate");
1671
1755
  }
1672
1756
  static dehydrateAttributes(attributes) {
1673
- const casts = modelStatics(this).$casts ?? {};
1757
+ const casts = modelStatics(Model).$casts ?? {};
1674
1758
  return applyCasts(attributes, casts, "dehydrate");
1675
1759
  }
1676
1760
  static fromRecord(record, repository, exists = true) {
1677
- const statics = modelStatics(this);
1761
+ const statics = modelStatics(Model);
1678
1762
  const hydrated = statics.hydrateAttributes(record);
1679
1763
  return new statics(hydrated, repository, exists);
1680
1764
  }
1681
1765
  static boot() {}
1682
1766
  static addGlobalScope(_name, scope) {
1683
- ensureBooted(this);
1684
- const existing = modelGlobalScopes.get(this) ?? [];
1685
- modelGlobalScopes.set(this, [
1767
+ ensureBooted(Model);
1768
+ const existing = modelGlobalScopes.get(Model) ?? [];
1769
+ modelGlobalScopes.set(Model, [
1686
1770
  ...existing,
1687
1771
  scope
1688
1772
  ]);
@@ -1692,17 +1776,17 @@ class Model {
1692
1776
  }
1693
1777
  static query() {
1694
1778
  ensureBooted(this);
1695
- const repository = resolveModelRepository(this);
1779
+ const repository = resolveModelRepository(Model);
1696
1780
  let query = repository.query();
1697
- for (const scope of getGlobalScopes(this)) {
1781
+ for (const scope of getGlobalScopes(Model)) {
1698
1782
  query = scope(query);
1699
1783
  }
1700
1784
  return query;
1701
1785
  }
1702
1786
  static async create(attributes) {
1703
1787
  const statics = modelStatics(this);
1704
- ensureBooted(this);
1705
- const repository = resolveModelRepository(this);
1788
+ ensureBooted(Model);
1789
+ const repository = resolveModelRepository(Model);
1706
1790
  const table = repository.getTable();
1707
1791
  const timestamps = statics.$timestamps ?? true;
1708
1792
  const assignable = filterMassAssignable(statics.$fillable, statics.$guarded, attributes);
@@ -1712,23 +1796,23 @@ class Model {
1712
1796
  return statics.fromRecord(record, repository, true);
1713
1797
  }
1714
1798
  static async find(id) {
1715
- const statics = modelStatics(this);
1716
- const repository = resolveModelRepository(this);
1799
+ const statics = modelStatics(Model);
1800
+ const repository = resolveModelRepository(Model);
1717
1801
  const primaryKey = repository.getTable().primaryKey;
1718
- const record = await Model.query.call(this).where({ [primaryKey]: id }).first();
1802
+ const record = await Model.query.call(Model).where({ [primaryKey]: id }).first();
1719
1803
  return record ? statics.fromRecord(record, repository, true) : null;
1720
1804
  }
1721
1805
  static async findOrFail(id, errorFactory) {
1722
- const model = await Model.find.call(this, id);
1806
+ const model = await Model.find.call(Model, id);
1723
1807
  if (model) {
1724
1808
  return model;
1725
1809
  }
1726
- throw errorFactory?.(id) ?? new NotFoundError(`${this.name} ${String(id)} not found.`);
1810
+ throw errorFactory?.(id) ?? new NotFoundError(`${Model.name} ${String(id)} not found.`);
1727
1811
  }
1728
1812
  static async all(options = {}) {
1729
1813
  const statics = modelStatics(this);
1730
- const repository = resolveModelRepository(this);
1731
- let query = Model.query.call(this);
1814
+ const repository = resolveModelRepository(Model);
1815
+ let query = Model.query.call(Model);
1732
1816
  if (options.orderBy) {
1733
1817
  query = query.orderBy(options.orderBy);
1734
1818
  }
@@ -1740,8 +1824,8 @@ class Model {
1740
1824
  }
1741
1825
  static async firstWhere(where, options = {}) {
1742
1826
  const statics = modelStatics(this);
1743
- const repository = resolveModelRepository(this);
1744
- let query = Model.query.call(this).where(where);
1827
+ const repository = resolveModelRepository(Model);
1828
+ let query = Model.query.call(Model).where(where);
1745
1829
  if (options.orderBy) {
1746
1830
  query = query.orderBy(options.orderBy);
1747
1831
  }
@@ -2488,94 +2572,12 @@ async function runSeedersFromDirectory(directory, db2, options) {
2488
2572
  function defineTable(definition) {
2489
2573
  return definition;
2490
2574
  }
2491
- // ../../src/core/database/connection.ts
2492
- function createDatabaseConnection2(source) {
2493
- return {
2494
- async unsafe(query, params = []) {
2495
- return await source.unsafe(query, params);
2496
- }
2497
- };
2498
- }
2499
-
2500
2575
  // ../../src/core/database/transaction.ts
2501
2576
  async function runInTransaction(operation) {
2502
2577
  return await connection_default.begin(async (transaction) => {
2503
2578
  return await operation(createDatabaseConnection2(transaction));
2504
2579
  });
2505
2580
  }
2506
- // ../../src/core/logging/logger.ts
2507
- class Logger {
2508
- channel;
2509
- constructor(channel = "app") {
2510
- this.channel = channel;
2511
- }
2512
- write(level, message, context = {}) {
2513
- const entry = {
2514
- level,
2515
- channel: this.channel,
2516
- message,
2517
- timestamp: new Date().toISOString(),
2518
- ...context
2519
- };
2520
- const line = JSON.stringify(entry);
2521
- if (level === "error") {
2522
- console.error(line);
2523
- return;
2524
- }
2525
- console.log(line);
2526
- }
2527
- debug(message, context) {
2528
- this.write("debug", message, context);
2529
- }
2530
- info(message, context) {
2531
- this.write("info", message, context);
2532
- }
2533
- warn(message, context) {
2534
- this.write("warn", message, context);
2535
- }
2536
- error(message, context) {
2537
- this.write("error", message, context);
2538
- }
2539
- }
2540
- var appLogger = new Logger("app");
2541
-
2542
- // ../../src/bootstrap/config.ts
2543
- var CORE_QUEUE_TOKEN = "core.queue";
2544
- var CORE_POLICY_GATE_TOKEN = "core.policyGate";
2545
- var CORE_AUTH_TOKEN = "core.auth";
2546
- var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
2547
- var DEFAULT_QUEUE_DRIVER = "sync";
2548
-
2549
- // ../../src/bootstrap/applicationRegistry.ts
2550
- var activeContext;
2551
- function requireActiveApplicationContext() {
2552
- if (!activeContext) {
2553
- throw new Error("The application context has not been bootstrapped.");
2554
- }
2555
- return activeContext;
2556
- }
2557
- function resolveApplicationCache() {
2558
- return getRequiredDependency(requireActiveApplicationContext().dependencies, "cache");
2559
- }
2560
- function resolveApplicationQueue() {
2561
- return requireActiveApplicationContext().container.resolve(CORE_QUEUE_TOKEN);
2562
- }
2563
- function resolveApplicationAuth() {
2564
- return requireActiveApplicationContext().container.resolve(CORE_AUTH_TOKEN);
2565
- }
2566
- function resolveApplicationPolicyGate() {
2567
- return requireActiveApplicationContext().container.resolve(CORE_POLICY_GATE_TOKEN);
2568
- }
2569
- function resolveApplicationConfig() {
2570
- return requireActiveApplicationContext().config;
2571
- }
2572
- function resolveApplicationLogger() {
2573
- return appLogger;
2574
- }
2575
- function resolveApplicationDependencies() {
2576
- return requireActiveApplicationContext().dependencies;
2577
- }
2578
-
2579
2581
  // ../../src/core/mail/mailer.ts
2580
2582
  function resolveSmtpConfig() {
2581
2583
  const host = process.env.MAIL_HOST?.trim();
@@ -2924,8 +2926,35 @@ function createBodySizeLimitMiddleware(maxBytes = resolveMaxBodyBytes()) {
2924
2926
  return await next();
2925
2927
  };
2926
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
+ }
2927
2956
  // ../../src/core/http/csrfToken.ts
2928
- import { createHmac, randomBytes, timingSafeEqual } from "crypto";
2957
+ import { timingSafeEqual } from "crypto";
2929
2958
 
2930
2959
  // ../../src/core/http/requestMetaContext.ts
2931
2960
  import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
@@ -2946,70 +2975,28 @@ var CSRF_TTL_MS = 60 * 60 * 1000;
2946
2975
  function resolveCsrfSecret() {
2947
2976
  return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || "workhub-dev-csrf-secret";
2948
2977
  }
2949
- function signCsrfToken(token, issuedAt) {
2950
- const payload = `${token}.${issuedAt}`;
2951
- const signature = createHmac("sha256", resolveCsrfSecret()).update(payload).digest("hex");
2952
- return `${payload}.${signature}`;
2953
- }
2954
- function readCsrfCookie(request) {
2955
- const cookieHeader = request.headers.get("cookie");
2956
- if (!cookieHeader) {
2957
- return null;
2958
- }
2959
- for (const part of cookieHeader.split(";")) {
2960
- const [name, ...rest] = part.trim().split("=");
2961
- if (name === CSRF_COOKIE) {
2962
- return decodeURIComponent(rest.join("="));
2963
- }
2964
- }
2965
- return null;
2978
+ function csrfVerifyOptions() {
2979
+ return { secret: resolveCsrfSecret(), maxAge: CSRF_TTL_MS };
2966
2980
  }
2967
- function parseSignedCsrfValue(cookieValue) {
2968
- const parts = cookieValue.split(".");
2969
- if (parts.length !== 3) {
2970
- return null;
2971
- }
2972
- const [token, issuedAtRaw, cookieSignature] = parts;
2973
- if (!token || !issuedAtRaw || !cookieSignature) {
2974
- return null;
2975
- }
2976
- const issuedAt = Number.parseInt(issuedAtRaw, 10);
2977
- if (!Number.isFinite(issuedAt)) {
2978
- return null;
2979
- }
2980
- if (Date.now() - issuedAt > CSRF_TTL_MS) {
2981
- return null;
2982
- }
2983
- const expectedSignature = signCsrfToken(token, issuedAt).split(".").pop();
2984
- if (!expectedSignature) {
2985
- return null;
2986
- }
2987
- const expectedBuffer = Buffer.from(expectedSignature);
2988
- const actualBuffer = Buffer.from(cookieSignature);
2989
- if (expectedBuffer.length !== actualBuffer.length) {
2990
- return null;
2991
- }
2992
- if (!timingSafeEqual(expectedBuffer, actualBuffer)) {
2993
- 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;
2994
2986
  }
2995
- return { token, issuedAt };
2987
+ return timingSafeEqual(leftBuffer, rightBuffer);
2996
2988
  }
2997
2989
  function createCsrfTokenCookie() {
2998
- const token = randomBytes(24).toString("hex");
2999
- const issuedAt = Date.now();
3000
- const value = signCsrfToken(token, issuedAt);
2990
+ const token = Bun.CSRF.generate(resolveCsrfSecret(), { expiresIn: CSRF_TTL_MS });
3001
2991
  return {
3002
2992
  token,
3003
- 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)}`
3004
2994
  };
3005
2995
  }
3006
2996
  function resolveCsrfToken(request) {
3007
- const cookieValue = readCsrfCookie(request);
3008
- if (cookieValue) {
3009
- const parsed = parseSignedCsrfValue(cookieValue);
3010
- if (parsed) {
3011
- return { token: parsed.token };
3012
- }
2997
+ const cookieValue = readRequestCookie(request, CSRF_COOKIE);
2998
+ if (cookieValue && Bun.CSRF.verify(cookieValue, csrfVerifyOptions())) {
2999
+ return { token: cookieValue };
3013
3000
  }
3014
3001
  return createCsrfTokenCookie();
3015
3002
  }
@@ -3032,6 +3019,10 @@ async function readSubmittedCsrfTokenFromBody(request) {
3032
3019
  if (typeof field === "string" && field.trim().length > 0) {
3033
3020
  return field.trim();
3034
3021
  }
3022
+ const legacyField = formData.get("_csrf");
3023
+ if (typeof legacyField === "string" && legacyField.trim().length > 0) {
3024
+ return legacyField.trim();
3025
+ }
3035
3026
  }
3036
3027
  return null;
3037
3028
  }
@@ -3039,20 +3030,14 @@ function verifyCsrfToken(request, submittedToken) {
3039
3030
  if (!submittedToken) {
3040
3031
  return false;
3041
3032
  }
3042
- const cookieValue = readCsrfCookie(request);
3033
+ const cookieValue = readRequestCookie(request, CSRF_COOKIE);
3043
3034
  if (!cookieValue) {
3044
3035
  return false;
3045
3036
  }
3046
- const parsed = parseSignedCsrfValue(cookieValue);
3047
- if (!parsed) {
3037
+ if (!tokensMatch(submittedToken, cookieValue)) {
3048
3038
  return false;
3049
3039
  }
3050
- const submittedBuffer = Buffer.from(submittedToken);
3051
- const expectedBuffer = Buffer.from(parsed.token);
3052
- if (submittedBuffer.length !== expectedBuffer.length) {
3053
- return false;
3054
- }
3055
- return timingSafeEqual(submittedBuffer, expectedBuffer);
3040
+ return Bun.CSRF.verify(submittedToken, csrfVerifyOptions());
3056
3041
  }
3057
3042
  function resolveCsrfTokenForRequest(request) {
3058
3043
  const metaToken = currentRequestMeta().csrfToken;
@@ -3093,8 +3078,30 @@ function createCsrfMiddleware() {
3093
3078
  return await next();
3094
3079
  };
3095
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
+
3096
3104
  // ../../src/core/http/etag.ts
3097
- import { createHash } from "crypto";
3098
3105
  function isEtagEnabled() {
3099
3106
  return (process.env.FEATURE_ETAG ?? "true") !== "false";
3100
3107
  }
@@ -3102,13 +3109,13 @@ function formatWeakEtag(digest) {
3102
3109
  return `W/"${digest}"`;
3103
3110
  }
3104
3111
  function computeEtagFromJson(data) {
3105
- const digest = createHash("sha256").update(JSON.stringify(data)).digest("hex").slice(0, 32);
3112
+ const digest = nonCryptographicDigest(JSON.stringify(data)).slice(0, 32);
3106
3113
  return formatWeakEtag(digest);
3107
3114
  }
3108
3115
  function etagFromResource(resource) {
3109
3116
  const version = resource.updated_at ?? resource.created_at ?? "";
3110
3117
  const versionText = version instanceof Date ? version.toISOString() : version ? String(version) : "0";
3111
- 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);
3112
3119
  return formatWeakEtag(digest);
3113
3120
  }
3114
3121
  function normalizeEtag(value) {
@@ -3595,7 +3602,7 @@ async function verifyPassword(password, passwordHash) {
3595
3602
  }
3596
3603
 
3597
3604
  // ../../src/core/crypto/fieldEncryption.ts
3598
- import { createCipheriv, createDecipheriv, createHmac as createHmac2, randomBytes as randomBytes2 } from "crypto";
3605
+ import { createCipheriv, createDecipheriv, createHmac, randomBytes } from "crypto";
3599
3606
  var ENCRYPTION_PREFIX = "enc:v1:";
3600
3607
  var IV_LENGTH = 12;
3601
3608
  var TAG_LENGTH = 16;
@@ -3624,7 +3631,7 @@ function isFieldEncryptionEnabled() {
3624
3631
  return (process.env.APP_ENV ?? "local") === "production";
3625
3632
  }
3626
3633
  function encryptField(plaintext, key) {
3627
- const iv = randomBytes2(IV_LENGTH);
3634
+ const iv = randomBytes(IV_LENGTH);
3628
3635
  const cipher = createCipheriv("aes-256-gcm", key, iv);
3629
3636
  const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
3630
3637
  const tag = cipher.getAuthTag();
@@ -3644,7 +3651,7 @@ function decryptField(value, key) {
3644
3651
  return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
3645
3652
  }
3646
3653
  function hashLookupValue(normalizedValue, key) {
3647
- return createHmac2("sha256", key).update(normalizedValue).digest("hex");
3654
+ return createHmac("sha256", key).update(normalizedValue).digest("hex");
3648
3655
  }
3649
3656
  function normalizeEmail(email) {
3650
3657
  return email.trim().toLowerCase();
@@ -3724,7 +3731,7 @@ function resolveDefaultTokenExpiryDays() {
3724
3731
  }
3725
3732
 
3726
3733
  // ../../src/core/security/totp.ts
3727
- import { createHmac as createHmac3 } from "crypto";
3734
+ import { createHmac as createHmac2 } from "crypto";
3728
3735
  function decodeBase32(input) {
3729
3736
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
3730
3737
  const normalized = input.replace(/=+$/u, "").toUpperCase();
@@ -3746,7 +3753,7 @@ function generateTotp(secret, counter, digits = 6) {
3746
3753
  const key = decodeBase32(secret);
3747
3754
  const buffer = Buffer.alloc(8);
3748
3755
  buffer.writeBigUInt64BE(BigInt(counter));
3749
- const digest = createHmac3("sha1", key).update(buffer).digest();
3756
+ const digest = createHmac2("sha1", key).update(buffer).digest();
3750
3757
  const lastByte = digest[digest.length - 1] ?? 0;
3751
3758
  const offset = lastByte & 15;
3752
3759
  const b0 = digest[offset] ?? 0;
@@ -3938,30 +3945,30 @@ var userTable = defineTable({
3938
3945
  });
3939
3946
 
3940
3947
  // ../../src/core/auth/tokenHash.ts
3941
- import { createHash as createHash2, createHmac as createHmac4 } from "crypto";
3948
+ import { createHash, createHmac as createHmac3 } from "crypto";
3942
3949
  function resolveTokenPepper() {
3943
3950
  return process.env.TOKEN_HASH_PEPPER?.trim() ?? "workhub-dev-token-pepper";
3944
3951
  }
3945
3952
  function hashApiToken(token) {
3946
3953
  const pepper = resolveTokenPepper();
3947
3954
  if (pepper && pepper !== "workhub-dev-token-pepper") {
3948
- return createHmac4("sha256", pepper).update(token).digest("hex");
3955
+ return createHmac3("sha256", pepper).update(token).digest("hex");
3949
3956
  }
3950
- return createHash2("sha256").update(token).digest("hex");
3957
+ return createHash("sha256").update(token).digest("hex");
3951
3958
  }
3952
3959
 
3953
3960
  // ../../src/modules/user/provider.ts
3954
3961
  var tokenServiceToken = CORE_TOKEN_SERVICE_TOKEN;
3955
3962
 
3956
3963
  // ../../src/core/http/flashSession.ts
3957
- import { createHmac as createHmac5, timingSafeEqual as timingSafeEqual2 } from "crypto";
3964
+ import { createHmac as createHmac4, timingSafeEqual as timingSafeEqual2 } from "crypto";
3958
3965
  var FLASH_COOKIE = "workhub_flash";
3959
3966
  var FLASH_TTL_MS = 60 * 1000;
3960
3967
  function resolveFlashSecret() {
3961
3968
  return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || "workhub-dev-flash-secret";
3962
3969
  }
3963
3970
  function signFlashPayload(payload, issuedAt) {
3964
- const signature = createHmac5("sha256", resolveFlashSecret()).update(`${payload}.${issuedAt}`).digest("hex");
3971
+ const signature = createHmac4("sha256", resolveFlashSecret()).update(`${payload}.${issuedAt}`).digest("hex");
3965
3972
  return `${payload}.${issuedAt}.${signature}`;
3966
3973
  }
3967
3974
  function readFlashCookie(request) {
@@ -5359,11 +5366,13 @@ export {
5359
5366
  withMigrationLock,
5360
5367
  withMiddleware,
5361
5368
  withErrorHandling,
5369
+ verifyCsrfToken,
5362
5370
  validateObject,
5363
5371
  toResourceCollection,
5364
5372
  toPaginatedResourceCollection,
5365
5373
  stringRule,
5366
5374
  storageFacade as storage,
5375
+ setActiveApplicationContext,
5367
5376
  serializeDate,
5368
5377
  securedBindRouteModelByKey,
5369
5378
  securedBindRouteModel,
@@ -5377,9 +5386,22 @@ export {
5377
5386
  resolveWebLayoutData,
5378
5387
  resolveService,
5379
5388
  resolveDatabaseDriver,
5389
+ resolveCsrfTokenForRequest,
5390
+ resolveCsrfToken,
5391
+ resolveApplicationQueue,
5392
+ resolveApplicationPolicyGate,
5393
+ resolveApplicationLogger,
5394
+ resolveApplicationDependencies,
5395
+ resolveApplicationConfig,
5396
+ resolveApplicationCache,
5397
+ resolveApplicationAuth,
5380
5398
  required,
5381
5399
  registerShutdownHandler,
5382
5400
  registerModelRepository,
5401
+ readSubmittedCsrfTokenFromBody,
5402
+ readSubmittedCsrfToken,
5403
+ readRequestCookie,
5404
+ readBunRequestCookie,
5383
5405
  queue,
5384
5406
  prometheusRegistry,
5385
5407
  policyGate,
@@ -5434,6 +5456,9 @@ export {
5434
5456
  createMemoryThrottleMiddleware,
5435
5457
  createLoginThrottleMiddleware,
5436
5458
  createFailedJobService,
5459
+ createDatabaseConnection2 as createDatabaseConnection,
5460
+ createCsrfTokenCookie,
5461
+ createCsrfProtection,
5437
5462
  createCsrfMiddleware,
5438
5463
  createBodySizeLimitMiddleware,
5439
5464
  createAuthorizeMiddleware,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getstrata/core",
3
- "version": "0.5.4",
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
  }