@@ -0,0 +1,60 @@
"use strict";

var passport = require('passport');
var User = require('user/module');
var Auth = require('user/helper');
var Controllers = {};

/**
* Login
*/
Controllers.login = function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) {
return res.status(404).json({
message: err.message
});
}

var token = Auth.signToken(user);

res.status(200).json({
token: token,
user: user
});
})(req, res, next);
};

/**
* Logout
*/
Controllers.logout = function(req, res, next) {

};

/**
* Signup
*/
Controllers.signup = function(req, res, next) {
var _user = new User(req.body);
_user.provider = 'local';

_user.save(function(err, user) {
if (err) return next(err);

if (!user) {
return res.status(404).json({
message: 'User does not exist.'
});
}

var token = Auth.signToken(user);

res.status(200).json({
token: token,
user: user
});
});
};

module.exports = Controllers;
@@ -1,112 +1,27 @@
'use strict';
"use strict";

var User = require('../user.module'),
passport = require('passport'),
config = require('../../config'),
jwt = require('jsonwebtoken');

var validationError = function(res, err) {
return res.status(422).json({ message: err });
};
var User = require('user/module');
var Controllers = {};

/**
* 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);
});
};
* Me
*/
Controllers.me = function(req, res, next) {
var id = req.user._id;

/**
* 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) {
if (err) return validationError(res, err);

var token = jwt.sign({_id: user._id }, config.secrets.session, { expiresInMinutes: 60*5 });

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) {
User.findOne({
_id: 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);
});
};

/**
* Change a users password
*/
exports.changePassword = function(req, res, next) {
var userId = req.user._id,
oldPass = String(req.body.oldPassword),
newPass = String(req.body.newPassword);

User.findById(userId, function (err, user) {
if (user.authenticate(oldPass)) {
user.password = newPass;

user.save(function(err) {
if (err) return validationError(res, err);

res.send(200);
if (!user) {
return res.status(404).json({
message: 'User not found.'
});
} else {
res.send(403);
}
});
};

/**
* Get my info
*/
exports.me = function(req, res, next) {
var userId = req.user._id;

User.findOne({
_id: userId
}, '-salt -hashedPassword', function(err, user) {
if (err) return next(err);

if (!user) return res.json(401);

res.json(user);
res.status(200).json(user);
});
};

/**
* Authentication callback
*/
exports.authCallback = function(req, res, next) {
res.redirect('/');
};
module.exports = Controllers;
@@ -0,0 +1,47 @@
"use strict";

var jwt = require('jsonwebtoken');
var config = require('config');
var Auth = {};

/**
* Sign Token
* @param {String} id
*/
Auth.signToken = function(user) {
return jwt.sign(user, config.secrets.session, { expiresInMinutes: 60*5 });
};

/**
* Authenticate
*/
Auth.isAuthenticated = function(req, res, next) {
var token = req.headers['authorization'];

if (token) {
jwt.verify(token.split(' ')[1], config.secrets.session, function(err, user) {
if (err) {
console.log(err);
return res.status(400).json({
message: 'Failed to authenticate token.'
});
} else {
if (!user) {
return res.send(401).json({
message: 'User not found, invalid token.'
});
}

req.user = user;

next();
}
});
} else {
return res.status(403).json({
message: 'No token provided.'
});
}
};

module.exports = Auth;

This file was deleted.

This file was deleted.

@@ -0,0 +1,134 @@
"use strict";

var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var crypto = require('crypto');

var UserSchema = new Schema({
name: {
first: String,
last: String
},
email: {
type: String,
lowercase: true
},
role: {
type: String,
default: 'user'
},
provider: String,
hashedPassword: String,
salt: String,
created: {
type: Date,
default: Date.now
},
lastModified: {
type: Date,
default: Date.now
}
});

/**
* Virtuals
*/

// Password
UserSchema.virtual('password').set(function(password) {
this._password = password;
this.salt = this.makeSalt();

this.hashedPassword = this.encryptPassword(password);
}).get(function() {
return this._password;
});

// Full Name
UserSchema.virtual('name.full').get(function() {
return this.name.first + ' ' + this.name.last;
});

/**
* Validate
*/

// Validate empty email
UserSchema.path('email').validate(function(email) {
return email.length;
}, 'Email cannot be blank');

// Validate empty password
UserSchema.path('hashedPassword').validate(function(hashedPassword) {
return hashedPassword.length;
}, 'Password cannot be blank');

// 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);
}

respond(true);
});
}, 'The specified email address is already in use.');

