@getstrata/core 0.5.55 → 0.5.56

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 (77) hide show
  1. package/README.md +2 -0
  2. package/dist/core/media/imageTransform.d.ts +13 -0
  3. package/dist/core/scheduler/osCron.d.ts +14 -0
  4. package/dist/core/terminal/runShell.d.ts +6 -0
  5. package/dist/core/view/assertEtaHtmlSource.d.ts +5 -0
  6. package/dist/core/view/etaViewEngine.d.ts +4 -0
  7. package/dist/entries/audit/exportAuditLogs.js +2 -2
  8. package/dist/entries/audit/siemFormatter.js +2 -2
  9. package/dist/entries/auth/accessControl.js +4 -4
  10. package/dist/entries/auth/membershipScope.js +6 -6
  11. package/dist/entries/auth/oauth/providers.js +2 -2
  12. package/dist/entries/auth/password.js +2 -2
  13. package/dist/entries/auth/policy.js +2 -2
  14. package/dist/entries/auth/sessionCookie.js +4 -4
  15. package/dist/entries/auth/sessionGuard.js +0 -91
  16. package/dist/entries/auth/tokenHash.js +2 -2
  17. package/dist/entries/config/envSchema.js +2 -2
  18. package/dist/entries/contracts/container.js +2 -2
  19. package/dist/entries/contracts/di.js +2 -2
  20. package/dist/entries/contracts/serviceTokens.js +6 -6
  21. package/dist/entries/crypto/fieldEncryption.js +8 -8
  22. package/dist/entries/crypto/mfaSecret.js +2 -2
  23. package/dist/entries/database/errors.js +2 -2
  24. package/dist/entries/database/migrations.js +8 -8
  25. package/dist/entries/database/model.js +5 -5
  26. package/dist/entries/database/query.js +19 -19
  27. package/dist/entries/database/relationships.js +13 -13
  28. package/dist/entries/database/schema.js +13 -13
  29. package/dist/entries/database/seeders.js +2 -2
  30. package/dist/entries/facades.js +8 -8
  31. package/dist/entries/http/authMiddleware.js +2 -2
  32. package/dist/entries/http/bodySizeLimitMiddleware.js +2 -2
  33. package/dist/entries/http/cookies.js +2 -2
  34. package/dist/entries/http/csrfProtection.js +2 -2
  35. package/dist/entries/http/csrfToken.js +6 -6
  36. package/dist/entries/http/etag.js +8 -8
  37. package/dist/entries/http/flashSession.js +4 -4
  38. package/dist/entries/http/formRequest.js +2 -2
  39. package/dist/entries/http/loginThrottleMiddleware.js +2 -2
  40. package/dist/entries/http/metricsMiddleware.js +2 -2
  41. package/dist/entries/http/pagination.js +4 -4
  42. package/dist/entries/http/parseFormBody.js +2 -2
  43. package/dist/entries/http/parseMultipartUpload.js +2 -2
  44. package/dist/entries/http/resources.js +2 -2
  45. package/dist/entries/http/response.js +186 -98
  46. package/dist/entries/http/securedRouteModelBinding.js +2 -2
  47. package/dist/entries/http/throttleMiddleware.js +2 -2
  48. package/dist/entries/http/validation.js +13 -13
  49. package/dist/entries/http/webErrorResponse.js +184 -96
  50. package/dist/entries/lifecycle/gracefulShutdown.js +3 -3
  51. package/dist/entries/logging/logger.js +2 -2
  52. package/dist/entries/mail/mailer.js +6 -6
  53. package/dist/entries/mail/markdownMail.js +3 -3
  54. package/dist/entries/mail/markdownMailable.js +2 -2
  55. package/dist/entries/media/imageTransform.js +40 -0
  56. package/dist/entries/metrics/prometheus.js +2 -2
  57. package/dist/entries/openapi/generator.js +2 -2
  58. package/dist/entries/queue/createAppQueue.js +4 -4
  59. package/dist/entries/queue/publicQueue.js +10 -10
  60. package/dist/entries/queue/queueMetrics.js +2 -2
  61. package/dist/entries/queue/redisQueue.js +7 -7
  62. package/dist/entries/queue.js +3 -3
  63. package/dist/entries/scheduler/osCron.js +27 -0
  64. package/dist/entries/scheduler/schedule.js +2 -2
  65. package/dist/entries/security/oauthState.js +3 -3
  66. package/dist/entries/security/publicReads.js +2 -2
  67. package/dist/entries/security/safeFetch.js +2 -2
  68. package/dist/entries/security/safeUrl.js +5 -5
  69. package/dist/entries/security/scimTenantTokens.js +2 -2
  70. package/dist/entries/security/totp.js +2 -2
  71. package/dist/entries/storage/storage.js +7 -7
  72. package/dist/entries/tenant/tenantDatabaseScope.js +2 -2
  73. package/dist/entries/terminal/runShell.js +18 -0
  74. package/dist/entries/validation/rules.js +12 -12
  75. package/dist/entries/view.js +186 -98
  76. package/dist/index.js +431 -469
  77. package/package.json +17 -2
