diff options
author | Raphael <mail@raphaelkabo.com> | 2023-10-09 11:10:51 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-10-09 11:10:51 +0100 |
commit | cc6fcb4c405d8cffacbf9b1082abf61e918482fa (patch) | |
tree | 693f324550dccedd50b6313165b88281a8ebcac8 /src | |
parent | 25fcdd1023550631f5fec6f750829fe09a311d66 (diff) | |
parent | 31022a7d323a351041b7b8508fb56c14fd699580 (diff) |
Merge pull request #114 from lowercasename/rk/static-pages
Static pages
Diffstat (limited to 'src')
-rwxr-xr-x | src/app.ts | 2 | ||||
-rw-r--r-- | src/lib/config.ts | 23 | ||||
-rwxr-xr-x | src/routes.js | 6 | ||||
-rw-r--r-- | src/routes/activitypub.ts | 19 | ||||
-rw-r--r-- | src/routes/event.ts | 4 | ||||
-rw-r--r-- | src/routes/frontend.ts | 27 | ||||
-rw-r--r-- | src/routes/static.ts | 36 | ||||
-rw-r--r-- | src/util/config.ts | 19 | ||||
-rw-r--r-- | src/util/markdown.ts | 16 |
9 files changed, 99 insertions, 53 deletions
@@ -6,6 +6,7 @@ import frontend from "./routes/frontend.js"; import activitypub from "./routes/activitypub.js"; import event from "./routes/event.js"; import group from "./routes/group.js"; +import staticPages from "./routes/static.js"; import { initEmailService } from "./lib/email.js"; @@ -53,6 +54,7 @@ app.use(express.json({ type: "application/json" })); app.use(express.urlencoded({ extended: true })); // Router // +app.use("/", staticPages); app.use("/", frontend); app.use("/", activitypub); app.use("/", event); diff --git a/src/lib/config.ts b/src/lib/config.ts index 7b35b98..6f142e5 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -2,6 +2,12 @@ import fs from "fs"; import toml from "toml"; import { exitWithError } from "./process.js"; +interface StaticPage { + title: string; + path: string; + filename: string; +} + interface GathioConfig { general: { domain: string; @@ -25,9 +31,21 @@ interface GathioConfig { sendgrid?: { api_key: string; }; + static_pages: StaticPage[]; +} + +interface FrontendConfig { + domain: string; + siteName: string; + isFederated: boolean; + emailLogoUrl: string; + showKofi: boolean; + showInstanceInformation: boolean; + staticPages: StaticPage[]; + version: string; } -export const publicConfig = () => { +export const frontendConfig = (): FrontendConfig => { const config = getConfig(); return { domain: config.general.domain, @@ -35,6 +53,9 @@ export const publicConfig = () => { isFederated: config.general.is_federated, emailLogoUrl: config.general.email_logo_url, showKofi: config.general.show_kofi, + showInstanceInformation: config.static_pages?.length > 0, + staticPages: config.static_pages, + version: process.env.npm_package_version || "unknown", }; }; diff --git a/src/routes.js b/src/routes.js index 5371e0e..d59a738 100755 --- a/src/routes.js +++ b/src/routes.js @@ -2,7 +2,7 @@ import fs from "fs"; import express from "express"; import { customAlphabet } from "nanoid"; import randomstring from "randomstring"; -import { getConfig } from "./lib/config.js"; +import { frontendConfig, getConfig } from "./lib/config.js"; import { addToLog } from "./helpers.js"; import moment from "moment-timezone"; import crypto from "crypto"; @@ -1505,9 +1505,7 @@ router.post("/activitypub/inbox", (req, res) => { }); router.use(function (req, res, next) { - res.status(404); - res.render("404", { url: req.url }); - return; + return res.status(404).render("404", frontendConfig()); }); addToLog("startup", "success", "Started up successfully"); diff --git a/src/routes/activitypub.ts b/src/routes/activitypub.ts index 2c4231a..2b8fb4a 100644 --- a/src/routes/activitypub.ts +++ b/src/routes/activitypub.ts @@ -1,7 +1,7 @@ import { Router, Request, Response, NextFunction } from "express"; import { createFeaturedPost, createWebfinger } from "../activitypub.js"; import { acceptsActivityPub } from "../lib/activitypub.js"; -import getConfig from "../lib/config.js"; +import getConfig, { frontendConfig } from "../lib/config.js"; import Event from "../models/Event.js"; import { addToLog } from "../helpers.js"; @@ -15,8 +15,7 @@ const send404IfNotFederated = ( next: NextFunction, ) => { if (!config.general.is_federated) { - res.status(404).render("404", { url: req.url }); - return; + return res.status(404).render("404", frontendConfig()); } next(); }; @@ -49,10 +48,10 @@ router.get("/:eventID/m/:hash", async (req: Request, res: Response) => { id: eventID, }); if (!event) { - return res.status(404).render("404", { url: req.url }); + return res.status(404).render("404", frontendConfig()); } else { if (!event.activityPubMessages) { - return res.status(404).render("404", { url: req.url }); + return res.status(404).render("404", frontendConfig()); } const message = event.activityPubMessages.find( (el) => el.id === id, @@ -69,7 +68,7 @@ router.get("/:eventID/m/:hash", async (req: Request, res: Response) => { ); } } else { - return res.status(404).render("404", { url: req.url }); + return res.status(404).render("404", frontendConfig()); } } } catch (err) { @@ -81,7 +80,7 @@ router.get("/:eventID/m/:hash", async (req: Request, res: Response) => { " failed with error: " + err, ); - return res.status(404).render("404", { url: req.url }); + return res.status(404).render("404", frontendConfig()); } }); @@ -103,7 +102,7 @@ router.get("/.well-known/webfinger", async (req, res) => { const event = await Event.findOne({ id: eventID }); if (!event) { - return res.status(404).render("404", { url: req.url }); + return res.status(404).render("404", frontendConfig()); } else { if (acceptsActivityPub(req)) { res.header( @@ -122,7 +121,7 @@ router.get("/.well-known/webfinger", async (req, res) => { "error", `Attempt to render webfinger for ${resource} failed with error: ${err}`, ); - return res.status(404).render("404", { url: req.url }); + return res.status(404).render("404", frontendConfig()); } } }); @@ -167,7 +166,7 @@ router.get("/:eventID/followers", async (req, res) => { "error", `Attempt to render followers for ${eventID} failed with error: ${err}`, ); - return res.status(404).render("404", { url: req.url }); + return res.status(404).render("404", frontendConfig()); } }); diff --git a/src/routes/event.ts b/src/routes/event.ts index 2245009..cfd877e 100644 --- a/src/routes/event.ts +++ b/src/routes/event.ts @@ -2,7 +2,6 @@ import { Router, Response, Request } from "express"; import multer from "multer"; import Jimp from "jimp"; import moment from "moment-timezone"; -import { marked } from "marked"; import { generateEditToken, generateEventID, @@ -26,6 +25,7 @@ import getConfig from "../lib/config.js"; import { sendEmailFromTemplate } from "../lib/email.js"; import crypto from "crypto"; import ical from "ical"; +import { markdownToSanitizedHTML } from "../util/markdown.js"; const config = getConfig(); @@ -148,7 +148,7 @@ router.post( eventID, config.general.domain, publicKey, - marked.parse(eventData.eventDescription), + markdownToSanitizedHTML(eventData.eventDescription), eventData.eventName, eventData.eventLocation, eventImageFilename, diff --git a/src/routes/frontend.ts b/src/routes/frontend.ts index c9594ef..c405572 100644 --- a/src/routes/frontend.ts +++ b/src/routes/frontend.ts @@ -1,9 +1,8 @@ import { Router, Request, Response } from "express"; import moment from "moment-timezone"; import { marked } from "marked"; -import { frontendConfig } from "../util/config.js"; -import { renderPlain } from "../util/markdown.js"; -import getConfig from "../lib/config.js"; +import { markdownToSanitizedHTML, renderPlain } from "../util/markdown.js"; +import getConfig, { frontendConfig } from "../lib/config.js"; import { addToLog, exportICal } from "../helpers.js"; import Event from "../models/Event.js"; import EventGroup, { IEventGroup } from "../models/EventGroup.js"; @@ -30,9 +29,7 @@ router.get("/:eventID", async (req: Request, res: Response) => { .lean() // Required, see: https://stackoverflow.com/questions/59690923/handlebars-access-has-been-denied-to-resolve-the-property-from-because-it-is .populate("eventGroup"); if (!event) { - res.status(404); - res.render("404", { url: req.url }); - return; + return res.status(404).render("404", frontendConfig()); } const parsedLocation = event.location.replace(/\s+/g, "+"); let displayDate; @@ -94,7 +91,7 @@ router.get("/:eventID", async (req: Request, res: Response) => { eventHasBegun = true; } let fromNow = moment.tz(event.start, event.timezone).fromNow(); - let parsedDescription = marked.parse(event.description); + let parsedDescription = markdownToSanitizedHTML(event.description); let eventEditToken = event.editToken; let escapedName = event.name.replace(/\s+/g, "+"); @@ -252,7 +249,7 @@ router.get("/:eventID", async (req: Request, res: Response) => { err, ); console.log(err); - res.status(404).render("404", { url: req.url }); + return res.status(404).render("404", frontendConfig()); } }); @@ -263,9 +260,11 @@ router.get("/group/:eventGroupID", async (req: Request, res: Response) => { }).lean(); if (!eventGroup) { - return res.status(404).render("404", { url: req.url }); + return res.status(404).render("404", frontendConfig()); } - const parsedDescription = marked.parse(eventGroup.description); + const parsedDescription = markdownToSanitizedHTML( + eventGroup.description, + ); const eventGroupEditToken = eventGroup.editToken; const escapedName = eventGroup.name.replace(/\s+/g, "+"); const eventGroupHasCoverImage = !!eventGroup.image; @@ -364,7 +363,7 @@ router.get("/group/:eventGroupID", async (req: Request, res: Response) => { `Attempt to display event group ${req.params.eventGroupID} failed with error: ${err}`, ); console.log(err); - return res.status(404).render("404", { url: req.url }); + return res.status(404).render("404", frontendConfig()); } }); @@ -391,7 +390,7 @@ router.get( `Attempt to display event group feed for ${req.params.eventGroupID} failed with error: ${err}`, ); console.log(err); - res.status(404).render("404", { url: req.url }); + return res.status(404).render("404", frontendConfig()); } }, ); @@ -413,7 +412,7 @@ router.get("/export/event/:eventID", async (req: Request, res: Response) => { `Attempt to export event ${req.params.eventID} failed with error: ${err}`, ); console.log(err); - res.status(404).render("404", { url: req.url }); + return res.status(404).render("404", frontendConfig()); } }); @@ -439,7 +438,7 @@ router.get( `Attempt to export event group ${req.params.eventGroupID} failed with error: ${err}`, ); console.log(err); - res.status(404).render("404", { url: req.url }); + return res.status(404).render("404", frontendConfig()); } }, ); diff --git a/src/routes/static.ts b/src/routes/static.ts new file mode 100644 index 0000000..33f0225 --- /dev/null +++ b/src/routes/static.ts @@ -0,0 +1,36 @@ +import { Router, Request, Response } from "express"; +import fs from "fs"; +import getConfig, { frontendConfig } from "../lib/config.js"; +import { markdownToSanitizedHTML } from "../util/markdown.js"; + +const config = getConfig(); +const router = Router(); + +if (config.static_pages?.length) { + config.static_pages + .filter((page) => page.path?.startsWith("/") && page.filename) + .forEach((page) => { + router.get(page.path, (_: Request, res: Response) => { + try { + if (fs.existsSync(`./static/${page.filename}`)) { + const fileBody = fs.readFileSync( + `./static/${page.filename}`, + "utf-8", + ); + const parsed = markdownToSanitizedHTML(fileBody); + return res.render("static", { + title: page.title, + content: parsed, + ...frontendConfig(), + }); + } + return res.status(404).render("404", frontendConfig()); + } catch (err) { + console.error(err); + return res.status(404).render("404", frontendConfig()); + } + }); + }); +} + +export default router; diff --git a/src/util/config.ts b/src/util/config.ts deleted file mode 100644 index d1fd05b..0000000 --- a/src/util/config.ts +++ /dev/null @@ -1,19 +0,0 @@ -import getConfig from "../lib/config.js"; - -const config = getConfig(); - -interface FrontendConfig { - domain: string; - email: string; - siteName: string; - showKofi: boolean; - isFederated: boolean; -} - -export const frontendConfig = (): FrontendConfig => ({ - domain: config.general.domain, - email: config.general.email, - siteName: config.general.site_name, - showKofi: config.general.show_kofi, - isFederated: config.general.is_federated, -}); diff --git a/src/util/markdown.ts b/src/util/markdown.ts index 9f5d384..bab50bd 100644 --- a/src/util/markdown.ts +++ b/src/util/markdown.ts @@ -1,7 +1,6 @@ -// Extra marked renderer (used to render plaintext event description for page metadata) -// Adapted from https://dustinpfister.github.io/2017/11/19/nodejs-marked/ - import { marked } from "marked"; +import { JSDOM } from "jsdom"; +import DOMPurify from "dompurify"; // ? to ? helper function htmlEscapeToText(text: string) { @@ -14,6 +13,9 @@ function htmlEscapeToText(text: string) { }); } +// Extra marked renderer (used to render plaintext event description for page metadata) +// Adapted from https://dustinpfister.github.io/2017/11/19/nodejs-marked/ + export const renderPlain = () => { var render = new marked.Renderer(); // render just the text of a link, strong, em @@ -42,3 +44,11 @@ export const renderPlain = () => { }; return render; }; + +export const markdownToSanitizedHTML = (markdown: string) => { + const html = marked.parse(markdown); + const window = new JSDOM("").window; + const purify = DOMPurify(window); + const clean = purify.sanitize(html); + return clean; +}; |