diff --git a/config/config.example.yml b/config/config.example.yml index 28ba095098..ec1087525c 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -39,11 +39,16 @@ cache: maxBufferSize: 100 timePerMethod: PATCH: 3 # time in seconds - # In-memory cache of server-side rendered content + # In-memory cache of server-side rendered pages. This cache stores the most recently accessed public pages. + # Pages are automatically added/dropped from this cache based on how recently they have been used. serverSide: - # Maximum number of pages (rendered via SSR) to cache. Set to zero to disable server side caching. + # Maximum number of pages to cache. Set to zero (0) to disable server side caching. Default is 100, which means + # the 100 most recently accessed public pages will be cached. As all pages are cached in server memory, + # increasing this value will increase memory needs. Individual cached pages are usually small (<100KB), + # so max=100 should only require a maximum of 9-10MB of memory. Restarting the app clears this page cache. max: 100 - # Amount of time after which cached pages are considered stale (in ms) + # Amount of time after which cached pages are considered stale (in ms). After becoming stale, the cached + # copy is automatically refreshed on the next request. timeToLive: 900000 # 15 minutes # Authentication settings diff --git a/server.ts b/server.ts index c5905e0ccf..3417cb3b96 100644 --- a/server.ts +++ b/server.ts @@ -54,6 +54,8 @@ import { buildAppConfig } from './src/config/config.server'; import { APP_CONFIG, AppConfig } from './src/config/app-config.interface'; import { extendEnvironmentWithAppConfig } from './src/config/config.util'; import { logStartupMessage } from './startup-message'; +import { TOKENITEM } from 'src/app/core/auth/models/auth-token-info.model'; +import { isAuthenticated } from 'src/app/core/auth/selectors'; /* @@ -113,13 +115,13 @@ export function app() { /* * Add cookie parser middleware - * See [morgan](https://github.com/expressjs/cookie-parser) + * See [cookie-parser](https://github.com/expressjs/cookie-parser) */ server.use(cookieParser()); /* - * Add parser for request bodies - * See [morgan](https://github.com/expressjs/body-parser) + * Add JSON parser for request bodies + * See [body-parser](https://github.com/expressjs/body-parser) */ server.use(json()); @@ -258,7 +260,7 @@ function serverSideRender(req, res, sendToUser: boolean = true) { if (hasNoValue(err) && hasValue(data)) { res.locals.ssr = true; // mark response as SSR (enables text compression) // save server side rendered data to cache - saveToCache(getCacheKey(req), data); + saveToCache(req, data); if (sendToUser) { // send rendered page to user res.send(data); @@ -313,7 +315,7 @@ function addCacheControl(req, res, next) { */ function enableCache() { if (cacheEnabled()) { - // Initialize a new "least-recently-used" item cache. + // Initialize a new "least-recently-used" item cache (where least recently used items are removed first) // See https://www.npmjs.com/package/lru-cache cache = new LRU( { max: environment.cache.serverSide.max || 100, // 100 items in cache maximum @@ -340,8 +342,10 @@ function cacheCheck(req, res, next) { let cacheHit = false; let debug = false; // Enable to see cache hits & re-rendering logs - // Only check cache if cache enabled & SSR is enabled - if (cacheEnabled()) { + // Only check cache if cache enabled & NOT authenticated. + // NOTE: Authenticated users cannot use the SSR cache. Cached pages only show data available to anonymous users. + // Only public pages can currently be cached, as the cached data is not user-specific. + if (cacheEnabled() && !isUserAuthenticated(req)) { const key = getCacheKey(req); // Check if this page is in our cache @@ -387,29 +391,25 @@ function getCacheKey(req): string { } /** - * Save data to server side cache, if enabled. If caching is not enabled, this is a noop - * @param key page key + * Save data to server side cache, if enabled. If caching is not enabled or user is authenticated, this is a noop + * @param req current page request * @param data page data to save to cache */ -function saveToCache(key: string, data: any) { - // Only cache if caching is enabled and this path is allowed to be cached - if (cacheEnabled() && canCachePage(key)) { - cache.set(key, data); +function saveToCache(req, data: any) { + // Only cache if caching is enabled and no one is currently authenticated. This means ONLY public pages can be cached. + // NOTE: It's not safe to save page data to the cache when a user is authenticated. In that situation, + // the page may include sensitive or user-specific materials. As the cache is shared across all users, it can only contain public info. + if (cacheEnabled() && !isUserAuthenticated(req)) { + cache.set(getCacheKey(req), data); } } /** - * Whether this path is allowed to be cached. Only public paths can be cached as the cache is shared across all users. - * @param key page key (corresponds to path of page) - * @returns true if allowed to be cached, false otherwise. + * Whether a user is authenticated or not */ -function canCachePage(key: string) { - // Only these publicly accessible pages can be cached. - // NOTE: Caching pages which require authentication is NOT ALLOWED. The same cache is used by all users & user-specific data must NEVER appear in cache. - const allowedPages = [/^\/$/, /^\/home$/, /^\/items\//, /^\/entities\//, /^\/collections\//, /^\/communities\//, /^\/search[\/?]?/, /\/browse\//, /^\/community-list$/]; - - // Check whether any of these regexs match with the passed in key - return allowedPages.some(regex => regex.test(key)); +function isUserAuthenticated(req): boolean { + // Check whether our authentication Cookie exists or not + return req.cookies[TOKENITEM]; } /*