summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/app.ts2
-rw-r--r--src/lib/config.ts23
-rwxr-xr-xsrc/routes.js6
-rw-r--r--src/routes/activitypub.ts19
-rw-r--r--src/routes/event.ts4
-rw-r--r--src/routes/frontend.ts27
-rw-r--r--src/routes/static.ts36
-rw-r--r--src/util/config.ts19
-rw-r--r--src/util/markdown.ts16
9 files changed, 99 insertions, 53 deletions
diff --git a/src/app.ts b/src/app.ts
index c43f31d..3370d27 100755
--- a/src/app.ts
+++ b/src/app.ts
@@ -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;
+};