@angular/ssr 19.2.20 → 19.2.22
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/app-engine.d-DM74JoSc.d.ts +196 -0
- package/fesm2022/node.mjs +81 -21
- package/fesm2022/node.mjs.map +1 -1
- package/fesm2022/ssr.mjs +95 -11
- package/fesm2022/ssr.mjs.map +1 -1
- package/fesm2022/validation-DhKAntwS.mjs +213 -0
- package/fesm2022/validation-DhKAntwS.mjs.map +1 -0
- package/index.d.ts +8 -157
- package/node/index.d.ts +32 -4
- package/package.json +1 -1
- package/third_party/beasties/index.js +5 -1
- package/third_party/beasties/index.js.map +1 -1
package/fesm2022/ssr.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { v as validateRequest, c as cloneRequestAndPatchHeaders } from './validation-DhKAntwS.mjs';
|
|
1
2
|
import { ɵConsole as _Console, InjectionToken, makeEnvironmentProviders, ɵENABLE_ROOT_COMPONENT_BOOTSTRAP as _ENABLE_ROOT_COMPONENT_BOOTSTRAP, ApplicationRef, Compiler, runInInjectionContext, ɵresetCompiledComponents as _resetCompiledComponents, REQUEST, REQUEST_CONTEXT, RESPONSE_INIT, LOCALE_ID } from '@angular/core';
|
|
2
3
|
import { ROUTES, Router, ɵloadChildren as _loadChildren } from '@angular/router';
|
|
3
4
|
import { APP_BASE_HREF, PlatformLocation } from '@angular/common';
|
|
@@ -212,24 +213,26 @@ function addTrailingSlash(url) {
|
|
|
212
213
|
* ```
|
|
213
214
|
*/
|
|
214
215
|
function joinUrlParts(...parts) {
|
|
215
|
-
const
|
|
216
|
+
const normalizedParts = [];
|
|
216
217
|
for (const part of parts) {
|
|
217
218
|
if (part === '') {
|
|
218
219
|
// Skip any empty parts
|
|
219
220
|
continue;
|
|
220
221
|
}
|
|
221
|
-
let
|
|
222
|
-
|
|
223
|
-
|
|
222
|
+
let start = 0;
|
|
223
|
+
let end = part.length;
|
|
224
|
+
// Use "Pointers" to avoid intermediate slices
|
|
225
|
+
while (start < end && part[start] === '/') {
|
|
226
|
+
start++;
|
|
224
227
|
}
|
|
225
|
-
|
|
226
|
-
|
|
228
|
+
while (end > start && part[end - 1] === '/') {
|
|
229
|
+
end--;
|
|
227
230
|
}
|
|
228
|
-
if (
|
|
229
|
-
|
|
231
|
+
if (start < end) {
|
|
232
|
+
normalizedParts.push(part.slice(start, end));
|
|
230
233
|
}
|
|
231
234
|
}
|
|
232
|
-
return addLeadingSlash(
|
|
235
|
+
return addLeadingSlash(normalizedParts.join('/'));
|
|
233
236
|
}
|
|
234
237
|
/**
|
|
235
238
|
* Strips `/index.html` from the end of a URL's path, if present.
|
|
@@ -2003,6 +2006,21 @@ class AngularServerApp {
|
|
|
2003
2006
|
}
|
|
2004
2007
|
return html;
|
|
2005
2008
|
}
|
|
2009
|
+
/**
|
|
2010
|
+
* Serves the client-side version of the application.
|
|
2011
|
+
* TODO(alanagius): Remove this method in version 22.
|
|
2012
|
+
* @internal
|
|
2013
|
+
*/
|
|
2014
|
+
async serveClientSidePage() {
|
|
2015
|
+
const { manifest: { locale }, assets, } = this;
|
|
2016
|
+
const html = await assets.getServerAsset('index.csr.html').text();
|
|
2017
|
+
return new Response(html, {
|
|
2018
|
+
headers: new Headers({
|
|
2019
|
+
'Content-Type': 'text/html;charset=UTF-8',
|
|
2020
|
+
...(locale !== undefined ? { 'Content-Language': locale } : {}),
|
|
2021
|
+
}),
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2006
2024
|
}
|
|
2007
2025
|
let angularServerApp;
|
|
2008
2026
|
/**
|
|
@@ -2264,6 +2282,10 @@ class AngularAppEngine {
|
|
|
2264
2282
|
* The manifest for the server application.
|
|
2265
2283
|
*/
|
|
2266
2284
|
manifest = getAngularAppEngineManifest();
|
|
2285
|
+
/**
|
|
2286
|
+
* A set of allowed hostnames for the server application.
|
|
2287
|
+
*/
|
|
2288
|
+
allowedHosts;
|
|
2267
2289
|
/**
|
|
2268
2290
|
* A map of supported locales from the server application's manifest.
|
|
2269
2291
|
*/
|
|
@@ -2272,6 +2294,13 @@ class AngularAppEngine {
|
|
|
2272
2294
|
* A cache that holds entry points, keyed by their potential locale string.
|
|
2273
2295
|
*/
|
|
2274
2296
|
entryPointsCache = new Map();
|
|
2297
|
+
/**
|
|
2298
|
+
* Creates a new instance of the Angular server application engine.
|
|
2299
|
+
* @param options Options for the Angular server application engine.
|
|
2300
|
+
*/
|
|
2301
|
+
constructor(options) {
|
|
2302
|
+
this.allowedHosts = new Set([...(options?.allowedHosts ?? []), ...this.manifest.allowedHosts]);
|
|
2303
|
+
}
|
|
2275
2304
|
/**
|
|
2276
2305
|
* Handles an incoming HTTP request by serving prerendered content, performing server-side rendering,
|
|
2277
2306
|
* or delivering a static file for client-side rendered routes based on the `RenderMode` setting.
|
|
@@ -2282,11 +2311,35 @@ class AngularAppEngine {
|
|
|
2282
2311
|
*
|
|
2283
2312
|
* @remarks A request to `https://www.example.com/page/index.html` will serve or render the Angular route
|
|
2284
2313
|
* corresponding to `https://www.example.com/page`.
|
|
2314
|
+
*
|
|
2315
|
+
* @remarks
|
|
2316
|
+
* To prevent potential Server-Side Request Forgery (SSRF), this function verifies the hostname
|
|
2317
|
+
* of the `request.url` against a list of authorized hosts.
|
|
2318
|
+
* If the hostname is not recognized and `allowedHosts` is not empty, a Client-Side Rendered (CSR) version of the
|
|
2319
|
+
* page is returned otherwise a 400 Bad Request is returned.
|
|
2320
|
+
* Resolution:
|
|
2321
|
+
* Authorize your hostname by configuring `allowedHosts` in `angular.json` in:
|
|
2322
|
+
* `projects.[project-name].architect.build.options.security.allowedHosts`.
|
|
2323
|
+
* Alternatively, you pass it directly through the configuration options of `AngularAppEngine`.
|
|
2324
|
+
*
|
|
2325
|
+
* For more information see: https://angular.dev/best-practices/security#preventing-server-side-request-forgery-ssrf
|
|
2285
2326
|
*/
|
|
2286
2327
|
async handle(request, requestContext) {
|
|
2287
|
-
const
|
|
2328
|
+
const allowedHost = this.allowedHosts;
|
|
2329
|
+
try {
|
|
2330
|
+
validateRequest(request, allowedHost);
|
|
2331
|
+
}
|
|
2332
|
+
catch (error) {
|
|
2333
|
+
return this.handleValidationError(error, request);
|
|
2334
|
+
}
|
|
2335
|
+
// Clone request with patched headers to prevent unallowed host header access.
|
|
2336
|
+
const { request: securedRequest, onError: onHeaderValidationError } = cloneRequestAndPatchHeaders(request, allowedHost);
|
|
2337
|
+
const serverApp = await this.getAngularServerAppForRequest(securedRequest);
|
|
2288
2338
|
if (serverApp) {
|
|
2289
|
-
return
|
|
2339
|
+
return Promise.race([
|
|
2340
|
+
onHeaderValidationError.then((error) => this.handleValidationError(error, securedRequest)),
|
|
2341
|
+
serverApp.handle(securedRequest, requestContext),
|
|
2342
|
+
]);
|
|
2290
2343
|
}
|
|
2291
2344
|
if (this.supportedLocales.length > 1) {
|
|
2292
2345
|
// Redirect to the preferred language if i18n is enabled.
|
|
@@ -2392,6 +2445,37 @@ class AngularAppEngine {
|
|
|
2392
2445
|
const potentialLocale = getPotentialLocaleIdFromUrl(url, basePath);
|
|
2393
2446
|
return this.getEntryPointExports(potentialLocale) ?? this.getEntryPointExports('');
|
|
2394
2447
|
}
|
|
2448
|
+
/**
|
|
2449
|
+
* Handles validation errors by logging the error and returning an appropriate response.
|
|
2450
|
+
*
|
|
2451
|
+
* @param error - The validation error to handle.
|
|
2452
|
+
* @param request - The HTTP request that caused the validation error.
|
|
2453
|
+
* @returns A promise that resolves to a `Response` object with a 400 status code if allowed hosts are configured,
|
|
2454
|
+
* or `null` if allowed hosts are not configured (in which case the request is served client-side).
|
|
2455
|
+
*/
|
|
2456
|
+
async handleValidationError(error, request) {
|
|
2457
|
+
const isAllowedHostConfigured = this.allowedHosts.size > 0;
|
|
2458
|
+
const errorMessage = error.message;
|
|
2459
|
+
// eslint-disable-next-line no-console
|
|
2460
|
+
console.error(`ERROR: Bad Request ("${request.url}").\n` +
|
|
2461
|
+
errorMessage +
|
|
2462
|
+
(isAllowedHostConfigured
|
|
2463
|
+
? ''
|
|
2464
|
+
: '\nFalling back to client side rendering. This will become a 400 Bad Request in a future major version.') +
|
|
2465
|
+
'\n\nFor more information, see https://angular.dev/best-practices/security#preventing-server-side-request-forgery-ssrf');
|
|
2466
|
+
if (isAllowedHostConfigured) {
|
|
2467
|
+
// Allowed hosts has been configured incorrectly, thus we can return a 400 bad request.
|
|
2468
|
+
return new Response(errorMessage, {
|
|
2469
|
+
status: 400,
|
|
2470
|
+
statusText: 'Bad Request',
|
|
2471
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
2472
|
+
});
|
|
2473
|
+
}
|
|
2474
|
+
// Fallback to CSR to avoid a breaking change.
|
|
2475
|
+
// TODO(alanagius): Return a 400 and remove this fallback in the next major version (v22).
|
|
2476
|
+
const serverApp = await this.getAngularServerAppForRequest(request);
|
|
2477
|
+
return serverApp?.serveClientSidePage() ?? null;
|
|
2478
|
+
}
|
|
2395
2479
|
}
|
|
2396
2480
|
|
|
2397
2481
|
/**
|