From e0247787b724aa1169a4ef8a8e50c12ab5ba8a9b Mon Sep 17 00:00:00 2001 From: Daryl Thayil Date: Wed, 20 May 2020 13:10:47 -0700 Subject: [PATCH 1/7] hone surface for msal extension library --- lib/msal-node/src/cache/CacheContext.ts | 47 ---------- lib/msal-node/src/cache/CacheManager.ts | 94 +++++++++++++++++++ lib/msal-node/src/cache/ICachePlugin.ts | 5 + lib/msal-node/src/cache/Storage.ts | 34 +++++-- lib/msal-node/src/client/ClientApplication.ts | 17 ++-- lib/msal-node/src/config/Configuration.ts | 19 +--- lib/msal-node/src/index.ts | 2 + samples/msal-node-auth-code/index.js | 63 ++++++++++--- 8 files changed, 187 insertions(+), 94 deletions(-) delete mode 100644 lib/msal-node/src/cache/CacheContext.ts create mode 100644 lib/msal-node/src/cache/CacheManager.ts create mode 100644 lib/msal-node/src/cache/ICachePlugin.ts diff --git a/lib/msal-node/src/cache/CacheContext.ts b/lib/msal-node/src/cache/CacheContext.ts deleted file mode 100644 index acea0faa6c..0000000000 --- a/lib/msal-node/src/cache/CacheContext.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. - */ - -import { - JsonCache, - Deserializer -} from '@azure/msal-common'; -import { Storage } from '../cache/Storage'; - -/** - * class managing sync between the persistent cache blob in the disk and the in memory cache of the node - */ -export class CacheContext { - - private defaultSerializedCache: JsonCache = { - Account: {}, - IdToken: {}, - AccessToken: {}, - RefreshToken: {}, - AppMetadata: {} - }; - - constructor() {} - - /** - * Update the library cache - * @param storage - */ - setCurrentCache(storage: Storage, cacheObject: JsonCache) { - const cacheWithOverlayedDefaults = this.overlayDefaults(cacheObject); - storage.setCache(Deserializer.deserializeAllCache(cacheWithOverlayedDefaults)); - } - - - overlayDefaults (passedInCache: JsonCache): JsonCache { - return { - Account: { ...this.defaultSerializedCache.Account, ...passedInCache.Account }, - IdToken: { ...this.defaultSerializedCache.IdToken, ...passedInCache.IdToken }, - AccessToken: { ...this.defaultSerializedCache.AccessToken, ...passedInCache.AccessToken }, - RefreshToken: { ...this.defaultSerializedCache.RefreshToken, ...passedInCache.RefreshToken }, - AppMetadata: { ...this.defaultSerializedCache.AppMetadata, ...passedInCache.AppMetadata} - }; - } - -} diff --git a/lib/msal-node/src/cache/CacheManager.ts b/lib/msal-node/src/cache/CacheManager.ts new file mode 100644 index 0000000000..a26785b461 --- /dev/null +++ b/lib/msal-node/src/cache/CacheManager.ts @@ -0,0 +1,94 @@ +import { Storage } from './Storage'; +import { Serializer, Deserializer, InMemoryCache, JsonCache } from '@azure/msal-common'; +import { ICachePlugin } from './ICachePlugin'; + +const defaultSerializedCache: JsonCache = { + Account: {}, + IdToken: {}, + AccessToken: {}, + RefreshToken: {}, + AppMetadata: {} +}; + + +export class CacheManager { + + hasChanged: boolean; + storage: Storage; + persistence: ICachePlugin; + + constructor(storage: Storage, cachePlugin?: ICachePlugin) { + this.hasChanged = false; + this.storage = storage; + this.storage.registerChangeEmitter(this.handleChangeEvent.bind(this)) + if (cachePlugin) { + this.setPersistence(cachePlugin); + } + + } + + setPersistence(persistence: ICachePlugin): void { + this.persistence = persistence; + } + + handleChangeEvent() { + this.hasChanged = true; + } + + mergeState(oldState: JsonCache, currentState: JsonCache) { + // TODO + // mergeUpdates(old, new) + // mergeRemovals(old, new) + return { + ...oldState, + ...currentState + }; + } + + //TODO think about separating serialize / deserialize from writeToPersistance and readFromPersistance + + async serialize(): Promise { + const cache: string = JSON.stringify(Serializer.serializeAllCache(this.storage.getCache())); + if (this.persistence) { + await this.persistence.writeToStorage((stateFromDisk) => { + const jsonFromDisk = JSON.parse(stateFromDisk); + return JSON.stringify( + this.mergeState( + jsonFromDisk, + Serializer.serializeAllCache(this.storage.getCache()) + ), null, 4 + ); + }); + } + this.hasChanged = false; + return cache; + } + + async deserialize(cache?: string): Promise { + let deserializedCache: InMemoryCache; + if (this.persistence) { + const stringCacheFromStorage = await this.persistence.readFromStorage(); + deserializedCache = Deserializer.deserializeAllCache(this.overlayDefaults(JSON.parse(stringCacheFromStorage))); + } else if (cache) { + deserializedCache = Deserializer.deserializeAllCache(this.overlayDefaults(JSON.parse(cache))); + } else { + throw Error("Cache Must be passed in or configured"); + } + this.storage.setCache(deserializedCache) + } + + cacheHasChanged(): boolean { + return this.hasChanged; + } + + private overlayDefaults (passedInCache: JsonCache): JsonCache { + return { + Account: { ...defaultSerializedCache.Account, ...passedInCache.Account }, + IdToken: { ...defaultSerializedCache.IdToken, ...passedInCache.IdToken }, + AccessToken: { ...defaultSerializedCache.AccessToken, ...passedInCache.AccessToken }, + RefreshToken: { ...defaultSerializedCache.RefreshToken, ...passedInCache.RefreshToken }, + AppMetadata: { ...defaultSerializedCache.AppMetadata, ...passedInCache.AppMetadata} + }; + } + +} diff --git a/lib/msal-node/src/cache/ICachePlugin.ts b/lib/msal-node/src/cache/ICachePlugin.ts new file mode 100644 index 0000000000..a484d36d57 --- /dev/null +++ b/lib/msal-node/src/cache/ICachePlugin.ts @@ -0,0 +1,5 @@ + +export interface ICachePlugin { + readFromStorage: () => Promise; + writeToStorage: (getMergedState: (oldState: string) => string) => Promise; +} diff --git a/lib/msal-node/src/cache/Storage.ts b/lib/msal-node/src/cache/Storage.ts index 830e522e94..ad3f6d791c 100644 --- a/lib/msal-node/src/cache/Storage.ts +++ b/lib/msal-node/src/cache/Storage.ts @@ -6,20 +6,30 @@ import { ICacheStorage, InMemoryCache } from '@azure/msal-common'; -import { CacheOptions } from '../config/Configuration'; /** * This class implements Storage for node, reading cache from user specified storage location or an extension library */ export class Storage implements ICacheStorage { // Cache configuration, either set by user or default values. - private cacheConfig: CacheOptions;; - private inMemoryCache: InMemoryCache; + private inMemoryCache: InMemoryCache = { + accounts: {}, + accessTokens: {}, + refreshTokens: {}, + appMetadata: {}, + idTokens: {} + }; + private changeEmitters: Array = []; - constructor(cacheConfig: CacheOptions) { - this.cacheConfig = cacheConfig; - if (this.cacheConfig.cacheLocation! === "fileCache") - this.inMemoryCache = this.cacheConfig.cacheInMemory!; + constructor() { + } + + registerChangeEmitter(func: () => void): void { + this.changeEmitters.push(func); + } + + emitChange() { + this.changeEmitters.forEach(func => func.call(null)); } /** @@ -35,6 +45,7 @@ export class Storage implements ICacheStorage { */ setCache(inMemoryCache: InMemoryCache) { this.inMemoryCache = inMemoryCache; + this.emitChange(); } /** @@ -45,8 +56,10 @@ export class Storage implements ICacheStorage { */ setItem(key: string, value: string): void { if (key && value) { + this.emitChange(); return; } + this.emitChange(); } /** @@ -66,7 +79,11 @@ export class Storage implements ICacheStorage { * TODO: implement after the lookup implementation */ removeItem(key: string): void { - if (!key) return; + if (!key) { + this.emitChange(); + return; + } + this.emitChange(); } /** @@ -90,6 +107,7 @@ export class Storage implements ICacheStorage { * Clears all cache entries created by MSAL (except tokens). */ clear(): void { + this.emitChange(); return; } } diff --git a/lib/msal-node/src/client/ClientApplication.ts b/lib/msal-node/src/client/ClientApplication.ts index 94088586b1..e752b2333e 100644 --- a/lib/msal-node/src/client/ClientApplication.ts +++ b/lib/msal-node/src/client/ClientApplication.ts @@ -11,21 +11,19 @@ import { RefreshTokenClient, RefreshTokenRequest, AuthenticationResult, - JsonCache, - Serializer } from '@azure/msal-common'; import { Configuration, buildAppConfiguration } from '../config/Configuration'; import { CryptoProvider } from '../crypto/CryptoProvider'; import { Storage } from '../cache/Storage'; import { version } from '../../package.json'; import { Constants } from './../utils/Constants'; -import { CacheContext } from '../cache/CacheContext'; +import { CacheManager } from '../cache/CacheManager'; export abstract class ClientApplication { protected config: Configuration; - protected cacheContext: CacheContext; protected storage: Storage; + private cacheManager: CacheManager; /** * @constructor @@ -49,8 +47,8 @@ export abstract class ClientApplication { */ protected constructor(configuration: Configuration) { this.config = buildAppConfiguration(configuration); - this.storage = new Storage(this.config.cache!); - this.cacheContext = new CacheContext(); + this.storage = new Storage(); + this.cacheManager = new CacheManager(this.storage, this.config.cache?.cachePlugin); } /** @@ -126,11 +124,8 @@ export abstract class ClientApplication { }; } - initializeCache(cacheObject: JsonCache) { - this.cacheContext.setCurrentCache(this.storage, cacheObject) + getCacheManager(): CacheManager { + return this.cacheManager; } - readCache(): JsonCache { - return Serializer.serializeAllCache(this.storage.getCache()); - } } diff --git a/lib/msal-node/src/config/Configuration.ts b/lib/msal-node/src/config/Configuration.ts index 4892deb60d..9e2f419774 100644 --- a/lib/msal-node/src/config/Configuration.ts +++ b/lib/msal-node/src/config/Configuration.ts @@ -6,12 +6,11 @@ import { AuthOptions, LoggerOptions, INetworkModule, - LogLevel, - InMemoryCache, + LogLevel } from '@azure/msal-common'; import { NetworkUtils } from '../utils/NetworkUtils'; -import { CACHE } from '../utils/Constants'; import debug from "debug"; +import { ICachePlugin } from 'cache/ICachePlugin'; export type NodeAuthOptions = AuthOptions; @@ -20,12 +19,12 @@ export type NodeAuthOptions = AuthOptions; * * - cacheLocation - Used to specify the cacheLocation user wants to set. Valid values are "localStorage" and "sessionStorage" * - storeAuthStateInCookie - If set, MSAL store's the auth request state required for validation of the auth flows in the browser cookies. By default this flag is set to false. + * - cachePlugin for persistence provided to library */ // TODO Temporary placeholder - this will be rewritten by cache PR. export type CacheOptions = { - cacheLocation?: string; storeAuthStateInCookie?: boolean; - cacheInMemory?: InMemoryCache; + cachePlugin?: ICachePlugin; }; /** @@ -60,15 +59,7 @@ const DEFAULT_AUTH_OPTIONS: NodeAuthOptions = { }; const DEFAULT_CACHE_OPTIONS: CacheOptions = { - cacheLocation: CACHE.FILE_CACHE, - storeAuthStateInCookie: false, - cacheInMemory: { - accounts: {}, - idTokens: {}, - accessTokens: {}, - refreshTokens: {}, - appMetadata: {}, - }, + storeAuthStateInCookie: false }; const DEFAULT_LOGGER_OPTIONS: LoggerOptions = { diff --git a/lib/msal-node/src/index.ts b/lib/msal-node/src/index.ts index 0ce92b76fc..8229888110 100644 --- a/lib/msal-node/src/index.ts +++ b/lib/msal-node/src/index.ts @@ -5,6 +5,8 @@ export { Storage } from './cache/Storage'; // crypto export { CryptoProvider } from './crypto/CryptoProvider'; +export { CacheManager } from './cache/CacheManager'; +export { ICachePlugin } from './cache/ICachePlugin'; // Common Object Formats export { diff --git a/samples/msal-node-auth-code/index.js b/samples/msal-node-auth-code/index.js index 93c44fc212..226aa054f0 100644 --- a/samples/msal-node-auth-code/index.js +++ b/samples/msal-node-auth-code/index.js @@ -4,12 +4,42 @@ */ const express = require("express"); const msal = require('@azure/msal-node'); -const myLocalCache = require("./data/cache"); const fs = require("fs"); const SERVER_PORT = process.env.PORT || 3000; -// initialize msal public client application + +const readFromStorage = () => { + return new Promise((resolve, reject) => { + fs.readFile("./data/cache.json", (err, data) => { + if (err) { + reject(err); + } else { + resolve(data); + } + }); + }); +}; +const writeToStorage = (getMergedState) => { + return new Promise((resolve, reject) => { + readFromStorage().then(oldFile => { + const mergedState = getMergedState(oldFile); + fs.writeFile("./data/cache.json", mergedState, (err, data) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + }); +}; + +const cachePlugin = { + readFromStorage: readFromStorage, + writeToStorage: writeToStorage +}; + const publicClientConfig = { auth: { clientId: "99cab759-2aab-420b-91d8-5e3d8d4f063b", @@ -18,29 +48,32 @@ const publicClientConfig = { redirectUri: "http://localhost:3000/redirect", }, cache: { - cacheLocation: "fileCache", // This configures where your cache will be stored storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge + cachePlugin: cachePlugin }, }; const pca = new msal.PublicClientApplication(publicClientConfig); -pca.initializeCache(myLocalCache); +const msalCacheManager = pca.getCacheManager(); + // Create Express App and Routes const app = express(); app.get('/', (req, res) => { - const authCodeUrlParameters = { - scopes: ["user.read"], - redirectUri: ["http://localhost:3000/redirect"], - }; - // get url to sign user in and consent to scopes needed for application - pca.getAuthCodeUrl(authCodeUrlParameters) - .then((response) => { + msalCacheManager.deserialize().then(() => { + const authCodeUrlParameters = { + scopes: ["user.read"], + redirectUri: ["http://localhost:3000/redirect"], + }; + + // get url to sign user in and consent to scopes needed for application + pca.getAuthCodeUrl(authCodeUrlParameters).then((response) => { console.log(response); res.redirect(response); }) .catch((error) => console.log(JSON.stringify(error))); + }); }); app.get('/redirect', (req, res) => { @@ -54,12 +87,14 @@ app.get('/redirect', (req, res) => { pca.acquireTokenByCode(tokenRequest).then((response) => { console.log("\nResponse: \n:", response); res.send(200); - // uncomment this to show writing of cache, dont commit real tokens. - // fs.writeFileSync("./data/cache.json", JSON.stringify(pca.readCache()), null, 4); + return msalCacheManager.serialize(); }).catch((error) => { console.log(error); res.status(500).send(error); }); }); -app.listen(SERVER_PORT, () => console.log(`Msal Node Auth Code Sample app listening on port ${SERVER_PORT}!`)) +msalCacheManager.deserialize().then(() => { + app.listen(SERVER_PORT, () => console.log(`Msal Node Auth Code Sample app listening on port ${SERVER_PORT}!`)) +}); + From ed515b5408107c87ebe6ef052b5d5a8cb87dc5c0 Mon Sep 17 00:00:00 2001 From: Daryl Thayil Date: Tue, 2 Jun 2020 09:42:52 -0700 Subject: [PATCH 2/7] update based on comments --- lib/msal-node/src/cache/CacheManager.ts | 85 +++++++++++++------ lib/msal-node/src/cache/ICachePlugin.ts | 5 +- lib/msal-node/src/cache/Storage.ts | 10 +-- lib/msal-node/src/client/ClientApplication.ts | 19 +++-- lib/msal-node/src/config/Configuration.ts | 17 ++-- lib/msal-node/src/utils/Constants.ts | 2 +- .../client/PublicClientApplication.spec.ts | 17 +--- .../test/config/ClientConfiguration.spec.ts | 14 +-- lib/msal-node/test/utils/TestConstants.ts | 18 ++-- samples/msal-node-auth-code/index.js | 31 ++----- 10 files changed, 116 insertions(+), 102 deletions(-) diff --git a/lib/msal-node/src/cache/CacheManager.ts b/lib/msal-node/src/cache/CacheManager.ts index a26785b461..0f3a2d1f76 100644 --- a/lib/msal-node/src/cache/CacheManager.ts +++ b/lib/msal-node/src/cache/CacheManager.ts @@ -1,5 +1,10 @@ import { Storage } from './Storage'; -import { Serializer, Deserializer, InMemoryCache, JsonCache } from '@azure/msal-common'; +import { + Serializer, + Deserializer, + InMemoryCache, + JsonCache, +} from '@azure/msal-common'; import { ICachePlugin } from './ICachePlugin'; const defaultSerializedCache: JsonCache = { @@ -7,24 +12,21 @@ const defaultSerializedCache: JsonCache = { IdToken: {}, AccessToken: {}, RefreshToken: {}, - AppMetadata: {} + AppMetadata: {}, }; - export class CacheManager { - hasChanged: boolean; storage: Storage; - persistence: ICachePlugin; + private persistence: ICachePlugin; constructor(storage: Storage, cachePlugin?: ICachePlugin) { this.hasChanged = false; this.storage = storage; - this.storage.registerChangeEmitter(this.handleChangeEvent.bind(this)) + this.storage.registerChangeEmitter(this.handleChangeEvent.bind(this)); if (cachePlugin) { this.setPersistence(cachePlugin); } - } setPersistence(persistence: ICachePlugin): void { @@ -41,54 +43,85 @@ export class CacheManager { // mergeRemovals(old, new) return { ...oldState, - ...currentState + ...currentState, }; } - //TODO think about separating serialize / deserialize from writeToPersistance and readFromPersistance + // TODO think about separating serialize / deserialize from writeToPersistance and readFromPersistance async serialize(): Promise { - const cache: string = JSON.stringify(Serializer.serializeAllCache(this.storage.getCache())); + const cache: string = JSON.stringify( + Serializer.serializeAllCache(this.storage.getCache()) + ); if (this.persistence) { - await this.persistence.writeToStorage((stateFromDisk) => { - const jsonFromDisk = JSON.parse(stateFromDisk); + const getMergedState = (stateFromDisk: any) => { + let jsonFromDisk = {}; + try { + jsonFromDisk = JSON.parse(stateFromDisk); + } catch (e) { + // TODO make first class error + throw Error('Invalid cachce from disk'); + } + return JSON.stringify( this.mergeState( jsonFromDisk, Serializer.serializeAllCache(this.storage.getCache()) - ), null, 4 + ), + null, + 4 ); - }); + }; + await this.persistence.writeToStorage(getMergedState); } this.hasChanged = false; return cache; } async deserialize(cache?: string): Promise { - let deserializedCache: InMemoryCache; if (this.persistence) { const stringCacheFromStorage = await this.persistence.readFromStorage(); - deserializedCache = Deserializer.deserializeAllCache(this.overlayDefaults(JSON.parse(stringCacheFromStorage))); + const deserializedCache = Deserializer.deserializeAllCache( + this.overlayDefaults(JSON.parse(stringCacheFromStorage)) + ); + this.storage.setCache(deserializedCache); } else if (cache) { - deserializedCache = Deserializer.deserializeAllCache(this.overlayDefaults(JSON.parse(cache))); + const deserializedCache = Deserializer.deserializeAllCache( + this.overlayDefaults(JSON.parse(cache)) + ); + this.storage.setCache(deserializedCache); } else { - throw Error("Cache Must be passed in or configured"); + // TODO make first class error + throw Error('cache mus be configured'); } - this.storage.setCache(deserializedCache) } cacheHasChanged(): boolean { return this.hasChanged; } - private overlayDefaults (passedInCache: JsonCache): JsonCache { + private overlayDefaults(passedInCache: JsonCache): JsonCache { return { - Account: { ...defaultSerializedCache.Account, ...passedInCache.Account }, - IdToken: { ...defaultSerializedCache.IdToken, ...passedInCache.IdToken }, - AccessToken: { ...defaultSerializedCache.AccessToken, ...passedInCache.AccessToken }, - RefreshToken: { ...defaultSerializedCache.RefreshToken, ...passedInCache.RefreshToken }, - AppMetadata: { ...defaultSerializedCache.AppMetadata, ...passedInCache.AppMetadata} + Account: { + ...defaultSerializedCache.Account, + ...passedInCache.Account, + }, + IdToken: { + ...defaultSerializedCache.IdToken, + ...passedInCache.IdToken, + }, + AccessToken: { + ...defaultSerializedCache.AccessToken, + ...passedInCache.AccessToken, + }, + RefreshToken: { + ...defaultSerializedCache.RefreshToken, + ...passedInCache.RefreshToken, + }, + AppMetadata: { + ...defaultSerializedCache.AppMetadata, + ...passedInCache.AppMetadata, + }, }; } - } diff --git a/lib/msal-node/src/cache/ICachePlugin.ts b/lib/msal-node/src/cache/ICachePlugin.ts index a484d36d57..23cac946c0 100644 --- a/lib/msal-node/src/cache/ICachePlugin.ts +++ b/lib/msal-node/src/cache/ICachePlugin.ts @@ -1,5 +1,6 @@ - export interface ICachePlugin { readFromStorage: () => Promise; - writeToStorage: (getMergedState: (oldState: string) => string) => Promise; + writeToStorage: ( + getMergedState: (oldState: string) => string + ) => Promise; } diff --git a/lib/msal-node/src/cache/Storage.ts b/lib/msal-node/src/cache/Storage.ts index ad3f6d791c..8fefa4c97f 100644 --- a/lib/msal-node/src/cache/Storage.ts +++ b/lib/msal-node/src/cache/Storage.ts @@ -2,10 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ -import { - ICacheStorage, - InMemoryCache -} from '@azure/msal-common'; +import { ICacheStorage, InMemoryCache } from '@azure/msal-common'; /** * This class implements Storage for node, reading cache from user specified storage location or an extension library @@ -17,13 +14,10 @@ export class Storage implements ICacheStorage { accessTokens: {}, refreshTokens: {}, appMetadata: {}, - idTokens: {} + idTokens: {}, }; private changeEmitters: Array = []; - constructor() { - } - registerChangeEmitter(func: () => void): void { this.changeEmitters.push(func); } diff --git a/lib/msal-node/src/client/ClientApplication.ts b/lib/msal-node/src/client/ClientApplication.ts index e752b2333e..141f6dd560 100644 --- a/lib/msal-node/src/client/ClientApplication.ts +++ b/lib/msal-node/src/client/ClientApplication.ts @@ -20,7 +20,6 @@ import { Constants } from './../utils/Constants'; import { CacheManager } from '../cache/CacheManager'; export abstract class ClientApplication { - protected config: Configuration; protected storage: Storage; private cacheManager: CacheManager; @@ -48,7 +47,10 @@ export abstract class ClientApplication { protected constructor(configuration: Configuration) { this.config = buildAppConfiguration(configuration); this.storage = new Storage(); - this.cacheManager = new CacheManager(this.storage, this.config.cache?.cachePlugin); + this.cacheManager = new CacheManager( + this.storage, + this.config.cache?.cachePlugin + ); } /** @@ -97,8 +99,12 @@ export abstract class ClientApplication { * handle the caching and refreshing of tokens automatically. * @param request */ - async acquireTokenByRefreshToken(request: RefreshTokenRequest): Promise{ - const refreshTokenClient = new RefreshTokenClient(this.buildOauthClientConfiguration()); + async acquireTokenByRefreshToken( + request: RefreshTokenRequest + ): Promise { + const refreshTokenClient = new RefreshTokenClient( + this.buildOauthClientConfiguration() + ); return refreshTokenClient.acquireToken(request); } @@ -118,8 +124,8 @@ export abstract class ClientApplication { libraryInfo: { sku: Constants.MSAL_SKU, version: version, - cpu: process.arch || "", - os: process.platform || "" + cpu: process.arch || '', + os: process.platform || '', }, }; } @@ -127,5 +133,4 @@ export abstract class ClientApplication { getCacheManager(): CacheManager { return this.cacheManager; } - } diff --git a/lib/msal-node/src/config/Configuration.ts b/lib/msal-node/src/config/Configuration.ts index 9e2f419774..16f3673b98 100644 --- a/lib/msal-node/src/config/Configuration.ts +++ b/lib/msal-node/src/config/Configuration.ts @@ -6,10 +6,10 @@ import { AuthOptions, LoggerOptions, INetworkModule, - LogLevel + LogLevel, } from '@azure/msal-common'; import { NetworkUtils } from '../utils/NetworkUtils'; -import debug from "debug"; +import debug from 'debug'; import { ICachePlugin } from 'cache/ICachePlugin'; export type NodeAuthOptions = AuthOptions; @@ -23,7 +23,6 @@ export type NodeAuthOptions = AuthOptions; */ // TODO Temporary placeholder - this will be rewritten by cache PR. export type CacheOptions = { - storeAuthStateInCookie?: boolean; cachePlugin?: ICachePlugin; }; @@ -58,13 +57,15 @@ const DEFAULT_AUTH_OPTIONS: NodeAuthOptions = { knownAuthorities: [], }; -const DEFAULT_CACHE_OPTIONS: CacheOptions = { - storeAuthStateInCookie: false -}; +const DEFAULT_CACHE_OPTIONS: CacheOptions = {}; const DEFAULT_LOGGER_OPTIONS: LoggerOptions = { - loggerCallback: (level: LogLevel, message: string, containsPii: boolean) => { - debug(`msal:${LogLevel[level]}${containsPii ? "-Pii": ""}`)(message); + loggerCallback: ( + level: LogLevel, + message: string, + containsPii: boolean + ) => { + debug(`msal:${LogLevel[level]}${containsPii ? '-Pii' : ''}`)(message); }, piiLoggingEnabled: false, logLevel: LogLevel.Info, diff --git a/lib/msal-node/src/utils/Constants.ts b/lib/msal-node/src/utils/Constants.ts index 541a310b19..3f04c00b96 100644 --- a/lib/msal-node/src/utils/Constants.ts +++ b/lib/msal-node/src/utils/Constants.ts @@ -43,5 +43,5 @@ export const CACHE = { * Constants for headers */ export const Constants = { - MSAL_SKU: "msal.js.node", + MSAL_SKU: 'msal.js.node', }; diff --git a/lib/msal-node/test/client/PublicClientApplication.spec.ts b/lib/msal-node/test/client/PublicClientApplication.spec.ts index 1cd2f3d022..db510406f6 100644 --- a/lib/msal-node/test/client/PublicClientApplication.spec.ts +++ b/lib/msal-node/test/client/PublicClientApplication.spec.ts @@ -1,6 +1,6 @@ import { PublicClientApplication } from './../../src/client/PublicClientApplication'; import { AuthorizationCodeRequest, Configuration } from './../../src/index'; -import { TEST_CONSTANTS } from "../utils/TestConstants"; +import { TEST_CONSTANTS } from '../utils/TestConstants'; import { AuthorizationCodeClient, AuthorizationCodeUrlRequest, @@ -10,19 +10,16 @@ import { RefreshTokenRequest, // ClientConfiguration, // ClientConfigurationError, -} -from "@azure/msal-common"; +} from '@azure/msal-common'; -jest.mock("@azure/msal-common"); +jest.mock('@azure/msal-common'); describe('PublicClientApplication', () => { - let appConfig: Configuration = { auth: { clientId: TEST_CONSTANTS.CLIENT_ID, authority: TEST_CONSTANTS.AUTHORITY, }, - }; // const expectedOauthClientConfig: ClientConfiguration = { @@ -35,13 +32,11 @@ describe('PublicClientApplication', () => { }); test('exports a class', () => { - const authApp = new PublicClientApplication(appConfig); expect(authApp).toBeInstanceOf(PublicClientApplication); }); test('acquireTokenByDeviceCode', async () => { - const request: DeviceCodeRequest = { deviceCodeCallback: response => { console.log(response); @@ -56,7 +51,6 @@ describe('PublicClientApplication', () => { }); test('acquireTokenByAuthorizationCode', async () => { - const request: AuthorizationCodeRequest = { scopes: TEST_CONSTANTS.DEFAULT_GRAPH_SCOPE, redirectUri: TEST_CONSTANTS.REDIRECT_URI, @@ -67,14 +61,12 @@ describe('PublicClientApplication', () => { await authApp.acquireTokenByCode(request); expect(AuthorizationCodeClient).toHaveBeenCalledTimes(1); // expect(AuthorizationCodeClient).toHaveBeenCalledWith(expect.objectContaining(expectedOauthClientConfig)); - }); test('acquireTokenByRefreshToken', async () => { - const request: RefreshTokenRequest = { scopes: TEST_CONSTANTS.DEFAULT_GRAPH_SCOPE, - refreshToken: TEST_CONSTANTS.REFRESH_TOKEN + refreshToken: TEST_CONSTANTS.REFRESH_TOKEN, }; const authApp = new PublicClientApplication(appConfig); @@ -84,7 +76,6 @@ describe('PublicClientApplication', () => { }); test('create AuthorizationCode URL', async () => { - const request: AuthorizationCodeUrlRequest = { scopes: TEST_CONSTANTS.DEFAULT_GRAPH_SCOPE, redirectUri: TEST_CONSTANTS.REDIRECT_URI, diff --git a/lib/msal-node/test/config/ClientConfiguration.spec.ts b/lib/msal-node/test/config/ClientConfiguration.spec.ts index a89f72ffd8..6ebff95504 100644 --- a/lib/msal-node/test/config/ClientConfiguration.spec.ts +++ b/lib/msal-node/test/config/ClientConfiguration.spec.ts @@ -1,12 +1,14 @@ -import {buildAppConfiguration, Configuration} from "../../src/config/Configuration"; -import {CACHE} from "../../src/utils/Constants"; -import {HttpClient} from "../../src/network/HttpClient"; -import {TEST_CONSTANTS} from "../utils/TestConstants"; -import {LogLevel, NetworkRequestOptions} from "@azure/msal-common"; +import { + buildAppConfiguration, + Configuration, +} from '../../src/config/Configuration'; +import { CACHE } from '../../src/utils/Constants'; +import { HttpClient } from '../../src/network/HttpClient'; +import { TEST_CONSTANTS } from '../utils/TestConstants'; +import { LogLevel, NetworkRequestOptions } from '@azure/msal-common'; describe('ClientConfiguration tests', () => { test('builds configuration and assigns default functions', () => { - const config: Configuration = buildAppConfiguration({}); // network options diff --git a/lib/msal-node/test/utils/TestConstants.ts b/lib/msal-node/test/utils/TestConstants.ts index 83f86afaa9..6867b469a2 100644 --- a/lib/msal-node/test/utils/TestConstants.ts +++ b/lib/msal-node/test/utils/TestConstants.ts @@ -3,14 +3,16 @@ */ export const TEST_CONSTANTS = { - CLIENT_ID: "b41a6fbb-c728-4e03-aa59-d25b0fd383b6", - AUTHORITY: "https://login.microsoftonline.com/TenantId", - REDIRECT_URI: "http://localhost:8080", - DEFAULT_GRAPH_SCOPE: ["user.read"], - AUTHORIZATION_CODE: "0.ASgAqPq4kJXMDkamGO53C-4XWVm3ypmrKgtCkdhePY1PBjsoAJg.AQABAAIAAAAm-06blBE1TpVMil8KPQ41DOje1jDj1oK3KxTXGKg89VjLYJi71gx_npOoxVfC7X49MqOX7IltTJOilUId-IAHndHXlfWzoSGq3GUmwAOLMisftceBRtq3YBsvHX7giiuSZXJgpgu03uf3V2h5Z3GJNpnSXT1f7iVFuRvGh1-jqjWxKs2un8AS5rhti1ym1zxkeicKT43va5jQeHVUlTQo69llnwQJ3iKmKLDVq_Q25Au4EQjYaeEx6TP5IZSqPPm7x0bynmjE8cqR5r4ySP4wH8fjnxlLySrUEZObk2VgREB1AdH6-xKIa04EnJEj9dUgTwiFvQumkuHHetFOgH7ep_9diFOdAOQLUK8C9N4Prlj0JiOcgn6l0xYd5Q9691Ylw8UfifLwq_B7f30mMLN64_XgoBY9K9CR1L4EC1kPPwIhVv3m6xmbhXZ3efx-A-bbV2SYcO4D4ZlnQztHzie_GUlredtsdEMAOE3-jaMJs7i2yYMuIEEtRcHIjV_WscVooCDdKmVncHOObWhNUSdULAejBr3pFs0v3QO_xZ269eLu5Z0qHzCZ_EPg2aL-ERz-rpgdclQ_H_KnEtMsC4F1RgAnDjVmSRKJZZdnNLfKSX_Wd40t_nuo4kjN2cSt8QzzeL533zIZ4CxthOsC4HH2RcUZDIgHdLDLT2ukg-Osc6J9URpZP-IUpdjXg_uwbkHEjrXDMBMo2pmCqaWbMJKo5Lr7CrystifnDITXzZmmOah8HV83Xyb6EP8Gno6JRuaG80j8BKDWyb1Yof4rnLI1kZ59n_t2d0LnRBXz50PdWCWX6vtkg-kAV-bGJQr45XDSKBSv0Q_fVsdLMk24NacUZcF5ujUtqv__Bv-wATzCHWlbUDGHC8nHEi84PcYAjSsgAA", - REFRESH_TOKEN: "thisIsARefreshT0ken", - AUTH_CODE_URL: "https://login.microsoftonline.com/TenantId/oauth2.0/v2.0/authorize?client_id=b41a6fbb-c728-4e03-aa59-d25b0fd383b6&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%2F8080%2F&response_mode=query&scope=user.read%20openid%20profile%20offline_access", - CACHE_LOCATION: "Test", + CLIENT_ID: 'b41a6fbb-c728-4e03-aa59-d25b0fd383b6', + AUTHORITY: 'https://login.microsoftonline.com/TenantId', + REDIRECT_URI: 'http://localhost:8080', + DEFAULT_GRAPH_SCOPE: ['user.read'], + AUTHORIZATION_CODE: + '0.ASgAqPq4kJXMDkamGO53C-4XWVm3ypmrKgtCkdhePY1PBjsoAJg.AQABAAIAAAAm-06blBE1TpVMil8KPQ41DOje1jDj1oK3KxTXGKg89VjLYJi71gx_npOoxVfC7X49MqOX7IltTJOilUId-IAHndHXlfWzoSGq3GUmwAOLMisftceBRtq3YBsvHX7giiuSZXJgpgu03uf3V2h5Z3GJNpnSXT1f7iVFuRvGh1-jqjWxKs2un8AS5rhti1ym1zxkeicKT43va5jQeHVUlTQo69llnwQJ3iKmKLDVq_Q25Au4EQjYaeEx6TP5IZSqPPm7x0bynmjE8cqR5r4ySP4wH8fjnxlLySrUEZObk2VgREB1AdH6-xKIa04EnJEj9dUgTwiFvQumkuHHetFOgH7ep_9diFOdAOQLUK8C9N4Prlj0JiOcgn6l0xYd5Q9691Ylw8UfifLwq_B7f30mMLN64_XgoBY9K9CR1L4EC1kPPwIhVv3m6xmbhXZ3efx-A-bbV2SYcO4D4ZlnQztHzie_GUlredtsdEMAOE3-jaMJs7i2yYMuIEEtRcHIjV_WscVooCDdKmVncHOObWhNUSdULAejBr3pFs0v3QO_xZ269eLu5Z0qHzCZ_EPg2aL-ERz-rpgdclQ_H_KnEtMsC4F1RgAnDjVmSRKJZZdnNLfKSX_Wd40t_nuo4kjN2cSt8QzzeL533zIZ4CxthOsC4HH2RcUZDIgHdLDLT2ukg-Osc6J9URpZP-IUpdjXg_uwbkHEjrXDMBMo2pmCqaWbMJKo5Lr7CrystifnDITXzZmmOah8HV83Xyb6EP8Gno6JRuaG80j8BKDWyb1Yof4rnLI1kZ59n_t2d0LnRBXz50PdWCWX6vtkg-kAV-bGJQr45XDSKBSv0Q_fVsdLMk24NacUZcF5ujUtqv__Bv-wATzCHWlbUDGHC8nHEi84PcYAjSsgAA', + REFRESH_TOKEN: 'thisIsARefreshT0ken', + AUTH_CODE_URL: + 'https://login.microsoftonline.com/TenantId/oauth2.0/v2.0/authorize?client_id=b41a6fbb-c728-4e03-aa59-d25b0fd383b6&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%2F8080%2F&response_mode=query&scope=user.read%20openid%20profile%20offline_access', + CACHE_LOCATION: 'Test', }; export const AUTHENTICATION_RESULT = { diff --git a/samples/msal-node-auth-code/index.js b/samples/msal-node-auth-code/index.js index 226aa054f0..71818ed360 100644 --- a/samples/msal-node-auth-code/index.js +++ b/samples/msal-node-auth-code/index.js @@ -4,40 +4,25 @@ */ const express = require("express"); const msal = require('@azure/msal-node'); -const fs = require("fs"); +const { promises: fs } = require("fs"); const SERVER_PORT = process.env.PORT || 3000; const readFromStorage = () => { - return new Promise((resolve, reject) => { - fs.readFile("./data/cache.json", (err, data) => { - if (err) { - reject(err); - } else { - resolve(data); - } - }); - }); + return fs.readFile("./data/cache.json"); }; const writeToStorage = (getMergedState) => { - return new Promise((resolve, reject) => { - readFromStorage().then(oldFile => { - const mergedState = getMergedState(oldFile); - fs.writeFile("./data/cache.json", mergedState, (err, data) => { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); + return readFromStorage() + .then(oldFile => { + const mergedState = getMergedState(oldFile); + return fs.writeFile("./data/cache.json", mergedState) }); }; const cachePlugin = { - readFromStorage: readFromStorage, - writeToStorage: writeToStorage + readFromStorage, + writeToStorage }; const publicClientConfig = { From 36911a497ead7297972c25fcc1178b9e2a952b60 Mon Sep 17 00:00:00 2001 From: Santiago Gonzalez Date: Thu, 4 Jun 2020 17:07:00 -0700 Subject: [PATCH 3/7] Update CacheManager. Update auth code sample. --- lib/msal-common/src/error/ClientAuthError.ts | 11 ++ .../src/unifiedCache/utils/CacheTypes.ts | 1 + lib/msal-common/test/utils/StringConstants.ts | 3 +- lib/msal-node/package-lock.json | 52 +++-- lib/msal-node/src/cache/CacheManager.ts | 183 ++++++++++++------ lib/msal-node/src/cache/ICachePlugin.ts | 4 +- lib/msal-node/src/config/Configuration.ts | 2 - samples/msal-node-auth-code/data/cache.json | 74 +------ samples/msal-node-auth-code/index.js | 47 ++--- 9 files changed, 193 insertions(+), 184 deletions(-) diff --git a/lib/msal-common/src/error/ClientAuthError.ts b/lib/msal-common/src/error/ClientAuthError.ts index b33dacd667..3b1e97935d 100644 --- a/lib/msal-common/src/error/ClientAuthError.ts +++ b/lib/msal-common/src/error/ClientAuthError.ts @@ -106,6 +106,10 @@ export const ClientAuthErrorMessage = { DeviceCodeExpired: { code: "device_code_expired", desc: "Device code is expired." + }, + CachePluginError: { + code: "no cache plugin set on CacheManager", + desc: "ICachePlugin needs to be set before using readFromStorage or writeFromStorage" } }; @@ -310,4 +314,11 @@ export class ClientAuthError extends AuthError { static createDeviceCodeExpiredError(): ClientAuthError { return new ClientAuthError(ClientAuthErrorMessage.DeviceCodeExpired.code, `${ClientAuthErrorMessage.DeviceCodeExpired.desc}`); } + + /** + * Throws error if ICachePlugin not set on CacheManager + */ + static createCachePluginError(): ClientAuthError { + return new ClientAuthError(ClientAuthErrorMessage.CachePluginError.code, `${ClientAuthErrorMessage.CachePluginError.desc}`); + } } diff --git a/lib/msal-common/src/unifiedCache/utils/CacheTypes.ts b/lib/msal-common/src/unifiedCache/utils/CacheTypes.ts index 8786e6d758..d7c8a4bba7 100644 --- a/lib/msal-common/src/unifiedCache/utils/CacheTypes.ts +++ b/lib/msal-common/src/unifiedCache/utils/CacheTypes.ts @@ -23,6 +23,7 @@ export type JsonCache = { AccessToken?: StringDict; RefreshToken?: StringDict; AppMetadata?: StringDict; + [key: string]: StringDict; }; export type InMemoryCache = { diff --git a/lib/msal-common/test/utils/StringConstants.ts b/lib/msal-common/test/utils/StringConstants.ts index e974adfc26..7b89eb5a4a 100644 --- a/lib/msal-common/test/utils/StringConstants.ts +++ b/lib/msal-common/test/utils/StringConstants.ts @@ -17,7 +17,7 @@ export const TEST_TOKENS = { SAMPLE_JWT_PAYLOAD: "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ", SAMPLE_JWT_SIG: "SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", SAMPLE_MALFORMED_JWT: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ", - CACHE_IDTOKEN: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Imk2bEdrM0ZaenhSY1ViMkMzbkVRN3N5SEpsWSIsImtpZCI6Imk2bEdrM0ZaenhSY1ViMkMzbkVRN3N5SEpsWSJ9.eyJvaWQiOiAib2JqZWN0MTIzNCIsICJwcmVmZXJyZWRfdXNlcm5hbWUiOiAiSm9obiBEb2UiLCAic3ViIjogInN1YiJ9.D3H6pMUtQnoJAGq6AHd" + CACHE_IDTOKEN: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Imk2bEdrM0ZaenhSY1ViMkMzbkVRN3N5SEpsWSIsImtpZCI6Imk2bEdrM0ZaenhSY1ViMkMzbkVRN3N5SEpsWSJ9.eyJvaWQiOiAib2JqZWN0MTIzNCIsICJwcmVmZXJyZWRfdXNlcm5hbWUiOiAiSm9obiBEb2UiLCAic3ViIjogInN1YiJ9.D3H6pMUtQnoJAGq6AHd", }; // Test Expiration Vals @@ -234,3 +234,4 @@ export const AUTHORIZATION_PENDING_RESPONSE = { error_uri: 'https://login.microsoftonline.com/error?code=70016' } }; + diff --git a/lib/msal-node/package-lock.json b/lib/msal-node/package-lock.json index 43c641eb90..735c7b5d2b 100644 --- a/lib/msal-node/package-lock.json +++ b/lib/msal-node/package-lock.json @@ -4,14 +4,6 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "@azure/msal-common": { - "version": "1.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-1.0.0-beta.1.tgz", - "integrity": "sha512-QkSODLb278Kqn6ziHHi7lPsmoAnBgCn4vHBYWVgLKF/Urn24TqDqyVDkXmaLwmbptkCDcGgpaEkIUSOpWaoliQ==", - "requires": { - "debug": "^4.1.1" - } - }, "@babel/code-frame": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", @@ -4658,7 +4650,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -4679,12 +4672,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4699,17 +4694,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -4826,7 +4824,8 @@ "inherits": { "version": "2.0.4", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -4838,6 +4837,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4852,6 +4852,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -4859,12 +4860,14 @@ "minimist": { "version": "1.2.5", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.9.0", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -4883,6 +4886,7 @@ "version": "0.5.3", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "^1.2.5" } @@ -4944,7 +4948,8 @@ "npm-normalize-package-bin": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "npm-packlist": { "version": "1.4.8", @@ -4972,7 +4977,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -4984,6 +4990,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5061,7 +5068,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -5097,6 +5105,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5116,6 +5125,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5159,12 +5169,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, diff --git a/lib/msal-node/src/cache/CacheManager.ts b/lib/msal-node/src/cache/CacheManager.ts index 3746c6ba0a..d0caff1cdc 100644 --- a/lib/msal-node/src/cache/CacheManager.ts +++ b/lib/msal-node/src/cache/CacheManager.ts @@ -3,6 +3,7 @@ import { Serializer, Deserializer, JsonCache, + ClientAuthError, } from '@azure/msal-common'; import { ICachePlugin } from './ICachePlugin'; @@ -15,88 +16,158 @@ const defaultSerializedCache: JsonCache = { }; export class CacheManager { - hasChanged: boolean; - storage: Storage; - private persistence: ICachePlugin; + + private storage: Storage; + private hasChanged: boolean; + private cacheSnapshot: string; + private readonly persistence: ICachePlugin; constructor(storage: Storage, cachePlugin?: ICachePlugin) { this.hasChanged = false; this.storage = storage; this.storage.registerChangeEmitter(this.handleChangeEvent.bind(this)); if (cachePlugin) { - this.setPersistence(cachePlugin); + this.persistence = cachePlugin; } } - setPersistence(persistence: ICachePlugin): void { - this.persistence = persistence; + /** + * Set to true if cache state has changed since last time serialized() or writeToPersistence was called + */ + cacheHasChanged(): boolean { + return this.hasChanged; } - handleChangeEvent() { - this.hasChanged = true; - } + /** + * Serializes in memory cache to JSON + */ + serialize(): string { + const mergedCache = this.mergeState( + JSON.parse(this.cacheSnapshot), + Serializer.serializeAllCache(this.storage.getCache())); - mergeState(oldState: JsonCache, currentState: JsonCache) { - // TODO - // mergeUpdates(old, new) - // mergeRemovals(old, new) - return { - ...oldState, - ...currentState, - }; + this.hasChanged = false; + + return JSON.stringify(mergedCache); } - // TODO think about separating serialize / deserialize from writeToPersistance and readFromPersistance + /** + * Deserializes JSON to in-memory cache. JSON should be in MSAL cache schema format + * @param cache + */ + deserialize(cache: string): void { + this.cacheSnapshot = cache; + const deserializedCache = Deserializer.deserializeAllCache(this.overlayDefaults(JSON.parse(cache))); + this.storage.setCache(deserializedCache); + } - async serialize(): Promise { - const cache: string = JSON.stringify( - Serializer.serializeAllCache(this.storage.getCache()) - ); + /** + * Serializes cache into JSON and calls ICachePlugin.writeToStorage. ICachePlugin must be set on ClientApplication + */ + async writeToPersistence(): Promise { + console.log("writing to persistence"); if (this.persistence) { - const getMergedState = (stateFromDisk: any) => { - let jsonFromDisk = {}; - try { - jsonFromDisk = JSON.parse(stateFromDisk); - } catch (e) { - // TODO make first class error - throw Error('Invalid cachce from disk'); - } - return JSON.stringify( - this.mergeState( - jsonFromDisk, - Serializer.serializeAllCache(this.storage.getCache()) - ), - null, - 4 - ); - }; - await this.persistence.writeToStorage(getMergedState); + this.cacheSnapshot = await this.persistence.readFromStorage(); + + const mergedCache = this.mergeState( + JSON.parse(this.cacheSnapshot), + Serializer.serializeAllCache(this.storage.getCache())); + + this.hasChanged = false; + await this.persistence.writeToStorage(JSON.stringify(mergedCache)); + } else { + throw ClientAuthError.createCachePluginError(); } - this.hasChanged = false; - return cache; } - async deserialize(cache?: string): Promise { + /** + * Calls ICachePlugin.readFromStorage and deserializes JSON to in-memory cache. + * ICachePlugin must be set on ClientApplication. + */ + async readFromPersistence(): Promise { + console.log("reading from persistence"); if (this.persistence) { - const stringCacheFromStorage = await this.persistence.readFromStorage(); - const deserializedCache = Deserializer.deserializeAllCache( - this.overlayDefaults(JSON.parse(stringCacheFromStorage)) - ); - this.storage.setCache(deserializedCache); - } else if (cache) { - const deserializedCache = Deserializer.deserializeAllCache( - this.overlayDefaults(JSON.parse(cache)) - ); + this.cacheSnapshot = await this.persistence.readFromStorage(); + const deserializedCache = Deserializer.deserializeAllCache(this.overlayDefaults(JSON.parse(this.cacheSnapshot))); this.storage.setCache(deserializedCache); } else { - // TODO make first class error - throw Error('cache mus be configured'); + throw ClientAuthError.createCachePluginError(); } } - cacheHasChanged(): boolean { - return this.hasChanged; + /** + * Called when the cache has changed state. + */ + private handleChangeEvent() { + this.hasChanged = true; + } + + /** + * Merge in memory cache with the cache snapshot. + * @param oldState + * @param currentState + */ + private mergeState(oldState: JsonCache, currentState: JsonCache): JsonCache { + let stateAfterRemoval = this.mergeRemovals(oldState, currentState); + return this.mergeUpdates(stateAfterRemoval, currentState); + } + + /** + * Deep update of oldState based on newState values + * @param oldState + * @param newState + */ + private mergeUpdates(oldState: any, newState: any): JsonCache { + Object.keys(newState).forEach((newKey) => { + let newValue = newState[newKey]; + + // if oldState does not contain value but newValue does, add it + if (!oldState.hasOwnProperty(newKey)) { + if (newValue != null) { + oldState[newKey] = newValue; + } + } else { + // merge oldState and newState + let newValueNotNull = newValue !== null; + let newValueIsObject = typeof newValue === 'object'; + let newValueIsNotArray = !Array.isArray(newValue); + + if (newValueNotNull && newValueIsObject && newValueIsNotArray) { + this.mergeUpdates(oldState[newKey], newValue); + } else { + oldState[newKey] = newValue; + } + } + }); + + return oldState; + } + + /** + * Removes entities in oldState that the were removed from newState. If there are any unknown values in root of + * oldState that are not recognized, they are left untouched. + * @param oldState + * @param newState + */ + private mergeRemovals(oldState: JsonCache, newState: JsonCache): JsonCache { + // set of entities created because we only want to remove these. If the oldState contains any other things, + // we leave them untouched + const entities = new Set(["Account", "AccessToken", "RefreshToken", "IdToken", "AppMetadata"]); + + entities.forEach((entity: string) => { + let oldEntries = oldState[entity]; + let newEntries = newState[entity]; + // if entity is in oldState but not in newState remove it + if (oldEntries != null) { + Object.keys(oldEntries).forEach((oldKey) => { + if (!newEntries || !(newEntries.hasOwnProperty(oldKey))) { + delete oldEntries[oldKey]; + } + }) + } + }); + return oldState; } private overlayDefaults(passedInCache: JsonCache): JsonCache { diff --git a/lib/msal-node/src/cache/ICachePlugin.ts b/lib/msal-node/src/cache/ICachePlugin.ts index 23cac946c0..4f7dc1ff71 100644 --- a/lib/msal-node/src/cache/ICachePlugin.ts +++ b/lib/msal-node/src/cache/ICachePlugin.ts @@ -1,6 +1,4 @@ export interface ICachePlugin { readFromStorage: () => Promise; - writeToStorage: ( - getMergedState: (oldState: string) => string - ) => Promise; + writeToStorage: (cache: string) => Promise; } diff --git a/lib/msal-node/src/config/Configuration.ts b/lib/msal-node/src/config/Configuration.ts index 962019ddc9..df9dc951f2 100644 --- a/lib/msal-node/src/config/Configuration.ts +++ b/lib/msal-node/src/config/Configuration.ts @@ -25,8 +25,6 @@ export type NodeAuthOptions = { /** * Use this to configure the below cache configuration options: * - * - cacheLocation - Used to specify the cacheLocation user wants to set. Valid values are "localStorage" and "sessionStorage" - * - storeAuthStateInCookie - If set, MSAL store's the auth request state required for validation of the auth flows in the browser cookies. By default this flag is set to false. * - cachePlugin for persistence provided to library */ // TODO Temporary placeholder - this will be rewritten by cache PR. diff --git a/samples/msal-node-auth-code/data/cache.json b/samples/msal-node-auth-code/data/cache.json index d3fdd20f5b..04a8a27dba 100644 --- a/samples/msal-node-auth-code/data/cache.json +++ b/samples/msal-node-auth-code/data/cache.json @@ -1,73 +1 @@ -{ - "Account": { - "uid.utid-login.microsoftonline.com-microsoft": { - "username": "John Doe", - "local_account_id": "object1234", - "realm": "microsoft", - "environment": "login.microsoftonline.com", - "home_account_id": "uid.utid", - "authority_type": "MSSTS", - "client_info": "base64encodedjson" - } - }, - "RefreshToken": { - "uid.utid-login.microsoftonline.com-refreshtoken-mock_client_id--": { - "environment": "login.microsoftonline.com", - "credential_type": "RefreshToken", - "secret": "a refresh token", - "client_id": "mock_client_id", - "home_account_id": "uid.utid" - }, - "uid.utid-login.microsoftonline.com-refreshtoken-1--": { - "environment": "login.microsoftonline.com", - "credential_type": "RefreshToken", - "secret": "a refresh token", - "client_id": "mock_client_id", - "home_account_id": "uid.utid", - "familyId": "1" - } - }, - "AccessToken": { - "uid.utid-login.microsoftonline.com-accesstoken-mock_client_id-microsoft-scope1 scope2 scope3": { - "environment": "login.microsoftonline.com", - "credential_type": "AccessToken", - "secret": "an access token", - "realm": "microsoft", - "target": "scope1 scope2 scope3", - "client_id": "mock_client_id", - "cached_at": "1000", - "home_account_id": "uid.utid", - "extended_expires_on": "4600", - "expires_on": "4600" - }, - "uid.utid-login.microsoftonline.com-accesstoken-mock_client_id-microsoft-scope4 scope5": { - "environment": "login.microsoftonline.com", - "credential_type": "AccessToken", - "secret": "an access token", - "realm": "microsoft", - "target": "scope4 scope5", - "client_id": "mock_client_id", - "cached_at": "1000", - "home_account_id": "uid.utid", - "extended_expires_on": "4600", - "expires_on": "4600" - } - }, - "IdToken": { - "uid.utid-login.microsoftonline.com-idtoken-mock_client_id-microsoft-": { - "realm": "microsoft", - "environment": "login.microsoftonline.com", - "credential_type": "IdToken", - "secret": "header.eyJvaWQiOiAib2JqZWN0MTIzNCIsICJwcmVmZXJyZWRfdXNlcm5hbWUiOiAiSm9obiBEb2UiLCAic3ViIjogInN1YiJ9.signature", - "client_id": "mock_client_id", - "home_account_id": "uid.utid" - } - }, - "AppMetadata": { - "appmetadata-login.microsoftonline.com-mock_client_id": { - "environment": "login.microsoftonline.com", - "family_id": "1", - "client_id": "mock_client_id" - } - } -} +{"Account":{},"RefreshToken":{},"AccessToken":{},"IdToken":{},"unknownEntity":{"field1":"1","field2":"whats"},"AppMetadata":{}} \ No newline at end of file diff --git a/samples/msal-node-auth-code/index.js b/samples/msal-node-auth-code/index.js index f78c307397..04249fe70e 100644 --- a/samples/msal-node-auth-code/index.js +++ b/samples/msal-node-auth-code/index.js @@ -4,20 +4,16 @@ */ const express = require("express"); const msal = require('@azure/msal-node'); -const { promises: fs } = require("fs"); +const {promises: fs} = require("fs"); const SERVER_PORT = process.env.PORT || 3000; - const readFromStorage = () => { - return fs.readFile("./data/cache.json"); + return fs.readFile("./data/cache.json", "utf-8"); }; -const writeToStorage = (getMergedState) => { - return readFromStorage() - .then(oldFile => { - const mergedState = getMergedState(oldFile); - return fs.writeFile("./data/cache.json", mergedState) - }); + +const writeToStorage = (cache) => { + return fs.writeFile("./data/cacheAfterWrite.json", cache) }; const cachePlugin = { @@ -28,36 +24,30 @@ const cachePlugin = { const publicClientConfig = { auth: { clientId: "99cab759-2aab-420b-91d8-5e3d8d4f063b", - authority: - "https://login.microsoftonline.com/90b8faa8-cc95-460e-a618-ee770bee1759", + authority: "https://login.microsoftonline.com/90b8faa8-cc95-460e-a618-ee770bee1759", redirectUri: "http://localhost:3000/redirect", }, cache: { - storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge cachePlugin: cachePlugin }, }; const pca = new msal.PublicClientApplication(publicClientConfig); const msalCacheManager = pca.getCacheManager(); - // Create Express App and Routes const app = express(); -app.get('/', (req, res) => { - msalCacheManager.deserialize().then(() => { - const authCodeUrlParameters = { - scopes: ["user.read"], - redirectUri: ["http://localhost:3000/redirect"], - }; +app.get('/', (req, res) => { + const authCodeUrlParameters = { + scopes: ["user.read"], + redirectUri: ["http://localhost:3000/redirect"], + }; - // get url to sign user in and consent to scopes needed for application - pca.getAuthCodeUrl(authCodeUrlParameters).then((response) => { - console.log(response); - res.redirect(response); - }) - .catch((error) => console.log(JSON.stringify(error))); - }); + // get url to sign user in and consent to scopes needed for application + pca.getAuthCodeUrl(authCodeUrlParameters).then((response) => { + console.log(response); + res.redirect(response); + }).catch((error) => console.log(JSON.stringify(error))); }); app.get('/redirect', (req, res) => { @@ -65,20 +55,19 @@ app.get('/redirect', (req, res) => { code: req.query.code, redirectUri: "http://localhost:3000/redirect", scopes: ["user.read"], - // codeVerifier: "" }; pca.acquireTokenByCode(tokenRequest).then((response) => { console.log("\nResponse: \n:", response); res.send(200); - return msalCacheManager.serialize(); + return msalCacheManager.writeToPersistence(); }).catch((error) => { console.log(error); res.status(500).send(error); }); }); -msalCacheManager.deserialize().then(() => { +msalCacheManager.readFromPersistence().then(() => { app.listen(SERVER_PORT, () => console.log(`Msal Node Auth Code Sample app listening on port ${SERVER_PORT}!`)) }); From f0ed215377f169402e5dc6f407dc57ad3ec67b26 Mon Sep 17 00:00:00 2001 From: Santiago Gonzalez Date: Thu, 4 Jun 2020 17:16:47 -0700 Subject: [PATCH 4/7] Revert sample cache.json changes --- samples/msal-node-auth-code/data/cache.json | 74 ++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/samples/msal-node-auth-code/data/cache.json b/samples/msal-node-auth-code/data/cache.json index 04a8a27dba..d3fdd20f5b 100644 --- a/samples/msal-node-auth-code/data/cache.json +++ b/samples/msal-node-auth-code/data/cache.json @@ -1 +1,73 @@ -{"Account":{},"RefreshToken":{},"AccessToken":{},"IdToken":{},"unknownEntity":{"field1":"1","field2":"whats"},"AppMetadata":{}} \ No newline at end of file +{ + "Account": { + "uid.utid-login.microsoftonline.com-microsoft": { + "username": "John Doe", + "local_account_id": "object1234", + "realm": "microsoft", + "environment": "login.microsoftonline.com", + "home_account_id": "uid.utid", + "authority_type": "MSSTS", + "client_info": "base64encodedjson" + } + }, + "RefreshToken": { + "uid.utid-login.microsoftonline.com-refreshtoken-mock_client_id--": { + "environment": "login.microsoftonline.com", + "credential_type": "RefreshToken", + "secret": "a refresh token", + "client_id": "mock_client_id", + "home_account_id": "uid.utid" + }, + "uid.utid-login.microsoftonline.com-refreshtoken-1--": { + "environment": "login.microsoftonline.com", + "credential_type": "RefreshToken", + "secret": "a refresh token", + "client_id": "mock_client_id", + "home_account_id": "uid.utid", + "familyId": "1" + } + }, + "AccessToken": { + "uid.utid-login.microsoftonline.com-accesstoken-mock_client_id-microsoft-scope1 scope2 scope3": { + "environment": "login.microsoftonline.com", + "credential_type": "AccessToken", + "secret": "an access token", + "realm": "microsoft", + "target": "scope1 scope2 scope3", + "client_id": "mock_client_id", + "cached_at": "1000", + "home_account_id": "uid.utid", + "extended_expires_on": "4600", + "expires_on": "4600" + }, + "uid.utid-login.microsoftonline.com-accesstoken-mock_client_id-microsoft-scope4 scope5": { + "environment": "login.microsoftonline.com", + "credential_type": "AccessToken", + "secret": "an access token", + "realm": "microsoft", + "target": "scope4 scope5", + "client_id": "mock_client_id", + "cached_at": "1000", + "home_account_id": "uid.utid", + "extended_expires_on": "4600", + "expires_on": "4600" + } + }, + "IdToken": { + "uid.utid-login.microsoftonline.com-idtoken-mock_client_id-microsoft-": { + "realm": "microsoft", + "environment": "login.microsoftonline.com", + "credential_type": "IdToken", + "secret": "header.eyJvaWQiOiAib2JqZWN0MTIzNCIsICJwcmVmZXJyZWRfdXNlcm5hbWUiOiAiSm9obiBEb2UiLCAic3ViIjogInN1YiJ9.signature", + "client_id": "mock_client_id", + "home_account_id": "uid.utid" + } + }, + "AppMetadata": { + "appmetadata-login.microsoftonline.com-mock_client_id": { + "environment": "login.microsoftonline.com", + "family_id": "1", + "client_id": "mock_client_id" + } + } +} From 04bdfd85cac1b533855dea2a36241c7a4aa91b70 Mon Sep 17 00:00:00 2001 From: Santiago Gonzalez Date: Thu, 4 Jun 2020 18:15:12 -0700 Subject: [PATCH 5/7] Update comments --- lib/msal-common/src/unifiedCache/utils/CacheTypes.ts | 2 +- lib/msal-node/src/cache/CacheManager.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/msal-common/src/unifiedCache/utils/CacheTypes.ts b/lib/msal-common/src/unifiedCache/utils/CacheTypes.ts index d7c8a4bba7..c4f6feed38 100644 --- a/lib/msal-common/src/unifiedCache/utils/CacheTypes.ts +++ b/lib/msal-common/src/unifiedCache/utils/CacheTypes.ts @@ -18,12 +18,12 @@ export type RefreshTokenCache = { [key: string]: RefreshTokenEntity }; export type AppMetadataCache = { [key: string]: AppMetadataEntity }; export type JsonCache = { + [key: string]: StringDict; Account?: StringDict; IdToken?: StringDict; AccessToken?: StringDict; RefreshToken?: StringDict; AppMetadata?: StringDict; - [key: string]: StringDict; }; export type InMemoryCache = { diff --git a/lib/msal-node/src/cache/CacheManager.ts b/lib/msal-node/src/cache/CacheManager.ts index d0caff1cdc..a50e727296 100644 --- a/lib/msal-node/src/cache/CacheManager.ts +++ b/lib/msal-node/src/cache/CacheManager.ts @@ -15,6 +15,9 @@ const defaultSerializedCache: JsonCache = { AppMetadata: {}, }; +/** + * + */ export class CacheManager { private storage: Storage; @@ -65,7 +68,6 @@ export class CacheManager { * Serializes cache into JSON and calls ICachePlugin.writeToStorage. ICachePlugin must be set on ClientApplication */ async writeToPersistence(): Promise { - console.log("writing to persistence"); if (this.persistence) { this.cacheSnapshot = await this.persistence.readFromStorage(); @@ -86,7 +88,6 @@ export class CacheManager { * ICachePlugin must be set on ClientApplication. */ async readFromPersistence(): Promise { - console.log("reading from persistence"); if (this.persistence) { this.cacheSnapshot = await this.persistence.readFromStorage(); const deserializedCache = Deserializer.deserializeAllCache(this.overlayDefaults(JSON.parse(this.cacheSnapshot))); @@ -128,7 +129,7 @@ export class CacheManager { oldState[newKey] = newValue; } } else { - // merge oldState and newState + // both oldState and newState contain the key, do deep update let newValueNotNull = newValue !== null; let newValueIsObject = typeof newValue === 'object'; let newValueIsNotArray = !Array.isArray(newValue); From 40598e8ee1cddbf8e7587738d76a38aa05b0ed26 Mon Sep 17 00:00:00 2001 From: Santiago Gonzalez Date: Mon, 8 Jun 2020 17:30:49 -0700 Subject: [PATCH 6/7] Update Cache Manager --- lib/msal-node/src/cache/CacheManager.ts | 46 +++++++++++++++++-------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/lib/msal-node/src/cache/CacheManager.ts b/lib/msal-node/src/cache/CacheManager.ts index a50e727296..c710d87eab 100644 --- a/lib/msal-node/src/cache/CacheManager.ts +++ b/lib/msal-node/src/cache/CacheManager.ts @@ -4,6 +4,7 @@ import { Deserializer, JsonCache, ClientAuthError, + StringUtils } from '@azure/msal-common'; import { ICachePlugin } from './ICachePlugin'; @@ -16,7 +17,7 @@ const defaultSerializedCache: JsonCache = { }; /** - * + * In-memory token cache manager */ export class CacheManager { @@ -45,13 +46,18 @@ export class CacheManager { * Serializes in memory cache to JSON */ serialize(): string { - const mergedCache = this.mergeState( - JSON.parse(this.cacheSnapshot), - Serializer.serializeAllCache(this.storage.getCache())); + let finalState = Serializer.serializeAllCache(this.storage.getCache()); + // if cacheSnapshot not null or empty, merge + if (!StringUtils.isEmpty(this.cacheSnapshot)) { + finalState = this.mergeState( + JSON.parse(this.cacheSnapshot), + finalState + ); + } this.hasChanged = false; - return JSON.stringify(mergedCache); + return JSON.stringify(finalState); } /** @@ -60,8 +66,11 @@ export class CacheManager { */ deserialize(cache: string): void { this.cacheSnapshot = cache; - const deserializedCache = Deserializer.deserializeAllCache(this.overlayDefaults(JSON.parse(cache))); - this.storage.setCache(deserializedCache); + + if (!StringUtils.isEmpty(this.cacheSnapshot)) { + const deserializedCache = Deserializer.deserializeAllCache(this.overlayDefaults(JSON.parse(this.cacheSnapshot))); + this.storage.setCache(deserializedCache); + } } /** @@ -69,15 +78,19 @@ export class CacheManager { */ async writeToPersistence(): Promise { if (this.persistence) { - this.cacheSnapshot = await this.persistence.readFromStorage(); - const mergedCache = this.mergeState( - JSON.parse(this.cacheSnapshot), - Serializer.serializeAllCache(this.storage.getCache())); + let finalState = Serializer.serializeAllCache(this.storage.getCache()); + // if cacheSnapshot not null or empty, merge + if (!StringUtils.isEmpty(this.cacheSnapshot)) { + finalState = this.mergeState( + JSON.parse(this.cacheSnapshot), + Serializer.serializeAllCache(this.storage.getCache())); + } this.hasChanged = false; - await this.persistence.writeToStorage(JSON.stringify(mergedCache)); + + await this.persistence.writeToStorage(JSON.stringify(finalState)); } else { throw ClientAuthError.createCachePluginError(); } @@ -89,9 +102,12 @@ export class CacheManager { */ async readFromPersistence(): Promise { if (this.persistence) { - this.cacheSnapshot = await this.persistence.readFromStorage(); - const deserializedCache = Deserializer.deserializeAllCache(this.overlayDefaults(JSON.parse(this.cacheSnapshot))); - this.storage.setCache(deserializedCache); + const cache = await this.persistence.readFromStorage(); + + if (!StringUtils.isEmpty(cache)) { + const deserializedCache = Deserializer.deserializeAllCache(this.overlayDefaults(JSON.parse(cache))); + this.storage.setCache(deserializedCache); + } } else { throw ClientAuthError.createCachePluginError(); } From 99236b63305d8d03163c45747d5fa40d7ea1d7e7 Mon Sep 17 00:00:00 2001 From: Santiago Gonzalez Date: Mon, 15 Jun 2020 17:03:27 -0700 Subject: [PATCH 7/7] Add callback to writeToStorage --- lib/msal-node/src/cache/CacheManager.ts | 33 +++++++++++++++---------- lib/msal-node/src/cache/ICachePlugin.ts | 9 ++++++- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/lib/msal-node/src/cache/CacheManager.ts b/lib/msal-node/src/cache/CacheManager.ts index c710d87eab..422d8fb25b 100644 --- a/lib/msal-node/src/cache/CacheManager.ts +++ b/lib/msal-node/src/cache/CacheManager.ts @@ -1,3 +1,8 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + import { Storage } from './Storage'; import { Serializer, @@ -78,19 +83,20 @@ export class CacheManager { */ async writeToPersistence(): Promise { if (this.persistence) { - this.cacheSnapshot = await this.persistence.readFromStorage(); + let cache = Serializer.serializeAllCache(this.storage.getCache()); - let finalState = Serializer.serializeAllCache(this.storage.getCache()); + const getMergedState = (stateFromDisk: string) => { - // if cacheSnapshot not null or empty, merge - if (!StringUtils.isEmpty(this.cacheSnapshot)) { - finalState = this.mergeState( - JSON.parse(this.cacheSnapshot), - Serializer.serializeAllCache(this.storage.getCache())); - } - this.hasChanged = false; + if (!StringUtils.isEmpty(stateFromDisk)) { + this.cacheSnapshot = stateFromDisk; + cache = this.mergeState(JSON.parse(stateFromDisk), cache); + } + + return JSON.stringify(cache); + }; - await this.persistence.writeToStorage(JSON.stringify(finalState)); + await this.persistence.writeToStorage(getMergedState); + this.hasChanged = false; } else { throw ClientAuthError.createCachePluginError(); } @@ -102,10 +108,11 @@ export class CacheManager { */ async readFromPersistence(): Promise { if (this.persistence) { - const cache = await this.persistence.readFromStorage(); + this.cacheSnapshot = await this.persistence.readFromStorage(); - if (!StringUtils.isEmpty(cache)) { - const deserializedCache = Deserializer.deserializeAllCache(this.overlayDefaults(JSON.parse(cache))); + if (!StringUtils.isEmpty(this.cacheSnapshot)) { + const cache = this.overlayDefaults(JSON.parse(this.cacheSnapshot)); + const deserializedCache = Deserializer.deserializeAllCache(cache); this.storage.setCache(deserializedCache); } } else { diff --git a/lib/msal-node/src/cache/ICachePlugin.ts b/lib/msal-node/src/cache/ICachePlugin.ts index 4f7dc1ff71..8d2c8a12bf 100644 --- a/lib/msal-node/src/cache/ICachePlugin.ts +++ b/lib/msal-node/src/cache/ICachePlugin.ts @@ -1,4 +1,11 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + export interface ICachePlugin { readFromStorage: () => Promise; - writeToStorage: (cache: string) => Promise; + writeToStorage: ( + getMergedState: (oldState: string) => string + ) => Promise; }