@getstrata/core 0.5.13 → 0.5.15

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.
Files changed (46) hide show
  1. package/README.md +16 -6
  2. package/dist/bootstrap/contracts.d.ts +2 -0
  3. package/dist/bootstrap/httpKernel.d.ts +1 -2
  4. package/dist/core/admin/registry.d.ts +1 -0
  5. package/dist/core/auth/membershipService.d.ts +5 -1
  6. package/dist/core/database/baseRepository.d.ts +6 -1
  7. package/dist/core/database/connectionContext.d.ts +2 -1
  8. package/dist/core/database/defaultConnection.d.ts +6 -0
  9. package/dist/core/database/queryProxy.d.ts +3 -0
  10. package/dist/core/database/repositoryConnection.d.ts +3 -3
  11. package/dist/core/jobs/dispatchWebhookJob.d.ts +0 -1
  12. package/dist/core/security/safeFetch.d.ts +2 -0
  13. package/dist/core/security/safeUrl.d.ts +16 -1
  14. package/dist/core/storage/storage.d.ts +6 -3
  15. package/dist/core/tenant/tenantDatabaseScope.d.ts +2 -1
  16. package/dist/db/connection/index.d.ts +1 -1
  17. package/dist/entries/auth/accessControl.js +1 -113
  18. package/dist/entries/auth/authContext.js +1 -15
  19. package/dist/entries/auth/guard.js +1 -3044
  20. package/dist/entries/auth/membershipContext.js +1 -276
  21. package/dist/entries/auth/membershipScope.js +1 -390
  22. package/dist/entries/auth/membershipService.js +1 -480
  23. package/dist/entries/auth/policy.js +1 -134
  24. package/dist/entries/database.js +1 -2398
  25. package/dist/entries/http/csrfToken.js +0 -6
  26. package/dist/entries/http/middleware.js +1 -68
  27. package/dist/entries/http/requestMetaContext.js +1 -18
  28. package/dist/entries/http/webErrorResponse.js +94 -563
  29. package/dist/entries/http/webFormRequest.js +0 -131
  30. package/dist/entries/http.js +1 -4091
  31. package/dist/entries/jobs/dispatchWebhookJob.js +109 -102
  32. package/dist/entries/queue/createAppQueue.js +111 -631
  33. package/dist/entries/queue/jobRunner.js +0 -3
  34. package/dist/entries/queue/publicQueue.js +45 -546
  35. package/dist/entries/queue/queueMetrics.js +111 -631
  36. package/dist/entries/security/safeUrl.js +29 -0
  37. package/dist/entries/security/securityEvents.js +1 -41
  38. package/dist/entries/storage/storage.js +14 -4
  39. package/dist/entries/tenant/tenantContext.js +1 -30
  40. package/dist/entries/tenant/tenantMiddleware.js +1 -312
  41. package/dist/entries/tracing/traceContext.js +1 -15
  42. package/dist/entries/view.js +94 -563
  43. package/dist/framework/public-api.d.ts +36 -4
  44. package/dist/index.js +2270 -1666
  45. package/dist/modules/user/repository.d.ts +1 -0
  46. package/package.json +6 -5
