From b1cf1319253afd1984172ba09a8b860ab5764ae0 Mon Sep 17 00:00:00 2001 From: Dorian Niemiec Date: Sun, 8 Sep 2024 05:44:44 +0200 Subject: [PATCH] fix: change MongoDB database to one from .env file, add unsubscribe IDs, and add email validation --- app/api/contact/route.ts | 25 +++++++++++ app/api/newsletter/send/route.ts | 2 +- app/api/newsletter/subscriber/route.ts | 2 +- app/api/subscribe/route.ts | 57 +++++++++++++++++++++++++- package-lock.json | 9 ++++ package.json | 1 + 6 files changed, 92 insertions(+), 4 deletions(-) diff --git a/app/api/contact/route.ts b/app/api/contact/route.ts index 27c419b..880c0a9 100644 --- a/app/api/contact/route.ts +++ b/app/api/contact/route.ts @@ -1,5 +1,7 @@ import { mailOptions, transporter } from "@/lib/nodemailer/nodemailer"; import { NextRequest, NextResponse } from "next/server"; +import dns from "dns/promises"; +import { isEmail } from "validator"; const CONTACT_MESSAGE_FIELDS: Record = { name: "Name", @@ -129,6 +131,29 @@ export async function POST(req: NextRequest) { ); } + // Check email address + if (!isEmail(data.email)) { + return NextResponse.json( + { message: "Invalid email address" }, + { status: 400 } + ); + } + + // Check email host + const emailDomainMatch = data.email.match(/@([^@]+)/); + const emailDomain = emailDomainMatch ? emailDomainMatch[1] : ""; + let isEmailHostValid = false; + try { + const mxRecords = await dns.resolveMx(emailDomain); + if (mxRecords.length > 0) isEmailHostValid = true; + } catch (err) {} + if (!isEmailHostValid) { + return NextResponse.json( + { message: "Email domain is misconfigured" }, + { status: 400 } + ); + } + await transporter.sendMail({ ...mailOptions, ...generateEmailContent(data), diff --git a/app/api/newsletter/send/route.ts b/app/api/newsletter/send/route.ts index 5af6c13..6aed0ca 100644 --- a/app/api/newsletter/send/route.ts +++ b/app/api/newsletter/send/route.ts @@ -31,7 +31,7 @@ export async function POST(req: NextRequest) { const { subject, html } = await req.json(); const client = await clientPromise; - const db = client.db("newsletter"); + const db = client.db(process.env.MONGODB_DB); const collection = db.collection("subscribers"); const subscribers = await collection diff --git a/app/api/newsletter/subscriber/route.ts b/app/api/newsletter/subscriber/route.ts index 3b006ff..704dc41 100644 --- a/app/api/newsletter/subscriber/route.ts +++ b/app/api/newsletter/subscriber/route.ts @@ -16,7 +16,7 @@ export async function GET(req: Request) { const skip = (page - 1) * limit; const client = await clientPromise; - const db = client.db("newsletter"); + const db = client.db(process.env.MONGODB_DB); const collection = db.collection("subscribers"); // Pagination diff --git a/app/api/subscribe/route.ts b/app/api/subscribe/route.ts index c264a41..f4f461c 100644 --- a/app/api/subscribe/route.ts +++ b/app/api/subscribe/route.ts @@ -1,7 +1,33 @@ import { NextRequest, NextResponse } from "next/server"; import clientPromise from "@/lib/db"; +import { Collection } from "mongodb"; +import dns from "dns/promises"; +import { isEmail } from "validator"; export async function POST(req: NextRequest) { + const generateUnsubscribeID = () => { + const chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let result = ""; + for (let i = 0; i < 32; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return result; + }; + + const generateUniqueUnsubscribeID = async (collection: Collection) => { + const id = generateUnsubscribeID(); + const result = await collection + .find({ + unsubscribeId: id + }) + .toArray(); + if (result.length > 0) { + return await generateUniqueUnsubscribeID(collection); + } + return id; + }; + try { const { email, captchaToken } = await req.json(); @@ -33,8 +59,31 @@ export async function POST(req: NextRequest) { ); } + // Check email address + if (!isEmail(email)) { + return NextResponse.json( + { message: "Invalid email address" }, + { status: 400 } + ); + } + + // Check email host + const emailDomainMatch = email.match(/@([^@]+)/); + const emailDomain = emailDomainMatch ? emailDomainMatch[1] : ""; + let isEmailHostValid = false; + try { + const mxRecords = await dns.resolveMx(emailDomain); + if (mxRecords.length > 0) isEmailHostValid = true; + } catch (err) {} + if (!isEmailHostValid) { + return NextResponse.json( + { message: "Email domain is misconfigured" }, + { status: 400 } + ); + } + const client = await clientPromise; - const db = client.db("newsletter"); + const db = client.db(process.env.MONGODB_DB); const collection = db.collection("subscribers"); // checking if email alr exists @@ -47,7 +96,11 @@ export async function POST(req: NextRequest) { } // saves the email in the db - await collection.insertOne({ email, subscribedAt: new Date() }); + await collection.insertOne({ + email, + subscribedAt: new Date(), + unsubscribeId: await generateUniqueUnsubscribeID(collection) + }); return NextResponse.json( { message: "Successfully subscribed!" }, diff --git a/package-lock.json b/package-lock.json index f440d41..285b453 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,6 +61,7 @@ "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", "uploadthing": "^6.12.0", + "validator": "^13.12.0", "zod": "^3.23.8" }, "devDependencies": { @@ -27913,6 +27914,14 @@ "builtins": "^1.0.3" } }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", diff --git a/package.json b/package.json index d9a88dc..8d0859c 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", "uploadthing": "^6.12.0", + "validator": "^13.12.0", "zod": "^3.23.8" }, "devDependencies": {