From 864a31afb7b028eed2dedf7213f3b4098242ddac Mon Sep 17 00:00:00 2001 From: MuntazirHaider Date: Mon, 27 Nov 2023 10:26:04 +0530 Subject: [PATCH] Feat-Mutation for forgot password option #95 --- graphql/resolvers.js | 32 ++++++++++++++++++++++++++++++++ graphql/schema.js | 14 ++++++++++++++ index.mjs | 3 ++- package-lock.json | 28 ++++++++++++++++++++++++++++ package.json | 2 ++ permissions/index.js | 3 +++ utils/generate_otp.js | 7 +++++++ utils/send_email.js | 36 ++++++++++++++++++++++++++++++++++++ 8 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 utils/generate_otp.js create mode 100644 utils/send_email.js diff --git a/graphql/resolvers.js b/graphql/resolvers.js index 3714d37..2ebe08e 100644 --- a/graphql/resolvers.js +++ b/graphql/resolvers.js @@ -9,6 +9,7 @@ const Group = require("../models/group.js"); const Landmark = require("../models/landmark.js"); const { User } = require("../models/user.js"); const { MongoServerError } = require("mongodb"); +const { sendEmail } = require('../utils/send_email.js'); const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // even if we generate 10 IDs per hour, @@ -58,6 +59,37 @@ const resolvers = { }, Mutation: { + + requestPasswordReset: async (_parent, { email }) => { + if (!email) return new UserInputError("Email is required to reset password"); + const user = await User.findOne({ email }); + if (!user) return new UserInputError("User with this email not exist"); + const otp = sendEmail(email); + const encryptOtp = await bcrypt.hash(otp, 10); + return { message: "Otp send successfully", success: true, encryptOtp }; + }, + + verifyPasswordResetOtp: async (_parent, { userOtp }, { otp }) => { + if (!userOtp || !otp) return new UserInputError("OTP is required"); + const verified = await bcrypt.compare(userOtp, otp); + if (!verified) return new UserInputError("OTP doesn't match"); + return { message: "Otp matched successfully", success: true }; + }, + + resetPassword: async (_parent, { credentials }) => { + const { email, password } = credentials; + if (!email) return new UserInputError("Email is required"); + if (!password) return new UserInputError("Password is required"); + const user = await User.findOne({ email }); + if (!user) return new UserInputError("User with this email does not exist"); + const encryptNewPass = await bcrypt.hash(password, 10); + await User.updateOne( + { email: email }, + { $set: { password: encryptNewPass } } + ); + return { message: "Password changed successfully", success: true }; + }, + register: async (_parent, { user }) => { const { name, credentials } = user; diff --git a/graphql/schema.js b/graphql/schema.js index c0d0662..f19fcdd 100644 --- a/graphql/schema.js +++ b/graphql/schema.js @@ -104,6 +104,17 @@ const typeDefs = gql` hello: String } + type requestPasswordResetResponse { + message: String! + success: Boolean! + encryptOtp: String! + } + + type PasswordResetResponse { + message: String! + success: Boolean! + } + type Mutation { """ if start time not supplied, default is Date.now @@ -122,6 +133,9 @@ const typeDefs = gql` changeBeaconDuration(newExpiresAt: Float!, beaconID: ID!): Beacon! createGroup(group: GroupInput): Group! joinGroup(shortcode: String!): Group! + requestPasswordReset(email: String!): requestPasswordResetResponse! + verifyPasswordResetOtp(userOtp: String!): PasswordResetResponse! + resetPassword(credentials: AuthPayload): PasswordResetResponse! } type Subscription { diff --git a/index.mjs b/index.mjs index 0f68fc2..f125cf9 100644 --- a/index.mjs +++ b/index.mjs @@ -21,8 +21,9 @@ const server = new ApolloServer({ if (connection) { return { user: connection.context.user, pubsub }; } + const otp = req?.headers?.otp || (connection?.context?.headers?.otp ? connection.context.headers.otp : null); const user = req?.user ? await User.findById(req.user.sub).populate("beacons") : null; - return { user, pubsub }; + return { user, pubsub, otp }; }, stopGracePeriodMillis: Infinity, stopOnTerminationSignals: false, diff --git a/package-lock.json b/package-lock.json index 7fea523..f34a470 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,8 @@ "jsonwebtoken": "^8.5.1", "mongoose": "^6.2.6", "nanoid": "^3.3.1", + "nodemailer": "^6.9.7", + "otp-generator": "^4.0.1", "pm2": "^5.2.0" }, "devDependencies": { @@ -8579,6 +8581,14 @@ "node": ">=6" } }, + "node_modules/nodemailer": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.7.tgz", + "integrity": "sha512-rUtR77ksqex/eZRLmQ21LKVH5nAAsVicAtAYudK7JgwenEDZ0UIQ1adUGqErz7sMkWYxWTTU1aeP2Jga6WQyJw==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -8840,6 +8850,14 @@ "node": ">=0.10.0" } }, + "node_modules/otp-generator": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/otp-generator/-/otp-generator-4.0.1.tgz", + "integrity": "sha512-2TJ52vUftA0+J3eque4wwVtpaL4/NdIXDL0gFWFJFVUAZwAN7+9tltMhL7GCNYaHJtuONoier8Hayyj4HLbSag==", + "engines": { + "node": ">=14.10.0" + } + }, "node_modules/p-cancelable": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", @@ -18759,6 +18777,11 @@ "sorted-array-functions": "^1.3.0" } }, + "nodemailer": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.7.tgz", + "integrity": "sha512-rUtR77ksqex/eZRLmQ21LKVH5nAAsVicAtAYudK7JgwenEDZ0UIQ1adUGqErz7sMkWYxWTTU1aeP2Jga6WQyJw==" + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -18946,6 +18969,11 @@ "dev": true, "peer": true }, + "otp-generator": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/otp-generator/-/otp-generator-4.0.1.tgz", + "integrity": "sha512-2TJ52vUftA0+J3eque4wwVtpaL4/NdIXDL0gFWFJFVUAZwAN7+9tltMhL7GCNYaHJtuONoier8Hayyj4HLbSag==" + }, "p-cancelable": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", diff --git a/package.json b/package.json index 2c9f3d1..2636947 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,8 @@ "jsonwebtoken": "^8.5.1", "mongoose": "^6.2.6", "nanoid": "^3.3.1", + "nodemailer": "^6.9.7", + "otp-generator": "^4.0.1", "pm2": "^5.2.0" }, "devDependencies": { diff --git a/permissions/index.js b/permissions/index.js index e394e83..3943d5d 100644 --- a/permissions/index.js +++ b/permissions/index.js @@ -8,6 +8,9 @@ const permissions = shield({ }, Mutation: { "*": isAuthenticated, + requestPasswordReset: not(isAuthenticated), + verifyPasswordResetOtp: not(isAuthenticated), + resetPassword: not(isAuthenticated), register: not(isAuthenticated), login: not(isAuthenticated), }, diff --git a/utils/generate_otp.js b/utils/generate_otp.js new file mode 100644 index 0000000..0b046a1 --- /dev/null +++ b/utils/generate_otp.js @@ -0,0 +1,7 @@ +const otpGenerator = require('otp-generator') + +const generateOtp = () => { + return otpGenerator.generate(6, { upperCaseAlphabets: false, lowerCaseAlphabets: false, specialChars: false }); +} + +module.exports = {generateOtp}; \ No newline at end of file diff --git a/utils/send_email.js b/utils/send_email.js new file mode 100644 index 0000000..1221582 --- /dev/null +++ b/utils/send_email.js @@ -0,0 +1,36 @@ +const nodemailer = require("nodemailer"); +const { generateOtp } = require("./generate_otp"); + +const transporter = nodemailer.createTransport({ + host: process.env.SMTP_HOST, + port: process.env.SMTP_PORT, + secure: true, + auth: { + user: process.env.SMTP_MAIL, + pass: process.env.SMTP_PASSWORD, + }, +}); + +const sendEmail = (email) => { + + const otp = generateOtp(); + var mailOptions = { + from: process.env.SMTP_MAIL, + to: email, + subject: "OTP from CCExtractor Beacon", + html: `

Dear User, Your OTP for reseting password from CCExtractor BEACON is :

${otp}` + } + + transporter.sendMail(mailOptions, (error, info) => { + if(error){ + console.log("Error -", error); + return false; + }else{ + console.log("Email send Successfully"); + return true; + } + }) + return otp; +} + +module.exports = {sendEmail}; \ No newline at end of file