diff --git a/server/config/JWTAuthentication.js b/server/config/JWTAuthentication.js new file mode 100644 index 0000000..accaa0f --- /dev/null +++ b/server/config/JWTAuthentication.js @@ -0,0 +1,22 @@ +import passport from "passport"; +const authenticateJWT = (req, res, next) => { + passport.authenticate("jwt", { session: false }, (err, user, info) => { + if (err) { + return res.status(500).json({ + success: false, + message: "Internal Server Error", + }); + } + if (!user) { + return res.status(401).json({ + success: false, + isLoggedIn: false, + message: "Invalid Request. Please Sign In Your Account", + }); + } + req.user = user; + next(); + })(req, res, next); +}; + +export { authenticateJWT }; diff --git a/server/controllers/auth/authController.js b/server/controllers/auth/authController.js new file mode 100644 index 0000000..b9b03f5 --- /dev/null +++ b/server/controllers/auth/authController.js @@ -0,0 +1,177 @@ +import { UserModel } from "../../models/userModel.js"; +import { OtpModel } from "../../models/OtpModel.js"; +import { generateOTP } from "../../utils/generateOTP.js"; +import { sendEmail } from "../../utils/sendEmail.js"; +import { generateToken } from "../../utils/generateJwtToken.js"; +import { decodeJWT } from "../../utils/decodeJwtToken.js"; +/* + Forgot Password + +*/ +const forgotPassword = async (req, res) => { + try { + const { email } = req.body; + const user = await UserModel.findOne({ email }); + if (!user) { + return res.status(404).json({ + message: "Invalid Email or User Not Found", + success: false, + }); + } + + // generating otp + const otp = generateOTP(); + await OtpModel.create({ email: user.email, otp }); + // Send OTP via email + const emailOptions = { + mail: user.email, + subject: "FORGOT PASSWORD - OTP", + + messageContent: ` +
+

Verify Password Reset Request

+

Dear ${user.fullname},

+

We noticed that you recently requested to reset your password for your account. Please use the following One-Time Password (OTP) to reset your password:

+

OTP: ${otp}

+ +

This OTP Will expires in 2:00 Minutes.

+

Note: If you did not request this password reset, please ignore this email. Your account security is important to us, and we apologize for any inconvenience.

+

If you encounter any issues or need further assistance, please don't hesitate to contact our support team at [Support Email] or call us at [Support Phone Number].

+

Thank you for choosing Shopy !

+

Best regards,
[Company Name]

+
+ `, + }; + await sendEmail(emailOptions); + res.status(201).json({ + success: true, + message: `An OTP is send to your email ${email} .`, + otp: otp, + }); + } catch (error) { + return res.status(500).json({ + success: false, + error: error.message, + }); + } +}; +const verifyAndResetPassword = async (req, res) => { + try { + const { email, otp } = req.body; + // console.log(req.body); + + // Retrieve the OTP stored in the database for the user's email + const otpData = await OtpModel.findOne({ email }).sort({ createdAt: -1 }); + + // Check if OTP exists in the database + if (!otpData) { + return res + .status(404) + .json({ success: false, message: "OTP Expired. Click on Resend OTP" }); + } + + // Compare the OTP provided by the user with the OTP stored in the database + if (otpData.otp !== otp) { + return res.status(400).json({ + success: false, + message: "Invalid OTP.", + }); + } + // - Delete the OTP record from the database + await otpData.deleteOne(); + const user = await UserModel.findOne({ email }); + if (!user) { + return res.status(404).json({ + message: "User Not Found", + success: false, + }); + } + + const password_Reset_Token = generateToken(user._id, "300s"); + const password_Reset_URL = `${process.env.CLIENT_URL}/reset-password/${password_Reset_Token}`; + // Send OTP via email + const emailOptions = { + mail: user.email, + subject: "RESET PASSWORD ", + + messageContent: ` +
+

Reset Your Password

+

Dear ${user.fullname},

+

We have received a request to reset the password associated with your account. To proceed with the password reset, please follow the instructions below:

+ +
    +
  1. Click on the following link to reset your password: Password Reset Link
  2. +
  3. Please note that this link is valid for 5:00 Minutes. After this period, you will need to request another password reset.
  4. +
  5. If you did not request this password reset, please change your Password Immediately by Logging into your account.
  6. +
+

Note: If the above link does not work then copy and paste this url into browser.
${password_Reset_URL}

+

Ensure the security of your account:

+ + +

If you encounter any issues or require further assistance, please feel free to reach out to our support team at [Support Email].

+ +

Thank you for your attention to this matter.

+

Best regards,
Shopy!!

+
+ `, + }; + await sendEmail(emailOptions); + + return res.status(200).json({ + success: true, + message: "Password Reset Link has been Sent to your email.", + password_Reset_URL, + }); + } catch (error) { + return res.status(500).json({ + success: false, + error: error.message, + }); + } +}; +const setNewPassword = async (req, res) => { + try { + const { token, newPassword } = req.body; + console.log(token); + + //decodes token id + const decoded = await decodeJWT(token); + console.log(decoded); + if (!decoded) { + return res.status(404).json({ + success: true, + message: "Please make another new password Reset request", + }); + } + let user = await UserModel.findOne({ _id: decoded.id }).select("password"); + // console.log(user); + if (!user) { + return res.status(400).json({ + success: false, + message: "Invalid Token or User not found", + }); + } + + user.password = newPassword; + user = await user.save(); + + res.status(200).send({ + message: "Password changed successfully", + success: true, + // user, + }); + } catch (error) { + return res.status(500).json({ + success: false, + error: error.message, + }); + } +}; + +export { forgotPassword, verifyAndResetPassword, setNewPassword }; diff --git a/server/index.js b/server/index.js index 8bcd57b..231b5e8 100644 --- a/server/index.js +++ b/server/index.js @@ -1,22 +1,28 @@ + import express from 'express'; import dotenv from 'dotenv'; import cors from 'cors'; import morgan from 'morgan'; import { MensRouter } from './routes/mens-route.js'; import connectDB from './config/DBconnect.js'; +import { authRouter } from "./routes/auth/authRoutes.js"; import { WomensRouter } from './routes/womens-route.js'; import { KidsRouter } from './routes/kids-route.js'; import StripeRouter from './routes/Stripe-route.js'; -const app=express(); -dotenv.config({path:'./config.env'}) +const app = express(); + + +dotenv.config(); + connectDB(); app.use(express.json()); -app.use(morgan('dev')) +app.use(morgan("dev")); + app.use(cors({ origin: process.env.CORS_ORIGIN || "https://shopy-mohitparmar1s-projects.vercel.app/", @@ -26,6 +32,7 @@ app.use(cors({ +app.use("/api/v1/auth", authRouter); app.use('/api/v1/mens',MensRouter); app.use('/api/v1/womens',WomensRouter); app.use('/api/v1/kids',KidsRouter); @@ -35,4 +42,5 @@ app.use('/api',StripeRouter); const port=process.env.PORT||7000; app.listen(port,()=>{ console.log(`Server is running on port ${port}`); -}) \ No newline at end of file +}) + diff --git a/server/models/OtpModel.js b/server/models/OtpModel.js new file mode 100644 index 0000000..3bdb13b --- /dev/null +++ b/server/models/OtpModel.js @@ -0,0 +1,20 @@ +import mongoose from "mongoose"; + +const OtpSchema = new mongoose.Schema({ + email: { + type: String, + required: true, + }, + otp: { + type: String, + required: true, + }, + createdAt: { + type: Date, + default: Date.now, + expires: 60, // Set expiration time in seconds (e.g., 60 seconds) + }, +}); + +const OtpModel = mongoose.model("otp", OtpSchema); +export { OtpModel }; diff --git a/server/models/userModel.js b/server/models/userModel.js new file mode 100644 index 0000000..f4a206d --- /dev/null +++ b/server/models/userModel.js @@ -0,0 +1,101 @@ +import mongoose from "mongoose"; +import bcrypt from "bcryptjs"; +import jwt from "jsonwebtoken"; + +const { genSalt, hash, compare } = bcrypt; +const UserSchema = new mongoose.Schema( + { + fullname: { + type: String, + required: true, + }, + email: { + type: String, + required: true, + }, + password: { + type: String, + select: false, + }, + role: { + type: [String], + default: ["user"], + }, + isVerified: { + type: Boolean, + default: false, + }, + }, + + { + timestamps: true, + } +); + +// Attachments : Genrating a JWT Token +UserSchema.methods.generateJwtToken = function () { + return jwt.sign({ user: this._id.toString() }, process.env.JWT_SECRET, { + expiresIn: "7d", + }); +}; + +// Encrypting the user Password +UserSchema.pre("save", function (next) { + // gives the data of current user + const user = this; + + //password is modified + if (!user.isModified("password")) return next(); + + //generate bcrypt salt : means mkaing encrypting password more stronger + bcrypt.genSalt(8, (error, salt) => { + if (error) return next(error); + + // hash the password + bcrypt.hash(user.password, salt, (error, hash) => { + if (error) return next(error); + + // assigning hashed password + user.password = hash; + + return next(); + }); + }); +}); + +// Helper Function : +UserSchema.statics.isUserExist = async ({ email }) => { + const isEmailExist = await UserModel.findOne({ email }); + + if (isEmailExist) { + // return res.status(404).json({ + // success: false, + // message: "User already Exists...", + // }); + throw new Error("User already Exists...."); + } +}; + +UserSchema.statics.findByEmailAndPassword = async ({ email, password }) => { + const user = await UserModel.findOne({ email }).select("password"); + + if (!user) { + // return res.status(404).json({ + // success: false, + // message: "User does not Exists.... !", + // }); + throw new Error("User does not Exists.... !"); + } + + // compare password + const doesPasswordMatch = await bcrypt.compare(password, user.password); + + if (!doesPasswordMatch) { + throw new Error("Invalid Credentials !!!"); + } + + return user; +}; + +const UserModel = mongoose.model("user", UserSchema); +export { UserModel }; diff --git a/server/package.json b/server/package.json index 440ff8f..fe37cfe 100644 --- a/server/package.json +++ b/server/package.json @@ -18,7 +18,12 @@ "jsonwebtoken": "^9.0.2", "mongoose": "^8.3.5", "morgan": "^1.10.0", + + "nodemailer": "^6.9.13", "nodemon": "^3.1.0", + "passport": "^0.7.0" + "stripe": "^15.6.0" + } } diff --git a/server/routes/auth/authRoutes.js b/server/routes/auth/authRoutes.js new file mode 100644 index 0000000..16663d2 --- /dev/null +++ b/server/routes/auth/authRoutes.js @@ -0,0 +1,16 @@ +import express from "express"; +// import passport from "passport"; +// import { UserModel } from "../../models/userModel"; +// import { authenticateJWT } from "../../config/JWTAuthentication"; +import { + forgotPassword, + setNewPassword, + verifyAndResetPassword, +} from "../../controllers/auth/authController.js"; + +const authRouter = express.Router(); + +authRouter.post("/forgot-password", forgotPassword); +authRouter.post("/verify/password-otp", verifyAndResetPassword); +authRouter.post("/reset/set-new-password", setNewPassword); +export { authRouter }; diff --git a/server/utils/decodeJwtToken.js b/server/utils/decodeJwtToken.js new file mode 100644 index 0000000..eaac97b --- /dev/null +++ b/server/utils/decodeJwtToken.js @@ -0,0 +1,7 @@ +import jwt from "jsonwebtoken"; +// import dotenv from "dotenv"; +// dotenv.config(); + +export const decodeJWT = async (token) => { + return jwt.verify(token, process.env.JWT_SECRET); +}; diff --git a/server/utils/generateJwtToken.js b/server/utils/generateJwtToken.js new file mode 100644 index 0000000..d350c82 --- /dev/null +++ b/server/utils/generateJwtToken.js @@ -0,0 +1,11 @@ +// const jwt = require("jsonwebtoken"); +// const { JWT_SECRET } = require("./keys"); +import jwt from "jsonwebtoken"; +// const JWT_SECRET = process.env.JWT_SECRET; + +export const generateToken = (id, tokenValidity = "30d") => { + // console.log(tokenValidity); + return jwt.sign({ id }, process.env.JWT_SECRET, { + expiresIn: tokenValidity, + }); +}; diff --git a/server/utils/generateOTP.js b/server/utils/generateOTP.js new file mode 100644 index 0000000..bd9e92b --- /dev/null +++ b/server/utils/generateOTP.js @@ -0,0 +1,11 @@ +export const generateOTP = (length = 4) => { + const chars = "0123456789"; + let otp = ""; + + for (let i = 0; i < length; i++) { + const randomIndex = Math.floor(Math.random() * chars.length); + otp += chars[randomIndex]; + } + + return otp; +}; diff --git a/server/utils/sendEmail.js b/server/utils/sendEmail.js new file mode 100644 index 0000000..eb1b45c --- /dev/null +++ b/server/utils/sendEmail.js @@ -0,0 +1,30 @@ +import nodemailer from "nodemailer"; + +const sendEmail = async (options) => { + const transporter = nodemailer.createTransport({ + host: process.env.SMTP_HOST, + port: process.env.SMTP_PORT, + service: process.env.SMTP_SERVICES, + auth: { + user: process.env.SMTP_MAIL, + pass: process.env.SMTP_PASSWORD, + }, + }); + + const mailOptions = { + from: process.env.SMTP_MAIL, + to: options.mail, + subject: options.subject, + html: options.messageContent, + }; + + const mailInfo = transporter.sendMail(mailOptions, (error, result) => { + if (error) { + console.log(error); + // throw new Error("OTP can't be send"); + } + }); + console.log(mailInfo); +}; + +export { sendEmail };