mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Introduce inbuilt MsalCachePlugin to replace msal-node-extensions (#21335)
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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')}`;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user