diff --git a/Dockerfile b/Dockerfile index f97ac4d..d4a542d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,6 +19,7 @@ RUN npm i ADD prisma/ /app/prisma/ ADD scripts/ /app/scripts/ ADD src/ /app/src/ +ADD resources/ /app/resources COPY tsconfig.json /app/ # Generate the prisma client diff --git a/dev.sh b/dev.sh index 258ad38..000ccf3 100755 --- a/dev.sh +++ b/dev.sh @@ -1,5 +1,4 @@ #!/bin/bash - D_SERVICES="mail postgres redis" DATABASE_PASSWORD=server COMPOSE_PROJECT_NAME=detleph_server docker-compose up -d $D_SERVICES @@ -49,12 +48,10 @@ if [ "$RECREATE" = true ]; then --name detleph_server_dev \ --mount type=bind,source="$(pwd)",target=/app \ --network detleph_server_default \ - -p $D_PORT:$D_PORT -e PORT=$D_PORT \ -e DATABASE_PASSWORD=server \ -e DATABASE_URL="postgresql://server:server@postgres:5432/management?schema=public" \ -e NODE_ENV="development" \ --entrypoint "/app/scripts/docker-entrypoint.dev.sh" \ node fi - COMPOSE_PROJECT_NAME=detleph_server docker-compose stop diff --git a/package.json b/package.json index e51d789..a97963a 100644 --- a/package.json +++ b/package.json @@ -20,15 +20,20 @@ "homepage": "https://github.com/detleph/server#readme", "dependencies": { "@prisma/client": "^3.3.0", + "@types/handlebars": "^4.1.0", "@types/jsonwebtoken": "^8.5.5", + "@types/mjml": "^4.7.0", "@types/node": "^16.10.3", "@types/nodemailer": "^6.4.4", "@types/redis": "^2.8.32", "argon2": "^0.28.2", "dotenv": "^10.0.0", "express": "^4.17.1", + "handlebars": "^4.7.7", "express-async-errors": "^3.1.1", "jsonwebtoken": "^8.5.1", + "mjml": "^4.11.0", + "nanoid": "^3.3.3", "nodemailer": "^6.7.0", "redis": "^3.1.2", "winston": "^3.7.2" diff --git a/prisma/schema.prisma b/prisma/schema.prisma index c316eca..67e12ad 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -134,9 +134,9 @@ model Group { name String user_limit Int @default(40) level Int - organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade) organisationId Int + participants Participant[] link Link? admins Admin[] diff --git a/src/Controllers/event.controller.ts b/src/Controllers/event.controller.ts index 6667aa6..99378fa 100644 --- a/src/Controllers/event.controller.ts +++ b/src/Controllers/event.controller.ts @@ -84,19 +84,16 @@ export const addEvent = async (req: Request, res: Response) => { if (req.auth?.permission_level !== "ELEVATED") { res.status(403).json(createInsufficientPermissionsError()); } - if ( typeof req.body.name !== "string" || typeof req.body.date !== "string" || typeof req.body.description !== "string" ) { - return res.status(400).json( - generateInvalidBodyError({ - name: DataType.STRING, - date: DataType.DATETIME, - description: DataType.STRING, - }) - ); + generateInvalidBodyError({ + name: DataType.STRING, + date: DataType.DATETIME, + description: DataType.STRING, + }); } //TODO: Check if date is valid diff --git a/src/Controllers/user_auth.controller.ts b/src/Controllers/user_auth.controller.ts new file mode 100644 index 0000000..cd8c94a --- /dev/null +++ b/src/Controllers/user_auth.controller.ts @@ -0,0 +1,56 @@ +import { Request, Response } from "express"; +import prisma from "../lib/prisma"; +import { mailClient } from "../lib/redis"; +import { nanoid } from "nanoid"; +import { verificationMail } from "../lib/mail"; + +export const register = async (req: Request, res: Response) => { + //TODO: Implemnt user endpoint and use following code to send verification mail + + const user = { + //Supposed to come from database + id: "10", + email: "test@test.com", + }; + + const usid = nanoid(); + + (await mailClient).set(usid, user.id); + + verificationMail(user.email, "eventname", usid); + + //Send status code +}; + +export const verifyEmail = async (req: Request, res: Response) => { + const { code } = req.body || {}; + + if (!(typeof code === "string")) { + return res.status(400).json({ + type: "error", + payload: { + message: "Invalid Request parameter", + }, + }); + } + + const acc = await mailClient.get(code); + + if (!acc) { + return res.status(404).json({ + type: "error", + payload: { + message: "Invalid token. It might be expired", + }, + }); + } + + prisma.participant.update({ + where: { + id: parseInt(acc), + }, + data: { + verified: true, + }, + }); +}; diff --git a/src/Middleware/debug/logger.ts b/src/Middleware/debug/logger.ts index 64310c1..a1c63bd 100644 --- a/src/Middleware/debug/logger.ts +++ b/src/Middleware/debug/logger.ts @@ -6,6 +6,6 @@ export default function debugLogger(req: Request, res: Response, next: NextFunct if (process.env.NODE_ENV === "development") { logger.debug(`Request to: ${req.url}`); } - + next(); } diff --git a/src/lib/mail.ts b/src/lib/mail.ts index de4a30c..4572b1e 100644 --- a/src/lib/mail.ts +++ b/src/lib/mail.ts @@ -1,44 +1,51 @@ +import { writeFile } from "fs"; +import Handlebars, { template } from "handlebars"; +import { mailClient } from "./redis"; import nodemailer from "nodemailer"; import SMTPTransport from "nodemailer/lib/smtp-transport"; -let mailAccount = { user: process.env.MAILUSER + "@mail." + process.env.DOMAIN, pass: process.env.MAILPASSWORD }; +import mjml from "./mjml"; +import { randomUUID } from "crypto"; -let transporter = nodemailer.createTransport( - new SMTPTransport({ - host: "localhost", - port: 587, - secure: false, - auth: { - user: mailAccount.user, - pass: mailAccount.pass, - }, - tls: { - rejectUnauthorized: false, - secureProtocol: "TLSv1_method", - }, - }) -); +export let mailAccount = { user: process.env.MAILUSER + "@mail." + process.env.DOMAIN, pass: process.env.MAILPASSWORD }; -if (process.env.DEV == "true") { - (async () => { - mailAccount = await nodemailer.createTestAccount(); - if (process.env.NODE_ENV != "test") { - console.log(mailAccount); - } - transporter = nodemailer.createTransport({ - host: "smtp.ethereal.email", - port: 587, - secure: false, // true for 465, false for other ports - auth: { - user: mailAccount.user, // generated ethereal user - pass: mailAccount.pass, // generated ethereal password - }, - }); - })(); -} +let transporter = + process.env.DEV == "true" || process.env.DOMAIN == undefined + ? (async () => { + mailAccount = await nodemailer.createTestAccount(); + if (process.env.NODE_ENV != "test") { + console.log(mailAccount); + } + return nodemailer.createTransport({ + host: "smtp.ethereal.email", + port: 587, + secure: false, // true for 465, false for other ports + auth: { + user: mailAccount.user, // generated ethereal user + pass: mailAccount.pass, // generated ethereal password + }, + }); + })() + : nodemailer.createTransport( + new SMTPTransport({ + host: "localhost", + port: 587, + secure: false, + auth: { + user: mailAccount.user, + pass: mailAccount.pass, + }, + tls: { + rejectUnauthorized: false, + secureProtocol: "TLSv1_method", + }, + }) + ); const sendMail = async (from: string, to: string, subject: string, text?: string, html?: string) => { - return await transporter.sendMail({ + return await ( + await transporter + ).sendMail({ from: from, to: to, subject: subject, @@ -47,4 +54,16 @@ const sendMail = async (from: string, to: string, subject: string, text?: string }); }; -export default { sendMail, mailAccount }; +export const verificationMail = async (to: string, eventName: string, verificationLink: string) => { + const raw = mjml.getTemplate("emailVerification"); + + //TODO: Replace other handlebars with final values + const message = Handlebars.compile(raw); + + const data = { eventName, verificationLink }; + const compiled = message(data); + + sendMail(mailAccount.user, to, "Verify Email", undefined, compiled); +}; + +export default { sendMail }; diff --git a/src/lib/mjml.ts b/src/lib/mjml.ts new file mode 100644 index 0000000..e148cf2 --- /dev/null +++ b/src/lib/mjml.ts @@ -0,0 +1,33 @@ +import fs from "fs"; +import path from "path"; +import mjml from "mjml"; + +const template_folder = "/app/resources/email/templates/"; + +const compileTemplates = () => { + console.log("Compiling templates: "); + + fs.readdir(template_folder, (err, files) => { + if (err) console.log(err); + + files.forEach((file) => { + if (path.extname(file) !== ".mjml") return; + + let content = fs.readFileSync(template_folder + file); + let mjmlres = mjml(content.toString()); + + let hbs = template_folder + file.replace(".mjml", ".hbs"); + fs.writeFileSync(hbs, mjmlres.html); + }); + }); +}; + +compileTemplates(); // This will compile all templates when this file is first included + +const getTemplate = (name: string): string => { + let file = ""; + file = fs.readFileSync(path.join(template_folder, name + ".hbs")).toString(); + return file; +}; + +export default { getTemplate }; diff --git a/src/lib/redis.ts b/src/lib/redis.ts index afe01f5..b685446 100644 --- a/src/lib/redis.ts +++ b/src/lib/redis.ts @@ -3,6 +3,7 @@ import { promisify } from "util"; const REDIS_INDICES = { auth: 0, + mail: 1, }; const REDIS_HOST = process.env.REDIS_HOST || "redis"; @@ -24,4 +25,13 @@ const auth = redis.createClient({ host: REDIS_HOST, port: REDIS_PORT }); auth.select(REDIS_INDICES.auth); +// ----------------- // +// Redis mail client // +// ----------------- // + +const mail = redis.createClient({ host: REDIS_HOST, port: REDIS_PORT }); + +mail.select(REDIS_INDICES.mail); + export const authClient = createAsyncClient(auth); +export const mailClient = createAsyncClient(mail);