diff --git a/src/middleware/baseContext.js b/src/middleware/baseContext.js new file mode 100644 index 0000000..617fb1f --- /dev/null +++ b/src/middleware/baseContext.js @@ -0,0 +1,14 @@ +// src/middleware/baseContext.js +const getBaseContext = require("../utils/baseContext"); + +module.exports = async function baseContextMiddleware(req, res, next) { + const isAuthenticated = !!req.isAuthenticated; + const baseContext = await getBaseContext(isAuthenticated); + res.locals.baseContext = baseContext; + + res.renderWithBaseContext = (template, overrides = {}) => { + res.render(template, Object.assign({}, baseContext, overrides)); + }; + + next(); +}; diff --git a/src/middleware/errorHandler.js b/src/middleware/errorHandler.js index 8fd48a2..e187198 100644 --- a/src/middleware/errorHandler.js +++ b/src/middleware/errorHandler.js @@ -42,7 +42,7 @@ return; } - const context = buildErrorRenderContext({ + const context = { req, requestId, timestamp, @@ -51,8 +51,8 @@ message, stack, errorContext, - }); + }; - const errorPageContext = await getBaseContext(context); - res.status(errorContext.statusCode).render("pages/error", errorPageContext); + res.status(errorContext.statusCode); + res.renderWithBaseContext("pages/error", context); }; diff --git a/src/middleware/index.js b/src/middleware/index.js index ab072f1..11581c1 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -9,6 +9,7 @@ const applyProductionSecurity = require("./applyProductionSecurity"); const validateRequestIntegrity = require("./validateRequestIntegrity"); const errorHandler = require("./errorHandler"); +const baseContext = require("./baseContext"); const hbs = require("./hbs"); const { @@ -20,7 +21,10 @@ function setupApp() { const app = express(); - + app.use((req, res, next) => { + req.isAuthenticated = !!req.headers['remote-user']; + next(); + }); const excludedPaths = ['/contact', '/analytics', '/track']; const DATA_LIMIT_BYTES = 10 * 1024; // 10k @@ -51,11 +55,12 @@ }); - // Setup logging - app.use(logEvent, morganInfo, morganWarn, morganError, loggingMiddleware); - // Setup handlebars app.use(hbs); + app.use(baseContext) + + // Setup logging + app.use(logEvent, morganInfo, morganWarn, morganError, loggingMiddleware); // Setup production environment if (process.env.NODE_ENV === "production") { diff --git a/src/routes/blog_index.js b/src/routes/blog_index.js index f227aee..fe45dde 100644 --- a/src/routes/blog_index.js +++ b/src/routes/blog_index.js @@ -1,7 +1,6 @@ const express = require("express"); const path = require("path"); const { getAllPosts } = require("../utils/postFileUtils"); -const getBaseContext = require("../utils/baseContext"); const router = express.Router(); router.get("/blog", async (req, res, next) => { @@ -30,8 +29,7 @@ templateContent: post.excerpt || "", })); - const context = await getBaseContext({ collections: { posts } }); - res.render("pages/blog_index", context); + res.renderWithBaseContext("pages/blog_index", { collections: { posts } }); }); module.exports = router; diff --git a/src/routes/contact.js b/src/routes/contact.js index 7c53654..a46b114 100644 --- a/src/routes/contact.js +++ b/src/routes/contact.js @@ -370,13 +370,13 @@ await logSecurityEvent(securityData, 'page_access'); - const context = await getBaseContext({ + const context = { csrfToken: res.locals.csrfToken, title: "Contact", formAction: qualifyLink("/contact"), formMethod: "POST" - }); - res.render("pages/contact.handlebars", context); + }; + res.renderWithBaseContext("pages/contact.handlebars", context); }); router.get("/contact/thankyou", async (req, res) => { @@ -387,10 +387,9 @@ await logSecurityEvent(securityData, 'thankyou_access'); - const context = await getBaseContext({ + res.renderWithBaseContext("pages/thankyou.handlebars", { title: "Thank You", }); - res.render("pages/thankyou.handlebars", context); }); module.exports = router; diff --git a/src/routes/errorPage.js b/src/routes/errorPage.js index 2d858a3..ca2efc1 100644 --- a/src/routes/errorPage.js +++ b/src/routes/errorPage.js @@ -6,12 +6,13 @@ const code = parseInt(req.query.code, 10) || 500; const errorContext = getErrorContext(code); - const context = await getBaseContext({ + const context = { title: errorContext.title, message: errorContext.message, statusCode: errorContext.statusCode, content: "", - }); + }; - res.status(errorContext.statusCode).render("pages/error", context); + res.status(errorContext.statusCode) + res.renderWithBaseContext("pages/error", context); }; diff --git a/src/routes/logs.js b/src/routes/logs.js index 97602ce..2d5b444 100644 --- a/src/routes/logs.js +++ b/src/routes/logs.js @@ -18,7 +18,7 @@ const db = new Database(dbPath, { readonly: true }); router.get("/logs", (req, res) => { - res.render("pages/logs", { layout: "logs" }); + res.renderWithBaseContext("pages/logs", { layout: "logs" }); }); router.post("/logs", (req, res) => { diff --git a/src/routes/newsletter.js b/src/routes/newsletter.js index 5bec8a9..0b3d72e 100644 --- a/src/routes/newsletter.js +++ b/src/routes/newsletter.js @@ -8,20 +8,20 @@ const { qualifyLink } = require("../utils/qualifyLinks"); router.get("/newsletter", async (req, res) => { - const context = await getBaseContext({ + const context = { csrfToken: res.locals.csrfToken, title: "Newsletter", formAction: qualifyLink("/newsletter"), formMethod: "POST" - }); - res.render("pages/newsletter.handlebars", context); + } + res.renderWithBaseContext("pages/newsletter.handlebars", context); }); router.get("/newsletter/success", async (req, res) => { - const context = await getBaseContext({ + const context = { title: "Thank You", - }); - res.render("pages/newsletter-success.handlebars", context); + } + res.renderWithBaseContext("pages/newsletter-success.handlebars", context); }); router.post("/newsletter", formLimiter, async (req, res, next) => { diff --git a/src/routes/post.js b/src/routes/post.js index aeb958f..3422cf3 100644 --- a/src/routes/post.js +++ b/src/routes/post.js @@ -40,13 +40,13 @@ throw new Error("Attempted to access an unpublished page in production"); } const htmlContent = marked(content); - const context = await getBaseContext({ + const context = { title: frontmatter.title, date: frontmatter.date, author: frontmatter.author, content: htmlContent, - }); - res.render("pages/post", context); + } + res.renderWithBaseContext("pages/post", context); } catch (err) { next(new HttpError("The requested blog post could not be found.", 404)); } diff --git a/src/routes/sitemap.js b/src/routes/sitemap.js index e82624c..a6dafcf 100644 --- a/src/routes/sitemap.js +++ b/src/routes/sitemap.js @@ -26,11 +26,11 @@ // HTML sitemap page router.get("/sitemap", async (req, res) => { - const context = await getBaseContext({ + const context = { title: "Site Map", sitemap: await sitemapService.getCompleteSitemap(), - }); - res.render("pages/sitemap", context); + } + res.renderWithBaseContext("pages/sitemap", context); }); // const getBaseUrl = require("../utils/baseUrl"); diff --git a/src/utils/baseContext.js b/src/utils/baseContext.js index 06c45b9..46653e9 100644 --- a/src/utils/baseContext.js +++ b/src/utils/baseContext.js @@ -5,24 +5,21 @@ const { qualifyNavLinks } = require("./qualifyLinks.js"); const { baseUrl } = require("./baseUrl.js"); const navLinks = require(path.join(__dirname, "../../content/navLinks.json")); +const filterSecureLinks = require("../utils/filterSecureLinks"); -async function getBaseContext(overrides = {}) { - - const qualifiedNavLinks = qualifyNavLinks(navLinks); +module.exports = async function getBaseContext(isAuthenticated, overrides = {}) { + const filteredNavLinks = filterSecureLinks(navLinks, isAuthenticated); + const qualifiedNavLinks = qualifyNavLinks(filteredNavLinks); const menu = await getPostsMenu(path.join(__dirname, "../../content/posts")); - return Object.assign( - { - siteOwner: process.env.SITE_OWNER, - originCountry: process.env.COUNTRY, - hCaptchaKey: process.env.HCAPTCHA_KEY, - navLinks: qualifiedNavLinks, - years: menu, - formatMonth, - baseUrl - }, - overrides - ); -} - -module.exports = getBaseContext; + return Object.assign({ + siteOwner: process.env.SITE_OWNER, + originCountry: process.env.COUNTRY, + hCaptchaKey: process.env.HCAPTCHA_KEY, + navLinks: qualifiedNavLinks, + years: menu, + formatMonth, + baseUrl, + isAuthenticated + }, overrides); +}; diff --git a/src/utils/filterSecureLinks.js b/src/utils/filterSecureLinks.js new file mode 100644 index 0000000..cc1a50d --- /dev/null +++ b/src/utils/filterSecureLinks.js @@ -0,0 +1,14 @@ +// src/utils/filterSecureLinks.js +function filterSecureLinks(links, isAuthenticated) { + return links + .filter(link => isAuthenticated || !link.secure) + .map(link => { + const item = { ...link }; + if (item.submenu) { + item.submenu = filterSecureLinks(item.submenu, isAuthenticated); + if (!item.submenu.length) delete item.submenu; + } + return item; + }); +} +module.exports = filterSecureLinks