|
@@ -537,5 +537,139 @@ module.exports = {
|
|
|
message: 'Password successfully updated.'
|
|
|
});
|
|
|
});
|
|
|
- })
|
|
|
+ }),
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Requests a password reset for an email
|
|
|
+ *
|
|
|
+ * @param {Object} session - the session object automatically added by socket.io
|
|
|
+ * @param {String} email - the email of the user that requests a password reset
|
|
|
+ * @param {Function} cb - gets called with the result
|
|
|
+ */
|
|
|
+ requestPasswordReset: (session, email, cb) => {
|
|
|
+ let code = utils.generateRandomString(8);
|
|
|
+ async.waterfall([
|
|
|
+ (next) => {
|
|
|
+ if (!email || typeof email !== 'string') return next('Invalid code.');
|
|
|
+ email = email.toLowerCase();
|
|
|
+ db.models.user.findOne({"email.address": email}, next);
|
|
|
+ },
|
|
|
+
|
|
|
+ (user, next) => {
|
|
|
+ if (!user) return next('User not found.');
|
|
|
+ if (!user.services.password || !user.services.password.password) return next('User does not have a password set, and probably uses GitHub to log in.');
|
|
|
+ next(null, user);
|
|
|
+ },
|
|
|
+
|
|
|
+ (user, next) => {
|
|
|
+ let expires = new Date();
|
|
|
+ expires.setDate(expires.getDate() + 1);
|
|
|
+ db.models.user.findOneAndUpdate({"email.address": email}, {$set: {"services.password.reset": {code: code, expires}}}, next);
|
|
|
+ },
|
|
|
+
|
|
|
+ (user, next) => {
|
|
|
+ mail.schemas.resetPasswordRequest(user.email.address, user.username, code, next);
|
|
|
+ }
|
|
|
+ ], (err) => {
|
|
|
+ if (err && err !== true) {
|
|
|
+ let error = 'An error occurred.';
|
|
|
+ if (typeof err === "string") error = err;
|
|
|
+ else if (err.message) error = err.message;
|
|
|
+ logger.error("REQUEST_PASSWORD_RESET", `Email '${email}' failed to request password reset. '${error}'`);
|
|
|
+ cb({status: 'failure', message: error});
|
|
|
+ } else {
|
|
|
+ logger.success("REQUEST_PASSWORD_RESET", `Email '${email}' successfully requested a password reset.`);
|
|
|
+ cb({
|
|
|
+ status: 'success',
|
|
|
+ message: 'Successfully requested password reset.'
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Verifies a reset code
|
|
|
+ *
|
|
|
+ * @param {Object} session - the session object automatically added by socket.io
|
|
|
+ * @param {String} code - the password reset code
|
|
|
+ * @param {Function} cb - gets called with the result
|
|
|
+ */
|
|
|
+ verifyPasswordResetCode: (session, code, cb) => {
|
|
|
+ async.waterfall([
|
|
|
+ (next) => {
|
|
|
+ if (!code || typeof code !== 'string') return next('Invalid code.');
|
|
|
+ db.models.user.findOne({"services.password.reset.code": code}, next);
|
|
|
+ },
|
|
|
+
|
|
|
+ (user, next) => {
|
|
|
+ if (!user) return next('Invalid code.');
|
|
|
+ if (!user.services.password.reset.expires > new Date()) return next('That code has expired.');
|
|
|
+ next(null);
|
|
|
+ }
|
|
|
+ ], (err) => {
|
|
|
+ if (err && err !== true) {
|
|
|
+ let error = 'An error occurred.';
|
|
|
+ if (typeof err === "string") error = err;
|
|
|
+ else if (err.message) error = err.message;
|
|
|
+ logger.error("VERIFY_PASSWORD_RESET_CODE", `Code '${code}' failed to verify. '${error}'`);
|
|
|
+ cb({status: 'failure', message: error});
|
|
|
+ } else {
|
|
|
+ logger.success("VERIFY_PASSWORD_RESET_CODE", `Code '${code}' successfully verified.`);
|
|
|
+ cb({
|
|
|
+ status: 'success',
|
|
|
+ message: 'Successfully verified password reset code.'
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Changes a user's password with a reset code
|
|
|
+ *
|
|
|
+ * @param {Object} session - the session object automatically added by socket.io
|
|
|
+ * @param {String} code - the password reset code
|
|
|
+ * @param {String} newPassword - the new password reset code
|
|
|
+ * @param {Function} cb - gets called with the result
|
|
|
+ */
|
|
|
+ changePasswordWithResetCode: (session, code, newPassword, cb) => {
|
|
|
+ async.waterfall([
|
|
|
+ (next) => {
|
|
|
+ if (!code || typeof code !== 'string') return next('Invalid code.');
|
|
|
+ db.models.user.findOne({"services.password.reset.code": code}, next);
|
|
|
+ },
|
|
|
+
|
|
|
+ (user, next) => {
|
|
|
+ if (!user) return next('Invalid code.');
|
|
|
+ if (!user.services.password.reset.expires > new Date()) return next('That code has expired.');
|
|
|
+ next();
|
|
|
+ },
|
|
|
+
|
|
|
+ (next) => {
|
|
|
+ bcrypt.genSalt(10, next);
|
|
|
+ },
|
|
|
+
|
|
|
+ // hash the password
|
|
|
+ (salt, next) => {
|
|
|
+ bcrypt.hash(sha256(newPassword), salt, next);
|
|
|
+ },
|
|
|
+
|
|
|
+ (hashedPassword, next) => {
|
|
|
+ db.models.user.update({"services.password.reset.code": code}, {$set: {"services.password.password": hashedPassword}, $unset: {"services.password.reset": ''}}, next);
|
|
|
+ }
|
|
|
+ ], (err) => {
|
|
|
+ if (err && err !== true) {
|
|
|
+ let error = 'An error occurred.';
|
|
|
+ if (typeof err === "string") error = err;
|
|
|
+ else if (err.message) error = err.message;
|
|
|
+ logger.error("CHANGE_PASSWORD_WITH_RESET_CODE", `Code '${code}' failed to change password. '${error}'`);
|
|
|
+ cb({status: 'failure', message: error});
|
|
|
+ } else {
|
|
|
+ logger.success("CHANGE_PASSWORD_WITH_RESET_CODE", `Code '${code}' successfully changed password.`);
|
|
|
+ cb({
|
|
|
+ status: 'success',
|
|
|
+ message: 'Successfully changed password.'
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
};
|