diff --git a/dist/redux-oidc.js b/dist/redux-oidc.js index 1f5affc..6050466 100644 --- a/dist/redux-oidc.js +++ b/dist/redux-oidc.js @@ -1 +1 @@ -!function(e,r){if("object"==typeof exports&&"object"==typeof module)module.exports=r(require("react"),require("immutable"),require("oidc-client"));else if("function"==typeof define&&define.amd)define(["react","immutable","oidc-client"],r);else{var t="object"==typeof exports?r(require("react"),require("immutable"),require("oidc-client")):r(e.react,e.immutable,e["oidc-client"]);for(var n in t)("object"==typeof exports?exports:e)[n]=t[n]}}(this,function(e,r,t){return function(e){function r(n){if(t[n])return t[n].exports;var o=t[n]={exports:{},id:n,loaded:!1};return e[n].call(o.exports,o,o.exports,r),o.loaded=!0,o.exports}var t={};return r.m=e,r.c=t,r.p="",r(0)}([function(e,r,t){e.exports=t(8)},function(e,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.STORAGE_KEY="oidc.expired",r.USER_EXPIRED="redux-oidc/USER_EXPIRED",r.REDIRECT_SUCCESS="redux-oidc/REDIRECT_SUCCESS",r.USER_LOADED="redux-oidc/USER_LOADED",r.SILENT_RENEW_ERROR="redux-oidc/SILENT_RENEW_ERROR",r.SESSION_TERMINATED="redux-oidc/SESSION_TERMINATED",r.USER_EXPIRING="redux-oidc/USER_EXPIRING",r.USER_FOUND="redux-oidc/USER_FOUND"},function(e,r,t){"use strict";function n(){return{type:a.USER_EXPIRED}}function o(e){return{type:a.REDIRECT_SUCCESS,payload:e}}function i(e){return{type:a.USER_FOUND,payload:e}}function u(e){return{type:a.SILENT_RENEW_ERROR,payload:e}}function s(){return{type:a.SESSION_TERMINATED}}function c(){return{type:a.USER_EXPIRING}}Object.defineProperty(r,"__esModule",{value:!0}),r.userExpired=n,r.redirectSuccess=o,r.userFound=i,r.silentRenewError=u,r.sessionTerminated=s,r.userExpiring=c;var a=t(1)},function(e,r,t){"use strict";function n(e){return new o.UserManager(e)}Object.defineProperty(r,"__esModule",{value:!0}),r["default"]=n;var o=t(13)},function(e,r){e.exports=require("react")},function(e,r,t){"use strict";function n(e){return e&&e.__esModule?e:{"default":e}}function o(e,r){if(!(e instanceof r))throw new TypeError("Cannot call a class as a function")}function i(e,r){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!r||"object"!=typeof r&&"function"!=typeof r?e:r}function u(e,r){if("function"!=typeof r&&null!==r)throw new TypeError("Super expression must either be null or a function, not "+typeof r);e.prototype=Object.create(r&&r.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),r&&(Object.setPrototypeOf?Object.setPrototypeOf(e,r):e.__proto__=r)}Object.defineProperty(r,"__esModule",{value:!0});var s=function(){function e(e,r){for(var t=0;t true; } + // store the provided user here + let providedUser = existingUser; + + // check for an interrupted login attempt and clear localStorage if necessary + if (localStorage.getItem(STORAGE_KEY) && window.location.pathname.indexOf(callbackRoute) === -1) { + localStorage.removeItem(STORAGE_KEY); + + // clear out any temporary data left behind by the userManager but keep the valid user data if present + for (const key in localStorage) { + if (key.indexOf('oidc') !== -1 && key.indexOf('oidc.user') === -1) { + localStorage.removeItem(key); + } + } + } + // the middleware return (store) => (next) => (action) => { + // dispatch a manually loaded user on startup + if (providedUser && !providedUser.expired) { + next(userFound(providedUser)); + providedUser = null; + } + if (shouldValidate(store.getState(), action) && !localStorage.getItem(STORAGE_KEY)) { // IF: validation should occur... if (!storedUser || storedUser.expired) { @@ -65,7 +90,7 @@ export default function createOidcMiddleware(userManager, shouldValidate, trigge .then((user) => getUserSuccessCallback(next, userManager, user, triggerAuthFlow, action)) .catch(getUserErrorCallback); } else { - // ELSE: user has been found and NOT is expired... + // ELSE: user has been found and is NOT expired... return next(action); } } else { diff --git a/tests/oidcMiddleware.test.js b/tests/oidcMiddleware.test.js index 213c6cc..d04a6ac 100644 --- a/tests/oidcMiddleware.test.js +++ b/tests/oidcMiddleware.test.js @@ -24,10 +24,12 @@ describe('createOidcMiddleware()', () => { let action; let stateMock; let href = 'http://some.url.com'; + let pathname = '/callback'; + let callbackRoute = '/callback'; beforeEach(() => { windowMock = { - location: { href } + location: { href, pathname } }; oldWindow = window; window = windowMock; @@ -81,7 +83,7 @@ describe('createOidcMiddleware()', () => { }); it('should return the correct middleware function', () => { - const middleware = createOidcMiddleware(userManagerMock); + const middleware = createOidcMiddleware(userManagerMock, null, null, callbackRoute); expect(typeof(middleware)).toEqual('function'); expect(middleware.length).toEqual(1); @@ -97,7 +99,7 @@ describe('createOidcMiddleware()', () => { it('should call the shouldValidate() function with the redux state and dispatched action', () => { const shouldValidate = sinon.stub(); - createOidcMiddleware(userManagerMock, shouldValidate)(storeMock)(nextStub)(action); + createOidcMiddleware(userManagerMock, shouldValidate, null, callbackRoute)(storeMock)(nextStub)(action); expect(getStateStub.called).toEqual(true); expect(shouldValidate.calledWith(stateMock, action)).toEqual(true); @@ -105,7 +107,7 @@ describe('createOidcMiddleware()', () => { it('should trigger the validation when shouldValidate() returns true', () => { const shouldValidate = sinon.stub().returns(true); - const result = createOidcMiddleware(userManagerMock, shouldValidate)(storeMock)(nextStub)(action); + createOidcMiddleware(userManagerMock, shouldValidate, null, callbackRoute)(storeMock)(nextStub)(action); expect(setItemStub.calledWith(STORAGE_KEY, true)).toEqual(true); expect(getUserStub.called).toEqual(true); @@ -115,7 +117,7 @@ describe('createOidcMiddleware()', () => { it('should not trigger the validation when shouldValidate() returns false', () => { const shouldValidate = sinon.stub().returns(false); - createOidcMiddleware(userManagerMock, shouldValidate)(storeMock)(nextStub)(action); + createOidcMiddleware(userManagerMock, shouldValidate, null, callbackRoute)(storeMock)(nextStub)(action); expect(setItemStub.called).toEqual(false); expect(getUserStub.called).toEqual(false); @@ -126,7 +128,7 @@ describe('createOidcMiddleware()', () => { it('should not trigger validation when the local storage key is set', () => { const shouldValidate = sinon.stub().returns(true); getItemStub.returns(true); - const result = createOidcMiddleware(userManagerMock, shouldValidate)(storeMock)(nextStub)(action); + const result = createOidcMiddleware(userManagerMock, shouldValidate, null, callbackRoute)(storeMock)(nextStub)(action); expect(setItemStub.called).toEqual(false); expect(getUserStub.called).toEqual(false); @@ -140,7 +142,7 @@ describe('createOidcMiddleware()', () => { setStoredUser({ some: 'user' }); const shouldValidate = sinon.stub().returns(true); - const result = createOidcMiddleware(userManagerMock, shouldValidate)(storeMock)(nextStub)(action); + const result = createOidcMiddleware(userManagerMock, shouldValidate, null, callbackRoute)(storeMock)(nextStub)(action); expect(setItemStub.called).toEqual(false); expect(getUserStub.called).toEqual(false); expect(thenStub.called).toEqual(false); @@ -153,7 +155,7 @@ describe('createOidcMiddleware()', () => { setStoredUser({ expired: true }); const shouldValidate = sinon.stub().returns(true); - const result = createOidcMiddleware(userManagerMock, shouldValidate)(storeMock)(nextStub)(action); + const result = createOidcMiddleware(userManagerMock, shouldValidate, null, callbackRoute)(storeMock)(nextStub)(action); expect(setItemStub.called).toEqual(true); expect(getUserStub.called).toEqual(true); expect(thenStub.called).toEqual(true); @@ -213,4 +215,66 @@ describe('createOidcMiddleware()', () => { expect(() => getUserErrorCallback(error)).toThrow(/error/); expect(removeItemStub.calledWith(STORAGE_KEY)).toEqual(true); }); + + it('should dispatch a manually loaded user if it has been provided', () => { + const loadedUser = { some: 'user' }; + createOidcMiddleware(userManagerMock, null, null, callbackRoute, loadedUser)(storeMock)(nextStub)(action); + + expect(nextStub.calledWith(userFound(loadedUser))).toEqual(true); + }); + + it('should not dispatch a manually loaded user if it has not been provided', () => { + createOidcMiddleware(userManagerMock, null, null, callbackRoute)(storeMock)(nextStub)(action); + expect(nextStub.calledWith(userFound(undefined))).toEqual(false); + }); + + it('should not dispatch a manually loaded user if it is expired', () => { + const loadedUser = { + some: 'user', + expired: true + }; + createOidcMiddleware(userManagerMock, null, null, callbackRoute)(storeMock)(nextStub)(action); + + expect(nextStub.calledWith(userFound(loadedUser))).toEqual(false); + }); + + it('should clear localStorage when after a failed login attempt but keep the valid user entry', () => { + localStorageMock = { + STORAGE_KEY, + 'oidc.something': 'something', + getItem: getItemStub, + removeItem: removeItemStub, + setItem: setItemStub, + 'oidc.user': 'some user data' + }; + localStorage = localStorageMock; + + windowMock = { + location: { + pathname: '/someRoute', + href + } + }; + window = windowMock; + getItemStub.returns(true); + + createOidcMiddleware(userManagerMock, null, null, callbackRoute)(storeMock)(nextStub)(action); + + expect(removeItemStub.calledWith(STORAGE_KEY)).toEqual(true); + expect(removeItemStub.calledWith('oidc.something')).toEqual(true); + expect(removeItemStub.neverCalledWith('oidc.user')).toEqual(true); + }); + + it('should not clear localStorage when the login attempt has been successfully redirected', () => { + localStorageMock = { + STORAGE_KEY, + 'oidc.something': 'something' + }; + getItemStub.returns(true); + + createOidcMiddleware(userManagerMock, null, null, callbackRoute)(storeMock)(nextStub)(action); + + expect(removeItemStub.neverCalledWith(STORAGE_KEY)).toEqual(true); + expect(removeItemStub.neverCalledWith('oidc.something')).toEqual(true); + }); });