added endpoint to update password

This commit is contained in:
bill bittner 2017-11-30 14:46:32 -08:00
parent 6ae9b78dbf
commit f16abf08c2
7 changed files with 136 additions and 55 deletions

View file

@ -14,31 +14,42 @@ module.exports = {
} }
next(); next();
}, },
serializeSpeechUser (user, done) { serializeSpeechUser (user, done) { // returns user id to be serialized into session token
logger.debug('serializing session');
done(null, user.id); done(null, user.id);
}, },
deserializeSpeechUser (id, done) { deserializeSpeechUser (id, done) { // deserializes session token and provides user from user id
let userInfo = {}; logger.debug('deserializing session');
db.User.findOne({ where: { id } }) return db.User.findOne({ where: { id } })
.then(user => { .then(user => {
userInfo['id'] = user.id; return module.exports.returnUserAndChannelInfo(user);
userInfo['userName'] = user.userName;
return user.getChannel();
}) })
.then(channel => { .then((userInfo) => {
userInfo['channelName'] = channel.channelName; return done(null, userInfo);
userInfo['channelClaimId'] = channel.channelClaimId;
return db.Certificate.getShortChannelIdFromLongChannelId(channel.channelClaimId, channel.channelName);
})
.then(shortChannelId => {
userInfo['shortChannelId'] = shortChannelId;
// return done(null, userInfo);
done(null, userInfo);
return null;
}) })
.catch(error => { .catch(error => {
logger.error(error); return done(error);
done(error, null); });
},
returnUserAndChannelInfo (userInstance) {
return new Promise((resolve, reject) => {
let userInfo = {};
userInfo['id'] = userInstance.id;
userInfo['userName'] = userInstance.userName;
userInstance
.getChannel()
.then(({channelName, channelClaimId}) => {
userInfo['channelName'] = channelName;
userInfo['channelClaimId'] = channelClaimId;
return db.Certificate.getShortChannelIdFromLongChannelId(channelClaimId, channelName);
})
.then(shortChannelId => {
userInfo['shortChannelId'] = shortChannelId;
resolve(userInfo);
})
.catch(error => {
reject(error);
});
}); });
}, },
}; };

View file

