LP#1527694 Webstaff egHatch supports 'LoginSession' storage
authorBill Erickson <berickxx@gmail.com>
Fri, 5 Aug 2016 16:39:54 +0000 (12:39 -0400)
committerMike Rylander <mrylander@gmail.com>
Thu, 18 Aug 2016 19:34:23 +0000 (15:34 -0400)
Adds support for a class of cached value (AKA "LoginSession" items)
that are cleared when either the user logs out or the browser is closed.
Values are stored as cookies.

Authentication tokens and "retrieve last patron" data are now stored as
"LoginSession" items.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Mike Rylander <mrylander@gmail.com>
Open-ILS/web/js/ui/default/staff/circ/patron/app.js
Open-ILS/web/js/ui/default/staff/services/auth.js
Open-ILS/web/js/ui/default/staff/services/hatch.js

index d31afa2..fe13939 100644 (file)
@@ -207,8 +207,8 @@ angular.module('egPatronApp', ['ngRoute', 'ui.bootstrap',
  * Patron service
  */
 .factory('patronSvc',
-       ['$q','$timeout','$location','$cookies','egCore','egUser','$locale',
-function($q , $timeout , $location , $cookies , egCore,  egUser , $locale) {
+       ['$q','$timeout','$location','egCore','egUser','$locale',
+function($q , $timeout , $location , egCore,  egUser , $locale) {
 
     var service = {
         // cached patron search results
@@ -271,7 +271,7 @@ function($q , $timeout , $location , $cookies , egCore,  egUser , $locale) {
 
         // when loading a new patron, update the last patron setting
         if (!service.current || service.current.id() != user_id)
-            $cookies.put('eg.circ.last_patron', user_id);
+            egCore.hatch.setLoginSessionItem('eg.circ.last_patron', user_id);
 
         // avoid running multiple retrievals for the same patron, which
         // can happen during dbl-click by maintaining a single running
@@ -1553,10 +1553,10 @@ function($scope,  $routeParams , $q , egCore , patronSvc) {
 }])
 
 .controller('PatronFetchLastCtrl',
-       ['$scope','$location','$cookies','egCore',
-function($scope , $location , $cookies, egCore) {
+       ['$scope','$location','egCore',
+function($scope , $location , egCore) {
 
-    var id = $cookies.get('eg.circ.last_patron');
+    var id = egCore.hatch.getLoginSessionItem('eg.circ.last_patron');
     if (id) return $location.path('/circ/patron/' + id + '/checkout');
 
     $scope.no_last = true;
index 75ae9ba..a677ff4 100644 (file)
@@ -17,12 +17,12 @@ function($q , $timeout , $rootScope , egNet , egHatch) {
 
         // the currently active auth token string
         token : function() {
-            return egHatch.getLocalItem('eg.auth.token');
+            return egHatch.getLoginSessionItem('eg.auth.token');
         },
 
         // authtime in seconds
         authtime : function() {
-            return egHatch.getLocalItem('eg.auth.time');
+            return egHatch.getLoginSessionItem('eg.auth.time');
         },
 
         // the currently active workstation name
@@ -68,7 +68,7 @@ function($q , $timeout , $rootScope , egNet , egHatch) {
                     }
                 } else {
                     // authtoken test failed
-                    egHatch.removeLocalItem('eg.auth.token');
+                    egHatch.clearLoginSessionItems();
                     deferred.reject(); 
                 }
             });
@@ -87,6 +87,17 @@ function($q , $timeout , $rootScope , egNet , egHatch) {
      */
     service.login = function(args) {
         var deferred = $q.defer();
+
+        // Clear old LoginSession keys that were left in localStorage
+        // when the previous user closed the browser without logging
+        // out.  Under normal circumstance, LoginSession data would
+        // have been cleared by now, either during logout or cookie
+        // expiration.  But, if for some reason the user manually
+        // removed the auth token cookie w/o closing the browser
+        // (say, for testing), then this serves double duty to ensure
+        // LoginSession data cannot persist across logins.
+        egHatch.clearLoginSessionItems();
+
         egNet.request(
             'open-ils.auth',
             'open-ils.auth.authenticate.init', args.username).then(
@@ -99,9 +110,9 @@ function($q , $timeout , $rootScope , egNet , egHatch) {
                         if (evt.textcode == 'SUCCESS') {
                             service.ws = args.workstation; 
                             service.poll();
-                            egHatch.setLocalItem(
+                            egHatch.setLoginSessionItem(
                                 'eg.auth.token', evt.payload.authtoken);
-                            egHatch.setLocalItem(
+                            egHatch.setLoginSessionItem(
                                 'eg.auth.time', evt.payload.authtime);
                             deferred.resolve();
                         } else {
@@ -157,8 +168,7 @@ function($q , $timeout , $rootScope , egNet , egHatch) {
                 'open-ils.auth', 
                 'open-ils.auth.session.delete', 
                 service.token()); // fire and forget
-            egHatch.removeLocalItem('eg.auth.token');
-            egHatch.removeLocalItem('eg.auth.time');
+            egHatch.clearLoginSessionItems();
         }
         service._user = null;
     };
index 0b8935b..ff517fc 100644 (file)
@@ -25,8 +25,8 @@
 angular.module('egCoreMod')
 
 .factory('egHatch',
-           ['$q','$window','$timeout','$interpolate','$http',
-    function($q , $window , $timeout , $interpolate , $http) {
+           ['$q','$window','$timeout','$interpolate','$http','$cookies',
+    function($q , $window , $timeout , $interpolate , $http , $cookies) {
 
     var service = {};
     service.msgId = 0;
@@ -301,14 +301,25 @@ angular.module('egCoreMod')
         return JSON.parse(val);
     }
 
+    service.getLoginSessionItem = function(key) {
+        var val = $cookies.get(key);
+        if (val == null) return;
+        return JSON.parse(val);
+    }
+
     service.getSessionItem = function(key) {
         var val = $window.sessionStorage.getItem(key);
         if (val == null) return;
         return JSON.parse(val);
     }
 
+    /**
+     * @param tmp bool Store the value as a session cookie only.  
+     * tmp values are removed during logout or browser close.
+     */
     service.setItem = function(key, value) {
         var str = JSON.stringify(value);
+
         return service.setRemoteItem(key, str)['catch'](
             function(msg) {
                 if (service.hatchRequired()) {
@@ -330,7 +341,8 @@ angular.module('egCoreMod')
         });
     }
 
-    // Set the value for the given key
+    // Set the value for the given key.
+    // "Local" items persist indefinitely.
     // If the value is raw, pass it as 'value'.  If it was
     // externally JSONified, pass it via jsonified.
     service.setLocalItem = function(key, value, jsonified) {
@@ -339,6 +351,23 @@ angular.module('egCoreMod')
         $window.localStorage.setItem(key, jsonified);
     }
 
+    // Set the value for the given key.  
+    // "LoginSession" items are removed when the user logs out or the 
+    // browser is closed.
+    // If the value is raw, pass it as 'value'.  If it was
+    // externally JSONified, pass it via jsonified.
+    service.setLoginSessionItem = function(key, value, jsonified) {
+        service.addLoginSessionKey(key);
+        if (jsonified === undefined ) 
+            jsonified = JSON.stringify(value);
+        $cookies.put(key, jsonified);
+    }
+
+    // Set the value for the given key.  
+    // "Session" items are browser tab-specific and are removed when the
+    // tab is closed.
+    // If the value is raw, pass it as 'value'.  If it was
+    // externally JSONified, pass it via jsonified.
     service.setSessionItem = function(key, value, jsonified) {
         if (jsonified === undefined ) 
             jsonified = JSON.stringify(value);
@@ -403,10 +432,27 @@ angular.module('egCoreMod')
         $window.localStorage.removeItem(key);
     }
 
+    service.removeLoginSessionItem = function(key) {
+        service.removeLoginSessionKey(key);
+        $cookies.remove(key);
+    }
+
     service.removeSessionItem = function(key) {
         $window.sessionStorage.removeItem(key);
     }
 
+    /**
+     * Remove all "LoginSession" items.
+     */
+    service.clearLoginSessionItems = function() {
+        angular.forEach(service.getLoginSessionKeys(), function(key) {
+            service.removeLoginSessionItem(key);
+        });
+
+        // remove the keys cache.
+        service.removeLocalItem('eg.hatch.login_keys');
+    }
+
     // if set, prefix limits the return set to keys starting with 'prefix'
     service.getKeys = function(prefix) {
         return service.getRemoteKeys(prefix)['catch'](
@@ -439,6 +485,42 @@ angular.module('egCoreMod')
         return keys;
     }
 
+
+    /**
+     * Array of "LoginSession" keys.
+     * Note we have to store these as "Local" items so browser tabs can
+     * share them.  We could store them as cookies, but it's more data
+     * that has to go back/forth to the server.  A "LoginSession" key name is
+     * not private, though, so it's OK if they are left in localStorage
+     * until the next login.
+     */
+    service.getLoginSessionKeys = function(prefix) {
+        var keys = [];
+        var idx = 0;
+        var login_keys = service.getLocalItem('eg.hatch.login_keys') || [];
+        angular.forEach(login_keys, function(k) {
+            // key prefix match test
+            if (prefix && k.substr(0, prefix.length) != prefix) return;
+            keys.push(k);
+        });
+        return keys;
+    }
+
+    service.addLoginSessionKey = function(key) {
+        var keys = service.getLoginSessionKeys();
+        if (keys.indexOf(key) < 0) {
+            keys.push(key);
+            service.setLocalItem('eg.hatch.login_keys', keys);
+        }
+    }
+
+    service.removeLoginSessionKey = function(key) {
+        var keys = service.getLoginSessionKeys().filter(function(k) {
+            return k != key;
+        });
+        service.setLocalItem('eg.hatch.login_keys', keys);
+    }
+
     return service;
 }])