diff options
author | Raphael Kabo <raphaelkabo@hey.com> | 2024-02-25 17:56:25 +0000 |
---|---|---|
committer | Raphael Kabo <raphaelkabo@hey.com> | 2024-02-25 17:56:25 +0000 |
commit | cd0f291eb1a608589fcc2c1875fa7099ed8e2c51 (patch) | |
tree | 05b1d8b1d63baed174883cc96807051e530969a2 /src/routes | |
parent | b17238eb2840553c69fc2dae168be557afbcee9c (diff) |
feat: optionally restrict event creation to specific emails
Diffstat (limited to 'src/routes')
-rw-r--r-- | src/routes/event.ts | 3 | ||||
-rw-r--r-- | src/routes/frontend.ts | 32 | ||||
-rw-r--r-- | src/routes/group.ts | 2 | ||||
-rw-r--r-- | src/routes/magicLink.ts | 70 |
4 files changed, 107 insertions, 0 deletions
diff --git a/src/routes/event.ts b/src/routes/event.ts index cfd877e..fb9d8c7 100644 --- a/src/routes/event.ts +++ b/src/routes/event.ts @@ -26,6 +26,7 @@ import { sendEmailFromTemplate } from "../lib/email.js"; import crypto from "crypto"; import ical from "ical"; import { markdownToSanitizedHTML } from "../util/markdown.js"; +import { checkMagicLink } from "../lib/middleware.js"; const config = getConfig(); @@ -60,6 +61,7 @@ const router = Router(); router.post( "/event", upload.single("imageUpload"), + checkMagicLink, async (req: Request, res: Response) => { const { data: eventData, errors } = validateEventData(req.body); if (errors && errors.length > 0) { @@ -527,6 +529,7 @@ router.put( router.post( "/import/event", icsUpload.single("icsImportControl"), + checkMagicLink, async (req: Request, res: Response) => { if (!req.file) { return res.status(400).json({ diff --git a/src/routes/frontend.ts b/src/routes/frontend.ts index 8ddfbf6..0d8793a 100644 --- a/src/routes/frontend.ts +++ b/src/routes/frontend.ts @@ -10,6 +10,7 @@ import { acceptsActivityPub, activityPubContentType, } from "../lib/activitypub.js"; +import MagicLink from "../models/MagicLink.js"; const config = getConfig(); @@ -19,9 +20,40 @@ router.get("/", (_: Request, res: Response) => { }); router.get("/new", (_: Request, res: Response) => { + if (config.general.creator_email_addresses?.length) { + return res.render("createEventMagicLink", frontendConfig()); + } + return res.render("newevent", { + title: "New event", + ...frontendConfig(), + }); +}); + +router.get("/new/:magicLinkToken", async (req: Request, res: Response) => { + // If we don't have any creator email addresses, we don't need to check the magic link + // so we can just redirect to the new event page + if (!config.general.creator_email_addresses?.length) { + return res.redirect("/new"); + } + const magicLink = await MagicLink.findOne({ + token: req.params.magicLinkToken, + expiryTime: { $gt: new Date() }, + permittedActions: "createEvent", + }); + if (!magicLink) { + return res.render("createEventMagicLink", { + ...frontendConfig(), + message: { + type: "danger", + text: "This magic link is invalid or has expired. Please request a new one here.", + }, + }); + } res.render("newevent", { title: "New event", ...frontendConfig(), + magicLinkToken: req.params.magicLinkToken, + creatorEmail: magicLink.email, }); }); diff --git a/src/routes/group.ts b/src/routes/group.ts index 40dcccb..34377b0 100644 --- a/src/routes/group.ts +++ b/src/routes/group.ts @@ -9,6 +9,7 @@ import EventGroup from "../models/EventGroup.js"; import { sendEmailFromTemplate } from "../lib/email.js"; import { marked } from "marked"; import { renderPlain } from "../util/markdown.js"; +import { checkMagicLink } from "../lib/middleware.js"; const config = getConfig(); @@ -32,6 +33,7 @@ const router = Router(); router.post( "/group", upload.single("imageUpload"), + checkMagicLink, async (req: Request, res: Response) => { const { data: groupData, errors } = validateGroupData(req.body); if (errors && errors.length > 0) { diff --git a/src/routes/magicLink.ts b/src/routes/magicLink.ts new file mode 100644 index 0000000..24f0667 --- /dev/null +++ b/src/routes/magicLink.ts @@ -0,0 +1,70 @@ +import { Router, Request, Response } from "express"; +import getConfig, { frontendConfig } from "../lib/config.js"; +import { sendEmailFromTemplate } from "../lib/email.js"; +import { generateMagicLinkToken } from "../util/generator.js"; +import MagicLink from "../models/MagicLink.js"; + +const router = Router(); +const config = getConfig(); + +router.post("/magic-link/event/create", async (req: Request, res: Response) => { + const { email } = req.body; + if (!email) { + res.render("createEventMagicLink", { + ...frontendConfig(), + message: { + type: "danger", + text: "Please provide an email address.", + }, + }); + return; + } + const allowedEmails = config.general.creator_email_addresses; + if (!allowedEmails?.length) { + // No creator email addresses are configured, so skip the magic link check + return res.redirect("/new"); + } + if (!allowedEmails.includes(email)) { + res.render("createEventMagicLink", { + ...frontendConfig(), + message: { + type: "success", + text: "Thanks! If this email address can create events, you should receive an email with a magic link.", + }, + }); + return; + } + const token = generateMagicLinkToken(); + const magicLink = new MagicLink({ + email, + token, + expiryTime: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours + permittedActions: ["createEvent"], + }); + await magicLink.save(); + + // Take this opportunity to delete any expired magic links + await MagicLink.deleteMany({ expiryTime: { $lt: new Date() } }); + + sendEmailFromTemplate( + email, + `Magic link to create an event`, + "createEventMagicLink", + { + token, + siteName: config.general.site_name, + siteLogo: config.general.email_logo_url, + domain: config.general.domain, + }, + req, + ); + res.render("createEventMagicLink", { + ...frontendConfig(), + message: { + type: "success", + text: "Thanks! If this email address can create events, you should receive an email with a magic link.", + }, + }); +}); + +export default router; |