This commit is contained in:
RochesterX
2025-11-12 10:13:24 -05:00
parent d5b0f97adb
commit 6e820464d5
9761 changed files with 706938 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import {
AccountInfo,
AuthenticationScheme,
CredentialEntity,
CredentialType,
} from "@azure/msal-common/node";
import { CACHE } from "../utils/Constants.js";
export function generateCredentialKey(credential: CredentialEntity): string {
const familyId =
(credential.credentialType === CredentialType.REFRESH_TOKEN &&
credential.familyId) ||
credential.clientId;
const scheme =
credential.tokenType &&
credential.tokenType.toLowerCase() !==
AuthenticationScheme.BEARER.toLowerCase()
? credential.tokenType.toLowerCase()
: "";
const credentialKey = [
credential.homeAccountId,
credential.environment,
credential.credentialType,
familyId,
credential.realm || "",
credential.target || "",
credential.requestedClaimsHash || "",
scheme,
];
return credentialKey.join(CACHE.KEY_SEPARATOR).toLowerCase();
}
export function generateAccountKey(account: AccountInfo): string {
const homeTenantId = account.homeAccountId.split(".")[1];
const accountKey = [
account.homeAccountId,
account.environment,
homeTenantId || account.tenantId || "",
];
return accountKey.join(CACHE.KEY_SEPARATOR).toLowerCase();
}

24
node_modules/@azure/msal-node/src/cache/ITokenCache.ts generated vendored Normal file
View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { AccountInfo } from "@azure/msal-common/node";
/**
* Token cache interface for the client, giving access to cache APIs
* @public
*/
export interface ITokenCache {
/** API that retrieves all accounts currently in cache to the user */
getAllAccounts(): Promise<AccountInfo[]>;
/** Returns the signed in account matching homeAccountId */
getAccountByHomeId(homeAccountId: string): Promise<AccountInfo | null>;
/** Returns the signed in account matching localAccountId */
getAccountByLocalId(localAccountId: string): Promise<AccountInfo | null>;
/** API to remove a specific account and the relevant data from cache */
removeAccount(account: AccountInfo): Promise<void>;
}

560
node_modules/@azure/msal-node/src/cache/NodeStorage.ts generated vendored Normal file
View File

