@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
@@ -151,17 +151,17 @@ function validateObject(payload, schema) {
151
151
  return output;
152
152
  }
153
153
  export {
154
- validateObject,
155
- stringRule,
156
- required,
157
- positiveIntegerRule,
158
- pattern,
159
- optional,
160
- minLength,
161
- maxLength,
162
- integerRule,
163
- integerRange,
164
- enumRule,
154
+ confirmed,
165
155
  emailRule,
166
- confirmed
156
+ enumRule,
157
+ integerRange,
158
+ integerRule,
159
+ maxLength,
160
+ minLength,
161
+ optional,
162
+ pattern,
163
+ positiveIntegerRule,
164
+ required,
165
+ stringRule,
166
+ validateObject
167
167
  };
@@ -1,7 +1,182 @@
1
1
  // @bun
2
2
  // ../../src/core/view/etaViewEngine.ts
3
- import { join } from "path";
3
+ import { join, relative } from "path";
4
4
  import { Eta } from "eta";
5
+
6
+ // ../../src/core/view/assertEtaHtmlSource.ts
7
+ var HTML_TAGS = new Set([
8
+ "a",
9
+ "abbr",
10
+ "address",
11
+ "article",
12
+ "aside",
13
+ "audio",
14
+ "b",
15
+ "blockquote",
16
+ "body",
17
+ "button",
18
+ "canvas",
19
+ "caption",
20
+ "cite",
21
+ "code",
22
+ "col",
23
+ "colgroup",
24
+ "data",
25
+ "datalist",
26
+ "dd",
27
+ "del",
28
+ "details",
29
+ "dfn",
30
+ "dialog",
31
+ "div",
32
+ "dl",
33
+ "dt",
34
+ "em",
35
+ "fieldset",
36
+ "figcaption",
37
+ "figure",
38
+ "footer",
39
+ "form",
40
+ "h1",
41
+ "h2",
42
+ "h3",
43
+ "h4",
44
+ "h5",
45
+ "h6",
46
+ "header",
47
+ "hgroup",
48
+ "hr",
49
+ "html",
50
+ "i",
51
+ "iframe",
52
+ "img",
53
+ "input",
54
+ "ins",
55
+ "kbd",
56
+ "label",
57
+ "legend",
58
+ "li",
59
+ "main",
60
+ "map",
61
+ "mark",
62
+ "menu",
63
+ "meter",
64
+ "nav",
65
+ "object",
66
+ "ol",
67
+ "optgroup",
68
+ "option",
69
+ "output",
70
+ "p",
71
+ "picture",
72
+ "pre",
73
+ "progress",
74
+ "q",
75
+ "rp",
76
+ "rt",
77
+ "ruby",
78
+ "s",
79
+ "samp",
80
+ "script",
81
+ "search",
82
+ "section",
83
+ "select",
84
+ "slot",
85
+ "small",
86
+ "source",
87
+ "span",
88
+ "strong",
89
+ "style",
90
+ "sub",
91
+ "summary",
92
+ "sup",
93
+ "table",
94
+ "tbody",
95
+ "td",
96
+ "template",
97
+ "textarea",
98
+ "tfoot",
99
+ "th",
100
+ "thead",
101
+ "time",
102
+ "title",
103
+ "tr",
104
+ "track",
105
+ "u",
106
+ "ul",
107
+ "var",
108
+ "video",
109
+ "wbr"
110
+ ]);
111
+ var TAG_PATTERN = "([a-z][a-z0-9]*)";
112
+ var CLASS_OR_ID_PATTERN = "[.#][\\w-]+";
113
+ var CLASS_ID_SHORTHAND = new RegExp(`^${TAG_PATTERN}(?:${CLASS_OR_ID_PATTERN})+`);
114
+ var ATTRIBUTE_SHORTHAND = new RegExp(`^${TAG_PATTERN}(?:${CLASS_OR_ID_PATTERN})*\\s+[\\w:-]+=`);
115
+ var TAG_SHORTHAND = new RegExp(`^${TAG_PATTERN}(?:>|\\s+\\S|$)`);
116
+ function htmlTagName(match) {
117
+ const tag = match?.[1];
118
+ return tag && HTML_TAGS.has(tag) ? tag : undefined;
119
+ }
120
+ function describePugLikeLine(line) {
121
+ if (/^\|(?:\s|$)/.test(line)) {
122
+ return "piped text";
123
+ }
124
+ if (htmlTagName(line.match(CLASS_ID_SHORTHAND))) {
125
+ return "class/id shorthand";
126
+ }
127
+ if (htmlTagName(line.match(ATTRIBUTE_SHORTHAND))) {
128
+ return "attribute shorthand";
129
+ }
130
+ if (htmlTagName(line.match(TAG_SHORTHAND))) {
131
+ return "tag shorthand";
132
+ }
133
+ return;
134
+ }
135
+ function updateSkipBlock(lower, skipBlock) {
136
+ if (skipBlock) {
137
+ if (skipBlock === "comment" && lower.includes("-->")) {
138
+ return null;
139
+ }
140
+ if (skipBlock !== "comment" && lower.includes(`</${skipBlock}`)) {
141
+ return null;
142
+ }
143
+ return skipBlock;
144
+ }
145
+ if (lower.startsWith("<!--") && !lower.includes("-->")) {
146
+ return "comment";
147
+ }
148
+ if (lower.startsWith("<script") && !lower.includes("</script")) {
149
+ return "script";
150
+ }
151
+ if (lower.startsWith("<style") && !lower.includes("</style")) {
152
+ return "style";
153
+ }
154
+ return null;
155
+ }
156
+ function assertEtaHtmlSource(templateName, source) {
157
+ let skipBlock = null;
158
+ for (const rawLine of source.split(/\r?\n/)) {
159
+ const trimmed = rawLine.trim();
160
+ if (!trimmed) {
161
+ continue;
162
+ }
163
+ const lower = trimmed.toLowerCase();
164
+ const nextSkipBlock = updateSkipBlock(lower, skipBlock);
165
+ if (skipBlock || nextSkipBlock) {
166
+ skipBlock = nextSkipBlock;
167
+ continue;
168
+ }
169
+ if (trimmed.startsWith("<") || trimmed.startsWith("<%")) {
170
+ continue;
171
+ }
172
+ const kind = describePugLikeLine(trimmed);
173
+ if (kind) {
174
+ throw new Error(`Eta view "${templateName}" contains Pug-like markup (${kind}: ${trimmed}). Eta views must be HTML + <% %> tags, not Pug.`);
175
+ }
176
+ }
177
+ }
178
+
179
+ // ../../src/core/view/etaViewEngine.ts
5
180
  var DEFAULT_VIEWS_DIRECTORY = join(process.cwd(), "resources/views");
