From 23ba613254ccd21a432407faadf594dfa85e7740 Mon Sep 17 00:00:00 2001 From: Steve Hobbs Date: Mon, 20 Jan 2020 17:45:44 +0000 Subject: [PATCH] Wrapped InMemoryCache implementation in a closure --- __tests__/cache.test.ts | 85 ++++++++++++++++++++++++++++------------- __tests__/index.test.ts | 22 ++++++++--- src/Auth0Client.ts | 2 +- src/cache.ts | 54 ++++++++++++++------------ 4 files changed, 104 insertions(+), 59 deletions(-) diff --git a/__tests__/cache.test.ts b/__tests__/cache.test.ts index 59c98ea92..e6ec2018f 100644 --- a/__tests__/cache.test.ts +++ b/__tests__/cache.test.ts @@ -1,13 +1,13 @@ -import { InMemoryCache, LocalStorageCache } from '../src/cache'; +import { InMemoryCache, LocalStorageCache, ICache } from '../src/cache'; const nowSeconds = () => Math.floor(Date.now() / 1000); const dayInSeconds = 86400; describe('InMemoryCache', () => { - let cache: InMemoryCache; + let cache: ICache; beforeEach(() => { - cache = new InMemoryCache(); + cache = new InMemoryCache().enclosedCache; jest.useFakeTimers(); }); @@ -15,12 +15,16 @@ describe('InMemoryCache', () => { it('returns undefined when there is no data', () => { expect( - cache.get({ client_id: 'test-client', audience: 'a', scope: 's' }) + cache.get({ + client_id: 'test-client', + audience: 'a', + scope: 's' + }) ).toBeUndefined(); }); - it('builds key correctly', () => { - cache.save({ + it('retrieves values from the cache', () => { + const data = { client_id: 'test-client', audience: 'the_audience', scope: 'the_scope', @@ -31,17 +35,23 @@ describe('InMemoryCache', () => { claims: { __raw: 'idtoken', exp: 1, name: 'Test' }, user: { name: 'Test' } } - }); + }; - expect(Object.keys(cache.cache)[0]).toBe( - '@@auth0spajs@@::test-client::the_audience::the_scope' - ); + cache.save(data); + + expect( + cache.get({ + client_id: 'test-client', + audience: 'the_audience', + scope: 'the_scope' + }) + ).toStrictEqual(data); }); it('expires after `expires_in` when `expires_in` < `user.exp`', () => { - cache.save({ + const data = { client_id: 'test-client', - audience: 'the_audiene', + audience: 'the_audience', scope: 'the_scope', id_token: 'idtoken', access_token: 'accesstoken', @@ -54,21 +64,29 @@ describe('InMemoryCache', () => { }, user: { name: 'Test' } } - }); + }; + + const cacheEntry = { + client_id: 'test-client', + audience: 'the_audience', + scope: 'the_scope' + }; + + cache.save(data); // Test that the cache state is normal up until just before the expiry time.. jest.advanceTimersByTime(799); - expect(Object.keys(cache.cache).length).toBe(1); + expect(cache.get(cacheEntry)).toStrictEqual(data); // Advance the time to match the expiry time.. jest.advanceTimersByTime(1); // and test that the cache has been emptied. - expect(Object.keys(cache.cache).length).toBe(0); + expect(cache.get(cacheEntry)).toBeUndefined(); }); it('strips everything except the refresh token when expiry has been reached', () => { - cache.save({ + const data = { client_id: 'test-client', audience: 'the_audience', scope: 'the_scope', @@ -84,25 +102,30 @@ describe('InMemoryCache', () => { }, user: { name: 'Test' } } - }); + }; + + cache.save(data); + + const cacheEntry = { + client_id: 'test-client', + audience: 'the_audience', + scope: 'the_scope' + }; // Test that the cache state is normal up until just before the expiry time.. jest.advanceTimersByTime(799); - expect(Object.keys(cache.cache).length).toBe(1); + expect(cache.get(cacheEntry)).toStrictEqual(data); // Advance the time to just past the expiry.. jest.advanceTimersByTime(1); - // And test that the cache has been emptied, except for the refresh token - expect(cache.cache).toStrictEqual({ - '@@auth0spajs@@::test-client::the_audience::the_scope': { - refresh_token: 'refreshtoken' - } + expect(cache.get(cacheEntry)).toStrictEqual({ + refresh_token: 'refreshtoken' }); }); it('expires after `user.exp` when `user.exp` < `expires_in`', () => { - cache.save({ + const data = { client_id: 'test-client', audience: 'the_audience', scope: 'the_scope', @@ -117,17 +140,25 @@ describe('InMemoryCache', () => { }, user: { name: 'Test' } } - }); + }; + + cache.save(data); + + const cacheEntry = { + client_id: 'test-client', + audience: 'the_audience', + scope: 'the_scope' + }; // Test that the cache state is normal up until just before the expiry time.. jest.advanceTimersByTime(799); - expect(Object.keys(cache.cache).length).toBe(1); + expect(cache.get(cacheEntry)).toStrictEqual(data); // Advance the time to just past the expiry.. jest.advanceTimersByTime(1); // And test that the cache has been emptied - expect(Object.keys(cache.cache).length).toBe(0); + expect(cache.get(cacheEntry)).toBeUndefined(); }); }); diff --git a/__tests__/index.test.ts b/__tests__/index.test.ts index d4a4ffc75..656bb590a 100644 --- a/__tests__/index.test.ts +++ b/__tests__/index.test.ts @@ -1,7 +1,6 @@ jest.mock('browser-tabs-lock'); jest.mock('../src/jwt'); jest.mock('../src/storage'); -jest.mock('../src/cache'); jest.mock('../src/transaction-manager'); jest.mock('../src/utils'); @@ -40,6 +39,18 @@ const DEFAULT_POPUP_CONFIG_OPTIONS: PopupConfigOptions = { timeoutInSeconds: DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS }; +const mockEnclosedCache = { + get: jest.fn(), + save: jest.fn(), + clear: jest.fn() +}; + +jest.mock('../src/cache', () => ({ + InMemoryCache: () => ({ + enclosedCache: mockEnclosedCache + }) +})); + const setup = async (options = {}) => { const auth0 = await createAuth0Client({ domain: TEST_DOMAIN, @@ -47,10 +58,6 @@ const setup = async (options = {}) => { ...options }); - // Return the specific instance you want from the module by supplying fn - // e.g. getInstance('../module', m => m.SomeInstance) - const getInstance = (m, fn) => fn(require(m)).mock.instances[0]; - const getDefaultInstance = m => require(m).default.mock.instances[0]; const storage = { @@ -60,7 +67,8 @@ const setup = async (options = {}) => { }; const lock = require('browser-tabs-lock'); - const cache = getInstance('../src/cache', m => m.InMemoryCache); + const cache = mockEnclosedCache; + const tokenVerifier = require('../src/jwt').verify; const transactionManager = getDefaultInstance('../src/transaction-manager'); const utils = require('../src/utils'); @@ -1615,6 +1623,7 @@ describe('Auth0', () => { result.cache.get.mockReturnValue({ access_token: TEST_ACCESS_TOKEN }); return result; }; + it('calls `loginWithPopup` with the correct default options', async () => { const { auth0, utils } = await localSetup(); @@ -1626,6 +1635,7 @@ describe('Auth0', () => { }, DEFAULT_POPUP_CONFIG_OPTIONS ); + expect(utils.getUniqueScopes).toHaveBeenCalledWith( 'openid profile email', undefined, diff --git a/src/Auth0Client.ts b/src/Auth0Client.ts index a2850d753..8cd8bcad4 100644 --- a/src/Auth0Client.ts +++ b/src/Auth0Client.ts @@ -27,7 +27,7 @@ const GET_TOKEN_SILENTLY_LOCK_KEY = 'auth0.lock.getTokenSilently'; const cacheFactory = location => { const builders = { - memory: () => new InMemoryCache(), + memory: () => new InMemoryCache().enclosedCache, localstorage: () => new LocalStorageCache() }; diff --git a/src/cache.ts b/src/cache.ts index 1e7644d88..2b7fb0122 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -152,37 +152,41 @@ export class LocalStorageCache implements ICache { } } -export class InMemoryCache implements ICache { - cache: CachedTokens = {}; +export class InMemoryCache { + public enclosedCache: ICache = (function() { + let cache: CachedTokens = {}; - public save(entry: CacheEntry) { - const key = createKey(entry); - this.cache[key] = entry; + return { + save(entry: CacheEntry) { + const key = createKey(entry); + cache[key] = entry; - const timeout = getExpirationTimeoutInMilliseconds( - entry.expires_in, - entry.decodedToken.claims.exp - ); + const timeout = getExpirationTimeoutInMilliseconds( + entry.expires_in, + entry.decodedToken.claims.exp + ); - setTimeout(() => { - const payload = this.cache[key]; + setTimeout(() => { + const payload = cache[key]; - if (!payload) return; + if (!payload) return; - if (payload.refresh_token) { - this.cache[key] = { refresh_token: payload.refresh_token }; - return; - } + if (payload.refresh_token) { + cache[key] = { refresh_token: payload.refresh_token }; + return; + } - delete this.cache[key]; - }, timeout); - } + delete cache[key]; + }, timeout); + }, - public get(key: CacheKeyData) { - return this.cache[createKey(key)]; - } + get(key: CacheKeyData) { + return cache[createKey(key)]; + }, - public clear() { - this.cache = {}; - } + clear() { + cache = {}; + } + }; + })(); }