added sample app for client-server authentication
This commit is contained in:
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 <Author>
|
||||
Copyright (c) 2013 Sahat Yalkabov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
0
client/css/.gitignore
vendored
Executable file
0
client/css/.gitignore
vendored
Executable file
51
client/css/app.css
Executable file
51
client/css/app.css
Executable file
@ -0,0 +1,51 @@
|
||||
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
#cover {
|
||||
position:absolute;
|
||||
height:100%;
|
||||
width:100%;
|
||||
background:white;
|
||||
}
|
||||
|
||||
#userInfo {
|
||||
float:right;padding:8px;
|
||||
}
|
||||
|
||||
.icon-twitter {
|
||||
color:#00A7E7;
|
||||
}
|
||||
|
||||
.icon-facebook-sign {
|
||||
color:#3662A0;
|
||||
}
|
||||
|
||||
.icon-google-plus-sign {
|
||||
color: #D74634;
|
||||
}
|
||||
|
||||
.fade-hide-setup, .fade-show-setup {
|
||||
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
||||
-moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
||||
-o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
||||
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
||||
}
|
||||
|
||||
.fade-hide-setup {
|
||||
opacity:1;
|
||||
}
|
||||
.fade-hide-setup.fade-hide-start {
|
||||
opacity:0;
|
||||
}
|
||||
|
||||
.fade-show-setup {
|
||||
opacity:0;
|
||||
}
|
||||
.fade-show-setup.fade-show-start {
|
||||
opacity:1;
|
||||
}
|
0
client/img/.gitignore
vendored
Executable file
0
client/img/.gitignore
vendored
Executable file
71
client/js/app.js
Executable file
71
client/js/app.js
Executable file
@ -0,0 +1,71 @@
|
||||
angular.module('myapp', ['ngCookies', 'ngRoute'])
|
||||
.config(['$routeProvider', '$locationProvider', '$httpProvider', function ($routeProvider, $locationProvider, $httpProvider) {
|
||||
var access = routingConfig.accessLevels;
|
||||
|
||||
$routeProvider.when('/', {
|
||||
templateUrl: 'home',
|
||||
controller: 'HomeCtrl',
|
||||
access: access.user
|
||||
});
|
||||
$routeProvider.when('/login', {
|
||||
templateUrl: 'login',
|
||||
controller: 'LoginCtrl',
|
||||
access: access.anon
|
||||
});
|
||||
$routeProvider.when('/register', {
|
||||
templateUrl: 'register',
|
||||
controller: 'RegisterCtrl',
|
||||
access: access.anon
|
||||
});
|
||||
$routeProvider.when('/private', {
|
||||
templateUrl: 'private',
|
||||
controller: 'PrivateCtrl',
|
||||
access: access.user
|
||||
});
|
||||
$routeProvider.when('/admin', {
|
||||
templateUrl: 'admin',
|
||||
controller: 'AdminCtrl',
|
||||
access: access.admin
|
||||
});
|
||||
$routeProvider.when('/404', {
|
||||
templateUrl: '404',
|
||||
access: access.public
|
||||
});
|
||||
$routeProvider.otherwise({
|
||||
redirectTo:'/404'
|
||||
});
|
||||
|
||||
$locationProvider.html5Mode(true);
|
||||
|
||||
var interceptor = ['$location', '$q', function($location, $q) {
|
||||
function success(response) {
|
||||
return response;
|
||||
}
|
||||
|
||||
function error(response) {
|
||||
|
||||
if(response.status === 401) {
|
||||
$location.path('/login');
|
||||
return $q.reject(response);
|
||||
}
|
||||
else {
|
||||
return $q.reject(response);
|
||||
}
|
||||
}
|
||||
|
||||
return function(promise) {
|
||||
return promise.then(success, error);
|
||||
}
|
||||
}];
|
||||
|
||||
$httpProvider.responseInterceptors.push(interceptor);
|
||||
}])
|
||||
.run(['$rootScope', '$location', 'Auth', function ($rootScope, $location, Auth) {
|
||||
$rootScope.$on("$routeChangeStart", function (event, next, current) {
|
||||
$rootScope.error = null;
|
||||
if (!Auth.authorize(next.access)) {
|
||||
if(Auth.isLoggedIn()) $location.path('/');
|
||||
else $location.path('/login');
|
||||
}
|
||||
});
|
||||
}]);
|
92
client/js/controllers.js
Executable file
92
client/js/controllers.js
Executable file
@ -0,0 +1,92 @@
|
||||
'use strict';
|
||||
|
||||
/* Controllers */
|
||||
|
||||
angular.module('angular-client-side-auth')
|
||||
.controller('NavCtrl', ['$rootScope', '$scope', '$location', 'Auth', function($rootScope, $scope, $location, Auth) {
|
||||
$scope.user = Auth.user;
|
||||
$scope.userRoles = Auth.userRoles;
|
||||
$scope.accessLevels = Auth.accessLevels;
|
||||
|
||||
$scope.logout = function() {
|
||||
Auth.logout(function() {
|
||||
$location.path('/login');
|
||||
}, function() {
|
||||
$rootScope.error = "Failed to logout";
|
||||
});
|
||||
};
|
||||
}]);
|
||||
|
||||
angular.module('angular-client-side-auth')
|
||||
.controller('LoginCtrl',
|
||||
['$rootScope', '$scope', '$location', '$window', 'Auth', function($rootScope, $scope, $location, $window, Auth) {
|
||||
|
||||
$scope.rememberme = true;
|
||||
$scope.login = function() {
|
||||
Auth.login({
|
||||
username: $scope.username,
|
||||
password: $scope.password,
|
||||
rememberme: $scope.rememberme
|
||||
},
|
||||
function(res) {
|
||||
$location.path('/');
|
||||
},
|
||||
function(err) {
|
||||
$rootScope.error = "Failed to login";
|
||||
});
|
||||
};
|
||||
|
||||
$scope.loginOauth = function(provider) {
|
||||
$window.location.href = '/auth/' + provider;
|
||||
};
|
||||
}]);
|
||||
|
||||
angular.module('angular-client-side-auth')
|
||||
.controller('HomeCtrl',
|
||||
['$rootScope', function($rootScope) {
|
||||
|
||||
}]);
|
||||
|
||||
angular.module('angular-client-side-auth')
|
||||
.controller('RegisterCtrl',
|
||||
['$rootScope', '$scope', '$location', 'Auth', function($rootScope, $scope, $location, Auth) {
|
||||
$scope.role = Auth.userRoles.user;
|
||||
$scope.userRoles = Auth.userRoles;
|
||||
|
||||
$scope.register = function() {
|
||||
Auth.register({
|
||||
username: $scope.username,
|
||||
password: $scope.password,
|
||||
role: $scope.role
|
||||
},
|
||||
function() {
|
||||
$location.path('/');
|
||||
},
|
||||
function(err) {
|
||||
$rootScope.error = err;
|
||||
});
|
||||
};
|
||||
}]);
|
||||
|
||||
angular.module('angular-client-side-auth')
|
||||
.controller('PrivateCtrl',
|
||||
['$rootScope', function($rootScope) {
|
||||
}]);
|
||||
|
||||
|
||||
angular.module('angular-client-side-auth')
|
||||
.controller('AdminCtrl',
|
||||
['$rootScope', '$scope', 'Users', 'Auth', function($rootScope, $scope, Users, Auth) {
|
||||
$scope.loading = true;
|
||||
$scope.userRoles = Auth.userRoles;
|
||||
|
||||
Users.getAll(function(res) {
|
||||
$scope.users = res;
|
||||
$scope.loading = false;
|
||||
}, function(err) {
|
||||
$rootScope.error = "Failed to fetch users.";
|
||||
$scope.loading = false;
|
||||
});
|
||||
|
||||
}]);
|
||||
|
55
client/js/directives.js
Executable file
55
client/js/directives.js
Executable file
@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('angular-client-side-auth')
|
||||
.directive('accessLevel', ['Auth', function(Auth) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, element, attrs) {
|
||||
var prevDisp = element.css('display')
|
||||
, userRole
|
||||
, accessLevel;
|
||||
|
||||
$scope.user = Auth.user;
|
||||
$scope.$watch('user', function(user) {
|
||||
if(user.role)
|
||||
userRole = user.role;
|
||||
updateCSS();
|
||||
}, true);
|
||||
|
||||
attrs.$observe('accessLevel', function(al) {
|
||||
if(al) accessLevel = $scope.$eval(al);
|
||||
updateCSS();
|
||||
});
|
||||
|
||||
function updateCSS() {
|
||||
if(userRole && accessLevel) {
|
||||
if(!Auth.authorize(accessLevel, userRole))
|
||||
element.css('display', 'none');
|
||||
else
|
||||
element.css('display', prevDisp);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
angular.module('angular-client-side-auth').directive('activeNav', ['$location', function($location) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, element, attrs) {
|
||||
var nestedA = element.find('a')[0];
|
||||
var path = nestedA.href;
|
||||
|
||||
scope.location = $location;
|
||||
scope.$watch('location.absUrl()', function(newPath) {
|
||||
if (path === newPath) {
|
||||
element.addClass('active');
|
||||
} else {
|
||||
element.removeClass('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}]);
|
0
client/js/filters.js
Executable file
0
client/js/filters.js
Executable file
97
client/js/routingConfig.js
Executable file
97
client/js/routingConfig.js
Executable file
@ -0,0 +1,97 @@
|
||||
(function(exports){
|
||||
|
||||
var config = {
|
||||
|
||||
/* List all the roles you wish to use in the app
|
||||
* You have a max of 31 before the bit shift pushes the accompanying integer out of
|
||||
* the memory footprint for an integer
|
||||
*/
|
||||
roles :[
|
||||
'public',
|
||||
'user',
|
||||
'admin'],
|
||||
|
||||
/*
|
||||
Build out all the access levels you want referencing the roles listed above
|
||||
You can use the "*" symbol to represent access to all roles
|
||||
*/
|
||||
accessLevels : {
|
||||
'public' : "*",
|
||||
'anon': ['public'],
|
||||
'user' : ['user', 'admin'],
|
||||
'admin': ['admin']
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
exports.userRoles = buildRoles(config.roles);
|
||||
exports.accessLevels = buildAccessLevels(config.accessLevels, exports.userRoles);
|
||||
|
||||
/*
|
||||
Method to build a distinct bit mask for each role
|
||||
It starts off with "1" and shifts the bit to the left for each element in the
|
||||
roles array parameter
|
||||
*/
|
||||
|
||||
function buildRoles(roles){
|
||||
|
||||
var bitMask = "01";
|
||||
var userRoles = {};
|
||||
|
||||
for(var role in roles){
|
||||
var intCode = parseInt(bitMask, 2);
|
||||
userRoles[roles[role]] = {
|
||||
bitMask: intCode,
|
||||
title: roles[role]
|
||||
};
|
||||
bitMask = (intCode << 1 ).toString(2)
|
||||
}
|
||||
|
||||
return userRoles;
|
||||
}
|
||||
|
||||
/*
|
||||
This method builds access level bit masks based on the accessLevelDeclaration parameter which must
|
||||
contain an array for each access level containing the allowed user roles.
|
||||
*/
|
||||
function buildAccessLevels(accessLevelDeclarations, userRoles){
|
||||
|
||||
var accessLevels = {};
|
||||
for(var level in accessLevelDeclarations){
|
||||
|
||||
if(typeof accessLevelDeclarations[level] == 'string'){
|
||||
if(accessLevelDeclarations[level] == '*'){
|
||||
|
||||
var resultBitMask = '';
|
||||
|
||||
for( var role in userRoles){
|
||||
resultBitMask += "1"
|
||||
}
|
||||
//accessLevels[level] = parseInt(resultBitMask, 2);
|
||||
accessLevels[level] = {
|
||||
bitMask: parseInt(resultBitMask, 2),
|
||||
title: accessLevelDeclarations[level]
|
||||
};
|
||||
}
|
||||
else console.log("Access Control Error: Could not parse '" + accessLevelDeclarations[level] + "' as access definition for level '" + level + "'")
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
var resultBitMask = 0;
|
||||
for(var role in accessLevelDeclarations[level]){
|
||||
if(userRoles.hasOwnProperty(accessLevelDeclarations[level][role]))
|
||||
resultBitMask = resultBitMask | userRoles[accessLevelDeclarations[level][role]].bitMask
|
||||
else console.log("Access Control Error: Could not find role '" + accessLevelDeclarations[level][role] + "' in registered roles while building access for '" + level + "'")
|
||||
}
|
||||
accessLevels[level] = {
|
||||
bitMask: resultBitMask,
|
||||
title: accessLevelDeclarations[level][role]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return accessLevels;
|
||||
}
|
||||
|
||||
})(typeof exports === 'undefined' ? this['routingConfig'] = {} : exports);
|
62
client/js/services.js
Executable file
62
client/js/services.js
Executable file
@ -0,0 +1,62 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('angular-client-side-auth')
|
||||
.factory('Auth', function($http, $cookieStore){
|
||||
|
||||
var accessLevels = routingConfig.accessLevels
|
||||
, userRoles = routingConfig.userRoles
|
||||
, currentUser = $cookieStore.get('user') || { username: '', role: userRoles.public };
|
||||
|
||||
$cookieStore.remove('user');
|
||||
|
||||
function changeUser(user) {
|
||||
_.extend(currentUser, user);
|
||||
};
|
||||
|
||||
return {
|
||||
authorize: function(accessLevel, role) {
|
||||
if(role === undefined)
|
||||
role = currentUser.role;
|
||||
|
||||
return accessLevel.bitMask & role.bitMask;
|
||||
},
|
||||
isLoggedIn: function(user) {
|
||||
if(user === undefined)
|
||||
user = currentUser;
|
||||
return user.role.title == userRoles.user.title || user.role.title == userRoles.admin.title;
|
||||
},
|
||||
register: function(user, success, error) {
|
||||
$http.post('/register', user).success(function(res) {
|
||||
changeUser(res);
|
||||
success();
|
||||
}).error(error);
|
||||
},
|
||||
login: function(user, success, error) {
|
||||
$http.post('/login', user).success(function(user){
|
||||
changeUser(user);
|
||||
success(user);
|
||||
}).error(error);
|
||||
},
|
||||
logout: function(success, error) {
|
||||
$http.post('/logout').success(function(){
|
||||
changeUser({
|
||||
username: '',
|
||||
role: userRoles.public
|
||||
});
|
||||
success();
|
||||
}).error(error);
|
||||
},
|
||||
accessLevels: accessLevels,
|
||||
userRoles: userRoles,
|
||||
user: currentUser
|
||||
};
|
||||
});
|
||||
|
||||
angular.module('angular-client-side-auth')
|
||||
.factory('Users', function($http) {
|
||||
return {
|
||||
getAll: function(success, error) {
|
||||
$http.get('/users').success(success).error(error);
|
||||
}
|
||||
};
|
||||
});
|
66
client/views/index.jade
Executable file
66
client/views/index.jade
Executable file
@ -0,0 +1,66 @@
|
||||
!!! 5
|
||||
html(lang='en', data-ng-app='angular-client-side-auth')
|
||||
head
|
||||
meta(charset='utf-8')
|
||||
title Angular Auth Example
|
||||
link(rel='stylesheet', href='css/app.css')
|
||||
link(href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.no-icons.min.css", rel="stylesheet")
|
||||
link(href="//netdna.bootstrapcdn.com/font-awesome/3.1.1/css/font-awesome.min.css", rel="stylesheet")
|
||||
|
||||
// This is needed because Facebook login redirects add #_=_ at the end of the URL
|
||||
script(type="text/javascript").
|
||||
if (window.location.href.indexOf('#_=_') > 0) {
|
||||
window.location = window.location.href.replace(/#.*/, '');
|
||||
}
|
||||
body(data-ng-cloak)
|
||||
|
||||
.navbar(data-ng-controller="NavCtrl")
|
||||
.navbar-inner
|
||||
.container-fluid
|
||||
ul.nav
|
||||
li(data-access-level='accessLevels.anon', active-nav)
|
||||
a(href='/login') Log in
|
||||
li(data-access-level='accessLevels.anon', active-nav)
|
||||
a(href='/register') Register
|
||||
li(data-access-level='accessLevels.user', active-nav)
|
||||
a(href='/') Home
|
||||
li(data-access-level='accessLevels.user', active-nav)
|
||||
a(href='/private') Private
|
||||
li(data-access-level='accessLevels.admin', active-nav)
|
||||
a(href='/admin') Admin
|
||||
li(data-access-level='accessLevels.user')
|
||||
a(href="", data-ng-click="logout()")
|
||||
| Log out
|
||||
div#userInfo.pull-right(data-access-level='accessLevels.user')
|
||||
| Welcome
|
||||
strong {{ user.username }}
|
||||
span.label(data-ng-class='{"label-info": user.role.title == userRoles.user.title, "label-success": user.role.title == userRoles.admin.title}') {{ user.role.title }}
|
||||
|
||||
.container
|
||||
div(data-ng-view='ng-view')
|
||||
.alert.alert-error(data-ng-bind="error", data-ng-show="error")
|
||||
|
||||
script(src='http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js')
|
||||
script(src='https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js')
|
||||
script(src='https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular-cookies.min.js')
|
||||
script(src='https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular-route.min.js')
|
||||
script(src='/js/routingConfig.js')
|
||||
script(src='/js/app.js')
|
||||
script(src='/js/services.js')
|
||||
script(src='/js/controllers.js')
|
||||
script(src='/js/filters.js')
|
||||
script(src='/js/directives.js')
|
||||
|
||||
// Partial views... Load up front to make transitions smoother
|
||||
script(type="text/ng-template", id="404")
|
||||
include partials/404
|
||||
script(type="text/ng-template", id="admin")
|
||||
include partials/admin
|
||||
script(type="text/ng-template", id="home")
|
||||
include partials/home
|
||||
script(type="text/ng-template", id="login")
|
||||
include partials/login
|
||||
script(type="text/ng-template", id="private")
|
||||
include partials/private
|
||||
script(type="text/ng-template", id="register")
|
||||
include partials/register
|
0
client/views/partials/.gitignore
vendored
Executable file
0
client/views/partials/.gitignore
vendored
Executable file
2
client/views/partials/404.jade
Executable file
2
client/views/partials/404.jade
Executable file
@ -0,0 +1,2 @@
|
||||
h1 404
|
||||
p Ain't nothing here
|
20
client/views/partials/admin.jade
Executable file
20
client/views/partials/admin.jade
Executable file
@ -0,0 +1,20 @@
|
||||
h1 Admin
|
||||
p This view is visible to users with the administrator role.
|
||||
|
||||
table.table.table-striped(data-ng-hide="loading")
|
||||
thead
|
||||
tr
|
||||
th #
|
||||
th Username
|
||||
th Role
|
||||
tbody
|
||||
tr(data-ng-repeat="user in users")
|
||||
td {{ user.id }}
|
||||
td
|
||||
i.icon-twitter(data-ng-show="user.provider == 'twitter'")
|
||||
i.icon-facebook-sign(data-ng-show="user.provider == 'facebook'")
|
||||
i.icon-google-plus-sign(data-ng-show="user.provider == 'google'")
|
||||
i.icon-linkedin(data-ng-show="user.provider == 'linkedin'")
|
||||
| {{ user.username }}
|
||||
td
|
||||
span.label(data-ng-class='{"label-info": user.role.title == userRoles.user.title, "label-success": user.role.title == userRoles.admin.title}') {{ user.role.title }}
|
2
client/views/partials/home.jade
Executable file
2
client/views/partials/home.jade
Executable file
@ -0,0 +1,2 @@
|
||||
h1 Hello
|
||||
p This view is visible to logged in users.
|
42
client/views/partials/login.jade
Executable file
42
client/views/partials/login.jade
Executable file
@ -0,0 +1,42 @@
|
||||
.hero-unit
|
||||
h1 Log in
|
||||
p This site is an example of how one can implement role based authentication in Angular applications as outlined in
|
||||
a(href="http://www.frederiknakstad.com/authentication-in-single-page-applications-with-angular-js/") this blogpost
|
||||
| . All the code can be found in
|
||||
a(href="https://github.com/fnakstad/angular-client-side-auth") this GitHub repository
|
||||
| . You can either register a new user, log in with one of the two predefined users...
|
||||
ul
|
||||
li admin/123
|
||||
li user/123
|
||||
form.form-horizontal(ng-submit="login()", name="loginForm")
|
||||
.control-group
|
||||
label.control-label(for="username") Username
|
||||
.controls
|
||||
input(type="text", data-ng-model="username", placeholder="Username", name="username", required, autofocus)
|
||||
.control-group
|
||||
label.control-label(for="password") Password
|
||||
.controls
|
||||
input(type="password", data-ng-model="password", placeholder="Password", name="password", required)
|
||||
.control-group
|
||||
.controls
|
||||
label(for="rememberme").checkbox
|
||||
input(type="checkbox", data-ng-model="rememberme", name="rememberme")
|
||||
| Remember me
|
||||
.control-group
|
||||
.controls
|
||||
button.btn(type="submit", data-ng-disabled="loginForm.$invalid") Log in
|
||||
hr
|
||||
p ... or use one of them fancy social logins:
|
||||
.btn-group
|
||||
a.btn(href="", data-ng-click="loginOauth('facebook')")
|
||||
i.icon-facebook-sign
|
||||
| Facebook
|
||||
a.btn(href="", data-ng-click="loginOauth('twitter')")
|
||||
i.icon-twitter
|
||||
| Twitter
|
||||
a.btn(href="", data-ng-click="loginOauth('google')")
|
||||
i.icon-google-plus-sign
|
||||
| Google
|
||||
a.btn(href="", data-ng-click="loginOauth('linkedin')")
|
||||
i.icon-linkedin
|
||||
| LinkedIn
|
2
client/views/partials/private.jade
Executable file
2
client/views/partials/private.jade
Executable file
@ -0,0 +1,2 @@
|
||||
h1 Private view
|
||||
p This view is visible to logged in users
|
30
client/views/partials/register.jade
Executable file
30
client/views/partials/register.jade
Executable file
@ -0,0 +1,30 @@
|
||||
h1 Register
|
||||
form.form-horizontal(ng-submit="register()", name="registerForm")
|
||||
.control-group
|
||||
label.control-label(for="username") Username
|
||||
.controls
|
||||
input(type="text", data-ng-model="username", placeholder="Username", name="username", required, data-ng-minlength="1", data-ng-maxlength="20", autofocus)
|
||||
.control-group
|
||||
label.control-label(for="password") Password
|
||||
.controls
|
||||
input(type="password", data-ng-model="password", placeholder="Password", name="password", required, data-ng-minlength="5", data-ng-maxlength="60")
|
||||
.control-group
|
||||
label.radio.inline
|
||||
input(type="radio", name="role", data-ng-model="role", id="adminRole", data-ng-value="userRoles.admin")
|
||||
| Administrator
|
||||
label.radio.inline
|
||||
input(type="radio", name="role", data-ng-model="role", id="adminRole", data-ng-value="userRoles.user")
|
||||
| Normal user
|
||||
.control-group
|
||||
.controls
|
||||
button.btn(type="submit", data-ng-disabled="registerForm.$invalid") Submit
|
||||
|
||||
.alert.alert-error(ng-show="registerForm.$invalid && registerForm.$dirty")
|
||||
strong Please correct the following errors:
|
||||
ul
|
||||
li(ng-show="registerForm.username.$error.required") Username is required
|
||||
li(ng-show="registerForm.username.$error.minlength") Username has to be at least 1 character long
|
||||
li(ng-show="registerForm.username.$error.maxlength") Username has to be at most 20 character long
|
||||
li(ng-show="registerForm.password.$error.required") Password is required
|
||||
li(ng-show="registerForm.password.$error.minlength") Password must be at least 5 characters long
|
||||
li(ng-show="registerForm.password.$error.maxlength") Password must be at most 60 characters long
|
@ -1,12 +0,0 @@
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var mongoose = require('mongoose'),
|
||||
_ = require('underscore');
|
||||
|
||||
|
||||
exports.render = function(req, res) {
|
||||
res.render('index', {
|
||||
user: req.user ? JSON.stringify(req.user) : "null"
|
||||
});
|
||||
};
|
87
gruntfile.js
87
gruntfile.js
@ -1,87 +0,0 @@
|
||||
exports.exports = function(grunt) {
|
||||
// Project Configuration
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
watch: {
|
||||
jade: {
|
||||
files: ['app/views/**'],
|
||||
options: {
|
||||
livereload: true
|
||||
}
|
||||
},
|
||||
js: {
|
||||
files: ['public/js/**', 'app/**/*.js'],
|
||||
tasks: ['jshint'],
|
||||
options: {
|
||||
livereload: true
|
||||
}
|
||||
},
|
||||
html: {
|
||||
files: ['public/views/**'],
|
||||
options: {
|
||||
livereload: true
|
||||
}
|
||||
},
|
||||
css: {
|
||||
files: ['public/css/**'],
|
||||
options: {
|
||||
livereload: true
|
||||
}
|
||||
}
|
||||
},
|
||||
jshint: {
|
||||
all: ['gruntfile.js', 'public/js/**/*.js', 'test/**/*.js', 'app/**/*.js']
|
||||
},
|
||||
nodemon: {
|
||||
dev: {
|
||||
options: {
|
||||
file: 'server.js',
|
||||
args: [],
|
||||
ignoredFiles: ['README.md', 'node_modules/**', '.DS_Store'],
|
||||
watchedExtensions: ['js'],
|
||||
watchedFolders: ['app', 'config'],
|
||||
debug: true,
|
||||
delayTime: 1,
|
||||
env: {
|
||||
PORT: 3000
|
||||
},
|
||||
cwd: __dirname
|
||||
}
|
||||
}
|
||||
},
|
||||
concurrent: {
|
||||
tasks: ['nodemon', 'watch'],
|
||||
options: {
|
||||
logConcurrentOutput: true
|
||||
}
|
||||
},
|
||||
mochaTest: {
|
||||
options: {
|
||||
reporter: 'spec'
|
||||
},
|
||||
src: ['test/**/*.js']
|
||||
},
|
||||
env: {
|
||||
test: {
|
||||
NODE_ENV: 'test'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//Load NPM tasks
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||
grunt.loadNpmTasks('grunt-mocha-test');
|
||||
grunt.loadNpmTasks('grunt-nodemon');
|
||||
grunt.loadNpmTasks('grunt-concurrent');
|
||||
grunt.loadNpmTasks('grunt-env');
|
||||
|
||||
//Making grunt default to force in order not to break the project.
|
||||
grunt.option('force', true);
|
||||
|
||||
//Default task(s).
|
||||
grunt.registerTask('default', ['jshint', 'concurrent']);
|
||||
|
||||
//Test task.
|
||||
grunt.registerTask('test', ['env:test', 'mochaTest']);
|
||||
};
|
@ -1,16 +1,9 @@
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var mongoose = require('mongoose'),
|
||||
Schema = mongoose.Schema,
|
||||
crypto = require('crypto'),
|
||||
_ = require('underscore'),
|
||||
authTypes = ['github', 'twitter', 'facebook', 'google'];
|
||||
Schema = mongoose.Schema,
|
||||
crypto = require('crypto'),
|
||||
_ = require('underscore');
|
||||
|
||||
|
||||
/**
|
||||
* User Schema
|
||||
*/
|
||||
var UserSchema = new Schema({
|
||||
name: String,
|
||||
email: String,
|
||||
@ -23,7 +16,6 @@ var UserSchema = new Schema({
|
||||
salt: String,
|
||||
facebook: {},
|
||||
twitter: {},
|
||||
github: {},
|
||||
google: {}
|
||||
});
|
||||
|
||||
|
14
server.js
14
server.js
@ -21,6 +21,20 @@ app.use(app.router);
|
||||
app.use(express.static(config.root + '/public'));
|
||||
|
||||
|
||||
/**
|
||||
* API Controllers
|
||||
*/
|
||||
var articles = require('./controllers/articles');
|
||||
var users = require('./controllers/users');
|
||||
|
||||
|
||||
/**
|
||||
* API Models
|
||||
*/
|
||||
var Article = require('./models/article');
|
||||
var User = require('./models/user');
|
||||
|
||||
|
||||
/**
|
||||
* API Routes
|
||||
*/
|
||||
|
46
server/controllers/auth.js
Executable file
46
server/controllers/auth.js
Executable file
@ -0,0 +1,46 @@
|
||||
var passport = require('passport')
|
||||
, User = require('../models/User.js');
|
||||
|
||||
module.exports = {
|
||||
register: function(req, res, next) {
|
||||
try {
|
||||
User.validate(req.body);
|
||||
}
|
||||
catch(err) {
|
||||
return res.send(400, err.message);
|
||||
}
|
||||
|
||||
User.addUser(req.body.username, req.body.password, req.body.role, function(err, user) {
|
||||
if(err === 'UserAlreadyExists') return res.send(403, "User already exists");
|
||||
else if(err) return res.send(500);
|
||||
|
||||
req.logIn(user, function(err) {
|
||||
if(err) { next(err); }
|
||||
else { res.json(200, { "role": user.role, "username": user.username }); }
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
login: function(req, res, next) {
|
||||
passport.authenticate('local', function(err, user) {
|
||||
|
||||
if(err) { return next(err); }
|
||||
if(!user) { return res.send(400); }
|
||||
|
||||
|
||||
req.logIn(user, function(err) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if(req.body.rememberme) req.session.cookie.maxAge = 1000 * 60 * 60 * 24 * 7;
|
||||
res.json(200, { "role": user.role, "username": user.username });
|
||||
});
|
||||
})(req, res, next);
|
||||
},
|
||||
|
||||
logout: function(req, res) {
|
||||
req.logout();
|
||||
res.send(200);
|
||||
}
|
||||
};
|
17
server/controllers/user.js
Executable file
17
server/controllers/user.js
Executable file
@ -0,0 +1,17 @@
|
||||
var _ = require('underscore')
|
||||
, User = require('../models/User.js')
|
||||
, userRoles = require('../../client/js/routingConfig').userRoles;
|
||||
|
||||
module.exports = {
|
||||
index: function(req, res) {
|
||||
var users = User.findAll();
|
||||
_.each(users, function(user) {
|
||||
delete user.password;
|
||||
delete user.twitter;
|
||||
delete user.facebook;
|
||||
delete user.google;
|
||||
delete user.linkedin;
|
||||
});
|
||||
res.json(users);
|
||||
}
|
||||
};
|
175
server/models/User.js
Executable file
175
server/models/User.js
Executable file
@ -0,0 +1,175 @@
|
||||
var User
|
||||
, _ = require('underscore')
|
||||
, passport = require('passport')
|
||||
, LocalStrategy = require('passport-local').Strategy
|
||||
, TwitterStrategy = require('passport-twitter').Strategy
|
||||
, FacebookStrategy = require('passport-facebook').Strategy
|
||||
, GoogleStrategy = require('passport-google').Strategy
|
||||
, LinkedInStrategy = require('passport-linkedin').Strategy
|
||||
, check = require('validator').check
|
||||
, userRoles = require('../../client/js/routingConfig').userRoles;
|
||||
|
||||
var users = [
|
||||
{
|
||||
id: 1,
|
||||
username: "user",
|
||||
password: "123",
|
||||
role: userRoles.user
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
username: "admin",
|
||||
password: "123",
|
||||
role: userRoles.admin
|
||||
}
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
addUser: function(username, password, role, callback) {
|
||||
if(this.findByUsername(username) !== undefined) return callback("UserAlreadyExists");
|
||||
|
||||
// Clean up when 500 users reached
|
||||
if(users.length > 500) {
|
||||
users = users.slice(0, 2);
|
||||
}
|
||||
|
||||
var user = {
|
||||
id: _.max(users, function(user) { return user.id; }).id + 1,
|
||||
username: username,
|
||||
password: password,
|
||||
role: role
|
||||
};
|
||||
users.push(user);
|
||||
callback(null, user);
|
||||
},
|
||||
|
||||
findOrCreateOauthUser: function(provider, providerId) {
|
||||
var user = module.exports.findByProviderId(provider, providerId);
|
||||
if(!user) {
|
||||
user = {
|
||||
id: _.max(users, function(user) { return user.id; }).id + 1,
|
||||
username: provider + '_user', // Should keep Oauth users anonymous on demo site
|
||||
role: userRoles.user,
|
||||
provider: provider
|
||||
};
|
||||
user[provider] = providerId;
|
||||
users.push(user);
|
||||
}
|
||||
|
||||
return user;
|
||||
},
|
||||
|
||||
findAll: function() {
|
||||
return _.map(users, function(user) { return _.clone(user); });
|
||||
},
|
||||
|
||||
findById: function(id) {
|
||||
return _.clone(_.find(users, function(user) { return user.id === id }));
|
||||
},
|
||||
|
||||
findByUsername: function(username) {
|
||||
return _.clone(_.find(users, function(user) { return user.username === username; }));
|
||||
},
|
||||
|
||||
findByProviderId: function(provider, id) {
|
||||
return _.find(users, function(user) { return user[provider] === id; });
|
||||
},
|
||||
|
||||
validate: function(user) {
|
||||
check(user.username, 'Username must be 1-20 characters long').len(1, 20);
|
||||
check(user.password, 'Password must be 5-60 characters long').len(5, 60);
|
||||
check(user.username, 'Invalid username').not(/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)/);
|
||||
|
||||
// TODO: Seems node-validator's isIn function doesn't handle Number arrays very well...
|
||||
// Till this is rectified Number arrays must be converted to string arrays
|
||||
// https://github.com/chriso/node-validator/issues/185
|
||||
var stringArr = _.map(_.values(userRoles), function(val) { return val.toString() });
|
||||
check(user.role, 'Invalid user role given').isIn(stringArr);
|
||||
},
|
||||
|
||||
localStrategy: new LocalStrategy(
|
||||
function(username, password, done) {
|
||||
|
||||
var user = module.exports.findByUsername(username);
|
||||
|
||||
if(!user) {
|
||||
done(null, false, { message: 'Incorrect username.' });
|
||||
}
|
||||
else if(user.password != password) {
|
||||
done(null, false, { message: 'Incorrect username.' });
|
||||
}
|
||||
else {
|
||||
return done(null, user);
|
||||
}
|
||||
|
||||
}
|
||||
),
|
||||
|
||||
twitterStrategy: function() {
|
||||
if(!process.env.TWITTER_CONSUMER_KEY) throw new Error('A Twitter Consumer Key is required if you want to enable login via Twitter.');
|
||||
if(!process.env.TWITTER_CONSUMER_SECRET) throw new Error('A Twitter Consumer Secret is required if you want to enable login via Twitter.');
|
||||
|
||||
return new TwitterStrategy({
|
||||
consumerKey: process.env.TWITTER_CONSUMER_KEY,
|
||||
consumerSecret: process.env.TWITTER_CONSUMER_SECRET,
|
||||
callbackURL: process.env.TWITTER_CALLBACK_URL || 'http://localhost:8000/auth/twitter/callback'
|
||||
},
|
||||
function(token, tokenSecret, profile, done) {
|
||||
var user = module.exports.findOrCreateOauthUser(profile.provider, profile.id);
|
||||
done(null, user);
|
||||
});
|
||||
},
|
||||
|
||||
facebookStrategy: function() {
|
||||
if(!process.env.FACEBOOK_APP_ID) throw new Error('A Facebook App ID is required if you want to enable login via Facebook.');
|
||||
if(!process.env.FACEBOOK_APP_SECRET) throw new Error('A Facebook App Secret is required if you want to enable login via Facebook.');
|
||||
|
||||
return new FacebookStrategy({
|
||||
clientID: process.env.FACEBOOK_APP_ID,
|
||||
clientSecret: process.env.FACEBOOK_APP_SECRET,
|
||||
callbackURL: process.env.FACEBOOK_CALLBACK_URL || "http://localhost:8000/auth/facebook/callback"
|
||||
},
|
||||
function(accessToken, refreshToken, profile, done) {
|
||||
var user = module.exports.findOrCreateOauthUser(profile.provider, profile.id);
|
||||
done(null, user);
|
||||
});
|
||||
},
|
||||
|
||||
googleStrategy: function() {
|
||||
|
||||
return new GoogleStrategy({
|
||||
returnURL: process.env.GOOGLE_RETURN_URL || "http://localhost:8000/auth/google/return",
|
||||
realm: process.env.GOOGLE_REALM || "http://localhost:8000/"
|
||||
},
|
||||
function(identifier, profile, done) {
|
||||
var user = module.exports.findOrCreateOauthUser('google', identifier);
|
||||
done(null, user);
|
||||
});
|
||||
},
|
||||
|
||||
linkedInStrategy: function() {
|
||||
if(!process.env.LINKED_IN_KEY) throw new Error('A LinkedIn App Key is required if you want to enable login via LinkedIn.');
|
||||
if(!process.env.LINKED_IN_SECRET) throw new Error('A LinkedIn App Secret is required if you want to enable login via LinkedIn.');
|
||||
|
||||
return new LinkedInStrategy({
|
||||
consumerKey: process.env.LINKED_IN_KEY,
|
||||
consumerSecret: process.env.LINKED_IN_SECRET,
|
||||
callbackURL: process.env.LINKED_IN_CALLBACK_URL || "http://localhost:8000/auth/linkedin/callback"
|
||||
},
|
||||
function(token, tokenSecret, profile, done) {
|
||||
var user = module.exports.findOrCreateOauthUser('linkedin', profile.id);
|
||||
done(null,user);
|
||||
}
|
||||
);
|
||||
},
|
||||
serializeUser: function(user, done) {
|
||||
done(null, user.id);
|
||||
},
|
||||
|
||||
deserializeUser: function(id, done) {
|
||||
var user = module.exports.findById(id);
|
||||
|
||||
if(user) { done(null, user); }
|
||||
else { done(null, false); }
|
||||
}
|
||||
};
|
155
server/routes.js
Executable file
155
server/routes.js
Executable file
@ -0,0 +1,155 @@
|
||||
var _ = require('underscore')
|
||||
, path = require('path')
|
||||
, passport = require('passport')
|
||||
, AuthCtrl = require('./controllers/auth')
|
||||
, UserCtrl = require('./controllers/user')
|
||||
, User = require('./models/User.js')
|
||||
, userRoles = require('../client/js/routingConfig').userRoles
|
||||
, accessLevels = require('../client/js/routingConfig').accessLevels;
|
||||
|
||||
var routes = [
|
||||
|
||||
// Views
|
||||
{
|
||||
path: '/partials/*',
|
||||
httpMethod: 'GET',
|
||||
middleware: [function (req, res) {
|
||||
var requestedView = path.join('./', req.url);
|
||||
res.render(requestedView);
|
||||
}]
|
||||
},
|
||||
|
||||
// OAUTH
|
||||
{
|
||||
path: '/auth/twitter',
|
||||
httpMethod: 'GET',
|
||||
middleware: [passport.authenticate('twitter')]
|
||||
},
|
||||
{
|
||||
path: '/auth/twitter/callback',
|
||||
httpMethod: 'GET',
|
||||
middleware: [passport.authenticate('twitter', {
|
||||
successRedirect: '/',
|
||||
failureRedirect: '/login'
|
||||
})]
|
||||
},
|
||||
{
|
||||
path: '/auth/facebook',
|
||||
httpMethod: 'GET',
|
||||
middleware: [passport.authenticate('facebook')]
|
||||
},
|
||||
{
|
||||
path: '/auth/facebook/callback',
|
||||
httpMethod: 'GET',
|
||||
middleware: [passport.authenticate('facebook', {
|
||||
successRedirect: '/',
|
||||
failureRedirect: '/login'
|
||||
})]
|
||||
},
|
||||
{
|
||||
path: '/auth/google',
|
||||
httpMethod: 'GET',
|
||||
middleware: [passport.authenticate('google')]
|
||||
},
|
||||
{
|
||||
path: '/auth/google/return',
|
||||
httpMethod: 'GET',
|
||||
middleware: [passport.authenticate('google', {
|
||||
successRedirect: '/',
|
||||
failureRedirect: '/login'
|
||||
})]
|
||||
},
|
||||
{
|
||||
path: '/auth/linkedin',
|
||||
httpMethod: 'GET',
|
||||
middleware: [passport.authenticate('linkedin')]
|
||||
},
|
||||
{
|
||||
path: '/auth/linkedin/callback',
|
||||
httpMethod: 'GET',
|
||||
middleware: [passport.authenticate('linkedin', {
|
||||
successRedirect: '/',
|
||||
failureRedirect: '/login'
|
||||
})]
|
||||
},
|
||||
|
||||
// Local Auth
|
||||
{
|
||||
path: '/register',
|
||||
httpMethod: 'POST',
|
||||
middleware: [AuthCtrl.register]
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
httpMethod: 'POST',
|
||||
middleware: [AuthCtrl.login]
|
||||
},
|
||||
{
|
||||
path: '/logout',
|
||||
httpMethod: 'POST',
|
||||
middleware: [AuthCtrl.logout]
|
||||
},
|
||||
|
||||
// User resource
|
||||
{
|
||||
path: '/users',
|
||||
httpMethod: 'GET',
|
||||
middleware: [UserCtrl.index],
|
||||
accessLevel: accessLevels.admin
|
||||
},
|
||||
|
||||
// All other get requests should be handled by AngularJS's client-side routing system
|
||||
{
|
||||
path: '/*',
|
||||
httpMethod: 'GET',
|
||||
middleware: [function(req, res) {
|
||||
var role = userRoles.public, username = '';
|
||||
if(req.user) {
|
||||
role = req.user.role;
|
||||
username = req.user.username;
|
||||
}
|
||||
res.cookie('user', JSON.stringify({
|
||||
'username': username,
|
||||
'role': role
|
||||
}));
|
||||
res.render('index');
|
||||
}]
|
||||
}
|
||||
];
|
||||
|
||||
module.exports = function(app) {
|
||||
|
||||
_.each(routes, function(route) {
|
||||
route.middleware.unshift(ensureAuthorized);
|
||||
var args = _.flatten([route.path, route.middleware]);
|
||||
|
||||
switch(route.httpMethod.toUpperCase()) {
|
||||
case 'GET':
|
||||
app.get.apply(app, args);
|
||||
break;
|
||||
case 'POST':
|
||||
app.post.apply(app, args);
|
||||
break;
|
||||
case 'PUT':
|
||||
app.put.apply(app, args);
|
||||
break;
|
||||
case 'DELETE':
|
||||
app.delete.apply(app, args);
|
||||
break;
|
||||
default:
|
||||
throw new Error('Invalid HTTP method specified for route ' + route.path);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function ensureAuthorized(req, res, next) {
|
||||
var role;
|
||||
if(!req.user) role = userRoles.public;
|
||||
else role = req.user.role;
|
||||
|
||||
var accessLevel = _.findWhere(routes, { path: req.route.path }).accessLevel || accessLevels.public;
|
||||
|
||||
if(!(accessLevel.bitMask & role.bitMask)) return res.send(403);
|
||||
return next();
|
||||
}
|
57
server/tests/integration/index.spec.js
Executable file
57
server/tests/integration/index.spec.js
Executable file
@ -0,0 +1,57 @@
|
||||
var app = require('../../../server'),
|
||||
request = require('supertest'),
|
||||
passportStub = require('passport-stub');
|
||||
passportStub.install(app);
|
||||
|
||||
// user account
|
||||
var user = {
|
||||
'username':'newUser',
|
||||
'role':{bitMask: 2,title: "user"},
|
||||
'password':'12345'
|
||||
};
|
||||
|
||||
// user account 2 - no role
|
||||
var user2 = {
|
||||
'username':'newUser',
|
||||
'password':'12345'
|
||||
};
|
||||
|
||||
// admin account
|
||||
var admin = {
|
||||
'username':'admin',
|
||||
'role': { bitMask: 4, title: 'admin' },
|
||||
'id': '2',
|
||||
'password':'123'
|
||||
};
|
||||
|
||||
describe('Server Integration Tests - ', function (done) {
|
||||
afterEach(function() {
|
||||
passportStub.logout(); // logout after each test
|
||||
});
|
||||
it('Homepage - Return a 200', function(done) {
|
||||
request(app).get('/').expect(200, done);
|
||||
});
|
||||
it('Logout - Return a 200', function(done) {
|
||||
request(app).post('/logout').expect(200, done);
|
||||
});
|
||||
it('As a Logout user, on /users - Return a 403', function(done) {
|
||||
request(app).get('/users').expect(403, done);
|
||||
});
|
||||
it('Register a new user(no role) - Return a 400', function(done) {
|
||||
request(app).post('/register').send(user2).expect(400, done);
|
||||
});
|
||||
it('Register a new user - Return a 200', function(done) {
|
||||
request(app).post('/register').send(user).expect(200, done);
|
||||
});
|
||||
it('As a normal user, on /users - Return a 403', function(done) {
|
||||
passportStub.login(user); // login as user
|
||||
request(app).get('/users').expect(403, done);
|
||||
});
|
||||
it('Login as Admin - Return a 200', function(done) {
|
||||
request(app).post('/login').send(admin).expect(200, done);
|
||||
});
|
||||
it('As a Admin user, on /users - Return a 200', function(done) {
|
||||
passportStub.login(admin); // login as admin
|
||||
request(app).get('/users').expect(200, done);
|
||||
});
|
||||
});
|
102
server/tests/unit/controllers/auth.spec.js
Executable file
102
server/tests/unit/controllers/auth.spec.js
Executable file
@ -0,0 +1,102 @@
|
||||
var expect = require('chai').expect
|
||||
, sinon = require('sinon')
|
||||
, AuthCtrl = require('../../../controllers/auth')
|
||||
, User = require('../../../models/User');
|
||||
|
||||
describe('Auth controller Unit Tests - ', function() {
|
||||
|
||||
var req = { }
|
||||
, res = {}
|
||||
, next = {}
|
||||
, sandbox = sinon.sandbox.create();
|
||||
|
||||
beforeEach(function() {
|
||||
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('register()', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
req.body = {
|
||||
username: "user",
|
||||
password: "pass",
|
||||
role: 1
|
||||
};
|
||||
});
|
||||
|
||||
it('should return a 400 when user validation fails', function(done) {
|
||||
|
||||
var userValidateStub = sandbox.stub(User, 'validate').throws();
|
||||
res.send = function(httpStatus) {
|
||||
expect(httpStatus).to.equal(400);
|
||||
done();
|
||||
};
|
||||
|
||||
AuthCtrl.register(req, res, next);
|
||||
});
|
||||
|
||||
it('should return a 403 when UserAlreadyExists error is returned from User.addUser()', function(done) {
|
||||
var userValidateStub = sandbox.stub(User, 'validate').returns();
|
||||
var userAddUserStub = sandbox.stub(User, 'addUser', function(username, password, role, callback) {
|
||||
callback('UserAlreadyExists');
|
||||
});
|
||||
|
||||
res.send = function(httpStatus) {
|
||||
expect(httpStatus).to.equal(403);
|
||||
done();
|
||||
};
|
||||
|
||||
AuthCtrl.register(req, res, next);
|
||||
});
|
||||
|
||||
it('should return a 500 if error other than UserAlreadyExists is returned from User.addUser()', function(done) {
|
||||
var userValidateStub = sandbox.stub(User, 'validate').returns();
|
||||
var userAddUserStub = sandbox.stub(User, 'addUser', function(username, password, role, callback) {
|
||||
callback('SomeError');
|
||||
});
|
||||
|
||||
res.send = function(httpStatus) {
|
||||
expect(httpStatus).to.equal(500);
|
||||
done();
|
||||
};
|
||||
|
||||
AuthCtrl.register(req, res, next);
|
||||
});
|
||||
|
||||
it('should call next() with an error argument if req.logIn() returns error', function(done) {
|
||||
var userValidateStub = sandbox.stub(User, 'validate').returns();
|
||||
var userAddUserStub = sandbox.stub(User, 'addUser', function(username, password, role, callback) {
|
||||
callback(null, req.body);
|
||||
});
|
||||
req.logIn = function(user, callback) { return callback('SomeError'); };
|
||||
|
||||
next = function(err) {
|
||||
expect(err).to.exist;
|
||||
done();
|
||||
};
|
||||
|
||||
AuthCtrl.register(req, res, next);
|
||||
});
|
||||
|
||||
it('should return a 200 with a username and role in the response body', function(done) {
|
||||
var userValidateStub = sandbox.stub(User, 'validate').returns();
|
||||
var userAddUserStub = sandbox.stub(User, 'addUser', function(username, password, role, callback) {
|
||||
callback(null, req.body);
|
||||
});
|
||||
req.logIn = function(user, callback) { return callback(null); };
|
||||
|
||||
res.json = function(httpStatus, user) {
|
||||
expect(httpStatus).to.equal(200);
|
||||
expect(user.username).to.exist;
|
||||
expect(user.role).to.exist;
|
||||
done();
|
||||
};
|
||||
|
||||
AuthCtrl.register(req, res, next);
|
||||
});
|
||||
});
|
||||
});
|
37
server2.js
Executable file
37
server2.js
Executable file
@ -0,0 +1,37 @@
|
||||
var express = require('express')
|
||||
, http = require('http')
|
||||
, passport = require('passport')
|
||||
, path = require('path')
|
||||
, User = require('./server/models/User.js');
|
||||
|
||||
var app = module.exports = express();
|
||||
|
||||
app.set('views', __dirname + '/client/views');
|
||||
app.set('view engine', 'jade');
|
||||
app.use(express.logger('dev'))
|
||||
app.use(express.cookieParser());
|
||||
app.use(express.bodyParser());
|
||||
app.use(express.methodOverride());
|
||||
app.use(express.static(path.join(__dirname, 'client')));
|
||||
app.use(express.cookieSession(
|
||||
{
|
||||
secret: process.env.COOKIE_SECRET || "Superdupersecret"
|
||||
}));
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
passport.use(User.localStrategy);
|
||||
passport.use(User.twitterStrategy()); // Comment out this line if you don't want to enable login via Twitter
|
||||
passport.use(User.facebookStrategy()); // Comment out this line if you don't want to enable login via Facebook
|
||||
passport.use(User.googleStrategy()); // Comment out this line if you don't want to enable login via Google
|
||||
passport.use(User.linkedInStrategy()); // Comment out this line if you don't want to enable login via LinkedIn
|
||||
|
||||
passport.serializeUser(User.serializeUser);
|
||||
passport.deserializeUser(User.deserializeUser);
|
||||
|
||||
require('./server/routes.js')(app);
|
||||
|
||||
app.set('port', process.env.PORT || 8000);
|
||||
http.createServer(app).listen(app.get('port'), function(){
|
||||
console.log("Express server listening on port " + app.get('port'));
|
||||
});
|
Reference in New Issue
Block a user