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;  | 