@@ -86,6 +86,6 @@ function createMetricsMiddleware() {
86
86
  };
87
87
  }
88
88
  export {
89
- normalizeMetricPath,
90
- createMetricsMiddleware
89
+ createMetricsMiddleware,
90
+ normalizeMetricPath
91
91
  };
@@ -174,9 +174,9 @@ function paginatedResponse(data, meta, init = {}) {
174
174
  return Response.json({ data, meta }, init);
175
175
  }
176
176
  export {
177
- parsePaginationQuery,
178
- paginatedResponse,
179
- buildPaginationMeta,
177
+ DEFAULT_PER_PAGE,
180
178
  MAX_PER_PAGE,
181
- DEFAULT_PER_PAGE
179
+ buildPaginationMeta,
180
+ paginatedResponse,
181
+ parsePaginationQuery
182
182
  };
@@ -18,6 +18,6 @@ function formDataToRecord(formData) {
18
18
  return values;
19
19
  }
20
20
  export {
21
- parseFormBody,
22
- formDataToRecord
21
+ formDataToRecord,
22
+ parseFormBody
23
23
  };
@@ -76,6 +76,6 @@ async function parseMultipartUpload(request, fieldName = "file") {
76
76
  };
77
77
  }
78
78
  export {
79
- sanitizeUploadFileName,
80
- parseMultipartUpload
79
+ parseMultipartUpload,
80
+ sanitizeUploadFileName
81
81
  };
@@ -13,7 +13,7 @@ function toPaginatedResourceCollection(items, meta, transformer) {
13
13
  };
14
14
  }
15
15
  export {
16
- toResourceCollection,
16
+ serializeDate,
17
17
  toPaginatedResourceCollection,
18
- serializeDate
18
+ toResourceCollection
19
19
  };
@@ -84,8 +84,183 @@ function isViewsEnabled() {
84
84
  }
85
85
 
86
86
  // ../../src/core/view/etaViewEngine.ts
87
- import { join } from "path";
87
+ import { join, relative } from "path";
88
88
  import { Eta } from "eta";
