From 919bcf129d6f8493f4dbb0224dae614cb9afa15c Mon Sep 17 00:00:00 2001 From: Philipp Pracht Date: Fri, 29 Nov 2024 10:19:52 +0100 Subject: [PATCH] Auth exp check reset (#4028) * option to reset expiration intervals for oauth2 * prettier * Add unit tests for auth layer * prettier --------- Co-authored-by: Anna Milewska Co-authored-by: Waldemar Mazurek --- core/src/core-api/auth.js | 7 +++- core/src/services/auth-layer.js | 20 ++++++---- core/test/services/auth-layer.spec.js | 55 +++++++++++++++++++++++++++ plugins/auth/src/auth-oauth2/index.js | 15 +++++--- 4 files changed, 82 insertions(+), 15 deletions(-) diff --git a/core/src/core-api/auth.js b/core/src/core-api/auth.js index 69b0868b35..f5d419f584 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 1a0e185b25..56b13bf5f5 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/core/test/services/auth-layer.spec.js b/core/test/services/auth-layer.spec.js index 1a07a516f5..a68471204a 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); + }); + }); }); }); diff --git a/plugins/auth/src/auth-oauth2/index.js b/plugins/auth/src/auth-oauth2/index.js index 91570d5ff7..8bc94f0fa1 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() {