@getstrata/bootstrap 0.2.6 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bootstrap/contracts.d.ts +2 -0
- package/dist/bootstrap/providers/storage.d.ts +3 -0
- package/dist/bootstrap/scimRoutes.d.ts +1 -1
- package/dist/core/auth/membershipScope.d.ts +16 -0
- package/dist/core/auth/membershipService.d.ts +24 -0
- package/dist/core/database/baseRepository.d.ts +6 -1
- package/dist/core/database/connectionContext.d.ts +2 -1
- package/dist/core/database/defaultConnection.d.ts +6 -0
- package/dist/core/database/queryProxy.d.ts +3 -0
- package/dist/core/database/repositoryConnection.d.ts +3 -3
- package/dist/core/jobs/dispatchWebhookJob.d.ts +0 -1
- package/dist/core/queue/queueMetrics.d.ts +15 -0
- package/dist/core/security/safeFetch.d.ts +2 -0
- package/dist/core/security/safeUrl.d.ts +16 -1
- package/dist/core/tenant/tenantDatabaseScope.d.ts +2 -1
- package/dist/db/connection/index.d.ts +1 -1
- package/dist/domain/workhub.d.ts +35 -0
- package/dist/entries/applicationRegistry.js +185 -0
- package/dist/entries/config.js +42 -0
- package/dist/entries/context.js +4208 -0
- package/dist/entries/contracts.js +92 -0
- package/dist/entries/createWebRoutes.js +996 -0
- package/dist/entries/httpKernel.js +264 -0
- package/dist/entries/providers/view.js +635 -0
- package/dist/entries/providers.js +4094 -0
- package/dist/framework/public-api.d.ts +29 -6
- package/dist/index.js +290 -109
- package/dist/modules/organization/repository.d.ts +14 -0
- package/dist/modules/organization/table.d.ts +3 -0
- package/dist/modules/organization/types.d.ts +10 -0
- package/dist/modules/scim/controller.d.ts +2 -1
- package/dist/modules/scim/scimResponse.d.ts +1 -1
- package/dist/modules/scim/service.d.ts +4 -7
- package/dist/modules/user/repository.d.ts +1 -0
- package/package.json +5 -4
|
@@ -8,10 +8,17 @@ export { ConfigStore, resolveService, ServiceContainer, } from "../bootstrap/con
|
|
|
8
8
|
export type { AdminColumn, AdminColumnType, AdminResource, AdminResourceDefinition, AdminResourceHandlers, } from "../core/admin/index.ts";
|
|
9
9
|
export { AdminResourceRegistry, formatAdminValue, } from "../core/admin/index.ts";
|
|
10
10
|
export type { AbilityChecker } from "../core/auth/abilityChecker.ts";
|
|
11
|
+
export { isGlobalAdmin, resolveUserId } from "../core/auth/accessControl.ts";
|
|
11
12
|
export type { AuthUser } from "../core/auth/authContext.ts";
|
|
12
|
-
export { currentAuthUser, runWithAuthUser } from "../core/auth/authContext.ts";
|
|
13
|
+
export { authContext, currentAuthUser, runWithAuthUser } from "../core/auth/authContext.ts";
|
|
14
|
+
export type { AuthGuard } from "../core/auth/guard.ts";
|
|
15
|
+
export { ApiTokenGuard, AuthManager, CompositeGuard, DatabaseTokenGuard, GuestGuard, } from "../core/auth/guard.ts";
|
|
16
|
+
export { currentOrganizationIds, currentOrgRole, hasMinimumOrgRole, hasOrgMembership, } from "../core/auth/membershipContext.ts";
|
|
13
17
|
export { createMembershipMiddleware } from "../core/auth/membershipMiddleware.ts";
|
|
18
|
+
export { appendOrganizationScope, appendProjectScope, assertOrganizationReadable, assertResourceInCurrentTenant, emptyPaginateResult, resolveOrganizationScope, scopedOrganizationIds, } from "../core/auth/membershipScope.ts";
|
|
19
|
+
export { default as MembershipService, resolveMembershipService, } from "../core/auth/membershipService.ts";
|
|
14
20
|
export { Policy, PolicyGate } from "../core/auth/policy.ts";
|
|
21
|
+
export { createScimAuthMiddleware } from "../core/auth/scimAuthMiddleware.ts";
|
|
15
22
|
export { default as CacheRepository } from "../core/cache/repository.ts";
|
|
16
23
|
export { CACHE_TAGS } from "../core/cache/tags.ts";
|
|
17
24
|
export type { DatabaseConnection } from "../core/database/baseRepository.ts";
|
|
@@ -19,13 +26,16 @@ export { default as BaseRepository } from "../core/database/baseRepository.ts";
|
|
|
19
26
|
export { bindDatabaseConnection } from "../core/database/bindConnection.ts";
|
|
20
27
|
export { createDatabaseConnection } from "../core/database/connection.ts";
|
|
21
28
|
export { getActiveDatabaseConnection, runWithDatabaseConnection, } from "../core/database/connectionContext.ts";
|
|
29
|
+
export { getDefaultDatabasePool, getDefaultDatabaseQuery, registerDefaultDatabasePool, } from "../core/database/defaultConnection.ts";
|
|
22
30
|
export { withMigrationLock } from "../core/database/migrations/advisoryLock.ts";
|
|
23
31
|
export { freshDatabase, getMigrationStatus, loadMigrationsFromDirectory, migrateDatabase, rollbackDatabase, } from "../core/database/migrations/runner.ts";
|
|
24
32
|
export type { Migration, MigrationDatabase, MigrationStatus, } from "../core/database/migrations/types.ts";
|
|
25
33
|
export type { CastType, GlobalScopeFn, ModelConstructor } from "../core/database/model.ts";
|
|
26
34
|
export { applyCasts, dehydrateValue, filterMassAssignable, hydrateValue, Model, registerModelRepository, } from "../core/database/model.ts";
|
|
35
|
+
export { createDatabaseQueryProxy } from "../core/database/queryProxy.ts";
|
|
27
36
|
export type { BelongsToManyRelation, BelongsToRelation, HasManyRelation, HasOneRelation, MorphManyRelation, MorphOneRelation, MorphToRelation, } from "../core/database/relationships.ts";
|
|
28
37
|
export { belongsTo, belongsToMany, hasMany, hasOne, indexBelongsToManyRelation, indexBelongsToRelation, indexHasManyRelation, indexHasOneRelation, indexMorphManyRelation, indexMorphOneRelation, indexMorphToRelation, morphMany, morphOne, morphTo, } from "../core/database/relationships.ts";
|
|
38
|
+
export { repositoryConnection, resolveRepositoryConnection, } from "../core/database/repositoryConnection.ts";
|
|
29
39
|
export { RepositoryQuery } from "../core/database/repositoryQuery.ts";
|
|
30
40
|
export type { BlueprintAction, BlueprintCallback, ColumnKind, DatabaseDriver, ForeignKeyOptions, Grammar, IndexDefinition, IndexKind, ResolveDatabaseDriverOptions, SchemaBuilder, } from "../core/database/schema/index.ts";
|
|
31
41
|
export { Blueprint, ColumnDefinition, compileBlueprint, createSchemaBuilder, ForeignIdColumnDefinition, grammarForDriver, inferReferencedTable, MySqlGrammar, PostgresGrammar, resolveDatabaseDriver, Schema, SqliteGrammar, UnsupportedSchemaFeatureError, } from "../core/database/schema/index.ts";
|
|
@@ -40,24 +50,29 @@ export { BadRequestError, ConflictError, ForbiddenError, NotFoundError, Precondi
|
|
|
40
50
|
export { EventBus } from "../core/events/eventBus.ts";
|
|
41
51
|
export { auth, cache, config, events, log, mail, policyGate, queue, storage, } from "../core/facades/index.ts";
|
|
42
52
|
export { createBodySizeLimitMiddleware } from "../core/http/bodySizeLimitMiddleware.ts";
|
|
53
|
+
export { conditionalJsonResponse } from "../core/http/conditionalResponse.ts";
|
|
43
54
|
export { readBunRequestCookie, readRequestCookie } from "../core/http/cookies.ts";
|
|
44
55
|
export { createCorsMiddleware } from "../core/http/corsMiddleware.ts";
|
|
45
56
|
export { createCsrfMiddleware } from "../core/http/csrfMiddleware.ts";
|
|
46
57
|
export { createCsrfProtection } from "../core/http/csrfProtection.ts";
|
|
47
58
|
export { createCsrfTokenCookie, readSubmittedCsrfToken, readSubmittedCsrfTokenFromBody, resolveCsrfToken, resolveCsrfTokenForRequest, verifyCsrfToken, } from "../core/http/csrfToken.ts";
|
|
48
|
-
export { assertIfMatch, etagFromResource, isEtagEnabled, } from "../core/http/etag.ts";
|
|
59
|
+
export { applyConditionalGet, assertIfMatch, type EtagVersioned, etagFromResource, isEtagEnabled, } from "../core/http/etag.ts";
|
|
49
60
|
export { createFlashMiddleware } from "../core/http/flashMiddleware.ts";
|
|
50
61
|
export { FormRequest } from "../core/http/formRequest.ts";
|
|
51
|
-
export { applyMiddlewareToRoutes, composeMiddleware, createAuthMiddleware, createAuthorizeMiddleware, createdResponse, createRequireAuthMiddleware, jsonResponse, noContentResponse, paginatedResponse, parsePaginationQuery, requestIdMiddleware, securedBindRouteModel, securedBindRouteModelByKey, withErrorHandling, withMiddleware, } from "../core/http/index.ts";
|
|
62
|
+
export { applyMiddlewareToRoutes, bindRouteModel, buildRequestCacheKey, composeMiddleware, createAuthMiddleware, createAuthorizeMiddleware, createdResponse, createRequireAuthMiddleware, jsonResponse, noContentResponse, paginatedResponse, parsePaginationQuery, parsePositiveIntParam, requestIdMiddleware, securedBindRouteModel, securedBindRouteModelByKey, withErrorHandling, withMiddleware, wrapRouteHandler, } from "../core/http/index.ts";
|
|
52
63
|
export { createLoginThrottleMiddleware } from "../core/http/loginThrottleMiddleware.ts";
|
|
53
64
|
export { createMemoryThrottleMiddleware } from "../core/http/memoryThrottleMiddleware.ts";
|
|
54
65
|
export { createMetricsMiddleware, normalizeMetricPath } from "../core/http/metricsMiddleware.ts";
|
|
55
66
|
export type { Middleware, RouteHandler } from "../core/http/middleware.ts";
|
|
67
|
+
export { type ParsedUpload, parseMultipartUpload } from "../core/http/parseMultipartUpload.ts";
|
|
68
|
+
export type { RequestMeta } from "../core/http/requestMetaContext.ts";
|
|
69
|
+
export { currentRequestMeta, runWithRequestMeta } from "../core/http/requestMetaContext.ts";
|
|
56
70
|
export { createRequireAbilityMiddleware } from "../core/http/requireAbilityMiddleware.ts";
|
|
57
71
|
export { createRequireGlobalAdminMiddleware } from "../core/http/requireGlobalAdminMiddleware.ts";
|
|
58
72
|
export { createRequireWebAuthMiddleware } from "../core/http/requireWebAuthMiddleware.ts";
|
|
59
73
|
export { serializeDate, toPaginatedResourceCollection, toResourceCollection, } from "../core/http/resources.ts";
|
|
60
74
|
export type { RouteRequest } from "../core/http/route.ts";
|
|
75
|
+
export { createScimThrottleMiddleware } from "../core/http/scimThrottleMiddleware.ts";
|
|
61
76
|
export { createSecurityHeadersMiddleware } from "../core/http/securityHeadersMiddleware.ts";
|
|
62
77
|
export { createThrottleMiddleware } from "../core/http/throttleMiddleware.ts";
|
|
63
78
|
export { WebFormRequest } from "../core/http/webFormRequest.ts";
|
|
@@ -77,12 +92,20 @@ export type { CursorPaginatedResult, PaginatedResult, PaginationMeta, } from "..
|
|
|
77
92
|
export type { Queue, QueuePriority } from "../core/queue/index.ts";
|
|
78
93
|
export { AsyncQueue, createQueue, Job, SyncQueue } from "../core/queue/index.ts";
|
|
79
94
|
export { createFailedJobService, createProductionQueue, createQueueWorker, createTrackedJob, FailedJobRepository, FailedJobService, jobRegistry, QueueWorker, RedisQueue, ResilientQueue, runQueueJob, } from "../core/queue/publicQueue.ts";
|
|
95
|
+
export type { QueueMetricsSnapshot } from "../core/queue/queueMetrics.ts";
|
|
96
|
+
export { collectQueueMetrics } from "../core/queue/queueMetrics.ts";
|
|
80
97
|
export type { ScheduledTask } from "../core/scheduler/schedule.ts";
|
|
81
98
|
export { appSchedule, runDueScheduledTasks, Schedule } from "../core/scheduler/schedule.ts";
|
|
82
|
-
export { isPublicReadsEnabled } from "../core/security/publicReads.ts";
|
|
99
|
+
export { guestCanViewResource, isPublicReadsEnabled } from "../core/security/publicReads.ts";
|
|
100
|
+
export type { SecurityEventDetails } from "../core/security/securityEvents.ts";
|
|
101
|
+
export { logSecurityEvent } from "../core/security/securityEvents.ts";
|
|
83
102
|
export type { StorageDriver } from "../core/storage/storage.ts";
|
|
84
|
-
export { LocalStorageDriver, resetDefaultStorage, StorageManager, } from "../core/storage/storage.ts";
|
|
85
|
-
export {
|
|
103
|
+
export { createStorageDriver, LocalStorageDriver, resetDefaultStorage, StorageManager, } from "../core/storage/storage.ts";
|
|
104
|
+
export { currentTenant, currentTenantId, runWithTenant } from "../core/tenant/tenantContext.ts";
|
|
105
|
+
export { isInsideTenantDatabaseScope, runWithTenantDatabase, } from "../core/tenant/tenantDatabaseScope.ts";
|
|
106
|
+
export { auditChecksum, createTenantMiddleware, DEFAULT_TENANT, resolveUserTenantId, } from "../core/tenant/tenantMiddleware.ts";
|
|
107
|
+
export type { TraceContext } from "../core/tracing/traceContext.ts";
|
|
108
|
+
export { currentTraceId, runWithTraceContext } from "../core/tracing/traceContext.ts";
|
|
86
109
|
export { createTracingMiddleware } from "../core/tracing/tracingMiddleware.ts";
|
|
87
110
|
export type { ValidationRule, ValidationSchema } from "../core/validation/rules.ts";
|
|
88
111
|
export { emailRule, maxLength, minLength, required, stringRule, validateObject, } from "../core/validation/rules.ts";
|
package/dist/index.js
CHANGED
|
@@ -65,18 +65,13 @@ var appConfig = {
|
|
|
65
65
|
apiPrefix: process.env.API_PREFIX ?? "/api/v1"
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
-
// ../../src/
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
|
|
72
|
-
}
|
|
73
|
-
var databaseConfig = {
|
|
74
|
-
url: process.env.DATABASE_URL ?? "",
|
|
75
|
-
poolMax: readInteger("DB_POOL_MAX", 10),
|
|
76
|
-
idleTimeoutSeconds: readInteger("DB_POOL_IDLE_TIMEOUT", 30),
|
|
77
|
-
maxLifetimeSeconds: readInteger("DB_POOL_MAX_LIFETIME", 3600),
|
|
78
|
-
connectionTimeoutSeconds: readInteger("DB_CONNECTION_TIMEOUT", 10)
|
|
68
|
+
// ../../src/core/database/boundConnection.ts
|
|
69
|
+
var boundConnectionHolder = {
|
|
70
|
+
connection: null
|
|
79
71
|
};
|
|
72
|
+
function getBoundDatabaseConnection() {
|
|
73
|
+
return boundConnectionHolder.connection;
|
|
74
|
+
}
|
|
80
75
|
|
|
81
76
|
// ../../src/core/database/connectionContext.ts
|
|
82
77
|
import { AsyncLocalStorage } from "async_hooks";
|
|
@@ -85,84 +80,65 @@ function getActiveDatabaseConnection(fallback) {
|
|
|
85
80
|
return activeConnection.getStore() ?? fallback;
|
|
86
81
|
}
|
|
87
82
|
|
|
88
|
-
// ../../src/
|
|
89
|
-
var
|
|
90
|
-
function
|
|
91
|
-
|
|
92
|
-
|
|
83
|
+
// ../../src/core/database/queryProxy.ts
|
|
84
|
+
var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
|
|
85
|
+
function createDatabaseQueryProxy(pool) {
|
|
86
|
+
function resolveDatabase() {
|
|
87
|
+
return getActiveDatabaseConnection(pool);
|
|
93
88
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
89
|
+
function resolveDatabaseForProperty(property) {
|
|
90
|
+
if (typeof property === "string" && POOL_CONNECTION_METHODS.has(property)) {
|
|
91
|
+
return pool;
|
|
92
|
+
}
|
|
93
|
+
return resolveDatabase();
|
|
94
|
+
}
|
|
95
|
+
return new Proxy(function database() {}, {
|
|
96
|
+
apply(_target, _thisArg, args) {
|
|
97
|
+
return resolveDatabase()(...args);
|
|
98
|
+
},
|
|
99
|
+
get(_target, property) {
|
|
100
|
+
const connection = resolveDatabaseForProperty(property);
|
|
101
|
+
const value = connection[property];
|
|
102
|
+
return typeof value === "function" ? value.bind(connection) : value;
|
|
103
|
+
}
|
|
100
104
|
});
|
|
101
105
|
}
|
|
102
106
|
|
|
103
|
-
// ../../src/
|
|
104
|
-
var
|
|
107
|
+
// ../../src/core/database/defaultConnection.ts
|
|
108
|
+
var defaultPool = {
|
|
105
109
|
connection: null
|
|
106
110
|
};
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
|
|
114
|
-
function resolveDatabase() {
|
|
115
|
-
return getActiveDatabaseConnection(getDatabase());
|
|
111
|
+
var defaultQuery = {
|
|
112
|
+
connection: null
|
|
113
|
+
};
|
|
114
|
+
function registerDefaultDatabasePool(connection) {
|
|
115
|
+
defaultPool.connection = connection;
|
|
116
|
+
defaultQuery.connection = createDatabaseQueryProxy(connection);
|
|
116
117
|
}
|
|
117
|
-
function
|
|
118
|
-
if (
|
|
119
|
-
|
|
118
|
+
function getDefaultDatabaseQuery() {
|
|
119
|
+
if (!defaultQuery.connection) {
|
|
120
|
+
throw new Error("Default database query handle is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
|
|
120
121
|
}
|
|
121
|
-
return
|
|
122
|
+
return defaultQuery.connection;
|
|
122
123
|
}
|
|
123
|
-
|
|
124
|
+
|
|
125
|
+
// ../../src/core/database/repositoryConnection.ts
|
|
126
|
+
function resolveRepositoryConnection() {
|
|
127
|
+
return getBoundDatabaseConnection() ?? getDefaultDatabaseQuery();
|
|
128
|
+
}
|
|
129
|
+
var repositoryConnection = new Proxy(function repositoryConnection2() {}, {
|
|
124
130
|
apply(_target, _thisArg, args) {
|
|
125
|
-
return
|
|
131
|
+
return resolveRepositoryConnection()(...args);
|
|
126
132
|
},
|
|
127
133
|
get(_target, property) {
|
|
128
|
-
const connection =
|
|
134
|
+
const connection = resolveRepositoryConnection();
|
|
129
135
|
const value = connection[property];
|
|
130
136
|
return typeof value === "function" ? value.bind(connection) : value;
|
|
131
137
|
}
|
|
132
138
|
});
|
|
133
|
-
var connection_default = db;
|
|
134
139
|
|
|
135
|
-
// ../../src/core/security/
|
|
136
|
-
|
|
137
|
-
async function safeFetch(input, init = {}, options = {}) {
|
|
138
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
|
|
139
|
-
const maxRedirects = options.maxRedirects ?? 0;
|
|
140
|
-
const controller = new AbortController;
|
|
141
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
142
|
-
try {
|
|
143
|
-
let currentUrl = input;
|
|
144
|
-
let redirectCount = 0;
|
|
145
|
-
while (true) {
|
|
146
|
-
const response = await fetch(currentUrl, {
|
|
147
|
-
...init,
|
|
148
|
-
signal: controller.signal,
|
|
149
|
-
redirect: "manual"
|
|
150
|
-
});
|
|
151
|
-
if (response.status >= 300 && response.status < 400) {
|
|
152
|
-
const location = response.headers.get("location");
|
|
153
|
-
if (!location || redirectCount >= maxRedirects) {
|
|
154
|
-
return response;
|
|
155
|
-
}
|
|
156
|
-
currentUrl = new URL(location, currentUrl).toString();
|
|
157
|
-
redirectCount += 1;
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
return response;
|
|
161
|
-
}
|
|
162
|
-
} finally {
|
|
163
|
-
clearTimeout(timeout);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
140
|
+
// ../../src/core/security/safeUrl.ts
|
|
141
|
+
import { lookup as dnsLookupImpl } from "dns/promises";
|
|
166
142
|
|
|
167
143
|
// ../../src/core/errors/http.ts
|
|
168
144
|
class HttpError extends Error {
|
|
@@ -205,6 +181,7 @@ class UnauthorizedError extends HttpError {
|
|
|
205
181
|
}
|
|
206
182
|
|
|
207
183
|
// ../../src/core/security/safeUrl.ts
|
|
184
|
+
var dnsLookup = dnsLookupImpl;
|
|
208
185
|
var BLOCKED_HOSTNAMES = new Set([
|
|
209
186
|
"localhost",
|
|
210
187
|
"127.0.0.1",
|
|
@@ -276,14 +253,63 @@ function assertSafeOutboundUrl(rawUrl, options = {}) {
|
|
|
276
253
|
}
|
|
277
254
|
return parsed;
|
|
278
255
|
}
|
|
256
|
+
function isBlockedIpAddress(address) {
|
|
257
|
+
return isBlockedHostname(address.trim().toLowerCase());
|
|
258
|
+
}
|
|
259
|
+
async function assertSafeOutboundUrlResolved(rawUrl, options = {}) {
|
|
260
|
+
const parsed = assertSafeOutboundUrl(rawUrl, options);
|
|
261
|
+
if (options.resolveDns === false) {
|
|
262
|
+
return parsed;
|
|
263
|
+
}
|
|
264
|
+
const hostname = parsed.hostname.trim().toLowerCase();
|
|
265
|
+
const results = await dnsLookup(hostname, { all: true, verbatim: true });
|
|
266
|
+
if (results.some((result) => isBlockedIpAddress(result.address))) {
|
|
267
|
+
throw new BadRequestError("Webhook URL targets a blocked host.");
|
|
268
|
+
}
|
|
269
|
+
return parsed;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ../../src/core/security/safeFetch.ts
|
|
273
|
+
var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
|
|
274
|
+
async function safeFetch(input, init = {}, options = {}) {
|
|
275
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
|
|
276
|
+
const maxRedirects = options.maxRedirects ?? 0;
|
|
277
|
+
const resolveDns = options.resolveDns ?? appConfig.env === "production";
|
|
278
|
+
const urlOptions = { allowHttp: options.allowHttp, resolveDns };
|
|
279
|
+
const controller = new AbortController;
|
|
280
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
281
|
+
try {
|
|
282
|
+
let currentUrl = (await assertSafeOutboundUrlResolved(input, urlOptions)).toString();
|
|
283
|
+
let redirectCount = 0;
|
|
284
|
+
while (true) {
|
|
285
|
+
const response = await fetch(currentUrl, {
|
|
286
|
+
...init,
|
|
287
|
+
signal: controller.signal,
|
|
288
|
+
redirect: "manual"
|
|
289
|
+
});
|
|
290
|
+
if (response.status >= 300 && response.status < 400) {
|
|
291
|
+
const location = response.headers.get("location");
|
|
292
|
+
if (!location || redirectCount >= maxRedirects) {
|
|
293
|
+
return response;
|
|
294
|
+
}
|
|
295
|
+
currentUrl = (await assertSafeOutboundUrlResolved(new URL(location, currentUrl).toString(), urlOptions)).toString();
|
|
296
|
+
redirectCount += 1;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
return response;
|
|
300
|
+
}
|
|
301
|
+
} finally {
|
|
302
|
+
clearTimeout(timeout);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
279
305
|
|
|
280
306
|
// ../../src/core/tenant/databaseTenantContext.ts
|
|
281
307
|
async function runWithMigrationBypass(callback) {
|
|
282
|
-
await
|
|
308
|
+
await repositoryConnection`SELECT set_config('app.bypass_rls', 'true', false)`;
|
|
283
309
|
try {
|
|
284
310
|
return await callback();
|
|
285
311
|
} finally {
|
|
286
|
-
await
|
|
312
|
+
await repositoryConnection`SELECT set_config('app.bypass_rls', 'false', false)`;
|
|
287
313
|
}
|
|
288
314
|
}
|
|
289
315
|
|
|
@@ -340,7 +366,7 @@ async function exportPendingAuditLogs() {
|
|
|
340
366
|
return 0;
|
|
341
367
|
}
|
|
342
368
|
return await runWithMigrationBypass(async () => {
|
|
343
|
-
const rows = await
|
|
369
|
+
const rows = await repositoryConnection`
|
|
344
370
|
SELECT
|
|
345
371
|
id,
|
|
346
372
|
user_id,
|
|
@@ -384,13 +410,13 @@ async function exportPendingAuditLogs() {
|
|
|
384
410
|
...process.env.SIEM_EXPORT_TOKEN ? { authorization: `Bearer ${process.env.SIEM_EXPORT_TOKEN}` } : {}
|
|
385
411
|
},
|
|
386
412
|
body
|
|
387
|
-
});
|
|
413
|
+
}, { allowHttp: appConfig.env !== "production" });
|
|
388
414
|
if (!response.ok) {
|
|
389
415
|
throw new Error(`SIEM export failed with status ${response.status}.`);
|
|
390
416
|
}
|
|
391
417
|
const ids = rows.map((row) => row.id);
|
|
392
418
|
for (const id of ids) {
|
|
393
|
-
await
|
|
419
|
+
await repositoryConnection`UPDATE audit_log SET exported_at = NOW() WHERE id = ${id}`;
|
|
394
420
|
}
|
|
395
421
|
return rows.length;
|
|
396
422
|
});
|
|
@@ -550,7 +576,8 @@ class ConfigStore {
|
|
|
550
576
|
}
|
|
551
577
|
var requiredDependencyKeys = [
|
|
552
578
|
"container",
|
|
553
|
-
"cache"
|
|
579
|
+
"cache",
|
|
580
|
+
"storage"
|
|
554
581
|
];
|
|
555
582
|
function getRequiredDependency(dependencies, key) {
|
|
556
583
|
const dependency = dependencies[key];
|
|
@@ -643,6 +670,60 @@ import { SamlProvider } from "@getstrata/core/auth/oauth/samlProvider";
|
|
|
643
670
|
// ../../src/modules/user/apiTokenRepository.ts
|
|
644
671
|
import { BaseRepository } from "@getstrata/core/database";
|
|
645
672
|
|
|
673
|
+
// ../../src/config/database.ts
|
|
674
|
+
function readInteger(name, fallback) {
|
|
675
|
+
const parsed = Number.parseInt(process.env[name] ?? String(fallback), 10);
|
|
676
|
+
return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
|
|
677
|
+
}
|
|
678
|
+
var databaseConfig = {
|
|
679
|
+
url: process.env.DATABASE_URL ?? "",
|
|
680
|
+
poolMax: readInteger("DB_POOL_MAX", 10),
|
|
681
|
+
idleTimeoutSeconds: readInteger("DB_POOL_IDLE_TIMEOUT", 30),
|
|
682
|
+
maxLifetimeSeconds: readInteger("DB_POOL_MAX_LIFETIME", 3600),
|
|
683
|
+
connectionTimeoutSeconds: readInteger("DB_CONNECTION_TIMEOUT", 10)
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
// ../../src/db/connection/createConnection.ts
|
|
687
|
+
var {SQL } = globalThis.Bun;
|
|
688
|
+
function createDatabaseConnection(config) {
|
|
689
|
+
if (!config.url) {
|
|
690
|
+
throw new Error("DATABASE_URL is not configured. Set DATABASE_URL before starting the app or running integration tests.");
|
|
691
|
+
}
|
|
692
|
+
return new SQL({
|
|
693
|
+
url: config.url,
|
|
694
|
+
max: config.poolMax,
|
|
695
|
+
idleTimeout: config.idleTimeoutSeconds,
|
|
696
|
+
maxLifetime: config.maxLifetimeSeconds,
|
|
697
|
+
connectionTimeout: config.connectionTimeoutSeconds
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// ../../src/db/connection/index.ts
|
|
702
|
+
var connectionHolder = {
|
|
703
|
+
connection: null
|
|
704
|
+
};
|
|
705
|
+
function getDatabase() {
|
|
706
|
+
if (!connectionHolder.connection) {
|
|
707
|
+
connectionHolder.connection = createDatabaseConnection(databaseConfig);
|
|
708
|
+
registerDefaultDatabasePool(connectionHolder.connection);
|
|
709
|
+
}
|
|
710
|
+
return connectionHolder.connection;
|
|
711
|
+
}
|
|
712
|
+
function getDb() {
|
|
713
|
+
getDatabase();
|
|
714
|
+
return getDefaultDatabaseQuery();
|
|
715
|
+
}
|
|
716
|
+
var db = new Proxy(function database() {}, {
|
|
717
|
+
apply(_target, _thisArg, args) {
|
|
718
|
+
return getDb()(...args);
|
|
719
|
+
},
|
|
720
|
+
get(_target, property) {
|
|
721
|
+
const connection = getDb();
|
|
722
|
+
const value = connection[property];
|
|
723
|
+
return typeof value === "function" ? value.bind(connection) : value;
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
|
|
646
727
|
// ../../src/modules/user/apiTokenTable.ts
|
|
647
728
|
import { defineTable } from "@getstrata/core/database";
|
|
648
729
|
var apiTokenTable = defineTable({
|
|
@@ -1741,13 +1822,10 @@ function resolveApplicationQueue() {
|
|
|
1741
1822
|
// ../../src/core/jobs/dispatchWebhookJob.ts
|
|
1742
1823
|
import { createHmac as createHmac2 } from "crypto";
|
|
1743
1824
|
class DispatchWebhookJob extends Job {
|
|
1744
|
-
constructor() {
|
|
1745
|
-
super();
|
|
1746
|
-
}
|
|
1747
1825
|
maxAttempts = 3;
|
|
1748
1826
|
backoffMs = 2000;
|
|
1749
1827
|
async handle(payload) {
|
|
1750
|
-
const rows = await
|
|
1828
|
+
const rows = await repositoryConnection`
|
|
1751
1829
|
SELECT id, url, secret
|
|
1752
1830
|
FROM webhook
|
|
1753
1831
|
WHERE id = ${payload.webhookId} AND active = TRUE
|
|
@@ -1770,14 +1848,14 @@ class DispatchWebhookJob extends Job {
|
|
|
1770
1848
|
"x-workhub-signature": signature
|
|
1771
1849
|
},
|
|
1772
1850
|
body
|
|
1773
|
-
});
|
|
1851
|
+
}, { allowHttp: appConfig.env !== "production" });
|
|
1774
1852
|
responseStatus = response.status;
|
|
1775
1853
|
if (!response.ok) {
|
|
1776
1854
|
throw new Error(`Webhook delivery failed with status ${response.status}.`);
|
|
1777
1855
|
}
|
|
1778
1856
|
} catch (error) {
|
|
1779
1857
|
errorMessage = error instanceof Error ? error.message : String(error);
|
|
1780
|
-
await
|
|
1858
|
+
await repositoryConnection`
|
|
1781
1859
|
INSERT INTO webhook_delivery (webhook_id, event, payload, response_status, error)
|
|
1782
1860
|
VALUES (
|
|
1783
1861
|
${webhook.id},
|
|
@@ -1789,7 +1867,7 @@ class DispatchWebhookJob extends Job {
|
|
|
1789
1867
|
`;
|
|
1790
1868
|
throw error instanceof Error ? error : new Error(errorMessage);
|
|
1791
1869
|
}
|
|
1792
|
-
await
|
|
1870
|
+
await repositoryConnection`
|
|
1793
1871
|
INSERT INTO webhook_delivery (webhook_id, event, payload, response_status)
|
|
1794
1872
|
VALUES (
|
|
1795
1873
|
${webhook.id},
|
|
@@ -2369,26 +2447,6 @@ function indexMorphToRelation(children, parentsByType, relation) {
|
|
|
2369
2447
|
return result;
|
|
2370
2448
|
}
|
|
2371
2449
|
|
|
2372
|
-
// ../../src/core/database/boundConnection.ts
|
|
2373
|
-
var boundConnectionHolder = {
|
|
2374
|
-
connection: null
|
|
2375
|
-
};
|
|
2376
|
-
function getBoundDatabaseConnection() {
|
|
2377
|
-
return boundConnectionHolder.connection;
|
|
2378
|
-
}
|
|
2379
|
-
|
|
2380
|
-
// ../../src/core/database/repositoryConnection.ts
|
|
2381
|
-
function resolveRepositoryConnection() {
|
|
2382
|
-
return getBoundDatabaseConnection() ?? connection_default;
|
|
2383
|
-
}
|
|
2384
|
-
var repositoryConnection = new Proxy({}, {
|
|
2385
|
-
get(_target, property) {
|
|
2386
|
-
const connection = resolveRepositoryConnection();
|
|
2387
|
-
const value = connection[property];
|
|
2388
|
-
return typeof value === "function" ? value.bind(connection) : value;
|
|
2389
|
-
}
|
|
2390
|
-
});
|
|
2391
|
-
|
|
2392
2450
|
// ../../src/core/database/whereBuilder.ts
|
|
2393
2451
|
class WhereBuilder {
|
|
2394
2452
|
nodes = [];
|
|
@@ -3816,6 +3874,128 @@ var queueProvider = {
|
|
|
3816
3874
|
};
|
|
3817
3875
|
var queue_default = queueProvider;
|
|
3818
3876
|
|
|
3877
|
+
// ../../src/core/storage/storage.ts
|
|
3878
|
+
import { mkdir, readFile, unlink, writeFile } from "fs/promises";
|
|
3879
|
+
import { dirname, join as join3 } from "path";
|
|
3880
|
+
var {S3Client } = globalThis.Bun;
|
|
3881
|
+
|
|
3882
|
+
class LocalStorageDriver {
|
|
3883
|
+
rootDirectory;
|
|
3884
|
+
constructor(rootDirectory) {
|
|
3885
|
+
this.rootDirectory = rootDirectory;
|
|
3886
|
+
}
|
|
3887
|
+
resolveRootDirectory() {
|
|
3888
|
+
return this.rootDirectory ?? process.env.STORAGE_PATH ?? "storage";
|
|
3889
|
+
}
|
|
3890
|
+
resolvePath(path) {
|
|
3891
|
+
return join3(this.resolveRootDirectory(), path.replace(/^\/+/, ""));
|
|
3892
|
+
}
|
|
3893
|
+
async put(path, contents) {
|
|
3894
|
+
const absolutePath = this.resolvePath(path);
|
|
3895
|
+
await mkdir(dirname(absolutePath), { recursive: true });
|
|
3896
|
+
await writeFile(absolutePath, contents);
|
|
3897
|
+
return path;
|
|
3898
|
+
}
|
|
3899
|
+
async get(path) {
|
|
3900
|
+
try {
|
|
3901
|
+
return await readFile(this.resolvePath(path));
|
|
3902
|
+
} catch {
|
|
3903
|
+
return null;
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
async delete(path) {
|
|
3907
|
+
try {
|
|
3908
|
+
await unlink(this.resolvePath(path));
|
|
3909
|
+
return true;
|
|
3910
|
+
} catch {
|
|
3911
|
+
return false;
|
|
3912
|
+
}
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
|
|
3916
|
+
class S3StorageDriver {
|
|
3917
|
+
client;
|
|
3918
|
+
constructor(client) {
|
|
3919
|
+
this.client = client;
|
|
3920
|
+
}
|
|
3921
|
+
async put(path, contents) {
|
|
3922
|
+
await this.client.write(path.replace(/^\/+/, ""), contents);
|
|
3923
|
+
return path;
|
|
3924
|
+
}
|
|
3925
|
+
async get(path) {
|
|
3926
|
+
const normalizedPath = path.replace(/^\/+/, "");
|
|
3927
|
+
const file = this.client.file(normalizedPath);
|
|
3928
|
+
if (!await file.exists()) {
|
|
3929
|
+
return null;
|
|
3930
|
+
}
|
|
3931
|
+
return new Uint8Array(await file.arrayBuffer());
|
|
3932
|
+
}
|
|
3933
|
+
async delete(path) {
|
|
3934
|
+
try {
|
|
3935
|
+
await this.client.unlink(path.replace(/^\/+/, ""));
|
|
3936
|
+
return true;
|
|
3937
|
+
} catch {
|
|
3938
|
+
return false;
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
|
|
3943
|
+
class StorageManager {
|
|
3944
|
+
driver;
|
|
3945
|
+
constructor(driver) {
|
|
3946
|
+
this.driver = driver;
|
|
3947
|
+
}
|
|
3948
|
+
put(path, contents) {
|
|
3949
|
+
return this.driver.put(path, contents);
|
|
3950
|
+
}
|
|
3951
|
+
get(path) {
|
|
3952
|
+
return this.driver.get(path);
|
|
3953
|
+
}
|
|
3954
|
+
delete(path) {
|
|
3955
|
+
return this.driver.delete(path);
|
|
3956
|
+
}
|
|
3957
|
+
}
|
|
3958
|
+
function resolveS3Config() {
|
|
3959
|
+
const accessKeyId = process.env.AWS_ACCESS_KEY_ID?.trim();
|
|
3960
|
+
const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY?.trim();
|
|
3961
|
+
const bucket = process.env.AWS_BUCKET?.trim();
|
|
3962
|
+
if (!accessKeyId || !secretAccessKey || !bucket) {
|
|
3963
|
+
throw new Error('STORAGE_DRIVER="s3" requires AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_BUCKET.');
|
|
3964
|
+
}
|
|
3965
|
+
return {
|
|
3966
|
+
accessKeyId,
|
|
3967
|
+
secretAccessKey,
|
|
3968
|
+
bucket,
|
|
3969
|
+
...process.env.AWS_REGION?.trim() ? { region: process.env.AWS_REGION.trim() } : {},
|
|
3970
|
+
...process.env.AWS_ENDPOINT?.trim() ? { endpoint: process.env.AWS_ENDPOINT.trim() } : {}
|
|
3971
|
+
};
|
|
3972
|
+
}
|
|
3973
|
+
function createS3Client(config = resolveS3Config()) {
|
|
3974
|
+
return new S3Client({
|
|
3975
|
+
accessKeyId: config.accessKeyId,
|
|
3976
|
+
secretAccessKey: config.secretAccessKey,
|
|
3977
|
+
bucket: config.bucket,
|
|
3978
|
+
...config.region ? { region: config.region } : {},
|
|
3979
|
+
...config.endpoint ? { endpoint: config.endpoint } : {}
|
|
3980
|
+
});
|
|
3981
|
+
}
|
|
3982
|
+
function createStorageDriver() {
|
|
3983
|
+
const driver = process.env.STORAGE_DRIVER ?? "local";
|
|
3984
|
+
if (driver === "s3") {
|
|
3985
|
+
return new S3StorageDriver(createS3Client());
|
|
3986
|
+
}
|
|
3987
|
+
return new LocalStorageDriver;
|
|
3988
|
+
}
|
|
3989
|
+
|
|
3990
|
+
// ../../src/bootstrap/providers/storage.ts
|
|
3991
|
+
var storageProvider = {
|
|
3992
|
+
name: "core.storage",
|
|
3993
|
+
register({ dependencies }) {
|
|
3994
|
+
dependencies.storage = new StorageManager(createStorageDriver());
|
|
3995
|
+
}
|
|
3996
|
+
};
|
|
3997
|
+
var storage_default = storageProvider;
|
|
3998
|
+
|
|
3819
3999
|
// ../../src/config/frontend.ts
|
|
3820
4000
|
function readFrontendMode() {
|
|
3821
4001
|
const mode = (process.env.FRONTEND_MODE ?? "api").trim();
|
|
@@ -3842,9 +4022,9 @@ function currentRequestMeta() {
|
|
|
3842
4022
|
}
|
|
3843
4023
|
|
|
3844
4024
|
// ../../src/core/view/etaViewEngine.ts
|
|
3845
|
-
import { join as
|
|
4025
|
+
import { join as join4 } from "path";
|
|
3846
4026
|
import { Eta } from "eta";
|
|
3847
|
-
var DEFAULT_VIEWS_DIRECTORY =
|
|
4027
|
+
var DEFAULT_VIEWS_DIRECTORY = join4(process.cwd(), "resources/views");
|
|
3848
4028
|
var DEFAULT_LAYOUT = "layouts/app.eta";
|
|
3849
4029
|
|
|
3850
4030
|
class EtaViewEngine {
|
|
@@ -4069,6 +4249,7 @@ var viewProvider = {
|
|
|
4069
4249
|
var coreProviders = [
|
|
4070
4250
|
config_default,
|
|
4071
4251
|
cache_default,
|
|
4252
|
+
storage_default,
|
|
4072
4253
|
auth_default,
|
|
4073
4254
|
events_default,
|
|
4074
4255
|
policy_default,
|
|
@@ -4169,7 +4350,7 @@ function createAppContext() {
|
|
|
4169
4350
|
return appContext;
|
|
4170
4351
|
}
|
|
4171
4352
|
// ../../src/bootstrap/createWebRoutes.ts
|
|
4172
|
-
import { join as
|
|
4353
|
+
import { join as join5 } from "path";
|
|
4173
4354
|
|
|
4174
4355
|
// ../../src/core/http/middleware.ts
|
|
4175
4356
|
function isRouteHandler(value) {
|
|
@@ -4503,7 +4684,7 @@ function createWebRoutes(dependencies) {
|
|
|
4503
4684
|
registerRoute("GET", "/assets/*", ["global", "web"]);
|
|
4504
4685
|
const pathname = new URL(request.url).pathname;
|
|
4505
4686
|
const relativePath = pathname.replace(/^\//, "");
|
|
4506
|
-
const file = Bun.file(
|
|
4687
|
+
const file = Bun.file(join5(process.cwd(), "public", relativePath));
|
|
4507
4688
|
if (!await file.exists()) {
|
|
4508
4689
|
return htmlResponse("Not Found", { status: 404 });
|
|
4509
4690
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BaseRepository } from "@getstrata/core/database";
|
|
2
|
+
import type { OrganizationRecord } from "./types";
|
|
3
|
+
declare class OrganizationRepository extends BaseRepository<OrganizationRecord, "id"> {
|
|
4
|
+
constructor();
|
|
5
|
+
findBySlug(slug: string): Promise<OrganizationRecord | null>;
|
|
6
|
+
listForTenant(options: {
|
|
7
|
+
limit: number;
|
|
8
|
+
offset: number;
|
|
9
|
+
tenantId?: number;
|
|
10
|
+
}): Promise<OrganizationRecord[]>;
|
|
11
|
+
countForTenant(tenantId?: number): Promise<number>;
|
|
12
|
+
findForTenantOrThrow(id: number, tenantId?: number): Promise<OrganizationRecord>;
|
|
13
|
+
}
|
|
14
|
+
export default OrganizationRepository;
|