@@ -0,0 +1,560 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import {
TokenKeys,
AccountEntity,
IdTokenEntity,
AccessTokenEntity,
RefreshTokenEntity,
AppMetadataEntity,
ServerTelemetryEntity,
ThrottlingEntity,
CacheManager,
Logger,
ValidCacheType,
ICrypto,
AuthorityMetadataEntity,
ValidCredentialType,
StaticAuthorityOptions,
CacheHelpers,
CredentialEntity,
AccountInfo,
} from "@azure/msal-common/node";
import { Deserializer } from "./serializer/Deserializer.js";
import { Serializer } from "./serializer/Serializer.js";
import {
InMemoryCache,
JsonCache,
CacheKVStore,
} from "./serializer/SerializerTypes.js";
import { StubPerformanceClient } from "@azure/msal-common";
import { generateAccountKey, generateCredentialKey } from "./CacheHelpers.js";
/**
* This class implements Storage for node, reading cache from user specified storage location or an extension library
* @public
*/
export class NodeStorage extends CacheManager {
// Cache configuration, either set by user or default values.
private logger: Logger;
private cache: CacheKVStore = {};
private changeEmitters: Array<Function> = [];
constructor(
logger: Logger,
clientId: string,
cryptoImpl: ICrypto,
staticAuthorityOptions?: StaticAuthorityOptions
) {
super(
clientId,
cryptoImpl,
logger,
new StubPerformanceClient(),
staticAuthorityOptions
);
this.logger = logger;
}
/**
* Queue up callbacks
* @param func - a callback function for cache change indication
*/
registerChangeEmitter(func: () => void): void {
this.changeEmitters.push(func);
}
/**
* Invoke the callback when cache changes
*/
emitChange(): void {
this.changeEmitters.forEach((func) => func.call(null));
}
/**
* Converts cacheKVStore to InMemoryCache
* @param cache - key value store
*/
cacheToInMemoryCache(cache: CacheKVStore): InMemoryCache {
const inMemoryCache: InMemoryCache = {
accounts: {},
idTokens: {},
accessTokens: {},
refreshTokens: {},
appMetadata: {},
};
for (const key in cache) {
const value = cache[key];
if (typeof value !== "object") {
continue;
}
if (value instanceof AccountEntity) {
inMemoryCache.accounts[key] = value as AccountEntity;
} else if (CacheHelpers.isIdTokenEntity(value)) {
inMemoryCache.idTokens[key] = value as IdTokenEntity;
} else if (CacheHelpers.isAccessTokenEntity(value)) {
inMemoryCache.accessTokens[key] = value as AccessTokenEntity;
} else if (CacheHelpers.isRefreshTokenEntity(value)) {
inMemoryCache.refreshTokens[key] = value as RefreshTokenEntity;
} else if (CacheHelpers.isAppMetadataEntity(key, value)) {
inMemoryCache.appMetadata[key] = value as AppMetadataEntity;
} else {
continue;
}
}
return inMemoryCache;
}
/**
* converts inMemoryCache to CacheKVStore
* @param inMemoryCache - kvstore map for inmemory
*/
inMemoryCacheToCache(inMemoryCache: InMemoryCache): CacheKVStore {
// convert in memory cache to a flat Key-Value map
let cache = this.getCache();
cache = {
...cache,
...inMemoryCache.accounts,
...inMemoryCache.idTokens,
...inMemoryCache.accessTokens,
...inMemoryCache.refreshTokens,
...inMemoryCache.appMetadata,
};
// convert in memory cache to a flat Key-Value map
return cache;
}
/**
* gets the current in memory cache for the client
*/
getInMemoryCache(): InMemoryCache {
this.logger.trace("Getting in-memory cache");
// convert the cache key value store to inMemoryCache
const inMemoryCache = this.cacheToInMemoryCache(this.getCache());
return inMemoryCache;
}
/**
* sets the current in memory cache for the client
* @param inMemoryCache - key value map in memory
*/
setInMemoryCache(inMemoryCache: InMemoryCache): void {
this.logger.trace("Setting in-memory cache");
// convert and append the inMemoryCache to cacheKVStore
const cache = this.inMemoryCacheToCache(inMemoryCache);
this.setCache(cache);
this.emitChange();
}
/**
* get the current cache key-value store
*/
getCache(): CacheKVStore {
this.logger.trace("Getting cache key-value store");
return this.cache;
}
/**
* sets the current cache (key value store)
* @param cacheMap - key value map
*/
setCache(cache: CacheKVStore): void {
this.logger.trace("Setting cache key value store");
this.cache = cache;
// mark change in cache
this.emitChange();
}
/**
* Gets cache item with given key.
* @param key - lookup key for the cache entry
*/
getItem(key: string): ValidCacheType {
this.logger.tracePii(`Item key: ${key}`);
// read cache
const cache = this.getCache();
return cache[key];
}
/**
* Gets cache item with given key-value
* @param key - lookup key for the cache entry
* @param value - value of the cache entry
*/
setItem(key: string, value: ValidCacheType): void {
this.logger.tracePii(`Item key: ${key}`);
// read cache
const cache = this.getCache();
cache[key] = value;
// write to cache
this.setCache(cache);
}
generateCredentialKey(credential: CredentialEntity): string {
return generateCredentialKey(credential);
}
generateAccountKey(account: AccountInfo): string {
return generateAccountKey(account);
}
getAccountKeys(): string[] {
const inMemoryCache = this.getInMemoryCache();
const accountKeys = Object.keys(inMemoryCache.accounts);
return accountKeys;
}
getTokenKeys(): TokenKeys {
const inMemoryCache = this.getInMemoryCache();
const tokenKeys = {
idToken: Object.keys(inMemoryCache.idTokens),
accessToken: Object.keys(inMemoryCache.accessTokens),
refreshToken: Object.keys(inMemoryCache.refreshTokens),
};
return tokenKeys;
}
/**
* Reads account from cache, builds it into an account entity and returns it.
* @param accountKey - lookup key to fetch cache type AccountEntity
* @returns
*/
getAccount(accountKey: string): AccountEntity | null {
const cachedAccount = this.getItem(accountKey);
return cachedAccount
? Object.assign(new AccountEntity(), this.getItem(accountKey))
: null;
}
/**
* set account entity
* @param account - cache value to be set of type AccountEntity
*/
async setAccount(account: AccountEntity): Promise<void> {
const accountKey = this.generateAccountKey(account.getAccountInfo());
this.setItem(accountKey, account);
}
/**
* fetch the idToken credential
* @param idTokenKey - lookup key to fetch cache type IdTokenEntity
*/
getIdTokenCredential(idTokenKey: string): IdTokenEntity | null {
const idToken = this.getItem(idTokenKey) as IdTokenEntity;
if (CacheHelpers.isIdTokenEntity(idToken)) {
return idToken;
}
return null;
}
/**
* set idToken credential
* @param idToken - cache value to be set of type IdTokenEntity
*/
async setIdTokenCredential(idToken: IdTokenEntity): Promise<void> {
const idTokenKey = this.generateCredentialKey(idToken);
this.setItem(idTokenKey, idToken);
}
/**
* fetch the accessToken credential
* @param accessTokenKey - lookup key to fetch cache type AccessTokenEntity
*/
getAccessTokenCredential(accessTokenKey: string): AccessTokenEntity | null {
const accessToken = this.getItem(accessTokenKey) as AccessTokenEntity;
if (CacheHelpers.isAccessTokenEntity(accessToken)) {
return accessToken;
}
return null;
}
/**
* set accessToken credential
* @param accessToken - cache value to be set of type AccessTokenEntity
*/
async setAccessTokenCredential(
accessToken: AccessTokenEntity
): Promise<void> {
const accessTokenKey = this.generateCredentialKey(accessToken);
this.setItem(accessTokenKey, accessToken);
}
/**
* fetch the refreshToken credential
* @param refreshTokenKey - lookup key to fetch cache type RefreshTokenEntity
*/
getRefreshTokenCredential(
refreshTokenKey: string
): RefreshTokenEntity | null {
const refreshToken = this.getItem(
refreshTokenKey
) as RefreshTokenEntity;
if (CacheHelpers.isRefreshTokenEntity(refreshToken)) {
return refreshToken as RefreshTokenEntity;
}
return null;
}
/**
* set refreshToken credential
* @param refreshToken - cache value to be set of type RefreshTokenEntity
*/
async setRefreshTokenCredential(
refreshToken: RefreshTokenEntity
): Promise<void> {
const refreshTokenKey = this.generateCredentialKey(refreshToken);
this.setItem(refreshTokenKey, refreshToken);
}
/**
* fetch appMetadata entity from the platform cache
* @param appMetadataKey - lookup key to fetch cache type AppMetadataEntity
*/
getAppMetadata(appMetadataKey: string): AppMetadataEntity | null {
const appMetadata: AppMetadataEntity = this.getItem(
appMetadataKey
) as AppMetadataEntity;
if (CacheHelpers.isAppMetadataEntity(appMetadataKey, appMetadata)) {
return appMetadata;
}
return null;
}
/**
* set appMetadata entity to the platform cache
* @param appMetadata - cache value to be set of type AppMetadataEntity
*/
setAppMetadata(appMetadata: AppMetadataEntity): void {
const appMetadataKey = CacheHelpers.generateAppMetadataKey(appMetadata);
this.setItem(appMetadataKey, appMetadata);
}
/**
* fetch server telemetry entity from the platform cache
* @param serverTelemetrykey - lookup key to fetch cache type ServerTelemetryEntity
*/
getServerTelemetry(
serverTelemetrykey: string
): ServerTelemetryEntity | null {
const serverTelemetryEntity: ServerTelemetryEntity = this.getItem(
serverTelemetrykey
) as ServerTelemetryEntity;
if (
serverTelemetryEntity &&
CacheHelpers.isServerTelemetryEntity(
serverTelemetrykey,
serverTelemetryEntity
)
) {
return serverTelemetryEntity;
}
return null;
}
/**
* set server telemetry entity to the platform cache
* @param serverTelemetryKey - lookup key to fetch cache type ServerTelemetryEntity
* @param serverTelemetry - cache value to be set of type ServerTelemetryEntity
*/
setServerTelemetry(
serverTelemetryKey: string,
serverTelemetry: ServerTelemetryEntity
): void {
this.setItem(serverTelemetryKey, serverTelemetry);
}
/**
* fetch authority metadata entity from the platform cache
* @param key - lookup key to fetch cache type AuthorityMetadataEntity
*/
getAuthorityMetadata(key: string): AuthorityMetadataEntity | null {
const authorityMetadataEntity: AuthorityMetadataEntity = this.getItem(
key
) as AuthorityMetadataEntity;
if (
authorityMetadataEntity &&
CacheHelpers.isAuthorityMetadataEntity(key, authorityMetadataEntity)
) {
return authorityMetadataEntity;
}
return null;
}
/**
* Get all authority metadata keys
*/
getAuthorityMetadataKeys(): Array<string> {
return this.getKeys().filter((key) => {
return this.isAuthorityMetadata(key);
});
}
/**
* set authority metadata entity to the platform cache
* @param key - lookup key to fetch cache type AuthorityMetadataEntity
* @param metadata - cache value to be set of type AuthorityMetadataEntity
*/
setAuthorityMetadata(key: string, metadata: AuthorityMetadataEntity): void {
this.setItem(key, metadata);
}
/**
* fetch throttling entity from the platform cache
* @param throttlingCacheKey - lookup key to fetch cache type ThrottlingEntity
*/
getThrottlingCache(throttlingCacheKey: string): ThrottlingEntity | null {
const throttlingCache: ThrottlingEntity = this.getItem(
throttlingCacheKey
) as ThrottlingEntity;
if (
throttlingCache &&
CacheHelpers.isThrottlingEntity(throttlingCacheKey, throttlingCache)
) {
return throttlingCache;
}
return null;
}
/**
* set throttling entity to the platform cache
* @param throttlingCacheKey - lookup key to fetch cache type ThrottlingEntity
* @param throttlingCache - cache value to be set of type ThrottlingEntity
*/
setThrottlingCache(
throttlingCacheKey: string,
throttlingCache: ThrottlingEntity
): void {
this.setItem(throttlingCacheKey, throttlingCache);
}
/**
* Removes the cache item from memory with the given key.
* @param key - lookup key to remove a cache entity
* @param inMemory - key value map of the cache
*/
removeItem(key: string): boolean {
this.logger.tracePii(`Item key: ${key}`);
// read inMemoryCache
let result: boolean = false;
const cache = this.getCache();
if (!!cache[key]) {
delete cache[key];
result = true;
}
// write to the cache after removal
if (result) {
this.setCache(cache);
this.emitChange();
}
return result;
}
/**
* Remove account entity from the platform cache if it's outdated
* @param accountKey - lookup key to fetch cache type AccountEntity
*/
removeOutdatedAccount(accountKey: string): void {
this.removeItem(accountKey);
}
/**
* Checks whether key is in cache.
* @param key - look up key for a cache entity
*/
containsKey(key: string): boolean {
return this.getKeys().includes(key);
}
/**
* Gets all keys in window.
*/
getKeys(): string[] {
this.logger.trace("Retrieving all cache keys");
// read cache
const cache = this.getCache();
return [...Object.keys(cache)];
}
/**
* Clears all cache entries created by MSAL (except tokens).
*/
clear(): void {
this.logger.trace("Clearing cache entries created by MSAL");
// read inMemoryCache
const cacheKeys = this.getKeys();
// delete each element
cacheKeys.forEach((key) => {
this.removeItem(key);
});
this.emitChange();
}
/**
* Initialize in memory cache from an exisiting cache vault
* @param cache - blob formatted cache (JSON)
*/
static generateInMemoryCache(cache: string): InMemoryCache {
return Deserializer.deserializeAllCache(
Deserializer.deserializeJSONBlob(cache)
);
}
/**
* retrieves the final JSON
* @param inMemoryCache - itemised cache read from the JSON
*/
static generateJsonCache(inMemoryCache: InMemoryCache): JsonCache {
return Serializer.serializeAllCache(inMemoryCache);
}
/**
* Updates a credential's cache key if the current cache key is outdated
*/
updateCredentialCacheKey(
currentCacheKey: string,
credential: ValidCredentialType
): string {
const updatedCacheKey = this.generateCredentialKey(credential);
if (currentCacheKey !== updatedCacheKey) {
const cacheItem = this.getItem(currentCacheKey);
if (cacheItem) {
this.removeItem(currentCacheKey);
this.setItem(updatedCacheKey, cacheItem);
this.logger.verbose(
`Updated an outdated ${credential.credentialType} cache key`
);
return updatedCacheKey;
} else {
this.logger.error(
`Attempted to update an outdated ${credential.credentialType} cache key but no item matching the outdated key was found in storage`
);
}
}
return currentCacheKey;
}
}

