| @@ -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"; | ||
|
|
||
| var User = require('user/module'); | ||
| var Controllers = {}; | ||
|
|
||
| /** | ||
| * Me | ||
| */ | ||
| Controllers.me = function(req, res, next) { | ||
| var id = req.user._id; | ||
|
|
||
| User.findOne({ | ||
| _id: id | ||
| }, function(err, user) { | ||
| if (err) return next(err); | ||
|
|
||
| if (!user) { | ||
| return res.status(404).json({ | ||
| message: 'User not found.' | ||
| }); | ||
| } | ||
|
|
||
| res.status(200).json(user); | ||
| }); | ||
| }; | ||
|
|
||
| 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; |
| @@ -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; |
| @@ -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 | ||
| ]); | ||
| })(); |
| @@ -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/"; | ||
| $fa-font-path: "../fonts"; |
| @@ -6,5 +6,5 @@ | ||
| /** | ||
| * Load 3rd Party | ||
| */ | ||
| @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 to Boilerplate</title> | ||
| <link rel="stylesheet" href="/css/style.css"> | ||
| <base href="/"> | ||
| </head> | ||
| <body ng-class="pageClass"> | ||
| {{currentUser}} | ||
| <div ui-view></div> | ||
|
|
||
| <script src="/js/app.js"></script> | ||
| </body> | ||
| </html> |
| @@ -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'); | ||
| }); |