src/core/swa-cli-persistence-plugin/persistence-cache-plugin.ts (44 lines of code) (raw):

import { TokenCachePersistenceOptions } from "@azure/identity"; import { ICachePlugin, TokenCacheContext } from "@azure/msal-common"; import { logger } from "../utils/logger.js"; import { Environment } from "./impl/azure-environment.js"; import { NativeCredentialsStore } from "./impl/credentials-store.js"; import { CryptoService } from "./impl/crypto.js"; import { getMachineId } from "./impl/machine-identifier.js"; import { SecretStorage } from "./impl/secret-storage.js"; export interface SWACLIPersistenceCacheOptions { enableCache: boolean; clearCache: boolean; } /** * SWA CLI cache plugin which enables callers to write the MSAL cache to disk on Windows, * macOs, and Linux using native keychain. */ export class SWACLIPersistenceCachePlugin implements ICachePlugin { constructor(private options: TokenCachePersistenceOptions) {} /** * Reads from storage and saves an in-memory copy. If keychain has not been updated * since last time data was read, in memory copy is used. * * If cacheContext.cacheHasChanged === true, then file lock is created and not deleted until * afterCacheAccess() is called, to prevent the cache file from changing in between * beforeCacheAccess() and afterCacheAccess(). */ public async beforeCacheAccess(cacheContext: TokenCacheContext): Promise<void> { logger.silly(`Executing before cache access plugin`); const machineId = await getMachineId(); logger.silly(`Machine ID: ${machineId ? "<hidden>" : "<empty>"}`); const secretStorage = new SecretStorage(this.options, new NativeCredentialsStore(this.options), new CryptoService(machineId)); const cachedValue: string | undefined = await secretStorage.getCredentials(machineId, Environment.AzureCloud.name); logger.silly(`Credentials: ${cachedValue ? "<hidden>" : "<empty>"}`); cachedValue && cacheContext.tokenCache.deserialize(cachedValue); logger.silly(`Before cache access plugin. Done.`); } /** * Writes to storage if MSAL in memory copy of cache has been changed. */ public async afterCacheAccess(cacheContext: TokenCacheContext): Promise<void> { logger.silly(`Executing after cache access plugin`); const machineId = await getMachineId(); logger.silly(`Machine ID: ${machineId ? "<hidden>" : "<empty>"}`); const secretStorage = new SecretStorage(this.options, new NativeCredentialsStore(this.options), new CryptoService(machineId)); logger.silly(`Did TokenCacheContext cache changed: ${cacheContext.cacheHasChanged}`); if (cacheContext.cacheHasChanged) { await secretStorage.setCredentials(machineId, Environment.AzureCloud.name, cacheContext.tokenCache.serialize()); } logger.silly(`After cache access plugin. Done.`); } /** * Clears credentials cache. */ public async clearCache(): Promise<void> { logger.silly(`Clearing credentials cache`); const machineId = await getMachineId(); logger.silly(`Machine ID: ${machineId ? "<hidden>" : "<empty>"}`); const secretStorage = new SecretStorage(this.options, new NativeCredentialsStore(this.options), new CryptoService(machineId)); await secretStorage.deleteCredentials(machineId, Environment.AzureCloud.name); logger.silly(`Credentials cache cleared`); } }