@getstrata/core 1.1.8 → 1.1.9
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/CHANGELOG.md +8 -0
- package/dist/core/events/deferredModelEvents.d.ts +3 -0
- package/dist/core/events/index.d.ts +1 -0
- package/dist/entries/audit/exportAuditLogs.js +247 -53
- package/dist/entries/auth/accessControl.js +16 -2
- package/dist/entries/auth/basicAuthGuard.js +47 -26
- package/dist/entries/auth/membershipMiddleware.js +17 -0
- package/dist/entries/auth/membershipScope.js +21 -6
- package/dist/entries/auth/membershipService.js +20 -5
- package/dist/entries/auth/oauth/samlProvider.js +47 -26
- package/dist/entries/auth/oneTimeToken.js +18 -2
- package/dist/entries/auth/passwordLogin.js +29 -13
- package/dist/entries/auth/policy.js +16 -2
- package/dist/entries/auth/saml/samlServiceProvider.js +47 -26
- package/dist/entries/auth/scimAuthMiddleware.js +217 -63
- package/dist/entries/database/migrations.js +20 -4
- package/dist/entries/http/csrfMiddleware.js +28 -14
- package/dist/entries/http/csrfToken.js +28 -14
- package/dist/entries/http/flashMiddleware.js +28 -14
- package/dist/entries/jobs/exportAuditLogsJob.js +247 -53
- package/dist/entries/runtime/asyncContextStore.js +16 -0
- package/dist/entries/tenant/databaseTenantContext.js +203 -9
- package/dist/entries/tenant/tenancyConfig.js +19 -1
- package/dist/entries/tenant/tenantDatabaseScope.js +173 -17
- package/dist/entries/tracing/tracingMiddleware.js +28 -14
- package/dist/framework/public-api.d.ts +1 -1
- package/dist/index.js +406 -210
- package/package.json +1 -1
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
var __esm = (fn, res, err) => () => {
|
|
3
|
+
if (fn)
|
|
4
|
+
try {
|
|
5
|
+
res = fn(fn = 0);
|
|
6
|
+
} catch (e) {
|
|
7
|
+
err = [e];
|
|
8
|
+
}
|
|
9
|
+
if (err)
|
|
10
|
+
throw err[0];
|
|
11
|
+
return res;
|
|
12
|
+
};
|
|
4
13
|
|
|
5
14
|
// ../../src/core/runtime/asyncContextStore.ts
|
|
6
15
|
import { AsyncLocalStorage } from "async_hooks";
|
|
@@ -15,9 +24,9 @@ function createAsyncContextStore(key) {
|
|
|
15
24
|
globalRecord[symbol] = store;
|
|
16
25
|
return store;
|
|
17
26
|
}
|
|
27
|
+
var init_asyncContextStore = () => {};
|
|
18
28
|
|
|
19
29
|
// ../../src/core/database/connectionContext.ts
|
|
20
|
-
var activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
|
|
21
30
|
function runWithDatabaseConnection(connection, callback) {
|
|
22
31
|
return activeConnection.run(connection, callback);
|
|
23
32
|
}
|
|
@@ -27,9 +36,144 @@ function getActiveDatabaseConnection(fallback) {
|
|
|
27
36
|
function hasActiveDatabaseConnection() {
|
|
28
37
|
return activeConnection.getStore() !== undefined;
|
|
29
38
|
}
|
|
39
|
+
var activeConnection;
|
|
40
|
+
var init_connectionContext = __esm(() => {
|
|
41
|
+
init_asyncContextStore();
|
|
42
|
+
activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// ../../src/core/tenant/tenantContext.ts
|
|
46
|
+
function runWithTenant(tenant, callback) {
|
|
47
|
+
return tenantContext.run(tenant, callback);
|
|
48
|
+
}
|
|
49
|
+
function currentTenant() {
|
|
50
|
+
return tenantContext.getStore() ?? null;
|
|
51
|
+
}
|
|
52
|
+
var tenantContext;
|
|
53
|
+
var init_tenantContext = __esm(() => {
|
|
54
|
+
init_asyncContextStore();
|
|
55
|
+
tenantContext = createAsyncContextStore("@getstrata/tenantContext");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// ../../src/core/events/eventBus.ts
|
|
59
|
+
class EventBus {
|
|
60
|
+
constructor() {}
|
|
61
|
+
listeners = new Map;
|
|
62
|
+
on(event, listener) {
|
|
63
|
+
return this.listen(event, listener);
|
|
64
|
+
}
|
|
65
|
+
listen(event, listener) {
|
|
66
|
+
const handlers = this.listeners.get(event) ?? new Set;
|
|
67
|
+
handlers.add(listener);
|
|
68
|
+
this.listeners.set(event, handlers);
|
|
69
|
+
return () => {
|
|
70
|
+
handlers.delete(listener);
|
|
71
|
+
if (handlers.size === 0) {
|
|
72
|
+
this.listeners.delete(event);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
async emit(event, payload) {
|
|
77
|
+
await this.dispatch(event, payload);
|
|
78
|
+
}
|
|
79
|
+
async dispatch(event, payload) {
|
|
80
|
+
const handlers = this.listeners.get(event);
|
|
81
|
+
if (!handlers || handlers.size === 0) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
for (const handler of handlers) {
|
|
85
|
+
await handler(payload);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function readSharedEventBus() {
|
|
90
|
+
const globalBus = globalThis[EVENT_BUS_KEY];
|
|
91
|
+
if (globalBus) {
|
|
92
|
+
return globalBus;
|
|
93
|
+
}
|
|
94
|
+
const bus = new EventBus;
|
|
95
|
+
globalThis[EVENT_BUS_KEY] = bus;
|
|
96
|
+
return bus;
|
|
97
|
+
}
|
|
98
|
+
var EVENT_BUS_KEY, eventBus;
|
|
99
|
+
var init_eventBus = __esm(() => {
|
|
100
|
+
EVENT_BUS_KEY = Symbol.for("@getstrata/eventBus");
|
|
101
|
+
eventBus = readSharedEventBus();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// ../../src/core/events/deferredModelEvents.ts
|
|
105
|
+
function sameTenant(left, right) {
|
|
106
|
+
if (left === null && right === null) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
if (left === null || right === null) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
return left.id === right.id;
|
|
113
|
+
}
|
|
114
|
+
function groupQueuedModelEvents(queued) {
|
|
115
|
+
const groups = [];
|
|
116
|
+
for (const item of queued) {
|
|
117
|
+
const lastGroup = groups[groups.length - 1];
|
|
118
|
+
const last = lastGroup?.[lastGroup.length - 1];
|
|
119
|
+
if (!lastGroup || !last || !sameTenant(last.tenant, item.tenant)) {
|
|
120
|
+
groups.push([item]);
|
|
121
|
+
} else {
|
|
122
|
+
lastGroup.push(item);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return groups;
|
|
126
|
+
}
|
|
127
|
+
async function dispatchEventBatch(batch) {
|
|
128
|
+
for (const item of batch) {
|
|
129
|
+
await eventBus.dispatch(item.event, item.payload);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async function dispatchQueuedModelEvents(queued) {
|
|
133
|
+
if (queued.length === 0) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
await Promise.resolve().then(() => init_tenantDatabaseScope());
|
|
137
|
+
for (const batch of groupQueuedModelEvents(queued)) {
|
|
138
|
+
const tenant = batch[0]?.tenant ?? null;
|
|
139
|
+
if (tenant) {
|
|
140
|
+
await runWithTenantDatabase(tenant, () => dispatchEventBatch(batch));
|
|
141
|
+
} else {
|
|
142
|
+
await dispatchEventBatch(batch);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async function runWithDeferredModelEvents(callback) {
|
|
147
|
+
const existing = deferredModelEvents.getStore();
|
|
148
|
+
if (existing) {
|
|
149
|
+
existing.frames.push([]);
|
|
150
|
+
try {
|
|
151
|
+
const result = await callback();
|
|
152
|
+
const nested = existing.frames.pop() ?? [];
|
|
153
|
+
const parent = existing.frames[existing.frames.length - 1];
|
|
154
|
+
if (parent) {
|
|
155
|
+
parent.push(...nested);
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
} catch (error) {
|
|
159
|
+
existing.frames.pop();
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const state = { frames: [[]] };
|
|
164
|
+
const result = await deferredModelEvents.run(state, callback);
|
|
165
|
+
await dispatchQueuedModelEvents(state.frames[0] ?? []);
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
var deferredModelEvents;
|
|
169
|
+
var init_deferredModelEvents = __esm(() => {
|
|
170
|
+
init_asyncContextStore();
|
|
171
|
+
init_tenantContext();
|
|
172
|
+
init_eventBus();
|
|
173
|
+
deferredModelEvents = createAsyncContextStore("@getstrata/deferredModelEvents");
|
|
174
|
+
});
|
|
30
175
|
|
|
31
176
|
// ../../src/core/tenant/tenancyConfig.ts
|
|
32
|
-
var TENANCY_DRIVERS = ["rls", "column", "none"];
|
|
33
177
|
function readTenancyDriver(env = process.env) {
|
|
34
178
|
const raw = env.TENANCY_DRIVER?.trim();
|
|
35
179
|
if (raw === undefined || raw === "") {
|
|
@@ -46,17 +190,13 @@ function isTenancyEnabled(env = process.env) {
|
|
|
46
190
|
function isRlsTenancy(env = process.env) {
|
|
47
191
|
return readTenancyDriver(env) === "rls";
|
|
48
192
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return tenantContext.run(tenant, callback);
|
|
54
|
-
}
|
|
55
|
-
function currentTenant() {
|
|
56
|
-
return tenantContext.getStore() ?? null;
|
|
57
|
-
}
|
|
193
|
+
var TENANCY_DRIVERS;
|
|
194
|
+
var init_tenancyConfig = __esm(() => {
|
|
195
|
+
TENANCY_DRIVERS = ["rls", "column", "none"];
|
|
196
|
+
});
|
|
58
197
|
|
|
59
198
|
// ../../src/core/tenant/tenantDatabaseScope.ts
|
|
199
|
+
import { getDefaultDatabasePool } from "@getstrata/core/database/defaultConnection";
|
|
60
200
|
async function applyTenantContextToTransaction(transaction, tenantId) {
|
|
61
201
|
await transaction.unsafe(`SELECT set_config('app.tenant_id', $1, true)`, [String(tenantId)]);
|
|
62
202
|
await transaction.unsafe(`SELECT set_config('app.bypass_rls', $1, true)`, ["false"]);
|
|
@@ -70,16 +210,32 @@ async function runWithTenantDatabase(tenant, callback) {
|
|
|
70
210
|
await applyTenantContextToTransaction(activeConnection, tenant.id);
|
|
71
211
|
return await runWithTenant(tenant, callback);
|
|
72
212
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
213
|
+
const pool = getDefaultDatabasePool();
|
|
214
|
+
const begin = pool.begin;
|
|
215
|
+
if (typeof begin !== "function") {
|
|
216
|
+
throw new Error("RLS tenant scope requires a pool that supports begin(). Session-scoped set_config is not used on pooled connections.");
|
|
217
|
+
}
|
|
218
|
+
return await runWithDeferredModelEvents(async () => {
|
|
219
|
+
return await begin(async (transaction) => {
|
|
220
|
+
await applyTenantContextToTransaction(transaction, tenant.id);
|
|
221
|
+
return await runWithDatabaseConnection(transaction, async () => {
|
|
222
|
+
return await runWithTenant(tenant, callback);
|
|
223
|
+
});
|
|
77
224
|
});
|
|
78
225
|
});
|
|
79
226
|
}
|
|
80
227
|
function isInsideTenantDatabaseScope(tenantId = currentTenant()?.id) {
|
|
81
228
|
return hasActiveDatabaseConnection() && currentTenant()?.id === tenantId;
|
|
82
229
|
}
|
|
230
|
+
var init_tenantDatabaseScope = __esm(() => {
|
|
231
|
+
init_connectionContext();
|
|
232
|
+
init_deferredModelEvents();
|
|
233
|
+
init_tenancyConfig();
|
|
234
|
+
init_tenantContext();
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// entries/tenant/tenantDatabaseScope.ts
|
|
238
|
+
init_tenantDatabaseScope();
|
|
83
239
|
export {
|
|
84
240
|
applyTenantContextToTransaction,
|
|
85
241
|
isInsideTenantDatabaseScope,
|
|
@@ -1,4 +1,31 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
var __esm = (fn, res, err) => () => {
|
|
3
|
+
if (fn)
|
|
4
|
+
try {
|
|
5
|
+
res = fn(fn = 0);
|
|
6
|
+
} catch (e) {
|
|
7
|
+
err = [e];
|
|
8
|
+
}
|
|
9
|
+
if (err)
|
|
10
|
+
throw err[0];
|
|
11
|
+
return res;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// ../../src/core/runtime/asyncContextStore.ts
|
|
15
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
16
|
+
function createAsyncContextStore(key) {
|
|
17
|
+
const symbol = Symbol.for(key);
|
|
18
|
+
const globalRecord = globalThis;
|
|
19
|
+
const existing = globalRecord[symbol];
|
|
20
|
+
if (existing) {
|
|
21
|
+
return existing;
|
|
22
|
+
}
|
|
23
|
+
const store = new AsyncLocalStorage;
|
|
24
|
+
globalRecord[symbol] = store;
|
|
25
|
+
return store;
|
|
26
|
+
}
|
|
27
|
+
var init_asyncContextStore = () => {};
|
|
28
|
+
|
|
2
29
|
// ../../src/core/runtime/appEnv.ts
|
|
3
30
|
var NON_PRODUCTION_APP_ENVS = new Set(["local", "development", "dev", "test", "testing", "ci"]);
|
|
4
31
|
function normalizeEnvValue(value) {
|
|
@@ -422,21 +449,8 @@ async function exportOtelSpan(span) {
|
|
|
422
449
|
});
|
|
423
450
|
}
|
|
424
451
|
|
|
425
|
-
// ../../src/core/runtime/asyncContextStore.ts
|
|
426
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
427
|
-
function createAsyncContextStore(key) {
|
|
428
|
-
const symbol = Symbol.for(key);
|
|
429
|
-
const globalRecord = globalThis;
|
|
430
|
-
const existing = globalRecord[symbol];
|
|
431
|
-
if (existing) {
|
|
432
|
-
return existing;
|
|
433
|
-
}
|
|
434
|
-
const store = new AsyncLocalStorage;
|
|
435
|
-
globalRecord[symbol] = store;
|
|
436
|
-
return store;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
452
|
// ../../src/core/tracing/traceContext.ts
|
|
453
|
+
init_asyncContextStore();
|
|
440
454
|
var traceContextStorage = createAsyncContextStore("@getstrata/traceContext");
|
|
441
455
|
function runWithTraceContext(context, callback) {
|
|
442
456
|
return traceContextStorage.run(context, callback);
|
|
@@ -71,7 +71,7 @@ export { WhereBuilder } from "../core/database/whereBuilder.ts";
|
|
|
71
71
|
export { BadRequestError, ConflictError, ForbiddenError, HttpError, InternalServerError, isHttpErrorLike, NotFoundError, PayloadTooLargeError, PreconditionFailedError, toHttpError, UnauthorizedError, UnprocessableEntityError, ValidationError, } from "../core/errors/http.ts";
|
|
72
72
|
export type { EventListener } from "../core/events/eventBus.ts";
|
|
73
73
|
export { EventBus, eventBus, readSharedEventBus } from "../core/events/eventBus.ts";
|
|
74
|
-
export { modelEventName } from "../core/events/index.ts";
|
|
74
|
+
export { dispatchModelEvent, modelEventName, runWithDeferredModelEvents, } from "../core/events/index.ts";
|
|
75
75
|
export { auth, cache, config, events, log, mail, policyGate, queue, storage, } from "../core/facades/index.ts";
|
|
76
76
|
export { createBodySizeLimitMiddleware } from "../core/http/bodySizeLimitMiddleware.ts";
|
|
77
77
|
export { readClientIp, trustForwardedFor } from "../core/http/clientIp.ts";
|