89
+
90
+ // ../../src/core/view/assertEtaHtmlSource.ts
91
+ var HTML_TAGS = new Set([
92
+ "a",
93
+ "abbr",
94
+ "address",
95
+ "article",
96
+ "aside",
97
+ "audio",
98
+ "b",
99
+ "blockquote",
100
+ "body",
101
+ "button",
102
+ "canvas",
103
+ "caption",
104
+ "cite",
105
+ "code",
106
+ "col",
107
+ "colgroup",
108
+ "data",
109
+ "datalist",
110
+ "dd",
111
+ "del",
112
+ "details",
113
+ "dfn",
114
+ "dialog",
115
+ "div",
116
+ "dl",
117
+ "dt",
118
+ "em",
119
+ "fieldset",
120
+ "figcaption",
121
+ "figure",
122
+ "footer",
123
+ "form",
124
+ "h1",
125
+ "h2",
126
+ "h3",
127
+ "h4",
128
+ "h5",
129
+ "h6",
130
+ "header",
131
+ "hgroup",
132
+ "hr",
133
+ "html",
134
+ "i",
135
+ "iframe",
136
+ "img",
137
+ "input",
138
+ "ins",
139
+ "kbd",
140
+ "label",
141
+ "legend",
142
+ "li",
143
+ "main",
144
+ "map",
145
+ "mark",
146
+ "menu",
147
+ "meter",
148
+ "nav",
149
+ "object",
150
+ "ol",
151
+ "optgroup",
152
+ "option",
153
+ "output",
154
+ "p",
155
+ "picture",
156
+ "pre",
157
+ "progress",
158
+ "q",
159
+ "rp",
160
+ "rt",
161
+ "ruby",
162
+ "s",
163
+ "samp",
164
+ "script",
165
+ "search",
166
+ "section",
167
+ "select",
168
+ "slot",
169
+ "small",
170
+ "source",
171
+ "span",
172
+ "strong",
173
+ "style",
174
+ "sub",
175
+ "summary",
176
+ "sup",
177
+ "table",
178
+ "tbody",
179
+ "td",
180
+ "template",
181
+ "textarea",
182
+ "tfoot",
183
+ "th",
184
+ "thead",
185
+ "time",
186
+ "title",
187
+ "tr",
188
+ "track",
189
+ "u",
190
+ "ul",
191
+ "var",
192
+ "video",
193
+ "wbr"
194
+ ]);
195
+ var TAG_PATTERN = "([a-z][a-z0-9]*)";
196
+ var CLASS_OR_ID_PATTERN = "[.#][\\w-]+";
197
+ var CLASS_ID_SHORTHAND = new RegExp(`^${TAG_PATTERN}(?:${CLASS_OR_ID_PATTERN})+`);
198
+ var ATTRIBUTE_SHORTHAND = new RegExp(`^${TAG_PATTERN}(?:${CLASS_OR_ID_PATTERN})*\\s+[\\w:-]+=`);
199
+ var TAG_SHORTHAND = new RegExp(`^${TAG_PATTERN}(?:>|\\s+\\S|$)`);
200
+ function htmlTagName(match) {
201
+ const tag = match?.[1];
202
+ return tag && HTML_TAGS.has(tag) ? tag : undefined;
203
+ }
204
+ function describePugLikeLine(line) {
205
+ if (/^\|(?:\s|$)/.test(line)) {
206
+ return "piped text";
207
+ }
208
+ if (htmlTagName(line.match(CLASS_ID_SHORTHAND))) {
209
+ return "class/id shorthand";
210
+ }
211
+ if (htmlTagName(line.match(ATTRIBUTE_SHORTHAND))) {
212
+ return "attribute shorthand";
213
+ }
214
+ if (htmlTagName(line.match(TAG_SHORTHAND))) {
215
+ return "tag shorthand";
216
+ }
217
+ return;
218
+ }
219
+ function updateSkipBlock(lower, skipBlock) {
220
+ if (skipBlock) {
221
+ if (skipBlock === "comment" && lower.includes("-->")) {
222
+ return null;
223
+ }
224
+ if (skipBlock !== "comment" && lower.includes(`</${skipBlock}`)) {
225
+ return null;
226
+ }
227
+ return skipBlock;
228
+ }
229
+ if (lower.startsWith("<!--") && !lower.includes("-->")) {
230
+ return "comment";
231
+ }
232
+ if (lower.startsWith("<script") && !lower.includes("</script")) {
233
+ return "script";
234
+ }
235
+ if (lower.startsWith("<style") && !lower.includes("</style")) {
236
+ return "style";
237
+ }
238
+ return null;
239
+ }
240
+ function assertEtaHtmlSource(templateName, source) {
241
+ let skipBlock = null;
242
+ for (const rawLine of source.split(/\r?\n/)) {
243
+ const trimmed = rawLine.trim();
244
+ if (!trimmed) {
245
+ continue;
246
+ }
247
+ const lower = trimmed.toLowerCase();
248
+ const nextSkipBlock = updateSkipBlock(lower, skipBlock);
249
+ if (skipBlock || nextSkipBlock) {
250
+ skipBlock = nextSkipBlock;
251
+ continue;
252
+ }
253
+ if (trimmed.startsWith("<") || trimmed.startsWith("<%")) {
254
+ continue;
255
+ }
256
+ const kind = describePugLikeLine(trimmed);
257
+ if (kind) {
258
+ throw new Error(`Eta view "${templateName}" contains Pug-like markup (${kind}: ${trimmed}). Eta views must be HTML + <% %> tags, not Pug.`);
259
+ }
260
+ }
261
+ }
262
+
263
+ // ../../src/core/view/etaViewEngine.ts
89
264
  var DEFAULT_VIEWS_DIRECTORY = join(process.cwd(), "resources/views");