@ -76,13 +76,23 @@ module.exports = {
} }
}, },
cleanseChannelName (channelName) { cleanseChannelName (channelName) {
if (channelName) { if (!channelName) {
if (channelName.indexOf('@') !== 0) { return null;
channelName = `@${channelName}`; }
} if (channelName.indexOf('@') !== 0) {
channelName = `@${channelName}`;
} }
return channelName; return channelName;
}, },
cleanseUserName (userName) {
if (!userName) {
return null;
}
if (userName.indexOf('@') !== -1) {
userName = userName.substring(userName.indexOf('@'));
}
return userName;
},
createPublishParams (filePath, name, title, description, license, nsfw, thumbnail, channelName) { createPublishParams (filePath, name, title, description, license, nsfw, thumbnail, channelName) {
logger.debug(`Creating Publish Parameters`); logger.debug(`Creating Publish Parameters`);
// provide defaults for title // provide defaults for title

View file

@ -28,9 +28,40 @@ module.exports = (sequelize, { STRING }) => {
bcrypt.compare(password, this.password, callback); bcrypt.compare(password, this.password, callback);
}; };
User.prototype.changePassword = function (newPassword) {
return new Promise((resolve, reject) => {
// generate a salt string to use for hashing
bcrypt.genSalt((saltError, salt) => {
if (saltError) {
logger.error('salt error', saltError);
reject(saltError);
return;
}
// generate a hashed version of the user's password
bcrypt.hash(newPassword, salt, (hashError, hash) => {
// if there is an error with the hash generation return the error
if (hashError) {
logger.error('hash error', hashError);
reject(hashError);
return;
}
// replace the current password with the new hash
this
.update({password: hash})
.then(() => {
resolve();
})
.catch(error => {
reject(error);
});
});
});
});
};
// pre-save hook method to hash the user's password before the user's info is saved to the db. // pre-save hook method to hash the user's password before the user's info is saved to the db.
User.hook('beforeCreate', (user, options) => { User.hook('beforeCreate', (user, options) => {
logger.debug('...beforeCreate hook...'); logger.debug('User.beforeCreate hook...');
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// generate a salt string to use for hashing // generate a salt string to use for hashing
bcrypt.genSalt((saltError, salt) => { bcrypt.genSalt((saltError, salt) => {

View file

@ -2,6 +2,7 @@
const PassportLocalStrategy = require('passport-local').Strategy; const PassportLocalStrategy = require('passport-local').Strategy;
const db = require('../models'); const db = require('../models');
const logger = require('winston'); const logger = require('winston');
const { returnUserAndChannelInfo } = require('../helpers/authHelpers.js');
module.exports = new PassportLocalStrategy( module.exports = new PassportLocalStrategy(
{ {
@ -11,43 +12,31 @@ module.exports = new PassportLocalStrategy(
passReqToCallback: true, passReqToCallback: true,
}, },
(req, username, password, done) => { (req, username, password, done) => {
logger.debug(`verifying loggin attempt ${username} ${password}`); logger.debug('logging user in');
let userInfo = {}; return db
return db.User .User
.findOne({where: {userName: username}}) .findOne({where: {userName: username}})
.then(user => { .then(user => {
if (!user) { if (!user) {
logger.debug('no user found'); // logger.debug('no user found');
return done(null, false, {message: 'Incorrect username or password.'}); return done(null, false, {message: 'Incorrect username or password.'});
} }
logger.debug('user found:', user.dataValues);
logger.debug('...comparing password...');
return user.comparePassword(password, (passwordErr, isMatch) => { return user.comparePassword(password, (passwordErr, isMatch) => {
if (passwordErr) { if (passwordErr) {
logger.error('passwordErr:', passwordErr); logger.error('passwordErr:', passwordErr);
return done(passwordErr); return done(null, false, {message: passwordErr});
} }
if (!isMatch) { if (!isMatch) {
logger.debug('incorrect password'); // logger.debug('incorrect password');
return done(null, false, {message: 'Incorrect username or password.'}); return done(null, false, {message: 'Incorrect username or password.'});
} }
logger.debug('...password was a match...'); logger.debug('Password was a match, returning User');
userInfo['id'] = user.id; return returnUserAndChannelInfo(user)
userInfo['userName'] = user.userName; .then((userInfo) => {
// get the User's channel info
return user.getChannel()
.then(channel => {
userInfo['channelName'] = channel.channelName;
userInfo['channelClaimId'] = channel.channelClaimId;
return db.Certificate.getShortChannelIdFromLongChannelId(channel.channelClaimId, channel.channelName);
})
.then(shortChannelId => {
userInfo['shortChannelId'] = shortChannelId;
return done(null, userInfo); return done(null, userInfo);
}) })
.catch(error => { .catch(error => {
throw error; return done(error);
}); });
}); });
}) })

View file

@ -5,7 +5,7 @@ const multipartMiddleware = multipart({uploadDir: config.files.uploadDirectory})
const db = require('../models'); const db = require('../models');
const { publish } = require('../controllers/publishController.js'); const { publish } = require('../controllers/publishController.js');
const { getClaimList, resolveUri } = require('../helpers/lbryApi.js'); const { getClaimList, resolveUri } = require('../helpers/lbryApi.js');
const { createPublishParams, validateApiPublishRequest, validatePublishSubmission, cleanseChannelName, checkClaimNameAvailability, checkChannelAvailability } = require('../helpers/publishHelpers.js'); const { createPublishParams, validateApiPublishRequest, validatePublishSubmission, cleanseChannelName, cleanseUserName, checkClaimNameAvailability, checkChannelAvailability } = require('../helpers/publishHelpers.js');
const errorHandlers = require('../helpers/errorHandlers.js'); const errorHandlers = require('../helpers/errorHandlers.js');
const { postToStats, sendGoogleAnalytics } = require('../controllers/statsController.js'); const { postToStats, sendGoogleAnalytics } = require('../controllers/statsController.js');
const { authenticateOrSkip } = require('../auth/authentication.js'); const { authenticateOrSkip } = require('../auth/authentication.js');
@ -185,4 +185,44 @@ module.exports = (app) => {
errorHandlers.handleApiError('short channel id', originalUrl, ip, error, res); errorHandlers.handleApiError('short channel id', originalUrl, ip, error, res);
}); });
}); });
app.put('/api/password', ({ body, ip, originalUrl }, res) => {
let userName;
let { channelName, oldPassword, newPassword } = body;
// validate all necessary params were provided
if (!channelName || !oldPassword || !newPassword) {
res.status(400).json({success: false, message: 'provide channelName, oldPassword, and newPassword'});
}
// cleanse channel name
userName = cleanseUserName(channelName);
// validate password and respond
db
.User
.findOne({where: {userName: userName}})
.then(user => {
if (!user) {
return res.status(401).json({success: false, message: 'Incorrect username or password.'});
}
return user.comparePassword(oldPassword, (passwordErr, isMatch) => {
if (passwordErr) {
throw passwordErr;
}
if (!isMatch) {
return res.status(401).json({success: false, message: 'Incorrect username or password.'});
}
logger.debug('Password was a match, updating password');
return user
.changePassword(newPassword)
.then(() => {
logger.debug('Password successfully updated');
res.status(200).json({success: true, message: 'password successfully changed'});
})
.catch(error => {
throw error;
});
});
})
.catch(error => {
errorHandlers.handleApiError('password reset', originalUrl, ip, error, res);
});
});
}; };

View file

@ -14,7 +14,7 @@ module.exports = (app) => {
}); });
// route for log in // route for log in
app.post('/login', passport.authenticate('local-login'), (req, res) => { app.post('/login', passport.authenticate('local-login'), (req, res) => {
logger.debug('req.user:', req.user); // logger.debug('req.user:', req.user); // req.user contains the authenticated user's info
logger.debug('successful login'); logger.debug('successful login');
res.status(200).json({ res.status(200).json({
success : true, success : true,

View file

@ -33,10 +33,16 @@ app.use(bodyParser.json()); // 'body parser' for parsing application/json
app.use(bodyParser.urlencoded({ extended: true })); // 'body parser' for parsing application/x-www-form-urlencoded app.use(bodyParser.urlencoded({ extended: true })); // 'body parser' for parsing application/x-www-form-urlencoded
app.use((req, res, next) => { // custom logging middleware to log all incoming http requests app.use((req, res, next) => { // custom logging middleware to log all incoming http requests
logger.verbose(`Request on ${req.originalUrl} from ${req.ip}`); logger.verbose(`Request on ${req.originalUrl} from ${req.ip}`);
logger.debug('req.body:', req.body);
next(); next();
}); });
// configure passport
passport.serializeUser(serializeSpeechUser);
passport.deserializeUser(deserializeSpeechUser);
const localSignupStrategy = require('./passport/local-signup.js');
const localLoginStrategy = require('./passport/local-login.js');
passport.use('local-signup', localSignupStrategy);
passport.use('local-login', localLoginStrategy);
// initialize passport // initialize passport
app.use(cookieSession({ app.use(cookieSession({
name : 'session', name : 'session',
@ -45,12 +51,6 @@ app.use(cookieSession({
})); }));
app.use(passport.initialize()); app.use(passport.initialize());
app.use(passport.session()); app.use(passport.session());
passport.serializeUser(serializeSpeechUser); // takes the user id from the db and serializes it
passport.deserializeUser(deserializeSpeechUser); // this deserializes id then populates req.user with info
const localSignupStrategy = require('./passport/local-signup.js');
const localLoginStrategy = require('./passport/local-login.js');
passport.use('local-signup', localSignupStrategy);
passport.use('local-login', localLoginStrategy);
// configure handlebars & register it with express app // configure handlebars & register it with express app
const hbs = expressHandlebars.create({ const hbs = expressHandlebars.create({