diff --git a/src/controllers/userController.ts b/src/controllers/userController.ts index 4cbbaa0..1512f39 100644 --- a/src/controllers/userController.ts +++ b/src/controllers/userController.ts @@ -273,6 +273,72 @@ export async function Login(req: Request, res: Response) { } } +export async function ForgotPassword(req: Request, res: Response) { + try { + let { email, recaptcha } = req.body; + + // validate request + + if (!email || !recaptcha || (await isEmailValid(email))) { + return res.status(400).send({ err: "invalid request" }); + } + + email = email.toLowerCase(); + + // validate recaptcha + + const recaptchaValid = await verifyCaptcha( + recaptcha, + req.headers["x-real-ip"] as string + ); + + if (!recaptchaValid) { + return res.status(400).send({ err: "invalid request" }); + } + + // check if user exists + + const user = await User.findOne({ + where: { + email: email, + }, + attributes: ["user_id", "language"], + }); + + if (!user) { + return res.status(400).send({ err: "invalid request" }); + } + + // create email verification + + const emailVerificationId = newEmailVerificationId(); + const state = EMAIL_VERIFICATION_STATE.PENDING_FORGOT_PASSWORD; + + await EmailVerification.create({ + email_verification_id: emailVerificationId, + user_id: user.user_id, + state: state, + }); + + rabbitmq.sendEmail( + email, + "dashboardForgotPasswordEmailVerification", + user.language, + { + emailVerificationUrl: getEmailVerificationUrl( + state, + emailVerificationId + ), + } + ); + + res.status(200).send({ msg: "success" }); + } catch (error) { + logger.error(error); + res.status(500).send({ err: "invalid request" }); + } +} + export async function Logout(req: Request, res: Response) { try { const session = await getUserSession(req); @@ -951,6 +1017,62 @@ export async function VerifyEmail(req: Request, res: Response) { res.status(200).send({ msg: "email changed" }); return; + } else if ( + emailVerification.state === + EMAIL_VERIFICATION_STATE.PENDING_FORGOT_PASSWORD + ) { + const { password } = req.body; + + if (!password) { + return res.status(200).send({ status: "actionRequired" }); + } + + const user = await User.findOne({ + where: { + user_id: emailVerification.user_id, + }, + attributes: ["password"], + }); + + if (!user) { + return res.status(401).send({ err: "unauthorized" }); + } + + const decodedPassword = decodeBase64(password); + + const hashedPassword = await hashPassword(decodedPassword); + + // update user password + + await User.update( + { + password: hashedPassword, + }, + { + where: { + user_id: emailVerification.user_id, + }, + } + ); + + // delete email verification + + await EmailVerification.destroy({ + where: { + email_verification_id: emailVerificationId, + }, + }); + + // delete all sessions of this user by deleting all sessions with this user_id + + await Session.destroy({ + where: { + user_id: emailVerification.user_id, + }, + }); + + res.status(200).send({ msg: "password changed" }); + return; } res.status(400).send({ err: "invalid request" }); diff --git a/src/routes/userRoutes.ts b/src/routes/userRoutes.ts index 5abe039..a95730f 100644 --- a/src/routes/userRoutes.ts +++ b/src/routes/userRoutes.ts @@ -6,6 +6,7 @@ import { sessionProtection } from "../middleware/authMiddleware"; router.post("/auth/signup", userController.SignUp); router.post("/auth/login", userController.Login); +router.post("/auth/forgot-password", userController.ForgotPassword); router.delete("/auth/logout", sessionProtection, userController.Logout); router.get("/", sessionProtection, userController.GetUser); router.post("/auth/check/email", userController.IsEmailAvailable); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 87bf33e..7c45a90 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -75,6 +75,7 @@ export const ACCOUNT_DEMO_DAYS = Number( export enum EMAIL_VERIFICATION_STATE { PENDING_EMAIL_VERIFICATION = 0, // account is created but email is not verified yet PENDING_USER_PROFILE_EMAIL_CHANGE_VERIFICATION = 1, // user wants to change email, new email is not verified yet + PENDING_FORGOT_PASSWORD = 2, // user has requested a password reset, email is used to change password } export const DASHBOARD_URL = process.env.DASHBOARD_URL as string;