@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/README.md +3 -0
- package/dist/core/crypto/nonCryptographicHash.d.ts +2 -0
- package/dist/core/http/cookies.d.ts +4 -0
- package/dist/core/http/csrfProtection.d.ts +12 -0
- package/dist/entries/auth/guard.js +20 -20
- package/dist/entries/auth/membershipService.js +3 -0
- package/dist/entries/database.js +20 -20
- package/dist/entries/http/csrfToken.js +49 -65
- package/dist/entries/http/etag.js +6 -4
- package/dist/entries/http/webErrorResponse.js +73 -87
- package/dist/entries/http.js +83 -90
- package/dist/entries/queue/createAppQueue.js +23 -20
- package/dist/entries/queue/publicQueue.js +20 -20
- package/dist/entries/queue/queueMetrics.js +23 -20
- package/dist/entries/view.js +73 -87
- package/dist/framework/public-api.d.ts +5 -0
- package/dist/index.js +205 -180
- package/package.json +4 -1
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(
|
|
1750
|
+
return resolveModelRepository(Model).getTable().primaryKey;
|
|
1667
1751
|
}
|
|
1668
1752
|
static hydrateAttributes(attributes) {
|
|
1669
|
-
const casts = modelStatics(
|
|
1753
|
+
const casts = modelStatics(Model).$casts ?? {};
|
|
1670
1754
|
return applyCasts(attributes, casts, "hydrate");
|
|
1671
1755
|
}
|
|
1672
1756
|
static dehydrateAttributes(attributes) {
|
|
1673
|
-
const casts = modelStatics(
|
|
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(
|
|
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(
|
|
1684
|
-
const existing = modelGlobalScopes.get(
|
|
1685
|
-
modelGlobalScopes.set(
|
|
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(
|
|
1779
|
+
const repository = resolveModelRepository(Model);
|
|
1696
1780
|
let query = repository.query();
|
|
1697
|
-
for (const scope of getGlobalScopes(
|
|
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(
|
|
1705
|
-
const repository = resolveModelRepository(
|
|
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(
|
|
1716
|
-
const repository = resolveModelRepository(
|
|
1799
|
+
const statics = modelStatics(Model);
|
|
1800
|
+
const repository = resolveModelRepository(Model);
|
|
1717
1801
|
const primaryKey = repository.getTable().primaryKey;
|
|
1718
|
-
const record = await Model.query.call(
|
|
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(
|
|
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(`${
|
|
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(
|
|
1731
|
-
let query = Model.query.call(
|
|
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(
|
|
1744
|
-
let query = Model.query.call(
|
|
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 {
|
|
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
|
|
2950
|
-
|
|
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
|
|
2968
|
-
const
|
|
2969
|
-
|
|
2970
|
-
|
|
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
|
|
2987
|
+
return timingSafeEqual(leftBuffer, rightBuffer);
|
|
2996
2988
|
}
|
|
2997
2989
|
function createCsrfTokenCookie() {
|
|
2998
|
-
const token =
|
|
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(
|
|
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 =
|
|
3008
|
-
if (cookieValue) {
|
|
3009
|
-
|
|
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 =
|
|
3033
|
+
const cookieValue = readRequestCookie(request, CSRF_COOKIE);
|
|
3043
3034
|
if (!cookieValue) {
|
|
3044
3035
|
return false;
|
|
3045
3036
|
}
|
|
3046
|
-
|
|
3047
|
-
if (!parsed) {
|
|
3037
|
+
if (!tokensMatch(submittedToken, cookieValue)) {
|
|
3048
3038
|
return false;
|
|
3049
3039
|
}
|
|
3050
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
3955
|
+
return createHmac3("sha256", pepper).update(token).digest("hex");
|
|
3949
3956
|
}
|
|
3950
|
-
return
|
|
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
|
|
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 =
|
|
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.
|
|
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
|
}
|