/**
* Pre-save hook
*/
UserSchema.pre('save', function(next) {
if (!this.isNew) return next();

if (!!this.hashedPassword) {
next(new Error('Invalid password'));
} else {
next();
}
});

/**
* Methods
*/
UserSchema.methods = {
/**
* Authenticate - check if the passwords are the same
*
* @param {String} plainText
* @return {Boolean}
*/
authenticate: function(plainText) {
return this.encryptPassword(plainText) === this.hashedPassword;
},

/**
* Make salt
*
* @return {String}
*/
makeSalt: function() {
return crypto.randomBytes(16).toString('base64');
},

/**
* Encrypt password
*
* @param {String} password
* @return {String}
*/
encryptPassword: function(password) {
if (!password || !this.salt) return '';

var salt = new Buffer(this.salt, 'base64');

return crypto.pbkdf2Sync(password, salt, 10000, 64).toString('base64');
}
};

module.exports = mongoose.model('User', UserSchema);
@@ -0,0 +1,35 @@
"use strict";

var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var User = require('user/module');

module.exports = function() {
passport.use(new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
},
function(req, email, password, done) {
User.findOne({
email: email.toLowerCase()
}, function(err, user) {
if (err) return done(err);

if (!user) {
return done({
message: 'This email is not registered.'
}, false);
}

if (!user.authenticate(password)) {
return done({
message: 'This password is not correct.'
}, false);
}

return done(null, user);
});
}
));
};
@@ -0,0 +1,12 @@
"use strict";

var express = require('express');
var controller = require('user/controllers/auth');

var router = express.Router();

router.post('/login', controller.login);
router.get('/logout', controller.logout);
router.post('/signup', controller.signup);

module.exports = router;
@@ -0,0 +1,11 @@
"use strict";

var express = require('express');
var controller = require('user/controllers/user');
var Auth = require('user/helper');

var router = express.Router();

router.get('/me', Auth.isAuthenticated, controller.me);

module.exports = router;

This file was deleted.

This file was deleted.

This file was deleted.

@@ -0,0 +1,30 @@
(function() {
'use strict';

function Run($rootScope, $location) {
/**
* Change <body> class
* it sets state name and action
*/
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
if (toState.name) {
var pieces = toState.name.split('.');

$rootScope.pageClass = pieces.join(' ');
}
});
}

angular
.module('App', [
'ngCookies',
'ngResource',
'ui.router',
'User'
])
.run([
'$rootScope', '$location',
Run
]);

})();
@@ -0,0 +1,18 @@
/**
* App Config
*/
(function() {
'use strict';

function Config($stateProvider, $urlRouterProvider, $locationProvider, $httpProvider) {
$urlRouterProvider.otherwise('/');
$locationProvider.html5Mode(true);
}

angular
.module('App')
.config([
'$stateProvider', '$urlRouterProvider', '$locationProvider', '$httpProvider',
Config
]);
})();

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

