mirror of
https://github.com/detleph/server.git
synced 2026-09-07 23:44:40 +02:00
Merge pull request #33 from detleph/feature-email-verification
Feature email verification Finished ITS FINALY DONE
This commit is contained in:
@@ -19,6 +19,7 @@ RUN npm i
|
|||||||
ADD prisma/ /app/prisma/
|
ADD prisma/ /app/prisma/
|
||||||
ADD scripts/ /app/scripts/
|
ADD scripts/ /app/scripts/
|
||||||
ADD src/ /app/src/
|
ADD src/ /app/src/
|
||||||
|
ADD resources/ /app/resources
|
||||||
COPY tsconfig.json /app/
|
COPY tsconfig.json /app/
|
||||||
|
|
||||||
# Generate the prisma client
|
# Generate the prisma client
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
D_SERVICES="mail postgres redis"
|
D_SERVICES="mail postgres redis"
|
||||||
|
|
||||||
DATABASE_PASSWORD=server COMPOSE_PROJECT_NAME=detleph_server docker-compose up -d $D_SERVICES
|
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 \
|
--name detleph_server_dev \
|
||||||
--mount type=bind,source="$(pwd)",target=/app \
|
--mount type=bind,source="$(pwd)",target=/app \
|
||||||
--network detleph_server_default \
|
--network detleph_server_default \
|
||||||
-p $D_PORT:$D_PORT -e PORT=$D_PORT \
|
|
||||||
-e DATABASE_PASSWORD=server \
|
-e DATABASE_PASSWORD=server \
|
||||||
-e DATABASE_URL="postgresql://server:server@postgres:5432/management?schema=public" \
|
-e DATABASE_URL="postgresql://server:server@postgres:5432/management?schema=public" \
|
||||||
-e NODE_ENV="development" \
|
-e NODE_ENV="development" \
|
||||||
--entrypoint "/app/scripts/docker-entrypoint.dev.sh" \
|
--entrypoint "/app/scripts/docker-entrypoint.dev.sh" \
|
||||||
node
|
node
|
||||||
fi
|
fi
|
||||||
|
|
||||||
COMPOSE_PROJECT_NAME=detleph_server docker-compose stop
|
COMPOSE_PROJECT_NAME=detleph_server docker-compose stop
|
||||||
|
|||||||
@@ -20,15 +20,20 @@
|
|||||||
"homepage": "https://github.com/detleph/server#readme",
|
"homepage": "https://github.com/detleph/server#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "^3.3.0",
|
"@prisma/client": "^3.3.0",
|
||||||
|
"@types/handlebars": "^4.1.0",
|
||||||
"@types/jsonwebtoken": "^8.5.5",
|
"@types/jsonwebtoken": "^8.5.5",
|
||||||
|
"@types/mjml": "^4.7.0",
|
||||||
"@types/node": "^16.10.3",
|
"@types/node": "^16.10.3",
|
||||||
"@types/nodemailer": "^6.4.4",
|
"@types/nodemailer": "^6.4.4",
|
||||||
"@types/redis": "^2.8.32",
|
"@types/redis": "^2.8.32",
|
||||||
"argon2": "^0.28.2",
|
"argon2": "^0.28.2",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
|
"handlebars": "^4.7.7",
|
||||||
"express-async-errors": "^3.1.1",
|
"express-async-errors": "^3.1.1",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
|
"mjml": "^4.11.0",
|
||||||
|
"nanoid": "^3.3.3",
|
||||||
"nodemailer": "^6.7.0",
|
"nodemailer": "^6.7.0",
|
||||||
"redis": "^3.1.2",
|
"redis": "^3.1.2",
|
||||||
"winston": "^3.7.2"
|
"winston": "^3.7.2"
|
||||||
|
|||||||
@@ -134,9 +134,9 @@ model Group {
|
|||||||
name String
|
name String
|
||||||
user_limit Int @default(40)
|
user_limit Int @default(40)
|
||||||
level Int
|
level Int
|
||||||
|
|
||||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||||
organisationId Int
|
organisationId Int
|
||||||
|
|
||||||
participants Participant[]
|
participants Participant[]
|
||||||
link Link?
|
link Link?
|
||||||
admins Admin[]
|
admins Admin[]
|
||||||
|
|||||||
@@ -84,19 +84,16 @@ export const addEvent = async (req: Request, res: Response) => {
|
|||||||
if (req.auth?.permission_level !== "ELEVATED") {
|
if (req.auth?.permission_level !== "ELEVATED") {
|
||||||
res.status(403).json(createInsufficientPermissionsError());
|
res.status(403).json(createInsufficientPermissionsError());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
typeof req.body.name !== "string" ||
|
typeof req.body.name !== "string" ||
|
||||||
typeof req.body.date !== "string" ||
|
typeof req.body.date !== "string" ||
|
||||||
typeof req.body.description !== "string"
|
typeof req.body.description !== "string"
|
||||||
) {
|
) {
|
||||||
return res.status(400).json(
|
generateInvalidBodyError({
|
||||||
generateInvalidBodyError({
|
name: DataType.STRING,
|
||||||
name: DataType.STRING,
|
date: DataType.DATETIME,
|
||||||
date: DataType.DATETIME,
|
description: DataType.STRING,
|
||||||
description: DataType.STRING,
|
});
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Check if date is valid
|
//TODO: Check if date is valid
|
||||||
|
|||||||
@@ -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: "[email protected]",
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -6,6 +6,6 @@ export default function debugLogger(req: Request, res: Response, next: NextFunct
|
|||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === "development") {
|
||||||
logger.debug(`Request to: ${req.url}`);
|
logger.debug(`Request to: ${req.url}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|||||||
+54
-35
@@ -1,44 +1,51 @@
|
|||||||
|
import { writeFile } from "fs";
|
||||||
|
import Handlebars, { template } from "handlebars";
|
||||||
|
import { mailClient } from "./redis";
|
||||||
import nodemailer from "nodemailer";
|
import nodemailer from "nodemailer";
|
||||||
import SMTPTransport from "nodemailer/lib/smtp-transport";
|
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(
|
export let mailAccount = { user: process.env.MAILUSER + "@mail." + process.env.DOMAIN, pass: process.env.MAILPASSWORD };
|
||||||
new SMTPTransport({
|
|
||||||
host: "localhost",
|
|
||||||
port: 587,
|
|
||||||
secure: false,
|
|
||||||
auth: {
|
|
||||||
user: mailAccount.user,
|
|
||||||
pass: mailAccount.pass,
|
|
||||||
},
|
|
||||||
tls: {
|
|
||||||
rejectUnauthorized: false,
|
|
||||||
secureProtocol: "TLSv1_method",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (process.env.DEV == "true") {
|
let transporter =
|
||||||
(async () => {
|
process.env.DEV == "true" || process.env.DOMAIN == undefined
|
||||||
mailAccount = await nodemailer.createTestAccount();
|
? (async () => {
|
||||||
if (process.env.NODE_ENV != "test") {
|
mailAccount = await nodemailer.createTestAccount();
|
||||||
console.log(mailAccount);
|
if (process.env.NODE_ENV != "test") {
|
||||||
}
|
console.log(mailAccount);
|
||||||
transporter = nodemailer.createTransport({
|
}
|
||||||
host: "smtp.ethereal.email",
|
return nodemailer.createTransport({
|
||||||
port: 587,
|
host: "smtp.ethereal.email",
|
||||||
secure: false, // true for 465, false for other ports
|
port: 587,
|
||||||
auth: {
|
secure: false, // true for 465, false for other ports
|
||||||
user: mailAccount.user, // generated ethereal user
|
auth: {
|
||||||
pass: mailAccount.pass, // generated ethereal password
|
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) => {
|
const sendMail = async (from: string, to: string, subject: string, text?: string, html?: string) => {
|
||||||
return await transporter.sendMail({
|
return await (
|
||||||
|
await transporter
|
||||||
|
).sendMail({
|
||||||
from: from,
|
from: from,
|
||||||
to: to,
|
to: to,
|
||||||
subject: subject,
|
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 };
|
||||||
|
|||||||
@@ -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 };
|
||||||
@@ -3,6 +3,7 @@ import { promisify } from "util";
|
|||||||
|
|
||||||
const REDIS_INDICES = {
|
const REDIS_INDICES = {
|
||||||
auth: 0,
|
auth: 0,
|
||||||
|
mail: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const REDIS_HOST = process.env.REDIS_HOST || "redis";
|
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);
|
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 authClient = createAsyncClient(auth);
|
||||||
|
export const mailClient = createAsyncClient(mail);
|
||||||
|
|||||||
Reference in New Issue
Block a user