From 2e3eaf5e46aa7250ac19c81e788c52fdd079c333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Castaing?= Date: Tue, 22 Jul 2014 23:25:16 +0200 Subject: [PATCH 1/4] feat(login): add remember me functionality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - replace ngCookies by ngStorage: ngCookies only supports session cookies / ngStorage supports both local and session storage - add remember me checkbox in login.html and login.jade - pass remember me parameter to auth.service in login.controller - replace $cookieStore by $localStorage and $sessionStorage in app.js/coffee - replace $cookieStore by $localStorage and $sessionStorage in auth.service.js/coffee - add an expire parameter to signToken method in the server side auth.service.js Notes: - $sessionStorage is used when « remember me » is not checked - $localStorage is used when « remember me » is checked - with « remember me » checked, the token will expire after 7 days Closes #259 (partly) --- app/index.js | 2 +- app/templates/_bower.json | 2 +- .../app/account(auth)/login/login(html).html | 4 ++++ .../app/account(auth)/login/login(jade).jade | 3 +++ .../login/login.controller(coffee).coffee | 1 + .../login/login.controller(js).js | 3 ++- app/templates/client/app/app(coffee).coffee | 5 ++-- app/templates/client/app/app(js).js | 10 ++++---- .../auth(auth)/auth.service(coffee).coffee | 17 ++++++++----- .../components/auth(auth)/auth.service(js).js | 24 +++++++++++-------- .../server/auth(auth)/auth.service.js | 6 ++--- 11 files changed, 49 insertions(+), 28 deletions(-) diff --git a/app/index.js b/app/index.js index c37abac8c..a67fe681b 100644 --- a/app/index.js +++ b/app/index.js @@ -230,7 +230,7 @@ var AngularFullstackGenerator = yeoman.generators.Base.extend({ }); var angModules = [ - "'ngCookies'", + "'ngStorage'", "'ngResource'", "'ngSanitize'" ]; diff --git a/app/templates/_bower.json b/app/templates/_bower.json index 1681050a2..ad7088612 100644 --- a/app/templates/_bower.json +++ b/app/templates/_bower.json @@ -9,7 +9,7 @@ "bootstrap-sass-official": "~3.1.1",<% } %> "bootstrap": "~3.1.1",<% } %> "angular-resource": ">=1.2.*", - "angular-cookies": ">=1.2.*", + "ngstorage": "~0.3.0", "angular-sanitize": ">=1.2.*",<% if(filters.ngroute) { %> "angular-route": ">=1.2.*",<% } %><% if(filters.uibootstrap) { %> "angular-bootstrap": "~0.11.0",<% } %> diff --git a/app/templates/client/app/account(auth)/login/login(html).html b/app/templates/client/app/account(auth)/login/login(html).html index 483300478..e818a99ed 100644 --- a/app/templates/client/app/account(auth)/login/login(html).html +++ b/app/templates/client/app/account(auth)/login/login(html).html @@ -22,6 +22,10 @@

Login

+
+ Remember me +
+