@@ -0,0 +1,33 @@
/**
* User Routes
*/
(function() {
'use strict';

function Config($httpProvider) {
$httpProvider.interceptors.push('AuthInterceptorService');
}

function Run($rootScope, $location, AuthService) {
$rootScope.$on('$stateChangeStart', function(event, next) {
if(!next.unauthenticate && !AuthService.isLoggedIn()) {
$location.path('/login');
} else if(!next.unauthenticate) {
AuthService.getCurrentUser().then(function(user) {
$rootScope.currentUser = user;
});
}
});
}

angular
.module('User')
.config([
'$httpProvider',
Config
])
.run([
'$rootScope', '$location', 'AuthService',
Run
]);
})();
@@ -0,0 +1,76 @@
/**
* User Controller
*/
(function() {
'use strict';

/**
* Login Controller
* @param {Object} AuthService
* @param {Object} $state
*/
function LoginController(AuthService, $state) {
this.user = {};
this.errors = {};

/**
* Login Action
* @param {Object} form
*/
this.submit = function login(form) {
if (form.$valid) {
AuthService.login({
email: this.user.email,
password: this.user.password
})
.then(function success() {
// $state.go('index');
}).catch(function error(response) {
this.errors = response.data;
}.bind(this));
}
}.bind(this);
}

/**
* Sign Up Controller
* @param {Object} AuthService
* @param {Object} $state
*/
function SignUpController(AuthService, $state) {
this.user = {};
this.errors = {};

/**
* Singup Action
* @param {Object} form
*/
this.submit = function singup(form) {
if (form.$valid) {
AuthService.signup({
name: this.user.name,
email: this.user.email,
password: this.user.password
})
.then(function success() {
// $state.go('index');
})
.catch(function error(response) {
this.errors = response.data;
}.bind(this));
}
}.bind(this);
}

angular
.module('User')
.controller('authController@login', [
'AuthService', '$state',
LoginController
])
.controller('authController@signup', [
'AuthService', '$state',
SignUpController
]);

})();
@@ -0,0 +1,9 @@
/**
* User Module
*/
(function() {
'use strict';

angular
.module('User', []);
})();
@@ -0,0 +1,29 @@
/**
* User Routes
*/
(function() {
'use strict';

function UserRoutes($stateProvider) {
$stateProvider
.state('login', {
url: '/login',
controller: 'authController@login',
templateUrl: '/views/user/login.html',
unauthenticate: true
})
.state('signup', {
url: '/signup',
controller: 'authController@signup',
templateUrl: '/views/user/signup.html',
unauthenticate: true
});
}

angular
.module('User')
.config([
'$stateProvider', '$httpProvider',
UserRoutes
]);
})();
@@ -0,0 +1,53 @@
/**
* Auth Interceptor Service
*/
(function() {
'use strict';

function AuthInterceptor($location, $q, $cookieStore) {
var Interceptor = {};

/**
* Request Interceptor
* @param {Object} config
* @return {Object}
*/
Interceptor.request = function request(config) {
var token = $cookieStore.get('token');

if (!token) return config;

config.headers = config.headers || {};
config.headers.Authorization = 'Bearer ' + token;

return config;
};

/**
* Response Error Interceptor
* @param {Object} response
* @return {Object}
*/
Interceptor.responseError = function responseError(response) {
if (response.status === 401) {
$location.path('/login');

// remove any stale tokens
$cookieStore.remove('token');

return $q.reject(response);
} else {
return $q.reject(response);
}
};

return Interceptor;
}

angular
.module('User')
.factory('AuthInterceptorService', [
'$location', '$q', '$cookieStore',
AuthInterceptor
]);
})();
@@ -0,0 +1,97 @@
/**
* Auth Service
*/
(function() {
'use strict';

function AuthService($http, $q, $cookieStore) {
var Auth = {};
var currentUser;

/**
* User Login
* @param {Object} data
* @return {Object}
*/
Auth.login = function login(data) {
return $http.post('/api/auth/login', data)
.then(function success(response) {
$cookieStore.put('token', response.data.token);

currentUser = response.data.user;
});
};

Auth.logout = function logout() {
// return $http.get('/api/auth/logout')
};

/**
* User Singup
* @param {Object} data
* @return {Object}
*/
Auth.signup = function signup(data) {
return $http.post('/api/auth/signup', data)
.then(function success(response) {
$cookieStore.put('token', response.data.token);

currentUser = response.data.user;
});
};

/**
* Get Current User
* @return {Object}
*/
Auth.getCurrentUser = function getCurrentUser() {
var user = $q.defer();

if (!currentUser) {
$http.get('/api/user/me')
.then(function success(response) {
currentUser = response.data;

user.resolve(response.data);
});
} else {
user.resolve(currentUser);
}

return user.promise;
};

/**
* Is Current User Loggedin
* @return {Boolean}
*/
Auth.isLoggedIn = function isLoggedIn() {
return (currentUser);
};

/**
* Is User an Admin
* @return {Boolean}
*/
Auth.isAdmin = function isAdmin() {
return currentUser === 'admin';
};

/**
* Get Token
* @return {String}
*/
Auth.getToken = function getToken() {
return $cookieStore.get('token');
};

return Auth;
}

angular
.module('User')
.factory('AuthService', [
'$http', '$q', '$cookieStore',
AuthService
]);
})();
@@ -1,2 +1,3 @@
// font path
$icon-font-path: "../fonts/bootstrap/";
$icon-font-path: "../fonts/";
$fa-font-path: "../fonts";
@@ -6,5 +6,5 @@
/**
* Load 3rd Party
*/
@import "../bower_components/bootstrap-sass/vendor/assets/stylesheets/bootstrap.scss";
@import "../bower_components/fontawesome/scss/font-awesome.scss";
@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap.scss";
@import "../../bower_components/font-awesome/scss/font-awesome.scss";
@@ -1,13 +1,14 @@
<!DOCTYPE html>
<html ng-app="App">
<head>
<title>Welcome</title>
<link rel="stylesheet" href="/app/stylesheet/style.css">
<title>Welcome to Boilerplate</title>
<link rel="stylesheet" href="/css/style.css">
<base href="/">
</head>
<body ng-class="pageClass">
{{currentUser}}
<div ui-view></div>