@@ -1,480 +1 @@
1
- // @bun
2
- // ../../src/core/logging/logger.ts
3
- class Logger {
4
- channel;
5
- constructor(channel = "app") {
6
- this.channel = channel;
7
- }
8
- write(level, message, context = {}) {
9
- const entry = {
10
- level,
11
- channel: this.channel,
12
- message,
13
- timestamp: new Date().toISOString(),
14
- ...context
15
- };
16
- const line = JSON.stringify(entry);
17
- if (level === "error") {
18
- console.error(line);
19
- return;
20
- }
21
- console.log(line);
22
- }
23
- debug(message, context) {
24
- this.write("debug", message, context);
25
- }
26
- info(message, context) {
27
- this.write("info", message, context);
28
- }
29
- warn(message, context) {
30
- this.write("warn", message, context);
31
- }
32
- error(message, context) {
33
- this.write("error", message, context);
34
- }
35
- }
36
- var appLogger = new Logger("app");
37
-
38
- // ../../src/bootstrap/config.ts
39
- var CORE_QUEUE_TOKEN = "core.queue";
40
- var CORE_POLICY_GATE_TOKEN = "core.policyGate";
41
- var CORE_AUTH_TOKEN = "core.auth";
42
- var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
43
- var DEFAULT_QUEUE_DRIVER = "sync";
44
-
45
- // ../../src/bootstrap/contracts.ts
46
- class ServiceContainer {
47
- services = new Map;
48
- singletonFactories = new Map;
49
- bindings = new Map;
50
- set(key, value) {
51
- this.singletonFactories.delete(key);
52
- this.bindings.delete(key);
53
- this.services.set(key, value);
54
- return value;
55
- }
56
- singleton(key, factory) {
57
- this.bindings.delete(key);
58
- this.services.delete(key);
59
- this.singletonFactories.set(key, factory);
60
- }
61
- bind(key, factory) {
62
- this.singletonFactories.delete(key);
63
- this.services.delete(key);
64
- this.bindings.set(key, factory);
65
- }
66
- get(key) {
67
- if (this.services.has(key)) {
68
- return this.services.get(key);
69
- }
70
- const singletonFactory = this.singletonFactories.get(key);
71
- if (singletonFactory) {
72
- const value = singletonFactory(this);
73
- this.services.set(key, value);
74
- return value;
75
- }
76
- const binding = this.bindings.get(key);
77
- if (binding) {
78
- return binding(this);
79
- }
80
- throw new Error(`Service "${key}" is not registered.`);
81
- }
82
- resolve(key) {
83
- return this.get(key);
84
- }
85
- has(key) {
86
- return this.services.has(key) || this.singletonFactories.has(key) || this.bindings.has(key);
87
- }
88
- }
89
-
90
- class ConfigStore {
91
- values = new Map;
92
- set(key, value) {
93
- this.values.set(key, value);
94
- return value;
95
- }
96
- get(key) {
97
- return this.values.get(key);
98
- }
99
- require(key) {
100
- if (!this.values.has(key)) {
101
- throw new Error(`Config key "${key}" is not defined.`);
102
- }
103
- return this.values.get(key);
104
- }
105
- has(key) {
106
- return this.values.has(key);
107
- }
108
- }
109
- function getRequiredDependency(dependencies, key) {
110
- const dependency = dependencies[key];
111
- if (dependency === undefined) {
112
- throw new Error(`Required dependency "${key}" is not registered.`);
113
- }
114
- return dependency;
115
- }
116
- function resolveService(dependencies, token) {
117
- return dependencies.container.resolve(token);
118
- }
119
-
120
- // ../../src/bootstrap/applicationRegistry.ts
121
- var activeContext;
122
- function setActiveApplicationContext(context) {
123
- activeContext = context;
124
- }
125
- function requireActiveApplicationContext() {
126
- if (!activeContext) {
127
- throw new Error("The application context has not been bootstrapped.");
128
- }
129
- return activeContext;
130
- }
131
- function resolveApplicationCache() {
132
- return getRequiredDependency(requireActiveApplicationContext().dependencies, "cache");
133
- }
134
- function resolveApplicationQueue() {
135
- return requireActiveApplicationContext().container.resolve(CORE_QUEUE_TOKEN);
136
- }
137
- function resolveApplicationAuth() {
138
- return requireActiveApplicationContext().container.resolve(CORE_AUTH_TOKEN);
139
- }
140
- function resolveApplicationPolicyGate() {
141
- return requireActiveApplicationContext().container.resolve(CORE_POLICY_GATE_TOKEN);
142
- }
143
- function resolveApplicationConfig() {
144
- return requireActiveApplicationContext().config;
145
- }
146
- function resolveApplicationLogger() {
147
- return appLogger;
148
- }
149
- function resolveApplicationDependencies() {
150
- return requireActiveApplicationContext().dependencies;
151
- }
152
-
153
- // ../../src/core/errors/http.ts
154
- class HttpError extends Error {
155
- status;
156
- details;
157
- constructor(status, message, details) {
158
- super(message);
159
- this.name = new.target.name;
160
- this.status = status;
161
- this.details = details;
162
- }
163
- }
164
-
165
- class BadRequestError extends HttpError {
166
- constructor(message = "Bad Request", details) {
167
- super(400, message, details);
168
- }
169
- }
170
-
171
- class NotFoundError extends HttpError {
172
- constructor(message = "Not Found", details) {
173
- super(404, message, details);
174
- }
175
- }
176
-
177
- class ConflictError extends HttpError {
178
- constructor(message = "Conflict", details) {
179
- super(409, message, details);
180
- }
181
- }
182
-
183
- class UnprocessableEntityError extends HttpError {
184
- constructor(message = "Unprocessable Entity", details) {
185
- super(422, message, details);
186
- }
187
- }
188
-
189
- class ValidationError extends HttpError {
190
- constructor(message = "Validation failed", details) {
191
- super(422, message, details);
192
- }
193
- }
194
-
195
- class ForbiddenError extends HttpError {
196
- constructor(message = "Forbidden", details) {
197
- super(403, message, details);
198
- }
199
- }
200
-
201
- class UnauthorizedError extends HttpError {
202
- constructor(message = "Unauthorized", details) {
203
- super(401, message, details);
204
- }
205
- }
206
-
207
- class PayloadTooLargeError extends HttpError {
208
- constructor(message = "Payload Too Large", details) {
209
- super(413, message, details);
210
- }
211
- }
212
-
213
- class PreconditionFailedError extends HttpError {
214
- constructor(message = "Precondition Failed", details) {
215
- super(412, message, details);
216
- }
217
- }
218
-
219
- // ../../src/core/auth/authContext.ts
220
- import { AsyncLocalStorage } from "async_hooks";
221
- var authContext = new AsyncLocalStorage;
222
- function runWithAuthUser(user, callback) {
223
- return authContext.run(user, callback);
224
- }
225
- function currentAuthUser() {
226
- return authContext.getStore() ?? null;
227
- }
228
-
229
- // ../../src/core/auth/accessControl.ts
230
- var ROLE_RANK = {
231
- member: 1,
232
- admin: 2,
233
- owner: 3
234
- };
235
- function isGlobalAdmin(user) {
236
- return user?.role === "admin";
237
- }
238
- function hasMinimumOrgRole(role, minimum) {
239
- if (!role) {
240
- return false;
241
- }
242
- return ROLE_RANK[role] >= ROLE_RANK[minimum];
243
- }
244
- function requireAuthenticatedUser() {
245
- const user = currentAuthUser();
246
- if (!user) {
247
- throw new ForbiddenError("Authentication required.");
248
- }
249
- return user;
250
- }
251
- function resolveUserId(user) {
252
- const userId = typeof user.id === "number" ? user.id : Number(user.id);
253
- if (!Number.isInteger(userId) || userId <= 0) {
254
- throw new ForbiddenError("Invalid authenticated user.");
255
- }
256
- return userId;
257
- }
258
-
259
- // ../../src/core/auth/membershipContext.ts
260
- import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
261
-
262
- // ../../src/config/database.ts
263
- function readInteger(name, fallback) {
264
- const parsed = Number.parseInt(process.env[name] ?? String(fallback), 10);
265
- return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
266
- }
267
- var databaseConfig = {
268
- url: process.env.DATABASE_URL ?? "",
269
- poolMax: readInteger("DB_POOL_MAX", 10),
270
- idleTimeoutSeconds: readInteger("DB_POOL_IDLE_TIMEOUT", 30),
271
- maxLifetimeSeconds: readInteger("DB_POOL_MAX_LIFETIME", 3600),
272
- connectionTimeoutSeconds: readInteger("DB_CONNECTION_TIMEOUT", 10)
273
- };
274
-
275
- // ../../src/core/database/connectionContext.ts
276
- import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
277
- var activeConnection = new AsyncLocalStorage2;
278
- function runWithDatabaseConnection(connection, callback) {
279
- return activeConnection.run(connection, callback);
280
- }
281
- function getActiveDatabaseConnection(fallback) {
282
- return activeConnection.getStore() ?? fallback;
283
- }
284
-
285
- // ../../src/db/connection/createConnection.ts
286
- var {SQL } = globalThis.Bun;
287
- function createDatabaseConnection(config) {
288
- if (!config.url) {
289
- throw new Error("DATABASE_URL is not configured. Set DATABASE_URL before starting the app or running integration tests.");
290
- }
291
- return new SQL({
292
- url: config.url,
293
- max: config.poolMax,
294
- idleTimeout: config.idleTimeoutSeconds,
295
- maxLifetime: config.maxLifetimeSeconds,
296
- connectionTimeout: config.connectionTimeoutSeconds
297
- });
298
- }
299
-
300
- // ../../src/db/connection/index.ts
301
- var connectionHolder = {
302
- connection: null
303
- };
304
- function getDatabase() {
305
- if (!connectionHolder.connection) {
306
- connectionHolder.connection = createDatabaseConnection(databaseConfig);
307
- }
308
- return connectionHolder.connection;
309
- }
310
- var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
311
- function resolveDatabase() {
312
- return getActiveDatabaseConnection(getDatabase());
313
- }
314
- function resolveDatabaseForProperty(property) {
315
- if (typeof property === "string" && POOL_CONNECTION_METHODS.has(property)) {
316
- return getDatabase();
317
- }
318
- return resolveDatabase();
319
- }
320
- var db = new Proxy(function database() {}, {
321
- apply(_target, _thisArg, args) {
322
- return resolveDatabase()(...args);
323
- },
324
- get(_target, property) {
325
- const connection = resolveDatabaseForProperty(property);
326
- const value = connection[property];
327
- return typeof value === "function" ? value.bind(connection) : value;
328
- }
329
- });
330
- var connection_default = db;
331
-
332
- // ../../src/modules/organization/memberRepository.ts
333
- class OrganizationMemberRepository {
334
- constructor() {}
335
- async findMembership(userId, organizationId) {
336
- const rows = await connection_default`
337
- SELECT id, organization_id, user_id, role, created_at
338
- FROM organization_member
339
- WHERE user_id = ${userId} AND organization_id = ${organizationId}
340
- LIMIT 1
341
- `;
342
- return rows[0] ?? null;
343
- }
344
- async listForUser(userId) {
345
- return await connection_default`
346
- SELECT id, organization_id, user_id, role, created_at
347
- FROM organization_member
348
- WHERE user_id = ${userId}
349
- ORDER BY organization_id
350
- `;
351
- }
352
- async listForOrganization(organizationId) {
353
- return await connection_default`
354
- SELECT id, organization_id, user_id, role, created_at
355
- FROM organization_member
356
- WHERE organization_id = ${organizationId}
357
- ORDER BY id
358
- `;
359
- }
360
- async addMember(input) {
361
- const rows = await connection_default`
362
- INSERT INTO organization_member (organization_id, user_id, role)
363
- VALUES (${input.organizationId}, ${input.userId}, ${input.role ?? "member"})
364
- RETURNING id, organization_id, user_id, role, created_at
365
- `;
366
- const row = rows[0];
367
- if (!row) {
368
- throw new Error("Organization member insert did not return a row.");
369
- }
370
- return row;
371
- }
372
- async removeMember(organizationId, userId) {
373
- const rows = await connection_default`
374
- DELETE FROM organization_member
375
- WHERE organization_id = ${organizationId} AND user_id = ${userId}
376
- RETURNING id
377
- `;
378
- return rows.length > 0;
379
- }
380
- }
381
- var memberRepository_default = OrganizationMemberRepository;
382
-
383
- // ../../src/core/auth/membershipContext.ts
384
- var membershipContext = new AsyncLocalStorage3;
385
- var membershipRepository = new memberRepository_default;
386
- async function runWithMembershipContext(callback) {
387
- const user = currentAuthUser();
388
- if (!user || isGlobalAdmin(user)) {
389
- return await callback();
390
- }
391
- const memberships = await membershipRepository.listForUser(resolveUserId(user));
392
- const context = {
393
- organizationIds: memberships.map((membership) => membership.organization_id),
394
- rolesByOrganizationId: new Map(memberships.map((membership) => [membership.organization_id, membership.role]))
395
- };
396
- return await membershipContext.run(context, callback);
397
- }
398
- function currentOrgRole(organizationId) {
399
- return membershipContext.getStore()?.rolesByOrganizationId.get(organizationId) ?? null;
400
- }
401
- function hasOrgMembership(organizationId) {
402
- return currentOrgRole(organizationId) !== null;
403
- }
404
- function currentOrganizationIds() {
405
- return membershipContext.getStore()?.organizationIds ?? [];
406
- }
407
- function hasMinimumOrgRole2(organizationId, minimum) {
408
- const role = currentOrgRole(organizationId);
409
- if (!role) {
410
- return false;
411
- }
412
- const ranks = {
413
- member: 1,
414
- admin: 2,
415
- owner: 3
416
- };
417
- return ranks[role] >= ranks[minimum];
418
- }
419
-
420
- // ../../src/core/auth/membershipService.ts
421
- class MembershipService {
422
- constructor() {}
423
- async listOrganizationIdsForUser(userId) {
424
- const memberships = await membershipRepository.listForUser(userId);
425
- return memberships.map((membership) => membership.organization_id);
426
- }
427
- async getOrgRole(userId, organizationId) {
428
- const membership = await membershipRepository.findMembership(userId, organizationId);
429
- return membership?.role ?? null;
430
- }
431
- async requireOrgAccess(organizationId, minimumRole = "member", user = currentAuthUser()) {
432
- if (!user) {
433
- throw new ForbiddenError("Authentication required.");
434
- }
435
- if (isGlobalAdmin(user)) {
436
- return "owner";
437
- }
438
- const role = await this.getOrgRole(resolveUserId(user), organizationId);
439
- if (!role || !hasMinimumOrgRole(role, minimumRole)) {
440
- throw new ForbiddenError("Organization membership required.");
441
- }
442
- return role;
443
- }
444
- async filterAccessibleOrganizationIds(organizationIds, user = currentAuthUser()) {
445
- if (!user) {
446
- return [];
447
- }
448
- if (isGlobalAdmin(user)) {
449
- return organizationIds;
450
- }
451
- const allowed = new Set(await this.listOrganizationIdsForUser(resolveUserId(user)));
452
- return organizationIds.filter((organizationId) => allowed.has(organizationId));
453
- }
454
- async addOwnerOnOrganizationCreate(organizationId, userId) {
455
- await membershipRepository.addMember({
456
- organizationId,
457
- userId,
458
- role: "owner"
459
- });
460
- }
461
- listMembersForOrganization(organizationId) {
462
- return membershipRepository.listForOrganization(organizationId);
463
- }
464
- addMember(input) {
465
- return membershipRepository.addMember(input);
466
- }
467
- removeMember(organizationId, userId) {
468
- return membershipRepository.removeMember(organizationId, userId);
469
- }
470
- }
471
- function resolveMembershipService() {
472
- const dependencies = resolveApplicationDependencies();
473
- if (dependencies.container.has("core.membership")) {
474
- return dependencies.container.resolve("core.membership");
475
- }
476
- return new MembershipService;
477
- }
478
- export {
479
- resolveMembershipService
480
- };
1
+ export * from "../../index.js";
@@ -1,134 +1 @@
1
- // @bun
2
- // ../../src/core/errors/http.ts
3
- class HttpError extends Error {
4
- status;
5
- details;
6
- constructor(status, message, details) {
7
- super(message);
8
- this.name = new.target.name;
9
- this.status = status;
10
- this.details = details;
11
- }
12
- }
13
-
14
- class BadRequestError extends HttpError {
15
- constructor(message = "Bad Request", details) {
16
- super(400, message, details);
17
- }
18
- }
19
-
20
- class NotFoundError extends HttpError {
21
- constructor(message = "Not Found", details) {
22
- super(404, message, details);
23
- }
24
- }
25
-
26
- class ConflictError extends HttpError {
27
- constructor(message = "Conflict", details) {
28
- super(409, message, details);
29
- }
30
- }
31
-
32
- class UnprocessableEntityError extends HttpError {
33
- constructor(message = "Unprocessable Entity", details) {
34
- super(422, message, details);
35
- }
36
- }
37
-
38
- class ValidationError extends HttpError {
39
- constructor(message = "Validation failed", details) {
40
- super(422, message, details);
41
- }
42
- }
43
-
44
- class ForbiddenError extends HttpError {
45
- constructor(message = "Forbidden", details) {
46
- super(403, message, details);
47
- }
48
- }
49
-
50
- class UnauthorizedError extends HttpError {
51
- constructor(message = "Unauthorized", details) {
52
- super(401, message, details);
53
- }
54
- }
55
-
56
- class PayloadTooLargeError extends HttpError {
57
- constructor(message = "Payload Too Large", details) {
58
- super(413, message, details);
59
- }
60
- }
61
-
62
- class PreconditionFailedError extends HttpError {
63
- constructor(message = "Precondition Failed", details) {
64
- super(412, message, details);
65
- }
66
- }
67
-
68
- // ../../src/core/auth/authContext.ts
69
- import { AsyncLocalStorage } from "async_hooks";
70
- var authContext = new AsyncLocalStorage;
71
- function runWithAuthUser(user, callback) {
72
- return authContext.run(user, callback);
73
- }
74
- function currentAuthUser() {
75
- return authContext.getStore() ?? null;
76
- }
77
-
78
- // ../../src/core/auth/policy.ts
79
- class Policy {
80
- constructor() {}
81
- view(_user, _resource) {
82
- return false;
83
- }
84
- create(_user) {
85
- return false;
86
- }
87
- update(_user, _resource) {
88
- return false;
89
- }
90
- delete(_user, _resource) {
91
- return false;
92
- }
93
- }
94
- var BLOCKED_POLICY_ACTIONS = new Set([
95
- "constructor",
96
- "toString",
97
- "valueOf",
98
- "hasOwnProperty",
99
- "isPrototypeOf",
100
- "propertyIsEnumerable",
101
- "__proto__"
102
- ]);
103
-
104
- class PolicyGate {
105
- constructor() {}
106
- policies = new Map;
107
- register(resource, policy) {
108
- this.policies.set(resource, policy);
109
- }
110
- allows(resource, action, user, model) {
111
- const policy = this.policies.get(resource);
112
- if (!policy) {
113
- return false;
114
- }
115
- if (BLOCKED_POLICY_ACTIONS.has(action) || !(action in policy)) {
116
- return false;
117
- }
118
- const handler = policy[action];
119
- if (typeof handler !== "function") {
120
- return false;
121
- }
122
- const resolvedUser = user === undefined ? currentAuthUser() : user;
123
- return model === undefined ? handler.call(policy, resolvedUser) : handler.call(policy, resolvedUser, model);
124
- }
125
- authorize(resource, action, user, model) {
126
- if (!this.allows(resource, action, user, model)) {
127
- throw new ForbiddenError;
128
- }
129
- }
130
- }
131
- export {
132
- PolicyGate,
133
- Policy
134
- };
1
+ export * from "../../index.js";