Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[msal-node] hone surface for msal extension library #1687

Closed
wants to merge 10 commits into from
47 changes: 0 additions & 47 deletions lib/msal-node/src/cache/CacheContext.ts

This file was deleted.

94 changes: 94 additions & 0 deletions lib/msal-node/src/cache/CacheManager.ts
Original file line number Diff line number Diff line change
@@ -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 {
DarylThayil marked this conversation as resolved.
Show resolved Hide resolved

hasChanged: boolean;
storage: Storage;
persistence: ICachePlugin;
DarylThayil marked this conversation as resolved.
Show resolved Hide resolved

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;
DarylThayil marked this conversation as resolved.
Show resolved Hide resolved
}

mergeState(oldState: JsonCache, currentState: JsonCache) {
// TODO
// mergeUpdates(old, new)
// mergeRemovals(old, new)
return {
...oldState,
...currentState
DarylThayil marked this conversation as resolved.
Show resolved Hide resolved
};
}

//TODO think about separating serialize / deserialize from writeToPersistance and readFromPersistance

async serialize(): Promise<string> {
DarylThayil marked this conversation as resolved.
Show resolved Hide resolved
const cache: string = JSON.stringify(Serializer.serializeAllCache(this.storage.getCache()));
if (this.persistence) {
await this.persistence.writeToStorage((stateFromDisk) => {
DarylThayil marked this conversation as resolved.
Show resolved Hide resolved
const jsonFromDisk = JSON.parse(stateFromDisk);
return JSON.stringify(
this.mergeState(
jsonFromDisk,
DarylThayil marked this conversation as resolved.
Show resolved Hide resolved
Serializer.serializeAllCache(this.storage.getCache())
), null, 4
);
});
}
this.hasChanged = false;
return cache;
}

async deserialize(cache?: string): Promise<void> {
DarylThayil marked this conversation as resolved.
Show resolved Hide resolved
let deserializedCache: InMemoryCache;
if (this.persistence) {
const stringCacheFromStorage = await this.persistence.readFromStorage();
deserializedCache = Deserializer.deserializeAllCache(this.overlayDefaults(JSON.parse(stringCacheFromStorage)));
DarylThayil marked this conversation as resolved.
Show resolved Hide resolved
} 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit pick: not a big fan of mutation here, as it doesn't really add much value.

if (this.persistence) {
  const stringCacheFromStorage = await this.persistence.readFromStorage();
  const deserlizedCache = Deserializer.deserializeAllCache(this.overlayDefaults(JSON.parse(stringCacheFromStorage)));
  this.storage.setCache(deserializedCache);
  return;
}

if (cache) {
  const deserializedCache = Deserializer.deserializeAllCache(this.overlayDefaults(JSON.parse(cache)));
  this.storage.setCache(deserializedCache);
  return;
}

throw Error("Cache Must be passed in or configured");

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolving

}

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}
};
}

}
5 changes: 5 additions & 0 deletions lib/msal-node/src/cache/ICachePlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

export interface ICachePlugin {
readFromStorage: () => Promise<string>;
writeToStorage: (getMergedState: (oldState: string) => string) => Promise<void>;
}
34 changes: 26 additions & 8 deletions lib/msal-node/src/cache/Storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Function> = [];

constructor(cacheConfig: CacheOptions) {
this.cacheConfig = cacheConfig;
if (this.cacheConfig.cacheLocation! === "fileCache")
this.inMemoryCache = this.cacheConfig.cacheInMemory!;
constructor() {
}
DarylThayil marked this conversation as resolved.
Show resolved Hide resolved

registerChangeEmitter(func: () => void): void {
this.changeEmitters.push(func);
}

emitChange() {
this.changeEmitters.forEach(func => func.call(null));
}

/**
Expand All @@ -35,6 +45,7 @@ export class Storage implements ICacheStorage {
*/
setCache(inMemoryCache: InMemoryCache) {
this.inMemoryCache = inMemoryCache;
this.emitChange();
}

/**
Expand All @@ -45,8 +56,10 @@ export class Storage implements ICacheStorage {
*/
setItem(key: string, value: string): void {
if (key && value) {
this.emitChange();
return;
}
this.emitChange();
sameerag marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand All @@ -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();
sameerag marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand All @@ -90,6 +107,7 @@ export class Storage implements ICacheStorage {
* Clears all cache entries created by MSAL (except tokens).
*/
clear(): void {
this.emitChange();
return;
}
}
17 changes: 6 additions & 11 deletions lib/msal-node/src/client/ClientApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}

/**
Expand Down Expand Up @@ -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());
}
}
19 changes: 5 additions & 14 deletions lib/msal-node/src/config/Configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
};

/**
Expand Down Expand Up @@ -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 = {
Expand Down
2 changes: 2 additions & 0 deletions lib/msal-node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading