127.0.0.1:8000 watch-together / f9d7f6d
Implement password reset (except trigger it). Add good password validation. Fix responses / flow for request token endpoints Seva 4 years ago
15 changed file(s) with 261 addition(s) and 49 deletion(s). Raw diff Collapse all Expand all
2929 "update": {
3030 "display-name": function(req, res) {
3131 new Promise(function(resolve, reject) {
32 if (req.body.display_name) {
32 if (req.body.display_name && req.body.display_name.trim()) {
3333 resolve();
3434 } else {
35 reject("Could not update username");
35 reject("Could not update username: Username cannot be empty");
3636 }
3737 }).then(function() {
3838 return utils.query("UPDATE users SET display_name=? WHERE user_id=?", [
5252 },
5353 "password": function(req, res) {
5454 new Promise(function(resolve, reject) {
55 if (req.body.new_password && req.body.current_password) {
55 if (utils.validate.password(req.body.new_password)) {
5656 resolve();
5757 } else {
58 reject("Could not update username");
58 reject("New password invalid");
5959 }
6060 }).then(function() {
61 return bcrypt.compare(req.body.current_password, req.user.password);
61 if (req.body.current_password) {
62 return bcrypt.compare(req.body.current_password, req.user.password);
63 }
64 return false;
6265 }).then(function(result) {
6366 return new Promise(function(resolve, reject) {
6467 if (result) {
8992 "logout": function(req, res) {
9093 req.logout();
9194 res.send(utils.ok());
95 },
96 "password-reset": {
97 "": [{
98 auth: false
99 }, function(req, res) {
100 let email;
101
102 new Promise(function(resolve, reject) {
103 if (utils.validate.password(req.body.new_password)) {
104 resolve();
105 } else {
106 reject("New password invalid");
107 }
108 }).then(function() {
109 if (req.body.token) {
110 return utils.query("SELECT * FROM password_reset WHERE reset_id=? AND expires>?", [
111 req.body.token, Date.now()
112 ]);
113 }
114 return [];
115 }).then(function(rows) {
116 return new Promise(function(resolve, reject) {
117 if (rows.length) {
118 email = rows[0].email;
119 resolve();
120 } else {
121 reject("Token Invalid");
122 }
123 });
124 }).then(function() {
125 return bcrypt.hash(req.body.new_password, 10);
126 }).then(function(hashword) {
127 return utils.query("UPDATE users SET password=? WHERE email=?", [
128 hashword, email
129 ]);
130 }).then(function() {
131 return utils.query("DELETE FROM password_reset WHERE ?", {
132 reset_id: req.body.token
133 });
134 }).then(function() {
135 res.send(utils.ok({
136 email: email
137 }));
138 }).catch(function(error) {
139 console.error(error);
140 res.send(utils.error({
141 message: error || "Could not update password",
142 type: "error"
143 }));
144 });
145 }],
146 "request": [{
147 auth: false
148 }, function(req, res) {
149 let email;
150
151 utils.query('SELECT email FROM password_reset WHERE reset_id=? AND expires<?', [
152 req.body.token, Date.now()
153 ]).then(function(rows) {
154 return new Promise(function(resolve, reject) {
155 if (rows.length) {
156 reject("There is already a valid password reset link, please check your inbox");
157 } else {
158 email = rows[0].email;
159 resolve();
160 }
161 });
162 }).then(function() {
163 return utils.generate_token("password_reset", "reset_id", email);
164 }).then(function(token) {
165 return utils.email.send_password_reset_email(email, {
166 token: token
167 }).catch(function(error) {
168 // If the confirmation email fails to send, we need to delete the row
169 // from the email_confirmations table so that it can be sent again.
170 console.error(error);
171 return utils.query("DELETE FROM password_reset WHERE ?", {
172 reset_id: token
173 }).then(function() {
174 return new Promise(function(resolve, reject) {
175 reject(error);
176 });
177 });
178 });
179 }).then(function() {
180 return res.send(utils.ok({
181 message: "Password reset email sent, please check your inbox"
182 }));
183 }).catch(function(error) {
184 console.error(error);
185 res.send(utils.error({
186 message: error,
187 type: "error"
188 }));
189 });
190 }]
92191 }
93192 }
94193 };
2424 if (!req.body.password ||
2525 !req.body.email ||
2626 !req.body.display_name ||
27 !req.body.reg_id
27 !req.body.token
2828 ) {
2929 return res.send(utils.error({
3030 message: "Invalid request",
5151
5252 // Look for a registration session matching the reg_id
5353 utils.query('SELECT * FROM registrations WHERE ?', {
54 reg_id: req.body.reg_id
54 reg_id: req.body.token
5555 }).then(function(rows) {
5656 return new Promise(function(resolve, reject) {
5757 if (!rows.length) {
9595 }).then(function() {
9696 // Delete the registration session, the new user is created
9797 return utils.query('DELETE FROM registrations WHERE ?', {
98 reg_id: req.body.reg_id
98 reg_id: req.body.token
9999 });
100100 }).then(function() {
101101 // Create a confirmation session and send the confirmation email
107107 });
108108 }).then(function() {
109109 return utils.email.send_confirmation_email(req.body.email, {
110 confirmation_id: confirmation_id
110 token: confirmation_id
111111 });
112112 }).then(function() {
113113 // Finally we are done
127127 auth: false
128128 }, function(req, res) {
129129 utils.query('SELECT * FROM email_confirmations WHERE ?', {
130 confirmation_id: req.body.confirmation_id
130 confirmation_id: req.body.token
131131 }).then(function(rows) {
132132 return new Promise(function(resolve, reject) {
133133 if (!rows.length) {
147147 ]);
148148 }).then(function() {
149149 return utils.query("DELETE FROM email_confirmations WHERE ?", {
150 confirmation_id: req.body.confirmation_id
150 confirmation_id: req.body.token
151151 });
152152 }).then(function() {
153153 res.send(utils.ok({
167167 let email;
168168
169169 utils.query('SELECT email FROM email_confirmations WHERE confirmation_id=? AND expires<?', [
170 req.body.confirmation_id, Date.now()
170 req.body.token, Date.now()
171171 ]).then(function(rows) {
172172 return new Promise(function(resolve, reject) {
173173 if (rows.length) {
174 reject("There is already a valid confirmation token, please check your inbox");
174 reject("There is already a valid confirmation link, please check your inbox");
175175 } else {
176176 email = rows[0].email;
177177 resolve();
178178 }
179179 });
180180 }).then(function() {
181 return utils.email.generate_new_confirmation_token(email);
182 }).then(function(confirmation_id) {
181 return utils.generate_token("email_confirmations", "confirmation_id", email);
182 }).then(function(token) {
183183 return utils.email.send_confirmation_email(email, {
184 confirmation_id: confirmation_id
185 });
186 }).then(function() {
187 res.send(utils.ok({
184 token: token
185 }).catch(function(error) {
186 // If the confirmation email fails to send, we need to delete the row
187 // from the email_confirmations table so that it can be sent again.
188 console.error(error);
189 return utils.query("DELETE FROM email_confirmations WHERE ?", {
190 confirmation_id: token
191 }).then(function() {
192 return new Promise(function(resolve, reject) {
193 reject(error);
194 });
195 });
196 });
197 }).then(function() {
198 return res.send(utils.ok({
188199 message: "New confirmation email sent, please check your inbox"
189200 }));
190201 }).catch(function(error) {
194205 type: "error"
195206 }));
196207 });
197 }],
208 }]
198209 }
199210 }
200211 };
1818 auth: false
1919 }, function(req, res) {
2020 res.sendFile(path.join(HTML_ROOT, "confirm-email.html"));
21 }],
22 "password-reset": [{
23 auth: false
24 }, function(req, res) {
25 res.sendFile(path.join(HTML_ROOT, "password-reset.html"));
2126 }]
2227 }
2328 };
1212 module.exports = function(utils) {
1313 return {
1414 send_confirmation_email: function(recipient, query) {
15 return generateEmail("confirmation.html", {
15 return generate_email("confirmation.html", {
1616 query: qs.encode(query)
1717 }).then(function(html) {
1818 return mg.messages().send({
2424 });
2525 },
2626 send_password_reset_email: function(recipient, query) {
27 return generateEmail("password_reset.html", {
27 return generate_email("password_reset.html", {
2828 query: qs.encode(query)
2929 }).then(function(html) {
3030 return mg.messages().send({
3434 html: html
3535 });
3636 });
37 },
38 generate_new_confirmation_token: function(email) {
39 let confirmation_id = utils.uuid();
40 // So there either is no token at all, or there is one but it has expired
41 return utils.query(
42 "INSERT INTO email_confirmations (confirmation_id, email, expires)" +
43 "VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE confirmation_id=VALUES(confirmation_id), expires=VALUES(expires)",
44 [confirmation_id, email, Date.now() + date.one_day]
45 ).then(function() {
46 return confirmation_id;
47 });
4837 }
4938 };
5039 };
5140
52 function generateEmail(name, params) {
41 function generate_email(name, params) {
5342 return new Promise(function(resolve, reject) {
5443 fs.readFile(path.resolve(__dirname, name), function(error, data) {
5544 if (error) {
22 } = require('uuid');
33 const fs = require('fs');
44 const getSize = require('get-folder-size');
5 const date = require('./date');
56 const status_codes = require('../../status.json');
67 const max_video_dir_size = 25000000000; // 20 GB
78
5556 // 1 Number
5657 return str && str.match(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/gm) !== null;
5758 }
59 },
60 generate_token: function(table, key, email) {
61 let token = uuid();
62 // So there either is no token at all, or there is one but it has expired
63 return query(
64 `INSERT INTO ${table} (${key}, email, expires) ` +
65 `VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE ${key}=VALUES(${key}), expires=VALUES(expires)`,
66 [token, email, Date.now() + date.one_day]
67 ).then(function() {
68 return token;
69 });
5870 },
5971 get_storage_remaining: function() {
6072 return new Promise(function(resolve, reject) {
8080 if (!rows.length || rows[0].expires < Date.now()) {
8181 // User was registered but they never got a
8282 // token generated (registered through CLI)
83 // or they just haven't confirmed their email yet
84 utils.email.generate_new_confirmation_token(email).then(function(confirmation_id) {
85 utils.email.send_confirmation_email(email, {
86 confirmation_id: confirmation_id
87 });
83 // or they haven't confirmed their email yet
84 // and their token has expired.
85 return utils.email.generate_new_confirmation_token(email);
86 }
87 }).then(function(token) {
88 if (token) {
89 return utils.email.send_confirmation_email(email, {
90 token: token
8891 });
89 } // else there is already a valid token, they just need to confirm
90
92 }
93 // else no token was generated which means there is
94 // already a valid token, they just need to confirm
95 }).then(function() {
9196 done(null, false, {
9297 message: 'You need to confirm your email, please check your inbox'
9398 });
99 }).catch(function(error) {
100 console.error(error);
101 done(error);
94102 });
95103 }
96104 }
11 <html>
22
33 <head>
4 <title>Homepage</title>
4 <title>Watch Together</title>
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <link href="https://fonts.googleapis.com/css2?family=Asap&family=VT323&display=swap" rel="stylesheet">
77 <link href="../css/style.css" rel="stylesheet">
11 <html>
22
33 <head>
4 <title>Login to Watch Together Service</title>
4 <title>Watch Together - Login</title>
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <link href="https://fonts.googleapis.com/css2?family=Asap&family=VT323&display=swap" rel="stylesheet">
77 <link href="../css/style.css" rel="stylesheet">
0 <!DOCTYPE html>
1 <html>
2
3 <head>
4 <title>Watch Together - Password Reset</title>
5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <link href="https://fonts.googleapis.com/css2?family=Asap&family=VT323&display=swap" rel="stylesheet">
7 <link href="../css/style.css" rel="stylesheet">
8 </head>
9
10 <body>
11 <div class="banner"></div>
12 <div class="centered-form">
13 <div class="field-wrapper">
14 <label>New Password:</label>
15 <input type="password" id="new_password" />
16 </div>
17 <div class="field-wrapper">
18 <label>Confirm Password:</label>
19 <input type="password" id="confirm_password" />
20 </div>
21 <button style="float: right; margin: 10px;" id="update_password_button">Update Password</button>
22 </div>
23 <script src="../js/password-reset.js"></script>
24 </body>
25
26 </html>
11 <html>
22
33 <head>
4 <title>Register For Watch Together Service</title>
4 <title>Watch Together - Register</title>
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <link href="https://fonts.googleapis.com/css2?family=Asap&family=VT323&display=swap" rel="stylesheet">
77 <link href="../css/style.css" rel="stylesheet">
1111
1212 $("#request_new_confirmation").click(function() {
1313 api.post("/registration/confirm-email/request", {
14 confirmation_id: qs_data.confirmation_id
14 token: qs_data.token
1515 }).then(function(data) {
1616 utils.show_banner(data.message);
1717 }).catch(function(data) {
2020 });
2121 });
2222
23 if (qs_data.confirmation_id) {
23 if (qs_data.token) {
2424 api.post("/registration/confirm-email", {
25 confirmation_id: qs_data.confirmation_id
25 token: qs_data.token
2626 }).then(function(data) {
2727 $("#success_message").text(data.message);
2828 $("#loading").hide();
0 const $ = require('jquery');
1 const api = require('./utils/api');
2 const utils = require('./utils');
3 const qs = require('querystring');
4
5 let qs_data = qs.parse(location.search.substr(1));
6 console.log(qs_data);
7
8 $("#update_password_button").click(function() {
9 let new_password = $("#new_password").val();
10 let confirm_password = $("#confirm_password").val();
11
12 if (new_password !== confirm_password) {
13 utils.show_banner("Passwords don't match", "error");
14 } else if (!utils.validate.password.length(new_password)) {
15 utils.show_banner("Password should be at least 8 charactors", "error");
16 } else if (!utils.validate.password.letters(new_password)) {
17 utils.show_banner("Password should have at least 1 letter", "error");
18 } else if (!utils.validate.password.numbers(new_password)) {
19 utils.show_banner("Password should have at least 1 number", "error");
20 } else {
21 api.post("/account/password-reset", {
22 token: qs_data.token,
23 password: new_password
24 }).then(function(data) {
25 return api.post("/account/login", {
26 email: data.email,
27 password: new_password
28 });
29 }).then(function() {
30 window.location = "/";
31 }).catch(function(data) {
32 console.error(data);
33 utils.show_banner("Error: " + data.message || "Server Error", data.type || "error");
34 });
35 }
36 });
2727 utils.show_banner("Password should have at least 1 number", "error");
2828 } else {
2929 api.post("/registration", {
30 reg_id: qs_data.reg_id,
30 token: qs_data.token,
3131 email: email,
3232 display_name: display_name,
3333 password: new_password
0 new Promise(function(resolve) {
1 resolve();
2 }).then(function() {
3 return new Promise(function(resolve, reject) {
4 resolve("Nested reject");
5 }).then(function(e) {
6 console.log("then inside then", e);
7 }).catch(function(e) {
8 console.log("catch inside then", e);
9 return new Promise(function(resolve, reject) {
10 resolve("resolve x2 inside then");
11 }).then(function(e) {
12 console.log("then after catch inside then", e);
13 return new Promise(function(resolve, reject) {
14 reject(e);
15 });
16 });
17 });
18 }).then(function(a) {
19 console.log("final then", a);
20 }).catch(function(e) {
21 console.log("final catch", e);
22 });
88 "index": './src/js/index.js',
99 "login": './src/js/login.js',
1010 "register": './src/js/register.js',
11 "confirm-email": './src/js/confirm-email.js'
11 "confirm-email": './src/js/confirm-email.js',
12 "password-reset": './src/js/password-reset.js'
1213 },
1314 output: {
1415 filename: './js/[name].js',