90
265
  var DEFAULT_LAYOUT = "layouts/app.eta";
91
266
 
@@ -98,6 +273,12 @@ class EtaViewEngine {
98
273
  autoTrim: false
99
274
  });
100
275
  this.resolveLayoutData = resolveLayoutData;
276
+ const readFile = this.eta.readFile?.bind(this.eta);
277
+ this.eta.readFile = (path) => {
278
+ const source = readFile ? readFile(path) : "";
279
+ assertEtaHtmlSource(relative(viewsDirectory, path) || path, source);
280
+ return source;
281
+ };
101
282
  }
102
283
  async render(name, data = {}, options = {}) {
103
284
  const template = name.endsWith(".eta") ? name : `${name}.eta`;
@@ -361,99 +542,6 @@ function resolveAbilitiesForRole(role) {
361
542
  return [...MEMBER_ABILITIES];
362
543
  }
363
544
 
364
- // ../../src/modules/user/authService.ts
365
- class AuthService {
366
- users;
367
- tokens;
368
- oauthIdentities;
369
- oauthProviders = new Map;
370
- constructor(users, tokens, oauthIdentities) {
371
- this.users = users;
372
- this.tokens = tokens;
373
- this.oauthIdentities = oauthIdentities;
374
- }
375
- registerOAuthProvider(provider) {
376
- this.oauthProviders.set(provider.name, provider);
377
- }
378
- getOAuthProvider(name) {
379
- return this.oauthProviders.get(name);
380
- }
381
- async loginWithPassword(email, password, options = {}) {
382
- const user = await this.users.findByEmail(email);
383
- if (!user?.password_hash) {
384
- logSecurityEvent("auth_login_failed", { reason: "unknown_user", email });
385
- throw new UnauthorizedError("Invalid credentials.");
386
- }
387
- const valid = await verifyPassword(password, user.password_hash);
388
- if (!valid) {
389
- logSecurityEvent("auth_login_failed", { reason: "invalid_password", user_id: user.id });
390
- throw new UnauthorizedError("Invalid credentials.");
391
- }
392
- if (isFeatureEnabled("emailVerification") && !user.email_verified_at) {
393
- logSecurityEvent("auth_login_blocked", { reason: "email_unverified", user_id: user.id });
394
- throw new UnauthorizedError("Email address is not verified.");
395
- }
396
- if (isFeatureEnabled("mfa") && user.mfa_enabled) {
397
- const mfaSecret = revealMfaSecret(user.mfa_secret);
398
- if (!mfaSecret || !options.mfaCode || !verifyTotp(mfaSecret, options.mfaCode)) {
399
- logSecurityEvent("auth_login_failed", { reason: "invalid_mfa", user_id: user.id });
400
- throw new UnauthorizedError("Invalid MFA code.");
401
- }
402
- }
403
- logSecurityEvent("auth_login_success", { user_id: user.id, method: "password" });
404
- return await this.tokens.createToken(user.id, {
405
- name: "password-login",
406
- abilities: resolveAbilitiesForRole(user.role),
407
- expiresInDays: resolveDefaultTokenExpiryDays() ?? undefined
408
- });
409
- }
410
- async loginWithOAuth(providerName, code) {
411
- const provider = this.oauthProviders.get(providerName);
412
- if (!provider) {
413
- throw new UnauthorizedError("Unsupported OAuth provider.");
414
- }
415
- const profile = await provider.exchangeCode(code);
416
- const user = await this.findOrCreateOAuthUser(providerName, profile);
417
- logSecurityEvent("auth_login_success", { user_id: user.id, method: `oauth:${providerName}` });
418
- return await this.tokens.createToken(user.id, {
419
- name: `${providerName}-oauth`,
420
- abilities: resolveAbilitiesForRole(user.role),
421
- expiresInDays: resolveDefaultTokenExpiryDays() ?? undefined
422
- });
423
- }
424
- buildOAuthAuthorizationUrl(providerName, state) {
425
- const provider = this.oauthProviders.get(providerName);
426
- if (!provider) {
427
- throw new UnauthorizedError("Unsupported OAuth provider.");
428
- }
429
- return provider.getAuthorizationUrl(state);
430
- }
431
- async findOrCreateOAuthUser(providerName, profile) {
432
- const existingIdentity = await this.oauthIdentities.findByProviderUser(providerName, profile.providerUserId);
433
- if (existingIdentity) {
434
- return await this.users.findByIdOrThrow(existingIdentity.user_id);
435
- }
436
- const existingUser = await this.users.findByEmail(profile.email);
437
- const user = existingUser ?? await this.users.create({
438
- name: profile.name,
439
- email: profile.email,
440
- role: "member",
441
- tenant_id: currentTenantId(),
442
- email_verified_at: new Date,
443
- created_at: new Date,
444
- updated_at: new Date
445
- });
446
- await this.oauthIdentities.create({
447
- user_id: user.id,
448
- provider: providerName,
449
- provider_user_id: profile.providerUserId,
450
- email: profile.email,
451
- created_at: new Date
452
- });
453
- return user;
454
- }
455
- }
456
-
457
545
  // ../../src/modules/user/notificationRepository.ts
458
546
  import { BaseRepository as BaseRepository2 } from "@getstrata/core/database/baseRepository";
459
547
 
@@ -872,9 +960,9 @@ function withErrorHandling(handler) {
872
960
  };
873
961
  }
