diff --git a/app/index.js b/app/index.js index c37abac8c..1059c2b04 100644 --- a/app/index.js +++ b/app/index.js @@ -139,17 +139,17 @@ var AngularFullstackGenerator = yeoman.generators.Base.extend({ { value: 'googleAuth', name: 'Google', - checked: false + checked: true }, { value: 'facebookAuth', name: 'Facebook', - checked: false + checked: true }, { value: 'twitterAuth', name: 'Twitter', - checked: false + checked: true } ] }, { diff --git a/app/templates/client/app/account(auth)/settings/settings(html).html b/app/templates/client/app/account(auth)/settings/settings(html).html index bb5d8ded0..94d0ee013 100644 --- a/app/templates/client/app/account(auth)/settings/settings(html).html +++ b/app/templates/client/app/account(auth)/settings/settings(html).html @@ -3,16 +3,35 @@
-

Change Password

+

Email

-
+ + +
+ + +

+ | Email not valid +

+
+ + +
+
+
+
+
+

<% if (filters.oauth) { %>{{ user.localEnabled ? 'Change' : 'Set' }}<% } else { %>Change<% } %> Password

+
+
+
- + ng-disabled='!user.localEnabled' <% } %>/>

{{ errors.other }}

@@ -21,11 +40,11 @@

Change Password

- + required />

+ ng-show="(pwd.new.$error.minlength || pwd.new.$error.required) && (pwd.new.$dirty || pwd.submitted)"> Password must be at least 3 characters.

@@ -36,4 +55,12 @@

Change Password

