diff --git a/package-lock.json b/package-lock.json index b0ef5c6..7e5d75a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -472,6 +472,11 @@ "which": "^1.2.9" } }, + "crypto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", + "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==" + }, "crypto-random-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", @@ -869,7 +874,8 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -887,11 +893,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -904,15 +912,18 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -1015,7 +1026,8 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -1025,6 +1037,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1037,17 +1050,20 @@ "minimatch": { "version": "3.0.4", "bundled": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "bundled": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -1064,6 +1080,7 @@ "mkdirp": { "version": "0.5.1", "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -1136,7 +1153,8 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -1146,6 +1164,7 @@ "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { "wrappy": "1" } @@ -1221,7 +1240,8 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true + "bundled": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -1251,6 +1271,7 @@ "string-width": { "version": "1.0.2", "bundled": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -1268,6 +1289,7 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -1306,11 +1328,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true + "bundled": true, + "optional": true } } }, @@ -2060,6 +2084,11 @@ "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz", "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==" }, + "nodemailer": { + "version": "6.4.11", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.11.tgz", + "integrity": "sha512-BVZBDi+aJV4O38rxsUh164Dk1NCqgh6Cm0rQSb9SK/DHGll/DrCMnycVDD7msJgZCnmVa8ASo8EZzR7jsgTukQ==" + }, "nodemon": { "version": "1.19.3", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.19.3.tgz", diff --git a/package.json b/package.json index 638cbba..37eafc5 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "body-parser": "^1.19.0", "config": "^3.2.3", "cors": "^2.8.5", + "crypto": "^1.0.1", "emailjs": "^3.3.0", "emailjs-com": "^2.6.3", "express": "^4.17.1", @@ -30,6 +31,7 @@ "jsonwebtoken": "^8.5.1", "mongo-sanitize": "^1.0.1", "mongoose": "^5.7.4", + "nodemailer": "^6.4.11", "nodemon": "^1.19.3", "uuid": "^3.3.3" } diff --git a/src/app.js b/src/app.js index f7e90c7..ff83a09 100644 --- a/src/app.js +++ b/src/app.js @@ -19,5 +19,6 @@ app.use('/api/groups', require('./routes/groups')); app.use('/api/contact', require('./routes/contact')); app.use('/api/events', require('./routes/events')); app.use('/api/projects', require('./routes/projects')); +app.use('/api/password', require('./routes/reset')); app.listen(PORT, () => console.log(`server started at port ${PORT}`)); diff --git a/src/docs/index.html b/src/docs/index.html index 3b050d4..5a28a0a 100644 --- a/src/docs/index.html +++ b/src/docs/index.html @@ -30,6 +30,19 @@

Available Routes

OUTPUT DESCRIPTION + + LOGIN + [POST]/api/password/forget + {email:''} + {info:'',msg:'',token:''} + Accepts email and send user an email with link and returns reset token + + + [POST]/api/password/reset + {email:'',token:'',password:''} + {user:'',msg:''} + Accepts email, token and password and return user and msg after resetting the password + REGISTER [POST]/api/register diff --git a/src/models/user.js b/src/models/user.js index 50e55df..cdca37e 100644 --- a/src/models/user.js +++ b/src/models/user.js @@ -1,5 +1,16 @@ const mongoose = require('mongoose'); +const paassWordReset = new mongoose.Schema({ + token: { + type: String, + default: '' + }, + expiry: { + type: Date, + default: Date.now()+2*86400000 + } +}) + const usersSchema = new mongoose.Schema({ uid: { type: String, @@ -49,7 +60,8 @@ const usersSchema = new mongoose.Schema({ registeredEvents: { type: [String], default: [] - } + }, + resetInfo: paassWordReset }); module.exports = mongoose.model('Users', usersSchema); diff --git a/src/routes/reset.js b/src/routes/reset.js new file mode 100644 index 0000000..f6d4aa5 --- /dev/null +++ b/src/routes/reset.js @@ -0,0 +1,149 @@ +const express = require('express'); +const router = express.Router(); +const bcrypt = require('bcryptjs'); +const jwt = require('jsonwebtoken'); +const config = require('config'); +const crypto = require('crypto'); +const nodemailer = require('nodemailer'); +const sanitize = require('mongo-sanitize'); + +const transporter = nodemailer.createTransport({ + service: 'gmail', + auth: { + user: 'technojam.reply@gmail.com' , + pass: 'enter app password here' + } +}); + +const mailOptions = { + from: 'Password Reset ', // Something like: John Snow + to: null, + subject: 'noReply-password reset info', // email subject +}; + +const reset_expiry_days = 2; // number of days after which reset link expires +const exp_ms = reset_expiry_days*24*60*60*1000; // days to millisecond + +const User = require('../models/user'); + +/* +expects body +{ + email: "users email" +} +*/ +router.post('/forget', (req, res) => { + User.findOne(req.body) + .then(user => { + if(!user){ + return res.status(400).json({ msg: `No user with ${req.body.email} email`}); + } + + crypto.randomBytes(48, (err, buffer)=>{ + if(err){ + return res.status(500).json({ msg: `Reset token not generated`}); + } + const token = buffer.toString('hex'); + user.resetInfo = { + token: token, + expiry: Date.now()+exp_ms + } + + user.save() + .then(user => { + + const html = ` +

Hello ${user.name},

+

You have requested to reset the password of your account kindly visit below link for further instructions.

+
+ ${req.hostname} +

+ `; + mailOptions.html = html; + mailOptions.to = user.email; + transporter.sendMail(mailOptions, (err, info)=>{ + if(err){ + res.status(500).json(err); + }else{ + res.status(200).json({info: info, + msg: 'Mail has been send to your registered id', + token: user.resetInfo + }) + } + }); + + }, err => res.status(500).send('Server error')) + .catch(err => { + console.error(err.message); + res.status(500).send('Server error'); + }) + + }); + + }, err => res.status(500).send('Server error')) + .catch(err => { + console.error(err.message); + res.status(500).send('Server error'); + }) +}); + + +/* +expects body +{ + token: "token send to users email", + email: "users email", + password: "new password" +} +*/ +router.post('/reset', (req, res)=>{ + User.findOne({ + email: req.body.email + }) + .then(user => { + if(!user){ + return res.status(400).json({ msg: `No user with ${req.body.email} email`}); + } + if(user.resetInfo.expiry == undefined){ + return res.status(200).json({ msg: `Invalid url`}); + } + const currDate = Date.now(); + if(currDate > user.resetInfo.expiry) { + return res.status(200).json({ msg: `Sorry this link is expired`}); + } + if(req.body.token != this.user.resetInfo.token){ + return res.status(200).json({ msg: `Invalid token`}); + } + + bcrypt.genSalt(10, (err, salt) => { + if(err){ + return res.status(500).json(err); + } + bcrypt.hash(req.body.password, salt, (err, hash)=>{ + if(err){ + return res.status(500).json(err); + } + user.password = hash; + user.resetInfo = undefined; + user.save() + .then(user => { + if(!user){ + return res.status(500).send('Server error'); + } + res.status(200).json({user: user, msg:'Password successfully reset'}) + }, err => res.status(500).send('Server error')) + .catch(err => { + console.error(err.message); + res.status(500).send('Server error'); + }) + }); + }); + + }, err => res.status(500).send('Server error')) + .catch(err => { + console.error(err.message); + res.status(500).send('Server error'); + }) +}) + +module.exports = router; \ No newline at end of file