6
181
  var DEFAULT_LAYOUT = "layouts/app.eta";
7
182
 
@@ -14,6 +189,12 @@ class EtaViewEngine {
14
189
  autoTrim: false
15
190
  });
16
191
  this.resolveLayoutData = resolveLayoutData;
192
+ const readFile = this.eta.readFile?.bind(this.eta);
193
+ this.eta.readFile = (path) => {
194
+ const source = readFile ? readFile(path) : "";
195
+ assertEtaHtmlSource(relative(viewsDirectory, path) || path, source);
196
+ return source;
197
+ };
17
198
  }
18
199
  async render(name, data = {}, options = {}) {
19
200
  const template = name.endsWith(".eta") ? name : `${name}.eta`;
@@ -277,99 +458,6 @@ function resolveAbilitiesForRole(role) {
277
458
  return [...MEMBER_ABILITIES];
278
459
  }
279
460
 
280
- // ../../src/modules/user/authService.ts
281
- class AuthService {
282
- users;
283
- tokens;
284
- oauthIdentities;
285
- oauthProviders = new Map;
286
- constructor(users, tokens, oauthIdentities) {
287
- this.users = users;
288
- this.tokens = tokens;
289
- this.oauthIdentities = oauthIdentities;
290
- }
291
- registerOAuthProvider(provider) {
292
- this.oauthProviders.set(provider.name, provider);
293
- }
294
- getOAuthProvider(name) {
295
- return this.oauthProviders.get(name);
296
- }
297
- async loginWithPassword(email, password, options = {}) {
298
- const user = await this.users.findByEmail(email);
299
- if (!user?.password_hash) {
300
- logSecurityEvent("auth_login_failed", { reason: "unknown_user", email });
301
- throw new UnauthorizedError("Invalid credentials.");
302
- }
303
- const valid = await verifyPassword(password, user.password_hash);
304
- if (!valid) {
305
- logSecurityEvent("auth_login_failed", { reason: "invalid_password", user_id: user.id });
306
- throw new UnauthorizedError("Invalid credentials.");
307
- }
308
- if (isFeatureEnabled("emailVerification") && !user.email_verified_at) {
309
- logSecurityEvent("auth_login_blocked", { reason: "email_unverified", user_id: user.id });
310
- throw new UnauthorizedError("Email address is not verified.");
311
- }
312
- if (isFeatureEnabled("mfa") && user.mfa_enabled) {
313
- const mfaSecret = revealMfaSecret(user.mfa_secret);
314
- if (!mfaSecret || !options.mfaCode || !verifyTotp(mfaSecret, options.mfaCode)) {
315
- logSecurityEvent("auth_login_failed", { reason: "invalid_mfa", user_id: user.id });
316
- throw new UnauthorizedError("Invalid MFA code.");
317
- }
318
- }
319
- logSecurityEvent("auth_login_success", { user_id: user.id, method: "password" });
320
- return await this.tokens.createToken(user.id, {
321
- name: "password-login",
322
- abilities: resolveAbilitiesForRole(user.role),
323
- expiresInDays: resolveDefaultTokenExpiryDays() ?? undefined
324
- });
325
- }
326
- async loginWithOAuth(providerName, code) {
327
- const provider = this.oauthProviders.get(providerName);
328
- if (!provider) {
329
- throw new UnauthorizedError("Unsupported OAuth provider.");
330
- }
331
- const profile = await provider.exchangeCode(code);
332
- const user = await this.findOrCreateOAuthUser(providerName, profile);
333
- logSecurityEvent("auth_login_success", { user_id: user.id, method: `oauth:${providerName}` });
334
- return await this.tokens.createToken(user.id, {
335
- name: `${providerName}-oauth`,
336
- abilities: resolveAbilitiesForRole(user.role),
337
- expiresInDays: resolveDefaultTokenExpiryDays() ?? undefined
338
- });
339
- }
340
- buildOAuthAuthorizationUrl(providerName, state) {
341
- const provider = this.oauthProviders.get(providerName);
342
- if (!provider) {
343
- throw new UnauthorizedError("Unsupported OAuth provider.");
344
- }
345
- return provider.getAuthorizationUrl(state);
346
- }
347
- async findOrCreateOAuthUser(providerName, profile) {
348
- const existingIdentity = await this.oauthIdentities.findByProviderUser(providerName, profile.providerUserId);
349
- if (existingIdentity) {
350
- return await this.users.findByIdOrThrow(existingIdentity.user_id);
351
- }
352
- const existingUser = await this.users.findByEmail(profile.email);
353
- const user = existingUser ?? await this.users.create({
354
- name: profile.name,
355
- email: profile.email,
356
- role: "member",
357
- tenant_id: currentTenantId(),
358
- email_verified_at: new Date,
359
- created_at: new Date,
360
- updated_at: new Date
361
- });
362
- await this.oauthIdentities.create({
363
- user_id: user.id,
364
- provider: providerName,
365
- provider_user_id: profile.providerUserId,
366
- email: profile.email,
367
- created_at: new Date
368
- });
369
- return user;
370
- }
371
- }
372
-
373
461
  // ../../src/modules/user/notificationRepository.ts
374
462
  import { BaseRepository as BaseRepository2 } from "@getstrata/core/database/baseRepository";
375
463
 
@@ -694,9 +782,9 @@ async function resolveWebLayoutData(container, request) {
694
782
  }
695
783
  }
696
784
  export {
697
- resolveWebLayoutData,
698
- isHtmxRequest,
699
- htmlResponse,
785
+ DEFAULT_VIEWS_DIRECTORY,
700
786
  EtaViewEngine,
701
- DEFAULT_VIEWS_DIRECTORY
787
+ htmlResponse,
788
+ isHtmxRequest,
789
+ resolveWebLayoutData
702
790
  };