Repository: SAP/luigi (js)

Issue:
Option to reset expiration(-soon) check interval
in oauth2 plugin, when the onAuthExpireSoon event is fired, the check interval is cleared without an easy option to set it again, because the initial assumption was that a full auth redirect flow will eventually take place and thus the whole app state is reset anyway. There are now customer use cases where a new token can be obtained while staying at the page, which makes it necessary to establish new check intervals.

Pull request description:
Auth exp check reset
fixes #4007 

Patch:
diff --git a/core/src/core-api/auth.js b/core/src/core-api/auth.js
index 69b0868b..f5d419f5 100644
--- a/core/src/core-api/auth.js
+++ b/core/src/core-api/auth.js
@@ -123,7 +123,7 @@ class LuigiAuth {
        * @param {AuthData} data - new auth data object
        * @example Luigi.auth().store.setAuthData(data)
        */
-      setAuthData: data => AuthStoreSvc.setAuthData(data),
+      setAuthData: (data) => AuthStoreSvc.setAuthData(data),
       /**
        * Clears authorization data from store
        * @memberof AuthorizationStore
@@ -135,7 +135,10 @@ class LuigiAuth {
        * @memberof AuthorizationStore
        * @example Luigi.auth().store.setNewlyAuthorized()
        */
-      setNewlyAuthorized: () => AuthStoreSvc.setNewlyAuthorized()
+      setNewlyAuthorized: () => {
+        AuthStoreSvc.setNewlyAuthorized();
+        AuthLayerSvc.resetExpirationChecks();
+      }
     };
   }
 }
