From 5a36d10f0e8cc7466f3392484bb740582f21e312 Mon Sep 17 00:00:00 2001 From: Colin Kallemein Date: Sun, 27 Oct 2024 21:30:33 +0100 Subject: [PATCH] Added reset password function + basic mail layout --- .env.example | 8 +++++++- package-lock.json | 10 ++++++++++ package.json | 1 + src/services/userService.ts | 30 ++++++++++++++++++++++++++++++ src/utilities/config.ts | 5 +++++ src/utilities/http.ts | 26 +++++++++++++++++++++++++- src/utilities/zodTypes.ts | 8 ++++++++ 7 files changed, 86 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 6e7d4f1..563b9c8 100644 --- a/.env.example +++ b/.env.example @@ -11,4 +11,10 @@ ALLOW_DIAGONAL_MOVEMENT=false # Default character create values DEFAULT_CHARACTER_ZONE="0" DEFAULT_CHARACTER_POS_X="0" -DEFAULT_CHARACTER_POS_Y="0" \ No newline at end of file +DEFAULT_CHARACTER_POS_Y="0" + +# SMTP configuration +SMTP_HOST="my.directonline.io" +SMTP_PORT="587" +SMTP_USER="no-reply@sylvan.quest" +SMTP_PASSWORD="Z%kI*1xe67WuGg" \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 42cbff6..e819f41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "express": "^4.19.2", "ioredis": "^5.4.1", "jsonwebtoken": "^9.0.2", + "nodemailer": "^6.9.15", "pino": "^9.3.2", "prisma": "^5.17.0", "sharp": "^0.33.4", @@ -1955,6 +1956,15 @@ "node-gyp-build-optional-packages-test": "build-test.js" } }, + "node_modules/nodemailer": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.15.tgz", + "integrity": "sha512-AHf04ySLC6CIfuRtRiEYtGEXgRfa6INgWGluDhnxTZhHSKvrBu7lc1VVchQ0d8nPc4cFaZoPq8vkyNoZr0TpGQ==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nodemon": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", diff --git a/package.json b/package.json index 1a3bbef..d6b5203 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "express": "^4.19.2", "ioredis": "^5.4.1", "jsonwebtoken": "^9.0.2", + "nodemailer": "^6.9.15", "pino": "^9.3.2", "prisma": "^5.17.0", "sharp": "^0.33.4", diff --git a/src/services/userService.ts b/src/services/userService.ts index 285d08d..92e9644 100644 --- a/src/services/userService.ts +++ b/src/services/userService.ts @@ -2,6 +2,7 @@ import bcrypt from 'bcryptjs' import UserRepository from '../repositories/userRepository' import prisma from '../utilities/prisma' import { User } from '@prisma/client' +import config from '../utilities/config' /** * User service @@ -53,6 +54,35 @@ class UserService { } }) } + + /** + * Reset password + * @param email + */ + async resetPassword(email: string): Promise { + const nodemailer = require("nodemailer"); + + const transporter = nodemailer.createTransport({ + host: config.SMTP_HOST, + port: config.SMTP_PORT, + secure: false, + auth: { + user: config.SMTP_USER, + pass: config.SMTP_PASSWORD, + }, + }); + + const info = await transporter.sendMail({ + from: config.SMTP_USER, + to: email, + subject: "Reset your password", + text: "A password reset has been requested, reset your password here: ", // Plain text body + html: "

A password reset has been requested, reset your password here:

", // Html body + }); + console.log("Message sent: %s", info.messageId); + + return info.messageId + } } export default UserService diff --git a/src/utilities/config.ts b/src/utilities/config.ts index 8424179..f8c7be8 100644 --- a/src/utilities/config.ts +++ b/src/utilities/config.ts @@ -14,6 +14,11 @@ class config { static DEFAULT_CHARACTER_ZONE: number = parseInt(process.env.DEFAULT_CHARACTER_ZONE || '1') static DEFAULT_CHARACTER_X: number = parseInt(process.env.DEFAULT_CHARACTER_POS_X || '0') static DEFAULT_CHARACTER_Y: number = parseInt(process.env.DEFAULT_CHARACTER_POS_Y || '0') + + static SMTP_HOST: string = process.env.SMTP_HOST || 'my.directonline.io' + static SMTP_PORT: number = process.env.SMTP_PORT ? parseInt(process.env.SMTP_PORT) : 587 + static SMTP_USER: string = process.env.SMTP_USER || 'no-reply@sylvan.quest' + static SMTP_PASSWORD: string = process.env.SMTP_PASSWORD || 'password' } export default config diff --git a/src/utilities/http.ts b/src/utilities/http.ts index 52cbd63..45b8f69 100644 --- a/src/utilities/http.ts +++ b/src/utilities/http.ts @@ -2,7 +2,7 @@ import { Application, Request, Response } from 'express' import UserService from '../services/userService' import jwt from 'jsonwebtoken' import config from './config' -import { loginAccountSchema, registerAccountSchema } from './zodTypes' +import { loginAccountSchema, registerAccountSchema, resetPasswordSchema } from './zodTypes' import fs from 'fs' import { httpLogger } from './logger' import { getPublicPath } from './storage' @@ -58,6 +58,30 @@ async function addHttpRoutes(app: Application) { return res.status(400).json({ message: 'Failed to register user' }) }) + /** + * Register + * @param req + * @param res + */ + app.post('/reset-password', async (req: Request, res: Response) => { + const { email } = req.body + + try { + resetPasswordSchema.parse({ email }) + } catch (error: any) { + return res.status(400).json({ message: error.errors[0]?.message }) + } + + const userService = new UserService() + const user = await userService.resetPassword( email ) + + if (user) { + return res.status(200).json({ message: 'Email has been sent' }) + } + + return res.status(400).json({ message: 'Failed to send password reset request' }) + }) + /** * Get all tiles from a zone as an array of ids * @param req diff --git a/src/utilities/zodTypes.ts b/src/utilities/zodTypes.ts index 9c9489e..e6831f4 100644 --- a/src/utilities/zodTypes.ts +++ b/src/utilities/zodTypes.ts @@ -33,6 +33,14 @@ export const registerAccountSchema = z.object({ .max(255) }) +export const resetPasswordSchema = z.object({ + email: z + .string() + .min(3, { message: 'Email must be at least 3 characters long' }) + .max(255, { message: 'Email must be at most 255 characters long' }) + .regex(/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/, { message: 'Email must be valid' }) +}) + export const ZCharacterCreate = z.object({ name: z .string()