@getstrata/core 0.5.13 → 0.5.14
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 +16 -6
- package/dist/bootstrap/httpKernel.d.ts +1 -2
- package/dist/core/admin/registry.d.ts +1 -0
- package/dist/core/storage/storage.d.ts +6 -3
- package/dist/entries/storage/storage.js +14 -4
- package/dist/framework/public-api.d.ts +11 -2
- package/dist/index.js +663 -184
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Stable **Strata** framework surface for application modules.
|
|
4
4
|
|
|
5
5
|
**Source:** `src/framework/public-api.ts` (monorepo)
|
|
6
|
-
**Repository:** [EyK-26/strata](https://github.com/EyK-26/strata)
|
|
6
|
+
**Repository:** [EyK-26/strata](https://github.com/EyK-26/strata), directory `packages/strata-core`
|
|
7
7
|
|
|
8
8
|
WorkHub is the reference application built on Strata; import the framework from this package in your own modules.
|
|
9
9
|
|
|
@@ -15,21 +15,31 @@ Set `DATABASE_URL` before importing (connection is created lazily on first query
|
|
|
15
15
|
process.env.DATABASE_URL ??= "postgresql://postgres:postgres@localhost:5432/myapp";
|
|
16
16
|
|
|
17
17
|
import {
|
|
18
|
+
AdminResourceRegistry,
|
|
18
19
|
BaseRepository,
|
|
19
20
|
EtaViewEngine,
|
|
20
21
|
FormRequest,
|
|
21
22
|
Policy,
|
|
23
|
+
formatAdminValue,
|
|
22
24
|
mailer,
|
|
23
25
|
storage,
|
|
24
26
|
withErrorHandling,
|
|
25
27
|
} from "@getstrata/core";
|
|
26
28
|
```
|
|
27
29
|
|
|
28
|
-
**Dependency:** `eta` is bundled as a direct dependency of `@getstrata/core
|
|
30
|
+
**Dependency:** `eta` is bundled as a direct dependency of `@getstrata/core`. Apps do not need to list it separately. The database driver is your app's choice. WorkHub and getstrata use **Bun's built-in `Bun.sql`** client; bind it with `bindDatabaseConnection()`.
|
|
29
31
|
|
|
30
|
-
`orderBy` accepts explicit `{ column, direction }` objects or
|
|
32
|
+
`orderBy` accepts explicit `{ column, direction }` objects or column shorthand such as `{ published_at: "desc" }`.
|
|
31
33
|
|
|
32
|
-
##
|
|
34
|
+
## Admin and queue helpers
|
|
35
|
+
|
|
36
|
+
Exports for admin dashboards and queue recovery:
|
|
37
|
+
|
|
38
|
+
- `AdminResourceRegistry`, `formatAdminValue`: read-only resource browsers
|
|
39
|
+
- `createFailedJobService`, `FailedJobService.delete()`: failed job persistence and cleanup
|
|
40
|
+
- `runQueueJob`, `jobRegistry`: dispatch retried jobs from admin UIs
|
|
41
|
+
|
|
42
|
+
## Build and verify (monorepo root)
|
|
33
43
|
|
|
34
44
|
```bash
|
|
35
45
|
bun run build:framework
|
|
@@ -41,9 +51,9 @@ bun run verify:framework # build + public API tests
|
|
|
41
51
|
Package name: **`@getstrata/core`** (npm org [`@getstrata`](https://www.npmjs.com/org/getstrata)).
|
|
42
52
|
|
|
43
53
|
1. Add `NPM_TOKEN` to GitHub repository secrets.
|
|
44
|
-
2. Tag a release: `git tag v0.
|
|
54
|
+
2. Tag a release: `git tag v0.5.13 && git push origin v0.5.13`
|
|
45
55
|
3. [Release workflow](../../.github/workflows/release.yml) builds and runs `npm publish --access public`.
|
|
46
56
|
|
|
47
|
-
Previously published as `@eyk-workhub/framework@0.1.0
|
|
57
|
+
Previously published as `@eyk-workhub/framework@0.1.0`, deprecated in favor of this package.
|
|
48
58
|
|
|
49
59
|
See [docs/PACKAGING.md](../../docs/PACKAGING.md) for boundaries and future extraction.
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import { type Middleware, type RouteHandler } from "../core/http/middleware";
|
|
1
|
+
import { type Middleware, type Policy, type RouteHandler } from "@getstrata/core";
|
|
3
2
|
import type { AppDependencies } from "./contracts";
|
|
4
3
|
type MiddlewareGroupName = "api" | "authenticated" | "web";
|
|
5
4
|
declare class HttpKernel {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AdminResource, AdminResourceDefinition } from "./types.ts";
|
|
2
2
|
declare class AdminResourceRegistry {
|
|
3
3
|
private readonly resources;
|
|
4
|
+
constructor();
|
|
4
5
|
register<TEntity extends object>(resource: AdminResource<TEntity>): void;
|
|
5
6
|
get(name: string): AdminResource<object> | undefined;
|
|
6
7
|
list(): AdminResourceDefinition[];
|
|
@@ -12,8 +12,9 @@ interface S3StorageConfig {
|
|
|
12
12
|
endpoint?: string;
|
|
13
13
|
}
|
|
14
14
|
declare class LocalStorageDriver implements StorageDriver {
|
|
15
|
-
private readonly rootDirectory
|
|
16
|
-
constructor(rootDirectory
|
|
15
|
+
private readonly rootDirectory?;
|
|
16
|
+
constructor(rootDirectory?: string | undefined);
|
|
17
|
+
private resolveRootDirectory;
|
|
17
18
|
private resolvePath;
|
|
18
19
|
put(path: string, contents: string | Uint8Array): Promise<string>;
|
|
19
20
|
get(path: string): Promise<Uint8Array | null>;
|
|
@@ -37,5 +38,7 @@ declare function resolveS3Config(): S3StorageConfig;
|
|
|
37
38
|
declare function createS3Client(config?: S3StorageConfig): S3Client;
|
|
38
39
|
declare function createStorageDriver(): StorageDriver;
|
|
39
40
|
declare function storage(): StorageManager;
|
|
41
|
+
/** Test hook: drop the process-wide storage singleton (e.g. after changing STORAGE_PATH). */
|
|
42
|
+
declare function resetDefaultStorage(): void;
|
|
40
43
|
export type { S3StorageConfig, StorageDriver };
|
|
41
|
-
export { createS3Client, createStorageDriver, LocalStorageDriver, resolveS3Config, S3StorageDriver, StorageManager, storage, };
|
|
44
|
+
export { createS3Client, createStorageDriver, LocalStorageDriver, resetDefaultStorage, resolveS3Config, S3StorageDriver, StorageManager, storage, };
|
|
@@ -9,8 +9,11 @@ class LocalStorageDriver {
|
|
|
9
9
|
constructor(rootDirectory) {
|
|
10
10
|
this.rootDirectory = rootDirectory;
|
|
11
11
|
}
|
|
12
|
+
resolveRootDirectory() {
|
|
13
|
+
return this.rootDirectory ?? process.env.STORAGE_PATH ?? "storage";
|
|
14
|
+
}
|
|
12
15
|
resolvePath(path) {
|
|
13
|
-
return join(this.
|
|
16
|
+
return join(this.resolveRootDirectory(), path.replace(/^\/+/, ""));
|
|
14
17
|
}
|
|
15
18
|
async put(path, contents) {
|
|
16
19
|
const absolutePath = this.resolvePath(path);
|
|
@@ -106,15 +109,22 @@ function createStorageDriver() {
|
|
|
106
109
|
if (driver === "s3") {
|
|
107
110
|
return new S3StorageDriver(createS3Client());
|
|
108
111
|
}
|
|
109
|
-
return new LocalStorageDriver
|
|
112
|
+
return new LocalStorageDriver;
|
|
110
113
|
}
|
|
111
|
-
var defaultStorage =
|
|
114
|
+
var defaultStorage = { current: null };
|
|
112
115
|
function storage() {
|
|
113
|
-
|
|
116
|
+
if (!defaultStorage.current) {
|
|
117
|
+
defaultStorage.current = new StorageManager(createStorageDriver());
|
|
118
|
+
}
|
|
119
|
+
return defaultStorage.current;
|
|
120
|
+
}
|
|
121
|
+
function resetDefaultStorage() {
|
|
122
|
+
defaultStorage.current = null;
|
|
114
123
|
}
|
|
115
124
|
export {
|
|
116
125
|
storage,
|
|
117
126
|
resolveS3Config,
|
|
127
|
+
resetDefaultStorage,
|
|
118
128
|
createStorageDriver,
|
|
119
129
|
createS3Client,
|
|
120
130
|
StorageManager,
|
|
@@ -10,6 +10,7 @@ export { AdminResourceRegistry, formatAdminValue, } from "../core/admin/index.ts
|
|
|
10
10
|
export type { AbilityChecker } from "../core/auth/abilityChecker.ts";
|
|
11
11
|
export type { AuthUser } from "../core/auth/authContext.ts";
|
|
12
12
|
export { currentAuthUser, runWithAuthUser } from "../core/auth/authContext.ts";
|
|
13
|
+
export { createMembershipMiddleware } from "../core/auth/membershipMiddleware.ts";
|
|
13
14
|
export { Policy, PolicyGate } from "../core/auth/policy.ts";
|
|
14
15
|
export { default as CacheRepository } from "../core/cache/repository.ts";
|
|
15
16
|
export { CACHE_TAGS } from "../core/cache/tags.ts";
|
|
@@ -40,16 +41,20 @@ export { EventBus } from "../core/events/eventBus.ts";
|
|
|
40
41
|
export { auth, cache, config, events, log, mail, policyGate, queue, storage, } from "../core/facades/index.ts";
|
|
41
42
|
export { createBodySizeLimitMiddleware } from "../core/http/bodySizeLimitMiddleware.ts";
|
|
42
43
|
export { readBunRequestCookie, readRequestCookie } from "../core/http/cookies.ts";
|
|
44
|
+
export { createCorsMiddleware } from "../core/http/corsMiddleware.ts";
|
|
43
45
|
export { createCsrfMiddleware } from "../core/http/csrfMiddleware.ts";
|
|
44
46
|
export { createCsrfProtection } from "../core/http/csrfProtection.ts";
|
|
45
47
|
export { createCsrfTokenCookie, readSubmittedCsrfToken, readSubmittedCsrfTokenFromBody, resolveCsrfToken, resolveCsrfTokenForRequest, verifyCsrfToken, } from "../core/http/csrfToken.ts";
|
|
46
48
|
export { assertIfMatch, etagFromResource, isEtagEnabled, } from "../core/http/etag.ts";
|
|
49
|
+
export { createFlashMiddleware } from "../core/http/flashMiddleware.ts";
|
|
47
50
|
export { FormRequest } from "../core/http/formRequest.ts";
|
|
48
|
-
export { applyMiddlewareToRoutes, composeMiddleware, createAuthMiddleware, createAuthorizeMiddleware, createdResponse, createRequireAuthMiddleware, jsonResponse, noContentResponse, paginatedResponse, parsePaginationQuery, securedBindRouteModel, securedBindRouteModelByKey, withErrorHandling, withMiddleware, } from "../core/http/index.ts";
|
|
51
|
+
export { applyMiddlewareToRoutes, composeMiddleware, createAuthMiddleware, createAuthorizeMiddleware, createdResponse, createRequireAuthMiddleware, jsonResponse, noContentResponse, paginatedResponse, parsePaginationQuery, requestIdMiddleware, securedBindRouteModel, securedBindRouteModelByKey, withErrorHandling, withMiddleware, } from "../core/http/index.ts";
|
|
49
52
|
export { createLoginThrottleMiddleware } from "../core/http/loginThrottleMiddleware.ts";
|
|
50
53
|
export { createMemoryThrottleMiddleware } from "../core/http/memoryThrottleMiddleware.ts";
|
|
51
54
|
export { createMetricsMiddleware, normalizeMetricPath } from "../core/http/metricsMiddleware.ts";
|
|
52
55
|
export type { Middleware, RouteHandler } from "../core/http/middleware.ts";
|
|
56
|
+
export { createRequireAbilityMiddleware } from "../core/http/requireAbilityMiddleware.ts";
|
|
57
|
+
export { createRequireGlobalAdminMiddleware } from "../core/http/requireGlobalAdminMiddleware.ts";
|
|
53
58
|
export { createRequireWebAuthMiddleware } from "../core/http/requireWebAuthMiddleware.ts";
|
|
54
59
|
export { serializeDate, toPaginatedResourceCollection, toResourceCollection, } from "../core/http/resources.ts";
|
|
55
60
|
export type { RouteRequest } from "../core/http/route.ts";
|
|
@@ -57,6 +62,7 @@ export { createSecurityHeadersMiddleware } from "../core/http/securityHeadersMid
|
|
|
57
62
|
export { createThrottleMiddleware } from "../core/http/throttleMiddleware.ts";
|
|
58
63
|
export { WebFormRequest } from "../core/http/webFormRequest.ts";
|
|
59
64
|
export { installGracefulShutdownSignals, registerShutdownHandler, runGracefulShutdown, } from "../core/lifecycle/gracefulShutdown.ts";
|
|
65
|
+
export { createRequestLoggingMiddleware } from "../core/logging/requestLoggingMiddleware.ts";
|
|
60
66
|
export type { MailDriver, MailMessage } from "../core/mail/mailer.ts";
|
|
61
67
|
export { buildSmtpPayload, LogMailDriver, Mailer, mailer, } from "../core/mail/mailer.ts";
|
|
62
68
|
export type { MarkdownMailLayoutOptions, RenderedMarkdownMail } from "../core/mail/markdownMail.ts";
|
|
@@ -73,8 +79,11 @@ export { AsyncQueue, createQueue, Job, SyncQueue } from "../core/queue/index.ts"
|
|
|
73
79
|
export { createFailedJobService, createProductionQueue, createQueueWorker, createTrackedJob, FailedJobRepository, FailedJobService, jobRegistry, QueueWorker, RedisQueue, ResilientQueue, runQueueJob, } from "../core/queue/publicQueue.ts";
|
|
74
80
|
export type { ScheduledTask } from "../core/scheduler/schedule.ts";
|
|
75
81
|
export { appSchedule, runDueScheduledTasks, Schedule } from "../core/scheduler/schedule.ts";
|
|
82
|
+
export { isPublicReadsEnabled } from "../core/security/publicReads.ts";
|
|
76
83
|
export type { StorageDriver } from "../core/storage/storage.ts";
|
|
77
|
-
export { LocalStorageDriver, StorageManager } from "../core/storage/storage.ts";
|
|
84
|
+
export { LocalStorageDriver, resetDefaultStorage, StorageManager, } from "../core/storage/storage.ts";
|
|
85
|
+
export { createTenantMiddleware } from "../core/tenant/tenantMiddleware.ts";
|
|
86
|
+
export { createTracingMiddleware } from "../core/tracing/tracingMiddleware.ts";
|
|
78
87
|
export type { ValidationRule, ValidationSchema } from "../core/validation/rules.ts";
|
|
79
88
|
export { emailRule, maxLength, minLength, required, stringRule, validateObject, } from "../core/validation/rules.ts";
|
|
80
89
|
export { DEFAULT_VIEWS_DIRECTORY, EtaViewEngine, htmlResponse, isHtmxRequest, resolveWebLayoutData, } from "../core/view/index.ts";
|
package/dist/index.js
CHANGED
|
@@ -180,6 +180,7 @@ function formatAdminValue(value, type = "text") {
|
|
|
180
180
|
// ../../src/core/admin/registry.ts
|
|
181
181
|
class AdminResourceRegistry {
|
|
182
182
|
resources = new Map;
|
|
183
|
+
constructor() {}
|
|
183
184
|
register(resource) {
|
|
184
185
|
if (this.resources.has(resource.name)) {
|
|
185
186
|
throw new Error(`Admin resource "${resource.name}" is already registered.`);
|
|
@@ -190,7 +191,12 @@ class AdminResourceRegistry {
|
|
|
190
191
|
return this.resources.get(name);
|
|
191
192
|
}
|
|
192
193
|
list() {
|
|
193
|
-
|
|
194
|
+
const definitions = [];
|
|
195
|
+
for (const resource of this.resources.values()) {
|
|
196
|
+
const { handlers: _handlers, ...definition } = resource;
|
|
197
|
+
definitions.push(definition);
|
|
198
|
+
}
|
|
199
|
+
return definitions;
|
|
194
200
|
}
|
|
195
201
|
all() {
|
|
196
202
|
return [...this.resources.values()];
|
|
@@ -208,6 +214,130 @@ function runWithAuthUser(user, callback) {
|
|
|
208
214
|
function currentAuthUser() {
|
|
209
215
|
return authContext.getStore() ?? null;
|
|
210
216
|
}
|
|
217
|
+
// ../../src/core/auth/membershipContext.ts
|
|
218
|
+
import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
|
|
219
|
+
|
|
220
|
+
// ../../src/config/database.ts
|
|
221
|
+
function readInteger(name, fallback) {
|
|
222
|
+
const parsed = Number.parseInt(process.env[name] ?? String(fallback), 10);
|
|
223
|
+
return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
|
|
224
|
+
}
|
|
225
|
+
var databaseConfig = {
|
|
226
|
+
url: process.env.DATABASE_URL ?? "",
|
|
227
|
+
poolMax: readInteger("DB_POOL_MAX", 10),
|
|
228
|
+
idleTimeoutSeconds: readInteger("DB_POOL_IDLE_TIMEOUT", 30),
|
|
229
|
+
maxLifetimeSeconds: readInteger("DB_POOL_MAX_LIFETIME", 3600),
|
|
230
|
+
connectionTimeoutSeconds: readInteger("DB_CONNECTION_TIMEOUT", 10)
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// ../../src/core/database/connectionContext.ts
|
|
234
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
235
|
+
var activeConnection = new AsyncLocalStorage2;
|
|
236
|
+
function runWithDatabaseConnection(connection, callback) {
|
|
237
|
+
return activeConnection.run(connection, callback);
|
|
238
|
+
}
|
|
239
|
+
function getActiveDatabaseConnection(fallback) {
|
|
240
|
+
return activeConnection.getStore() ?? fallback;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ../../src/db/connection/createConnection.ts
|
|
244
|
+
var {SQL } = globalThis.Bun;
|
|
245
|
+
function createDatabaseConnection(config) {
|
|
246
|
+
if (!config.url) {
|
|
247
|
+
throw new Error("DATABASE_URL is not configured. Set DATABASE_URL before starting the app or running integration tests.");
|
|
248
|
+
}
|
|
249
|
+
return new SQL({
|
|
250
|
+
url: config.url,
|
|
251
|
+
max: config.poolMax,
|
|
252
|
+
idleTimeout: config.idleTimeoutSeconds,
|
|
253
|
+
maxLifetime: config.maxLifetimeSeconds,
|
|
254
|
+
connectionTimeout: config.connectionTimeoutSeconds
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ../../src/db/connection/index.ts
|
|
259
|
+
var connectionHolder = {
|
|
260
|
+
connection: null
|
|
261
|
+
};
|
|
262
|
+
function getDatabase() {
|
|
263
|
+
if (!connectionHolder.connection) {
|
|
264
|
+
connectionHolder.connection = createDatabaseConnection(databaseConfig);
|
|
265
|
+
}
|
|
266
|
+
return connectionHolder.connection;
|
|
267
|
+
}
|
|
268
|
+
var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
|
|
269
|
+
function resolveDatabase() {
|
|
270
|
+
return getActiveDatabaseConnection(getDatabase());
|
|
271
|
+
}
|
|
272
|
+
function resolveDatabaseForProperty(property) {
|
|
273
|
+
if (typeof property === "string" && POOL_CONNECTION_METHODS.has(property)) {
|
|
274
|
+
return getDatabase();
|
|
275
|
+
}
|
|
276
|
+
return resolveDatabase();
|
|
277
|
+
}
|
|
278
|
+
var db = new Proxy(function database() {}, {
|
|
279
|
+
apply(_target, _thisArg, args) {
|
|
280
|
+
return resolveDatabase()(...args);
|
|
281
|
+
},
|
|
282
|
+
get(_target, property) {
|
|
283
|
+
const connection = resolveDatabaseForProperty(property);
|
|
284
|
+
const value = connection[property];
|
|
285
|
+
return typeof value === "function" ? value.bind(connection) : value;
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
var connection_default = db;
|
|
289
|
+
|
|
290
|
+
// ../../src/modules/organization/memberRepository.ts
|
|
291
|
+
class OrganizationMemberRepository {
|
|
292
|
+
constructor() {}
|
|
293
|
+
async findMembership(userId, organizationId) {
|
|
294
|
+
const rows = await connection_default`
|
|
295
|
+
SELECT id, organization_id, user_id, role, created_at
|
|
296
|
+
FROM organization_member
|
|
297
|
+
WHERE user_id = ${userId} AND organization_id = ${organizationId}
|
|
298
|
+
LIMIT 1
|
|
299
|
+
`;
|
|
300
|
+
return rows[0] ?? null;
|
|
301
|
+
}
|
|
302
|
+
async listForUser(userId) {
|
|
303
|
+
return await connection_default`
|
|
304
|
+
SELECT id, organization_id, user_id, role, created_at
|
|
305
|
+
FROM organization_member
|
|
306
|
+
WHERE user_id = ${userId}
|
|
307
|
+
ORDER BY organization_id
|
|
308
|
+
`;
|
|
309
|
+
}
|
|
310
|
+
async listForOrganization(organizationId) {
|
|
311
|
+
return await connection_default`
|
|
312
|
+
SELECT id, organization_id, user_id, role, created_at
|
|
313
|
+
FROM organization_member
|
|
314
|
+
WHERE organization_id = ${organizationId}
|
|
315
|
+
ORDER BY id
|
|
316
|
+
`;
|
|
317
|
+
}
|
|
318
|
+
async addMember(input) {
|
|
319
|
+
const rows = await connection_default`
|
|
320
|
+
INSERT INTO organization_member (organization_id, user_id, role)
|
|
321
|
+
VALUES (${input.organizationId}, ${input.userId}, ${input.role ?? "member"})
|
|
322
|
+
RETURNING id, organization_id, user_id, role, created_at
|
|
323
|
+
`;
|
|
324
|
+
const row = rows[0];
|
|
325
|
+
if (!row) {
|
|
326
|
+
throw new Error("Organization member insert did not return a row.");
|
|
327
|
+
}
|
|
328
|
+
return row;
|
|
329
|
+
}
|
|
330
|
+
async removeMember(organizationId, userId) {
|
|
331
|
+
const rows = await connection_default`
|
|
332
|
+
DELETE FROM organization_member
|
|
333
|
+
WHERE organization_id = ${organizationId} AND user_id = ${userId}
|
|
334
|
+
RETURNING id
|
|
335
|
+
`;
|
|
336
|
+
return rows.length > 0;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
var memberRepository_default = OrganizationMemberRepository;
|
|
340
|
+
|
|
211
341
|
// ../../src/core/errors/http.ts
|
|
212
342
|
class HttpError extends Error {
|
|
213
343
|
status;
|
|
@@ -274,6 +404,84 @@ class PreconditionFailedError extends HttpError {
|
|
|
274
404
|
}
|
|
275
405
|
}
|
|
276
406
|
|
|
407
|
+
// ../../src/core/auth/accessControl.ts
|
|
408
|
+
var ROLE_RANK = {
|
|
409
|
+
member: 1,
|
|
410
|
+
admin: 2,
|
|
411
|
+
owner: 3
|
|
412
|
+
};
|
|
413
|
+
function isGlobalAdmin(user) {
|
|
414
|
+
return user?.role === "admin";
|
|
415
|
+
}
|
|
416
|
+
function hasMinimumOrgRole(role, minimum) {
|
|
417
|
+
if (!role) {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
return ROLE_RANK[role] >= ROLE_RANK[minimum];
|
|
421
|
+
}
|
|
422
|
+
function requireAuthenticatedUser() {
|
|
423
|
+
const user = currentAuthUser();
|
|
424
|
+
if (!user) {
|
|
425
|
+
throw new ForbiddenError("Authentication required.");
|
|
426
|
+
}
|
|
427
|
+
return user;
|
|
428
|
+
}
|
|
429
|
+
function resolveUserId(user) {
|
|
430
|
+
const userId = typeof user.id === "number" ? user.id : Number(user.id);
|
|
431
|
+
if (!Number.isInteger(userId) || userId <= 0) {
|
|
432
|
+
throw new ForbiddenError("Invalid authenticated user.");
|
|
433
|
+
}
|
|
434
|
+
return userId;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// ../../src/core/auth/membershipContext.ts
|
|
438
|
+
var membershipContext = new AsyncLocalStorage3;
|
|
439
|
+
var membershipRepository = new memberRepository_default;
|
|
440
|
+
async function runWithMembershipContext(callback) {
|
|
441
|
+
const user = currentAuthUser();
|
|
442
|
+
if (!user || isGlobalAdmin(user)) {
|
|
443
|
+
return await callback();
|
|
444
|
+
}
|
|
445
|
+
const memberships = await membershipRepository.listForUser(resolveUserId(user));
|
|
446
|
+
const context = {
|
|
447
|
+
organizationIds: memberships.map((membership) => membership.organization_id),
|
|
448
|
+
rolesByOrganizationId: new Map(memberships.map((membership) => [membership.organization_id, membership.role]))
|
|
449
|
+
};
|
|
450
|
+
return await membershipContext.run(context, callback);
|
|
451
|
+
}
|
|
452
|
+
function currentOrgRole(organizationId) {
|
|
453
|
+
return membershipContext.getStore()?.rolesByOrganizationId.get(organizationId) ?? null;
|
|
454
|
+
}
|
|
455
|
+
function hasOrgMembership(organizationId) {
|
|
456
|
+
return currentOrgRole(organizationId) !== null;
|
|
457
|
+
}
|
|
458
|
+
function currentOrganizationIds() {
|
|
459
|
+
return membershipContext.getStore()?.organizationIds ?? [];
|
|
460
|
+
}
|
|
461
|
+
function hasMinimumOrgRole2(organizationId, minimum) {
|
|
462
|
+
const role = currentOrgRole(organizationId);
|
|
463
|
+
if (!role) {
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
const ranks = {
|
|
467
|
+
member: 1,
|
|
468
|
+
admin: 2,
|
|
469
|
+
owner: 3
|
|
470
|
+
};
|
|
471
|
+
return ranks[role] >= ranks[minimum];
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// ../../src/core/auth/membershipContextMiddleware.ts
|
|
475
|
+
function createMembershipContextMiddleware() {
|
|
476
|
+
return async (_request, next) => {
|
|
477
|
+
return await runWithMembershipContext(async () => await next());
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// ../../src/core/auth/membershipMiddleware.ts
|
|
482
|
+
function createMembershipMiddleware() {
|
|
483
|
+
return createMembershipContextMiddleware();
|
|
484
|
+
}
|
|
277
485
|
// ../../src/core/auth/policy.ts
|
|
278
486
|
class Policy {
|
|
279
487
|
constructor() {}
|
|
@@ -1053,76 +1261,6 @@ function indexMorphToRelation(children, parentsByType, relation) {
|
|
|
1053
1261
|
return result;
|
|
1054
1262
|
}
|
|
1055
1263
|
|
|
1056
|
-
// ../../src/config/database.ts
|
|
1057
|
-
function readInteger(name, fallback) {
|
|
1058
|
-
const parsed = Number.parseInt(process.env[name] ?? String(fallback), 10);
|
|
1059
|
-
return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
|
|
1060
|
-
}
|
|
1061
|
-
var databaseConfig = {
|
|
1062
|
-
url: process.env.DATABASE_URL ?? "",
|
|
1063
|
-
poolMax: readInteger("DB_POOL_MAX", 10),
|
|
1064
|
-
idleTimeoutSeconds: readInteger("DB_POOL_IDLE_TIMEOUT", 30),
|
|
1065
|
-
maxLifetimeSeconds: readInteger("DB_POOL_MAX_LIFETIME", 3600),
|
|
1066
|
-
connectionTimeoutSeconds: readInteger("DB_CONNECTION_TIMEOUT", 10)
|
|
1067
|
-
};
|
|
1068
|
-
|
|
1069
|
-
// ../../src/core/database/connectionContext.ts
|
|
1070
|
-
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
1071
|
-
var activeConnection = new AsyncLocalStorage2;
|
|
1072
|
-
function runWithDatabaseConnection(connection, callback) {
|
|
1073
|
-
return activeConnection.run(connection, callback);
|
|
1074
|
-
}
|
|
1075
|
-
function getActiveDatabaseConnection(fallback) {
|
|
1076
|
-
return activeConnection.getStore() ?? fallback;
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
// ../../src/db/connection/createConnection.ts
|
|
1080
|
-
var {SQL } = globalThis.Bun;
|
|
1081
|
-
function createDatabaseConnection(config) {
|
|
1082
|
-
if (!config.url) {
|
|
1083
|
-
throw new Error("DATABASE_URL is not configured. Set DATABASE_URL before starting the app or running integration tests.");
|
|
1084
|
-
}
|
|
1085
|
-
return new SQL({
|
|
1086
|
-
url: config.url,
|
|
1087
|
-
max: config.poolMax,
|
|
1088
|
-
idleTimeout: config.idleTimeoutSeconds,
|
|
1089
|
-
maxLifetime: config.maxLifetimeSeconds,
|
|
1090
|
-
connectionTimeout: config.connectionTimeoutSeconds
|
|
1091
|
-
});
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
// ../../src/db/connection/index.ts
|
|
1095
|
-
var connectionHolder = {
|
|
1096
|
-
connection: null
|
|
1097
|
-
};
|
|
1098
|
-
function getDatabase() {
|
|
1099
|
-
if (!connectionHolder.connection) {
|
|
1100
|
-
connectionHolder.connection = createDatabaseConnection(databaseConfig);
|
|
1101
|
-
}
|
|
1102
|
-
return connectionHolder.connection;
|
|
1103
|
-
}
|
|
1104
|
-
var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
|
|
1105
|
-
function resolveDatabase() {
|
|
1106
|
-
return getActiveDatabaseConnection(getDatabase());
|
|
1107
|
-
}
|
|
1108
|
-
function resolveDatabaseForProperty(property) {
|
|
1109
|
-
if (typeof property === "string" && POOL_CONNECTION_METHODS.has(property)) {
|
|
1110
|
-
return getDatabase();
|
|
1111
|
-
}
|
|
1112
|
-
return resolveDatabase();
|
|
1113
|
-
}
|
|
1114
|
-
var db = new Proxy(function database() {}, {
|
|
1115
|
-
apply(_target, _thisArg, args) {
|
|
1116
|
-
return resolveDatabase()(...args);
|
|
1117
|
-
},
|
|
1118
|
-
get(_target, property) {
|
|
1119
|
-
const connection = resolveDatabaseForProperty(property);
|
|
1120
|
-
const value = connection[property];
|
|
1121
|
-
return typeof value === "function" ? value.bind(connection) : value;
|
|
1122
|
-
}
|
|
1123
|
-
});
|
|
1124
|
-
var connection_default = db;
|
|
1125
|
-
|
|
1126
1264
|
// ../../src/core/database/boundConnection.ts
|
|
1127
1265
|
var boundConnectionHolder = {
|
|
1128
1266
|
connection: null
|
|
@@ -3012,8 +3150,11 @@ class LocalStorageDriver {
|
|
|
3012
3150
|
constructor(rootDirectory) {
|
|
3013
3151
|
this.rootDirectory = rootDirectory;
|
|
3014
3152
|
}
|
|
3153
|
+
resolveRootDirectory() {
|
|
3154
|
+
return this.rootDirectory ?? process.env.STORAGE_PATH ?? "storage";
|
|
3155
|
+
}
|
|
3015
3156
|
resolvePath(path) {
|
|
3016
|
-
return join3(this.
|
|
3157
|
+
return join3(this.resolveRootDirectory(), path.replace(/^\/+/, ""));
|
|
3017
3158
|
}
|
|
3018
3159
|
async put(path, contents) {
|
|
3019
3160
|
const absolutePath = this.resolvePath(path);
|
|
@@ -3109,11 +3250,17 @@ function createStorageDriver() {
|
|
|
3109
3250
|
if (driver === "s3") {
|
|
3110
3251
|
return new S3StorageDriver(createS3Client());
|
|
3111
3252
|
}
|
|
3112
|
-
return new LocalStorageDriver
|
|
3253
|
+
return new LocalStorageDriver;
|
|
3113
3254
|
}
|
|
3114
|
-
var defaultStorage =
|
|
3255
|
+
var defaultStorage = { current: null };
|
|
3115
3256
|
function storage() {
|
|
3116
|
-
|
|
3257
|
+
if (!defaultStorage.current) {
|
|
3258
|
+
defaultStorage.current = new StorageManager(createStorageDriver());
|
|
3259
|
+
}
|
|
3260
|
+
return defaultStorage.current;
|
|
3261
|
+
}
|
|
3262
|
+
function resetDefaultStorage() {
|
|
3263
|
+
defaultStorage.current = null;
|
|
3117
3264
|
}
|
|
3118
3265
|
|
|
3119
3266
|
// ../../src/core/facades/index.ts
|
|
@@ -3197,12 +3344,62 @@ function readRequestCookie(request, name) {
|
|
|
3197
3344
|
function readBunRequestCookie(request, name) {
|
|
3198
3345
|
return request.cookies.get(name) ?? readRequestCookie(request, name);
|
|
3199
3346
|
}
|
|
3347
|
+
// ../../src/config/cors.ts
|
|
3348
|
+
var corsConfig = {
|
|
3349
|
+
allowedOrigins: (process.env.CORS_ALLOWED_ORIGINS ?? "*").split(",").map((origin) => origin.trim()).filter(Boolean),
|
|
3350
|
+
allowedMethods: ["GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"],
|
|
3351
|
+
allowedHeaders: [
|
|
3352
|
+
"Authorization",
|
|
3353
|
+
"Content-Type",
|
|
3354
|
+
"X-Request-Id",
|
|
3355
|
+
"X-Tenant-Id",
|
|
3356
|
+
"X-Authenticated-User-Id",
|
|
3357
|
+
"X-Authenticated-User-Role",
|
|
3358
|
+
"If-Match",
|
|
3359
|
+
"If-None-Match"
|
|
3360
|
+
],
|
|
3361
|
+
maxAgeSeconds: 86400
|
|
3362
|
+
};
|
|
3363
|
+
|
|
3364
|
+
// ../../src/core/http/corsMiddleware.ts
|
|
3365
|
+
function createCorsMiddleware() {
|
|
3366
|
+
return async (request, next) => {
|
|
3367
|
+
if (request.method === "OPTIONS") {
|
|
3368
|
+
return new Response(null, {
|
|
3369
|
+
status: 204,
|
|
3370
|
+
headers: buildCorsHeaders(request)
|
|
3371
|
+
});
|
|
3372
|
+
}
|
|
3373
|
+
const response = await next();
|
|
3374
|
+
const headers = new Headers(response.headers);
|
|
3375
|
+
for (const [key, value] of buildCorsHeaders(request)) {
|
|
3376
|
+
headers.set(key, value);
|
|
3377
|
+
}
|
|
3378
|
+
return new Response(response.body, {
|
|
3379
|
+
status: response.status,
|
|
3380
|
+
statusText: response.statusText,
|
|
3381
|
+
headers
|
|
3382
|
+
});
|
|
3383
|
+
};
|
|
3384
|
+
}
|
|
3385
|
+
function buildCorsHeaders(request) {
|
|
3386
|
+
const headers = new Headers;
|
|
3387
|
+
const origin = request.headers.get("origin");
|
|
3388
|
+
const allowedOrigins = corsConfig.allowedOrigins;
|
|
3389
|
+
const allowOrigin = allowedOrigins.includes("*") || origin && allowedOrigins.includes(origin) ? origin ?? "*" : allowedOrigins[0] ?? "*";
|
|
3390
|
+
headers.set("Access-Control-Allow-Origin", allowOrigin);
|
|
3391
|
+
headers.set("Access-Control-Allow-Methods", corsConfig.allowedMethods.join(", "));
|
|
3392
|
+
headers.set("Access-Control-Allow-Headers", corsConfig.allowedHeaders.join(", "));
|
|
3393
|
+
headers.set("Access-Control-Max-Age", String(corsConfig.maxAgeSeconds));
|
|
3394
|
+
headers.set("Vary", "Origin");
|
|
3395
|
+
return headers;
|
|
3396
|
+
}
|
|
3200
3397
|
// ../../src/core/http/csrfToken.ts
|
|
3201
3398
|
import { timingSafeEqual } from "crypto";
|
|
3202
3399
|
|
|
3203
3400
|
// ../../src/core/http/requestMetaContext.ts
|
|
3204
|
-
import { AsyncLocalStorage as
|
|
3205
|
-
var requestMetaContext = new
|
|
3401
|
+
import { AsyncLocalStorage as AsyncLocalStorage4 } from "async_hooks";
|
|
3402
|
+
var requestMetaContext = new AsyncLocalStorage4;
|
|
3206
3403
|
function runWithRequestMeta(meta, callback) {
|
|
3207
3404
|
return requestMetaContext.run(meta, callback);
|
|
3208
3405
|
}
|
|
@@ -3434,9 +3631,122 @@ function applyConditionalGet(request, response, etag) {
|
|
|
3434
3631
|
headers
|
|
3435
3632
|
});
|
|
3436
3633
|
}
|
|
3634
|
+
// ../../src/core/http/flashSession.ts
|
|
3635
|
+
import { createHmac, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
3636
|
+
var FLASH_COOKIE = "workhub_flash";
|
|
3637
|
+
var FLASH_TTL_MS = 60 * 1000;
|
|
3638
|
+
function resolveFlashSecret() {
|
|
3639
|
+
return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || "workhub-dev-flash-secret";
|
|
3640
|
+
}
|
|
3641
|
+
function signFlashPayload(payload, issuedAt) {
|
|
3642
|
+
const signature = createHmac("sha256", resolveFlashSecret()).update(`${payload}.${issuedAt}`).digest("hex");
|
|
3643
|
+
return `${payload}.${issuedAt}.${signature}`;
|
|
3644
|
+
}
|
|
3645
|
+
function readFlashCookie(request) {
|
|
3646
|
+
const cookieHeader = request.headers.get("cookie");
|
|
3647
|
+
if (!cookieHeader) {
|
|
3648
|
+
return null;
|
|
3649
|
+
}
|
|
3650
|
+
for (const part of cookieHeader.split(";")) {
|
|
3651
|
+
const [name, ...rest] = part.trim().split("=");
|
|
3652
|
+
if (name === FLASH_COOKIE) {
|
|
3653
|
+
return decodeURIComponent(rest.join("="));
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
return null;
|
|
3657
|
+
}
|
|
3658
|
+
function parseFlashCookie(cookieValue) {
|
|
3659
|
+
const parts = cookieValue.split(".");
|
|
3660
|
+
if (parts.length < 3) {
|
|
3661
|
+
return null;
|
|
3662
|
+
}
|
|
3663
|
+
const signature = parts.pop();
|
|
3664
|
+
const issuedAtRaw = parts.pop();
|
|
3665
|
+
const payload = parts.join(".");
|
|
3666
|
+
if (!signature || !issuedAtRaw || !payload) {
|
|
3667
|
+
return null;
|
|
3668
|
+
}
|
|
3669
|
+
const issuedAt = Number.parseInt(issuedAtRaw, 10);
|
|
3670
|
+
if (!Number.isFinite(issuedAt) || Date.now() - issuedAt > FLASH_TTL_MS) {
|
|
3671
|
+
return null;
|
|
3672
|
+
}
|
|
3673
|
+
const expectedSignature = signFlashPayload(payload, issuedAt).split(".").pop();
|
|
3674
|
+
if (!expectedSignature) {
|
|
3675
|
+
return null;
|
|
3676
|
+
}
|
|
3677
|
+
const expectedBuffer = Buffer.from(expectedSignature);
|
|
3678
|
+
const actualBuffer = Buffer.from(signature);
|
|
3679
|
+
if (expectedBuffer.length !== actualBuffer.length) {
|
|
3680
|
+
return null;
|
|
3681
|
+
}
|
|
3682
|
+
if (!timingSafeEqual2(expectedBuffer, actualBuffer)) {
|
|
3683
|
+
return null;
|
|
3684
|
+
}
|
|
3685
|
+
try {
|
|
3686
|
+
const parsed = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
3687
|
+
if (!parsed?.message || typeof parsed.message !== "string") {
|
|
3688
|
+
return null;
|
|
3689
|
+
}
|
|
3690
|
+
if (parsed.level !== "success" && parsed.level !== "error" && parsed.level !== "info") {
|
|
3691
|
+
return null;
|
|
3692
|
+
}
|
|
3693
|
+
return parsed;
|
|
3694
|
+
} catch {
|
|
3695
|
+
return null;
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3698
|
+
function createFlashCookie(message) {
|
|
3699
|
+
const payload = Buffer.from(JSON.stringify(message), "utf8").toString("base64url");
|
|
3700
|
+
const issuedAt = Date.now();
|
|
3701
|
+
const value = signFlashPayload(payload, issuedAt);
|
|
3702
|
+
return `${FLASH_COOKIE}=${encodeURIComponent(value)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=60`;
|
|
3703
|
+
}
|
|
3704
|
+
function clearFlashCookie() {
|
|
3705
|
+
return `${FLASH_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`;
|
|
3706
|
+
}
|
|
3707
|
+
function pullFlash(request) {
|
|
3708
|
+
const cookieValue = readFlashCookie(request);
|
|
3709
|
+
if (!cookieValue) {
|
|
3710
|
+
return null;
|
|
3711
|
+
}
|
|
3712
|
+
return parseFlashCookie(cookieValue);
|
|
3713
|
+
}
|
|
3714
|
+
function flashResponse(response, message) {
|
|
3715
|
+
const headers = new Headers(response.headers);
|
|
3716
|
+
headers.append("set-cookie", createFlashCookie(message));
|
|
3717
|
+
return new Response(response.body, {
|
|
3718
|
+
status: response.status,
|
|
3719
|
+
statusText: response.statusText,
|
|
3720
|
+
headers
|
|
3721
|
+
});
|
|
3722
|
+
}
|
|
3723
|
+
function withFlashClear(response) {
|
|
3724
|
+
const headers = new Headers(response.headers);
|
|
3725
|
+
headers.append("set-cookie", clearFlashCookie());
|
|
3726
|
+
return new Response(response.body, {
|
|
3727
|
+
status: response.status,
|
|
3728
|
+
statusText: response.statusText,
|
|
3729
|
+
headers
|
|
3730
|
+
});
|
|
3731
|
+
}
|
|
3732
|
+
|
|
3733
|
+
// ../../src/core/http/flashMiddleware.ts
|
|
3734
|
+
function createFlashMiddleware() {
|
|
3735
|
+
return async (request, next) => {
|
|
3736
|
+
const flash = pullFlash(request);
|
|
3737
|
+
const meta = currentRequestMeta();
|
|
3738
|
+
return await runWithRequestMeta({ ...meta, request, flash }, async () => {
|
|
3739
|
+
const response = await next();
|
|
3740
|
+
if (flash) {
|
|
3741
|
+
return withFlashClear(response);
|
|
3742
|
+
}
|
|
3743
|
+
return response;
|
|
3744
|
+
});
|
|
3745
|
+
};
|
|
3746
|
+
}
|
|
3437
3747
|
// ../../src/core/tenant/tenantContext.ts
|
|
3438
|
-
import { AsyncLocalStorage as
|
|
3439
|
-
var tenantContext = new
|
|
3748
|
+
import { AsyncLocalStorage as AsyncLocalStorage5 } from "async_hooks";
|
|
3749
|
+
var tenantContext = new AsyncLocalStorage5;
|
|
3440
3750
|
function runWithTenant(tenant, callback) {
|
|
3441
3751
|
return tenantContext.run(tenant, callback);
|
|
3442
3752
|
}
|
|
@@ -3846,7 +4156,7 @@ async function verifyPassword(password, passwordHash) {
|
|
|
3846
4156
|
}
|
|
3847
4157
|
|
|
3848
4158
|
// ../../src/core/crypto/fieldEncryption.ts
|
|
3849
|
-
import { createCipheriv, createDecipheriv, createHmac, randomBytes } from "crypto";
|
|
4159
|
+
import { createCipheriv, createDecipheriv, createHmac as createHmac2, randomBytes } from "crypto";
|
|
3850
4160
|
var ENCRYPTION_PREFIX = "enc:v1:";
|
|
3851
4161
|
var IV_LENGTH = 12;
|
|
3852
4162
|
var TAG_LENGTH = 16;
|
|
@@ -3895,7 +4205,7 @@ function decryptField(value, key) {
|
|
|
3895
4205
|
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
|
|
3896
4206
|
}
|
|
3897
4207
|
function hashLookupValue(normalizedValue, key) {
|
|
3898
|
-
return
|
|
4208
|
+
return createHmac2("sha256", key).update(normalizedValue).digest("hex");
|
|
3899
4209
|
}
|
|
3900
4210
|
function normalizeEmail(email) {
|
|
3901
4211
|
return email.trim().toLowerCase();
|
|
@@ -3975,7 +4285,7 @@ function resolveDefaultTokenExpiryDays() {
|
|
|
3975
4285
|
}
|
|
3976
4286
|
|
|
3977
4287
|
// ../../src/core/security/totp.ts
|
|
3978
|
-
import { createHmac as
|
|
4288
|
+
import { createHmac as createHmac3 } from "crypto";
|
|
3979
4289
|
function decodeBase32(input) {
|
|
3980
4290
|
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
3981
4291
|
const normalized = input.replace(/=+$/u, "").toUpperCase();
|
|
@@ -3997,7 +4307,7 @@ function generateTotp(secret, counter, digits = 6) {
|
|
|
3997
4307
|
const key = decodeBase32(secret);
|
|
3998
4308
|
const buffer = Buffer.alloc(8);
|
|
3999
4309
|
buffer.writeBigUInt64BE(BigInt(counter));
|
|
4000
|
-
const digest =
|
|
4310
|
+
const digest = createHmac3("sha1", key).update(buffer).digest();
|
|
4001
4311
|
const lastByte = digest[digest.length - 1] ?? 0;
|
|
4002
4312
|
const offset = lastByte & 15;
|
|
4003
4313
|
const b0 = digest[offset] ?? 0;
|
|
@@ -4189,14 +4499,14 @@ var userTable = defineTable({
|
|
|
4189
4499
|
});
|
|
4190
4500
|
|
|
4191
4501
|
// ../../src/core/auth/tokenHash.ts
|
|
4192
|
-
import { createHash, createHmac as
|
|
4502
|
+
import { createHash, createHmac as createHmac4 } from "crypto";
|
|
4193
4503
|
function resolveTokenPepper() {
|
|
4194
4504
|
return process.env.TOKEN_HASH_PEPPER?.trim() ?? "workhub-dev-token-pepper";
|
|
4195
4505
|
}
|
|
4196
4506
|
function hashApiToken(token) {
|
|
4197
4507
|
const pepper = resolveTokenPepper();
|
|
4198
4508
|
if (pepper && pepper !== "workhub-dev-token-pepper") {
|
|
4199
|
-
return
|
|
4509
|
+
return createHmac4("sha256", pepper).update(token).digest("hex");
|
|
4200
4510
|
}
|
|
4201
4511
|
return createHash("sha256").update(token).digest("hex");
|
|
4202
4512
|
}
|
|
@@ -4204,105 +4514,6 @@ function hashApiToken(token) {
|
|
|
4204
4514
|
// ../../src/modules/user/provider.ts
|
|
4205
4515
|
var tokenServiceToken = CORE_TOKEN_SERVICE_TOKEN;
|
|
4206
4516
|
|
|
4207
|
-
// ../../src/core/http/flashSession.ts
|
|
4208
|
-
import { createHmac as createHmac4, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
4209
|
-
var FLASH_COOKIE = "workhub_flash";
|
|
4210
|
-
var FLASH_TTL_MS = 60 * 1000;
|
|
4211
|
-
function resolveFlashSecret() {
|
|
4212
|
-
return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || "workhub-dev-flash-secret";
|
|
4213
|
-
}
|
|
4214
|
-
function signFlashPayload(payload, issuedAt) {
|
|
4215
|
-
const signature = createHmac4("sha256", resolveFlashSecret()).update(`${payload}.${issuedAt}`).digest("hex");
|
|
4216
|
-
return `${payload}.${issuedAt}.${signature}`;
|
|
4217
|
-
}
|
|
4218
|
-
function readFlashCookie(request) {
|
|
4219
|
-
const cookieHeader = request.headers.get("cookie");
|
|
4220
|
-
if (!cookieHeader) {
|
|
4221
|
-
return null;
|
|
4222
|
-
}
|
|
4223
|
-
for (const part of cookieHeader.split(";")) {
|
|
4224
|
-
const [name, ...rest] = part.trim().split("=");
|
|
4225
|
-
if (name === FLASH_COOKIE) {
|
|
4226
|
-
return decodeURIComponent(rest.join("="));
|
|
4227
|
-
}
|
|
4228
|
-
}
|
|
4229
|
-
return null;
|
|
4230
|
-
}
|
|
4231
|
-
function parseFlashCookie(cookieValue) {
|
|
4232
|
-
const parts = cookieValue.split(".");
|
|
4233
|
-
if (parts.length < 3) {
|
|
4234
|
-
return null;
|
|
4235
|
-
}
|
|
4236
|
-
const signature = parts.pop();
|
|
4237
|
-
const issuedAtRaw = parts.pop();
|
|
4238
|
-
const payload = parts.join(".");
|
|
4239
|
-
if (!signature || !issuedAtRaw || !payload) {
|
|
4240
|
-
return null;
|
|
4241
|
-
}
|
|
4242
|
-
const issuedAt = Number.parseInt(issuedAtRaw, 10);
|
|
4243
|
-
if (!Number.isFinite(issuedAt) || Date.now() - issuedAt > FLASH_TTL_MS) {
|
|
4244
|
-
return null;
|
|
4245
|
-
}
|
|
4246
|
-
const expectedSignature = signFlashPayload(payload, issuedAt).split(".").pop();
|
|
4247
|
-
if (!expectedSignature) {
|
|
4248
|
-
return null;
|
|
4249
|
-
}
|
|
4250
|
-
const expectedBuffer = Buffer.from(expectedSignature);
|
|
4251
|
-
const actualBuffer = Buffer.from(signature);
|
|
4252
|
-
if (expectedBuffer.length !== actualBuffer.length) {
|
|
4253
|
-
return null;
|
|
4254
|
-
}
|
|
4255
|
-
if (!timingSafeEqual2(expectedBuffer, actualBuffer)) {
|
|
4256
|
-
return null;
|
|
4257
|
-
}
|
|
4258
|
-
try {
|
|
4259
|
-
const parsed = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
4260
|
-
if (!parsed?.message || typeof parsed.message !== "string") {
|
|
4261
|
-
return null;
|
|
4262
|
-
}
|
|
4263
|
-
if (parsed.level !== "success" && parsed.level !== "error" && parsed.level !== "info") {
|
|
4264
|
-
return null;
|
|
4265
|
-
}
|
|
4266
|
-
return parsed;
|
|
4267
|
-
} catch {
|
|
4268
|
-
return null;
|
|
4269
|
-
}
|
|
4270
|
-
}
|
|
4271
|
-
function createFlashCookie(message) {
|
|
4272
|
-
const payload = Buffer.from(JSON.stringify(message), "utf8").toString("base64url");
|
|
4273
|
-
const issuedAt = Date.now();
|
|
4274
|
-
const value = signFlashPayload(payload, issuedAt);
|
|
4275
|
-
return `${FLASH_COOKIE}=${encodeURIComponent(value)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=60`;
|
|
4276
|
-
}
|
|
4277
|
-
function clearFlashCookie() {
|
|
4278
|
-
return `${FLASH_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`;
|
|
4279
|
-
}
|
|
4280
|
-
function pullFlash(request) {
|
|
4281
|
-
const cookieValue = readFlashCookie(request);
|
|
4282
|
-
if (!cookieValue) {
|
|
4283
|
-
return null;
|
|
4284
|
-
}
|
|
4285
|
-
return parseFlashCookie(cookieValue);
|
|
4286
|
-
}
|
|
4287
|
-
function flashResponse(response, message) {
|
|
4288
|
-
const headers = new Headers(response.headers);
|
|
4289
|
-
headers.append("set-cookie", createFlashCookie(message));
|
|
4290
|
-
return new Response(response.body, {
|
|
4291
|
-
status: response.status,
|
|
4292
|
-
statusText: response.statusText,
|
|
4293
|
-
headers
|
|
4294
|
-
});
|
|
4295
|
-
}
|
|
4296
|
-
function withFlashClear(response) {
|
|
4297
|
-
const headers = new Headers(response.headers);
|
|
4298
|
-
headers.append("set-cookie", clearFlashCookie());
|
|
4299
|
-
return new Response(response.body, {
|
|
4300
|
-
status: response.status,
|
|
4301
|
-
statusText: response.statusText,
|
|
4302
|
-
headers
|
|
4303
|
-
});
|
|
4304
|
-
}
|
|
4305
|
-
|
|
4306
4517
|
// ../../src/core/view/webLayoutData.ts
|
|
4307
4518
|
async function resolveWebLayoutData(container, request) {
|
|
4308
4519
|
const csrfToken = request ? resolveCsrfTokenForRequest(request) : "";
|
|
@@ -4898,6 +5109,38 @@ function createMetricsMiddleware() {
|
|
|
4898
5109
|
return response;
|
|
4899
5110
|
};
|
|
4900
5111
|
}
|
|
5112
|
+
// ../../src/core/http/requireAbilityMiddleware.ts
|
|
5113
|
+
function createRequireAbilityMiddleware(abilityChecker) {
|
|
5114
|
+
return (ability) => {
|
|
5115
|
+
return async (_request, next) => {
|
|
5116
|
+
const user = currentAuthUser();
|
|
5117
|
+
try {
|
|
5118
|
+
abilityChecker.requireAbility(user, ability);
|
|
5119
|
+
} catch (error) {
|
|
5120
|
+
if (error instanceof ForbiddenError) {
|
|
5121
|
+
return Response.json({ error: error.message }, { status: error.status });
|
|
5122
|
+
}
|
|
5123
|
+
throw error;
|
|
5124
|
+
}
|
|
5125
|
+
return await next();
|
|
5126
|
+
};
|
|
5127
|
+
};
|
|
5128
|
+
}
|
|
5129
|
+
// ../../src/core/http/requireGlobalAdminMiddleware.ts
|
|
5130
|
+
function createRequireGlobalAdminMiddleware() {
|
|
5131
|
+
return async (_request, next) => {
|
|
5132
|
+
const user = currentAuthUser();
|
|
5133
|
+
if (!isGlobalAdmin(user)) {
|
|
5134
|
+
logSecurityEvent("privilege_escalation_blocked", {
|
|
5135
|
+
required_role: "platform_admin",
|
|
5136
|
+
path: new URL(_request.url).pathname
|
|
5137
|
+
});
|
|
5138
|
+
const error = new ForbiddenError("Platform admin access required.");
|
|
5139
|
+
return Response.json({ error: error.message }, { status: error.status });
|
|
5140
|
+
}
|
|
5141
|
+
return await next();
|
|
5142
|
+
};
|
|
5143
|
+
}
|
|
4901
5144
|
// ../../src/core/http/requireWebAuthMiddleware.ts
|
|
4902
5145
|
function createRequireWebAuthMiddleware(auth2) {
|
|
4903
5146
|
return async (request, next) => {
|
|
@@ -5104,6 +5347,29 @@ function resetGracefulShutdownForTests() {
|
|
|
5104
5347
|
shutdownInstalled = false;
|
|
5105
5348
|
shuttingDown = false;
|
|
5106
5349
|
}
|
|
5350
|
+
// ../../src/core/logging/requestLoggingMiddleware.ts
|
|
5351
|
+
function createRequestLoggingMiddleware() {
|
|
5352
|
+
return async (request, next) => {
|
|
5353
|
+
return await runWithRequestMeta({
|
|
5354
|
+
ipAddress: request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? request.headers.get("x-real-ip"),
|
|
5355
|
+
userAgent: request.headers.get("user-agent"),
|
|
5356
|
+
request
|
|
5357
|
+
}, async () => {
|
|
5358
|
+
const startedAt = performance.now();
|
|
5359
|
+
const requestId = request.headers.get("x-request-id") ?? crypto.randomUUID();
|
|
5360
|
+
const response = await next();
|
|
5361
|
+
const durationMs = Math.round(performance.now() - startedAt);
|
|
5362
|
+
appLogger.info("HTTP request completed", {
|
|
5363
|
+
requestId,
|
|
5364
|
+
method: request.method,
|
|
5365
|
+
path: new URL(request.url).pathname,
|
|
5366
|
+
status: response.status,
|
|
5367
|
+
durationMs
|
|
5368
|
+
});
|
|
5369
|
+
return response;
|
|
5370
|
+
});
|
|
5371
|
+
};
|
|
5372
|
+
}
|
|
5107
5373
|
// ../../src/core/mail/markdownMail.ts
|
|
5108
5374
|
function escapeHtml(value) {
|
|
5109
5375
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -5604,6 +5870,208 @@ async function runDueScheduledTasks(schedule = appSchedule, now = new Date) {
|
|
|
5604
5870
|
}
|
|
5605
5871
|
return due.length;
|
|
5606
5872
|
}
|
|
5873
|
+
// ../../src/core/security/publicReads.ts
|
|
5874
|
+
function isPublicReadsEnabled() {
|
|
5875
|
+
return isFeatureEnabled("publicReads");
|
|
5876
|
+
}
|
|
5877
|
+
function guestCanViewResource() {
|
|
5878
|
+
return isPublicReadsEnabled();
|
|
5879
|
+
}
|
|
5880
|
+
// ../../src/core/tenant/tenantMiddleware.ts
|
|
5881
|
+
import { createHash as createHash2 } from "crypto";
|
|
5882
|
+
|
|
5883
|
+
// ../../src/core/tenant/databaseTenantContext.ts
|
|
5884
|
+
async function runWithMigrationBypass(callback) {
|
|
5885
|
+
await connection_default`SELECT set_config('app.bypass_rls', 'true', false)`;
|
|
5886
|
+
try {
|
|
5887
|
+
return await callback();
|
|
5888
|
+
} finally {
|
|
5889
|
+
await connection_default`SELECT set_config('app.bypass_rls', 'false', false)`;
|
|
5890
|
+
}
|
|
5891
|
+
}
|
|
5892
|
+
|
|
5893
|
+
// ../../src/core/tenant/resolveTenant.ts
|
|
5894
|
+
async function resolveTenant(tenantId) {
|
|
5895
|
+
const rows = await connection_default`
|
|
5896
|
+
SELECT id, slug, plan, region
|
|
5897
|
+
FROM tenant
|
|
5898
|
+
WHERE id = ${tenantId}
|
|
5899
|
+
LIMIT 1
|
|
5900
|
+
`;
|
|
5901
|
+
const row = rows[0];
|
|
5902
|
+
return row ? { id: row.id, slug: row.slug, plan: row.plan, region: row.region ?? "eu" } : null;
|
|
5903
|
+
}
|
|
5904
|
+
|
|
5905
|
+
// ../../src/core/tenant/tenantDatabaseScope.ts
|
|
5906
|
+
async function applyTenantContextToTransaction(transaction, tenantId) {
|
|
5907
|
+
await transaction.unsafe(`SELECT set_config('app.tenant_id', $1, true)`, [String(tenantId)]);
|
|
5908
|
+
await transaction.unsafe(`SELECT set_config('app.bypass_rls', $1, true)`, ["false"]);
|
|
5909
|
+
}
|
|
5910
|
+
async function runWithTenantDatabase(tenant, callback) {
|
|
5911
|
+
return await getDatabase().begin(async (transaction) => {
|
|
5912
|
+
await applyTenantContextToTransaction(transaction, tenant.id);
|
|
5913
|
+
return await runWithDatabaseConnection(transaction, async () => {
|
|
5914
|
+
return await runWithTenant(tenant, callback);
|
|
5915
|
+
});
|
|
5916
|
+
});
|
|
5917
|
+
}
|
|
5918
|
+
|
|
5919
|
+
// ../../src/core/tenant/tenantMiddleware.ts
|
|
5920
|
+
var DEFAULT_TENANT = {
|
|
5921
|
+
id: 1,
|
|
5922
|
+
slug: "default",
|
|
5923
|
+
plan: "enterprise",
|
|
5924
|
+
region: "eu"
|
|
5925
|
+
};
|
|
5926
|
+
async function resolveUserTenantId(userId) {
|
|
5927
|
+
return await runWithMigrationBypass(async () => {
|
|
5928
|
+
const rows = await connection_default`
|
|
5929
|
+
SELECT tenant_id
|
|
5930
|
+
FROM users
|
|
5931
|
+
WHERE id = ${userId}
|
|
5932
|
+
LIMIT 1
|
|
5933
|
+
`;
|
|
5934
|
+
return rows[0]?.tenant_id ?? DEFAULT_TENANT.id;
|
|
5935
|
+
});
|
|
5936
|
+
}
|
|
5937
|
+
function auditChecksum(payload) {
|
|
5938
|
+
return createHash2("sha256").update(JSON.stringify(payload)).digest("hex");
|
|
5939
|
+
}
|
|
5940
|
+
async function resolveTenantForRequest(request) {
|
|
5941
|
+
const user = currentAuthUser();
|
|
5942
|
+
const headerValue = request.headers.get("x-tenant-id")?.trim();
|
|
5943
|
+
const parsedHeader = headerValue !== undefined && headerValue.length > 0 ? Number.parseInt(headerValue, 10) : Number.NaN;
|
|
5944
|
+
if (user) {
|
|
5945
|
+
const userId = typeof user.id === "number" ? user.id : Number.parseInt(String(user.id), 10);
|
|
5946
|
+
if (Number.isInteger(userId) && userId > 0) {
|
|
5947
|
+
const userTenantId = await resolveUserTenantId(userId);
|
|
5948
|
+
if (!isGlobalAdmin(user)) {
|
|
5949
|
+
if (Number.isInteger(parsedHeader) && parsedHeader > 0 && parsedHeader !== userTenantId) {
|
|
5950
|
+
throw new ForbiddenError("Tenant header does not match your account.");
|
|
5951
|
+
}
|
|
5952
|
+
return await resolveTenant(userTenantId) ?? DEFAULT_TENANT;
|
|
5953
|
+
}
|
|
5954
|
+
if (Number.isInteger(parsedHeader) && parsedHeader > 0) {
|
|
5955
|
+
return await resolveTenant(parsedHeader) ?? DEFAULT_TENANT;
|
|
5956
|
+
}
|
|
5957
|
+
return await resolveTenant(userTenantId) ?? DEFAULT_TENANT;
|
|
5958
|
+
}
|
|
5959
|
+
}
|
|
5960
|
+
const tenantId = Number.isInteger(parsedHeader) && parsedHeader > 0 ? parsedHeader : DEFAULT_TENANT.id;
|
|
5961
|
+
return await resolveTenant(tenantId) ?? DEFAULT_TENANT;
|
|
5962
|
+
}
|
|
5963
|
+
function createTenantMiddleware() {
|
|
5964
|
+
return async (request, next) => {
|
|
5965
|
+
try {
|
|
5966
|
+
const tenant = await resolveTenantForRequest(request);
|
|
5967
|
+
return await runWithTenantDatabase(tenant, async () => {
|
|
5968
|
+
const response = await next();
|
|
5969
|
+
const headers = new Headers(response.headers);
|
|
5970
|
+
headers.set("x-tenant-id", String(tenant.id));
|
|
5971
|
+
headers.set("x-tenant-region", tenant.region);
|
|
5972
|
+
return new Response(response.body, {
|
|
5973
|
+
status: response.status,
|
|
5974
|
+
statusText: response.statusText,
|
|
5975
|
+
headers
|
|
5976
|
+
});
|
|
5977
|
+
});
|
|
5978
|
+
} catch (error) {
|
|
5979
|
+
if (error instanceof HttpError) {
|
|
5980
|
+
return Response.json({ error: error.message }, { status: error.status });
|
|
5981
|
+
}
|
|
5982
|
+
throw error;
|
|
5983
|
+
}
|
|
5984
|
+
};
|
|
5985
|
+
}
|
|
5986
|
+
// ../../src/core/tracing/otel.ts
|
|
5987
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
5988
|
+
function randomHex(bytes) {
|
|
5989
|
+
return randomBytes2(bytes).toString("hex");
|
|
5990
|
+
}
|
|
5991
|
+
function createSpan(input) {
|
|
5992
|
+
const spanId = randomHex(8);
|
|
5993
|
+
return {
|
|
5994
|
+
traceId: input.traceId,
|
|
5995
|
+
spanId,
|
|
5996
|
+
name: input.name,
|
|
5997
|
+
startTimeUnixNano: String(Math.floor(input.startedAt * 1e6)),
|
|
5998
|
+
endTimeUnixNano: String(Math.floor(input.endedAt * 1e6)),
|
|
5999
|
+
attributes: Object.entries(input.attributes ?? {}).map(([key, value]) => ({
|
|
6000
|
+
key,
|
|
6001
|
+
value: { stringValue: value }
|
|
6002
|
+
})),
|
|
6003
|
+
status: { code: 1 }
|
|
6004
|
+
};
|
|
6005
|
+
}
|
|
6006
|
+
async function exportOtelSpan(span) {
|
|
6007
|
+
const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT?.trim();
|
|
6008
|
+
if (!endpoint) {
|
|
6009
|
+
return;
|
|
6010
|
+
}
|
|
6011
|
+
const serviceName = process.env.OTEL_SERVICE_NAME?.trim() ?? "workhub-api";
|
|
6012
|
+
const url = endpoint.endsWith("/v1/traces") ? endpoint : `${endpoint.replace(/\/$/, "")}/v1/traces`;
|
|
6013
|
+
await fetch(url, {
|
|
6014
|
+
method: "POST",
|
|
6015
|
+
headers: { "content-type": "application/json" },
|
|
6016
|
+
body: JSON.stringify({
|
|
6017
|
+
resourceSpans: [
|
|
6018
|
+
{
|
|
6019
|
+
resource: {
|
|
6020
|
+
attributes: [{ key: "service.name", value: { stringValue: serviceName } }]
|
|
6021
|
+
},
|
|
6022
|
+
scopeSpans: [{ spans: [span] }]
|
|
6023
|
+
}
|
|
6024
|
+
]
|
|
6025
|
+
})
|
|
6026
|
+
});
|
|
6027
|
+
}
|
|
6028
|
+
|
|
6029
|
+
// ../../src/core/tracing/traceContext.ts
|
|
6030
|
+
import { AsyncLocalStorage as AsyncLocalStorage6 } from "async_hooks";
|
|
6031
|
+
var traceContextStorage = new AsyncLocalStorage6;
|
|
6032
|
+
function runWithTraceContext(context, callback) {
|
|
6033
|
+
return traceContextStorage.run(context, callback);
|
|
6034
|
+
}
|
|
6035
|
+
function currentTraceId() {
|
|
6036
|
+
return traceContextStorage.getStore()?.traceId ?? null;
|
|
6037
|
+
}
|
|
6038
|
+
|
|
6039
|
+
// ../../src/core/tracing/tracingMiddleware.ts
|
|
6040
|
+
function createTracingMiddleware() {
|
|
6041
|
+
return async (request, next) => {
|
|
6042
|
+
const traceId = (request.headers.get("x-trace-id") ?? crypto.randomUUID()).replace(/-/g, "");
|
|
6043
|
+
const spanId = crypto.randomUUID().replace(/-/g, "").slice(0, 16);
|
|
6044
|
+
const startedAt = performance.now();
|
|
6045
|
+
const path = new URL(request.url).pathname;
|
|
6046
|
+
return await runWithTraceContext({ traceId, spanId }, async () => {
|
|
6047
|
+
const response = await next();
|
|
6048
|
+
const endedAt = performance.now();
|
|
6049
|
+
const headers = new Headers(response.headers);
|
|
6050
|
+
headers.set("x-trace-id", traceId);
|
|
6051
|
+
headers.set("x-span-id", spanId);
|
|
6052
|
+
headers.set("traceparent", `00-${traceId}-${spanId}-01`);
|
|
6053
|
+
headers.set("server-timing", `app;dur=${(endedAt - startedAt).toFixed(2)}`);
|
|
6054
|
+
exportOtelSpan(createSpan({
|
|
6055
|
+
traceId,
|
|
6056
|
+
name: `${request.method} ${path}`,
|
|
6057
|
+
startedAt,
|
|
6058
|
+
endedAt,
|
|
6059
|
+
attributes: {
|
|
6060
|
+
"http.method": request.method,
|
|
6061
|
+
"http.route": path,
|
|
6062
|
+
"http.status_code": String(response.status)
|
|
6063
|
+
}
|
|
6064
|
+
})).catch(() => {
|
|
6065
|
+
return;
|
|
6066
|
+
});
|
|
6067
|
+
return new Response(response.body, {
|
|
6068
|
+
status: response.status,
|
|
6069
|
+
statusText: response.statusText,
|
|
6070
|
+
headers
|
|
6071
|
+
});
|
|
6072
|
+
});
|
|
6073
|
+
};
|
|
6074
|
+
}
|
|
5607
6075
|
// ../../src/core/validation/rules.ts
|
|
5608
6076
|
function required() {
|
|
5609
6077
|
return (field, value) => {
|
|
@@ -5791,7 +6259,9 @@ export {
|
|
|
5791
6259
|
resolveApplicationConfig,
|
|
5792
6260
|
resolveApplicationCache,
|
|
5793
6261
|
resolveApplicationAuth,
|
|
6262
|
+
resetDefaultStorage,
|
|
5794
6263
|
required,
|
|
6264
|
+
requestIdMiddleware,
|
|
5795
6265
|
renderMarkdownMail,
|
|
5796
6266
|
registerShutdownHandler,
|
|
5797
6267
|
registerModelRepository,
|
|
@@ -5820,6 +6290,7 @@ export {
|
|
|
5820
6290
|
loadMigrationsFromDirectory,
|
|
5821
6291
|
jsonResponse2 as jsonResponse,
|
|
5822
6292
|
jobRegistry,
|
|
6293
|
+
isPublicReadsEnabled,
|
|
5823
6294
|
isHtmxRequest,
|
|
5824
6295
|
isEtagEnabled,
|
|
5825
6296
|
installGracefulShutdownSignals,
|
|
@@ -5849,23 +6320,31 @@ export {
|
|
|
5849
6320
|
currentAuthUser,
|
|
5850
6321
|
createdResponse,
|
|
5851
6322
|
createTrackedJob,
|
|
6323
|
+
createTracingMiddleware,
|
|
5852
6324
|
createThrottleMiddleware,
|
|
6325
|
+
createTenantMiddleware,
|
|
5853
6326
|
createSecurityHeadersMiddleware,
|
|
5854
6327
|
createSchemaBuilder,
|
|
5855
6328
|
createRequireWebAuthMiddleware,
|
|
6329
|
+
createRequireGlobalAdminMiddleware,
|
|
5856
6330
|
createRequireAuthMiddleware,
|
|
6331
|
+
createRequireAbilityMiddleware,
|
|
6332
|
+
createRequestLoggingMiddleware,
|
|
5857
6333
|
createQueueWorker,
|
|
5858
6334
|
createQueue,
|
|
5859
6335
|
createProductionQueue,
|
|
5860
6336
|
createNotificationDispatcher,
|
|
5861
6337
|
createMetricsMiddleware,
|
|
5862
6338
|
createMemoryThrottleMiddleware,
|
|
6339
|
+
createMembershipMiddleware,
|
|
5863
6340
|
createLoginThrottleMiddleware,
|
|
6341
|
+
createFlashMiddleware,
|
|
5864
6342
|
createFailedJobService,
|
|
5865
6343
|
createDatabaseConnection2 as createDatabaseConnection,
|
|
5866
6344
|
createCsrfTokenCookie,
|
|
5867
6345
|
createCsrfProtection,
|
|
5868
6346
|
createCsrfMiddleware,
|
|
6347
|
+
createCorsMiddleware,
|
|
5869
6348
|
createBodySizeLimitMiddleware,
|
|
5870
6349
|
createAuthorizeMiddleware,
|
|
5871
6350
|
createAuthMiddleware,
|