From 94ecbea48f512a31b52377b22c8077232338718e Mon Sep 17 00:00:00 2001 From: Aydan Pirani Date: Sat, 25 May 2024 17:48:19 -0700 Subject: [PATCH 1/7] Added new endpoint templating --- src/app.ts | 2 ++ src/services/mail/drafts/drafts-subrouter.ts | 5 +++++ src/services/mail/lists/lists-subrouter.ts | 5 +++++ src/services/mail/mail-router.ts | 13 +++++++++++++ src/services/mail/templates/templates-subrouter.ts | 5 +++++ 5 files changed, 30 insertions(+) create mode 100644 src/services/mail/drafts/drafts-subrouter.ts create mode 100644 src/services/mail/lists/lists-subrouter.ts create mode 100644 src/services/mail/mail-router.ts create mode 100644 src/services/mail/templates/templates-subrouter.ts diff --git a/src/app.ts b/src/app.ts index 6da05cb..4502034 100644 --- a/src/app.ts +++ b/src/app.ts @@ -12,6 +12,7 @@ import errorHandler from "./middleware/error-handler"; import attendeeRouter from "./services/attendees/attendee-router"; import authRouter from "./services/auth/auth-router"; import eventsRouter from "./services/events/events-router"; +import mailRouter from "./services/mail/mail-router"; import notificationsRouter from "./services/notifications/notifications-router"; import registrationRouter from "./services/registration/registration-router"; import s3Router from "./services/s3/s3-router"; @@ -36,6 +37,7 @@ app.use("/", bodyParser.json()); app.use("/attendee", attendeeRouter); app.use("/auth", authRouter); app.use("/events", eventsRouter); +app.use("/mail", mailRouter); app.use("/notifications", notificationsRouter); app.use("/registration", registrationRouter); app.use("/s3", s3Router); diff --git a/src/services/mail/drafts/drafts-subrouter.ts b/src/services/mail/drafts/drafts-subrouter.ts new file mode 100644 index 0000000..ce98f35 --- /dev/null +++ b/src/services/mail/drafts/drafts-subrouter.ts @@ -0,0 +1,5 @@ +import { Router } from "express"; + +const draftsSubRouter = Router(); + +export default draftsSubRouter; diff --git a/src/services/mail/lists/lists-subrouter.ts b/src/services/mail/lists/lists-subrouter.ts new file mode 100644 index 0000000..6949191 --- /dev/null +++ b/src/services/mail/lists/lists-subrouter.ts @@ -0,0 +1,5 @@ +import { Router } from "express"; + +const listsSubRouter = Router(); + +export default listsSubRouter; diff --git a/src/services/mail/mail-router.ts b/src/services/mail/mail-router.ts new file mode 100644 index 0000000..4bd9b6e --- /dev/null +++ b/src/services/mail/mail-router.ts @@ -0,0 +1,13 @@ +import draftsSubRouter from "./drafts/drafts-subrouter"; +import listsSubRouter from "./lists/lists-subrouter"; +import templatesSubRouter from "./templates/templates-subrouter"; + +import { Router } from "express"; + +const mailRouter = Router(); + +mailRouter.use("drafts", draftsSubRouter); +mailRouter.use("lists", listsSubRouter); +mailRouter.use("templates", templatesSubRouter); + +export default mailRouter; diff --git a/src/services/mail/templates/templates-subrouter.ts b/src/services/mail/templates/templates-subrouter.ts new file mode 100644 index 0000000..ce0a8e5 --- /dev/null +++ b/src/services/mail/templates/templates-subrouter.ts @@ -0,0 +1,5 @@ +import { Router } from "express"; + +const templatesSubRouter = Router(); + +export default templatesSubRouter; From 2703a10ab6110083c98023a77301e319c730aa8a Mon Sep 17 00:00:00 2001 From: Aydan Pirani Date: Sat, 25 May 2024 18:17:26 -0700 Subject: [PATCH 2/7] Code for templates service --- src/database.ts | 5 ++ src/services/mail/lists/lists-schema.ts | 0 .../mail/templates/templates-schema.ts | 34 ++++++++++++ .../mail/templates/templates-subrouter.ts | 53 +++++++++++++++++++ 4 files changed, 92 insertions(+) create mode 100644 src/services/mail/lists/lists-schema.ts create mode 100644 src/services/mail/templates/templates-schema.ts diff --git a/src/database.ts b/src/database.ts index 460279a..b38e452 100644 --- a/src/database.ts +++ b/src/database.ts @@ -17,6 +17,10 @@ import { NotificationsSchema, NotificationsValidator, } from "./services/notifications/notifications-schema"; +import { + TemplateSchema, + TemplateValidator, +} from "./services/mail/templates/templates-schema"; mongoose.set("toObject", { versionKey: false }); @@ -61,6 +65,7 @@ export const Database = { RegistrationSchema, RegistrationValidator ), + TEMPLATES: initializeModel("templates", TemplateSchema, TemplateValidator), NOTIFICATIONS: initializeModel( "notifications", NotificationsSchema, diff --git a/src/services/mail/lists/lists-schema.ts b/src/services/mail/lists/lists-schema.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/services/mail/templates/templates-schema.ts b/src/services/mail/templates/templates-schema.ts new file mode 100644 index 0000000..2f38e4d --- /dev/null +++ b/src/services/mail/templates/templates-schema.ts @@ -0,0 +1,34 @@ +import { Schema } from "mongoose"; +import { z } from "zod"; + +export const TemplateValidator = z.object({ + templateId: z.coerce.string(), + subject: z.coerce.string(), + content: z.coerce.string().email(), + substitutions: z.string().array(), + usages: z.number().min(0).default(0), +}); + +export const TemplateSchema = new Schema({ + templateId: { + type: String, + required: true, + unique: true, + }, + subject: { + type: String, + required: true, + }, + content: { + type: String, + required: true, + }, + substitutions: { + type: [String], + required: true, + }, + usages: { + type: Number, + default: 0, + }, +}); diff --git a/src/services/mail/templates/templates-subrouter.ts b/src/services/mail/templates/templates-subrouter.ts index ce0a8e5..a289e4a 100644 --- a/src/services/mail/templates/templates-subrouter.ts +++ b/src/services/mail/templates/templates-subrouter.ts @@ -1,5 +1,58 @@ import { Router } from "express"; +import RoleChecker from "../../../middleware/role-checker"; +import { Role } from "../../auth/auth-models"; +import { Database } from "../../../database"; +import { StatusCodes } from "http-status-codes"; +import { TemplateValidator } from "./templates-schema"; +import { z } from "zod"; + +type TemplateData = z.infer; const templatesSubRouter = Router(); +templatesSubRouter.get( + "/", + RoleChecker([Role.Values.STAFF]), + async (req, res) => { + const templates = await Database.TEMPLATES.find(); + const cleanedTemplates = templates.map((x) => x.toObject()); + return res.status(StatusCodes.OK).json(cleanedTemplates); + } +); + +templatesSubRouter.post( + "/", + RoleChecker([Role.Values.ADMIN]), + async (req, res) => { + try { + const templateData = TemplateValidator.parse(req.body); + await Database.TEMPLATES.create(templateData); + return res.sendStatus(StatusCodes.CREATED); + } catch (error) { + return res.status(StatusCodes.BAD_REQUEST).send(error); + } + } +); + +templatesSubRouter.delete( + "/:TEMPLATEID", + RoleChecker([Role.Values.ADMIN]), + async (req, res) => { + try { + const templateId = req.params.TEMPLATEID; + const templateData = (await Database.TEMPLATES.findOne({ + templateId: templateId, + })) as TemplateData; + + if (!templateData) { + return res + .status(StatusCodes.BAD_REQUEST) + .json({ error: "NoSuchId" }); + } + } catch (error) { + return res.status(StatusCodes.BAD_REQUEST).send(error); + } + } +); + export default templatesSubRouter; From e931f8f79c92b27a778dbd827f83f7801ad209aa Mon Sep 17 00:00:00 2001 From: Aydan Pirani Date: Sun, 26 May 2024 00:14:00 -0700 Subject: [PATCH 3/7] Added auth whitelist --- src/config.ts | 13 ++++++++++--- src/services/auth/auth-utils.ts | 9 ++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/config.ts b/src/config.ts index b92f607..9c2bdf6 100644 --- a/src/config.ts +++ b/src/config.ts @@ -25,9 +25,16 @@ export const Config = { CLIENT_ID: getEnv("OAUTH_GOOGLE_CLIENT_ID"), CLIENT_SECRET: getEnv("OAUTH_GOOGLE_CLIENT_SECRET"), - // AUTH_CALLBACK_URI_BASE: "http://localhost:3000/auth/callback/", - AUTH_CALLBACK_URI_BASE: - "https://api.reflectionsprojections.org/auth/callback/", + AUTH_CALLBACK_URI_BASE: "http://localhost:3000/auth/callback/", + // AUTH_CALLBACK_URI_BASE: + // "https://api.reflectionsprojections.org/auth/callback/", + + AUTH_ADMIN_WHITELIST: new Set([ + "apirani2@illinois.edu", // Aydan Pirani (Dev) + "divyack2@illinois.edu", // Divya Koya (Dev) + "ritikav2@illinois.edu", // Ritika Vithani (Director) + "ojaswee2@illinois.edu", // Ojaswee Chaudhary (Director) + ]), JWT_SIGNING_SECRET: getEnv("JWT_SIGNING_SECRET"), JWT_EXPIRATION_TIME: "1 day", diff --git a/src/services/auth/auth-utils.ts b/src/services/auth/auth-utils.ts index b4a3c2e..c71c322 100644 --- a/src/services/auth/auth-utils.ts +++ b/src/services/auth/auth-utils.ts @@ -2,6 +2,7 @@ import { Strategy as GoogleStrategy } from "passport-google-oauth20"; import { Config } from "../../config"; import { Database } from "../../database"; +import { Role } from "./auth-models"; export function createGoogleStrategy(device: string) { return new GoogleStrategy( @@ -16,10 +17,16 @@ export function createGoogleStrategy(device: string) { const userId = `user${profile.id}`; const name = profile.displayName; const email = profile._json.email; + + let roles = []; + + if (Config.AUTH_ADMIN_WHITELIST.has(email ?? "")) { + roles.push(Role.Values.ADMIN); + } Database.ROLES.findOneAndUpdate( { userId: userId }, - { userId, name, email }, + { userId, name, email, roles }, { upsert: true } ) .then(() => cb(null, profile)) From 4adcac348e9bb27175241373fca47d4592d00e27 Mon Sep 17 00:00:00 2001 From: Aydan Pirani Date: Sun, 26 May 2024 00:15:02 -0700 Subject: [PATCH 4/7] Undid config chnage --- src/config.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config.ts b/src/config.ts index 9c2bdf6..3443ab5 100644 --- a/src/config.ts +++ b/src/config.ts @@ -25,9 +25,9 @@ export const Config = { CLIENT_ID: getEnv("OAUTH_GOOGLE_CLIENT_ID"), CLIENT_SECRET: getEnv("OAUTH_GOOGLE_CLIENT_SECRET"), - AUTH_CALLBACK_URI_BASE: "http://localhost:3000/auth/callback/", - // AUTH_CALLBACK_URI_BASE: - // "https://api.reflectionsprojections.org/auth/callback/", + // AUTH_CALLBACK_URI_BASE: "http://localhost:3000/auth/callback/", + AUTH_CALLBACK_URI_BASE: + "https://api.reflectionsprojections.org/auth/callback/", AUTH_ADMIN_WHITELIST: new Set([ "apirani2@illinois.edu", // Aydan Pirani (Dev) From 6f598becf6a601abd778831a52db17079eb3da9f Mon Sep 17 00:00:00 2001 From: Aydan Pirani Date: Sun, 26 May 2024 00:36:16 -0700 Subject: [PATCH 5/7] Finished templates --- src/config.ts | 2 ++ src/services/mail/mail-router.ts | 6 +++--- .../mail/templates/templates-schema.ts | 15 ++++++------- .../mail/templates/templates-subrouter.ts | 21 +++++++++---------- 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/config.ts b/src/config.ts index 3443ab5..3998878 100644 --- a/src/config.ts +++ b/src/config.ts @@ -49,6 +49,8 @@ export const Config = { // QR Scanning QR_HASH_ITERATIONS: 10000, QR_HASH_SECRET: getEnv("QR_HASH_SECRET"), + + MAIL_TEMPLATE_REGEX: /\${{([^{}]+)}}/g, }; export const DeviceRedirects: Record = { diff --git a/src/services/mail/mail-router.ts b/src/services/mail/mail-router.ts index 4bd9b6e..ede8596 100644 --- a/src/services/mail/mail-router.ts +++ b/src/services/mail/mail-router.ts @@ -6,8 +6,8 @@ import { Router } from "express"; const mailRouter = Router(); -mailRouter.use("drafts", draftsSubRouter); -mailRouter.use("lists", listsSubRouter); -mailRouter.use("templates", templatesSubRouter); +mailRouter.use("/drafts", draftsSubRouter); +mailRouter.use("/lists", listsSubRouter); +mailRouter.use("/templates", templatesSubRouter); export default mailRouter; diff --git a/src/services/mail/templates/templates-schema.ts b/src/services/mail/templates/templates-schema.ts index 2f38e4d..f1542c7 100644 --- a/src/services/mail/templates/templates-schema.ts +++ b/src/services/mail/templates/templates-schema.ts @@ -2,11 +2,12 @@ import { Schema } from "mongoose"; import { z } from "zod"; export const TemplateValidator = z.object({ - templateId: z.coerce.string(), - subject: z.coerce.string(), - content: z.coerce.string().email(), - substitutions: z.string().array(), - usages: z.number().min(0).default(0), + templateId: z.string().regex(/^\S*$/, { + message: "Spaces Not Allowed", + }), + subject: z.string(), + content: z.string(), + substitutions: z.string().array().default([]), }); export const TemplateSchema = new Schema({ @@ -27,8 +28,4 @@ export const TemplateSchema = new Schema({ type: [String], required: true, }, - usages: { - type: Number, - default: 0, - }, }); diff --git a/src/services/mail/templates/templates-subrouter.ts b/src/services/mail/templates/templates-subrouter.ts index a289e4a..b578e23 100644 --- a/src/services/mail/templates/templates-subrouter.ts +++ b/src/services/mail/templates/templates-subrouter.ts @@ -5,6 +5,7 @@ import { Database } from "../../../database"; import { StatusCodes } from "http-status-codes"; import { TemplateValidator } from "./templates-schema"; import { z } from "zod"; +import { Config } from "../../../config"; type TemplateData = z.infer; @@ -25,8 +26,12 @@ templatesSubRouter.post( RoleChecker([Role.Values.ADMIN]), async (req, res) => { try { - const templateData = TemplateValidator.parse(req.body); - await Database.TEMPLATES.create(templateData); + + let templateData = TemplateValidator.parse(req.body); + let substitutions = templateData.content.matchAll(Config.MAIL_TEMPLATE_REGEX) + const subVars = Array.from(substitutions, substitutions => substitutions[1]); + + await Database.TEMPLATES.create({...templateData, substitutions: subVars}); return res.sendStatus(StatusCodes.CREATED); } catch (error) { return res.status(StatusCodes.BAD_REQUEST).send(error); @@ -40,15 +45,9 @@ templatesSubRouter.delete( async (req, res) => { try { const templateId = req.params.TEMPLATEID; - const templateData = (await Database.TEMPLATES.findOne({ - templateId: templateId, - })) as TemplateData; - - if (!templateData) { - return res - .status(StatusCodes.BAD_REQUEST) - .json({ error: "NoSuchId" }); - } + await Database.TEMPLATES.findOneAndDelete({templateId: templateId}); + return res.sendStatus(StatusCodes.NO_CONTENT); + } catch (error) { return res.status(StatusCodes.BAD_REQUEST).send(error); } From 89595e48ec6c6948d98b091de18d923b847548e4 Mon Sep 17 00:00:00 2001 From: Aydan Pirani Date: Sun, 26 May 2024 00:45:10 -0700 Subject: [PATCH 6/7] template code --- .../mail/templates/templates-subrouter.ts | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/services/mail/templates/templates-subrouter.ts b/src/services/mail/templates/templates-subrouter.ts index b578e23..1ff7e72 100644 --- a/src/services/mail/templates/templates-subrouter.ts +++ b/src/services/mail/templates/templates-subrouter.ts @@ -26,7 +26,6 @@ templatesSubRouter.post( RoleChecker([Role.Values.ADMIN]), async (req, res) => { try { - let templateData = TemplateValidator.parse(req.body); let substitutions = templateData.content.matchAll(Config.MAIL_TEMPLATE_REGEX) const subVars = Array.from(substitutions, substitutions => substitutions[1]); @@ -39,6 +38,41 @@ templatesSubRouter.post( } ); +templatesSubRouter.put( + "/", + RoleChecker([Role.Values.ADMIN]), + async (req, res) => { + try { + let templateData = TemplateValidator.parse(req.body); + let substitutions = templateData.content.matchAll(Config.MAIL_TEMPLATE_REGEX) + const subVars = Array.from(substitutions, substitutions => substitutions[1]); + + const updateResult = await Database.TEMPLATES.findOneAndUpdate({templateId: templateData.templateId}, {...templateData, substitutions: subVars}); + + if (!updateResult) { + return res.status(StatusCodes.NOT_FOUND).send({error: "NoSuchId"}); + } + + return res.sendStatus(StatusCodes.OK); + } catch (error) { + return res.status(StatusCodes.BAD_REQUEST).send(error); + } + } +); + +templatesSubRouter.get( + "/:TEMPLATEID", + RoleChecker([Role.Values.STAFF]), + async (req, res) => { + const templateId = req.params.TEMPLATEID; + const templateInfo = await Database.TEMPLATES.findOne({templateId: templateId}); + if (!templateInfo) { + return res.status(StatusCodes.NOT_FOUND).send({error: "NoSuchId"}); + } + return res.status(StatusCodes.OK).json(templateInfo?.toObject()); + } +); + templatesSubRouter.delete( "/:TEMPLATEID", RoleChecker([Role.Values.ADMIN]), From dd13539d57fd89467f7ccda42ecc7996eb4cd750 Mon Sep 17 00:00:00 2001 From: Aydan Pirani Date: Sun, 26 May 2024 00:46:33 -0700 Subject: [PATCH 7/7] linting --- src/config.ts | 10 ++-- src/services/auth/auth-utils.ts | 2 +- .../mail/templates/templates-subrouter.ts | 50 +++++++++++++------ 3 files changed, 41 insertions(+), 21 deletions(-) diff --git a/src/config.ts b/src/config.ts index 3998878..e968b3a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -28,12 +28,12 @@ export const Config = { // AUTH_CALLBACK_URI_BASE: "http://localhost:3000/auth/callback/", AUTH_CALLBACK_URI_BASE: "https://api.reflectionsprojections.org/auth/callback/", - + AUTH_ADMIN_WHITELIST: new Set([ - "apirani2@illinois.edu", // Aydan Pirani (Dev) - "divyack2@illinois.edu", // Divya Koya (Dev) - "ritikav2@illinois.edu", // Ritika Vithani (Director) - "ojaswee2@illinois.edu", // Ojaswee Chaudhary (Director) + "apirani2@illinois.edu", // Aydan Pirani (Dev) + "divyack2@illinois.edu", // Divya Koya (Dev) + "ritikav2@illinois.edu", // Ritika Vithani (Director) + "ojaswee2@illinois.edu", // Ojaswee Chaudhary (Director) ]), JWT_SIGNING_SECRET: getEnv("JWT_SIGNING_SECRET"), diff --git a/src/services/auth/auth-utils.ts b/src/services/auth/auth-utils.ts index c71c322..31df201 100644 --- a/src/services/auth/auth-utils.ts +++ b/src/services/auth/auth-utils.ts @@ -17,7 +17,7 @@ export function createGoogleStrategy(device: string) { const userId = `user${profile.id}`; const name = profile.displayName; const email = profile._json.email; - + let roles = []; if (Config.AUTH_ADMIN_WHITELIST.has(email ?? "")) { diff --git a/src/services/mail/templates/templates-subrouter.ts b/src/services/mail/templates/templates-subrouter.ts index 1ff7e72..6333076 100644 --- a/src/services/mail/templates/templates-subrouter.ts +++ b/src/services/mail/templates/templates-subrouter.ts @@ -4,11 +4,8 @@ import { Role } from "../../auth/auth-models"; import { Database } from "../../../database"; import { StatusCodes } from "http-status-codes"; import { TemplateValidator } from "./templates-schema"; -import { z } from "zod"; import { Config } from "../../../config"; -type TemplateData = z.infer; - const templatesSubRouter = Router(); templatesSubRouter.get( @@ -27,10 +24,18 @@ templatesSubRouter.post( async (req, res) => { try { let templateData = TemplateValidator.parse(req.body); - let substitutions = templateData.content.matchAll(Config.MAIL_TEMPLATE_REGEX) - const subVars = Array.from(substitutions, substitutions => substitutions[1]); + let substitutions = templateData.content.matchAll( + Config.MAIL_TEMPLATE_REGEX + ); + const subVars = Array.from( + substitutions, + (substitutions) => substitutions[1] + ); - await Database.TEMPLATES.create({...templateData, substitutions: subVars}); + await Database.TEMPLATES.create({ + ...templateData, + substitutions: subVars, + }); return res.sendStatus(StatusCodes.CREATED); } catch (error) { return res.status(StatusCodes.BAD_REQUEST).send(error); @@ -44,13 +49,23 @@ templatesSubRouter.put( async (req, res) => { try { let templateData = TemplateValidator.parse(req.body); - let substitutions = templateData.content.matchAll(Config.MAIL_TEMPLATE_REGEX) - const subVars = Array.from(substitutions, substitutions => substitutions[1]); + let substitutions = templateData.content.matchAll( + Config.MAIL_TEMPLATE_REGEX + ); + const subVars = Array.from( + substitutions, + (substitutions) => substitutions[1] + ); + + const updateResult = await Database.TEMPLATES.findOneAndUpdate( + { templateId: templateData.templateId }, + { ...templateData, substitutions: subVars } + ); - const updateResult = await Database.TEMPLATES.findOneAndUpdate({templateId: templateData.templateId}, {...templateData, substitutions: subVars}); - if (!updateResult) { - return res.status(StatusCodes.NOT_FOUND).send({error: "NoSuchId"}); + return res + .status(StatusCodes.NOT_FOUND) + .send({ error: "NoSuchId" }); } return res.sendStatus(StatusCodes.OK); @@ -65,9 +80,13 @@ templatesSubRouter.get( RoleChecker([Role.Values.STAFF]), async (req, res) => { const templateId = req.params.TEMPLATEID; - const templateInfo = await Database.TEMPLATES.findOne({templateId: templateId}); + const templateInfo = await Database.TEMPLATES.findOne({ + templateId: templateId, + }); if (!templateInfo) { - return res.status(StatusCodes.NOT_FOUND).send({error: "NoSuchId"}); + return res + .status(StatusCodes.NOT_FOUND) + .send({ error: "NoSuchId" }); } return res.status(StatusCodes.OK).json(templateInfo?.toObject()); } @@ -79,9 +98,10 @@ templatesSubRouter.delete( async (req, res) => { try { const templateId = req.params.TEMPLATEID; - await Database.TEMPLATES.findOneAndDelete({templateId: templateId}); + await Database.TEMPLATES.findOneAndDelete({ + templateId: templateId, + }); return res.sendStatus(StatusCodes.NO_CONTENT); - } catch (error) { return res.status(StatusCodes.BAD_REQUEST).send(error); }