+ <% if (filters.oauth) { %> + + <% } %>
\ No newline at end of file diff --git a/app/templates/client/app/account(auth)/settings/settings(jade).jade b/app/templates/client/app/account(auth)/settings/settings(jade).jade index 2dc55d402..8565781c2 100644 --- a/app/templates/client/app/account(auth)/settings/settings(jade).jade +++ b/app/templates/client/app/account(auth)/settings/settings(jade).jade @@ -2,20 +2,47 @@ div(ng-include='"components/navbar/navbar.html"') .container .row .col-sm-12 - h1 Change Password + h1 Email + + .col-sm-12 + form(role='form', name='email', ng-submit='changeEmail()', novalidate) + + .form-group.has-feedback + label Current Email + input.form-control(type='email', name='email', ng-model='user.email', placeholder='ex. me@awesome.com') + span.glyphicon.glyphicon-ok.form-control-feedback(ng-if='email.confirmed', title='email confirmed') + p.help-block(ng-show='!email.email.$valid') + | Email not valid + + button.btn.btn-lg.btn-primary(type='submit') Save changes + + .row .col-sm-12 - form.form(name='form', ng-submit='changePassword(form)', novalidate='') + h1 <% if (filters.oauth) { %>{{ user.localEnabled ? 'Change' : 'Set' }}<% } else { %>Change<% } %> Password + + .col-sm-12 + form(role='form', name='pwd', ng-submit='<% if(filters.oauth) { %>!user.localEnabled ? setPassword() : <% } %>changePassword()', novalidate) + .form-group label Current Password - input.form-control(type='password', name='password', ng-model='user.oldPassword', mongoose-error='') - p.help-block(ng-show='form.password.$error.mongoose') + input.form-control(type='password', name='old', placeholder='ex. password123', ng-model='user.oldPassword', <% if (filters.oauth) { %>ng-disabled='!user.localEnabled', <% } %>mongoose-error='') + p.help-block(ng-show='pwd.old.$error.mongoose') | {{ errors.other }} + .form-group label New Password - input.form-control(type='password', name='newPassword', ng-model='user.newPassword', ng-minlength='3', required='') - p.help-block(ng-show='(form.newPassword.$error.minlength || form.newPassword.$error.required) && (form.newPassword.$dirty || submitted)') + input.form-control(type='password', name='new', placeholder='ex. GoofyM1ckeyDonald&Pluto', ng-model='user.newPassword', ng-minlength='3', required) + p.help-block(ng-show='(pwd.new.$error.minlength || pwd.new.$error.required) && (pwd.new.$dirty || pwd.submitted)') | Password must be at least 3 characters. - p.help-block {{ message }} + p.help-block {{ message }} button.btn.btn-lg.btn-primary(type='submit') Save changes + +<% if (filters.oauth) { %> + //- .row + //- .col-sm-12 + //- h1 Social accounts + + //- .col-sm-12 +<% } %> diff --git a/app/templates/client/app/account(auth)/settings/settings.controller(coffee).coffee b/app/templates/client/app/account(auth)/settings/settings.controller(coffee).coffee index e058f167e..4cb856146 100644 --- a/app/templates/client/app/account(auth)/settings/settings.controller(coffee).coffee +++ b/app/templates/client/app/account(auth)/settings/settings.controller(coffee).coffee @@ -1,17 +1,59 @@ 'use strict' angular.module '<%= scriptAppName %>' -.controller 'SettingsCtrl', ($scope, User, Auth) -> +.controller 'SettingsCtrl', ($scope, Auth) -> $scope.errors = {} - $scope.changePassword = (form) -> - $scope.submitted = true - if form.$valid + $scope.user = Auth.getCurrentUser() + $scope.email = {} + + getEmail = (user) -> + return [null, null] unless $scope.user.credentials.length + + for c in $scope.user.credentials when c.type is 'email' + return [c.value, c.confirmed] + + [null, null] + + [initialEmail, $scope.email.confirmed] = getEmail $scope.user + + $scope.user.email = initialEmail + + $scope.changeEmail = -> + if $scope.email.$valid + Auth.changeEmail initialEmail, $scope.user.email + .then -> + $scope.message = 'Email successfully changed' + + .catch -> + # TODO: handle errors + $scope.message = '' + + $scope.changePassword = -> + $scope.pwd.submitted = true + + if $scope.pwd.$valid Auth.changePassword $scope.user.oldPassword, $scope.user.newPassword .then -> - $scope.message = 'Password successfully changed.' + $scope.message = 'Password successfully changed' .catch -> - form.password.$setValidity 'mongoose', false + $scope.pwd.old.$setValidity 'mongoose', false $scope.errors.other = 'Incorrect password' $scope.message = '' +<% if (filters.oauth) { %> + $scope.setPassword = -> + $scope.pwd.submitted = true + + if $scope.pwd.$valid + Auth.setPassword $scope.user.newPassword + .then -> + $scope.message = 'Password successfully set' + $scope.user.localEnabled = true + $scope.user.newPassword = '' + + .catch -> + $scope.pwd.old.$setValidity 'mongoose', false + $scope.errors.other = 'Another account with that email already exists' + $scope.message = '' +<% } %> diff --git a/app/templates/client/app/account(auth)/settings/settings.controller(js).js b/app/templates/client/app/account(auth)/settings/settings.controller(js).js index 829bd8248..2fcf3cbf7 100644 --- a/app/templates/client/app/account(auth)/settings/settings.controller(js).js +++ b/app/templates/client/app/account(auth)/settings/settings.controller(js).js @@ -1,21 +1,71 @@ 'use strict'; angular.module('<%= scriptAppName %>') - .controller('SettingsCtrl', function ($scope, User, Auth) { + .controller('SettingsCtrl', function ($scope, Auth) { $scope.errors = {}; - $scope.changePassword = function(form) { - $scope.submitted = true; - if(form.$valid) { + $scope.user = Auth.getCurrentUser(); + $scope.email = {}; + + var getEmail = function(user) { + if (!$scope.user.credentials.length) { + return null; + } + + for(var i in $scope.user.credentials) { + var c = $scope.user.credentials[i]; + if(c.type==='email') return [c.value, c.confirmed]; + } + }; + + var tmp = getEmail($scope.user); + + var initialEmail = tmp ? tmp[0] : null; + $scope.email.confirmed = tmp ? tmp[1] : null; + + $scope.user.email = initialEmail; + + $scope.changeEmail = function () { + if($scope.email.$valid) { + Auth.changeEmail(initialEmail, $scope.user.email) + .then(function() { + $scope.message = 'Email successfully changed'; + }) + .catch(function() { + // TODO: handle errors + $scope.message = ''; + }); + } + } + + $scope.changePassword = function() { + $scope.pwd.submitted = true; + if($scope.pwd.$valid) { Auth.changePassword( $scope.user.oldPassword, $scope.user.newPassword ) .then( function() { - $scope.message = 'Password successfully changed.'; + $scope.message = 'Password successfully changed'; }) .catch( function() { - form.password.$setValidity('mongoose', false); + $scope.pwd.old.$setValidity('mongoose', false); $scope.errors.other = 'Incorrect password'; $scope.message = ''; }); } }; +<% if (filters.oauth) { %> + $scope.setPassword = function() { + $scope.submitted = true; + if($scope.pwd.$valid) { + Auth.changePassword( $scope.user.newPassword ) + .then( function() { + $scope.message = 'Password successfully set'; + }) + .catch( function() { + $scope.pwd.old.$setValidity('mongoose', false); + $scope.errors.other = 'Another account with that email already exists'; + $scope.message = ''; + }); + } + }; +<% } %> }); diff --git a/app/templates/client/app/admin(auth)/admin(css).css b/app/templates/client/app/admin(auth)/admin(css).css index a6f536dc5..f064e4d88 100644 --- a/app/templates/client/app/admin(auth)/admin(css).css +++ b/app/templates/client/app/admin(auth)/admin(css).css @@ -1 +1,3 @@ .trash { color:rgb(209, 91, 71); } +.confirm { color:rgb(240, 173, 78); } +.confirmed { color:rgb(92, 184, 92); } diff --git a/app/templates/client/app/admin(auth)/admin(html).html b/app/templates/client/app/admin(auth)/admin(html).html index 5c27c7af2..8d4833c85 100644 --- a/app/templates/client/app/admin(auth)/admin(html).html +++ b/app/templates/client/app/admin(auth)/admin(html).html @@ -6,6 +6,7 @@
  • {{user.name}}
    {{user.email}} +
  • diff --git a/app/templates/client/app/admin(auth)/admin(jade).jade b/app/templates/client/app/admin(auth)/admin(jade).jade index a4672dadd..d5983eccd 100644 --- a/app/templates/client/app/admin(auth)/admin(jade).jade +++ b/app/templates/client/app/admin(auth)/admin(jade).jade @@ -6,6 +6,9 @@ div(ng-include='"components/navbar/navbar.html"') li.list-group-item(ng-repeat='user in users') strong {{user.name}} br - span.text-muted {{user.email}} - a.trash(ng-click='delete(user)') + span.text-muted {{user.credentials[0].value}} + a(ng-click='confirm(user)', ng-class="user.credentials[0].confirmed ? 'confirmed' : 'confirm'", title='confirm email') + span.glyphicon.glyphicon-ok-circle + + a.trash(ng-click='delete(user)', title='delete user') span.glyphicon.glyphicon-trash.pull-right diff --git a/app/templates/client/app/admin(auth)/admin(less).less b/app/templates/client/app/admin(auth)/admin(less).less index ad8202750..f0a247e2c 100644 --- a/app/templates/client/app/admin(auth)/admin(less).less +++ b/app/templates/client/app/admin(auth)/admin(less).less @@ -1 +1,3 @@ -.trash { color:rgb(209, 91, 71); } \ No newline at end of file +.trash { color:rgb(209, 91, 71); } +.confirm { color:rgb(240, 173, 78); } +.confirmed { color:rgb(92, 184, 92); } \ No newline at end of file diff --git a/app/templates/client/app/admin(auth)/admin(sass).scss b/app/templates/client/app/admin(auth)/admin(sass).scss index a6f536dc5..f064e4d88 100644 --- a/app/templates/client/app/admin(auth)/admin(sass).scss +++ b/app/templates/client/app/admin(auth)/admin(sass).scss @@ -1 +1,3 @@ .trash { color:rgb(209, 91, 71); } +.confirm { color:rgb(240, 173, 78); } +.confirmed { color:rgb(92, 184, 92); } diff --git a/app/templates/client/app/admin(auth)/admin(stylus).styl b/app/templates/client/app/admin(auth)/admin(stylus).styl index d57e50db5..29f31a2d6 100644 --- a/app/templates/client/app/admin(auth)/admin(stylus).styl +++ b/app/templates/client/app/admin(auth)/admin(stylus).styl @@ -1,2 +1,8 @@ .trash - color rgb(209, 91, 71) \ No newline at end of file + color rgb(209, 91, 71) + +.confirm + color: rgb(240, 173, 78) + +.confirmed + color: rgb(92, 184, 92) \ No newline at end of file diff --git a/app/templates/client/app/admin(auth)/admin.controller(coffee).coffee b/app/templates/client/app/admin(auth)/admin.controller(coffee).coffee index 6f5aef8c4..bb4dc4ed4 100644 --- a/app/templates/client/app/admin(auth)/admin.controller(coffee).coffee +++ b/app/templates/client/app/admin(auth)/admin.controller(coffee).coffee @@ -1,7 +1,7 @@ 'use strict' angular.module '<%= scriptAppName %>' -.controller 'AdminCtrl', ($scope, $http, Auth, User) -> +.controller 'AdminCtrl', ($scope, $http, User) -> $http.get '/api/users' .success (users) -> @@ -11,3 +11,6 @@ angular.module '<%= scriptAppName %>' User.remove id: user._id angular.forEach $scope.users, (u, i) -> $scope.users.splice i, 1 if u is user + + $scope.confirm = (user) -> + User.confirm id: user._id, null diff --git a/app/templates/client/app/admin(auth)/admin.controller(js).js b/app/templates/client/app/admin(auth)/admin.controller(js).js index 1c3e56167..a44512167 100644 --- a/app/templates/client/app/admin(auth)/admin.controller(js).js +++ b/app/templates/client/app/admin(auth)/admin.controller(js).js @@ -1,7 +1,7 @@ 'use strict'; angular.module('<%= scriptAppName %>') - .controller('AdminCtrl', function ($scope, $http, Auth, User) { + .controller('AdminCtrl', function ($scope, $http, User) { $http.get('/api/users').success(function(users) { $scope.users = users; @@ -15,4 +15,8 @@ angular.module('<%= scriptAppName %>') } }); }; + + $scope.confirm = function(user) { + User.confirm({ id:user._id }, null); + }; }); diff --git a/app/templates/client/components/auth(auth)/auth.service(coffee).coffee b/app/templates/client/components/auth(auth)/auth.service(coffee).coffee index ac503ed0b..129d1a325 100644 --- a/app/templates/client/components/auth(auth)/auth.service(coffee).coffee +++ b/app/templates/client/components/auth(auth)/auth.service(coffee).coffee @@ -86,6 +86,50 @@ angular.module '<%= scriptAppName %>' .$promise +<% if (filters.oauth) { %> + ### + Set password (vel create LocalStrategy) + + @param {String} newPassword + @param {Function} callback - optional + @return {Promise} + ### + setPassword: (newPassword, callback) -> + User.setPassword + id: currentUser._id + , + newPassword: newPassword + + , (user) -> + callback? user + + , (err) -> + callback? err + + .$promise +<% } %> + ### + Change email + + @param {String} email + @param {Function} callback - optional + @return {Promise} + ### + changeEmail: (oldEmail, newEmail, callback) -> + User.changeEmail + id: currentUser._id + , + oldEmail: oldEmail + newEmail: newEmail + + , (user) -> + callback? user + + , (err) -> + callback? err + + .$promise + ### Gets all available info on authenticated user diff --git a/app/templates/client/components/auth(auth)/auth.service(js).js b/app/templates/client/components/auth(auth)/auth.service(js).js index 9afb12da9..da2b00c1f 100644 --- a/app/templates/client/components/auth(auth)/auth.service(js).js +++ b/app/templates/client/components/auth(auth)/auth.service(js).js @@ -91,6 +91,45 @@ angular.module('<%= scriptAppName %>') return cb(err); }).$promise; }, +<% if (filters.oauth) { %> + /** + * Set password (vel create LocalStrategy) + * + * @param {String} newPassword + * @param {Function} callback - optional + * @return {Promise} + */ + setPassword: function(newPassword, callback) { + var cb = callback || angular.noop; + + return User.setPassword({ id: currentUser._id }, { + newPassword: newPassword + }, function(user) { + return cb(user); + }, function(err) { + return cb(err); + }).$promise; + }, +<% } %> + /** + * Change email + * + * @param {String} email + * @param {Function} callback - optional + * @return {Promise} + */ + changeEmail: function(oldEmail, newEmail, callback) { + var cb = callback || angular.noop; + + return User.changeEmail({ id: currentUser._id }, { + oldEmail: oldEmail, + newEmail: newEmail + }, function(user) { + return cb(user); + }, function(err) { + return cb(err); + }).$promise; + }, /** * Gets all available info on authenticated user diff --git a/app/templates/client/components/auth(auth)/user.service(coffee).coffee b/app/templates/client/components/auth(auth)/user.service(coffee).coffee index e0dc2e839..bdd7e5bf5 100644 --- a/app/templates/client/components/auth(auth)/user.service(coffee).coffee +++ b/app/templates/client/components/auth(auth)/user.service(coffee).coffee @@ -9,9 +9,23 @@ angular.module '<%= scriptAppName %>' method: 'PUT' params: controller: 'password' +<% if (filters.oauth) { %> + setPassword: + method: 'POST' + params: + controller: 'password' +<% } %> + changeEmail: + method: 'PUT' + params: + controller: 'email' get: method: 'GET' params: id: 'me' + confirm: + method: 'POST' + params: + controller: 'confirm' \ No newline at end of file diff --git a/app/templates/client/components/auth(auth)/user.service(js).js b/app/templates/client/components/auth(auth)/user.service(js).js index c41fe8312..9cb49ded6 100644 --- a/app/templates/client/components/auth(auth)/user.service(js).js +++ b/app/templates/client/components/auth(auth)/user.service(js).js @@ -11,12 +11,30 @@ angular.module('<%= scriptAppName %>') params: { controller:'password' } + }<% if (filters.oauth) { %>, + setPassword: { + method: 'POST', + params: { + controller: 'password' + } + }<% } %>, + changeEmail: { + method: 'PUT', + params: { + controller: 'email' + } }, get: { method: 'GET', params: { id:'me' } + }, + confirm: { + method: 'POST', + params: { + controller: 'confirm' + } } }); }); diff --git a/app/templates/server/api/user(auth)/index.js b/app/templates/server/api/user(auth)/index.js index 48567e485..006b3d52c 100644 --- a/app/templates/server/api/user(auth)/index.js +++ b/app/templates/server/api/user(auth)/index.js @@ -7,11 +7,16 @@ var auth = require('../../auth/auth.service'); var router = express.Router(); -router.get('/', auth.hasRole('admin'), controller.index); -router.delete('/:id', auth.hasRole('admin'), controller.destroy); router.get('/me', auth.isAuthenticated(), controller.me); -router.put('/:id/password', auth.isAuthenticated(), controller.changePassword); +router.put('/:id/password', auth.isAuthenticated(), controller.changePassword);<% if (filters.oauth) { %> +router.post('/:id/password', auth.isAuthenticated(), controller.setPassword);<% } %> +router.put('/:id/email', auth.isAuthenticated(), controller.changeEmail); router.get('/:id', auth.isAuthenticated(), controller.show); router.post('/', controller.create); +// admin roles +router.get('/', auth.hasRole('admin'), controller.index); +router.delete('/:id', auth.hasRole('admin'), controller.destroy); +router.post('/:id/confirm', auth.hasRole('admin'), controller.confirm); + module.exports = router; diff --git a/app/templates/server/api/user(auth)/user.controller.js b/app/templates/server/api/user(auth)/user.controller.js index f4cd10c29..dae0c1554 100644 --- a/app/templates/server/api/user(auth)/user.controller.js +++ b/app/templates/server/api/user(auth)/user.controller.js @@ -1,101 +1,133 @@ 'use strict'; -var User = require('./user.model'); -var passport = require('passport'); -var config = require('../../config/environment'); -var jwt = require('jsonwebtoken'); +var passport = require('passport'); +var jwt = require('jsonwebtoken'); + +var User = require('./user.model'); +var config = require('../../config/environment'); + var validationError = function(res, err) { return res.json(422, err); }; -/** - * Get list of users - * restriction: 'admin' - */ -exports.index = function(req, res) { - User.find({}, '-salt -hashedPassword', function (err, users) { - if(err) return res.send(500, err); - res.json(200, users); - }); -}; +var excludedFields = '-salt -hashedPassword'; -/** - * Creates a new user - */ -exports.create = function (req, res, next) { - var newUser = new User(req.body); - newUser.provider = 'local'; - newUser.role = 'user'; - newUser.save(function(err, user) { +// Create new LocalStrategy user +exports.create = function(req, res) { + User.create(req.body, function(err, user) { if (err) return validationError(res, err); - var token = jwt.sign({_id: user._id }, config.secrets.session, { expiresInMinutes: 60*5 }); + + var token = jwt.sign({ _id: user._id }, config.secrets.session, { expiresInMinutes: 30 * 24 * 60 }); res.json({ token: token }); }); }; -/** - * Get a single user - */ -exports.show = function (req, res, next) { - var userId = req.params.id; - - User.findById(userId, function (err, user) { +// Get a single user +exports.show = function(req, res, next) { + User.findById(req.params.id, function(err, user) { if (err) return next(err); if (!user) return res.send(401); - res.json(user.profile); - }); -}; -/** - * Deletes a user - * restriction: 'admin' - */ -exports.destroy = function(req, res) { - User.findByIdAndRemove(req.params.id, function(err, user) { - if(err) return res.send(500, err); - return res.send(204); + return res.json(user.profile); }); }; -/** - * Change a users password - */ -exports.changePassword = function(req, res, next) { - var userId = req.user._id; +// Change user's password +exports.changePassword = function(req, res) { var oldPass = String(req.body.oldPassword); var newPass = String(req.body.newPassword); - User.findById(userId, function (err, user) { - if(user.authenticate(oldPass)) { + User.findById(req.user._id, function(err, user) { + if (user.authenticate(oldPass)) { user.password = newPass; user.save(function(err) { if (err) return validationError(res, err); + res.send(200); }); - } else { - res.send(403); - } + + } else res.send(403); }); }; +<% if (filters.oauth) { %> +// Set password (vel. enable LocalStrategy) +exports.setPassword = function(req, res) { + User.findById(req.user._id, function(err, user) { + // TODO: should anything else except jwt be checked here? + if (true) { + user.password = String(req.body.newPassword); + user.save(function(err) { + if (err) return validationError(res, err); -/** - * Get my info - */ + res.send(200); + }); + + } else res.send(403); + + }); +}; +<% } %> +// Change email +exports.changeEmail = function(req, res) { + User.findById(req.user._id, function(err, user) { + if (err) return res.send(500, err); + if (!user) return res.json(401); + + user.changeEmail(req.body.oldEmail, req.body.newEmail, function(err) { + if (err) return res.send(500, err); + + res.send(200, user); + }); + }); +}; + +// Get currently logged in user info exports.me = function(req, res, next) { - var userId = req.user._id; - User.findOne({ - _id: userId - }, '-salt -hashedPassword', function(err, user) { // don't ever give out the password or salt + User.findById(req.user._id, excludedFields, function(err, user) { if (err) return next(err); if (!user) return res.json(401); + res.json(user); }); }; +// Authentication callback +exports.authCallback = function(req, res) { + return res.redirect('/'); +}; + + /** - * Authentication callback - */ -exports.authCallback = function(req, res, next) { - res.redirect('/'); + * Admin methods + **/ + +// Get list of users +exports.index = function(req, res) { + User.find({}, excludedFields, function(err, users) { + if (err) return res.send(500, err); + + res.json(200, users); + }); }; + +exports.confirm = function(req, res) { + User.findById(req.params.id, function(err, user) { + if (err) return res.send(500, err); + + user.confirm(user.email, function(err) { + if (err) return res.send(500, err); + + res.send(200, user); + }); + }); +}; + +// Delete a user +exports.destroy = function(req, res) { + User.findByIdAndRemove(req.params.id, function(err, user) { + if (err) return res.send(500, err); + + res.send(204); + }); +}; \ No newline at end of file diff --git a/app/templates/server/api/user(auth)/user.model.js b/app/templates/server/api/user(auth)/user.model.js index cc8d59263..d363b1ed3 100644 --- a/app/templates/server/api/user(auth)/user.model.js +++ b/app/templates/server/api/user(auth)/user.model.js @@ -1,149 +1,205 @@ 'use strict'; -var mongoose = require('mongoose'); -var Schema = mongoose.Schema; -var crypto = require('crypto');<% if(filters.oauth) { %> -var authTypes = ['github', 'twitter', 'facebook', 'google'];<% } %> +var mongoose = require('mongoose'); +var Schema = mongoose.Schema; +var crypto = require('crypto'); + + +var CredentialSchema = new Schema({ + type: { + type: String, + "default": 'email', + "enum": ['email', 'phone'] + }, + value: { + type: String, + required: true, + lowercase: true, + trim: true + }, + confirmed: { + type: Boolean, + "default": false + } +}, { _id:false }); + + var UserSchema = new Schema({ name: String, - email: { type: String, lowercase: true }, role: { type: String, - default: 'user' + "default": 'user' }, + username: String, + salt: String, hashedPassword: String, - provider: String, - salt: String<% if (filters.oauth) { %>,<% if (filters.facebookAuth) { %> - facebook: {},<% } %><% if (filters.twitterAuth) { %> - twitter: {},<% } %><% if (filters.googleAuth) { %> - google: {},<% } %> - github: {}<% } %> + + credentials: [ CredentialSchema ]<% if (filters.oauth) { %>, + + // NOTE: using `Mixed` is tricky. Should be changed to sth else. + strategies: { + type: Schema.Types.Mixed, + "default": {} + }, + localEnabled: { + type: Boolean, + "default": false + }<% } %> }); -/** - * Virtuals - */ UserSchema - .virtual('password') - .set(function(password) { - this._password = password; - this.salt = this.makeSalt(); - this.hashedPassword = this.encryptPassword(password); - }) - .get(function() { - return this._password; - }); +.virtual('password') +.set(function(pwd) { + this.salt = this.makeSalt(); + this.hashedPassword = this.encryptPassword(pwd); + + <% if (filters.oauth) { %>// Setting password implies enabling LocalStrategy + this.localEnabled = true;<% } %> +}); -// Public profile information UserSchema - .virtual('profile') - .get(function() { - return { - 'name': this.name, - 'role': this.role - }; - }); +.path('hashedPassword')<% if (filters.oauth) { %> +.validate(function(hashedPwd) { + return !!this.emails.length; + +}, 'Cannot set password with empty email')<% } %> +.validate(function(hashedPwd) {<% if (filters.oauth) { %> + if (!this.localEnabled) return true;<% } %> + + return !!hashedPwd.length; +}, 'Password cannot be blank'); -// Non-sensitive info we'll be putting in the token UserSchema - .virtual('token') - .get(function() { - return { - '_id': this._id, - 'role': this.role - }; +.virtual('email') +.set(function(email) { + this.credentials.push({ + value: email }); -/** - * Validations - */ +}).get(function() { + // returns only first found email + // TODO: in case of multiple emails, should prioritize confirmed ones + return this.credentials.filter(function(c) { + return c.type==='email'; -// Validate empty email -UserSchema - .path('email') - .validate(function(email) {<% if (filters.oauth) { %> - if (authTypes.indexOf(this.provider) !== -1) return true;<% } %> - return email.length; - }, 'Email cannot be blank'); + })[0].value; +}); -// Validate empty password UserSchema - .path('hashedPassword') - .validate(function(hashedPassword) {<% if (filters.oauth) { %> - if (authTypes.indexOf(this.provider) !== -1) return true;<% } %> - return hashedPassword.length; - }, 'Password cannot be blank'); +.virtual('emails') +.get(function() { + return this.credentials + .filter(function(c) { return c.type==='email'; }) + .map(function(c) { return c.value; }); + +}); -// Validate email is not taken UserSchema - .path('email') - .validate(function(value, respond) { - var self = this; - this.constructor.findOne({email: value}, function(err, user) { - if(err) throw err; - if(user) { - if(self.id === user.id) return respond(true); - return respond(false); +.pre('save', function(next) {<% if (filters.oauth) { %> + if(!this.localEnabled) { + if (Object.keys(this.strategies).length===0) { + return next(new Error('No connected accounts')); + } + return next(); + }<% } %> + + mongoose.models['User']<% if (filters.oauth) { %> + .find({ localEnabled:true })<% } %> + .where('credentials.type').equals('email') + .where('credentials.value').equals(this.email) + .where('_id').ne(String(this._id)) + .exec(function(err, users) { + if (users.length) { + return next(new Error('Account with this email address already exists')); } - respond(true); + next(); }); -}, 'The specified email address is already in use.'); - -var validatePresenceOf = function(value) { - return value && value.length; -}; -/** - * Pre-save hook - */ -UserSchema - .pre('save', function(next) { - if (!this.isNew) return next(); - - if (!validatePresenceOf(this.hashedPassword)<% if (filters.oauth) { %> && authTypes.indexOf(this.provider) === -1<% } %>) - next(new Error('Invalid password')); - else - next(); - }); +}); -/** - * Methods - */ UserSchema.methods = { - /** - * Authenticate - check if the passwords are the same - * - * @param {String} plainText - * @return {Boolean} - * @api public - */ - authenticate: function(plainText) { - return this.encryptPassword(plainText) === this.hashedPassword; + authenticate: function(pwd) { + return this.hashedPassword === this.encryptPassword(pwd); + }, + encryptPassword: function(pwd) { + var salt; + if (!pwd || !this.salt) { + return null; + } + salt = new Buffer(this.salt, 'base64'); + return crypto.pbkdf2Sync(pwd, salt, 10000, 64).toString('base64'); + }, + confirm: function(emailOrPhone, cb) { + this.credentials.forEach(function(c) { + if (c.value===emailOrPhone) { + c.confirmed = true; + } + }); + this.save(cb); + }, + changeEmail: function(oldEmail, newEmail, cb) { + this.credentials.forEach(function(c) { + if (c.value===oldEmail) { + c.value = newEmail; + c.confirmed = false; + } + }); + this.save(cb); }, - - /** - * Make salt - * - * @return {String} - * @api public - */ makeSalt: function() { return crypto.randomBytes(16).toString('base64'); - }, + }<% if (filters.oauth) { %>, + absorb: function(name, profile) { + if (!this.strategies[name]) { + this.strategies[name] = profile; + this.markModified('strategies'); + this.save(); + } else { + // TODO: move current to archive, and save current as current + console.log("update profile"); + } + }<% } %> +}; - /** - * Encrypt password - * - * @param {String} password - * @return {String} - * @api public - */ - encryptPassword: function(password) { - if (!password || !this.salt) return ''; - var salt = new Buffer(this.salt, 'base64'); - return crypto.pbkdf2Sync(password, salt, 10000, 64).toString('base64'); - } +UserSchema.statics = { + findOneByEmail: function(email, cb) { + this.find({ 'credentials.value': email.toLowerCase() }) + .where('credentials.type').equals('email') + .exec(function(err, user) { + if (err) return cb(err); + if (user.length === 0) return cb(null, null); + + cb(null, user[0]); + }); + }<% if (filters.oauth) { %>, + findDuplicates: function(data, cb) { + var dataFormatted; + dataFormatted = []; + + if (data.email != null) { + dataFormatted.push({ + 'credentials.type': 'email', + 'credentials.value': data.email + }); + } + + if (data.phone != null) { + dataFormatted.push({ + 'credentials.type': 'phone', + 'credentials.value': data.phone + }); + } + + this.find({ 'credentials.confirmed':true }) + .or(dataFormatted) + .exec(function(err, users) { + if (err) return cb(err); + if (users.length===0) return cb(null, null); + + cb(null, users); + }); + }<% } %> }; -module.exports = mongoose.model('User', UserSchema); +module.exports = mongoose.model('User', UserSchema); \ No newline at end of file diff --git a/app/templates/server/api/user(auth)/user.model.spec.js b/app/templates/server/api/user(auth)/user.model.spec.js index 2c1b7dfbe..1781521dc 100644 --- a/app/templates/server/api/user(auth)/user.model.spec.js +++ b/app/templates/server/api/user(auth)/user.model.spec.js @@ -5,22 +5,22 @@ var app = require('../../app'); var User = require('./user.model'); var user = new User({ - provider: 'local', name: 'Fake User', email: 'test@test.com', password: 'password' }); describe('User Model', function() { + + // Clear users before testing before(function(done) { - // Clear users before testing - User.remove().exec().then(function() { + User.remove().exec().then(function(){ done(); }); }); afterEach(function(done) { - User.remove().exec().then(function() { + User.remove().exec().then(function(){ done(); }); }); diff --git a/app/templates/server/auth(auth)/auth.service.js b/app/templates/server/auth(auth)/auth.service.js index 38ec34302..56a5233c8 100644 --- a/app/templates/server/auth(auth)/auth.service.js +++ b/app/templates/server/auth(auth)/auth.service.js @@ -1,31 +1,32 @@ 'use strict'; -var mongoose = require('mongoose'); -var passport = require('passport'); -var config = require('../config/environment'); -var jwt = require('jsonwebtoken'); -var expressJwt = require('express-jwt'); -var compose = require('composable-middleware'); -var User = require('../api/user/user.model'); +var mongoose = require('mongoose'); +var passport = require('passport'); +var jwt = require('jsonwebtoken'); +var expressJwt = require('express-jwt'); +var compose = require('composable-middleware'); + +var config = require('../config/environment'); +var User = require('../api/user/user.model'); + var validateJwt = expressJwt({ secret: config.secrets.session }); -/** - * Attaches the user object to the request if authenticated - * Otherwise returns 403 - */ -function isAuthenticated() { + +// Attaches the user object to the request if authenticated +// Otherwise returns 403 +var isAuthenticated = function() { return compose() // Validate jwt .use(function(req, res, next) { // allow access_token to be passed through query parameter as well - if(req.query && req.query.hasOwnProperty('access_token')) { + if (req.query && req.query.hasOwnProperty('access_token')) { req.headers.authorization = 'Bearer ' + req.query.access_token; } validateJwt(req, res, next); }) // Attach user to request .use(function(req, res, next) { - User.findById(req.user._id, function (err, user) { + User.findById(req.user._id, function(err, user) { if (err) return next(err); if (!user) return res.send(401); @@ -33,44 +34,42 @@ function isAuthenticated() { next(); }); }); -} +}; -/** - * Checks if the user role meets the minimum requirements of the route - */ -function hasRole(roleRequired) { +// Checks if the user role meets the minimum requirements of the route +var hasRole = function(roleRequired) { if (!roleRequired) throw new Error('Required role needs to be set'); return compose() .use(isAuthenticated()) - .use(function meetsRequirements(req, res, next) { + .use(function(req, res, next) { if (config.userRoles.indexOf(req.user.role) >= config.userRoles.indexOf(roleRequired)) { next(); - } - else { - res.send(403); - } + + } else res.send(403); }); -} +}; + +// Returns a jwt token signed by the app secret +var signToken = function(id, role) { + var payload = { _id: id }; + if (role !== null) payload.role = role; + + return jwt.sign(payload, config.secrets.session, { expiresInMinutes: 30 * 24 * 60 }); // 30 days +}; -/** - * Returns a jwt token signed by the app secret - */ -function signToken(id) { - return jwt.sign({ _id: id }, config.secrets.session, { expiresInMinutes: 60*5 }); -} +// Set token cookie directly for oAuth strategies +var setTokenCookie = function(req, res) { + if (!req.user) return res.json(404, { message: 'Something went wrong, please try again' }); -/** - * Set token cookie directly for oAuth strategies - */ -function setTokenCookie(req, res) { - if (!req.user) return res.json(404, { message: 'Something went wrong, please try again.'}); var token = signToken(req.user._id, req.user.role); res.cookie('token', JSON.stringify(token)); res.redirect('/'); -} +}; -exports.isAuthenticated = isAuthenticated; -exports.hasRole = hasRole; -exports.signToken = signToken; -exports.setTokenCookie = setTokenCookie; \ No newline at end of file +module.exports = { + isAuthenticated: isAuthenticated, + hasRole: hasRole, + signToken: signToken, + setTokenCookie: setTokenCookie +}; \ No newline at end of file diff --git a/app/templates/server/auth(auth)/facebook(facebookAuth)/index.js b/app/templates/server/auth(auth)/facebook(facebookAuth)/index.js index 4a6f87886..d94841e62 100644 --- a/app/templates/server/auth(auth)/facebook(facebookAuth)/index.js +++ b/app/templates/server/auth(auth)/facebook(facebookAuth)/index.js @@ -6,9 +6,15 @@ var auth = require('../auth.service'); var router = express.Router(); + +// available scopes: +// https://developers.facebook.com/docs/facebook-login/permissions/v2.0#reference +var SCOPE = ['email', 'user_about_me']; + + router .get('/', passport.authenticate('facebook', { - scope: ['email', 'user_about_me'], + scope: SCOPE, failureRedirect: '/signup', session: false })) diff --git a/app/templates/server/auth(auth)/facebook(facebookAuth)/passport.js b/app/templates/server/auth(auth)/facebook(facebookAuth)/passport.js index 90ae48939..71a8480b2 100644 --- a/app/templates/server/auth(auth)/facebook(facebookAuth)/passport.js +++ b/app/templates/server/auth(auth)/facebook(facebookAuth)/passport.js @@ -1,37 +1,49 @@ -var passport = require('passport'); -var FacebookStrategy = require('passport-facebook').Strategy; +'use strict'; + +var passport = require('passport'); +var FacebookStrategy= require('passport-facebook').Strategy; exports.setup = function (User, config) { - passport.use(new FacebookStrategy({ - clientID: config.facebook.clientID, - clientSecret: config.facebook.clientSecret, - callbackURL: config.facebook.callbackURL - }, + passport.use(new FacebookStrategy(config.facebook, function(accessToken, refreshToken, profile, done) { - User.findOne({ - 'facebook.id': profile.id - }, - function(err, user) { - if (err) { - return done(err); - } - if (!user) { - user = new User({ + + User.findOne({ 'strategies.facebook.id': profile.id }, function(err, user) { + if (err) return done(err); + if (user) return done(null, user); + + User.findDuplicates({ + + // We can treat this email as confirmed because: + // Without confirming user's are not able to "log in to apps" + // more: http://goo.gl/OKcd6H + email: profile.emails[0].value + + }, function (err, users) { + if (err) return done(err); + if (users) { + var user = users[0]; + user.absorb(profile.provider, profile); + + // we can do that because we have it handled by Facebook + user.confirm(profile.emails[0].value); + return done(null, user); + } + + var user = new User({ name: profile.displayName, email: profile.emails[0].value, - role: 'user', username: profile.username, - provider: 'facebook', - facebook: profile._json + strategies: { facebook: profile._json } }); + user.confirm(profile.emails[0].value); + user.save(function(err) { - if (err) done(err); - return done(err, user); + if(err) return done(err); + return done(null, user); }); - } else { - return done(err, user); - } - }) + + }); + }); } )); }; \ No newline at end of file diff --git a/app/templates/server/auth(auth)/google(googleAuth)/index.js b/app/templates/server/auth(auth)/google(googleAuth)/index.js index 9b1ce39fe..220def868 100644 --- a/app/templates/server/auth(auth)/google(googleAuth)/index.js +++ b/app/templates/server/auth(auth)/google(googleAuth)/index.js @@ -6,13 +6,16 @@ var auth = require('../auth.service'); var router = express.Router(); + +// available scopes: +// https://developers.google.com/+/api/oauth +var SCOPE = [ 'profile', 'email' ]; + + router .get('/', passport.authenticate('google', { + scope: SCOPE, failureRedirect: '/signup', - scope: [ - 'https://www.googleapis.com/auth/userinfo.profile', - 'https://www.googleapis.com/auth/userinfo.email' - ], session: false })) diff --git a/app/templates/server/auth(auth)/google(googleAuth)/passport.js b/app/templates/server/auth(auth)/google(googleAuth)/passport.js index 211fe78d7..953f2b9eb 100644 --- a/app/templates/server/auth(auth)/google(googleAuth)/passport.js +++ b/app/templates/server/auth(auth)/google(googleAuth)/passport.js @@ -1,32 +1,48 @@ -var passport = require('passport'); -var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy; +'use strict'; + +var passport = require('passport'); +var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy; exports.setup = function (User, config) { - passport.use(new GoogleStrategy({ - clientID: config.google.clientID, - clientSecret: config.google.clientSecret, - callbackURL: config.google.callbackURL - }, + passport.use(new GoogleStrategy(config.google, function(accessToken, refreshToken, profile, done) { - User.findOne({ - 'google.id': profile.id - }, function(err, user) { - if (!user) { - user = new User({ + + User.findOne({ 'strategies.google.id': profile.id }, function(err, user) { + if (err) return done(err); + if (user) return done(null, user); + + User.findDuplicates({ + + // We can treat this email as confirmed because: + // It's immpossible to create Google account without Gmail address, + // and that one is returned by Google API after user signed in. + email: profile.emails[0].value + + }, function (err, users) { + if (err) return done(err); + if (users) { + var user = users[0]; + user.absorb(profile.provider, profile); + + // we can do that because we have it handled by Google + user.confirm(profile.emails[0].value); + return done(null, user); + } + + var user = new User({ name: profile.displayName, email: profile.emails[0].value, - role: 'user', username: profile.username, - provider: 'google', - google: profile._json + strategies: { google: profile._json } }); + user.confirm(profile.emails[0].value); + user.save(function(err) { - if (err) done(err); - return done(err, user); + if(err) return done(err); + return done(null, user); }); - } else { - return done(err, user); - } + + }); }); } )); diff --git a/app/templates/server/auth(auth)/index.js b/app/templates/server/auth(auth)/index.js index e3e6c87ad..8c7449242 100644 --- a/app/templates/server/auth(auth)/index.js +++ b/app/templates/server/auth(auth)/index.js @@ -7,15 +7,15 @@ var User = require('../api/user/user.model'); // Passport Configuration require('./local/passport').setup(User, config);<% if (filters.facebookAuth) { %> -require('./facebook/passport').setup(User, config);<% } %><% if (filters.googleAuth) { %> -require('./google/passport').setup(User, config);<% } %><% if (filters.twitterAuth) { %> +require('./facebook/passport').setup(User, config);<% } if (filters.googleAuth) { %> +require('./google/passport').setup(User, config);<% } if (filters.twitterAuth) { %> require('./twitter/passport').setup(User, config);<% } %> var router = express.Router(); router.use('/local', require('./local'));<% if (filters.facebookAuth) { %> -router.use('/facebook', require('./facebook'));<% } %><% if (filters.twitterAuth) { %> -router.use('/twitter', require('./twitter'));<% } %><% if (filters.googleAuth) { %> +router.use('/facebook', require('./facebook'));<% } if (filters.twitterAuth) { %> +router.use('/twitter', require('./twitter'));<% } if (filters.googleAuth) { %> router.use('/google', require('./google'));<% } %> module.exports = router; \ No newline at end of file diff --git a/app/templates/server/auth(auth)/local/index.js b/app/templates/server/auth(auth)/local/index.js index 8bf88a046..0e2e5c137 100644 --- a/app/templates/server/auth(auth)/local/index.js +++ b/app/templates/server/auth(auth)/local/index.js @@ -14,6 +14,7 @@ router.post('/', function(req, res, next) { var token = auth.signToken(user._id, user.role); res.json({token: token}); + })(req, res, next) }); diff --git a/app/templates/server/auth(auth)/local/passport.js b/app/templates/server/auth(auth)/local/passport.js index ac82b42a2..444847751 100644 --- a/app/templates/server/auth(auth)/local/passport.js +++ b/app/templates/server/auth(auth)/local/passport.js @@ -1,25 +1,24 @@ +'use strict'; + var passport = require('passport'); var LocalStrategy = require('passport-local').Strategy; exports.setup = function (User, config) { passport.use(new LocalStrategy({ - usernameField: 'email', - passwordField: 'password' // this is the virtual field on the model - }, - function(email, password, done) { - User.findOne({ - email: email.toLowerCase() - }, function(err, user) { - if (err) return done(err); + usernameField: 'email', + passwordField: 'password' // this is the virtual field on the model + }, + function(email, password, done) { + User.findOneByEmail(email, function(err, user) { + if (err) return done(err); - if (!user) { - return done(null, false, { message: 'This email is not registered.' }); - } - if (!user.authenticate(password)) { - return done(null, false, { message: 'This password is not correct.' }); - } - return done(null, user); - }); - } - )); + if (!user) { + return done(null, false, { message: 'This email is not registered.' }); + } + if (!user.authenticate(password)) { + return done(null, false, { message: 'This password is not correct.' }); + } + return done(null, user); + }); + })); }; \ No newline at end of file diff --git a/app/templates/server/auth(auth)/twitter(twitterAuth)/passport.js b/app/templates/server/auth(auth)/twitter(twitterAuth)/passport.js index a2eb4a537..c283b6e90 100644 --- a/app/templates/server/auth(auth)/twitter(twitterAuth)/passport.js +++ b/app/templates/server/auth(auth)/twitter(twitterAuth)/passport.js @@ -1,35 +1,27 @@ +'use strict'; + +var passport = require('passport'); +var TwitterStrategy = require('passport-twitter').Strategy; + exports.setup = function (User, config) { - var passport = require('passport'); - var TwitterStrategy = require('passport-twitter').Strategy; + passport.use(new TwitterStrategy(config.twitter, + function(token, tokenSecret, profile, done) { + + User.findOne({ 'strategies.twitter.id_str': profile.id }, function(err, user) { + if (err) return done(err); + if (user) return done(null, user); - passport.use(new TwitterStrategy({ - consumerKey: config.twitter.clientID, - consumerSecret: config.twitter.clientSecret, - callbackURL: config.twitter.callbackURL - }, - function(token, tokenSecret, profile, done) { - User.findOne({ - 'twitter.id_str': profile.id - }, function(err, user) { - if (err) { - return done(err); - } - if (!user) { - user = new User({ + User.create({ name: profile.displayName, username: profile.username, - role: 'user', - provider: 'twitter', - twitter: profile._json - }); - user.save(function(err) { + strategies: { twitter: profile._json } + + }, function(err, user) { if (err) return done(err); - return done(err, user); + return done(null, user); + }); - } else { - return done(err, user); - } - }); + }); } )); }; \ No newline at end of file diff --git a/app/templates/server/config/_local.env.js b/app/templates/server/config/_local.env.js index e95b1c6ad..2576351d3 100644 --- a/app/templates/server/config/_local.env.js +++ b/app/templates/server/config/_local.env.js @@ -6,18 +6,18 @@ // This file should not be tracked by git. module.exports = { - DOMAIN: 'http://localhost:9000', - SESSION_SECRET: "<%= _.slugify(appname) + '-secret' %>",<% if (filters.facebookAuth) { %> + DOMAIN: 'http://localhost:9000', + SESSION_SECRET: "<%= _.slugify(appname) + '-secret' %>",<% if (filters.facebookAuth) { %> - FACEBOOK_ID: 'app-id', - FACEBOOK_SECRET: 'secret',<% } if (filters.twitterAuth) { %> + FACEBOOK_ID: 'app-id', + FACEBOOK_SECRET: 'secret',<% } if (filters.twitterAuth) { %> - TWITTER_ID: 'app-id', - TWITTER_SECRET: 'secret',<% } if (filters.googleAuth) { %> + TWITTER_ID: 'app-id', + TWITTER_SECRET: 'secret',<% } if (filters.googleAuth) { %> + + GOOGLE_ID: 'app-id', + GOOGLE_SECRET: 'secret',<% } %> - GOOGLE_ID: 'app-id', - GOOGLE_SECRET: 'secret', -<% } %> // Control debug level for modules using visionmedia/debug DEBUG: '' }; diff --git a/app/templates/server/config/environment/index.js b/app/templates/server/config/environment/index.js index b6c5ed60a..2f50a92bb 100644 --- a/app/templates/server/config/environment/index.js +++ b/app/templates/server/config/environment/index.js @@ -42,21 +42,21 @@ var all = { }, <% if(filters.facebookAuth) { %> facebook: { - clientID: process.env.FACEBOOK_ID || 'id', - clientSecret: process.env.FACEBOOK_SECRET || 'secret', - callbackURL: process.env.DOMAIN + '/auth/facebook/callback' + clientID: process.env.FACEBOOK_ID || 'id', + clientSecret: process.env.FACEBOOK_SECRET || 'secret', + callbackURL: process.env.DOMAIN + '/auth/facebook/callback' }, -<% } %><% if(filters.twitterAuth) { %> +<% } if(filters.twitterAuth) { %> twitter: { - clientID: process.env.TWITTER_ID || 'id', - clientSecret: process.env.TWITTER_SECRET || 'secret', - callbackURL: process.env.DOMAIN + '/auth/twitter/callback' + consumerKey: process.env.TWITTER_ID || 'id', + consumerSecret: process.env.TWITTER_SECRET || 'secret', + callbackURL: process.env.DOMAIN + '/auth/twitter/callback' }, -<% } %><% if(filters.googleAuth) { %> +<% } if(filters.googleAuth) { %> google: { - clientID: process.env.GOOGLE_ID || 'id', - clientSecret: process.env.GOOGLE_SECRET || 'secret', - callbackURL: process.env.DOMAIN + '/auth/google/callback' + clientID: process.env.GOOGLE_ID || 'id', + clientSecret: process.env.GOOGLE_SECRET || 'secret', + callbackURL: process.env.DOMAIN + '/auth/google/callback' }<% } %> }; diff --git a/app/templates/server/config/seed(mongoose).js b/app/templates/server/config/seed(mongoose).js index 27ab19417..8eaef4ede 100644 --- a/app/templates/server/config/seed(mongoose).js +++ b/app/templates/server/config/seed(mongoose).js @@ -27,23 +27,24 @@ Thing.find({}).remove(function() { },{ name : 'Deployment Ready', info : 'Easily deploy your app to Heroku or Openshift with the heroku and openshift subgenerators' + }, function() { + console.log('finished populating things'); }); });<% if (filters.auth) { %> User.find({}).remove(function() { User.create({ - provider: 'local', name: 'Test User', email: 'test@test.com', password: 'test' + }, { - provider: 'local', role: 'admin', name: 'Admin', email: 'admin@admin.com', password: 'admin' + }, function() { - console.log('finished populating users'); - } - ); + console.log('finished populating users'); + }); });<% } %> \ No newline at end of file