From 78d0446dc2fd9e2b5d23ff9a9c273d4378d3b133 Mon Sep 17 00:00:00 2001 From: airizarryOSU <67487429+airizarryOSU@users.noreply.github.com> Date: Thu, 30 Jul 2020 16:48:07 -0400 Subject: [PATCH 1/3] added files for password reset --- index.js | 11 ++++++++ package-lock.json | 11 +++----- public/css/styles.css | 58 +++++++++++++++++++++++++++++++++++++++++ reset.js | 15 +++++++++++ views/reset.handlebars | 20 ++++++++++++++ views/signup.handlebars | 3 +++ 6 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 reset.js create mode 100644 views/reset.handlebars diff --git a/index.js b/index.js index 0b36bd6..365b854 100644 --- a/index.js +++ b/index.js @@ -71,6 +71,8 @@ app.use('/task', require('./task.js')); app.use('/mytasks', require('./myTasks.js')); +app.use('/pass/reset', require('./reset.js')); + app.get('/', function(req, res, next) { var context = {}; res.render('signup', context); @@ -81,6 +83,15 @@ app.get('/login', function(req, res, next) { res.render('login', context); }); +app.get('/reset', function(req, res, next) { + const context = { email: ''}; + res.render('reset', context); +}); + +app.post('/pass/reset', function(req, res) { + console.log(req.body.user_email); +}); + app.post('/add-new-user', function(req, res) { mysql.pool.query( "SELECT id from users where email=" + mysql.pool.escape(req.body.user_email), diff --git a/package-lock.json b/package-lock.json index 6d2d77b..412b655 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "optional": true, "requires": { "kind-of": "^3.0.2", "longest": "^1.0.1", @@ -805,8 +804,7 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=", - "optional": true + "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=" }, "is-ci": { "version": "2.0.0", @@ -951,7 +949,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -1009,8 +1006,7 @@ "longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "optional": true + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" }, "lowercase-keys": { "version": "1.0.1", @@ -1402,8 +1398,7 @@ "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "optional": true + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" }, "responselike": { "version": "1.0.2", diff --git a/public/css/styles.css b/public/css/styles.css index 86cb634..e41199f 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -181,6 +181,64 @@ body { margin-bottom: 30px; } +/* reset page CSS */ +#reset-page #danger { + color: red; +} + +#reset-page * { + box-sizing: border-box; +} + +#reset-page { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; +} + +#reset-page .form-content { + width: 50%; + max-width: 600px; + background-color: white; + border-radius: 3px; + border: 1px solid #808080; + padding: 2% 5%; +} + +#reset-page label { + margin: 10px 0; + display: block; +} + +#reset-page input { + background: none; + border: 1px solid #808080; + border-radius: 0px; + width: 100%; + padding: 10px; +} + +#reset-page .button.button-block { + display: block; + margin-top: 20px; + background: #fcbd01; + border-color: #fcbd01; + color: #fff; + font-size: 0.9em; + font-weight: 900; + letter-spacing: 1px; +} + +#reset-page .button.button-block:hover { + background: #fcb101; + border-color: #fcb101; +} + +#reset_form .input-group:last-of-type { + margin-bottom: 30px; +} + /* buttons */ .btn.btn-primary { diff --git a/reset.js b/reset.js new file mode 100644 index 0000000..8e45d35 --- /dev/null +++ b/reset.js @@ -0,0 +1,15 @@ +//Was trying to put all the password reset stuff here, but was unable to grab it +//so its still currently in index.js + +//const express = require('express'); +//const mysql = require('./dbcon.js'); +//const router = express.Router(); +// +//router.post('/pass/reset', function(req, res) { +// +//console.log(req.body); +// +// +//}); +// +//module.exports = router; \ No newline at end of file diff --git a/views/reset.handlebars b/views/reset.handlebars new file mode 100644 index 0000000..905afa6 --- /dev/null +++ b/views/reset.handlebars @@ -0,0 +1,20 @@ +
+
+

Enter Email Address

+
+
+ + +
+
+ +
+
+
+
\ No newline at end of file diff --git a/views/signup.handlebars b/views/signup.handlebars index db6e749..4d24bdf 100644 --- a/views/signup.handlebars +++ b/views/signup.handlebars @@ -40,6 +40,9 @@
Already have an account? Login
+
+
Forgot your password? Reset
+
\ No newline at end of file From 721d4a7e284daf8dd439f00a4389bb5f2f388797 Mon Sep 17 00:00:00 2001 From: Sarah Forest Date: Thu, 30 Jul 2020 20:58:27 -0400 Subject: [PATCH 2/3] password reset functionality --- EC3_DDL.sql | 6 +- index.js | 157 ++++++++++++++++++++++++++++++++++++++-- package-lock.json | 16 +++- package.json | 3 +- public/css/styles.css | 26 +++++-- views/forgot.handlebars | 27 +++++++ views/login.handlebars | 3 + views/reset.handlebars | 41 ++++++----- views/signup.handlebars | 3 - 9 files changed, 242 insertions(+), 40 deletions(-) create mode 100644 views/forgot.handlebars diff --git a/EC3_DDL.sql b/EC3_DDL.sql index 56e8860..6857e42 100644 --- a/EC3_DDL.sql +++ b/EC3_DDL.sql @@ -15,7 +15,9 @@ CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, `name` varchar(255) DEFAULT NULL, `email` varchar(255) DEFAULT NULL, -`password` varchar(500) DEFAULT NULL +`password` varchar(500) DEFAULT NULL, +`token` varchar(255) DEFAULT NULL, +`tokenExpiration` varchar(255) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ALTER TABLE `users` @@ -77,4 +79,4 @@ INSERT INTO `tasks` (`project_id`, `name`, `assignee_id`, `due_date`, `status`, (2, 'Create template files.', 2, '2020-07-10', 'To Do', 'Use stagehand.'); INSERT INTO `subtasks` (`project_id`, `task_id`, `name`, `assignee_id`, `due_date`, `status`, `description`) VALUES -(2, 1, 'sample subtask', 1, '2020-07-15', 'To Do', 'sample subtask description'); +(2, 1, 'sample subtask', 1, '2020-07-15', 'To Do', 'sample subtask description'); \ No newline at end of file diff --git a/index.js b/index.js index 365b854..6b236cb 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,8 @@ var express = require('express'); var bodyParser = require('body-parser'); var CryptoJS = require("crypto-js"); +var crypto = require("crypto"); +var nodemailer = require('nodemailer'); var path = require('path'); var session = require('express-session'); @@ -71,7 +73,7 @@ app.use('/task', require('./task.js')); app.use('/mytasks', require('./myTasks.js')); -app.use('/pass/reset', require('./reset.js')); +//app.use('/pass/reset', require('./reset.js')); app.get('/', function(req, res, next) { var context = {}; @@ -83,13 +85,158 @@ app.get('/login', function(req, res, next) { res.render('login', context); }); -app.get('/reset', function(req, res, next) { +app.get('/forgot', function(req, res, next) { const context = { email: ''}; - res.render('reset', context); + res.render('forgot', context); }); -app.post('/pass/reset', function(req, res) { - console.log(req.body.user_email); + +function getToken(res, user, complete) { + crypto.randomBytes(20, function(err, buf) { + var token = buf.toString('hex'); + user.resetPasswordToken = token; + complete(); + }); +} + +function checkIfUserExists(email, res, user, complete) { + mysql.pool.query( + "SELECT id from users where email=" + mysql.pool.escape(email), + function(err, result) { + // if error, handle by outputting issue encountered + if (err) { + console.log(JSON.stringify(err)); + res.write(JSON.stringify(err)); + res.end(); + } + // if id exists in the db, user already exists, don't proceed with sign up + else if (!result[0] || !result[0].id) { + console.log("Doesnt exist") + res.render('forgot', { + errors: 'Email address doesn\'t exist.', + }); + } + else { + user.email = email; + user.id = result[0].id; + user.resetPasswordExpires = Date.now() + 3600000; // 1 hour + complete(); + } + }) +} + +function updateUserTokens(res, user, complete) { + var sql = "UPDATE users SET token = ?, tokenExpiration = ? WHERE id = ?"; + var inserts = [user.resetPasswordToken, user.resetPasswordExpires, user.id]; + sql = mysql.pool.query(sql, inserts, function(error, results, fields){ + if(error){ + res.write(JSON.stringify(error)); + res.status(400); + res.end(); + } + complete(); + }) +} + +app.post('/pass/reset', function(req, res, next) { + var callbackCount = 0; + var user = {}; + + getToken(res, user, complete); + function complete(){ + callbackCount++; + if(callbackCount == 1){ + checkIfUserExists(req.body.user_email, res, user, complete); + } else if (callbackCount == 2) { + updateUserTokens(res, user, complete); + } else if (callbackCount == 3) { + + const transporter = nodemailer.createTransport({ + service: 'gmail', + auth: { + user: 'cs361osu@gmail.com', + pass: 'superlongstringtest123' + } + }); + + const mailOptions = { + from: 'admin@ec3taskmanagement.com', + to: user.email, + subject: 'Password Reset Requested', + text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' + + 'Please click on the following link, or paste this into your browser to complete the process:\n\n' + + 'http://' + req.headers.host + '/reset/' + user.resetPasswordToken + '\n\n' + + 'If you did not request this, please ignore this email and your password will remain unchanged.\n' + }; + + transporter.sendMail(mailOptions, function(error, info) { + if (error) { + console.log(error); + } else { + console.log('Email sent: ' + info.response); + } + }); + res.render('forgot', { + info: 'An e-mail has been sent to ' + user.email + ' with further instructions.', + }); + } + } +}); + + +app.get('/reset/:token', function(req, res) { + + var sql = "SELECT id, tokenExpiration from users WHERE token = ?"; + var inserts = [req.params.token]; + sql = mysql.pool.query(sql, inserts, function(error, result, fields){ + if(error){ + res.write(JSON.stringify(error)); + res.status(400); + res.end(); + } + else if (!result[0] || !result[0].id) { + + res.render('reset', { + errors: 'Password reset token is invalid.', + link: 'http://' + req.headers.host + '/forgot' + }); + } + else if (Date.now() > result[0].tokenExpiration) { + res.render('reset', { + errors: 'Password reset token is expired.', + link: 'http://' + req.headers.host + '/forgot' + }); + } + else { + var context = { + id: result[0].id, + token: Date.now(), + showForm: true + }; + res.render('reset', context) + } + }) +}); + +app.post('/reset-password', function(req, res, next) { + + var ciphertext = CryptoJS.AES.encrypt(req.body.password, config.cryptoSecret).toString(); + + var sql = "UPDATE users SET password = ?, tokenExpiration = ? WHERE id = ?"; + var inserts = [ciphertext, req.body.token, req.body.id]; + + sql = mysql.pool.query(sql, inserts, function(error, result, fields){ + if(error){ + res.write(JSON.stringify(error)); + res.status(400); + res.end(); + } + + res.render('reset', { + info: 'Password reset successfully.', + link: 'http://' + req.headers.host + '/login' + }); + }) }); app.post('/add-new-user', function(req, res) { diff --git a/package-lock.json b/package-lock.json index 412b655..b2d2700 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "optional": true, "requires": { "kind-of": "^3.0.2", "longest": "^1.0.1", @@ -804,7 +805,8 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=" + "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=", + "optional": true }, "is-ci": { "version": "2.0.0", @@ -949,6 +951,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -1006,7 +1009,8 @@ "longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "optional": true }, "lowercase-keys": { "version": "1.0.1", @@ -1109,6 +1113,11 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha1-/qz3zPUlp3rpY0Q2pkiD/+yjRvs=" }, + "nodemailer": { + "version": "6.4.11", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.11.tgz", + "integrity": "sha512-BVZBDi+aJV4O38rxsUh164Dk1NCqgh6Cm0rQSb9SK/DHGll/DrCMnycVDD7msJgZCnmVa8ASo8EZzR7jsgTukQ==" + }, "nodemon": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.4.tgz", @@ -1398,7 +1407,8 @@ "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "optional": true }, "responselike": { "version": "1.0.2", diff --git a/package.json b/package.json index 119a960..53b7144 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "express-handlebars": "^2.0.1", "express-session": "^1.17.0", "jsonwebtoken": "^8.5.1", - "mysql": "^2.8.0" + "mysql": "^2.8.0", + "nodemailer": "^6.4.11" }, "bugs": { "url": "https://github.com/sarahforest/cs361/issues" diff --git a/public/css/styles.css b/public/css/styles.css index e41199f..121ca75 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -186,18 +186,18 @@ body { color: red; } -#reset-page * { +#reset-page *, #forgot-page * { box-sizing: border-box; } -#reset-page { +#reset-page, #forgot-page { display: flex; justify-content: center; align-items: center; height: 100vh; } -#reset-page .form-content { +#reset-page .form-content, #forgot-page .form-content { width: 50%; max-width: 600px; background-color: white; @@ -206,12 +206,12 @@ body { padding: 2% 5%; } -#reset-page label { +#reset-page label, #forgot-page label { margin: 10px 0; display: block; } -#reset-page input { +#reset-page input, #forgot-page input { background: none; border: 1px solid #808080; border-radius: 0px; @@ -219,7 +219,7 @@ body { padding: 10px; } -#reset-page .button.button-block { +#reset-page .button.button-block, #forgot-page .button.button-block { display: block; margin-top: 20px; background: #fcbd01; @@ -230,12 +230,12 @@ body { letter-spacing: 1px; } -#reset-page .button.button-block:hover { +#reset-page .button.button-block:hover, #forgot-page .button.button-block:hover { background: #fcb101; border-color: #fcb101; } -#reset_form .input-group:last-of-type { +#reset_form .input-group:last-of-type, #forgot-page .input-group:last-of-type { margin-bottom: 30px; } @@ -284,3 +284,13 @@ body { border-top: 4px solid #fcbd01; } +#login-page .login-section { + color: #FFB703; +} + +#login-page .login-section a, #forgot-page a, #reset-page a { + color: #FFB703 !important; + text-decoration: underline !important; + cursor: pointer !important; +} + diff --git a/views/forgot.handlebars b/views/forgot.handlebars new file mode 100644 index 0000000..e187076 --- /dev/null +++ b/views/forgot.handlebars @@ -0,0 +1,27 @@ +
+
+

Enter Email Address

+ {{#if errors}} +
{{errors}}
+ {{/if}} + {{#if info}} +
{{info}}
+ {{else}} +
+
+ + +
+
+ +
+
+ {{/if}} +
+
\ No newline at end of file diff --git a/views/login.handlebars b/views/login.handlebars index f6d0b19..f6565fa 100644 --- a/views/login.handlebars +++ b/views/login.handlebars @@ -29,5 +29,8 @@ +
+
Forgot your password? Reset
+
\ No newline at end of file diff --git a/views/reset.handlebars b/views/reset.handlebars index 905afa6..47cfc8d 100644 --- a/views/reset.handlebars +++ b/views/reset.handlebars @@ -1,20 +1,25 @@
-
-

Enter Email Address

-
-
- - -
-
- -
-
-
+
+

Reset Password

+
+ {{#if errors}} +
{{errors}} Reset password.
+ {{/if}} + {{#if info}} +
{{info}} Login.
+ {{/if}} + {{#if showForm}} +
+
+ + + + +
+
+ +
+
+ {{/if}} +
\ No newline at end of file diff --git a/views/signup.handlebars b/views/signup.handlebars index 4d24bdf..db6e749 100644 --- a/views/signup.handlebars +++ b/views/signup.handlebars @@ -40,9 +40,6 @@
Already have an account? Login
-
-
Forgot your password? Reset
-
\ No newline at end of file From 843803c2ea90510077baa4393611f5da98604d56 Mon Sep 17 00:00:00 2001 From: airizarryOSU <67487429+airizarryOSU@users.noreply.github.com> Date: Fri, 31 Jul 2020 18:12:18 -0400 Subject: [PATCH 3/3] Delete reset.js File no longer needed --- reset.js | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 reset.js diff --git a/reset.js b/reset.js deleted file mode 100644 index 8e45d35..0000000 --- a/reset.js +++ /dev/null @@ -1,15 +0,0 @@ -//Was trying to put all the password reset stuff here, but was unable to grab it -//so its still currently in index.js - -//const express = require('express'); -//const mysql = require('./dbcon.js'); -//const router = express.Router(); -// -//router.post('/pass/reset', function(req, res) { -// -//console.log(req.body); -// -// -//}); -// -//module.exports = router; \ No newline at end of file