<script src="/app/javascript/app.min.js"></script>
<script src="/js/app.js"></script>
</body>
</html>
@@ -1,15 +1,15 @@
<div class="container">
<form class="form-signin" role="form">
<div class="container" ng-controller="authController@login as Login">
<form class="form-signin" name="login" ng-submit="Login.submit(login)">
<h2 class="form-signin-heading">Please sign in</h2>

<div class="form-group">
<label for="email-address">Email Address</label>
<input type="email" id="email-address" class="form-control" placeholder="Email address" required autofocus />
<input type="email" id="email-address" ng-model="Login.user.email" class="form-control" placeholder="Email address" required autofocus />
</div>

<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" class="form-control" placeholder="Password" required />
<input type="password" id="password" ng-model="Login.user.password" class="form-control" placeholder="Password" required />
</div>

<div class="checkbox">
@@ -1,22 +1,30 @@
<div class="container">
<form role="form" ng-submit="signup(signupForm)" name="signupForm">
<div class="container" ng-controller="authController@signup as Signup">
<form class="form-signin" name="signup" ng-submit="Signup.submit(signup)">
<h2 class="form-signin-heading">Please sign up</h2>

<div class="form-group">
<label for="full-name">Full Name</label>
<input type="text" class="form-control" id="full-name" ng-model="user.name" placeholder="Full Name" />
<label for="first-name">First Name</label>
<input type="text" class="form-control" id="first-name" ng-model="Signup.user.name.first" placeholder="First Name" />
</div>

<div class="form-group">
<label for="company">Company</label>
<input type="text" class="form-control" id="company" ng-model="user.company" placeholder="Company" />
<label for="last-name">Last Name</label>
<input type="text" class="form-control" id="last-name" ng-model="Signup.user.name.last" placeholder="Last Name" />
</div>

<div class="form-group">
<label for="email">Email Address</label>
<input type="email" class="form-control" id="email" ng-model="user.email" placeholder="Email Address" />
<input type="email" class="form-control" id="email" ng-model="Signup.user.email" placeholder="Email Address" />
</div>

<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" id="password" ng-model="user.password" placeholder="Password" />
<input type="password" class="form-control" id="password" ng-model="Signup.user.password" placeholder="Password" />
</div>

<div class="form-group">
<label for="password">Confirm Password</label>
<input type="password" class="form-control" id="password" ng-model="Signup.user.confirm_password" placeholder="Confirm Password" />
</div>
<button type="submit" class="btn btn-primary btn-lg">Create Account</button>
</form>
@@ -16,13 +16,11 @@
"tests"
],
"dependencies": {
"jquery": "~2.1.1",
"angular": "~1.2.22",
"angular-resource": "~1.2.22",
"angular-cookies": "~1.2.22",
"angular-ui-router": "~0.2.10",
"angular-bootstrap": "~0.11.0",
"fontawesome": "~4.1.0",
"bootstrap-sass": "~3.0.2"
"bootstrap-sass": "~3.3.5",
"angular": "~1.4.3",
"ui-router": "~0.2.15",
"angular-resource": "~1.4.3",
"angular-cookies": "~1.4.3",
"font-awesome": "~4.3.0"
}
}
@@ -0,0 +1,96 @@
'use strict';

