finished resend email verification, reset password, and forgot password logic
This commit is contained in:
@@ -18,7 +18,6 @@ export default {
|
||||
sendgridKey: process.env.SENDGRID_KEY,
|
||||
sendgridEmail: process.env.SENDGRID_EMAIL,
|
||||
remoteURL: process.env.REMOTE_URL,
|
||||
disableEmailVerification: process.env.DISABLE_EMAIL_VERIFICATION,
|
||||
secureCookies: process.env.SECURE_COOKIES,
|
||||
tempDirectory: process.env.TEMP_DIRECTORY,
|
||||
emailVerification: process.env.EMAIL_VERIFICATION,
|
||||
@@ -49,7 +48,6 @@ module.exports = {
|
||||
sendgridKey: process.env.SENDGRID_KEY,
|
||||
sendgridEmail: process.env.SENDGRID_EMAIL,
|
||||
remoteURL: process.env.REMOTE_URL,
|
||||
disableEmailVerification: process.env.DISABLE_EMAIL_VERIFICATION,
|
||||
secureCookies: process.env.SECURE_COOKIES,
|
||||
tempDirectory: process.env.TEMP_DIRECTORY,
|
||||
emailVerification: process.env.EMAIL_VERIFICATION,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Router } from "express";
|
||||
import auth from "../middleware/auth";
|
||||
import authNoEmailVerification from "../middleware/authNoEmailVerificication";
|
||||
import UserController from "../controllers/user-controller";
|
||||
import authRefresh from "../middleware/authRefresh";
|
||||
import authLogout from "../middleware/authLogout";
|
||||
@@ -15,11 +14,7 @@ const router = Router();
|
||||
|
||||
// GET
|
||||
|
||||
router.get(
|
||||
"/user-service/user",
|
||||
authNoEmailVerification,
|
||||
userController.getUser
|
||||
);
|
||||
router.get("/user-service/user", auth, userController.getUser);
|
||||
|
||||
router.get("/user-service/user-detailed", auth, userController.getUserDetailed);
|
||||
|
||||
@@ -37,21 +32,6 @@ router.post(
|
||||
userController.createUser
|
||||
);
|
||||
|
||||
router.post("/user-service/verify-email", userController.verifyEmail);
|
||||
|
||||
router.post(
|
||||
"/user-service/resend-verify-email",
|
||||
authNoEmailVerification,
|
||||
userController.resendVerifyEmail
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/user-service/send-password-reset",
|
||||
userController.sendPasswordReset
|
||||
);
|
||||
|
||||
router.post("/user-service/reset-password", userController.resetPassword);
|
||||
|
||||
router.post("/user-service/get-token", authRefresh, userController.getToken);
|
||||
|
||||
// PATCH
|
||||
@@ -63,4 +43,19 @@ router.patch(
|
||||
userController.changePassword
|
||||
);
|
||||
|
||||
router.patch(
|
||||
"/user-service/resend-verify-email",
|
||||
auth,
|
||||
userController.resendVerifyEmail
|
||||
);
|
||||
|
||||
router.patch("/user-service/verify-email", userController.verifyEmail);
|
||||
|
||||
router.patch("/user-service/reset-password", userController.resetPassword);
|
||||
|
||||
router.patch(
|
||||
"/user-service/send-password-reset",
|
||||
userController.sendPasswordReset
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -33,8 +33,6 @@ const auth = async (req: RequestType, res: Response, next: NextFunction) => {
|
||||
const user = decoded.user;
|
||||
|
||||
if (!user) throw new Error("No User");
|
||||
if (!user.emailVerified && !env.disableEmailVerification)
|
||||
throw new Error("Email Not Verified");
|
||||
|
||||
req.user = user;
|
||||
|
||||
|
||||
@@ -38,8 +38,6 @@ const authFullUser = async (
|
||||
const user = decoded.user;
|
||||
|
||||
if (!user) throw new Error("No User");
|
||||
if (!user.emailVerified && !env.disableEmailVerification)
|
||||
throw new Error("Email Not Verified");
|
||||
|
||||
const fullUser = await User.findById(user._id);
|
||||
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
import jwt from "jsonwebtoken";
|
||||
import User, { UserInterface } from "../models/user-model";
|
||||
import env from "../enviroment/env";
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import userUpdateCheck from "../utils/userUpdateCheck";
|
||||
import mongoose from "mongoose";
|
||||
|
||||
interface RequestType extends Request {
|
||||
user?: userAccessType;
|
||||
token?: string;
|
||||
encryptedToken?: string;
|
||||
}
|
||||
|
||||
type jwtType = {
|
||||
iv: Buffer;
|
||||
user: userAccessType;
|
||||
};
|
||||
|
||||
type userAccessType = {
|
||||
_id: mongoose.Types.ObjectId;
|
||||
emailVerified: boolean;
|
||||
email: string;
|
||||
botChecked: boolean;
|
||||
};
|
||||
|
||||
const auth = async (req: RequestType, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const accessToken = req.cookies["access-token"];
|
||||
|
||||
if (!accessToken) throw new Error("No Access Token");
|
||||
|
||||
const decoded = jwt.verify(accessToken, env.passwordAccess!) as jwtType;
|
||||
|
||||
let user = decoded.user;
|
||||
|
||||
if (!user) throw new Error("No User");
|
||||
|
||||
if (!user.emailVerified && !env.disableEmailVerification) {
|
||||
const currentUUID = req.headers.uuid as string;
|
||||
user = await userUpdateCheck(res, user._id, currentUUID);
|
||||
}
|
||||
|
||||
req.user = user;
|
||||
|
||||
next();
|
||||
} catch (e) {
|
||||
if (
|
||||
e instanceof Error &&
|
||||
e.message !== "No Access Token" &&
|
||||
e.message !== "No User"
|
||||
) {
|
||||
console.log(
|
||||
"\nAuthorization No Email Verification Middleware Error:",
|
||||
e.message
|
||||
);
|
||||
}
|
||||
|
||||
return res.status(401).send("Error Authenticating");
|
||||
}
|
||||
};
|
||||
|
||||
export default auth;
|
||||
@@ -154,6 +154,12 @@ userSchema.methods.toJSON = function () {
|
||||
delete userObject.privateKey;
|
||||
delete userObject.publicKey;
|
||||
|
||||
if (env.emailVerification !== "true") {
|
||||
delete userObject.emailVerified;
|
||||
} else {
|
||||
userObject.emailVerified = user.emailVerified || false;
|
||||
}
|
||||
|
||||
return userObject;
|
||||
};
|
||||
|
||||
@@ -203,7 +209,7 @@ userSchema.methods.generateAuthToken = async function (
|
||||
|
||||
const userObj = {
|
||||
_id: user._id,
|
||||
emailVerified: user.emailVerified || env.disableEmailVerification,
|
||||
emailVerified: user.emailVerified,
|
||||
email: user.email,
|
||||
};
|
||||
|
||||
|
||||
@@ -172,9 +172,14 @@ class UserService {
|
||||
|
||||
const verifiedEmail = user.emailVerified;
|
||||
|
||||
if (env.emailVerification !== "true") {
|
||||
throw new ForbiddenError("Email Verification Disabled");
|
||||
}
|
||||
|
||||
if (!verifiedEmail) {
|
||||
const emailToken = await user.generateEmailVerifyToken();
|
||||
await sendVerificationEmail(user, emailToken);
|
||||
const result = await sendVerificationEmail(user, emailToken);
|
||||
if (!result) throw new InternalServerError("Email Verification Error");
|
||||
} else {
|
||||
throw new ForbiddenError("Email Already Authorized");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import env from "../enviroment/env";
|
||||
import nodemailer from "nodemailer";
|
||||
|
||||
const createEmailTransporter = () => {
|
||||
const emailVerification = env.emailVerification === "true";
|
||||
const emailAPIKey = env.emailAPIKey;
|
||||
const emailDomain = env.emailDomain;
|
||||
const emailHost = env.emailHost;
|
||||
const emailPort = env.emailPort;
|
||||
const emailAddress = env.emailAddress;
|
||||
|
||||
if (!emailVerification) {
|
||||
throw new Error("Email Verification Not Enabled");
|
||||
}
|
||||
|
||||
if (!emailAPIKey || !emailDomain || !emailHost || !emailAddress) {
|
||||
throw new Error("Email Verification Not Setup Correctly");
|
||||
}
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
// @ts-ignore
|
||||
host: emailHost,
|
||||
port: emailPort || 587,
|
||||
auth: {
|
||||
user: emailDomain,
|
||||
pass: emailAPIKey,
|
||||
},
|
||||
});
|
||||
|
||||
return transporter;
|
||||
};
|
||||
|
||||
export default createEmailTransporter;
|
||||
@@ -1,29 +1,56 @@
|
||||
import SMTPTransport from "nodemailer/lib/smtp-transport";
|
||||
import env from "../enviroment/env";
|
||||
import { UserInterface } from "../models/user-model";
|
||||
// import sgMail from "@sendgrid/mail";
|
||||
import nodemailer from "nodemailer";
|
||||
import createEmailTransporter from "./createEmailTransporter";
|
||||
|
||||
type MailOptionsType = {
|
||||
from: string;
|
||||
to: string;
|
||||
subject: string;
|
||||
text: string;
|
||||
};
|
||||
|
||||
const sendEmail = (
|
||||
transporter: nodemailer.Transporter<SMTPTransport.SentMessageInfo>,
|
||||
mailOptions: MailOptionsType
|
||||
) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
transporter.sendMail(mailOptions, (error, info) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(info);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const sendPasswordResetEmail = async (
|
||||
user: UserInterface,
|
||||
passwordResetToken: string
|
||||
resetToken: string
|
||||
) => {
|
||||
// if (process.env.NODE_ENV === "test") {
|
||||
// return;
|
||||
// }
|
||||
// const apiKey: any = env.sendgridKey;
|
||||
// const sendgridEmail:any = env.sendgridEmail;
|
||||
// const url = env.remoteURL + `/reset-password/${passwordResetToken}`
|
||||
// // console.log("send grid api key", apiKey)
|
||||
// // console.log("send grid email", sendgridEmail);
|
||||
// // console.log("send grid reset url", url)
|
||||
// sgMail.setApiKey(apiKey);
|
||||
// const msg = {
|
||||
// to: user.email,
|
||||
// from: sendgridEmail,
|
||||
// subject: "myDrive Password Reset",
|
||||
// text: `Please navigate to the following link to reset your password: ${url}`
|
||||
// }
|
||||
// await sgMail.send(msg);
|
||||
// //console.log("Send grid email sent reset");
|
||||
try {
|
||||
const transporter = createEmailTransporter();
|
||||
|
||||
const emailAddress = env.emailAddress!;
|
||||
const url = env.remoteURL + `/reset-password/${resetToken}`;
|
||||
|
||||
const mailOptions = {
|
||||
from: emailAddress,
|
||||
to: user.email,
|
||||
subject: "myDrive Password Reset",
|
||||
text:
|
||||
"Please navigate to the following link to reset your password: " + url,
|
||||
};
|
||||
|
||||
await sendEmail(transporter, mailOptions);
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.log("Error sending password reset email", e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default sendPasswordResetEmail;
|
||||
|
||||
@@ -2,6 +2,7 @@ import SMTPTransport from "nodemailer/lib/smtp-transport";
|
||||
import env from "../enviroment/env";
|
||||
import { UserInterface } from "../models/user-model";
|
||||
import nodemailer from "nodemailer";
|
||||
import createEmailTransporter from "./createEmailTransporter";
|
||||
|
||||
type MailOptionsType = {
|
||||
from: string;
|
||||
@@ -30,33 +31,11 @@ const sendVerificationEmail = async (
|
||||
emailToken: string
|
||||
) => {
|
||||
try {
|
||||
const emailVerification = env.emailVerification === "true";
|
||||
const emailAPIKey = env.emailAPIKey;
|
||||
const emailDomain = env.emailDomain;
|
||||
const emailHost = env.emailHost;
|
||||
const emailPort = env.emailPort;
|
||||
const emailAddress = env.emailAddress;
|
||||
|
||||
if (!emailVerification) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!emailAPIKey || !emailDomain || !emailHost || !emailAddress) {
|
||||
console.log("Email Verification Not Setup Correctly");
|
||||
return false;
|
||||
}
|
||||
const transporter = createEmailTransporter();
|
||||
|
||||
const emailAddress = env.emailAddress!;
|
||||
const url = env.remoteURL + `/verify-email/${emailToken}`;
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: emailHost,
|
||||
port: 587 || emailPort,
|
||||
auth: {
|
||||
user: emailDomain,
|
||||
pass: emailAPIKey,
|
||||
},
|
||||
});
|
||||
|
||||
const mailOptions = {
|
||||
from: emailAddress,
|
||||
to: user.email,
|
||||
@@ -66,15 +45,6 @@ const sendVerificationEmail = async (
|
||||
url,
|
||||
};
|
||||
|
||||
console.log("Sending email verification", mailOptions, {
|
||||
host: emailHost,
|
||||
port: 587 || emailPort,
|
||||
auth: {
|
||||
user: emailDomain,
|
||||
pass: emailAPIKey,
|
||||
},
|
||||
});
|
||||
|
||||
await sendEmail(transporter, mailOptions);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -57,3 +57,33 @@ export const changePasswordAPI = async (
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const resendVerifyEmailAPI = async () => {
|
||||
const response = await axios.patch("/user-service/resend-verify-email");
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const verifyEmailAPI = async (emailToken: string) => {
|
||||
const response = await axios.patch("/user-service/verify-email", {
|
||||
emailToken,
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const sendPasswordResetAPI = async (email: string) => {
|
||||
const response = await axios.patch("/user-service/send-password-reset", {
|
||||
email,
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const resetPasswordAPI = async (
|
||||
password: string,
|
||||
passwordToken: string
|
||||
) => {
|
||||
const response = await axios.patch("/user-service/reset-password", {
|
||||
passwordToken,
|
||||
password,
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { createAccountAPI, getUserAPI, loginAPI } from "../../api/user";
|
||||
import {
|
||||
createAccountAPI,
|
||||
getUserAPI,
|
||||
loginAPI,
|
||||
sendPasswordResetAPI,
|
||||
} from "../../api/user";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { setUser } from "../../reducers/user";
|
||||
import { useAppDispatch } from "../../hooks/store";
|
||||
@@ -8,6 +13,7 @@ import AlertIcon from "../../icons/AlertIcon";
|
||||
import Spinner from "../Spinner/Spinner";
|
||||
import classNames from "classnames";
|
||||
import { toast, ToastContainer } from "react-toastify";
|
||||
import Swal from "sweetalert2";
|
||||
|
||||
const LoginPage = () => {
|
||||
const [email, setEmail] = useState("");
|
||||
@@ -20,6 +26,7 @@ const LoginPage = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const lastSentPassowordReset = useRef(0);
|
||||
|
||||
const attemptLoginWithToken = async () => {
|
||||
setAttemptingLogin(true);
|
||||
@@ -75,6 +82,37 @@ const LoginPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const resetPassword = async () => {
|
||||
try {
|
||||
const currentDate = Date.now();
|
||||
if (currentDate - lastSentPassowordReset.current < 1000 * 60 * 1) {
|
||||
await Swal.fire({
|
||||
title: "Please wait 1 minute before resending",
|
||||
icon: "warning",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "Okay",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
lastSentPassowordReset.current = Date.now();
|
||||
|
||||
setLoadingLogin(true);
|
||||
|
||||
await toast.promise(sendPasswordResetAPI(email), {
|
||||
pending: "Sending password reset...",
|
||||
success: "Password Reset Sent",
|
||||
error: "Error Sending Password Reset",
|
||||
});
|
||||
|
||||
setLoadingLogin(false);
|
||||
} catch (e) {
|
||||
console.log("Create Account Error", e);
|
||||
setLoadingLogin(false);
|
||||
setError("Create Account Failed");
|
||||
}
|
||||
};
|
||||
|
||||
const isSubmitDisabled = useMemo(() => {
|
||||
switch (mode) {
|
||||
case "login":
|
||||
@@ -94,6 +132,8 @@ const LoginPage = () => {
|
||||
login();
|
||||
} else if (mode === "create") {
|
||||
createAccount();
|
||||
} else if (mode === "reset") {
|
||||
resetPassword();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -144,12 +184,7 @@ const LoginPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<div className="bg-[#F4F4F6] w-screen h-screen flex justify-center items-center">
|
||||
<div
|
||||
className={classNames(
|
||||
"rounded-md shadow-lg bg-white p-10 relative w-[90%] sm:w-[500px] animate-height",
|
||||
{}
|
||||
)}
|
||||
>
|
||||
<div className="rounded-md shadow-lg bg-white p-10 relative w-[90%] sm:w-[500px] animate-height">
|
||||
<div className="absolute -top-10 left-0 right-0 flex justify-center items-center">
|
||||
<div className="flex items-center justify-center rounded-full bg-white p-3 shadow-md">
|
||||
{!loadingLogin && (
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
const ResetPasswordPage = (props) => (
|
||||
<div className="modal__wrap">
|
||||
<div className="inner__modal">
|
||||
<div className="password__modal">
|
||||
<div className="head__password">
|
||||
<h2>{"Reset Password"}</h2>
|
||||
<div className="close__modal"></div>
|
||||
</div>
|
||||
<div className="password__content">
|
||||
<form onSubmit={props.resetPassword}>
|
||||
<div
|
||||
style={props.state.passwordChanged ? { display: "none" } : {}}
|
||||
className="group__password"
|
||||
>
|
||||
<input
|
||||
value={props.state.password}
|
||||
onChange={props.passwordOnChange}
|
||||
placeholder="New Password"
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={props.state.passwordChanged ? { display: "none" } : {}}
|
||||
className="group__password"
|
||||
>
|
||||
<input
|
||||
value={props.state.verifyPassword}
|
||||
onChange={props.verifyPasswordOnChange}
|
||||
placeholder="Verify New Password"
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
<div className="password__submit">
|
||||
<input
|
||||
type="submit"
|
||||
value={props.state.passwordChanged ? "Go Back" : "Submit"}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default ResetPasswordPage;
|
||||
@@ -0,0 +1,96 @@
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import Spinner from "../Spinner/Spinner";
|
||||
import { ToastContainer, toast } from "react-toastify";
|
||||
import { useState } from "react";
|
||||
import { resetPasswordAPI } from "../../api/user";
|
||||
import AlertIcon from "../../icons/AlertIcon";
|
||||
|
||||
const ResetPasswordPage = () => {
|
||||
const token = useParams().token!;
|
||||
const [password, setPassword] = useState("");
|
||||
const [verifyPassword, setVerifyPassword] = useState("");
|
||||
const [loadingLogin, setLoadingLogin] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const navigate = useNavigate();
|
||||
|
||||
const isSubmitDisabled = password !== verifyPassword;
|
||||
|
||||
const onSubmit = async (e: any) => {
|
||||
try {
|
||||
e.preventDefault();
|
||||
setLoadingLogin(true);
|
||||
await toast.promise(resetPasswordAPI(password, token), {
|
||||
pending: "Resetting password...",
|
||||
success: "Password Reset",
|
||||
error: "Error Resetting Password",
|
||||
});
|
||||
setTimeout(() => {
|
||||
navigate("/");
|
||||
}, 1500);
|
||||
} catch (e) {
|
||||
console.log("Reset Password Error", e);
|
||||
setLoadingLogin(false);
|
||||
setError("Reset Password Failed");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="bg-[#F4F4F6] w-screen h-screen flex justify-center items-center">
|
||||
<div className="rounded-md shadow-lg bg-white p-10 relative w-[90%] sm:w-[500px] animate-height">
|
||||
<div className="absolute -top-10 left-0 right-0 flex justify-center items-center">
|
||||
<div className="flex items-center justify-center rounded-full bg-white p-3 shadow-md">
|
||||
{!loadingLogin && (
|
||||
<img src="/images/icon.png" alt="logo" className="w-[45px]" />
|
||||
)}
|
||||
{loadingLogin && <Spinner />}
|
||||
</div>
|
||||
</div>
|
||||
<form onSubmit={onSubmit}>
|
||||
<p className="text-[#212B36] font-medium text-[25px] mt-0 mb-[15px] text-center">
|
||||
Reset password
|
||||
</p>
|
||||
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
className="w-full h-[48px] pl-[12px] pr-[70px] text-black border border-[#637381] rounded-[5px] outline-none text-[15px] mt-4"
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
value={password}
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Verify Password"
|
||||
className="w-full h-[48px] pl-[12px] pr-[12px] text-black border border-[#637381] rounded-[5px] outline-none text-[15px] mt-4"
|
||||
onChange={(e) => setVerifyPassword(e.target.value)}
|
||||
value={verifyPassword}
|
||||
/>
|
||||
|
||||
<div className="flex justify-center items-center mt-4">
|
||||
<input
|
||||
type="submit"
|
||||
value="Reset"
|
||||
disabled={isSubmitDisabled || loadingLogin}
|
||||
className="bg-[#3c85ee] border border-[#3c85ee] hover:bg-[#326bcc] rounded-[5px] text-white text-[15px] font-medium cursor-pointer py-2 px-4 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{(isSubmitDisabled || error) && (
|
||||
<div className="mt-4">
|
||||
<div className="flex justify-center items-center">
|
||||
<AlertIcon className="w-[20px] text-red-600 mr-2" />
|
||||
<p className="text-[#637381] text-[15px]">
|
||||
{isSubmitDisabled ? "Passwords do not match" : error}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<ToastContainer position="bottom-left" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResetPasswordPage;
|
||||
@@ -1,107 +0,0 @@
|
||||
import React from "react";
|
||||
import Swal from "sweetalert2";
|
||||
import axios from "../../axiosInterceptor";
|
||||
import env from "../../enviroment/envFrontEnd";
|
||||
import ResetPasswordPage from "./ResetPasswordPage";
|
||||
|
||||
class ResetPasswordPageContainer extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
password: "",
|
||||
verifyPassword: "",
|
||||
passwordChanged: false,
|
||||
}
|
||||
}
|
||||
|
||||
passwordOnChange = (e) => {
|
||||
|
||||
const value = e.target.value;
|
||||
|
||||
this.setState(() => {
|
||||
|
||||
return {
|
||||
...this.state,
|
||||
password: value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
verifyPasswordOnChange = (e) => {
|
||||
|
||||
const value = e.target.value;
|
||||
|
||||
this.setState(() => {
|
||||
|
||||
return {
|
||||
...this.state,
|
||||
verifyPassword : value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
resetPassword = (e) => {
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
if (this.state.passwordChanged) {
|
||||
|
||||
window.location.assign(env.url);
|
||||
return;
|
||||
}
|
||||
|
||||
const id = this.props.match.params.id;
|
||||
|
||||
if (this.state.password === this.state.verifyPassword) {
|
||||
|
||||
const data = {
|
||||
passwordToken: id,
|
||||
password: this.state.password
|
||||
}
|
||||
|
||||
axios.post("/user-service/reset-password", data).then((response) => {
|
||||
|
||||
Swal.fire(
|
||||
'Password Successfully Reset',
|
||||
'You Have Successfully Resetted Your Password, All Sessions Have Been Logged Out.',
|
||||
'success'
|
||||
)
|
||||
|
||||
this.setState(() => {
|
||||
return {
|
||||
...this.state,
|
||||
passwordChanged: true
|
||||
}
|
||||
})
|
||||
|
||||
}).catch((err) => {
|
||||
console.log("reset password err", err);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: 'Passwords Reset Error',
|
||||
})
|
||||
});
|
||||
|
||||
} else {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: 'Passwords do not match',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <ResetPasswordPage
|
||||
resetPassword={this.resetPassword}
|
||||
passwordOnChange={this.passwordOnChange}
|
||||
verifyPasswordOnChange={this.verifyPasswordOnChange}
|
||||
state={this.state}
|
||||
{...this.props}/>
|
||||
}
|
||||
}
|
||||
|
||||
export default ResetPasswordPageContainer;
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useRef, useState } from "react";
|
||||
import SettingsChangePasswordPopup from "./SettingsChangePasswordPopup";
|
||||
import { toast } from "react-toastify";
|
||||
import { logoutAPI } from "../../api/user";
|
||||
import { logoutAPI, resendVerifyEmailAPI } from "../../api/user";
|
||||
import Swal from "sweetalert2";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
@@ -9,11 +9,17 @@ interface SettingsPageAccountProps {
|
||||
user: {
|
||||
_id: string;
|
||||
email: string;
|
||||
emailVerified: boolean;
|
||||
};
|
||||
getUser: () => void;
|
||||
}
|
||||
|
||||
const SettingsPageAccount: React.FC<SettingsPageAccountProps> = ({ user }) => {
|
||||
const SettingsPageAccount: React.FC<SettingsPageAccountProps> = ({
|
||||
user,
|
||||
getUser,
|
||||
}) => {
|
||||
const [showChangePasswordPopup, setShowChangePasswordPopup] = useState(false);
|
||||
const lastSentEmailVerifiation = useRef(0);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const logoutClick = async () => {
|
||||
@@ -59,7 +65,7 @@ const SettingsPageAccount: React.FC<SettingsPageAccountProps> = ({ user }) => {
|
||||
await toast.promise(logoutAPI(), {
|
||||
pending: "Logging out all...",
|
||||
success: "Logged out all",
|
||||
error: "Error Logging Out Al",
|
||||
error: "Error Logging Out all",
|
||||
});
|
||||
|
||||
window.localStorage.removeItem("hasPreviouslyLoggedIn");
|
||||
@@ -70,6 +76,32 @@ const SettingsPageAccount: React.FC<SettingsPageAccountProps> = ({ user }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const resendEmailVerification = async () => {
|
||||
try {
|
||||
const currentDate = Date.now();
|
||||
if (currentDate - lastSentEmailVerifiation.current < 1000 * 60 * 1) {
|
||||
await Swal.fire({
|
||||
title: "Please wait 1 minute before resending",
|
||||
icon: "warning",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "Okay",
|
||||
});
|
||||
return;
|
||||
}
|
||||
lastSentEmailVerifiation.current = Date.now();
|
||||
|
||||
await toast.promise(resendVerifyEmailAPI(), {
|
||||
pending: "Resending email verification...",
|
||||
success: "Email Verification Resent",
|
||||
error: "Error Resending Email Verification",
|
||||
});
|
||||
|
||||
getUser();
|
||||
} catch (e) {
|
||||
console.log("Error resending email verification", e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{showChangePasswordPopup && (
|
||||
@@ -86,6 +118,21 @@ const SettingsPageAccount: React.FC<SettingsPageAccountProps> = ({ user }) => {
|
||||
<p className="text-gray-primary">Email</p>
|
||||
<p>{user.email}</p>
|
||||
</div>
|
||||
{"emailVerified" in user && (
|
||||
<div className="px-3 py-4 flex flex-row justify-between items-center border-b border-gray-secondary">
|
||||
<p className="text-gray-primary">
|
||||
{user.emailVerified ? "Email verified" : "Email unverified"}
|
||||
</p>
|
||||
{!user.emailVerified && (
|
||||
<button
|
||||
className="text-primary hover:text-primary-hover cursor-pointer"
|
||||
onClick={resendEmailVerification}
|
||||
>
|
||||
Resend
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="px-3 py-4 flex flex-row justify-between items-center border-b border-gray-secondary">
|
||||
<p className="text-gray-primary">Change password</p>
|
||||
<button
|
||||
|
||||
@@ -6,13 +6,14 @@ import AccountIcon from "../../icons/AccountIcon";
|
||||
import TuneIcon from "../../icons/TuneIcon";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import SettingsAccountSection from "./SettingsAccountSection";
|
||||
import { getUserDetailedAPI } from "../../api/user";
|
||||
import { getUserDetailedAPI, logoutAPI } from "../../api/user";
|
||||
import Spinner from "../Spinner/Spinner";
|
||||
import Swal from "sweetalert2";
|
||||
import SettingsGeneralSection from "./SettingsGeneralSection";
|
||||
import { useClickOutOfBounds } from "../../hooks/utils";
|
||||
import MenuIcon from "../../icons/MenuIcon";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
const SettingsPage = () => {
|
||||
const [user, setUser] = useState(null);
|
||||
@@ -36,6 +37,15 @@ const SettingsPage = () => {
|
||||
confirmButtonText: "Yes, logout",
|
||||
});
|
||||
if (result.value) {
|
||||
await toast.promise(logoutAPI(), {
|
||||
pending: "Logging out...",
|
||||
success: "Logged out",
|
||||
error: "Error Logging Out",
|
||||
});
|
||||
|
||||
window.localStorage.removeItem("hasPreviouslyLoggedIn");
|
||||
|
||||
navigate("/");
|
||||
} else {
|
||||
navigate("/home");
|
||||
}
|
||||
@@ -111,7 +121,9 @@ const SettingsPage = () => {
|
||||
onClick={() => setShowSidebarMobile(!showSidebarMobile)}
|
||||
/>
|
||||
</div>
|
||||
{tab === "account" && <SettingsAccountSection user={user} />}
|
||||
{tab === "account" && (
|
||||
<SettingsAccountSection user={user} getUser={getUser} />
|
||||
)}
|
||||
{tab === "general" && <SettingsGeneralSection />}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
const VerifyEmailPage = (props) => (
|
||||
<div className="verify-page__wrapper">
|
||||
<p className="verify-page__title">{props.state.error ? "Verification Error" : props.state.verified ? "Email Verified" : "Verifying Email..."}</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default VerifyEmailPage;
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { verifyEmailAPI } from "../../api/user";
|
||||
import { toast, ToastContainer } from "react-toastify";
|
||||
import { useEffect } from "react";
|
||||
|
||||
const VerifyEmailPage = () => {
|
||||
const token = useParams().token!;
|
||||
|
||||
const verifyEmail = async () => {
|
||||
try {
|
||||
await toast.promise(verifyEmailAPI(token), {
|
||||
pending: "Verifying email...",
|
||||
success: "Email Verified",
|
||||
error: "Error verifying email",
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.assign("/");
|
||||
}, 1500);
|
||||
} catch (e) {
|
||||
console.log("Error verifying email", e);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
verifyEmail();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="w-screen h-screen flex justify-center items-center flex-col">
|
||||
<h1>Verifying email...</h1>
|
||||
<ToastContainer position="bottom-left" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerifyEmailPage;
|
||||
@@ -1,73 +0,0 @@
|
||||
import React from "react";
|
||||
import axios from "../../axiosInterceptor";
|
||||
import Swal from "sweetalert2";
|
||||
import VerifyEmailPage from "./VerifyEmailPage";
|
||||
|
||||
class VerifiyEmailPageContainer extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
verified: false,
|
||||
error: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
componentDidMount = () => {
|
||||
this.checkEmailVerification();
|
||||
}
|
||||
|
||||
checkEmailVerification = () => {
|
||||
|
||||
const id = this.props.match.params.id
|
||||
|
||||
if (id) {
|
||||
|
||||
const data = {
|
||||
emailToken: id
|
||||
}
|
||||
|
||||
axios.post("/user-service/verify-email", data).then((response) => {
|
||||
|
||||
this.setState(() => {
|
||||
return {
|
||||
...this.state,
|
||||
verified: true,
|
||||
error: false
|
||||
}
|
||||
})
|
||||
|
||||
Swal.fire(
|
||||
'Email Address Verified',
|
||||
'You Have Successfully verified your email address, you may now log into myDrive.',
|
||||
'success'
|
||||
)
|
||||
|
||||
}).catch((err) => {
|
||||
|
||||
this.setState(() => {
|
||||
return {
|
||||
...this.state,
|
||||
error: true
|
||||
}
|
||||
})
|
||||
|
||||
Swal.fire(
|
||||
'Email Address Verifation Error',
|
||||
'Could Not Verifiy Email Address',
|
||||
'error'
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return <VerifyEmailPage
|
||||
state={this.state}/>
|
||||
}
|
||||
}
|
||||
|
||||
export default VerifiyEmailPageContainer;
|
||||
@@ -11,9 +11,9 @@ import HomePage from "../components/Homepage/Homepage";
|
||||
import PrivateRoute from "./PrivateRoute";
|
||||
import PublicRoute from "./PublicRoute";
|
||||
import DownloadPage from "../components/DownloadPage/DownloadPage";
|
||||
import VerifyEmailPage from "../components/VerifyEmailPage";
|
||||
import VerifyEmailPage from "../components/VerifyEmailPage/VerifyEmailPage";
|
||||
import uuid from "uuid";
|
||||
import ResetPasswordPage from "../components/ResetPasswordPage";
|
||||
import ResetPasswordPage from "../components/ResetPasswordPage/ResetPasswordPage";
|
||||
import SettingsPage from "../components/SettingsPage/SettingsPage";
|
||||
import Homepage from "../components/Homepage/Homepage";
|
||||
import { usePreferenceSetter } from "../hooks/preferenceSetter";
|
||||
@@ -95,8 +95,8 @@ const AppRouter = () => {
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route path="/verify-email/:id" element={<VerifyEmailPage />} />
|
||||
<Route path="/reset-password/:id" element={<ResetPasswordPage />} />
|
||||
<Route path="/verify-email/:token" element={<VerifyEmailPage />} />
|
||||
<Route path="/reset-password/:token" element={<ResetPasswordPage />} />
|
||||
<Route
|
||||
path="/settings"
|
||||
element={
|
||||
|
||||
Reference in New Issue
Block a user