@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
|
@@ -174,9 +174,9 @@ function paginatedResponse(data, meta, init = {}) {
|
|
|
174
174
|
return Response.json({ data, meta }, init);
|
|
175
175
|
}
|
|
176
176
|
export {
|
|
177
|
-
|
|
178
|
-
paginatedResponse,
|
|
179
|
-
buildPaginationMeta,
|
|
177
|
+
DEFAULT_PER_PAGE,
|
|
180
178
|
MAX_PER_PAGE,
|
|
181
|
-
|
|
179
|
+
buildPaginationMeta,
|
|
180
|
+
paginatedResponse,
|
|
181
|
+
parsePaginationQuery
|
|
182
182
|
};
|
|
@@ -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
|
-
|
|
876
|
-
noContentResponse,
|
|
877
|
-
jsonResponse,
|
|
963
|
+
createdResponse,
|
|
878
964
|
errorResponse,
|
|
879
|
-
|
|
965
|
+
jsonResponse,
|
|
966
|
+
noContentResponse,
|
|
967
|
+
withErrorHandling
|
|
880
968
|
};
|
|
@@ -128,18 +128,18 @@ function parsePositiveIntParam(value, name = "id") {
|
|
|
128
128
|
return parsed;
|
|
129
129
|
}
|
|
130
130
|
export {
|
|
131
|
-
|
|
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
|
-
|
|
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
|
};
|