From 0e750d593179190eb3279df87d618427e1ffa92a Mon Sep 17 00:00:00 2001 From: James Ketrenos Date: Tue, 4 Dec 2018 19:31:06 -0800 Subject: [PATCH] Password changing now works. Signed-off-by: James Ketrenos --- frontend/src/ketr-photos/ketr-photos.html | 9 +- server/lib/mail.js | 123 +++++++++++++++++----- server/lib/pascha.js | 2 - server/routes/users.js | 112 +++++++++++++++++++- 4 files changed, 213 insertions(+), 33 deletions(-) diff --git a/frontend/src/ketr-photos/ketr-photos.html b/frontend/src/ketr-photos/ketr-photos.html index bea7f29..8246546 100755 --- a/frontend/src/ketr-photos/ketr-photos.html +++ b/frontend/src/ketr-photos/ketr-photos.html @@ -790,8 +790,13 @@ }, profile: function(event) { - this.$.profile.user = this.user; - this.mode = "profile"; + if (this.mode == "profile") { + this.mode = this.lastMode; + } else { + this.$.profile.user = this.user; + this.lastMode = this.mode; + this.mode = "profile"; + } }, logout: function(event) { diff --git a/server/lib/mail.js b/server/lib/mail.js index f727918..944c272 100644 --- a/server/lib/mail.js +++ b/server/lib/mail.js @@ -5,31 +5,51 @@ const config = require("config"), hb = require("handlebars"); const templates = { - "html": [ - "

Hello {{username}},

", - "", - "

Welcome to ketrenos.com. You are almost done creating your account. ", - "Before you can access the system, you must verify your email address.

", - "", - "

To do so, simply access this link:

", - "

VERIFY {{mail}} ADDRESS

", - "", - "

Sincerely,

", - "

James

" - ].join("\n"), - "text": [ - "Hello {{username}},", - "", - "Welcome to ketrenos.com. You are almost done creating your account. ", - "Before you can access the system, you must verify your email address.", - "", - "To do so, simply access this link:", - "", - "{{url}}{{secret}}", - "", - "Sincerely,", - "James" - ].join("\n") + "verify": { + "html": [ + "

Hello {{username}},

", + "", + "

Welcome to ketrenos.com. You are almost done creating your account. ", + "Before you can access the system, you must verify your email address.

", + "", + "

To do so, simply access this link:

", + "

VERIFY {{mail}} ADDRESS

", + "", + "

Sincerely,

", + "

James

" + ].join("\n"), + "text": [ + "Hello {{username}},", + "", + "Welcome to ketrenos.com. You are almost done creating your account. ", + "Before you can access the system, you must verify your email address.", + "", + "To do so, simply access this link:", + "", + "{{url}}{{secret}}", + "", + "Sincerely,", + "James" + ].join("\n") + }, + "password": { + "html": [ + "

Hello {{username}},

", + "", + "

You changed your password on ketrenos.com.

", + "", + "

Sincerely,

", + "

James

" + ].join("\n"), + "text": [ + "Hello {{username}},", + "", + "You changed your password on ketrenos.com.", + "", + "Sincerely,

", + "James" + ].join("\n") + } }; const sendVerifyMail = function(userDB, req, user) { @@ -79,8 +99,8 @@ const sendVerifyMail = function(userDB, req, user) { subject: "Request to ketrenos.com create account for '" + data.username + "'", cc: "", bcc: config.get("admin.mail"), - text: hb.compile(templates.text)(data), - html: hb.compile(templates.html)(data) + text: hb.compile(templates.verify.text)(data), + html: hb.compile(templates.verify.html)(data) }; return new Promise(function (resolve, reject) { let attempts = 10; @@ -111,6 +131,53 @@ const sendVerifyMail = function(userDB, req, user) { }); }; +const sendPasswordChangedMail = function(userDB, req, user) { + const transporter = req.app.get("transporter"); + if (!transporter) { + console.log("Not sending VERIFY email; SMTP not configured."); + return; + } + + let data = { + username: user.displayName, + mail: user.mail, + url: req.protocol + "://" + req.hostname + req.app.get("basePath") + }, envelope = { + to: data.mail, + from: config.get("smtp.sender"), + subject: "Password changed on ketrenos.com for '" + data.username + "'", + cc: "", + bcc: config.get("admin.mail"), + text: hb.compile(templates.password.text)(data), + html: hb.compile(templates.password.html)(data) + }; + return new Promise(function (resolve, reject) { + let attempts = 10; + + function send(envelope) { + /* Rate limit to ten per second */ + transporter.sendMail(envelope, function (error, info) { + if (!error) { + console.log('Message sent: ' + info.response); + return resolve(); + } + + if (attempts == 0) { + console.log("Error sending email: ", error); + return reject(error); + } + + attempts--; + console.log("Unable to send mail. Trying again in 100ms (" + attempts + " attempts remain): ", error); + setTimeout(send.bind(undefined, envelope), 100); + }); + } + + send(envelope); + }); +}; + module.exports = { - sendVerifyMail + sendVerifyMail, + sendPasswordChangedMail } diff --git a/server/lib/pascha.js b/server/lib/pascha.js index 4a01f9f..9f9526d 100644 --- a/server/lib/pascha.js +++ b/server/lib/pascha.js @@ -108,8 +108,6 @@ function init(moment) { if (ds.length) { return ds.join('|'); } } }); - - console.log("Pascha initialized"); } module.exports = init; diff --git a/server/routes/users.js b/server/routes/users.js index e5e5432..53de543 100755 --- a/server/routes/users.js +++ b/server/routes/users.js @@ -3,7 +3,7 @@ const express = require("express"), config = require("config"), LdapAuth = require("ldapauth-fork"), - { sendVerifyMail } = require("../lib/mail"), + { sendVerifyMail, sendPasswordChangedMail } = require("../lib/mail"), crypto = require("crypto"); const router = express.Router(); @@ -45,6 +45,116 @@ function ldapPromise(username, password) { }); } +const ldapJS = require("ldapjs"), + ldapConfig = config.get("ldap"); + + +const ldapSetPassword = function(username, password) { + const client = ldapJS.createClient({ + url: ldapConfig.url + }); + + return new Promise(function(resolve, reject) { + client.bind(ldapConfig.bindDn, ldapConfig.bindCredentials, function(err) { + if (err) { + return reject("Error binding to LDAP: " + err); + } + + var change = new ldapJS.Change({ + operation: "replace", + modification: { + userPassword : password, + } + }); + + client.modify("uid=" + username + ",ou=people," + ldapConfig.searchBase, change, function(err) { + if (err) { + return reject("Error changing password: " + err); + } + return resolve(); + }); + }); + }).catch(function(error) { + console.error(error); + }).then(function() { + client.unbind(function(err) { + if (err) { + console.error("Error unbinding: " + err); + } + }); + }); +}; + +router.put("/password", function(req, res) { + console.log("/users/password"); + + const changes = { + currentPassword: req.query.c || req.body.c, + newPassword: req.query.n || req.body.n + }; + + if (!changes.currentPassword || !changes.newPassword) { + return res.status(400).send("Missing current password and/or new password."); + } + + if (changes.currentPassword == changes.newPassword) { + return res.status(400).send("Attempt to set new password to current password."); + } + + return getSessionUser(req).then(function(user) { + if (req.session.userId == "LDAP") { + return ldapPromise(user.username, changes.currentPassword).then(function() { + return user; + }).catch(function() { + return null; + }); + } + + /* Not an LDAP user, so query in the DB */ + return userDB.sequelize.query("SELECT id FROM users " + + "WHERE uid=:username AND password=:password", { + replacements: { + username: user.username, + password: crypto.createHash('sha256').update(changes.currentPassword).digest('base64') + }, + type: userDB.Sequelize.QueryTypes.SELECT, + raw: true + }).then(function(users) { + if (users.length != 1) { + return null; + } + return user; + }); + }).then(function(user) { + if (!user) { + console.log("Invalid password"); + /* Invalid password */ + res.status(401).send("Invalid password"); + return null; + } + + let updatePromise; + if (req.session.userId == "LDAP") { + updatePromise = ldapSetPassword(user.username, changes.newPassword); + } else { + updatePromise = userDB.sequelize.query("UPDATE users SET password=:password WHERE uid=:username", { + replacements: { + username: user.username, + password: crypto.createHash('sha256').update(changes.newPassword).digest('base64') + } + }); + } + + updatePromise.then(function() { + console.log("Password changed for user " + user.username + " to '" + changes.newPassword + "'."); + + res.status(200).send(user); + user.id = req.session.userId; + return sendPasswordChangedMail(userDB, req, user); + }); + }); +}); + router.post("/create", function(req, res) { console.log("/users/create");