changeset 178:4c0492488b9c

Remember user session when using basic auth Reviewed-by: jerboaa Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-September/024806.html
author Andrew Azores <aazores@redhat.com>
date Fri, 01 Sep 2017 14:17:02 -0400
parents 97e433c06ff8
children 8f116d9b0130
files src/app/app.controller.js src/app/app.controller.spec.js src/app/app.routing.js src/app/app.routing.spec.js src/app/components/auth/basic-auth.service.js src/app/components/auth/basic-auth.service.spec.js
diffstat 6 files changed, 92 insertions(+), 40 deletions(-) [+]
line wrap: on
line diff
--- a/src/app/app.controller.js	Thu Aug 31 14:54:11 2017 -0400
+++ b/src/app/app.controller.js	Fri Sep 01 14:17:02 2017 -0400
@@ -31,6 +31,9 @@
   constructor ($scope, environment, $state, authService) {
     'ngInject';
 
+    this._scope = $scope;
+    this._authService = authService;
+
     angular.element('logoutButton').removeAttr('hidden');
     if (environment !== 'production') {
       $scope.env = environment;
@@ -41,7 +44,12 @@
 
     $scope.logout = () => authService.logout();
 
-    $scope.$on('userLoginChanged', () => $scope.username = authService.username);
+    $scope.$on('userLoginChanged', () => this.updateUsernameLabel());
+    this.updateUsernameLabel();
+  }
+
+  updateUsernameLabel () {
+    this._scope.username = this._authService.username;
   }
 
 }
--- a/src/app/app.controller.spec.js	Thu Aug 31 14:54:11 2017 -0400
+++ b/src/app/app.controller.spec.js	Fri Sep 01 14:17:02 2017 -0400
@@ -134,8 +134,13 @@
       });
     }));
 
