From b1c5460bbba131d4f430f7d415000bc9a9809546 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Tue, 22 Apr 2025 16:16:27 +0200 Subject: [PATCH 1/3] [DURACOM-344] Adapt SSR page filtering mechanism to a not allowed list --- config/config.example.yml | 15 +++++++++++---- server.ts | 12 +++++++++++- src/config/ssr-config.interface.ts | 4 ++-- src/environments/environment.production.ts | 9 ++++++++- src/environments/environment.test.ts | 9 ++++++++- src/environments/environment.ts | 9 ++++++++- 6 files changed, 48 insertions(+), 10 deletions(-) diff --git a/config/config.example.yml b/config/config.example.yml index d5e7dfe5016..c6ad21ffb89 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -23,10 +23,17 @@ ssr: # Determining which styles are critical is a relatively expensive operation; this option is # disabled (false) by default to boost server performance at the expense of loading smoothness. inlineCriticalCss: false - # Path prefixes to enable SSR for. By default these are limited to paths of primary DSpace objects. - # NOTE: The "/handle/" path ensures Handle redirects work via SSR. The "/reload/" path ensures - # hard refreshes (e.g. after login) trigger SSR while fully reloading the page. - paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/', '/reload/' ] + # Regexes to be run against the path of the page to check if SSR is allowed. + # If the path match any of the regexes it will be served directly in CSR. + # By default, excludes community and collection browse, global browse, global search, community list, and statistics. + excludePathRegexes: [ + /^\/communities\/[ 0-9a-f ]{ 8 }-[ 0-9a-f ]{ 4 }-[ 0-9a-f ]{ 4 }-[ 0-9a-f ]{ 4 }-[ 0-9a-f ]{ 12 }\/browse(\/.*)?$/i, + /^\/collections\/[ 0-9a-f ]{ 8 }-[ 0-9a-f ]{ 4 }-[ 0-9a-f ]{ 4 }-[ 0-9a-f ]{ 4 }-[ 0-9a-f ]{ 12 }\/browse(\/.*)?$/i, + /^\/browse\//, + /^\/search$/, + /^\/community-list$/, + /^\/statistics$/, + ] # Whether to enable rendering of Search component on SSR. # If set to true the component will be included in the HTML returned from the server side rendering. # If set to false the component will not be included in the HTML returned from the server side rendering. diff --git a/server.ts b/server.ts index 52b2823d49c..afde39178a3 100644 --- a/server.ts +++ b/server.ts @@ -221,7 +221,7 @@ export function app() { * The callback function to serve server side angular */ function ngApp(req, res, next) { - if (environment.ssr.enabled && req.method === 'GET' && (req.path === '/' || environment.ssr.paths.some(pathPrefix => req.path.startsWith(pathPrefix)))) { + if (environment.ssr.enabled && req.method === 'GET' && (req.path === '/' || !isExcludedFromSsr(req.path, environment.ssr.excludePathRegexes))) { // Render the page to user via SSR (server side rendering) serverSideRender(req, res, next); } else { @@ -627,6 +627,16 @@ function start() { } } +/** + * Check if SSR should be skipped for path + * + * @param path + * @param excludePathRegexes + */ +function isExcludedFromSsr(path: string, excludePathRegexes: RegExp[]): boolean { + return excludePathRegexes.some((regex) => regex.test(path)); +} + /* * The callback function to serve health check requests */ diff --git a/src/config/ssr-config.interface.ts b/src/config/ssr-config.interface.ts index 2ce626b47b8..13b216befc1 100644 --- a/src/config/ssr-config.interface.ts +++ b/src/config/ssr-config.interface.ts @@ -39,9 +39,9 @@ export interface SSRConfig extends Config { replaceRestUrl: boolean; /** - * Paths to enable SSR for. Defaults to the home page and paths in the sitemap. + * Regexes to match url's path and check if SSR is disabled for it. */ - paths: Array; + excludePathRegexes: RegExp[]; /** * Whether to enable rendering of search component on SSR diff --git a/src/environments/environment.production.ts b/src/environments/environment.production.ts index 9bd663922c8..8a96a32b19c 100644 --- a/src/environments/environment.production.ts +++ b/src/environments/environment.production.ts @@ -10,7 +10,14 @@ export const environment: Partial = { inlineCriticalCss: false, transferState: true, replaceRestUrl: true, - paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/', '/reload/' ], + excludePathRegexes: [ + /^\/communities\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/browse(\/.*)?$/i, + /^\/collections\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/browse(\/.*)?$/i, + /^\/browse\//, + /^\/search$/, + /^\/community-list$/, + /^\/statistics$/, + ], enableSearchComponent: false, enableBrowseComponent: false, }, diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index b6348f949d5..20585e17b6d 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -14,7 +14,14 @@ export const environment: BuildConfig = { inlineCriticalCss: false, transferState: true, replaceRestUrl: false, - paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/', '/reload/' ], + excludePathRegexes: [ + /^\/communities\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/browse(\/.*)?$/i, + /^\/collections\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/browse(\/.*)?$/i, + /^\/browse\//, + /^\/search$/, + /^\/community-list$/, + /^\/statistics$/, + ], enableSearchComponent: false, enableBrowseComponent: false, }, diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 39ab31de03e..34d820aec91 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -15,7 +15,14 @@ export const environment: Partial = { inlineCriticalCss: false, transferState: true, replaceRestUrl: false, - paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/', '/reload/' ], + excludePathRegexes: [ + /^\/communities\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/browse(\/.*)?$/i, + /^\/collections\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/browse(\/.*)?$/i, + /^\/browse\//, + /^\/search$/, + /^\/community-list$/, + /^\/statistics$/, + ], enableSearchComponent: false, enableBrowseComponent: false, }, From 4c9638150ab994834ec6e732dc0045fad5166127 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Wed, 7 May 2025 15:25:08 +0200 Subject: [PATCH 2/3] [DURACOM-344] adapt patterns and example file, fix possible error from YAML --- config/config.example.yml | 22 +++++++++++++--------- server.ts | 11 +++++++---- src/config/ssr-config.interface.ts | 4 ++-- src/environments/environment.production.ts | 8 ++++++-- src/environments/environment.test.ts | 8 ++++++-- src/environments/environment.ts | 8 ++++++-- 6 files changed, 40 insertions(+), 21 deletions(-) diff --git a/config/config.example.yml b/config/config.example.yml index c6ad21ffb89..f90d821a355 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -23,17 +23,21 @@ ssr: # Determining which styles are critical is a relatively expensive operation; this option is # disabled (false) by default to boost server performance at the expense of loading smoothness. inlineCriticalCss: false - # Regexes to be run against the path of the page to check if SSR is allowed. + # Patterns to be run as regexes against the path of the page to check if SSR is allowed. # If the path match any of the regexes it will be served directly in CSR. # By default, excludes community and collection browse, global browse, global search, community list, and statistics. - excludePathRegexes: [ - /^\/communities\/[ 0-9a-f ]{ 8 }-[ 0-9a-f ]{ 4 }-[ 0-9a-f ]{ 4 }-[ 0-9a-f ]{ 4 }-[ 0-9a-f ]{ 12 }\/browse(\/.*)?$/i, - /^\/collections\/[ 0-9a-f ]{ 8 }-[ 0-9a-f ]{ 4 }-[ 0-9a-f ]{ 4 }-[ 0-9a-f ]{ 4 }-[ 0-9a-f ]{ 12 }\/browse(\/.*)?$/i, - /^\/browse\//, - /^\/search$/, - /^\/community-list$/, - /^\/statistics$/, - ] + excludePathPatterns: + - /^\/communities\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/browse(\/.*)?$/i + - /^\/collections\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/browse(\/.*)?$/i + - /^\/browse\// + - /^\/search$/ + - /^\/community-list$/ + - /^\/statistics\// + - /^\/admin$/ + - /^\/processes$/ + - /^\/notifications$/ + - /^\/health$/ + # Whether to enable rendering of Search component on SSR. # If set to true the component will be included in the HTML returned from the server side rendering. # If set to false the component will not be included in the HTML returned from the server side rendering. diff --git a/server.ts b/server.ts index afde39178a3..7cf475c239f 100644 --- a/server.ts +++ b/server.ts @@ -221,7 +221,7 @@ export function app() { * The callback function to serve server side angular */ function ngApp(req, res, next) { - if (environment.ssr.enabled && req.method === 'GET' && (req.path === '/' || !isExcludedFromSsr(req.path, environment.ssr.excludePathRegexes))) { + if (environment.ssr.enabled && req.method === 'GET' && (req.path === '/' || !isExcludedFromSsr(req.path, environment.ssr.excludePathPatterns))) { // Render the page to user via SSR (server side rendering) serverSideRender(req, res, next); } else { @@ -631,10 +631,13 @@ function start() { * Check if SSR should be skipped for path * * @param path - * @param excludePathRegexes + * @param excludePathPattern */ -function isExcludedFromSsr(path: string, excludePathRegexes: RegExp[]): boolean { - return excludePathRegexes.some((regex) => regex.test(path)); +function isExcludedFromSsr(path: string, excludePathPattern: (string | RegExp)[]): boolean { + return excludePathPattern.some((pattern) => { + const regex = new RegExp(pattern); + return regex.test(path) + }); } /* diff --git a/src/config/ssr-config.interface.ts b/src/config/ssr-config.interface.ts index 13b216befc1..ee39512d441 100644 --- a/src/config/ssr-config.interface.ts +++ b/src/config/ssr-config.interface.ts @@ -39,9 +39,9 @@ export interface SSRConfig extends Config { replaceRestUrl: boolean; /** - * Regexes to match url's path and check if SSR is disabled for it. + * Patterns to be used as regexes to match url's path and check if SSR is disabled for it. */ - excludePathRegexes: RegExp[]; + excludePathPatterns: (string | RegExp)[]; /** * Whether to enable rendering of search component on SSR diff --git a/src/environments/environment.production.ts b/src/environments/environment.production.ts index 8a96a32b19c..238c45c21e7 100644 --- a/src/environments/environment.production.ts +++ b/src/environments/environment.production.ts @@ -10,13 +10,17 @@ export const environment: Partial = { inlineCriticalCss: false, transferState: true, replaceRestUrl: true, - excludePathRegexes: [ + excludePathPatterns: [ /^\/communities\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/browse(\/.*)?$/i, /^\/collections\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/browse(\/.*)?$/i, /^\/browse\//, /^\/search$/, /^\/community-list$/, - /^\/statistics$/, + /^\/statistics\//, + /^\/admin$/, + /^\/processes$/, + /^\/notifications$/, + /^\/health$/, ], enableSearchComponent: false, enableBrowseComponent: false, diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 20585e17b6d..44f07601232 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -14,13 +14,17 @@ export const environment: BuildConfig = { inlineCriticalCss: false, transferState: true, replaceRestUrl: false, - excludePathRegexes: [ + excludePathPatterns: [ /^\/communities\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/browse(\/.*)?$/i, /^\/collections\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/browse(\/.*)?$/i, /^\/browse\//, /^\/search$/, /^\/community-list$/, - /^\/statistics$/, + /^\/statistics\//, + /^\/admin$/, + /^\/processes$/, + /^\/notifications$/, + /^\/health$/, ], enableSearchComponent: false, enableBrowseComponent: false, diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 34d820aec91..cd773664fcd 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -15,13 +15,17 @@ export const environment: Partial = { inlineCriticalCss: false, transferState: true, replaceRestUrl: false, - excludePathRegexes: [ + excludePathPatterns: [ /^\/communities\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/browse(\/.*)?$/i, /^\/collections\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/browse(\/.*)?$/i, /^\/browse\//, /^\/search$/, /^\/community-list$/, - /^\/statistics$/, + /^\/statistics\//, + /^\/admin$/, + /^\/processes$/, + /^\/notifications$/, + /^\/health$/, ], enableSearchComponent: false, enableBrowseComponent: false, From c442d355057adc6b6bcf27dde9138019c9e15d55 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Mon, 12 May 2025 17:34:07 +0200 Subject: [PATCH 3/3] [DURACOM-344] refactor solution to avoid double slashes --- config/config.example.yml | 25 +++++++++++--------- server.ts | 9 +++++--- src/config/ssr-config.interface.ts | 7 +++++- src/environments/environment.production.ts | 27 ++++++++++++++-------- src/environments/environment.test.ts | 27 ++++++++++++++-------- src/environments/environment.ts | 27 ++++++++++++++-------- 6 files changed, 77 insertions(+), 45 deletions(-) diff --git a/config/config.example.yml b/config/config.example.yml index f90d821a355..a9de709deaa 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -25,18 +25,21 @@ ssr: inlineCriticalCss: false # Patterns to be run as regexes against the path of the page to check if SSR is allowed. # If the path match any of the regexes it will be served directly in CSR. - # By default, excludes community and collection browse, global browse, global search, community list, and statistics. + # By default, excludes community and collection browse, global browse, global search, community list, statistics and various administrative tools. excludePathPatterns: - - /^\/communities\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/browse(\/.*)?$/i - - /^\/collections\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/browse(\/.*)?$/i - - /^\/browse\// - - /^\/search$/ - - /^\/community-list$/ - - /^\/statistics\// - - /^\/admin$/ - - /^\/processes$/ - - /^\/notifications$/ - - /^\/health$/ + - pattern: "^/communities/[a-f0-9-]{36}/browse(/.*)?$", + flag: "i" + - pattern: "^/collections/[a-f0-9-]{36}/browse(/.*)?$" + flag: "i" + - pattern: "^/browse/" + - pattern: "^/search$" + - pattern: "^/community-list$" + - pattern: "^/admin/" + - pattern: "^/processes/?" + - pattern: "^/notifications/" + - pattern: "^/statistics/?" + - pattern: "^/access-control/" + - pattern: "^/health$" # Whether to enable rendering of Search component on SSR. # If set to true the component will be included in the HTML returned from the server side rendering. diff --git a/server.ts b/server.ts index 7cf475c239f..cf21eda6af9 100644 --- a/server.ts +++ b/server.ts @@ -58,6 +58,7 @@ import { REQUEST, RESPONSE, } from './src/express.tokens'; +import { SsrExcludePatterns } from "./src/config/ssr-config.interface"; /* * Set path for the browser application's dist folder @@ -633,9 +634,11 @@ function start() { * @param path * @param excludePathPattern */ -function isExcludedFromSsr(path: string, excludePathPattern: (string | RegExp)[]): boolean { - return excludePathPattern.some((pattern) => { - const regex = new RegExp(pattern); +function isExcludedFromSsr(path: string, excludePathPattern: SsrExcludePatterns[]): boolean { + const patterns = excludePathPattern.map(p => + new RegExp(p.pattern, p.flag || '') + ); + return patterns.some((regex) => { return regex.test(path) }); } diff --git a/src/config/ssr-config.interface.ts b/src/config/ssr-config.interface.ts index ee39512d441..16bb3f54d66 100644 --- a/src/config/ssr-config.interface.ts +++ b/src/config/ssr-config.interface.ts @@ -1,5 +1,10 @@ import { Config } from './config.interface'; +export interface SsrExcludePatterns { + pattern: string | RegExp; + flag?: string; +} + export interface SSRConfig extends Config { /** * A boolean flag indicating whether the SSR configuration is enabled @@ -41,7 +46,7 @@ export interface SSRConfig extends Config { /** * Patterns to be used as regexes to match url's path and check if SSR is disabled for it. */ - excludePathPatterns: (string | RegExp)[]; + excludePathPatterns: SsrExcludePatterns[]; /** * Whether to enable rendering of search component on SSR diff --git a/src/environments/environment.production.ts b/src/environments/environment.production.ts index 238c45c21e7..21cc8a25ea3 100644 --- a/src/environments/environment.production.ts +++ b/src/environments/environment.production.ts @@ -11,16 +11,23 @@ export const environment: Partial = { transferState: true, replaceRestUrl: true, excludePathPatterns: [ - /^\/communities\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/browse(\/.*)?$/i, - /^\/collections\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/browse(\/.*)?$/i, - /^\/browse\//, - /^\/search$/, - /^\/community-list$/, - /^\/statistics\//, - /^\/admin$/, - /^\/processes$/, - /^\/notifications$/, - /^\/health$/, + { + pattern: '^/communities/[a-f0-9-]{36}/browse(/.*)?$', + flag: 'i', + }, + { + pattern: '^/collections/[a-f0-9-]{36}/browse(/.*)?$', + flag: 'i', + }, + { pattern: '^/browse/' }, + { pattern: '^/search' }, + { pattern: '^/community-list$' }, + { pattern: '^/statistics/?' }, + { pattern: '^/admin/' }, + { pattern: '^/processes/?' }, + { pattern: '^/notifications/' }, + { pattern: '^/access-control/' }, + { pattern: '^/health$' }, ], enableSearchComponent: false, enableBrowseComponent: false, diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 44f07601232..ee59d09d734 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -15,16 +15,23 @@ export const environment: BuildConfig = { transferState: true, replaceRestUrl: false, excludePathPatterns: [ - /^\/communities\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/browse(\/.*)?$/i, - /^\/collections\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/browse(\/.*)?$/i, - /^\/browse\//, - /^\/search$/, - /^\/community-list$/, - /^\/statistics\//, - /^\/admin$/, - /^\/processes$/, - /^\/notifications$/, - /^\/health$/, + { + pattern: '^/communities/[a-f0-9-]{36}/browse(/.*)?$', + flag: 'i', + }, + { + pattern: '^/collections/[a-f0-9-]{36}/browse(/.*)?$', + flag: 'i', + }, + { pattern: '^/browse/' }, + { pattern: '^/search' }, + { pattern: '^/community-list$' }, + { pattern: '^/statistics/?' }, + { pattern: '^/admin/' }, + { pattern: '^/processes/?' }, + { pattern: '^/notifications/' }, + { pattern: '^/access-control/' }, + { pattern: '^/health$' }, ], enableSearchComponent: false, enableBrowseComponent: false, diff --git a/src/environments/environment.ts b/src/environments/environment.ts index cd773664fcd..2b6b6d9fd80 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -16,16 +16,23 @@ export const environment: Partial = { transferState: true, replaceRestUrl: false, excludePathPatterns: [ - /^\/communities\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/browse(\/.*)?$/i, - /^\/collections\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/browse(\/.*)?$/i, - /^\/browse\//, - /^\/search$/, - /^\/community-list$/, - /^\/statistics\//, - /^\/admin$/, - /^\/processes$/, - /^\/notifications$/, - /^\/health$/, + { + pattern: '^/communities/[a-f0-9-]{36}/browse(/.*)?$', + flag: 'i', + }, + { + pattern: '^/collections/[a-f0-9-]{36}/browse(/.*)?$', + flag: 'i', + }, + { pattern: '^/browse/' }, + { pattern: '^/search' }, + { pattern: '^/community-list$' }, + { pattern: '^/statistics/?' }, + { pattern: '^/admin/' }, + { pattern: '^/processes/?' }, + { pattern: '^/notifications/' }, + { pattern: '^/access-control/' }, + { pattern: '^/health$' }, ], enableSearchComponent: false, enableBrowseComponent: false,