From 3f3007c187a7992a9c2fe4eb069c77ec14d15a52 Mon Sep 17 00:00:00 2001 From: dovakiin0 Date: Mon, 18 Sep 2023 18:39:23 +0545 Subject: [PATCH] Email verification done with otp --- .github/workflows/cd.yml | 1 + .gitignore | 2 +- client/src/app/Register.tsx | 7 +- client/src/app/Verify.tsx | 111 ++++++++++++++++++++++ client/src/hooks/useAuth.tsx | 51 +++++++++- client/src/routes/router.tsx | 5 + client/src/types/IUser.ts | 4 + docker-compose-dev.yml | 3 + docker-compose.yml | 7 +- server/src/config/NodeMailer.ts | 27 ++++-- server/src/controllers/user.controller.ts | 13 ++- server/src/middlewares/auth.ts | 5 + 12 files changed, 211 insertions(+), 25 deletions(-) create mode 100644 client/src/app/Verify.tsx diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index d53a989..6631008 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -30,4 +30,5 @@ jobs: cd ~/TMpro git pull origin master git status + echo "$(cat ${{secrets.ENV_PROD}})" > .env.production sh deploy.sh diff --git a/.gitignore b/.gitignore index 3d75225..cd1fac3 100644 --- a/.gitignore +++ b/.gitignore @@ -96,7 +96,7 @@ web_modules/ # dotenv environment variable files -.env +.env.production .env.development.local .env.test.local .env.production.local diff --git a/client/src/app/Register.tsx b/client/src/app/Register.tsx index 9ff0968..aa99854 100644 --- a/client/src/app/Register.tsx +++ b/client/src/app/Register.tsx @@ -102,7 +102,9 @@ function Register({ }: Props) { form.setErrors({ password: "Please enter a strong password" }); return; } - register(values); + register(values, () => { + navigate(`/verify`, { state: { email: values.email } }); + }); }; useEffect(() => { @@ -149,11 +151,13 @@ function Register({ }: Props) { + + + + + ); +} + +export default Verify; diff --git a/client/src/hooks/useAuth.tsx b/client/src/hooks/useAuth.tsx index d50e597..1b0ea5d 100644 --- a/client/src/hooks/useAuth.tsx +++ b/client/src/hooks/useAuth.tsx @@ -10,7 +10,7 @@ export default function useAuth() { const [errors, setErrors] = useState(); const { current } = useAppSelector((state) => state.auth); const dispatch = useAppDispatch(); - const { Error } = useToast(); + const { Error, Success } = useToast(); useEffect(() => { checkUser(); @@ -33,7 +33,7 @@ export default function useAuth() { setLoading(true); const response = await client.post("/api/auth", credentials); if (response.status === 200) { - dispatch(loginSuccess(response.data)); + dispatch(loginSuccess(response.data.user)); } } catch (err: any) { setErrors(err); @@ -43,7 +43,7 @@ export default function useAuth() { } }; - const register = async (userData: IRegisterUser) => { + const register = async (userData: IRegisterUser, cb?: () => void) => { try { setLoading(true); const response = await client.post("/api/auth/register", { @@ -52,7 +52,8 @@ export default function useAuth() { password: userData.password, }); if (response.status === 201) { - dispatch(loginSuccess(response.data)); + await generateOTP(response.data.user.email); + cb?.(); } } catch (err: any) { setErrors(err); @@ -62,6 +63,37 @@ export default function useAuth() { } }; + const generateOTP = async (email: string) => { + try { + const response = await client.post("/api/auth/otp", { email }); + if (response.status === 200) { + Success({ + message: response.data.message, + }); + } + } catch (err: any) { + Error({ message: err.response.data.message }); + } + }; + + const verifyOTP = async (email: string, otp: number, cb: () => void) => { + try { + setLoading(true); + const response = await client.post("/api/auth/otp/verify", { + email, + otp, + }); + if (response.status === 200) { + Success({ + message: response.data.message, + }); + cb(); + } + } catch (err: any) { + Error({ message: err.response.data.message }); + } + }; + const logoutUser = async () => { try { setLoading(true); @@ -77,5 +109,14 @@ export default function useAuth() { } }; - return { login, loading, errors, register, logoutUser, current }; + return { + login, + loading, + errors, + register, + logoutUser, + current, + verifyOTP, + generateOTP, + }; } diff --git a/client/src/routes/router.tsx b/client/src/routes/router.tsx index 2a5f09e..80e4a7a 100644 --- a/client/src/routes/router.tsx +++ b/client/src/routes/router.tsx @@ -5,6 +5,7 @@ const Home = React.lazy(() => import("../app/Home")); const ProtectedRoute = React.lazy(() => import("./ProtectedRoute")); const Login = React.lazy(() => import("../app/Login")); const Register = React.lazy(() => import("../app/Register")); +const Verify = React.lazy(() => import("../app/Verify")); const Error = React.lazy(() => import("../components/Error")); // initialize route paths @@ -28,4 +29,8 @@ export const router = createBrowserRouter([ path: "/register", element: , }, + { + path: "/verify", + element: , + }, ]); diff --git a/client/src/types/IUser.ts b/client/src/types/IUser.ts index 173c2df..8d7b62a 100644 --- a/client/src/types/IUser.ts +++ b/client/src/types/IUser.ts @@ -21,3 +21,7 @@ export interface IUserResponse { email: string; username: string; } + +export interface IVerifyOtp { + otp: number; +} diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 362f7f5..9d6169a 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -12,6 +12,9 @@ services: - MONGO_URI=mongodb://mongo_db:27017/TMpro - JWT_SECRET=verysecretkey - NODE_ENV=development + - EMAIL_HOST=smtp.ethereal.email + - EMAIL_PASS=TKJ64DySyD75dVnncu + - EMAIL_USER=mathew.terry98@ethereal.email depends_on: - mongo_db diff --git a/docker-compose.yml b/docker-compose.yml index b6e85fc..ca9f603 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,11 +15,8 @@ services: restart: always ports: - "5001:5001" - environment: - - MONGO_URI=mongodb://mongo_db:27017/TMpro - - JWT_SECRET=verysecretkey - - PORT=5001 - - NODE_ENV=production + env_file: + - .env.production depends_on: - mongo_db diff --git a/server/src/config/NodeMailer.ts b/server/src/config/NodeMailer.ts index eac722e..1268779 100644 --- a/server/src/config/NodeMailer.ts +++ b/server/src/config/NodeMailer.ts @@ -3,14 +3,25 @@ import * as path from "path"; import hbs from "nodemailer-express-handlebars"; export const Transporter = () => { - const transporter = nodemailer.createTransport({ - host: process.env.EMAIL_HOST as string, - port: 587, - auth: { - user: process.env.EMAIL_USER, - pass: process.env.EMAIL_PASS, - }, - }); + let transporter; + if (process.env.NODE_ENV === "development") { + transporter = nodemailer.createTransport({ + host: process.env.EMAIL_HOST, + port: 587, + auth: { + user: process.env.EMAIL_USER, + pass: process.env.EMAIL_PASS, + }, + }); + } else { + transporter = nodemailer.createTransport({ + service: "gmail", + auth: { + user: process.env.EMAIL_USER, + pass: process.env.EMAIL_PASS, + }, + }); + } const handleBarsOptions: hbs.NodemailerExpressHandlebarsOptions = { viewEngine: { diff --git a/server/src/controllers/user.controller.ts b/server/src/controllers/user.controller.ts index 2dc786f..4872dc0 100644 --- a/server/src/controllers/user.controller.ts +++ b/server/src/controllers/user.controller.ts @@ -94,6 +94,7 @@ const registerUser = asyncHandler(async (req: IRequest, res: Response) => { success: true, user: { _id: user._id, + email: user.email, }, }); }); @@ -105,9 +106,9 @@ const registerUser = asyncHandler(async (req: IRequest, res: Response) => { */ const generateOtpForUser = asyncHandler( async (req: IRequest, res: Response) => { - const { id } = req.body; + const { email } = req.body; - const user = await User.findById({ id }); + const user = await User.findOne({ email }); if (!user) { res.status(400); throw new Error("User does not exists"); @@ -134,7 +135,7 @@ const generateOtpForUser = asyncHandler( }, }); - res.status(400).json({ + res.status(200).json({ success: true, message: "OTP has been sent to your email address", }); @@ -147,8 +148,8 @@ const generateOtpForUser = asyncHandler( @Method POST */ const verifyOTP = asyncHandler(async (req: IRequest, res: Response) => { - const { otp, id } = req.body; - const user = await User.findById({ id }); + const { otp, email } = req.body; + const user = await User.findOne({ email }); if (!user || !user.otp || !user.otpExpiration) { res.status(400); @@ -166,6 +167,8 @@ const verifyOTP = asyncHandler(async (req: IRequest, res: Response) => { user.otp = null; user.otpExpiration = null; + await user.save(); + res .status(200) .json({ success: true, message: "Account verified, Please login!" }); diff --git a/server/src/middlewares/auth.ts b/server/src/middlewares/auth.ts index cb6311a..fc4fe0a 100644 --- a/server/src/middlewares/auth.ts +++ b/server/src/middlewares/auth.ts @@ -10,9 +10,14 @@ export const isAuth = async ( ) => { try { let token = req.cookies["access_token"]; + if (typeof token === "undefined") { + next(); + return; + } const decoded = verifyJWT(token); if (decoded === null) { + next(); return; }