@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.
- package/README.md +2 -0
- package/dist/core/media/imageTransform.d.ts +13 -0
- package/dist/core/scheduler/osCron.d.ts +14 -0
- package/dist/core/terminal/runShell.d.ts +6 -0
- package/dist/core/view/assertEtaHtmlSource.d.ts +5 -0
- package/dist/core/view/etaViewEngine.d.ts +4 -0
- package/dist/entries/audit/exportAuditLogs.js +2 -2
- package/dist/entries/audit/siemFormatter.js +2 -2
- package/dist/entries/auth/accessControl.js +4 -4
- package/dist/entries/auth/membershipScope.js +6 -6
- package/dist/entries/auth/oauth/providers.js +2 -2
- package/dist/entries/auth/password.js +2 -2
- package/dist/entries/auth/policy.js +2 -2
- package/dist/entries/auth/sessionCookie.js +4 -4
- package/dist/entries/auth/sessionGuard.js +0 -91
- package/dist/entries/auth/tokenHash.js +2 -2
- package/dist/entries/config/envSchema.js +2 -2
- package/dist/entries/contracts/container.js +2 -2
- package/dist/entries/contracts/di.js +2 -2
- package/dist/entries/contracts/serviceTokens.js +6 -6
- package/dist/entries/crypto/fieldEncryption.js +8 -8
- package/dist/entries/crypto/mfaSecret.js +2 -2
- package/dist/entries/database/errors.js +2 -2
- package/dist/entries/database/migrations.js +8 -8
- package/dist/entries/database/model.js +5 -5
- package/dist/entries/database/query.js +19 -19
- package/dist/entries/database/relationships.js +13 -13
- package/dist/entries/database/schema.js +13 -13
- package/dist/entries/database/seeders.js +2 -2
- package/dist/entries/facades.js +8 -8
- package/dist/entries/http/authMiddleware.js +2 -2
- package/dist/entries/http/bodySizeLimitMiddleware.js +2 -2
- package/dist/entries/http/cookies.js +2 -2
- package/dist/entries/http/csrfProtection.js +2 -2
- package/dist/entries/http/csrfToken.js +6 -6
- package/dist/entries/http/etag.js +8 -8
- package/dist/entries/http/flashSession.js +4 -4
- package/dist/entries/http/formRequest.js +2 -2
- package/dist/entries/http/loginThrottleMiddleware.js +2 -2
- package/dist/entries/http/metricsMiddleware.js +2 -2
- package/dist/entries/http/pagination.js +4 -4
- package/dist/entries/http/parseFormBody.js +2 -2
- package/dist/entries/http/parseMultipartUpload.js +2 -2
- package/dist/entries/http/resources.js +2 -2
- package/dist/entries/http/response.js +186 -98
- package/dist/entries/http/securedRouteModelBinding.js +2 -2
- package/dist/entries/http/throttleMiddleware.js +2 -2
- package/dist/entries/http/validation.js +13 -13
- package/dist/entries/http/webErrorResponse.js +184 -96
- package/dist/entries/lifecycle/gracefulShutdown.js +3 -3
- package/dist/entries/logging/logger.js +2 -2
- package/dist/entries/mail/mailer.js +6 -6
- package/dist/entries/mail/markdownMail.js +3 -3
- package/dist/entries/mail/markdownMailable.js +2 -2
- package/dist/entries/media/imageTransform.js +40 -0
- package/dist/entries/metrics/prometheus.js +2 -2
- package/dist/entries/openapi/generator.js +2 -2
- package/dist/entries/queue/createAppQueue.js +4 -4
- package/dist/entries/queue/publicQueue.js +10 -10
- package/dist/entries/queue/queueMetrics.js +2 -2
- package/dist/entries/queue/redisQueue.js +7 -7
- package/dist/entries/queue.js +3 -3
- package/dist/entries/scheduler/osCron.js +27 -0
- package/dist/entries/scheduler/schedule.js +2 -2
- package/dist/entries/security/oauthState.js +3 -3
- package/dist/entries/security/publicReads.js +2 -2
- package/dist/entries/security/safeFetch.js +2 -2
- package/dist/entries/security/safeUrl.js +5 -5
- package/dist/entries/security/scimTenantTokens.js +2 -2
- package/dist/entries/security/totp.js +2 -2
- package/dist/entries/storage/storage.js +7 -7
- package/dist/entries/tenant/tenantDatabaseScope.js +2 -2
- package/dist/entries/terminal/runShell.js +18 -0
- package/dist/entries/validation/rules.js +12 -12
- package/dist/entries/view.js +186 -98
- package/dist/index.js +431 -469
- package/package.json +17 -2
|
@@ -151,17 +151,17 @@ function validateObject(payload, schema) {
|
|
|
151
151
|
return output;
|
|
152
152
|
}
|
|
153
153
|
export {
|
|
154
|
-
|
|
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
|
-
|
|
156
|
+
enumRule,
|
|
157
|
+
integerRange,
|
|
158
|
+
integerRule,
|
|
159
|
+
maxLength,
|
|
160
|
+
minLength,
|
|
161
|
+
optional,
|
|
162
|
+
pattern,
|
|
163
|
+
positiveIntegerRule,
|
|
164
|
+
required,
|
|
165
|
+
stringRule,
|
|
166
|
+
validateObject
|
|
167
167
|
};
|
package/dist/entries/view.js
CHANGED
|
@@ -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
|
-
|
|
698
|
-
isHtmxRequest,
|
|
699
|
-
htmlResponse,
|
|
785
|
+
DEFAULT_VIEWS_DIRECTORY,
|
|
700
786
|
EtaViewEngine,
|
|
701
|
-
|
|
787
|
+
htmlResponse,
|
|
788
|
+
isHtmxRequest,
|
|
789
|
+
resolveWebLayoutData
|
|
702
790
|
};
|