var gulp = require('gulp');
var uglify = require('gulp-uglify');
var sass = require('gulp-sass');
var concat = require('gulp-concat');
var watch = require('gulp-watch');
var del = require('del');

var paths = {
scripts: [
'!**/test/*',

'./bower_components/angular/angular.js',
'./bower_components/angular-cookies/angular-cookies.js',
'./bower_components/angular-resource/angular-resource.js',
'./bower_components/ui-router/release/angular-ui-router.js',

'./app/js/app.js',
'./app/js/**/module.js',
'./app/js/**/*.js'
],
styles: [
'./app/scss/**/*.scss'
],
images: [
'./app/img/**/*.{png,jpg,gif}'
],
views: [
'./app/views/**/*.html'
],
fonts: [
'./bower_components/font-awesome/fonts/**/*',
'./bower_components/bootstrap-sass/assets/fonts/bootstrap/**/*'
]
};

gulp.task('clean', function(cb) {
del(['./public'], cb);
});

gulp.task('javascript', function() {
return gulp.src(paths.scripts)
.pipe(concat('app.js'))
.pipe(uglify())
.pipe(gulp.dest('./public/js'));
});

gulp.task('sass', function() {
return gulp.src(paths.styles)
.pipe(sass())
.pipe(gulp.dest('./public/css'));
});

gulp.task('images', function() {
return gulp.src(paths.images)
.pipe(gulp.dest('./public/img'));
});

gulp.task('fonts', function() {
return gulp.src(paths.fonts)
.pipe(gulp.dest('./public/fonts'));
});

gulp.task('views', function() {
return gulp.src(paths.views)
.pipe(gulp.dest('./public/views'));
});

gulp.task('watch', function() {
watch(paths.styles, function() {
gulp.start('sass');
});

watch(paths.scripts, function() {
gulp.start('javascript');
});

watch(paths.images, function() {
gulp.start('images');
});

watch(paths.views, function() {
gulp.start('views');
});

watch(paths.fonts, function() {
gulp.start('fonts');
});
});

gulp.task('compile', ['javascript', 'sass', 'images', 'fonts', 'views']);

gulp.task('default', ['clean'], function() {
gulp.start('compile');
});
@@ -1,13 +1,12 @@
{
"name": "node.js-angular.js-boilerplate",
"version": "0.0.1",
"version": "2.0.0",
"description": "Node.js and Angular.js Boilerplate",
"engines": {
"node": "0.10.x"
},
"scripts": {
"start": "node api/app.js",
"postinstall": "bower install && grunt"
"start": "node api/app.js"
},
"main": "api/app.js",
"repository": {
@@ -25,29 +24,20 @@
},
"homepage": "https://github.com/sonnyt/node.js-angular.js-boilerplate",
"dependencies": {
"body-parser": "^1.9.0",
"bower": "^1.3.12",
"composable-middleware": "^0.3.0",
"cookie-parser": "^1.3.3",
"commander": "^2.3.0",
"express": "^4.8.3",
"express-jwt": "^0.4.0",
"express-session": "^1.8.2",
"grunt": "^0.4.5",
"grunt-contrib-clean": "^0.6.0",
"grunt-contrib-compass": "^0.9.1",
"grunt-contrib-concat": "^0.5.0",
"grunt-contrib-copy": "^0.5.0",
"grunt-contrib-jshint": "^0.10.0",
"grunt-contrib-uglify": "^0.5.1",
"grunt-contrib-watch": "^0.6.1",
"grunt-express-server": "^0.4.17",
"grunt-jscs": "^0.7.1",
"jsonwebtoken": "^1.1.2",
"load-grunt-tasks": "^0.6.0",
"lodash": "^2.4.1",
"mongoose": "^3.8.14",
"passport": "^0.2.1",
"app-module-path": "^1.0.3",
"body-parser": "^1.13.2",
"express": "^4.13.1",
"jsonwebtoken": "^5.0.4",
"mongoose": "^4.1.0",
"passport": "^0.2.2",
"passport-local": "^1.0.0"
},
"devDependencies": {
"del": "^1.2.0",
"gulp": "^3.9.0",
"gulp-concat": "^2.6.0",
"gulp-sass": "^2.0.4",
"gulp-uglify": "^1.2.0",
"gulp-watch": "^4.3.4"
}
}