874
962
  export {
875
- withErrorHandling,
876
- noContentResponse,
877
- jsonResponse,
963
+ createdResponse,
878
964
  errorResponse,
879
- createdResponse
965
+ jsonResponse,
966
+ noContentResponse,
967
+ withErrorHandling
880
968
  };
@@ -282,6 +282,6 @@ function securedBindRouteModelByKey(param, resolver, authorization, handler) {
282
282
  };
283
283
  }
284
284
  export {
285
- securedBindRouteModelByKey,
286
- securedBindRouteModel
285
+ securedBindRouteModel,
286
+ securedBindRouteModelByKey
287
287
  };
@@ -37,6 +37,6 @@ function createThrottleMiddleware(options) {
37
37
  };
38
38
  }
39
39
  export {
40
- resolveThrottleIdentity,
41
- createThrottleMiddleware
40
+ createThrottleMiddleware,
41
+ resolveThrottleIdentity
42
42
  };
@@ -128,18 +128,18 @@ function parsePositiveIntParam(value, name = "id") {
128
128
  return parsed;
129
129
  }
130
130
  export {
131
- readRequiredString,
132
- readRequiredPositiveInt,
133
- readRequiredEnum,
134
- readOptionalString,
135
- readOptionalPositiveInt,
136
- readOptionalEnum,
137
- parsePositiveIntParam,
138
- parseOptionalPositiveIntQueryParam,
139
- parseOptionalEnumQueryParam,
140
- parseOptionalBooleanQueryParam,
141
- parseJsonBody,
142
- getQueryParams,
131
+ buildRequestCacheKey,
143
132
  expectObject,
144
- buildRequestCacheKey
133
+ getQueryParams,
134
+ parseJsonBody,
135
+ parseOptionalBooleanQueryParam,
136
+ parseOptionalEnumQueryParam,
137
+ parseOptionalPositiveIntQueryParam,
138
+ parsePositiveIntParam,
139
+ readOptionalEnum,
140
+ readOptionalPositiveInt,
141
+ readOptionalString,
142
+ readRequiredEnum,
143
+ readRequiredPositiveInt,
144
+ readRequiredString
145
145
  };