UNPKG

@adinsure-ops/ops-cli

Version:

Operations CLI for working with AdInsure

308 lines (307 loc) 13 kB
import { __awaiter } from "tslib"; import { AuthError, ConfidentialClientApplication, CryptoProvider, InteractionRequiredAuthError, PublicClientApplication, } from '@azure/msal-node'; import { reject } from 'lodash-es'; import path from 'path'; import LocalCachePlugin from './cache.js'; /** * Security context that keeps track of current authentication token. * It's not recommended you instantiate this class on your own but rather inherit your commands from [[CommandBase]]. */ export default class SecurityContext { /** * Initializes [[SecurityContext]] with config directory where access token JSON will be stored. * @param configDir Directory where CLI configuration is stored */ constructor(configDir) { this.clientId = 'bed40d09-53d7-4af8-bf63-3288658b65fb'; this.devopsResourceId = '499b84ac-1321-427f-aa17-267ca6975798'; this.tenant = 'adfintech.onmicrosoft.com'; this.authorityUrl = `https://login.microsoftonline.com/${this.tenant}/`; this.redirectUri = 'http://localhost:3044'; this.resource = 'https://ops.adinsure.com'; this._verifier = null; this._challenge = null; this._accessTokenPath = path.resolve(configDir, 'accessToken.json'); this._cryptoProvider = new CryptoProvider(); this._pca = new PublicClientApplication({ auth: { clientId: this.clientId, authority: this.authorityUrl, }, cache: { cachePlugin: new LocalCachePlugin(this._accessTokenPath), }, }); } /** * Get the accessTokenPath, as it is a private variable * @returns {string} Returns access token path */ getTokenPath() { return this._accessTokenPath; } /** * Contacts authority server, fetches and saves the authentication token to be used in subsequent requests. * * @param code Authorization code used to fetch the token. * @returns [[Promise<TokenResponse>]] */ getLoginUrl() { return new Promise((resolve) => { // Generate PKCE Codes before starting the authorization flow this._cryptoProvider .generatePkceCodes() .then(({ verifier, challenge }) => { this._verifier = verifier; this._challenge = challenge; const authCodeUrlParameters = { scopes: [this.resource + '/.default', 'offline_access'], redirectUri: this.redirectUri, codeChallenge: this._challenge, codeChallengeMethod: 'S256', }; // Get url to sign user in and consent to scopes needed for applicatio this._pca .getAuthCodeUrl(authCodeUrlParameters) .then((url) => { resolve(url); }) .catch((error) => { reject(JSON.stringify(error)); }); }) .catch((error) => { reject(error); }); }); } /** * Retrieves the refresh token from the MSAL (Microsoft Authentication Library) token cache. * * @returns {string} The refresh token. * * @example * const refreshToken = getRefreshToken(); * console.log(refreshToken); * * @description * The `getRefreshToken` function extracts the refresh token from the serialized token cache * managed by the MSAL library. This function first serializes the token cache, parses it into * a JSON object, and then retrieves the refresh token from the parsed object. * * @throws {Error} If there are issues with parsing the token cache or accessing the refresh token. */ getRefreshToken() { const tokenCache = this._pca.getTokenCache().serialize(); const refreshTokenObject = JSON.parse(tokenCache).RefreshToken; const refreshToken = refreshTokenObject[Object.keys(refreshTokenObject)[0]].secret; return refreshToken; } /** * Gets the current authentication token. * This function will also automatically try to refresh token if expired. * * @throws Error when authentication failed and/or call to login is needed * @returns Promise that resolves bearer authentication token */ getToken() { return __awaiter(this, void 0, void 0, function* () { const msalTokenCache = this._pca.getTokenCache(); const cachedAccounts = yield msalTokenCache.getAllAccounts(); return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { if (cachedAccounts.length > 0) { this._pca .acquireTokenSilent({ account: cachedAccounts[0], scopes: [this.resource + '/.default', 'offline_access'], }) .then((response) => { if (response) { const { accessToken } = response; resolve(accessToken); } else { reject(new Error('Error getting token, please call `ops login`.')); } }) .catch((error) => __awaiter(this, void 0, void 0, function* () { if (error instanceof InteractionRequiredAuthError) { try { const tokenRespone = yield this._pca.acquireTokenByRefreshToken({ refreshToken: this.getRefreshToken(), scopes: [this.resource + '/.default'], forceCache: true, }); if (tokenRespone === null) { throw new AuthError('Cannot retrieve user cache and use the refresh token!'); } resolve(tokenRespone.accessToken); //eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error) { reject(new Error(error)); } } reject(new Error(error)); })); } else if (process.env.AZURE_CLIENT_ID && process.env.AZURE_CLIENT_SECRET) { const cca = new ConfidentialClientApplication({ auth: { clientId: process.env.AZURE_CLIENT_ID, clientSecret: process.env.AZURE_CLIENT_SECRET, authority: this.authorityUrl, }, cache: { cachePlugin: new LocalCachePlugin(this._accessTokenPath), }, }); const msalCcaTokenCache = cca.getTokenCache(); const ccaCachedAccounts = yield msalCcaTokenCache.getAllAccounts(); if (ccaCachedAccounts.length > 0) { cca.acquireTokenSilent({ account: ccaCachedAccounts[0], scopes: [this.resource + '/.default'], }) .then((response) => { if (response) { const { accessToken } = response; resolve(accessToken); } else { reject(new Error('Error getting token, please call `ops login`.')); } }) .catch((error) => { reject(new Error(error)); }); } else { cca.acquireTokenByClientCredential({ scopes: [this.resource + '/.default', 'offline_access'], }) .then((response) => { if (response) { const { accessToken } = response; console.log(accessToken); resolve(accessToken); } else { reject(new Error('Error getting token, please call `ops login`.')); } }) .catch((error) => { reject(new Error(error)); }); } } else { throw new Error('You are not authenticated, please call `ops login`.'); } })); }); } /** * Contacts authority server, fetches and saves the authentication token to be used in subsequent requests. * * @param query Authorization query used to fetch the token. * @returns [[Promise<TokenResponse>]] */ acquireToken(query) { return new Promise((resolve) => { if (this._verifier === null) { throw new Error(`PKCE verifier missing.`); } // Add PKCE code verifier to token request object const tokenRequest = { code: query.code, scopes: [this.resource + '/.default', 'offline_access'], redirectUri: this.redirectUri, codeVerifier: this._verifier, clientInfo: query.client_info, }; this._pca .acquireTokenByCode(tokenRequest) .then((token) => { var _a; if ((_a = token.account) === null || _a === void 0 ? void 0 : _a.name) { resolve(token.account.name); } }) .catch((error) => { if (error instanceof AuthError) { const authError = error; console.log(`Error name: + ${authError.name}`); console.log(`Error message: + ${authError.message}`); console.log(`Inner error message: + ${authError.errorMessage}`); console.log(`Error correlation id: + ${authError.correlationId}`); console.error(); } throw new Error(error); }); }); } /** * Acquires token using device flow. * * @returns [[Promise<TokenResponse>]] */ acquireTokenDeviceFlow() { return new Promise((resolve, reject) => { this._pca .acquireTokenByDeviceCode({ deviceCodeCallback(response) { if (response.message) { // tslint:disable-next-line: no-console console.log(response.message); } }, scopes: [this.resource + '/.default', 'offline_access'], }) .then(() => { resolve(); }) .catch((error) => { reject(error); }); }); } /** * Get NPM token for adinsure npm repositories * * @returns [[Promise<string>]] */ getNPMToken() { return __awaiter(this, void 0, void 0, function* () { const msalTokenCache = this._pca.getTokenCache(); const cachedAccounts = yield msalTokenCache.getAllAccounts(); return new Promise((resolve, reject) => { if (cachedAccounts.length > 0) { this._pca .acquireTokenSilent({ account: cachedAccounts[0], scopes: [ this.devopsResourceId + '/.default', 'offline_access', ], }) .then((response) => { if (response) { resolve(response.accessToken); } else { reject(new Error('Error getting token, please call `ops login`.')); } }) .catch((error) => { reject(new Error(error)); }); } else { reject(); } }); }); } }