394
node_modules/@azure/msal-node/src/cache/TokenCache.ts generated vendored Normal file
View File

@@ -0,0 +1,394 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { NodeStorage } from "./NodeStorage.js";
import {
AccountInfo,
Logger,
ISerializableTokenCache,
ICachePlugin,
TokenCacheContext,
} from "@azure/msal-common/node";
import {
InMemoryCache,
JsonCache,
SerializedAccountEntity,
SerializedAccessTokenEntity,
SerializedRefreshTokenEntity,
SerializedIdTokenEntity,
SerializedAppMetadataEntity,
CacheKVStore,
} from "./serializer/SerializerTypes.js";
import { Deserializer } from "./serializer/Deserializer.js";
import { Serializer } from "./serializer/Serializer.js";
import { ITokenCache } from "./ITokenCache.js";
import { CryptoProvider } from "../crypto/CryptoProvider.js";
import { GuidGenerator } from "../crypto/GuidGenerator.js";
const defaultSerializedCache: JsonCache = {
Account: {},
IdToken: {},
AccessToken: {},
RefreshToken: {},
AppMetadata: {},
};
/**
* In-memory token cache manager
* @public
*/
export class TokenCache implements ISerializableTokenCache, ITokenCache {
private storage: NodeStorage;
private cacheHasChanged: boolean;
private cacheSnapshot: string;
public readonly persistence: ICachePlugin;
private logger: Logger;
constructor(
storage: NodeStorage,
logger: Logger,
cachePlugin?: ICachePlugin
) {
this.cacheHasChanged = false;
this.storage = storage;
this.storage.registerChangeEmitter(this.handleChangeEvent.bind(this));
if (cachePlugin) {
this.persistence = cachePlugin;
}
this.logger = logger;
}
/**
* Set to true if cache state has changed since last time serialize or writeToPersistence was called
*/
hasChanged(): boolean {
return this.cacheHasChanged;
}
/**
* Serializes in memory cache to JSON
*/
serialize(): string {
this.logger.trace("Serializing in-memory cache");
let finalState = Serializer.serializeAllCache(
this.storage.getInMemoryCache() as InMemoryCache
);
// if cacheSnapshot not null or empty, merge
if (this.cacheSnapshot) {
this.logger.trace("Reading cache snapshot from disk");
finalState = this.mergeState(
JSON.parse(this.cacheSnapshot),
finalState
);
} else {
this.logger.trace("No cache snapshot to merge");
}
this.cacheHasChanged = false;
return JSON.stringify(finalState);
}
/**
* Deserializes JSON to in-memory cache. JSON should be in MSAL cache schema format
* @param cache - blob formatted cache
*/
deserialize(cache: string): void {
this.logger.trace("Deserializing JSON to in-memory cache");
this.cacheSnapshot = cache;
if (this.cacheSnapshot) {
this.logger.trace("Reading cache snapshot from disk");
const deserializedCache = Deserializer.deserializeAllCache(
this.overlayDefaults(JSON.parse(this.cacheSnapshot))
);
this.storage.setInMemoryCache(deserializedCache);
} else {
this.logger.trace("No cache snapshot to deserialize");
}
}
/**
* Fetches the cache key-value map
*/
getKVStore(): CacheKVStore {
return this.storage.getCache();
}
/**
* Gets cache snapshot in CacheKVStore format
*/
getCacheSnapshot(): CacheKVStore {
const deserializedPersistentStorage = NodeStorage.generateInMemoryCache(
this.cacheSnapshot
);
return this.storage.inMemoryCacheToCache(deserializedPersistentStorage);
}
/**
* API that retrieves all accounts currently in cache to the user
*/
async getAllAccounts(
correlationId: string = new CryptoProvider().createNewGuid()
): Promise<AccountInfo[]> {
this.logger.trace("getAllAccounts called");
let cacheContext;
try {
if (this.persistence) {
cacheContext = new TokenCacheContext(this, false);
await this.persistence.beforeCacheAccess(cacheContext);
}
return this.storage.getAllAccounts({}, correlationId);
} finally {
if (this.persistence && cacheContext) {
await this.persistence.afterCacheAccess(cacheContext);
}
}
}
/**
* Returns the signed in account matching homeAccountId.
* (the account object is created at the time of successful login)
* or null when no matching account is found
* @param homeAccountId - unique identifier for an account (uid.utid)
*/
async getAccountByHomeId(
homeAccountId: string
): Promise<AccountInfo | null> {
const allAccounts = await this.getAllAccounts();
if (homeAccountId && allAccounts && allAccounts.length) {
return (
allAccounts.filter(
(accountObj) => accountObj.homeAccountId === homeAccountId
)[0] || null
);
} else {
return null;
}
}
/**
* Returns the signed in account matching localAccountId.
* (the account object is created at the time of successful login)
* or null when no matching account is found
* @param localAccountId - unique identifier of an account (sub/obj when homeAccountId cannot be populated)
*/
async getAccountByLocalId(
localAccountId: string
): Promise<AccountInfo | null> {
const allAccounts = await this.getAllAccounts();
if (localAccountId && allAccounts && allAccounts.length) {
return (
allAccounts.filter(
(accountObj) => accountObj.localAccountId === localAccountId
)[0] || null
);
} else {
return null;
}
}
/**
* API to remove a specific account and the relevant data from cache
* @param account - AccountInfo passed by the user
*/
async removeAccount(
account: AccountInfo,
correlationId?: string
): Promise<void> {
this.logger.trace("removeAccount called");
let cacheContext;
try {
if (this.persistence) {
cacheContext = new TokenCacheContext(this, true);
await this.persistence.beforeCacheAccess(cacheContext);
}
this.storage.removeAccount(
account,
correlationId || new GuidGenerator().generateGuid()
);
} finally {
if (this.persistence && cacheContext) {
await this.persistence.afterCacheAccess(cacheContext);
}
}
}
/**
* Overwrites in-memory cache with persistent cache
*/
async overwriteCache(): Promise<void> {
if (!this.persistence) {
this.logger.info(
"No persistence layer specified, cache cannot be overwritten"
);
return;
}
this.logger.info("Overwriting in-memory cache with persistent cache");
this.storage.clear();
const cacheContext = new TokenCacheContext(this, false);
await this.persistence.beforeCacheAccess(cacheContext);
const cacheSnapshot = this.getCacheSnapshot();
this.storage.setCache(cacheSnapshot);
await this.persistence.afterCacheAccess(cacheContext);
}
/**
* Called when the cache has changed state.
*/
private handleChangeEvent() {
this.cacheHasChanged = true;
}
/**
* Merge in memory cache with the cache snapshot.
* @param oldState - cache before changes
* @param currentState - current cache state in the library
*/
private mergeState(
oldState: JsonCache,
currentState: JsonCache
): JsonCache {
this.logger.trace("Merging in-memory cache with cache snapshot");
const stateAfterRemoval = this.mergeRemovals(oldState, currentState);
return this.mergeUpdates(stateAfterRemoval, currentState);
}
/**
* Deep update of oldState based on newState values
* @param oldState - cache before changes
* @param newState - updated cache
*/
private mergeUpdates(oldState: object, newState: object): JsonCache {
Object.keys(newState).forEach((newKey: string) => {
const 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 {
// both oldState and newState contain the key, do deep update
const newValueNotNull = newValue !== null;
const newValueIsObject = typeof newValue === "object";
const newValueIsNotArray = !Array.isArray(newValue);
const oldStateNotUndefinedOrNull =
typeof oldState[newKey] !== "undefined" &&
oldState[newKey] !== null;
if (
newValueNotNull &&
newValueIsObject &&
newValueIsNotArray &&
oldStateNotUndefinedOrNull
) {
this.mergeUpdates(oldState[newKey], newValue);
} else {
oldState[newKey] = newValue;
}
}
});
return oldState as JsonCache;
}
/**
* 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 - cache before changes
* @param newState - updated cache
*/
private mergeRemovals(oldState: JsonCache, newState: JsonCache): JsonCache {
this.logger.trace("Remove updated entries in cache");
const accounts = oldState.Account
? this.mergeRemovalsDict<SerializedAccountEntity>(
oldState.Account,
newState.Account
)
: oldState.Account;
const accessTokens = oldState.AccessToken
? this.mergeRemovalsDict<SerializedAccessTokenEntity>(
oldState.AccessToken,
newState.AccessToken
)
: oldState.AccessToken;
const refreshTokens = oldState.RefreshToken
? this.mergeRemovalsDict<SerializedRefreshTokenEntity>(
oldState.RefreshToken,
newState.RefreshToken
)
: oldState.RefreshToken;
const idTokens = oldState.IdToken
? this.mergeRemovalsDict<SerializedIdTokenEntity>(
oldState.IdToken,
newState.IdToken
)
: oldState.IdToken;
const appMetadata = oldState.AppMetadata
? this.mergeRemovalsDict<SerializedAppMetadataEntity>(
oldState.AppMetadata,
newState.AppMetadata
)
: oldState.AppMetadata;
return {
...oldState,
Account: accounts,
AccessToken: accessTokens,
RefreshToken: refreshTokens,
IdToken: idTokens,
AppMetadata: appMetadata,
};
}
/**
* Helper to merge new cache with the old one
* @param oldState - cache before changes
* @param newState - updated cache
*/
private mergeRemovalsDict<T>(
oldState: Record<string, T>,
newState?: Record<string, T>
): Record<string, T> {
const finalState = { ...oldState };
Object.keys(oldState).forEach((oldKey) => {
if (!newState || !newState.hasOwnProperty(oldKey)) {
delete finalState[oldKey];
}
});
return finalState;
}
/**
* Helper to overlay as a part of cache merge
* @param passedInCache - cache read from the blob
*/
private overlayDefaults(passedInCache: JsonCache): JsonCache {
this.logger.trace("Overlaying input cache with the default cache");
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,
},
};
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import {
AccountEntity,
ICachePlugin,
TokenCacheContext,
} from "@azure/msal-common/node";
import { TokenCache } from "../TokenCache.js";
import { IPartitionManager } from "./IPartitionManager.js";
import { ICacheClient } from "./ICacheClient.js";
/**
* Cache plugin that serializes data to the cache and deserializes data from the cache
* @public
*/
export class DistributedCachePlugin implements ICachePlugin {
private client: ICacheClient;
private partitionManager: IPartitionManager;
constructor(client: ICacheClient, partitionManager: IPartitionManager) {
this.client = client;
this.partitionManager = partitionManager;
}
/**
* Deserializes the cache before accessing it
* @param cacheContext - TokenCacheContext
*/
public async beforeCacheAccess(
cacheContext: TokenCacheContext
): Promise<void> {
const partitionKey = await this.partitionManager.getKey();
const cacheData = await this.client.get(partitionKey);
cacheContext.tokenCache.deserialize(cacheData);
}
/**
* Serializes the cache after accessing it
* @param cacheContext - TokenCacheContext
*/
public async afterCacheAccess(
cacheContext: TokenCacheContext
): Promise<void> {
if (cacheContext.cacheHasChanged) {
const kvStore = (
cacheContext.tokenCache as TokenCache
).getKVStore();
const accountEntities = Object.values(kvStore).filter((value) =>
AccountEntity.isAccountEntity(value as object)
);
let partitionKey: string;
if (accountEntities.length > 0) {
const accountEntity = accountEntities[0] as AccountEntity;
partitionKey = await this.partitionManager.extractKey(
accountEntity
);
} else {
partitionKey = await this.partitionManager.getKey();
}
await this.client.set(
partitionKey,
cacheContext.tokenCache.serialize()
);
}
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
/**
* Interface for the cache that defines a getter and setter
* @public
*/
export interface ICacheClient {
/**
* Retrieve the value from the cache
*
* @param key - key of item in the cache
* @returns Promise<string>
*/
get(key: string): Promise<string>;
/**
* Save the required value using the provided key to cache
*
* @param key - key of item in the cache
* @param value - value of item to be saved in the cache
* @returns Promise<string>
*/
set(key: string, value: string): Promise<string>;
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { AccountEntity } from "@azure/msal-common/node";
/**
* Interface that defines getter methods to get keys used to identity data in the cache
* @public
*/
export interface IPartitionManager {
/**
* This function should return the correct key from which to read
* the specific user's information from cache.
*
* Example: Your application may be partitioning the user's cache
* information for each user using the homeAccountId and thus
* this function would return the homeAccountId for
* the user in question
*
* @returns Promise<string>
*/
getKey(): Promise<string>;
/**
* This function should return the correct key being used to save each
* user's cache information to cache - given an AccountEntity
*
* Example: Your application may be partitioning the user's cache
* information for each user using the homeAccountId thus
* this function would return the homeAccountId from
* the provided AccountEntity
*
* @param accountEntity - AccountEntity
* @returns Promise<string>
*/
extractKey(accountEntity: AccountEntity): Promise<string>;
}

View File

@@ -0,0 +1,222 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import {
AccountCache,
IdTokenCache,
AccessTokenCache,
RefreshTokenCache,
AppMetadataCache,
AccountEntity,
IdTokenEntity,
AccessTokenEntity,
RefreshTokenEntity,
CacheManager,
CredentialType,
AuthenticationScheme,
} from "@azure/msal-common/node";
import {
JsonCache,
InMemoryCache,
SerializedAccountEntity,
SerializedIdTokenEntity,
SerializedAccessTokenEntity,
SerializedRefreshTokenEntity,
SerializedAppMetadataEntity,
} from "./SerializerTypes.js";
/**
* This class deserializes cache entities read from the file into in-memory object types defined internally
* @internal
*/
export class Deserializer {
/**
* Parse the JSON blob in memory and deserialize the content
* @param cachedJson - JSON blob cache
*/
static deserializeJSONBlob(jsonFile: string): JsonCache {
const deserializedCache = !jsonFile ? {} : JSON.parse(jsonFile);
return deserializedCache;
}
/**
* Deserializes accounts to AccountEntity objects
* @param accounts - accounts of type SerializedAccountEntity
*/
static deserializeAccounts(
accounts: Record<string, SerializedAccountEntity>
): AccountCache {
const accountObjects: AccountCache = {};
if (accounts) {
Object.keys(accounts).map(function (key) {
const serializedAcc = accounts[key];
const mappedAcc = {
homeAccountId: serializedAcc.home_account_id,
environment: serializedAcc.environment,
realm: serializedAcc.realm,
localAccountId: serializedAcc.local_account_id,
username: serializedAcc.username,
authorityType: serializedAcc.authority_type,
name: serializedAcc.name,
clientInfo: serializedAcc.client_info,
lastModificationTime: serializedAcc.last_modification_time,
lastModificationApp: serializedAcc.last_modification_app,
tenantProfiles: serializedAcc.tenantProfiles?.map(
(serializedTenantProfile) => {
return JSON.parse(serializedTenantProfile);
}
),
lastUpdatedAt: Date.now().toString(),
};
const account: AccountEntity = new AccountEntity();
CacheManager.toObject(account, mappedAcc);
accountObjects[key] = account;
});
}
return accountObjects;
}
/**
* Deserializes id tokens to IdTokenEntity objects
* @param idTokens - credentials of type SerializedIdTokenEntity
*/
static deserializeIdTokens(
idTokens: Record<string, SerializedIdTokenEntity>
): IdTokenCache {
const idObjects: IdTokenCache = {};
if (idTokens) {
Object.keys(idTokens).map(function (key) {
const serializedIdT = idTokens[key];
const idToken: IdTokenEntity = {
homeAccountId: serializedIdT.home_account_id,
environment: serializedIdT.environment,
credentialType:
serializedIdT.credential_type as CredentialType,
clientId: serializedIdT.client_id,
secret: serializedIdT.secret,
realm: serializedIdT.realm,
lastUpdatedAt: Date.now().toString(),
};
idObjects[key] = idToken;
});
}
return idObjects;
}
/**
* Deserializes access tokens to AccessTokenEntity objects
* @param accessTokens - access tokens of type SerializedAccessTokenEntity
*/
static deserializeAccessTokens(
accessTokens: Record<string, SerializedAccessTokenEntity>
): AccessTokenCache {
const atObjects: AccessTokenCache = {};
if (accessTokens) {
Object.keys(accessTokens).map(function (key) {
const serializedAT = accessTokens[key];
const accessToken: AccessTokenEntity = {
homeAccountId: serializedAT.home_account_id,
environment: serializedAT.environment,
credentialType:
serializedAT.credential_type as CredentialType,
clientId: serializedAT.client_id,
secret: serializedAT.secret,
realm: serializedAT.realm,
target: serializedAT.target,
cachedAt: serializedAT.cached_at,
expiresOn: serializedAT.expires_on,
extendedExpiresOn: serializedAT.extended_expires_on,
refreshOn: serializedAT.refresh_on,
keyId: serializedAT.key_id,
tokenType: serializedAT.token_type as AuthenticationScheme,
requestedClaims: serializedAT.requestedClaims,
requestedClaimsHash: serializedAT.requestedClaimsHash,
userAssertionHash: serializedAT.userAssertionHash,
lastUpdatedAt: Date.now().toString(),
};
atObjects[key] = accessToken;
});
}
return atObjects;
}
/**
* Deserializes refresh tokens to RefreshTokenEntity objects
* @param refreshTokens - refresh tokens of type SerializedRefreshTokenEntity
*/
static deserializeRefreshTokens(
refreshTokens: Record<string, SerializedRefreshTokenEntity>
): RefreshTokenCache {
const rtObjects: RefreshTokenCache = {};
if (refreshTokens) {
Object.keys(refreshTokens).map(function (key) {
const serializedRT = refreshTokens[key];
const refreshToken: RefreshTokenEntity = {
homeAccountId: serializedRT.home_account_id,
environment: serializedRT.environment,
credentialType:
serializedRT.credential_type as CredentialType,
clientId: serializedRT.client_id,
secret: serializedRT.secret,
familyId: serializedRT.family_id,
target: serializedRT.target,
realm: serializedRT.realm,
lastUpdatedAt: Date.now().toString(),
};
rtObjects[key] = refreshToken;
});
}
return rtObjects;
}
/**
* Deserializes appMetadata to AppMetaData objects
* @param appMetadata - app metadata of type SerializedAppMetadataEntity
*/
static deserializeAppMetadata(
appMetadata: Record<string, SerializedAppMetadataEntity>
): AppMetadataCache {
const appMetadataObjects: AppMetadataCache = {};
if (appMetadata) {
Object.keys(appMetadata).map(function (key) {
const serializedAmdt = appMetadata[key];
appMetadataObjects[key] = {
clientId: serializedAmdt.client_id,
environment: serializedAmdt.environment,
familyId: serializedAmdt.family_id,
};
});
}
return appMetadataObjects;
}
/**
* Deserialize an inMemory Cache
* @param jsonCache - JSON blob cache
*/
static deserializeAllCache(jsonCache: JsonCache): InMemoryCache {
return {
accounts: jsonCache.Account
? this.deserializeAccounts(jsonCache.Account)
: {},
idTokens: jsonCache.IdToken
? this.deserializeIdTokens(jsonCache.IdToken)
: {},
accessTokens: jsonCache.AccessToken
? this.deserializeAccessTokens(jsonCache.AccessToken)
: {},
refreshTokens: jsonCache.RefreshToken
? this.deserializeRefreshTokens(jsonCache.RefreshToken)
: {},
appMetadata: jsonCache.AppMetadata
? this.deserializeAppMetadata(jsonCache.AppMetadata)
: {},
};
}
}

View File

@@ -0,0 +1,182 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import {
AccountCache,
IdTokenCache,
AccessTokenCache,
RefreshTokenCache,
AppMetadataCache,
} from "@azure/msal-common/node";
import {
InMemoryCache,
JsonCache,
SerializedAccountEntity,
SerializedIdTokenEntity,
SerializedAccessTokenEntity,
SerializedRefreshTokenEntity,
SerializedAppMetadataEntity,
} from "./SerializerTypes.js";
/**
* This class serializes cache entities to be saved into in-memory object types defined internally
* @internal
*/
export class Serializer {
/**
* serialize the JSON blob
* @param data - JSON blob cache
*/
static serializeJSONBlob(data: JsonCache): string {
return JSON.stringify(data);
}
/**
* Serialize Accounts
* @param accCache - cache of accounts
*/
static serializeAccounts(
accCache: AccountCache
): Record<string, SerializedAccountEntity> {
const accounts: Record<string, SerializedAccountEntity> = {};
Object.keys(accCache).map(function (key) {
const accountEntity = accCache[key];
accounts[key] = {
home_account_id: accountEntity.homeAccountId,
environment: accountEntity.environment,
realm: accountEntity.realm,
local_account_id: accountEntity.localAccountId,
username: accountEntity.username,
authority_type: accountEntity.authorityType,
name: accountEntity.name,
client_info: accountEntity.clientInfo,
last_modification_time: accountEntity.lastModificationTime,
last_modification_app: accountEntity.lastModificationApp,
tenantProfiles: accountEntity.tenantProfiles?.map(
(tenantProfile) => {
return JSON.stringify(tenantProfile);
}
),
};
});
return accounts;
}
/**
* Serialize IdTokens
* @param idTCache - cache of ID tokens
*/
static serializeIdTokens(
idTCache: IdTokenCache
): Record<string, SerializedIdTokenEntity> {
const idTokens: Record<string, SerializedIdTokenEntity> = {};
Object.keys(idTCache).map(function (key) {
const idTEntity = idTCache[key];
idTokens[key] = {
home_account_id: idTEntity.homeAccountId,
environment: idTEntity.environment,
credential_type: idTEntity.credentialType,
client_id: idTEntity.clientId,
secret: idTEntity.secret,
realm: idTEntity.realm,
};
});
return idTokens;
}
/**
* Serializes AccessTokens
* @param atCache - cache of access tokens
*/
static serializeAccessTokens(
atCache: AccessTokenCache
): Record<string, SerializedAccessTokenEntity> {
const accessTokens: Record<string, SerializedAccessTokenEntity> = {};
Object.keys(atCache).map(function (key) {
const atEntity = atCache[key];
accessTokens[key] = {
home_account_id: atEntity.homeAccountId,
environment: atEntity.environment,
credential_type: atEntity.credentialType,
client_id: atEntity.clientId,
secret: atEntity.secret,
realm: atEntity.realm,
target: atEntity.target,
cached_at: atEntity.cachedAt,
expires_on: atEntity.expiresOn,
extended_expires_on: atEntity.extendedExpiresOn,
refresh_on: atEntity.refreshOn,
key_id: atEntity.keyId,
token_type: atEntity.tokenType,
requestedClaims: atEntity.requestedClaims,
requestedClaimsHash: atEntity.requestedClaimsHash,
userAssertionHash: atEntity.userAssertionHash,
};
});
return accessTokens;
}
/**
* Serialize refreshTokens
* @param rtCache - cache of refresh tokens
*/
static serializeRefreshTokens(
rtCache: RefreshTokenCache
): Record<string, SerializedRefreshTokenEntity> {
const refreshTokens: Record<string, SerializedRefreshTokenEntity> = {};
Object.keys(rtCache).map(function (key) {
const rtEntity = rtCache[key];
refreshTokens[key] = {
home_account_id: rtEntity.homeAccountId,
environment: rtEntity.environment,
credential_type: rtEntity.credentialType,
client_id: rtEntity.clientId,
secret: rtEntity.secret,
family_id: rtEntity.familyId,
target: rtEntity.target,
realm: rtEntity.realm,
};
});
return refreshTokens;
}
/**
* Serialize amdtCache
* @param amdtCache - cache of app metadata
*/
static serializeAppMetadata(
amdtCache: AppMetadataCache
): Record<string, SerializedAppMetadataEntity> {
const appMetadata: Record<string, SerializedAppMetadataEntity> = {};
Object.keys(amdtCache).map(function (key) {
const amdtEntity = amdtCache[key];
appMetadata[key] = {
client_id: amdtEntity.clientId,
environment: amdtEntity.environment,
family_id: amdtEntity.familyId,
};
});
return appMetadata;
}
/**
* Serialize the cache
* @param inMemCache - itemised cache read from the JSON
*/
static serializeAllCache(inMemCache: InMemoryCache): JsonCache {
return {
Account: this.serializeAccounts(inMemCache.accounts),
IdToken: this.serializeIdTokens(inMemCache.idTokens),
AccessToken: this.serializeAccessTokens(inMemCache.accessTokens),
RefreshToken: this.serializeRefreshTokens(inMemCache.refreshTokens),
AppMetadata: this.serializeAppMetadata(inMemCache.appMetadata),
};
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import {
AccountCache,
IdTokenCache,
AccessTokenCache,
RefreshTokenCache,
AppMetadataCache,
ValidCacheType,
} from "@azure/msal-common/node";
/**
* Key value store for in-memory cache
* @public
*/
export type CacheKVStore = Record<string, ValidCacheType>;
/**
* Cache format read from the cache blob provided to the configuration during app instantiation
* @public
*/
export type JsonCache = {
Account: Record<string, SerializedAccountEntity>;
IdToken: Record<string, SerializedIdTokenEntity>;
AccessToken: Record<string, SerializedAccessTokenEntity>;
RefreshToken: Record<string, SerializedRefreshTokenEntity>;
AppMetadata: Record<string, SerializedAppMetadataEntity>;
};
/**
* Intermittent type to handle in-memory data objects with defined types
* @public
*/
export type InMemoryCache = {
accounts: AccountCache;
idTokens: IdTokenCache;
accessTokens: AccessTokenCache;
refreshTokens: RefreshTokenCache;
appMetadata: AppMetadataCache;
};
/**
* Account type
* @public
*/
export type SerializedAccountEntity = {
home_account_id: string;
environment: string;
realm: string;
local_account_id: string;
username: string;
authority_type: string;
name?: string;
client_info?: string;
last_modification_time?: string;
last_modification_app?: string;
tenantProfiles?: string[];
};
/**
* Idtoken credential type
* @public
*/
export type SerializedIdTokenEntity = {
home_account_id: string;
environment: string;
credential_type: string;
client_id: string;
secret: string;
realm: string;
};
/**
* Access token credential type
* @public
*/
export type SerializedAccessTokenEntity = {
home_account_id: string;
environment: string;
credential_type: string;
client_id: string;
secret: string;
realm: string;
target: string;
cached_at: string;
expires_on: string;
extended_expires_on?: string;
refresh_on?: string;
key_id?: string;
token_type?: string;
requestedClaims?: string;
requestedClaimsHash?: string;
userAssertionHash?: string;
};
/**
* Refresh token credential type
* @public
*/
export type SerializedRefreshTokenEntity = {
home_account_id: string;
environment: string;
credential_type: string;
client_id: string;
secret: string;
family_id?: string;
target?: string;
realm?: string;
};
/**
* AppMetadata type
* @public
*/
export type SerializedAppMetadataEntity = {
client_id: string;
environment: string;
family_id?: string;
};