diff --git a/models/user.js b/models/user.js index f4edc8f63..c3c81010a 100644 --- a/models/user.js +++ b/models/user.js @@ -196,6 +196,7 @@ UserSchema.statics.changePassword = function(id, password) { }) .then((hashedPassword) => { return User.update({id}, { + $inc: {__v: 1}, $set: { password: hashedPassword } @@ -346,6 +347,10 @@ UserSchema.statics.findByIdArray = function(ids) { }); }; +/** + * Creates a JWT from a user email. Only works for local accounts. + * @param {String} email of the local user + */ UserSchema.statics.createJWT = function (email) { if (!email || typeof email !== 'string') { return Promise.reject('email is required when creating a JWT for resetting passord'); @@ -353,11 +358,42 @@ UserSchema.statics.createJWT = function (email) { email = email.toLowerCase(); - const payload = {email, jti: uuid.v4()}; + return this.findOne({profiles: {$elemMatch: {id: email}}}) + .then(user => { - const token = jwt.sign(payload, process.env.TALK_SESSION_SECRET, {expiresIn: '1d'}); + if (user === null) { + return Promise.reject(`Uh oh! We've never heard of ${email}. Maybe there's a typo in there?`); + } - return Promise.resolve(token); + const payload = {email, jti: uuid.v4(), userId: user.id, version: user.__v}; + const token = jwt.sign(payload, process.env.TALK_SESSION_SECRET, {expiresIn: '1d'}); + + return token; + }); +}; + +/** + * verifies a jwt and returns the associated user + * @param {String} token the JSON Web Token to verify + */ +UserSchema.statics.verifyToken = function (token) { + return new Promise((resolve, reject) => { + jwt.verify(token, process.env.TALK_SESSION_SECRET, (error, decoded) => { + if (error) { + return reject(error); + } + + resolve(decoded); + }); + }) + .then(decoded => { + /** + * TODO: check the jti from this decoded token in redis + * and make an entry if it does not exist. + * reject if entry already exists. + */ + return this.findOne({id: decoded.userId}); + }); }; const User = mongoose.model('User', UserSchema); diff --git a/routes/admin/index.js b/routes/admin/index.js index 84c130ba8..2b967708a 100644 --- a/routes/admin/index.js +++ b/routes/admin/index.js @@ -1,5 +1,4 @@ const express = require('express'); -const jwt = require('jsonwebtoken'); const router = express.Router(); router.get('/embed/stream/preview', (req, res) => { @@ -7,15 +6,8 @@ router.get('/embed/stream/preview', (req, res) => { }); router.get('/password-reset/:token', (req, res, next) => { - jwt.verify(req.params.token, process.env.TALK_SESSION_SECRET, (error, decoded) => { - if (error) { - return res.status(400).json({error}); - } - - console.log(decoded); - - res.json(decoded); - }); + // render a page or something? + res.send('ok'); }); router.get('*', (req, res) => { diff --git a/routes/api/user/index.js b/routes/api/user/index.js index c0bc43c6a..8cbebfd1f 100644 --- a/routes/api/user/index.js +++ b/routes/api/user/index.js @@ -71,6 +71,27 @@ router.post('/:user_id/role', (req, res, next) => { .catch(next); }); +/** + * expects 2 fields in the body of the request + * 1) the token that was in the url of the email link {String} + * 2) the new password {String} + */ +router.post('/update-password', (req, res, next) => { + const {token, password} = req.body; + + User.verifyToken(token) + .then(user => { + return User.changePassword(user.id, password); + }) + .then(() => { + res.status(204).end(); + }) + .catch(error => { + console.error(error); + res.status(401).send('Not Authorized'); + }); +}); + /** * this endpoint takes an email (username) and checks if it belongs to a User account * if it does, create a JWT and send an email @@ -91,7 +112,9 @@ router.post('/request-password-reset', (req, res, next) => { subject: 'password reset requested', from: 'coralcore@mozillafoundation.org', to: 'riley.davis@gmail.com', - html: `reset password` + html: `
We received a request to reset your password. If you did not request this change, you can ignore this email. + If you did, please click here to reset password.
+ ${process.env.NODE_ENV === 'production' ? '' : `