+    it('should be set on init', () => {
+      scope.should.have.ownProperty('username');
+      scope.username.should.equal(authService.username);
+    });
+
     it('should be set on userLoginChanged according to authService username', () => {
-      scope.should.not.have.ownProperty('username');
+      authService.username = 'new-username';
       rootScope.$broadcast('userLoginChanged');
       scope.should.have.ownProperty('username');
       scope.username.should.equal(authService.username);
--- a/src/app/app.routing.js	Thu Aug 31 14:54:11 2017 -0400
+++ b/src/app/app.routing.js	Fri Sep 01 14:17:02 2017 -0400
@@ -56,21 +56,18 @@
 
 function transitionHook ($q, $transitions, $state, authService) {
   'ngInject';
-  $transitions.onBefore({ to: '/' }, () => {
-    return $state.target('landing');
-  });
+  $transitions.onBefore({ to: '/' }, () => $state.target('landing'));
+
+  $transitions.onEnter({}, () => { authService.refresh() });
 
-  $transitions.onBefore({ to: state => {
-    return state.name !== 'about' && state.name !== 'login' && !authService.status();
-  }}, () => {
-    let defer = $q.defer();
-    authService.refresh()
-      .then(() => defer.resolve(),
-        () => {
-          authService.goToLogin(defer);
-        });
-    return defer.promise;
-  });
+  $transitions.onBefore(
+    { to: state => state.name !== 'about' && state.name !== 'login' && !authService.status() },
+    () => {
+      let defer = $q.defer();
+      authService.refresh().then(() => defer.resolve(), () => authService.goToLogin(defer));
+      return defer.promise;
+    }
+  );
 }
 appRouter.run(transitionHook);
 export default appRouter.name;
--- a/src/app/app.routing.spec.js	Thu Aug 31 14:54:11 2017 -0400
+++ b/src/app/app.routing.spec.js	Fri Sep 01 14:17:02 2017 -0400
@@ -56,7 +56,8 @@
       goToLogin: sinon.spy()
     };
     transitions = {
-      onBefore: sinon.spy()
+      onBefore: sinon.spy(),
+      onEnter: sinon.spy()
     };
 
     module.errorRouting(stateProvider, urlRouterProvider);
@@ -107,11 +108,20 @@
   });
 
   describe('state change hook', () => {
-    it('should only be on state change start', () => {
-      transitions.onBefore.should.be.calledTwice();
+
+    describe('onEnter hook', () => {
+      it('should perform authService refresh on all transitions', () => {
+        authSvc.refresh.should.not.be.called();
+        let args = transitions.onEnter.args[0];
+        args[0].should.be.an.Object();
+        args[0].should.deepEqual({});
+        args[1].should.be.a.Function();
+        args[1]();
+        authSvc.refresh.should.be.calledOnce();
+      });
     });
 
-    describe('first hook', () => {
+    describe('first onBefore hook', () => {
       it('should match root transitions', () => {
         transitions.onBefore.args[0][0].should.have.ownProperty('to');
         transitions.onBefore.args[0][0].to.should.equal('/');
@@ -126,7 +136,7 @@
       });
     });
 
-    describe('second hook', () => {
+    describe('second onBefore hook', () => {
       it('should match non-login transitions', () => {
         transitions.onBefore.args[1][0].should.have.ownProperty('to');
         let fn = transitions.onBefore.args[1][0].to;
--- a/src/app/components/auth/basic-auth.service.js	Thu Aug 31 14:54:11 2017 -0400
+++ b/src/app/components/auth/basic-auth.service.js	Fri Sep 01 14:17:02 2017 -0400
@@ -34,9 +34,7 @@
     this.q = $q;
     this.$state = $state;
     this.cookies = $cookies;
-    this.state = false;
 
-    this._user = null;
     this._pass = null;
   }
 
@@ -45,16 +43,18 @@
   }
 
   status () {
-    return this.state;
+    return angular.isDefined(this.cookies.get('session'));
   }
 
   login (user, pass, success = angular.noop) {
-    this._user = user;
     this._pass = pass;
-    this.state = true;
     if (this._rememberUser) {
       this.cookies.put('username', user);
     }
+
+    this._refreshSession();
+    this.cookies.put('loggedInUser', user);
+
     this._rootScope.$broadcast('userLoginChanged');
     success();
   }
@@ -64,30 +64,43 @@
   }
 
   logout (callback = angular.noop) {
-    this._user = null;
     this._pass = null;
-    this.state = false;
     this.$state.go('login');
+
+    this.cookies.remove('session');
+    this.cookies.remove('loggedInUser');
+
     this._rootScope.$broadcast('userLoginChanged');
     callback();
   }
 
+  _refreshSession () {
+    let now = new Date();
+    let expiry = new Date(now);
+    expiry.setMinutes(now.getMinutes() + 15);
+    this.cookies.put('session', true, { expires: expiry });
+  }
+
   refresh () {
     let defer = this.q.defer();
-    if (this.state) {
+    let session = this.cookies.get('session');
+    if (session) {
+      this._refreshSession();
       defer.resolve();
     } else {
+      this.cookies.remove('session');
+      this.cookies.remove('loggedInUser');
       defer.reject();
     }
     return defer.promise;
   }
 
   get authHeader () {
-    return 'Basic ' + btoa(this._user + ':' + this._pass);
+    return 'Basic ' + btoa(this.username + ':' + this._pass);
   }
 
   get username () {
-    return this._user;
+    return this.cookies.get('loggedInUser');
   }
 
   get rememberedUsername () {
@@ -96,13 +109,13 @@
 
   getCommandChannelUrl (baseUrl) {
     let parsed = url.parse(baseUrl);
-    if (this._user == null && this._pass == null) {
+    if (this.username == null && this._pass == null) {
       // no-op
     }
-    if (this._user != null && this._pass == null) {
-      parsed.auth = this._user;
+    if (this.username != null && this._pass == null) {
+      parsed.auth = this.username;
     }
-    if (this._user != null && this._pass != null) {
+    if (this.username != null && this._pass != null) {
       parsed.auth = this.username + ':' + this._pass;
     }
     return url.format(parsed);
--- a/src/app/components/auth/basic-auth.service.spec.js	Thu Aug 31 14:54:11 2017 -0400
+++ b/src/app/components/auth/basic-auth.service.spec.js	Fri Sep 01 14:17:02 2017 -0400
@@ -63,14 +63,18 @@
   describe('#login()', () => {
     it('should set logged in status on successful login', done => {
       basicAuthService.login('client', 'client-pwd', () => {
+        cookies.put.should.be.calledWith('session', true, sinon.match.object);
+        cookies.get.withArgs('session').returns(true);
         basicAuthService.status().should.equal(true);
         done();
       });
     });
 
     it('should set username on successful login', done => {
-      should(basicAuthService.username).be.null();
+      should(basicAuthService.username).be.undefined();
       basicAuthService.login('client', 'client-pwd', () => {
+        cookies.put.should.be.calledWith('loggedInUser', 'client');
+        cookies.get.withArgs('loggedInUser').returns('client');
         basicAuthService.username.should.equal('client');
         done();
       });
@@ -78,7 +82,8 @@
 
     it('should not store username in cookies by default', done => {
       basicAuthService.login('client', 'client-pwd', () => {
-        cookies.put.should.not.be.called();
+        cookies.put.should.be.called();
+        cookies.put.should.not.be.calledWith('username');
         done();
       });
     });
@@ -86,8 +91,10 @@
     it('should store username in cookies when set', done => {
       basicAuthService.rememberUser(true);
       basicAuthService.login('client', 'client-pwd', () => {
-        cookies.put.should.be.calledOnce();
+        cookies.put.should.be.calledThrice();
+        cookies.put.should.be.calledWith('loggedInUser', 'client');
         cookies.put.should.be.calledWith('username', 'client');
+        cookies.put.should.be.calledWith('session', true, sinon.match.object);
         done();
       });
     });
@@ -114,8 +121,12 @@
   describe('#logout()', () => {
     it('should set logged out status', done => {
       basicAuthService.login('client', 'client-pwd');
+      cookies.put.should.be.calledWith('session', true, sinon.match.object);
+      cookies.get.withArgs('session').returns(true);
       basicAuthService.status().should.equal(true);
       basicAuthService.logout(() => {
+        cookies.remove.should.be.calledWith('session');
+        cookies.get.withArgs('session').returns(undefined);
         basicAuthService.status().should.equal(false);
         done();
       });
@@ -156,6 +167,8 @@
 
     it('should call success handler if logged in', done => {
       basicAuthService.login('foo', 'bar', () => {
+        cookies.put.should.be.calledWith('session', true, sinon.match.object);
+        cookies.get.withArgs('session').returns(true);
         basicAuthService.refresh().then(done, angular.noop);
       });
     });
@@ -163,6 +176,8 @@
     it('should call error handler if logged out', done => {
       qPromise.callsArg(1);
       basicAuthService.logout();
+      cookies.remove.should.be.calledWith('session');
+      cookies.get.withArgs('session').returns(false);
       basicAuthService.refresh().then(angular.noop, done);
     });
   });
@@ -170,6 +185,7 @@
   describe('#get authHeader()', () => {
     it('should return base64-encoded credentials', done => {
       basicAuthService.login('foo', 'bar', () => {
+        cookies.get.withArgs('loggedInUser').returns('foo');
         basicAuthService.authHeader.should.equal('Basic ' + btoa('foo:bar'));
         done();
       });
@@ -177,12 +193,13 @@
   });
 
   describe('#get username()', () => {
-    it('should be null before login', () => {
-      should(basicAuthService.username).be.null();
+    it('should be undefined before login', () => {
+      should(basicAuthService.username).be.undefined();
     });
 
     it('should return logged in user', done => {
       basicAuthService.login('foo', 'bar', () => {
+        cookies.get.withArgs('loggedInUser').returns('foo');
         basicAuthService.username.should.equal('foo');
         done();
       });
@@ -206,6 +223,7 @@
 
     it('should only add basic auth username when only username provided', done => {
       basicAuthService.login('foo', null, () => {
+        cookies.get.withArgs('loggedInUser').returns('foo');
         basicAuthService.getCommandChannelUrl('http://example.com/').should.equal('http://foo@example.com/');
         done();
       });
@@ -213,6 +231,7 @@
 
     it('should add basic auth username and password when provided', done => {
       basicAuthService.login('foo', 'bar', () => {
+        cookies.get.withArgs('loggedInUser').returns('foo');
         basicAuthService.getCommandChannelUrl('http://example.com/').should.equal('http://foo:bar@example.com/');
         done();
       });