diff --git a/server/.gitignore b/server/.gitignore index bf898501..65d04bd1 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -38,3 +38,4 @@ pnpm-debug.log* *.sw? # Mac +.vercel diff --git a/server/public/resetPasswordTemplate.html b/server/public/resetPasswordTemplate.html new file mode 100644 index 00000000..3478d6c1 --- /dev/null +++ b/server/public/resetPasswordTemplate.html @@ -0,0 +1,68 @@ + + + + + + + Reset Your Password + + + +
+

Hello, {{name}}!

+

We received a request to reset your password. You can reset your password by clicking the button below:

+ Reset Password +

This link is valid for only 1 hour. Please make sure to use it before it expires.

+

If you did not request a password reset, please ignore this email or contact our support team immediately.

+ +
+ + diff --git a/server/src/infrastructure/database/repositories/OtpRepository.ts b/server/src/infrastructure/repositories/OtpRepository.ts similarity index 72% rename from server/src/infrastructure/database/repositories/OtpRepository.ts rename to server/src/infrastructure/repositories/OtpRepository.ts index 4d62bff6..d417b180 100644 --- a/server/src/infrastructure/database/repositories/OtpRepository.ts +++ b/server/src/infrastructure/repositories/OtpRepository.ts @@ -1,6 +1,6 @@ -import IOtp from "../../../domain/entities/IOtp"; -import IOtpRepository from "../../../interface/repositories/IOtpRepository"; -import OtpModel from "../models/OtpModel"; +import IOtp from "../../domain/entities/IOtp"; +import IOtpRepository from "../../interface/repositories/IOtpRepository"; +import OtpModel from "../database/models/OtpModel"; export default class OtpRepository implements IOtpRepository { model = OtpModel; diff --git a/server/src/infrastructure/database/repositories/PatientRepository.ts b/server/src/infrastructure/repositories/PatientRepository.ts similarity index 83% rename from server/src/infrastructure/database/repositories/PatientRepository.ts rename to server/src/infrastructure/repositories/PatientRepository.ts index 3e279859..e1cee381 100644 --- a/server/src/infrastructure/database/repositories/PatientRepository.ts +++ b/server/src/infrastructure/repositories/PatientRepository.ts @@ -1,7 +1,7 @@ -import { IPatient } from "../../../domain/entities/Patient"; -import IPatientRepository from "../../../interface/repositories/IPatientRepository"; -import { isValidObjectId } from "../isValidObjId"; -import PatientModel from "../models/PatientModel"; +import { IPatient } from "../../domain/entities/Patient"; +import IPatientRepository from "../../interface/repositories/IPatientRepository"; +import { isValidObjectId } from "../database/isValidObjId"; +import PatientModel from "../database/models/PatientModel"; export default class PatientRepository implements IPatientRepository { model = PatientModel; diff --git a/server/src/infrastructure/services/EmailService.ts b/server/src/infrastructure/services/EmailService.ts index 78333646..ecd77ee7 100644 --- a/server/src/infrastructure/services/EmailService.ts +++ b/server/src/infrastructure/services/EmailService.ts @@ -1,35 +1,60 @@ import IEmailService from "../../interface/services/IEmailService"; import nodemailer from "nodemailer"; -import {promisify} from 'util' -import fs from 'fs' -import path from 'path' - +import { promisify } from "util"; +import fs from "fs"; +import path from "path"; const readFileAsync = promisify(fs.readFile); -export default class EmailService implements IEmailService{ - - async sendOtp(email: string, name: string, otp: number): Promise { - let htmlTemplate = await readFileAsync(path.join(__dirname, '../../../public/otpEmailTemplate.html'), 'utf-8'); - - htmlTemplate = htmlTemplate.replace('{{name}}', name); - htmlTemplate = htmlTemplate.replace('{{otp}}', otp.toString()); - - const transporter = nodemailer.createTransport({ - service: 'gmail', - auth: { - user: process.env.SENDER_EMAIL, - pass: process.env.NODEMAILER_PASSKEY, - }, - }); - - await transporter.sendMail({ - from: process.env.SENDER_MAIL, - to: email, - subject: 'Your OTP for Verification', - html: htmlTemplate, - }); - - } - +export default class EmailService implements IEmailService { + async sendOtp(email: string, name: string, otp: number): Promise { + let htmlTemplate = await readFileAsync(path.join(__dirname, "../../../public/otpEmailTemplate.html"), "utf-8"); + + htmlTemplate = htmlTemplate.replace("{{name}}", name); + htmlTemplate = htmlTemplate.replace("{{otp}}", otp.toString()); + + const transporter = nodemailer.createTransport({ + service: "gmail", + auth: { + user: process.env.SENDER_EMAIL, + pass: process.env.NODEMAILER_PASSKEY, + }, + }); + + const id = await transporter.sendMail({ + from: process.env.SENDER_MAIL, + to: email, + subject: "No Reply Mail: Otp Verification", + html: htmlTemplate, + }); + + console.log(id.messageId); + + } + + async sendResetMail(email: string, name: string, resetLink: string): Promise { + let htmlTemplate = await readFileAsync( + path.join(__dirname, "../../../public/resetPasswordTemplate.html"), + "utf-8" + ); + + htmlTemplate = htmlTemplate.replace("{{name}}", name); + htmlTemplate = htmlTemplate.replace("{{resetLink}}", resetLink); + + const transporter = nodemailer.createTransport({ + service: "gmail", + auth: { + user: process.env.SENDER_EMAIL, + pass: process.env.NODEMAILER_PASSKEY, + }, + }); + + const mail = await transporter.sendMail({ + from: process.env.SENDER_MAIL, + to: email, + subject: "No Reply Mail: Password Reset", + html: htmlTemplate, + }); + + }; } diff --git a/server/src/interface/services/IEmailService.ts b/server/src/interface/services/IEmailService.ts index 44e4c904..b942ef54 100644 --- a/server/src/interface/services/IEmailService.ts +++ b/server/src/interface/services/IEmailService.ts @@ -1,3 +1,4 @@ export default interface IEmailService { sendOtp(email: string, name: string, otp: number): Promise; + sendResetMail(email:string,name:string,resetLink:string):Promise; } diff --git a/server/src/presentation/controllers/PatientController.ts b/server/src/presentation/controllers/PatientController.ts index 239eeb96..d53ecc79 100644 --- a/server/src/presentation/controllers/PatientController.ts +++ b/server/src/presentation/controllers/PatientController.ts @@ -62,7 +62,7 @@ export default class PatientController { const { email } = req.body; if (!email) return res.status(400).json({ message: "Email is required" }); await this.authPatientUseCase.resendOtp(email); - res.status(200).json({message:"Otp Sended to the mail Address"}); + res.status(200).json({ message: "Otp Sended to the mail Address" }); } catch (error: any) { if (error.message === "Patient Not Found") { return res.status(422).json({ message: "Invalid Credentials" }); @@ -93,6 +93,16 @@ export default class PatientController { } } + async resetPassword(req: Request, res: Response, next: NextFunction) { + try { + const { email } = req.body; + if (!email) return res.status(400).json({ message: "Email is Required" }); + await this.authPatientUseCase.sendForgetPasswordMail(email); + } catch (error: any) { + next(error); + } + } + async refreshAccessToken(req: Request, res: Response, next: NextFunction) { try { const cookies = req.cookies; diff --git a/server/src/presentation/middlewares/errorHandler.ts b/server/src/presentation/middlewares/errorHandler.ts index 11478fee..bdfd398f 100644 --- a/server/src/presentation/middlewares/errorHandler.ts +++ b/server/src/presentation/middlewares/errorHandler.ts @@ -16,7 +16,7 @@ export const errorHandler = (err: any, req: Request, res: Response, next: NextFu return res.status(401).json({ message: err.message }); } else if (err.message === "Patient is blocked") { return res.status(403).json({ message: err.message }); - }else if (err.message==="Patient not found"){ + }else if (err.message==="Patient not Found"){ return res.status(404).json({message:err.message}) }else if (err.message===' getaddrinfo ENOTFOUND smtp.gmail.com'){ return res.status(500).json({message:"We are Having Issue with Email Service"}) diff --git a/server/src/presentation/routers/patient/AuthPatientRoutes.ts b/server/src/presentation/routers/patient/AuthPatientRoutes.ts index 581520a8..724ba465 100644 --- a/server/src/presentation/routers/patient/AuthPatientRoutes.ts +++ b/server/src/presentation/routers/patient/AuthPatientRoutes.ts @@ -1,28 +1,42 @@ import express from "express"; -import PatientRepository from "../../../infrastructure/database/repositories/PatientRepository"; +import PatientRepository from "../../../infrastructure/repositories/PatientRepository"; import PasswordService from "../../../infrastructure/services/PasswordService"; import RegisterPatientUseCase from "../../../use_case/patient/RegisterPatientUseCase"; import PatientController from "../../controllers/PatientController"; import AuthPatientUseCase from "../../../use_case/patient/AuthPatientUseCase"; import EmailService from "../../../infrastructure/services/EmailService"; -import OtpRepository from "../../../infrastructure/database/repositories/OtpRepository"; +import OtpRepository from "../../../infrastructure/repositories/OtpRepository"; import TokenService from "../../../infrastructure/services/TokenService"; -import PatientAuthMiddleware from "../../middlewares/PatientAuthMiddleware"; +import PatientAuthMiddleware from "../../middlewares/PatientAuthMiddleware"; -const route = express(); +const route = express.Router(); +// Services and Repositories const emailService = new EmailService(); const tokenService = new TokenService(); const otpRepository = new OtpRepository(); const passwordService = new PasswordService(); const patientRepository = new PatientRepository(); + +// Use Cases const registerPatientUseCase = new RegisterPatientUseCase(patientRepository, passwordService); -const authPatientUseCase = new AuthPatientUseCase(patientRepository, passwordService, emailService, otpRepository, tokenService); +const authPatientUseCase = new AuthPatientUseCase( + patientRepository, + passwordService, + emailService, + otpRepository, + tokenService +); + +// Controllers const patientController = new PatientController(registerPatientUseCase, authPatientUseCase); -const patientAuthMiddleWare = new PatientAuthMiddleware (tokenService); -route.post("/", (req, res, next) => { - patientController.register(req, res, next); +// Middleware +const patientAuthMiddleware = new PatientAuthMiddleware(tokenService); + + +route.post("/register", (req, res, next) => { + patientController.register(req, res, next); }); route.post("/login", (req, res, next) => { patientController.login(req, res, next); @@ -33,11 +47,14 @@ route.post("/resend-otp", (req, res, next) => { route.post("/otp-verification", (req, res, next) => { patientController.validateOtp(req, res, next); }); -route.get("/refresh",(req,res,next)=>{ - patientController.refreshAccessToken(req,res,next); +route.get("/refresh", (req, res, next) => { + patientController.refreshAccessToken(req, res, next); +}); +route.post("/reset-password", (req, res, next) => { + patientController.resetPassword(req, res, next); }); -route.post('/logout',patientAuthMiddleWare.exec,(req,res,next)=>{ - patientController.logout(req,res,next) +route.post("/logout", patientAuthMiddleware.exec, (req, res, next) => { + patientController.logout(req, res, next); }); export default route; diff --git a/server/src/use_case/patient/AuthPatientUseCase.ts b/server/src/use_case/patient/AuthPatientUseCase.ts index 6e3396a3..4d31c35d 100644 --- a/server/src/use_case/patient/AuthPatientUseCase.ts +++ b/server/src/use_case/patient/AuthPatientUseCase.ts @@ -24,9 +24,9 @@ export default class LoginPatientUseCase { if (foundedPatient.isBlocked) throw new Error("Unauthorized"); - let otp = parseInt(generateOTP(6),10); + let otp = parseInt(generateOTP(6), 10); while (otp.toString().length !== 6) { - otp = parseInt(generateOTP(6),10); + otp = parseInt(generateOTP(6), 10); } await this.otpRepository.create(otp, foundedPatient.email!); @@ -39,9 +39,9 @@ export default class LoginPatientUseCase { const patient = await this.patientRepository.findByEmail(email); if (!patient) throw new Error("Patient Not Found"); - let otp = parseInt(generateOTP(6),10); + let otp = parseInt(generateOTP(6), 10); while (otp.toString().length !== 6) { - otp = parseInt(generateOTP(6),10); + otp = parseInt(generateOTP(6), 10); } await this.otpRepository.create(otp, email); await this.emailService.sendOtp(email, patient.name!, otp); @@ -78,4 +78,11 @@ export default class LoginPatientUseCase { return { accessToken }; } + + async sendForgetPasswordMail(email: string): Promise { + const patient = await this.patientRepository.findByEmail(email); + if (!patient) throw new Error("Patient Not Found"); + if (patient.isBlocked) throw new Error("Patient is Blocked"); + await this.emailService.sendResetMail(email, patient.name!, `${process.env.CLIENT_URL}/signin/reset-password`!); + } }