LP#1724915 Webstaff auth timeout works w/ multiple tabs
authorBill Erickson <berickxx@gmail.com>
Thu, 19 Oct 2017 18:46:01 +0000 (14:46 -0400)
committerMike Rylander <mrylander@gmail.com>
Thu, 9 Nov 2017 14:42:54 +0000 (09:42 -0500)
Adds a new API parameter to open-ils.session.retrieve which allows the
session to be fetched without extending the auth session timeout.

Teach the browser client to use the new API.

Teach the browser client to notify all webstaff tabs when a logout event
has occurred, so every tab can immediately log out.

To test
-------
[0] Apply the patch.
[1] Log in the web staff client, then open a new window/tab
    and navigate to the web staff client.
[2] Log out of the web staff client in one window. Verify that
    the second window automatically refreshes and goes to the
    login page.
[3] Set a low staff idle timeout (optional).
[4] Repeat step 1, then wait for the timeout. Verify that the
    staff client is logged out in both windows.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>
Signed-off-by: Mike Rylander <mrylander@gmail.com>

Open-ILS/src/c-apps/oils_auth.c
Open-ILS/web/js/ui/default/staff/services/auth.js
Open-ILS/web/js/ui/default/staff/services/startup.js

index 4afb36c..b87d1ca 100644 (file)
@@ -98,10 +98,13 @@ int osrfAppInitialize() {
                MODULENAME,
                "open-ils.auth.session.retrieve",
                "oilsAuthSessionRetrieve",
-               "Pass in the auth token and this retrieves the user object.  The auth "
-               "timeout is reset when this call is made "
+               "Pass in the auth token and this retrieves the user object.  By "
+               "default, the auth timeout is reset when this call is made.  If "
+               "a second non-zero parameter is passed, the auth timeout info is "
+               "returned to the caller along with the user object.  If a 3rd "
+               "non-zero parameter is passed, the auth timeout will not be reset."
                "Returns the user object (password blanked) for the given login session "
-               "PARAMS( authToken )", 1, 0 );
+               "PARAMS( authToken[, returnTime[, doNotResetSession]] )", 1, 0 );
 
        osrfAppRegisterMethod(
                MODULENAME,
@@ -1206,6 +1209,7 @@ int oilsAuthResetTimeout( osrfMethodContext* ctx ) {
 int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
        OSRF_METHOD_VERIFY_CONTEXT(ctx);
     bool returnFull = false;
+    bool noTimeoutReset = false;
 
        const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
 
@@ -1214,6 +1218,14 @@ int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
         const char* rt = jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
         if(rt && strcmp(rt, "0") != 0) 
             returnFull = true;
+
+        if (ctx->params->size > 2) {
+            // Avoid resetting the auth session timeout.
+            const char* noReset = 
+                jsonObjectGetString(jsonObjectGetIndex(ctx->params, 2));
+            if (noReset && strcmp(noReset, "0") != 0) 
+                noTimeoutReset = true;
+        }
     }
 
        jsonObject* cacheObj = NULL;
@@ -1222,7 +1234,8 @@ int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
        if( authToken ){
 
                // Reset the timeout to keep the session alive
-               evt = _oilsAuthResetTimeout(authToken, 0);
+        if (!noTimeoutReset) 
+                   evt = _oilsAuthResetTimeout(authToken, 0);
 
                if( evt && strcmp(evt->event, OILS_EVENT_SUCCESS) ) {
                        osrfAppRespondComplete( ctx, oilsEventToJSON( evt ));    // can't reset timeout
index 3ee2296..8912c78 100644 (file)
@@ -50,7 +50,10 @@ function($q , $timeout , $rootScope , $window , $location , egNet , egHatch) {
         // For ws_ou or wsid(), see egAuth.user().ws_ou(), etc.
         workstation : function() {
             return this.ws;
-        }
+        },
+
+        // Listen for logout events in other tabs
+        authChannel : new BroadcastChannel('eg.auth')
     };
 
     /* Returns a promise, which is resolved if valid
@@ -268,19 +271,33 @@ function($q , $timeout , $rootScope , $window , $location , egNet , egHatch) {
      * Does that setting serve a purpose in a browser environment?
      */
     service.poll = function() {
-        if (!service.authtime()) return;
+
+        if (!service.authChannel.onmessage) {
+            // Now that we have an authtoken, listen for logout events 
+            // initiated by other tabs.
+            service.authChannel.onmessage = function(e) {
+                if (e.data.action == 'logout') {
+                    $rootScope.$broadcast(
+                        'egAuthExpired', {startedElsewhere : true});
+                }
+            }
+        }
 
         $timeout(
             function() {
-                if (!service.authtime()) return;
                 egNet.request(                                                     
                     'open-ils.auth',                                               
-                    'open-ils.auth.session.retrieve', service.token())   
-                .then(function(user) {
+                    'open-ils.auth.session.retrieve', 
+                    service.token(),
+                    0, // return extra auth details, unneeded here.
+                    1  // avoid extending the auth timeout
+                ).then(function(user) {
                     if (user && user.classname) { // all good
                         service.poll();
                     } else {
-                        $rootScope.$broadcast('egAuthExpired') 
+                        // NOTE: we should never get here, since egNet
+                        // filters responses for NO_SESSION events.
+                        $rootScope.$broadcast('egAuthExpired');
                     }
                 })
             },
@@ -290,7 +307,13 @@ function($q , $timeout , $rootScope , $window , $location , egNet , egHatch) {
         );
     }
 
-    service.logout = function() {
+    service.logout = function(broadcast) {
+
+        if (broadcast) {
+            // Tell the other tabs to shut it all down.
+            service.authChannel.postMessage({action : 'logout'});
+        }
+
         if (service.token()) {
             egNet.request(
                 'open-ils.auth', 
index 7792eb2..d947d75 100644 (file)
@@ -52,11 +52,16 @@ function($q,  $rootScope,  $location,  $window,  egIDL,  egAuth,  egEnv , egOrg
 
     // returns true if we are staying on the current page
     // false if we are redirecting to login
-    service.expiredAuthHandler = function() {
+    service.expiredAuthHandler = function(data) {
         if (lf.isOffline) return true; // Only set by the offline UI
 
         console.debug('egStartup.expiredAuthHandler()');
-        egAuth.logout(); // clean up
+
+        // Only notify other tabs the auth session has expired 
+        // when this tab was the first tab to know it.
+        var broadcast = !(data && data.startedElsewhere);
+
+        egAuth.logout(broadcast); // clean up
 
         // no need to redirect if we're on the /login page
         if ($location.path() == '/login') return true;