Please enter your email and password. diff --git a/app/templates/client/app/account(auth)/login/login(jade).jade b/app/templates/client/app/account(auth)/login/login(jade).jade index 4b13c0b13..396c35647 100644 --- a/app/templates/client/app/account(auth)/login/login(jade).jade +++ b/app/templates/client/app/account(auth)/login/login(jade).jade @@ -24,6 +24,9 @@ div(ng-include='"components/navbar/navbar.html"') .form-group label Password input.form-control(type='password', name='password', ng-model='user.password') + .checkbox + input(type='checkbox' value='remember-me' ng-model='user.rememberme') + | Remember me .form-group.has-error p.help-block(ng-show='form.email.$error.required && form.password.$error.required && submitted') diff --git a/app/templates/client/app/account(auth)/login/login.controller(coffee).coffee b/app/templates/client/app/account(auth)/login/login.controller(coffee).coffee index 3f90c25d7..cc87cff3e 100644 --- a/app/templates/client/app/account(auth)/login/login.controller(coffee).coffee +++ b/app/templates/client/app/account(auth)/login/login.controller(coffee).coffee @@ -12,6 +12,7 @@ angular.module '<%= scriptAppName %>' Auth.login email: $scope.user.email password: $scope.user.password + rememberme : $scope.user.rememberme .then -> $location.path '/' diff --git a/app/templates/client/app/account(auth)/login/login.controller(js).js b/app/templates/client/app/account(auth)/login/login.controller(js).js index 7b13da384..82dcddb20 100644 --- a/app/templates/client/app/account(auth)/login/login.controller(js).js +++ b/app/templates/client/app/account(auth)/login/login.controller(js).js @@ -11,7 +11,8 @@ angular.module('<%= scriptAppName %>') if(form.$valid) { Auth.login({ email: $scope.user.email, - password: $scope.user.password + password: $scope.user.password, + rememberme : $scope.user.rememberme }) .then( function() { // Logged in, redirect to home diff --git a/app/templates/client/app/app(coffee).coffee b/app/templates/client/app/app(coffee).coffee index ea9ae3c95..5676298bd 100644 --- a/app/templates/client/app/app(coffee).coffee +++ b/app/templates/client/app/app(coffee).coffee @@ -15,11 +15,12 @@ angular.module '<%= scriptAppName %>', [<%= angularModules %>] $locationProvider.html5Mode true<% if(filters.auth) { %> $httpProvider.interceptors.push 'authInterceptor'<% } %> <% } %><% if(filters.auth) { %> -.factory 'authInterceptor', ($rootScope, $q, $cookieStore, $location) -> +.factory 'authInterceptor', ($rootScope, $q, $localStorage, $sessionStorage, $location) -> # Add authorization token to headers request: (config) -> config.headers = config.headers or {} - config.headers.Authorization = 'Bearer ' + $cookieStore.get 'token' if $cookieStore.get 'token' + token = $localStorage.token||$sessionStorage.token + config.headers.Authorization = 'Bearer ' + token if token config # Intercept 401s and redirect you to login diff --git a/app/templates/client/app/app(js).js b/app/templates/client/app/app(js).js index eef485d7c..279014c09 100644 --- a/app/templates/client/app/app(js).js +++ b/app/templates/client/app/app(js).js @@ -17,13 +17,14 @@ angular.module('<%= scriptAppName %>', [<%= angularModules %>]) $httpProvider.interceptors.push('authInterceptor');<% } %> })<% } %><% if(filters.auth) { %> - .factory('authInterceptor', function ($rootScope, $q, $cookieStore, $location) { + .factory('authInterceptor', function ($rootScope, $q, $localStorage, $sessionStorage, $location) { return { // Add authorization token to headers request: function (config) { config.headers = config.headers || {}; - if ($cookieStore.get('token')) { - config.headers.Authorization = 'Bearer ' + $cookieStore.get('token'); + var token = $localStorage.token||$sessionStorage.token; + if (token) { + config.headers.Authorization = 'Bearer ' + token; } return config; }, @@ -33,7 +34,8 @@ angular.module('<%= scriptAppName %>', [<%= angularModules %>]) if(response.status === 401) { $location.path('/login'); // remove any stale tokens - $cookieStore.remove('token'); + delete $localStorage.token; + delete $sessionStorage.token; return $q.reject(response); } else { 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..67116a802 100644 --- a/app/templates/client/components/auth(auth)/auth.service(coffee).coffee +++ b/app/templates/client/components/auth(auth)/auth.service(coffee).coffee @@ -1,8 +1,8 @@ 'use strict' angular.module '<%= scriptAppName %>' -.factory 'Auth', ($location, $rootScope, $http, User, $cookieStore, $q) -> - currentUser = if $cookieStore.get 'token' then User.get() else {} +.factory 'Auth', ($location, $rootScope, $http, User, $localStorage, $sessionStorage, $q) -> + currentUser = if $localStorage.token||$sessionStorage.token then User.get() else {} ### Authenticate user and save token @@ -16,9 +16,13 @@ angular.module '<%= scriptAppName %>' $http.post '/auth/local', email: user.email password: user.password + rememberme : user.rememberme .success (data) -> - $cookieStore.put 'token', data.token + if user.rememberme + $localStorage.token = data.token + else + $sessionStorage.token = data.token currentUser = User.get() deferred.resolve data callback?() @@ -37,7 +41,8 @@ angular.module '<%= scriptAppName %>' @param {Function} ### logout: -> - $cookieStore.remove 'token' + delete $localStorage.token + delete $sessionStorage.token currentUser = {} return @@ -52,7 +57,7 @@ angular.module '<%= scriptAppName %>' createUser: (user, callback) -> User.save user, (data) -> - $cookieStore.put 'token', data.token + $sessionStorage.token = data.token currentUser = User.get() callback? user @@ -133,4 +138,4 @@ angular.module '<%= scriptAppName %>' Get auth token ### getToken: -> - $cookieStore.get 'token' + $localStorage.token||$sessionStorage.token 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..42b088461 100644 --- a/app/templates/client/components/auth(auth)/auth.service(js).js +++ b/app/templates/client/components/auth(auth)/auth.service(js).js @@ -1,11 +1,9 @@ 'use strict'; angular.module('<%= scriptAppName %>') - .factory('Auth', function Auth($location, $rootScope, $http, User, $cookieStore, $q) { - var currentUser = {}; - if($cookieStore.get('token')) { - currentUser = User.get(); - } + .factory('Auth', function Auth($location, $rootScope, $http, User, $localStorage, $sessionStorage, $q) { + + var currentUser = ($localStorage.token||$sessionStorage.token) ? User.get() : {}; return { @@ -22,10 +20,15 @@ angular.module('<%= scriptAppName %>') $http.post('/auth/local', { email: user.email, - password: user.password + password: user.password, + rememberme : user.rememberme }). success(function(data) { - $cookieStore.put('token', data.token); + if (user.rememberme) { + $localStorage.token = data.token; + } else { + $sessionStorage.token = data.token; + } currentUser = User.get(); deferred.resolve(data); return cb(); @@ -45,7 +48,8 @@ angular.module('<%= scriptAppName %>') * @param {Function} */ logout: function() { - $cookieStore.remove('token'); + delete $localStorage.token; + delete $sessionStorage.token; currentUser = {}; }, @@ -61,7 +65,7 @@ angular.module('<%= scriptAppName %>') return User.save(user, function(data) { - $cookieStore.put('token', data.token); + $sessionStorage.token = data.token; currentUser = User.get(); return cb(user); }, @@ -140,7 +144,7 @@ angular.module('<%= scriptAppName %>') * Get auth token */ getToken: function() { - return $cookieStore.get('token'); + return $localStorage.token||$sessionStorage.token; } }; }); diff --git a/app/templates/server/auth(auth)/auth.service.js b/app/templates/server/auth(auth)/auth.service.js index 38ec34302..784218bc0 100644 --- a/app/templates/server/auth(auth)/auth.service.js +++ b/app/templates/server/auth(auth)/auth.service.js @@ -56,8 +56,8 @@ function hasRole(roleRequired) { /** * Returns a jwt token signed by the app secret */ -function signToken(id) { - return jwt.sign({ _id: id }, config.secrets.session, { expiresInMinutes: 60*5 }); +function signToken(id, role, expire) { + return jwt.sign({ _id: id, role : role }, config.secrets.session, { expiresInMinutes: expire ? 60*24*7 : 60*5 }); } /** @@ -65,7 +65,7 @@ function signToken(id) { */ 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); + var token = signToken(req.user._id, req.user.role, req.body.rememberme); res.cookie('token', JSON.stringify(token)); res.redirect('/'); } From 7259d7a8a833ff3e8aadd6c5fd4e1be12dbbdb30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Castaing?= Date: Wed, 23 Jul 2014 13:24:49 +0200 Subject: [PATCH 2/4] fix(fixtures) add ngStorage to fixtures - replace angular-cookies by ngStorage --- app/templates/karma.conf.js | 2 +- test/fixtures/bower.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/karma.conf.js b/app/templates/karma.conf.js index 57b3fa6f2..277ddc697 100644 --- a/app/templates/karma.conf.js +++ b/app/templates/karma.conf.js @@ -15,7 +15,7 @@ module.exports = function(config) { 'client/bower_components/angular/angular.js', 'client/bower_components/angular-mocks/angular-mocks.js', 'client/bower_components/angular-resource/angular-resource.js', - 'client/bower_components/angular-cookies/angular-cookies.js', + 'client/bower_components/ngstorage/ngStorage.js', 'client/bower_components/angular-sanitize/angular-sanitize.js', 'client/bower_components/angular-route/angular-route.js',<% if(filters.uibootstrap) { %> 'client/bower_components/angular-bootstrap/ui-bootstrap-tpls.js',<% } %> diff --git a/test/fixtures/bower.json b/test/fixtures/bower.json index dea41015a..c02b7d967 100644 --- a/test/fixtures/bower.json +++ b/test/fixtures/bower.json @@ -9,7 +9,7 @@ "bootstrap-stylus": "latest", "bootstrap": "~3.1.1", "angular-resource": ">=1.2.*", - "angular-cookies": ">=1.2.*", + "ngstorage": "~0.3.0", "angular-sanitize": ">=1.2.*", "angular-route": ">=1.2.*", "angular-bootstrap": "~0.11.0", From e44fc1ba046e63f601bdc5276ad5fdf5e0809640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Castaing?= Date: Tue, 5 Aug 2014 23:23:44 +0200 Subject: [PATCH 3/4] fix(remember me) replace $sessionStorage by $cookies use $cookies instead of $sessionStorage allows to share token betwenn tabs or windows --- app/index.js | 1 + app/templates/_bower.json | 1 + app/templates/client/app/app(coffee).coffee | 4 ++-- app/templates/client/app/app(js).js | 6 +++--- .../auth(auth)/auth.service(coffee).coffee | 12 ++++++------ .../client/components/auth(auth)/auth.service(js).js | 12 ++++++------ test/fixtures/bower.json | 1 + 7 files changed, 20 insertions(+), 17 deletions(-) diff --git a/app/index.js b/app/index.js index a67fe681b..e93dd5726 100644 --- a/app/index.js +++ b/app/index.js @@ -230,6 +230,7 @@ var AngularFullstackGenerator = yeoman.generators.Base.extend({ }); var angModules = [ + "'ngCookies'", "'ngStorage'", "'ngResource'", "'ngSanitize'" diff --git a/app/templates/_bower.json b/app/templates/_bower.json index ad7088612..8c673e134 100644 --- a/app/templates/_bower.json +++ b/app/templates/_bower.json @@ -9,6 +9,7 @@ "bootstrap-sass-official": "~3.1.1",<% } %> "bootstrap": "~3.1.1",<% } %> "angular-resource": ">=1.2.*", + "angular-cookies": "~1.2.21", "ngstorage": "~0.3.0", "angular-sanitize": ">=1.2.*",<% if(filters.ngroute) { %> "angular-route": ">=1.2.*",<% } %><% if(filters.uibootstrap) { %> diff --git a/app/templates/client/app/app(coffee).coffee b/app/templates/client/app/app(coffee).coffee index 5676298bd..cb2aa16ac 100644 --- a/app/templates/client/app/app(coffee).coffee +++ b/app/templates/client/app/app(coffee).coffee @@ -15,11 +15,11 @@ angular.module '<%= scriptAppName %>', [<%= angularModules %>] $locationProvider.html5Mode true<% if(filters.auth) { %> $httpProvider.interceptors.push 'authInterceptor'<% } %> <% } %><% if(filters.auth) { %> -.factory 'authInterceptor', ($rootScope, $q, $localStorage, $sessionStorage, $location) -> +.factory 'authInterceptor', ($rootScope, $q, $localStorage, $cookies, $location) -> # Add authorization token to headers request: (config) -> config.headers = config.headers or {} - token = $localStorage.token||$sessionStorage.token + token = $localStorage.token||$cookies.token config.headers.Authorization = 'Bearer ' + token if token config diff --git a/app/templates/client/app/app(js).js b/app/templates/client/app/app(js).js index 279014c09..043b1ffac 100644 --- a/app/templates/client/app/app(js).js +++ b/app/templates/client/app/app(js).js @@ -17,12 +17,12 @@ angular.module('<%= scriptAppName %>', [<%= angularModules %>]) $httpProvider.interceptors.push('authInterceptor');<% } %> })<% } %><% if(filters.auth) { %> - .factory('authInterceptor', function ($rootScope, $q, $localStorage, $sessionStorage, $location) { + .factory('authInterceptor', function ($rootScope, $q, $localStorage, $cookies, $location) { return { // Add authorization token to headers request: function (config) { config.headers = config.headers || {}; - var token = $localStorage.token||$sessionStorage.token; + var token = $localStorage.token||$cookies.token; if (token) { config.headers.Authorization = 'Bearer ' + token; } @@ -35,7 +35,7 @@ angular.module('<%= scriptAppName %>', [<%= angularModules %>]) $location.path('/login'); // remove any stale tokens delete $localStorage.token; - delete $sessionStorage.token; + delete $cookies.token; return $q.reject(response); } else { 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 67116a802..08d59e433 100644 --- a/app/templates/client/components/auth(auth)/auth.service(coffee).coffee +++ b/app/templates/client/components/auth(auth)/auth.service(coffee).coffee @@ -1,8 +1,8 @@ 'use strict' angular.module '<%= scriptAppName %>' -.factory 'Auth', ($location, $rootScope, $http, User, $localStorage, $sessionStorage, $q) -> - currentUser = if $localStorage.token||$sessionStorage.token then User.get() else {} +.factory 'Auth', ($location, $rootScope, $http, User, $localStorage, $cookies, $q) -> + currentUser = if $localStorage.token||$cookies.token then User.get() else {} ### Authenticate user and save token @@ -22,7 +22,7 @@ angular.module '<%= scriptAppName %>' if user.rememberme $localStorage.token = data.token else - $sessionStorage.token = data.token + $cookies.token = data.token currentUser = User.get() deferred.resolve data callback?() @@ -42,7 +42,7 @@ angular.module '<%= scriptAppName %>' ### logout: -> delete $localStorage.token - delete $sessionStorage.token + delete $cookies.token currentUser = {} return @@ -57,7 +57,7 @@ angular.module '<%= scriptAppName %>' createUser: (user, callback) -> User.save user, (data) -> - $sessionStorage.token = data.token + $cookies.token = data.token currentUser = User.get() callback? user @@ -138,4 +138,4 @@ angular.module '<%= scriptAppName %>' Get auth token ### getToken: -> - $localStorage.token||$sessionStorage.token + $localStorage.token||$cookies.token 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 42b088461..2a309c9b2 100644 --- a/app/templates/client/components/auth(auth)/auth.service(js).js +++ b/app/templates/client/components/auth(auth)/auth.service(js).js @@ -1,9 +1,9 @@ 'use strict'; angular.module('<%= scriptAppName %>') - .factory('Auth', function Auth($location, $rootScope, $http, User, $localStorage, $sessionStorage, $q) { + .factory('Auth', function Auth($location, $rootScope, $http, User, $localStorage, $cookies, $q) { - var currentUser = ($localStorage.token||$sessionStorage.token) ? User.get() : {}; + var currentUser = ($localStorage.token||$cookies.token) ? User.get() : {}; return { @@ -27,7 +27,7 @@ angular.module('<%= scriptAppName %>') if (user.rememberme) { $localStorage.token = data.token; } else { - $sessionStorage.token = data.token; + $cookies.token = data.token; } currentUser = User.get(); deferred.resolve(data); @@ -49,7 +49,7 @@ angular.module('<%= scriptAppName %>') */ logout: function() { delete $localStorage.token; - delete $sessionStorage.token; + delete $cookies.token; currentUser = {}; }, @@ -65,7 +65,7 @@ angular.module('<%= scriptAppName %>') return User.save(user, function(data) { - $sessionStorage.token = data.token; + $cookies.token = data.token; currentUser = User.get(); return cb(user); }, @@ -144,7 +144,7 @@ angular.module('<%= scriptAppName %>') * Get auth token */ getToken: function() { - return $localStorage.token||$sessionStorage.token; + return $localStorage.token||$cookies.token; } }; }); diff --git a/test/fixtures/bower.json b/test/fixtures/bower.json index c02b7d967..1daa1ca21 100644 --- a/test/fixtures/bower.json +++ b/test/fixtures/bower.json @@ -9,6 +9,7 @@ "bootstrap-stylus": "latest", "bootstrap": "~3.1.1", "angular-resource": ">=1.2.*", + "angular-cookies": "~1.2.21", "ngstorage": "~0.3.0", "angular-sanitize": ">=1.2.*", "angular-route": ">=1.2.*", From b963089b5f972b2d898778a0cf91b8fb83165fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Castaing?= Date: Tue, 5 Aug 2014 23:56:29 +0200 Subject: [PATCH 4/4] fix(remember me) add ngCookies to karma.conf.js add ngCookies to karma.conf.js for fixing npm test --- app/templates/karma.conf.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/templates/karma.conf.js b/app/templates/karma.conf.js index 277ddc697..6832b065a 100644 --- a/app/templates/karma.conf.js +++ b/app/templates/karma.conf.js @@ -15,6 +15,7 @@ module.exports = function(config) { 'client/bower_components/angular/angular.js', 'client/bower_components/angular-mocks/angular-mocks.js', 'client/bower_components/angular-resource/angular-resource.js', + 'client/bower_components/angular-cookies/angular-cookies.js', 'client/bower_components/ngstorage/ngStorage.js', 'client/bower_components/angular-sanitize/angular-sanitize.js', 'client/bower_components/angular-route/angular-route.js',<% if(filters.uibootstrap) { %>