diff --git a/app/controllers/articles.server.controller.js b/app/controllers/articles.server.controller.js index c60e93035b..b709baf6d9 100644 --- a/app/controllers/articles.server.controller.js +++ b/app/controllers/articles.server.controller.js @@ -4,33 +4,10 @@ * Module dependencies. */ var mongoose = require('mongoose'), + errorHandler = require('./errors'), Article = mongoose.model('Article'), _ = require('lodash'); -/** - * Get the error message from error object - */ -var getErrorMessage = function(err) { - var message = ''; - - if (err.code) { - switch (err.code) { - case 11000: - case 11001: - message = 'Article already exists'; - break; - default: - message = 'Something went wrong'; - } - } else { - for (var errName in err.errors) { - if (err.errors[errName].message) message = err.errors[errName].message; - } - } - - return message; -}; - /** * Create a article */ @@ -41,7 +18,7 @@ exports.create = function(req, res) { article.save(function(err) { if (err) { return res.send(400, { - message: getErrorMessage(err) + message: errorHandler.getErrorMessage(err) }); } else { res.jsonp(article); @@ -67,7 +44,7 @@ exports.update = function(req, res) { article.save(function(err) { if (err) { return res.send(400, { - message: getErrorMessage(err) + message: errorHandler.getErrorMessage(err) }); } else { res.jsonp(article); @@ -84,7 +61,7 @@ exports.delete = function(req, res) { article.remove(function(err) { if (err) { return res.send(400, { - message: getErrorMessage(err) + message: errorHandler.getErrorMessage(err) }); } else { res.jsonp(article); @@ -99,7 +76,7 @@ exports.list = function(req, res) { Article.find().sort('-created').populate('user', 'displayName').exec(function(err, articles) { if (err) { return res.send(400, { - message: getErrorMessage(err) + message: errorHandler.getErrorMessage(err) }); } else { res.jsonp(articles); diff --git a/app/controllers/errors.server.controller.js b/app/controllers/errors.server.controller.js new file mode 100644 index 0000000000..e4604f8c9c --- /dev/null +++ b/app/controllers/errors.server.controller.js @@ -0,0 +1,42 @@ +'use strict'; + +/** + * Get unique error field name + */ +var getUniqueErrorMessage = function(err) { + var output; + + try { + var fieldName = err.err.substring(err.err.lastIndexOf('.$') + 2, err.err.lastIndexOf('_1')); + output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exist'; + + } catch(ex) { + output = 'Unique field already exist'; + } + + return output; +}; + +/** + * Get the error message from error object + */ +exports.getErrorMessage = function(err) { + var message = ''; + + if (err.code) { + switch (err.code) { + case 11000: + case 11001: + message = getUniqueErrorMessage(err); + break; + default: + message = 'Something went wrong'; + } + } else { + for (var errName in err.errors) { + if (err.errors[errName].message) message = err.errors[errName].message; + } + } + + return message; +}; \ No newline at end of file diff --git a/app/controllers/users.server.controller.js b/app/controllers/users.server.controller.js index 17c82f4b57..a749572764 100755 --- a/app/controllers/users.server.controller.js +++ b/app/controllers/users.server.controller.js @@ -1,542 +1,6 @@ 'use strict'; -/** - * Module dependencies. - */ -var mongoose = require('mongoose'), - passport = require('passport'), - User = mongoose.model('User'), - _ = require('lodash'); - /* Requires for reset password */ -var nodemailer = require('nodemailer'); -var LocalStrategy = require('passport-local').Strategy; -var bcrypt = require('bcrypt-nodejs'); -var async = require('async'); -var crypto = require('crypto'); - -/** - * Get the error message from error object - */ -var getErrorMessage = function(err) { - var message = ''; - - if (err.code) { - switch (err.code) { - case 11000: - case 11001: - message = 'Username already exists'; - break; - default: - message = 'Something went wrong'; - } - } else { - for (var errName in err.errors) { - if (err.errors[errName].message) message = err.errors[errName].message; - } - } - - return message; -}; - -/** - * Signup - */ -exports.signup = function(req, res) { - // For security measurement we remove the roles from the req.body object - delete req.body.roles; - - // Init Variables - var user = new User(req.body); - var message = null; - - // Add missing user fields - user.provider = 'local'; - user.displayName = user.firstName + ' ' + user.lastName; - - // Then save the user - user.save(function(err) { - if (err) { - return res.send(400, { - message: getErrorMessage(err) - }); - } else { - // Remove sensitive data before login - user.password = undefined; - user.salt = undefined; - - req.login(user, function(err) { - if (err) { - res.send(400, err); - } else { - res.jsonp(user); - } - }); - } - }); -}; - -/** - * Signin after passport authentication - */ -exports.signin = function(req, res, next) { - passport.authenticate('local', function(err, user, info) { - if (err || !user) { - res.send(400, info); - } else { - // Remove sensitive data before login - user.password = undefined; - user.salt = undefined; - - req.login(user, function(err) { - if (err) { - res.send(400, err); - } else { - res.jsonp(user); - } - }); - } - })(req, res, next); -}; - -/** - * Forgot for reset password (forgot POST) - */ -exports.forgot = function(req, res, next) { - async.waterfall([ - // Generate random token - function(done) { - crypto.randomBytes(20, function(err, buf) { - var token = buf.toString('hex'); - done(err, token); - }); - }, - // Lookup user by email address - function(token, done) { - if (req.body.email) { - User.findOne({ email: req.body.email }, function(err, user) { - if (!user) { - return res.send(400, { - message: 'No account with that email address exists' - }); - } - - user.resetPasswordToken = token; - user.resetPasswordExpires = Date.now() + 3600000; // 1 hour - - user.save(function(err) { - done(err, token, user); - }); - }); - } else { - return res.send(400, { - message: 'Email field must not be blank' - }); - } - - }, - // If valid email, send reset email using service - function(token, user, done) { - var smtpTransport = nodemailer.createTransport('SMTP', { - service: 'SendGrid', // Choose email service, default SendGrid - auth: { - user: 'your_sendgrid_email@domain.com', - pass: 'your_sendgrid_password' - } - }); - var mailOptions = { - to: user.email, - from: 'your_email@domain.com', - subject: 'Password Reset', - text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' + - 'Please click on the following link, or paste this into your browser to complete the process:\n\n' + - 'http://' + req.headers.host + '/auth/reset/' + token + '\n\n' + - 'If you did not request this, please ignore this email and your password will remain unchanged.\n' - }; - smtpTransport.sendMail(mailOptions, function(err) { - res.send(200, { - message: 'An email has been sent to ' + user.email + ' with further instructions.' - }); - done(err, 'done'); - }); - } - ], function(err) { - if (err) return next(err); - }); -}; - -/** - * Reset password GET from email token - */ -exports.resetGet = function(req, res) { - User.findOne({ resetPasswordToken: req.params.token, resetPasswordExpires: { $gt: Date.now() } }, function(err, user) { - if (!user) { - // res.render('404'); - res.send(400, { - message: 'Password reset token is invalid or has expired.' - }); - return res.redirect('/#!/forgot'); - } - - res.redirect('/#!/reset/' + req.params.token); - - }); -}; - -/** - * Reset password POST from email token - */ -exports.resetPost = function(req, res) { - // Init Variables - var passwordDetails = req.body; - var message = null; - - async.waterfall([ - function(done) { - User.findOne({ resetPasswordToken: req.params.token, resetPasswordExpires: { $gt: Date.now() } }, function(err, user) { - if (!err && user) { - if (passwordDetails.newPassword === passwordDetails.verifyPassword) { - user.password = passwordDetails.newPassword; - user.resetPasswordToken = undefined; - user.resetPasswordExpires = undefined; - - user.save(function(err) { - if (err) { - return res.send(400, { - message: getErrorMessage(err) - }); - } else { - req.login(user, function(err) { - if (err) { - res.send(400, err); - } else { - done(err, user); - } - }); - } - }); - } else { - return res.send(400, { - message: 'Passwords do not match' - }); - } - } else { - return res.send(400, { - message: 'Password reset token is invalid or has expired.' - }); - } - }); - }, - function(user, done) { - var smtpTransport = nodemailer.createTransport('SMTP', { - service: 'SendGrid', - auth: { - user: 'your_sendgrid_email@domain.com', - pass: 'your_sendgrid_password' - } - }); - var mailOptions = { - to: user.email, - from: 'your_email@domain.com', - subject: 'Your password has been changed', - text: 'Hello,\n\n' + - 'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n' - }; - smtpTransport.sendMail(mailOptions, function(err) { - res.send(200, { - message: 'Password changed successfully' - }); - }); - } - ], function(err) { - res.redirect('/'); - }); -}; - -/** - * Update user details - */ -exports.update = function(req, res) { - // Init Variables - var user = req.user; - var message = null; - - // For security measurement we remove the roles from the req.body object - delete req.body.roles; - - if (user) { - // Merge existing user - user = _.extend(user, req.body); - user.updated = Date.now(); - user.displayName = user.firstName + ' ' + user.lastName; - - user.save(function(err) { - if (err) { - return res.send(400, { - message: getErrorMessage(err) - }); - } else { - req.login(user, function(err) { - if (err) { - res.send(400, err); - } else { - res.jsonp(user); - } - }); - } - }); - } else { - res.send(400, { - message: 'User is not signed in' - }); - } -}; - -/** - * Change Password - */ -exports.changePassword = function(req, res, next) { - // Init Variables - var passwordDetails = req.body; - var message = null; - - if (req.user) { - if (passwordDetails.newPassword) { - User.findById(req.user.id, function(err, user) { - if (!err && user) { - if (user.authenticate(passwordDetails.currentPassword)) { - if (passwordDetails.newPassword === passwordDetails.verifyPassword) { - user.password = passwordDetails.newPassword; - - user.save(function(err) { - if (err) { - return res.send(400, { - message: getErrorMessage(err) - }); - } else { - req.login(user, function(err) { - if (err) { - res.send(400, err); - } else { - res.send({ - message: 'Password changed successfully' - }); - } - }); - } - }); - } else { - res.send(400, { - message: 'Passwords do not match' - }); - } - } else { - res.send(400, { - message: 'Current password is incorrect' - }); - } - } else { - res.send(400, { - message: 'User is not found' - }); - } - }); - } else { - res.send(400, { - message: 'Please provide a new password' - }); - } - } else { - res.send(400, { - message: 'User is not signed in' - }); - } -}; - -/** - * Signout - */ -exports.signout = function(req, res) { - req.logout(); - res.redirect('/'); -}; - -/** - * Send User - */ -exports.me = function(req, res) { - res.jsonp(req.user || null); -}; - -/** - * OAuth callback - */ -exports.oauthCallback = function(strategy) { - return function(req, res, next) { - passport.authenticate(strategy, function(err, user, redirectURL) { - if (err || !user) { - return res.redirect('/#!/signin'); - } - req.login(user, function(err) { - if (err) { - return res.redirect('/#!/signin'); - } - - return res.redirect(redirectURL || '/'); - }); - })(req, res, next); - }; -}; - -/** - * User middleware - */ -exports.userByID = function(req, res, next, id) { - User.findOne({ - _id: id - }).exec(function(err, user) { - if (err) return next(err); - if (!user) return next(new Error('Failed to load User ' + id)); - req.profile = user; - next(); - }); -}; - -/** - * Require login routing middleware - */ -exports.requiresLogin = function(req, res, next) { - if (!req.isAuthenticated()) { - return res.send(401, { - message: 'User is not logged in' - }); - } - - next(); -}; - -/** - * User authorizations routing middleware - */ -exports.hasAuthorization = function(roles) { - var _this = this; - - return function(req, res, next) { - _this.requiresLogin(req, res, function() { - if (_.intersection(req.user.roles, roles).length) { - return next(); - } else { - return res.send(403, { - message: 'User is not authorized' - }); - } - }); - }; -}; - -/** - * Helper function to save or update a OAuth user profile - */ -exports.saveOAuthUserProfile = function(req, providerUserProfile, done) { - if (!req.user) { - // Define a search query fields - var searchMainProviderIdentifierField = 'providerData.' + providerUserProfile.providerIdentifierField; - var searchAdditionalProviderIdentifierField = 'additionalProvidersData.' + providerUserProfile.provider + '.' + providerUserProfile.providerIdentifierField; - - // Define main provider search query - var mainProviderSearchQuery = {}; - mainProviderSearchQuery.provider = providerUserProfile.provider; - mainProviderSearchQuery[searchMainProviderIdentifierField] = providerUserProfile.providerData[providerUserProfile.providerIdentifierField]; - - // Define additional provider search query - var additionalProviderSearchQuery = {}; - additionalProviderSearchQuery[searchAdditionalProviderIdentifierField] = providerUserProfile.providerData[providerUserProfile.providerIdentifierField]; - - // Define a search query to find existing user with current provider profile - var searchQuery = { - $or: [mainProviderSearchQuery, additionalProviderSearchQuery] - }; - - User.findOne(searchQuery, function(err, user) { - if (err) { - return done(err); - } else { - if (!user) { - var possibleUsername = providerUserProfile.username || ((providerUserProfile.email) ? providerUserProfile.email.split('@')[0] : ''); - - User.findUniqueUsername(possibleUsername, null, function(availableUsername) { - user = new User({ - firstName: providerUserProfile.firstName, - lastName: providerUserProfile.lastName, - username: availableUsername, - displayName: providerUserProfile.displayName, - email: providerUserProfile.email, - provider: providerUserProfile.provider, - providerData: providerUserProfile.providerData - }); - - // And save the user - user.save(function(err) { - return done(err, user); - }); - }); - } else { - return done(err, user); - } - } - }); - } else { - // User is already logged in, join the provider data to the existing user - var user = req.user; - - // Check if user exists, is not signed in using this provider, and doesn't have that provider data already configured - if (user.provider !== providerUserProfile.provider && (!user.additionalProvidersData || !user.additionalProvidersData[providerUserProfile.provider])) { - // Add the provider data to the additional provider data field - if (!user.additionalProvidersData) user.additionalProvidersData = {}; - user.additionalProvidersData[providerUserProfile.provider] = providerUserProfile.providerData; - - // Then tell mongoose that we've updated the additionalProvidersData field - user.markModified('additionalProvidersData'); - - // And save the user - user.save(function(err) { - return done(err, user, '/#!/settings/accounts'); - }); - } else { - return done(new Error('User is already connected using this provider'), user); - } - } -}; - -/** - * Remove OAuth provider - */ -exports.removeOAuthProvider = function(req, res, next) { - var user = req.user; - var provider = req.param('provider'); - - if (user && provider) { - // Delete the additional provider - if (user.additionalProvidersData[provider]) { - delete user.additionalProvidersData[provider]; - - // Then tell mongoose that we've updated the additionalProvidersData field - user.markModified('additionalProvidersData'); - } - - user.save(function(err) { - if (err) { - return res.send(400, { - message: getErrorMessage(err) - }); - } else { - req.login(user, function(err) { - if (err) { - res.send(400, err); - } else { - res.jsonp(user); - } - }); - } - }); - } -}; \ No newline at end of file +module.exports.authentication = require('./users/users.authentication'); +module.exports.authorization = require('./users/users.authorization'); +module.exports.password = require('./users/users.password'); +module.exports.profile = require('./users/users.profile'); diff --git a/app/controllers/users/users.authentication.server.controller.js b/app/controllers/users/users.authentication.server.controller.js new file mode 100644 index 0000000000..c9296fe30a --- /dev/null +++ b/app/controllers/users/users.authentication.server.controller.js @@ -0,0 +1,98 @@ +'use strict'; + +/** + * Module dependencies. + */ +var _ = require('lodash'), + errorHandler = require('../errors'), + mongoose = require('mongoose'), + passport = require('passport'), + User = mongoose.model('User'); + +/** + * Signup + */ +exports.signup = function(req, res) { + // For security measurement we remove the roles from the req.body object + delete req.body.roles; + + // Init Variables + var user = new User(req.body); + var message = null; + + // Add missing user fields + user.provider = 'local'; + user.displayName = user.firstName + ' ' + user.lastName; + + // Then save the user + user.save(function(err) { + if (err) { + return res.send(400, { + message: errorHandler.getErrorMessage(err) + }); + } else { + // Remove sensitive data before login + user.password = undefined; + user.salt = undefined; + + req.login(user, function(err) { + if (err) { + res.send(400, err); + } else { + res.jsonp(user); + } + }); + } + }); +}; + +/** + * Signin after passport authentication + */ +exports.signin = function(req, res, next) { + passport.authenticate('local', function(err, user, info) { + if (err || !user) { + res.send(400, info); + } else { + // Remove sensitive data before login + user.password = undefined; + user.salt = undefined; + + req.login(user, function(err) { + if (err) { + res.send(400, err); + } else { + res.jsonp(user); + } + }); + } + })(req, res, next); +}; + +/** + * Signout + */ +exports.signout = function(req, res) { + req.logout(); + res.redirect('/'); +}; + +/** + * OAuth callback + */ +exports.oauthCallback = function(strategy) { + return function(req, res, next) { + passport.authenticate(strategy, function(err, user, redirectURL) { + if (err || !user) { + return res.redirect('/#!/signin'); + } + req.login(user, function(err) { + if (err) { + return res.redirect('/#!/signin'); + } + + return res.redirect(redirectURL || '/'); + }); + })(req, res, next); + }; +}; \ No newline at end of file diff --git a/app/controllers/users/users.authorization.server.controller.js b/app/controllers/users/users.authorization.server.controller.js new file mode 100644 index 0000000000..47cd66fbd9 --- /dev/null +++ b/app/controllers/users/users.authorization.server.controller.js @@ -0,0 +1,54 @@ +'use strict'; + +/** + * Module dependencies. + */ +var _ = require('lodash'), + mongoose = require('mongoose'), + User = mongoose.model('User'); + +/** + * User middleware + */ +exports.userByID = function(req, res, next, id) { + User.findOne({ + _id: id + }).exec(function(err, user) { + if (err) return next(err); + if (!user) return next(new Error('Failed to load User ' + id)); + req.profile = user; + next(); + }); +}; + +/** + * Require login routing middleware + */ +exports.requiresLogin = function(req, res, next) { + if (!req.isAuthenticated()) { + return res.send(401, { + message: 'User is not logged in' + }); + } + + next(); +}; + +/** + * User authorizations routing middleware + */ +exports.hasAuthorization = function(roles) { + var _this = this; + + return function(req, res, next) { + _this.requiresLogin(req, res, function() { + if (_.intersection(req.user.roles, roles).length) { + return next(); + } else { + return res.send(403, { + message: 'User is not authorized' + }); + } + }); + }; +}; \ No newline at end of file diff --git a/app/controllers/users/users.password.server.controller.js b/app/controllers/users/users.password.server.controller.js new file mode 100644 index 0000000000..e4069a0855 --- /dev/null +++ b/app/controllers/users/users.password.server.controller.js @@ -0,0 +1,244 @@ +'use strict'; + +/** + * Module dependencies. + */ +var _ = require('lodash'), + errorHandler = require('../errors'), + mongoose = require('mongoose'), + passport = require('passport'), + User = mongoose.model('User'), + config = require('../../../config/config'), + swig = require('swig'), + nodemailer = require('nodemailer'), + crypto = require('crypto'), + async = require('async'), + crypto = require('crypto'); + +/** + * Forgot for reset password (forgot POST) + */ +exports.forgot = function(req, res, next) { + async.waterfall([ + // Generate random token + function(done) { + crypto.randomBytes(20, function(err, buffer) { + var token = buffer.toString('hex'); + done(err, token); + }); + }, + // Lookup user by username + function(token, done) { + if (req.body.username) { + User.findOne({ + username: req.body.username + }, function(err, user) { + if (!user) { + return res.send(400, { + message: 'No account with that username has been found' + }); + } else if (user.provider !== 'local') { + return res.send(400, { + message: 'It seems like you signed up using your ' + user.provider + ' account' + }); + } else { + user.resetPasswordToken = token; + user.resetPasswordExpires = Date.now() + 3600000; // 1 hour + + user.save(function(err) { + done(err, token, user); + }); + } + }); + } else { + return res.send(400, { + message: 'Username field must not be blank' + }); + } + }, + function(token, user, done) { + res.render('templates/reset-password-email', { + name: user.displayName, + appName: config.app.title, + url: 'http://' + req.headers.host + '/auth/reset/' + token + }, function(err, emailHTML) { + done(err, emailHTML, user); + }); + }, + // If valid email, send reset email using service + function(emailHTML, user, done) { + var smtpTransport = nodemailer.createTransport(config.mailer.options); + var mailOptions = { + to: user.email, + from: config.mailer.fromEmail, + subject: 'Password Reset', + html: emailHTML + }; + smtpTransport.sendMail(mailOptions, function(err) { + res.send(200, { + message: 'An email has been sent to ' + user.email + ' with further instructions.' + }); + done(err, 'done'); + }); + } + ], function(err) { + if (err) return next(err); + }); +}; + +/** + * Reset password GET from email token + */ +exports.validateResetToken = function(req, res) { + User.findOne({ + resetPasswordToken: req.params.token, + resetPasswordExpires: { + $gt: Date.now() + } + }, function(err, user) { + if (!user) { + return res.redirect('/#!/password/reset/invalid'); + } + + res.redirect('/#!/password/reset/' + req.params.token); + }); +}; + +/** + * Reset password POST from email token + */ +exports.reset = function(req, res, next) { + // Init Variables + var passwordDetails = req.body; + var message = null; + + async.waterfall([ + + function(done) { + User.findOne({ + resetPasswordToken: req.params.token, + resetPasswordExpires: { + $gt: Date.now() + } + }, function(err, user) { + if (!err && user) { + if (passwordDetails.newPassword === passwordDetails.verifyPassword) { + user.password = passwordDetails.newPassword; + user.resetPasswordToken = undefined; + user.resetPasswordExpires = undefined; + + user.save(function(err) { + if (err) { + return res.send(400, { + message: errorHandler.getErrorMessage(err) + }); + } else { + req.login(user, function(err) { + if (err) { + res.send(400, err); + } else { + // Return authenticated user + res.jsonp(user); + + done(err, user); + } + }); + } + }); + } else { + return res.send(400, { + message: 'Passwords do not match' + }); + } + } else { + return res.send(400, { + message: 'Password reset token is invalid or has expired.' + }); + } + }); + }, + function(user, done) { + res.render('templates/reset-password-confirm-email', { + name: user.displayName + }, function(err, emailHTML) { + done(err, emailHTML, user); + }); + }, + // If valid email, send reset email using service + function(emailHTML, user, done) { + var smtpTransport = nodemailer.createTransport(config.mailer.options); + var mailOptions = { + to: user.email, + from: config.mailer.fromEmail, + subject: 'Your password has been changed', + html: emailHTML + }; + smtpTransport.sendMail(mailOptions, function(err) { + done(err, 'done'); + }); + } + ], function(err) { + if (err) return next(err); + }); +}; + +/** + * Change Password + */ +exports.changePassword = function(req, res, next) { + // Init Variables + var passwordDetails = req.body; + var message = null; + + if (req.user) { + if (passwordDetails.newPassword) { + User.findById(req.user.id, function(err, user) { + if (!err && user) { + if (user.authenticate(passwordDetails.currentPassword)) { + if (passwordDetails.newPassword === passwordDetails.verifyPassword) { + user.password = passwordDetails.newPassword; + + user.save(function(err) { + if (err) { + return res.send(400, { + message: errorHandler.getErrorMessage(err) + }); + } else { + req.login(user, function(err) { + if (err) { + res.send(400, err); + } else { + res.send({ + message: 'Password changed successfully' + }); + } + }); + } + }); + } else { + res.send(400, { + message: 'Passwords do not match' + }); + } + } else { + res.send(400, { + message: 'Current password is incorrect' + }); + } + } else { + res.send(400, { + message: 'User is not found' + }); + } + }); + } else { + res.send(400, { + message: 'Please provide a new password' + }); + } + } else { + res.send(400, { + message: 'User is not signed in' + }); + } +}; \ No newline at end of file diff --git a/app/controllers/users/users.profile.server.controller.js b/app/controllers/users/users.profile.server.controller.js new file mode 100644 index 0000000000..09695d74d0 --- /dev/null +++ b/app/controllers/users/users.profile.server.controller.js @@ -0,0 +1,164 @@ +'use strict'; + +/** + * Module dependencies. + */ +var _ = require('lodash'), + errorHandler = require('../errors'), + mongoose = require('mongoose'), + passport = require('passport'), + User = mongoose.model('User'); + +/** + * Update user details + */ +exports.update = function(req, res) { + // Init Variables + var user = req.user; + var message = null; + + // For security measurement we remove the roles from the req.body object + delete req.body.roles; + + if (user) { + // Merge existing user + user = _.extend(user, req.body); + user.updated = Date.now(); + user.displayName = user.firstName + ' ' + user.lastName; + + user.save(function(err) { + if (err) { + return res.send(400, { + message: errorHandler.getErrorMessage(err) + }); + } else { + req.login(user, function(err) { + if (err) { + res.send(400, err); + } else { + res.jsonp(user); + } + }); + } + }); + } else { + res.send(400, { + message: 'User is not signed in' + }); + } +}; + +/** + * Send User + */ +exports.me = function(req, res) { + res.jsonp(req.user || null); +}; + +/** + * Helper function to save or update a OAuth user profile + */ +exports.saveOAuthUserProfile = function(req, providerUserProfile, done) { + if (!req.user) { + // Define a search query fields + var searchMainProviderIdentifierField = 'providerData.' + providerUserProfile.providerIdentifierField; + var searchAdditionalProviderIdentifierField = 'additionalProvidersData.' + providerUserProfile.provider + '.' + providerUserProfile.providerIdentifierField; + + // Define main provider search query + var mainProviderSearchQuery = {}; + mainProviderSearchQuery.provider = providerUserProfile.provider; + mainProviderSearchQuery[searchMainProviderIdentifierField] = providerUserProfile.providerData[providerUserProfile.providerIdentifierField]; + + // Define additional provider search query + var additionalProviderSearchQuery = {}; + additionalProviderSearchQuery[searchAdditionalProviderIdentifierField] = providerUserProfile.providerData[providerUserProfile.providerIdentifierField]; + + // Define a search query to find existing user with current provider profile + var searchQuery = { + $or: [mainProviderSearchQuery, additionalProviderSearchQuery] + }; + + User.findOne(searchQuery, function(err, user) { + if (err) { + return done(err); + } else { + if (!user) { + var possibleUsername = providerUserProfile.username || ((providerUserProfile.email) ? providerUserProfile.email.split('@')[0] : ''); + + User.findUniqueUsername(possibleUsername, null, function(availableUsername) { + user = new User({ + firstName: providerUserProfile.firstName, + lastName: providerUserProfile.lastName, + username: availableUsername, + displayName: providerUserProfile.displayName, + email: providerUserProfile.email, + provider: providerUserProfile.provider, + providerData: providerUserProfile.providerData + }); + + // And save the user + user.save(function(err) { + return done(err, user); + }); + }); + } else { + return done(err, user); + } + } + }); + } else { + // User is already logged in, join the provider data to the existing user + var user = req.user; + + // Check if user exists, is not signed in using this provider, and doesn't have that provider data already configured + if (user.provider !== providerUserProfile.provider && (!user.additionalProvidersData || !user.additionalProvidersData[providerUserProfile.provider])) { + // Add the provider data to the additional provider data field + if (!user.additionalProvidersData) user.additionalProvidersData = {}; + user.additionalProvidersData[providerUserProfile.provider] = providerUserProfile.providerData; + + // Then tell mongoose that we've updated the additionalProvidersData field + user.markModified('additionalProvidersData'); + + // And save the user + user.save(function(err) { + return done(err, user, '/#!/settings/accounts'); + }); + } else { + return done(new Error('User is already connected using this provider'), user); + } + } +}; + +/** + * Remove OAuth provider + */ +exports.removeOAuthProvider = function(req, res, next) { + var user = req.user; + var provider = req.param('provider'); + + if (user && provider) { + // Delete the additional provider + if (user.additionalProvidersData[provider]) { + delete user.additionalProvidersData[provider]; + + // Then tell mongoose that we've updated the additionalProvidersData field + user.markModified('additionalProvidersData'); + } + + user.save(function(err) { + if (err) { + return res.send(400, { + message: errorHandler.getErrorMessage(err) + }); + } else { + req.login(user, function(err) { + if (err) { + res.send(400, err); + } else { + res.jsonp(user); + } + }); + } + }); + } +}; \ No newline at end of file diff --git a/app/models/user.server.model.js b/app/models/user.server.model.js index 6af0c8b3ad..76ff071ba2 100755 --- a/app/models/user.server.model.js +++ b/app/models/user.server.model.js @@ -50,7 +50,7 @@ var UserSchema = new Schema({ }, username: { type: String, - unique: true, + unique: 'testing error message', required: 'Please fill in a username', trim: true }, diff --git a/app/routes/articles.server.routes.js b/app/routes/articles.server.routes.js index 9dfcb03887..6c6be137ea 100644 --- a/app/routes/articles.server.routes.js +++ b/app/routes/articles.server.routes.js @@ -10,12 +10,12 @@ module.exports = function(app) { // Article Routes app.route('/articles') .get(articles.list) - .post(users.requiresLogin, articles.create); + .post(users.authorization.requiresLogin, articles.create); app.route('/articles/:articleId') .get(articles.read) - .put(users.requiresLogin, articles.hasAuthorization, articles.update) - .delete(users.requiresLogin, articles.hasAuthorization, articles.delete); + .put(users.authorization.requiresLogin, articles.hasAuthorization, articles.update) + .delete(users.authorization.requiresLogin, articles.hasAuthorization, articles.delete); // Finish by binding the article middleware app.param('articleId', articles.articleByID); diff --git a/app/routes/users.server.routes.js b/app/routes/users.server.routes.js index d7cd2fa71b..5b78392c0d 100644 --- a/app/routes/users.server.routes.js +++ b/app/routes/users.server.routes.js @@ -8,28 +8,32 @@ var passport = require('passport'); module.exports = function(app) { // User Routes var users = require('../../app/controllers/users'); - app.route('/users/me').get(users.me); - app.route('/users').put(users.update); - app.route('/users/password').post(users.changePassword); - app.route('/users/accounts').delete(users.removeOAuthProvider); - - // Setting up the users api - app.route('/auth/signup').post(users.signup); - app.route('/auth/signin').post(users.signin); - app.route('/auth/signout').get(users.signout); - app.route('/auth/forgot').post(users.forgot); - app.route('/auth/reset/:token').get(users.resetGet); - app.route('/auth/reset/:token').post(users.resetPost); + + // Setting up the users profile api + app.route('/users/me').get(users.profile.me); + app.route('/users').put(users.profile.update); + app.route('/users/accounts').delete(users.profile.removeOAuthProvider); + + // Setting up the users password api + app.route('/users/password').post(users.password.changePassword); + app.route('/auth/forgot').post(users.password.forgot); + app.route('/auth/reset/:token').get(users.password.validateResetToken); + app.route('/auth/reset/:token').post(users.password.reset); + + // Setting up the users authentication api + app.route('/auth/signup').post(users.authentication.signup); + app.route('/auth/signin').post(users.authentication.signin); + app.route('/auth/signout').get(users.authentication.signout); // Setting the facebook oauth routes app.route('/auth/facebook').get(passport.authenticate('facebook', { scope: ['email'] })); - app.route('/auth/facebook/callback').get(users.oauthCallback('facebook')); + app.route('/auth/facebook/callback').get(users.authentication.oauthCallback('facebook')); // Setting the twitter oauth routes app.route('/auth/twitter').get(passport.authenticate('twitter')); - app.route('/auth/twitter/callback').get(users.oauthCallback('twitter')); + app.route('/auth/twitter/callback').get(users.authentication.oauthCallback('twitter')); // Setting the google oauth routes app.route('/auth/google').get(passport.authenticate('google', { @@ -38,12 +42,12 @@ module.exports = function(app) { 'https://www.googleapis.com/auth/userinfo.email' ] })); - app.route('/auth/google/callback').get(users.oauthCallback('google')); + app.route('/auth/google/callback').get(users.authentication.oauthCallback('google')); // Setting the linkedin oauth routes app.route('/auth/linkedin').get(passport.authenticate('linkedin')); - app.route('/auth/linkedin/callback').get(users.oauthCallback('linkedin')); + app.route('/auth/linkedin/callback').get(users.authentication.oauthCallback('linkedin')); // Finish by binding the user middleware - app.param('userId', users.userByID); + app.param('userId', users.authorization.userByID); }; diff --git a/app/views/templates/reset-password-confirm-email.server.view.html b/app/views/templates/reset-password-confirm-email.server.view.html new file mode 100644 index 0000000000..eec61a672c --- /dev/null +++ b/app/views/templates/reset-password-confirm-email.server.view.html @@ -0,0 +1,16 @@ + + + +
+ + + +Dear {{name}},
+ +This is a confirmation that the password for your account has just been changed
+The {{appName}} Support Team
+ + + \ No newline at end of file diff --git a/app/views/templates/reset-password-email.server.view.html b/app/views/templates/reset-password-email.server.view.html new file mode 100644 index 0000000000..eb73cb83e3 --- /dev/null +++ b/app/views/templates/reset-password-email.server.view.html @@ -0,0 +1,22 @@ + + + + + + + + +Dear {{name}},
++ You have requested to have your password reset for your account at {{appName}} +
+Please visit this url to reset your password:
+{{url}}
+ If you didn't make this request, you can ignore this email. +The {{appName}} Support Team
+ + + \ No newline at end of file diff --git a/bower.json b/bower.json index 7335610f8c..f86606fe45 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "meanjs", - "version": "0.3.1", + "version": "0.4.0", "description": "Fullstack JavaScript with MongoDB, Express, AngularJS, and Node.js.", "dependencies": { "bootstrap": "~3", diff --git a/config/config.js b/config/config.js index 8ba8be42fb..3baa02e552 100644 --- a/config/config.js +++ b/config/config.js @@ -22,7 +22,7 @@ module.exports.getGlobbedFiles = function(globPatterns, removeRoot) { var _this = this; // URL paths regex - var urlRegex = new RegExp('^(?:[a-z]+:)?//', 'i'); + var urlRegex = new RegExp('^(?:[a-z]+:)?\/\/', 'i'); // The output array var output = []; diff --git a/config/env/development.js b/config/env/development.js index 8f1c18b73b..738208ab6d 100644 --- a/config/env/development.js +++ b/config/env/development.js @@ -1,9 +1,7 @@ 'use strict'; -var DB_HOST = process.env.DB_1_PORT_27017_TCP_ADDR || 'localhost'; - module.exports = { - db: 'mongodb://' + DB_HOST + '/mean-dev', + db: 'mongodb://' + (process.env.DB_1_PORT_27017_TCP_ADDR || 'localhost') + '/mean-dev', app: { title: 'MEAN.JS - Development Environment' }, @@ -26,5 +24,15 @@ module.exports = { clientID: process.env.LINKEDIN_ID || 'APP_ID', clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', callbackURL: 'http://localhost:3000/auth/linkedin/callback' + }, + mailer: { + fromEmail: process.env.MAILER_FROM_EMAIL || 'MAILER_FROM_EMAIL', + options: { + service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER', + auth: { + user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID', + pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD' + } + } } }; \ No newline at end of file diff --git a/config/env/production.js b/config/env/production.js index 2de715fef1..66d6b63678 100644 --- a/config/env/production.js +++ b/config/env/production.js @@ -1,9 +1,7 @@ 'use strict'; -var DB_HOST = process.env.DB_1_PORT_27017_TCP_ADDR || 'localhost'; - module.exports = { - db: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://' + DB_HOST + '/mean', + db: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://' + (process.env.DB_1_PORT_27017_TCP_ADDR || 'localhost') + '/mean', assets: { lib: { css: [ @@ -41,5 +39,15 @@ module.exports = { clientID: process.env.LINKEDIN_ID || 'APP_ID', clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', callbackURL: 'http://localhost:3000/auth/linkedin/callback' + }, + mailer: { + fromEmail: process.env.MAILER_FROM_EMAIL || 'MAILER_FROM_EMAIL', + options: { + service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER', + auth: { + user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID', + pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD' + } + } } }; \ No newline at end of file diff --git a/config/env/test.js b/config/env/test.js index 03135e6cfc..408f7563f5 100644 --- a/config/env/test.js +++ b/config/env/test.js @@ -1,9 +1,7 @@ 'use strict'; -var DB_HOST = process.env.DB_1_PORT_27017_TCP_ADDR || 'localhost'; - module.exports = { - db: 'mongodb://' + DB_HOST + '/mean-test', + db: 'mongodb://' + (process.env.DB_1_PORT_27017_TCP_ADDR || 'localhost') + '/mean-test', port: 3001, app: { title: 'MEAN.JS - Test Environment' @@ -27,5 +25,15 @@ module.exports = { clientID: process.env.LINKEDIN_ID || 'APP_ID', clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', callbackURL: 'http://localhost:3000/auth/linkedin/callback' + }, + mailer: { + fromEmail: process.env.MAILER_FROM_EMAIL || 'MAILER_FROM_EMAIL', + options: { + service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER', + auth: { + user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID', + pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD' + } + } } }; \ No newline at end of file diff --git a/config/express.js b/config/express.js index 1614cc63e4..bea1c88dac 100755 --- a/config/express.js +++ b/config/express.js @@ -73,7 +73,9 @@ module.exports = function(db) { } // Request body parsing middleware should be above methodOverride - app.use(bodyParser.urlencoded()); + app.use(bodyParser.urlencoded({ + extended: true + })); app.use(bodyParser.json()); app.use(methodOverride()); @@ -85,6 +87,8 @@ module.exports = function(db) { // Express MongoDB session storage app.use(session({ + saveUninitialized: true, + resave: true, secret: config.sessionSecret, store: new mongoStore({ db: db.connection.db, @@ -101,8 +105,8 @@ module.exports = function(db) { // Use helmet to secure Express headers app.use(helmet.xframe()); - app.use(helmet.iexss()); - app.use(helmet.contentTypeOptions()); + app.use(helmet.xssFilter()); + app.use(helmet.nosniff()); app.use(helmet.ienoopen()); app.disable('x-powered-by'); diff --git a/config/init.js b/config/init.js index 4fb15d5c47..d5bd17cc0e 100644 --- a/config/init.js +++ b/config/init.js @@ -19,9 +19,9 @@ module.exports = function() { console.log(); if (!environmentFiles.length) { if (process.env.NODE_ENV) { - console.log('\x1b[31m', 'No configuration file found for "' + process.env.NODE_ENV + '" environment using development instead'); + console.error('\x1b[31m', 'No configuration file found for "' + process.env.NODE_ENV + '" environment using development instead'); } else { - console.log('\x1b[31m', 'NODE_ENV is not defined! Using default development environment'); + console.error('\x1b[31m', 'NODE_ENV is not defined! Using default development environment'); } process.env.NODE_ENV = 'development'; diff --git a/gruntfile.js b/gruntfile.js index aab536a558..ca48bbd883 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -5,7 +5,7 @@ module.exports = function(grunt) { var watchFiles = { serverViews: ['app/views/**/*.*'], serverJS: ['gruntfile.js', 'server.js', 'config/**/*.js', 'app/**/*.js'], - clientViews: ['public/modules/**/views/*.html'], + clientViews: ['public/modules/**/views/**/*.html'], clientJS: ['public/js/*.js', 'public/modules/**/*.js'], clientCSS: ['public/modules/**/*.css'], mochaTests: ['app/tests/**/*.js'] diff --git a/package.json b/package.json index 99cf5d7cb3..650c2b0475 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "meanjs", "description": "Full-Stack JavaScript with MongoDB, Express, AngularJS, and Node.js.", - "version": "0.3.1", + "version": "0.4.0", "private": false, "author": "https://github.com/meanjs/mean/graphs/contributors", "repository": { @@ -18,18 +18,18 @@ "postinstall": "bower install --config.interactive=false" }, "dependencies": { - "express": "~4.2.0", - "express-session": "~1.1.0", - "body-parser": "~1.2.0", - "cookie-parser": "~1.1.0", - "compression": "~1.0.1", - "method-override": "~1.0.0", - "morgan": "~1.1.0", - "connect-mongo": "~0.4.0", + "express": "~4.7.2", + "express-session": "~1.7.2", + "body-parser": "~1.5.2", + "cookie-parser": "~1.3.2", + "compression": "~1.0.9", + "method-override": "~2.1.2", + "morgan": "~1.2.2", + "connect-mongo": "~0.4.1", "connect-flash": "~0.1.1", - "helmet": "~0.2.1", + "helmet": "~0.4.0", "consolidate": "~0.10.0", - "swig": "~1.3.2", + "swig": "~1.4.1", "mongoose": "~3.8.8", "passport": "~0.2.0", "passport-local": "~1.0.0", @@ -39,29 +39,28 @@ "passport-google-oauth": "~0.1.5", "lodash": "~2.4.1", "forever": "~0.11.0", - "bower": "~1.3.1", + "bower": "~1.3.8", "grunt-cli": "~0.1.13", - "glob": "~3.2.9", - "bcrypt-nodejs": "0.0.3", - "async": "~0.8.0", - "nodemailer": "~0.6.3" + "glob": "~4.0.5", + "async": "~0.9.0", + "nodemailer": "~1.1.1" }, "devDependencies": { - "supertest": "~0.12.1", - "should": "~3.3.1", + "supertest": "~0.13.0", + "should": "~4.0.4", "grunt-env": "~0.4.1", "grunt-node-inspector": "~0.1.3", "grunt-contrib-watch": "~0.6.1", "grunt-contrib-jshint": "~0.10.0", "grunt-contrib-csslint": "^0.2.0", "grunt-ngmin": "0.0.3", - "grunt-contrib-uglify": "~0.4.0", - "grunt-contrib-cssmin": "~0.9.0", - "grunt-nodemon": "~0.2.1", + "grunt-contrib-uglify": "~0.5.1", + "grunt-contrib-cssmin": "~0.10.0", + "grunt-nodemon": "~0.3.0", "grunt-concurrent": "~0.5.0", - "grunt-mocha-test": "~0.10.0", + "grunt-mocha-test": "~0.11.0", "grunt-karma": "~0.8.2", - "load-grunt-tasks": "~0.4.0", + "load-grunt-tasks": "~0.6.0", "karma": "~0.12.0", "karma-jasmine": "~0.2.1", "karma-coverage": "~0.2.0", diff --git a/public/config.js b/public/config.js index f61ff77ac9..75de1c4bce 100644 --- a/public/config.js +++ b/public/config.js @@ -7,9 +7,9 @@ var ApplicationConfiguration = (function() { var applicationModuleVendorDependencies = ['ngResource', 'ngAnimate', 'ui.router', 'ui.bootstrap', 'ui.utils']; // Add a new vertical module - var registerModule = function(moduleName) { + var registerModule = function(moduleName, dependencies) { // Create angular module - angular.module(moduleName, []); + angular.module(moduleName, dependencies || []); // Add the module to the AngularJS configuration file angular.module(applicationModuleName).requires.push(moduleName); diff --git a/public/modules/articles/controllers/articles.client.controller.js b/public/modules/articles/controllers/articles.client.controller.js index a5bc0c9e4c..d0a3fb12c6 100644 --- a/public/modules/articles/controllers/articles.client.controller.js +++ b/public/modules/articles/controllers/articles.client.controller.js @@ -49,7 +49,7 @@ angular.module('articles').controller('ArticlesController', ['$scope', '$statePa $scope.error = errorResponse.data.message; }); } else { - scope.submitted = true; + $scope.submitted = true; } }; diff --git a/public/modules/users/config/users.client.routes.js b/public/modules/users/config/users.client.routes.js index 8b35fac649..4ae5ff9a60 100755 --- a/public/modules/users/config/users.client.routes.js +++ b/public/modules/users/config/users.client.routes.js @@ -19,19 +19,27 @@ angular.module('users').config(['$stateProvider', }). state('signup', { url: '/signup', - templateUrl: 'modules/users/views/signup.client.view.html' + templateUrl: 'modules/users/views/authentication/signup.client.view.html' }). state('signin', { url: '/signin', - templateUrl: 'modules/users/views/signin.client.view.html' + templateUrl: 'modules/users/views/authentication/signin.client.view.html' }). state('forgot', { - url: '/forgot', - templateUrl: 'modules/users/views/forgot.client.view.html' + url: '/password/forgot', + templateUrl: 'modules/users/views/password/forgot-password.client.view.html' + }). + state('reset-invlaid', { + url: '/password/reset/invalid', + templateUrl: 'modules/users/views/password/reset-password-invalid.client.view.html' + }). + state('reset-success', { + url: '/password/reset/success', + templateUrl: 'modules/users/views/password/reset-password-success.client.view.html' }). state('reset', { - url: '/reset/:token', - templateUrl: 'modules/users/views/reset.client.view.html' + url: '/password/reset/:token', + templateUrl: 'modules/users/views/password/reset-password.client.view.html' }); } ]); \ No newline at end of file diff --git a/public/modules/users/controllers/authentication.client.controller.js b/public/modules/users/controllers/authentication.client.controller.js index 27430f025b..52b1891116 100644 --- a/public/modules/users/controllers/authentication.client.controller.js +++ b/public/modules/users/controllers/authentication.client.controller.js @@ -1,19 +1,19 @@ 'use strict'; angular.module('users').controller('AuthenticationController', ['$scope', '$stateParams', '$http', '$location', 'Authentication', - function($scope, $stateParams, $http, $location, Authentication) { - $scope.authentication = Authentication; + function($scope, $stateParams, $http, $location, Authentication) { + $scope.authentication = Authentication; - //If user is signed in then redirect back home + // If user is signed in then redirect back home if ($scope.authentication.user) $location.path('/'); $scope.signup = function(isValid) { - if (isValid){ + if (isValid) { $http.post('/auth/signup', $scope.credentials).success(function(response) { - //If successful we assign the response to the global user model + // If successful we assign the response to the global user model $scope.authentication.user = response; - - //And redirect to the index page + + // And redirect to the index page $location.path('/'); }).error(function(response) { $scope.error = response.message; @@ -25,45 +25,14 @@ angular.module('users').controller('AuthenticationController', ['$scope', '$stat $scope.signin = function() { $http.post('/auth/signin', $scope.credentials).success(function(response) { - //If successful we assign the response to the global user model + // If successful we assign the response to the global user model $scope.authentication.user = response; - //And redirect to the index page - $location.path('/'); - }).error(function(response) { - $scope.error = response.message; - }); - }; - - $scope.forgot = function() { - $scope.success = $scope.error = null; - - $http.post('/auth/forgot', $scope.credentials).success(function(response) { - // Show user success message and clear form - $scope.credentials = null; - $scope.success = response.message; - - }).error(function(response) { - // Show user error message and clear form - $scope.credentials = null; - $scope.error = response.message; - }); - }; - - // Change user password - $scope.reset = function() { - $scope.success = $scope.error = null; - - $http.post('/auth/reset/' + $stateParams.token, - $scope.passwordDetails).success(function(response) { - - // If successful show success message and clear form - $scope.success = response.message; - $scope.passwordDetails = null; - - }).error(function(response) { - $scope.error = response.message; - }); - }; - } -]); + // And redirect to the index page + $location.path('/'); + }).error(function(response) { + $scope.error = response.message; + }); + }; + } +]); \ No newline at end of file diff --git a/public/modules/users/controllers/password.client.controller.js b/public/modules/users/controllers/password.client.controller.js new file mode 100644 index 0000000000..dbc9e92977 --- /dev/null +++ b/public/modules/users/controllers/password.client.controller.js @@ -0,0 +1,44 @@ +'use strict'; + +angular.module('users').controller('PasswordController', ['$scope', '$stateParams', '$http', '$location', 'Authentication', + function($scope, $stateParams, $http, $location, Authentication) { + $scope.authentication = Authentication; + + //If user is signed in then redirect back home + if ($scope.authentication.user) $location.path('/'); + + // Submit forgotten password account id + $scope.askForPasswordReset = function() { + $scope.success = $scope.error = null; + + $http.post('/auth/forgot', $scope.credentials).success(function(response) { + // Show user success message and clear form + $scope.credentials = null; + $scope.success = response.message; + + }).error(function(response) { + // Show user error message and clear form + $scope.credentials = null; + $scope.error = response.message; + }); + }; + + // Change user password + $scope.resetUserPassword = function() { + $scope.success = $scope.error = null; + + $http.post('/auth/reset/' + $stateParams.token, $scope.passwordDetails).success(function(response) { + // If successful show success message and clear form + $scope.passwordDetails = null; + + // Attach user profile + Authentication.user = response; + + // And redirect to the index page + $location.path('/password/reset/success'); + }).error(function(response) { + $scope.error = response.message; + }); + }; + } +]); \ No newline at end of file diff --git a/public/modules/users/views/signin.client.view.html b/public/modules/users/views/authentication/signin.client.view.html similarity index 96% rename from public/modules/users/views/signin.client.view.html rename to public/modules/users/views/authentication/signin.client.view.html index 813cc82b60..4b7db025b1 100644 --- a/public/modules/users/views/signin.client.view.html +++ b/public/modules/users/views/authentication/signin.client.view.html @@ -31,7 +31,7 @@Enter your account username.