diff --git a/core/src/services/auth-layer.js b/core/src/services/auth-layer.js
index 1a0e185b..56b13bf5 100644
--- a/core/src/services/auth-layer.js
+++ b/core/src/services/auth-layer.js
@@ -51,11 +51,11 @@ class AuthLayerSvcClass {
     this.idpProviderInstance = this.getIdpProviderInstance(idpProviderName, idpProviderSettings);
     if (GenericHelpers.isPromise(this.idpProviderInstance)) {
       return this.idpProviderInstance
-        .then(resolved => {
+        .then((resolved) => {
           this.idpProviderInstance = resolved;
           return this.checkAuth(idpProviderSettings);
         })
-        .catch(err => {
+        .catch((err) => {
           const errorMsg = `Error: ${err.message || err}`;
           console.error(errorMsg, err.message && err);
           LuigiConfig.setErrorMessage(errorMsg);
@@ -88,13 +88,13 @@ class AuthLayerSvcClass {
     }
 
     if (this.idpProviderInstance.settings && GenericHelpers.isFunction(this.idpProviderInstance.settings.userInfoFn)) {
-      this.idpProviderInstance.settings.userInfoFn(this.idpProviderInstance.settings, authData).then(userInfo => {
+      this.idpProviderInstance.settings.userInfoFn(this.idpProviderInstance.settings, authData).then((userInfo) => {
         this.setUserInfo(userInfo);
         this.setLoggedIn(true);
       });
     } else {
       if (GenericHelpers.isFunction(this.idpProviderInstance.userInfo)) {
-        this.idpProviderInstance.userInfo(idpProviderSettings).then(userInfo => {
+        this.idpProviderInstance.userInfo(idpProviderSettings).then((userInfo) => {
           this.setUserInfo(userInfo);
           this.setLoggedIn(true);
         });
@@ -121,7 +121,7 @@ class AuthLayerSvcClass {
 
   async startAuthorization() {
     if (this.idpProviderInstance) {
-      return this.idpProviderInstance.login().then(res => {
+      return this.idpProviderInstance.login().then((res) => {
         AuthStoreSvc.setNewlyAuthorized();
         if (res) {
           // TODO: is not required for secure usecases, only if auth is done within core.
@@ -135,7 +135,7 @@ class AuthLayerSvcClass {
 
   logout() {
     const authData = AuthHelpers.getStoredAuthData();
-    const logoutCallback = async redirectUrl => {
+    const logoutCallback = async (redirectUrl) => {
       await LuigiAuth.handleAuthEvent('onLogout', this.idpProviderInstance.settings, undefined, redirectUrl);
       AuthStoreSvc.removeAuthData();
     };
@@ -162,7 +162,7 @@ class AuthLayerSvcClass {
     const idpProvider = GenericHelpers.getConfigValueFromObject(idpProviderSettings, 'idpProvider');
     if (idpProvider) {
       const customIdpInstance = await new idpProvider(idpProviderSettings);
-      ['login'].forEach(requiredFnName => {
+      ['login'].forEach((requiredFnName) => {
         if (!GenericHelpers.isFunction(customIdpInstance[requiredFnName])) {
           throw this.IdpProviderException(
             `${requiredFnName} function does not exist in custom IDP Provider ${idpProviderName}`
@@ -190,6 +190,12 @@ class AuthLayerSvcClass {
       this.idpProviderInstance.unload();
     }
   }
+
+  resetExpirationChecks() {
+    if (this.idpProviderInstance && GenericHelpers.isFunction(this.idpProviderInstance.resetExpirationChecks)) {
+      this.idpProviderInstance.resetExpirationChecks();
+    }
+  }
 }
 
 export const AuthLayerSvc = new AuthLayerSvcClass();
diff --git a/plugins/auth/src/auth-oauth2/index.js b/plugins/auth/src/auth-oauth2/index.js
index 91570d5f..8bc94f0f 100644
--- a/plugins/auth/src/auth-oauth2/index.js
+++ b/plugins/auth/src/auth-oauth2/index.js
@@ -25,10 +25,7 @@ export default class oAuth2ImplicitGrant {
   }
 
   parseIdToken(token) {
-    const payload = token
-      .split('.')[1]
-      .replace(/-/g, '+')
-      .replace(/_/g, '/');
+    const payload = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
     return JSON.parse(window.atob(payload));
   }
 
@@ -88,7 +85,7 @@ export default class oAuth2ImplicitGrant {
       // TODO: We're not resolving the promise at any time,
       // since oauth2 is redirecting off the page
       // maybe it is possible to catch errors
-      document.querySelector('form#signIn').addEventListener('load', e => {
+      document.querySelector('form#signIn').addEventListener('load', (e) => {
         console.info('load, e', e, this);
       });
     });
@@ -149,7 +146,13 @@ export default class oAuth2ImplicitGrant {
     const crypto = window.crypto;
     const random = Array.from(crypto.getRandomValues(new Uint8Array(20)));
 
-    return random.map(x => validChars[x % validChars.length]).join('');
+    return random.map((x) => validChars[x % validChars.length]).join('');
+  }
+
+  resetExpirationChecks() {
+    this.unload();
+    this.setTokenExpirationAction();
+    this.setTokenExpireSoonAction();
   }
 
   unload() {


Test patch:
diff --git a/core/test/services/auth-layer.spec.js b/core/test/services/auth-layer.spec.js
index 1a07a516..a6847120 100644
--- a/core/test/services/auth-layer.spec.js
+++ b/core/test/services/auth-layer.spec.js
@@ -496,5 +496,60 @@ describe('AuthLayer', () => {
         });
       });
     });
+
+    describe('resetExpirationChecks', () => {
+      let resetExpirationChecksStub;
+
+      beforeEach(() => {
+        resetExpirationChecksStub = sinon.stub();
+        AuthLayerSvc.idpProviderInstance = { resetExpirationChecks: resetExpirationChecksStub };
+      });
+
+      afterEach(() => {
+        sinon.restore();
+      });
+
+      it('should call resetExpirationChecks once when idpProviderInstance is defined', () => {
+        AuthLayerSvc.resetExpirationChecks();
+        sinon.assert.calledOnce(resetExpirationChecksStub);
+      });
+
+      it('should not call resetExpirationChecks when idpProviderInstance is undefined', () => {
+        AuthLayerSvc.idpProviderInstance = undefined;
+        AuthLayerSvc.resetExpirationChecks();
+        sinon.assert.notCalled(resetExpirationChecksStub);
+      });
+
+      it('should call resetExpirationChecks multiple times for consecutive invocations', () => {
+        AuthLayerSvc.resetExpirationChecks();
+        AuthLayerSvc.resetExpirationChecks();
+        sinon.assert.calledTwice(resetExpirationChecksStub);
+      });
+
+      it('should  handle errors thrown by resetExpirationChecks', () => {
+        const error = new Error('Test Error');
+        resetExpirationChecksStub.throws(error);
+        try {
+          AuthLayerSvc.resetExpirationChecks();
+          assert.fail('Expected error to be thrown');
+        } catch (e) {
+          assert.strictEqual(e.message, 'Test Error');
+        }
+      });
+
+      it('should invoke resetExpirationChecks independently for each idpProviderInstance', () => {
+        const resetExpirationChecksStub1 = sinon.stub();
+        const resetExpirationChecksStub2 = sinon.stub();
+
+        AuthLayerSvc.idpProviderInstance = { resetExpirationChecks: resetExpirationChecksStub1 };
+        AuthLayerSvc.resetExpirationChecks();
+
+        AuthLayerSvc.idpProviderInstance = { resetExpirationChecks: resetExpirationChecksStub2 };
+        AuthLayerSvc.resetExpirationChecks();
+
+        sinon.assert.calledOnce(resetExpirationChecksStub1);
+        sinon.assert.calledOnce(resetExpirationChecksStub2);
+      });
+    });
   });
 });