Introduce inbuilt MsalCachePlugin to replace msal-node-extensions (#21335)

This commit is contained in:
Cheena Malhotra
2022-11-30 21:23:32 -08:00
committed by GitHub
parent 02e9f8d3ef
commit 511523d002
9 changed files with 188 additions and 373 deletions

View File

@@ -88,7 +88,6 @@ export class FileDatabase {
this.isDirty = true;
}
public async initialize(): Promise<void> {
this.isInitialized = true;
this.saveInterval = setInterval(() => this.save(), 20 * 1000);

View File

@@ -0,0 +1,56 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as crypto from 'crypto';
export class FileEncryptionHelper {
constructor(
private _credentialService: azdata.CredentialProvider,
private _fileName: string,
) { }
private _ivBuffer: Buffer | undefined;
private _keyBuffer: Buffer | undefined;
async init(): Promise<void> {
const iv = await this._credentialService.readCredential(`${this._fileName}-iv`);
const key = await this._credentialService.readCredential(`${this._fileName}-key`);
if (!iv?.password || !key?.password) {
this._ivBuffer = crypto.randomBytes(16);
this._keyBuffer = crypto.randomBytes(32);
try {
await this._credentialService.saveCredential(`${this._fileName}-iv`, this._ivBuffer.toString('hex'));
await this._credentialService.saveCredential(`${this._fileName}-key`, this._keyBuffer.toString('hex'));
} catch (ex) {
console.log(ex);
}
} else {
this._ivBuffer = Buffer.from(iv.password, 'hex');
this._keyBuffer = Buffer.from(key.password, 'hex');
}
}
fileSaver = async (content: string): Promise<string> => {
if (!this._keyBuffer || !this._ivBuffer) {
await this.init();
}
const cipherIv = crypto.createCipheriv('aes-256-gcm', this._keyBuffer!, this._ivBuffer!);
return `${cipherIv.update(content, 'utf8', 'hex')}${cipherIv.final('hex')}%${cipherIv.getAuthTag().toString('hex')}`;
};
fileOpener = async (content: string): Promise<string> => {
if (!this._keyBuffer || !this._ivBuffer) {
await this.init();
}
const decipherIv = crypto.createDecipheriv('aes-256-gcm', this._keyBuffer!, this._ivBuffer!);
const split = content.split('%');
if (split.length !== 2) {
throw new Error('File didn\'t contain the auth tag.');
}
decipherIv.setAuthTag(Buffer.from(split[1], 'hex'));
return `${decipherIv.update(split[0], 'hex', 'utf8')}${decipherIv.final('utf8')}`;
};
}

View File

@@ -0,0 +1,64 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ICachePlugin, TokenCacheContext } from '@azure/msal-node';
import { constants, promises as fsPromises } from 'fs';
import * as path from 'path';
import { Logger } from '../../utils/Logger';
export class MsalCachePluginProvider {
constructor(
private readonly _serviceName: string,
private readonly _msalFilePath: string
) {
this._msalFilePath = path.join(this._msalFilePath, this._serviceName);
this._serviceName = this._serviceName.replace(/-/, '_');
Logger.verbose(`MsalCachePluginProvider: Using cache path ${_msalFilePath} and serviceName ${_serviceName}`);
}
public getCachePlugin(): ICachePlugin {
const beforeCacheAccess = async (cacheContext: TokenCacheContext): Promise<void> => {
let exists = true;
try {
await fsPromises.access(this._msalFilePath, constants.R_OK | constants.W_OK);
} catch {
exists = false;
}
if (exists) {
try {
const cache = await fsPromises.readFile(this._msalFilePath, { encoding: 'utf8' });
cacheContext.tokenCache.deserialize(cache);
Logger.verbose(`MsalCachePlugin: Token read from cache successfully.`);
} catch (e) {
Logger.error(`MsalCachePlugin: Failed to read from cache file. ${e}`);
throw e;
}
}
};
const afterCacheAccess = async (cacheContext: TokenCacheContext): Promise<void> => {
if (cacheContext.cacheHasChanged) {
try {
const data = cacheContext.tokenCache.serialize();
await fsPromises.writeFile(this._msalFilePath, data, { encoding: 'utf8' });
Logger.verbose(`MsalCachePlugin: Token written to cache successfully.`);
} catch (e) {
Logger.error(`MsalCachePlugin: Failed to write to cache file. ${e}`);
throw e;
}
}
};
// This is an implementation of ICachePlugin that uses the beforeCacheAccess and afterCacheAccess callbacks to read and write to a file
// Ref https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-node-migration#enable-token-caching
// In future we should use msal-node-extensions to provide a secure storage of tokens, instead of implementing our own
// However - as of now this library does not come with pre-compiled native libraries that causes runtime issues
// Ref https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/3332
return {
beforeCacheAccess,
afterCacheAccess,
};
}
}