UNPKG

@azure/identity

Version:

Provides credential implementations for Azure SDK libraries that can authenticate with Microsoft Entra ID

277 lines (276 loc) • 11.1 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var identityClient_exports = {}; __export(identityClient_exports, { IdentityClient: () => IdentityClient, getIdentityClientAuthorityHost: () => getIdentityClientAuthorityHost }); module.exports = __toCommonJS(identityClient_exports); var import_core_client = require("@azure/core-client"); var import_core_util = require("@azure/core-util"); var import_core_rest_pipeline = require("@azure/core-rest-pipeline"); var import_errors = require("../errors.js"); var import_identityTokenEndpoint = require("../util/identityTokenEndpoint.js"); var import_constants = require("../constants.js"); var import_tracing = require("../util/tracing.js"); var import_logging = require("../util/logging.js"); var import_utils = require("../credentials/managedIdentityCredential/utils.js"); const noCorrelationId = "noCorrelationId"; function getIdentityClientAuthorityHost(options) { let authorityHost = options?.authorityHost; if (import_core_util.isNode) { authorityHost = authorityHost ?? process.env.AZURE_AUTHORITY_HOST; } return authorityHost ?? import_constants.DefaultAuthorityHost; } class IdentityClient extends import_core_client.ServiceClient { authorityHost; allowLoggingAccountIdentifiers; abortControllers; allowInsecureConnection = false; // used for WorkloadIdentity tokenCredentialOptions; constructor(options) { const packageDetails = `azsdk-js-identity/${import_constants.SDK_VERSION}`; const userAgentPrefix = options?.userAgentOptions?.userAgentPrefix ? `${options.userAgentOptions.userAgentPrefix} ${packageDetails}` : `${packageDetails}`; const baseUri = getIdentityClientAuthorityHost(options); if (!baseUri.startsWith("https:")) { throw new Error("The authorityHost address must use the 'https' protocol."); } super({ requestContentType: "application/json; charset=utf-8", retryOptions: { maxRetries: 3 }, ...options, userAgentOptions: { userAgentPrefix }, baseUri }); this.authorityHost = baseUri; this.abortControllers = /* @__PURE__ */ new Map(); this.allowLoggingAccountIdentifiers = options?.loggingOptions?.allowLoggingAccountIdentifiers; this.tokenCredentialOptions = { ...options }; if (options?.allowInsecureConnection) { this.allowInsecureConnection = options.allowInsecureConnection; } } async sendTokenRequest(request) { import_logging.logger.info(`IdentityClient: sending token request to [${request.url}]`); const response = await this.sendRequest(request); if (response.bodyAsText && (response.status === 200 || response.status === 201)) { const parsedBody = JSON.parse(response.bodyAsText); if (!parsedBody.access_token) { return null; } this.logIdentifiers(response); const token = { accessToken: { token: parsedBody.access_token, expiresOnTimestamp: (0, import_utils.parseExpirationTimestamp)(parsedBody), refreshAfterTimestamp: (0, import_utils.parseRefreshTimestamp)(parsedBody), tokenType: "Bearer" }, refreshToken: parsedBody.refresh_token }; import_logging.logger.info( `IdentityClient: [${request.url}] token acquired, expires on ${token.accessToken.expiresOnTimestamp}` ); return token; } else { const error = new import_errors.AuthenticationError(response.status, response.bodyAsText); import_logging.logger.warning( `IdentityClient: authentication error. HTTP status: ${response.status}, ${error.errorResponse.errorDescription}` ); throw error; } } async refreshAccessToken(tenantId, clientId, scopes, refreshToken, clientSecret, options = {}) { if (refreshToken === void 0) { return null; } import_logging.logger.info( `IdentityClient: refreshing access token with client ID: ${clientId}, scopes: ${scopes} started` ); const refreshParams = { grant_type: "refresh_token", client_id: clientId, refresh_token: refreshToken, scope: scopes }; if (clientSecret !== void 0) { refreshParams.client_secret = clientSecret; } const query = new URLSearchParams(refreshParams); return import_tracing.tracingClient.withSpan( "IdentityClient.refreshAccessToken", options, async (updatedOptions) => { try { const urlSuffix = (0, import_identityTokenEndpoint.getIdentityTokenEndpointSuffix)(tenantId); const request = (0, import_core_rest_pipeline.createPipelineRequest)({ url: `${this.authorityHost}/${tenantId}/${urlSuffix}`, method: "POST", body: query.toString(), abortSignal: options.abortSignal, headers: (0, import_core_rest_pipeline.createHttpHeaders)({ Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" }), tracingOptions: updatedOptions.tracingOptions }); const response = await this.sendTokenRequest(request); import_logging.logger.info(`IdentityClient: refreshed token for client ID: ${clientId}`); return response; } catch (err) { if (err.name === import_errors.AuthenticationErrorName && err.errorResponse.error === "interaction_required") { import_logging.logger.info(`IdentityClient: interaction required for client ID: ${clientId}`); return null; } else { import_logging.logger.warning( `IdentityClient: failed refreshing token for client ID: ${clientId}: ${err}` ); throw err; } } } ); } // Here is a custom layer that allows us to abort requests that go through MSAL, // since MSAL doesn't allow us to pass options all the way through. generateAbortSignal(correlationId) { const controller = new AbortController(); const controllers = this.abortControllers.get(correlationId) || []; controllers.push(controller); this.abortControllers.set(correlationId, controllers); const existingOnAbort = controller.signal.onabort; controller.signal.onabort = (...params) => { this.abortControllers.set(correlationId, void 0); if (existingOnAbort) { existingOnAbort.apply(controller.signal, params); } }; return controller.signal; } abortRequests(correlationId) { const key = correlationId || noCorrelationId; const controllers = [ ...this.abortControllers.get(key) || [], // MSAL passes no correlation ID to the get requests... ...this.abortControllers.get(noCorrelationId) || [] ]; if (!controllers.length) { return; } for (const controller of controllers) { controller.abort(); } this.abortControllers.set(key, void 0); } getCorrelationId(options) { const parameter = options?.body?.split("&").map((part) => part.split("=")).find(([key]) => key === "client-request-id"); return parameter && parameter.length ? parameter[1] || noCorrelationId : noCorrelationId; } // The MSAL network module methods follow async sendGetRequestAsync(url, options) { const request = (0, import_core_rest_pipeline.createPipelineRequest)({ url, method: "GET", body: options?.body, allowInsecureConnection: this.allowInsecureConnection, headers: (0, import_core_rest_pipeline.createHttpHeaders)(options?.headers), abortSignal: this.generateAbortSignal(noCorrelationId) }); const response = await this.sendRequest(request); this.logIdentifiers(response); return { body: response.bodyAsText ? JSON.parse(response.bodyAsText) : void 0, headers: response.headers.toJSON(), status: response.status }; } async sendPostRequestAsync(url, options) { const request = (0, import_core_rest_pipeline.createPipelineRequest)({ url, method: "POST", body: options?.body, headers: (0, import_core_rest_pipeline.createHttpHeaders)(options?.headers), allowInsecureConnection: this.allowInsecureConnection, // MSAL doesn't send the correlation ID on the get requests. abortSignal: this.generateAbortSignal(this.getCorrelationId(options)) }); const response = await this.sendRequest(request); this.logIdentifiers(response); return { body: response.bodyAsText ? JSON.parse(response.bodyAsText) : void 0, headers: response.headers.toJSON(), status: response.status }; } /** * * @internal */ getTokenCredentialOptions() { return this.tokenCredentialOptions; } /** * If allowLoggingAccountIdentifiers was set on the constructor options * we try to log the account identifiers by parsing the received access token. * * The account identifiers we try to log are: * - `appid`: The application or Client Identifier. * - `upn`: User Principal Name. * - It might not be available in some authentication scenarios. * - If it's not available, we put a placeholder: "No User Principal Name available". * - `tid`: Tenant Identifier. * - `oid`: Object Identifier of the authenticated user. */ logIdentifiers(response) { if (!this.allowLoggingAccountIdentifiers || !response.bodyAsText) { return; } const unavailableUpn = "No User Principal Name available"; try { const parsed = response.parsedBody || JSON.parse(response.bodyAsText); const accessToken = parsed.access_token; if (!accessToken) { return; } const base64Metadata = accessToken.split(".")[1]; const { appid, upn, tid, oid } = JSON.parse( Buffer.from(base64Metadata, "base64").toString("utf8") ); import_logging.logger.info( `[Authenticated account] Client ID: ${appid}. Tenant ID: ${tid}. User Principal Name: ${upn || unavailableUpn}. Object ID (user): ${oid}` ); } catch (e) { import_logging.logger.warning( "allowLoggingAccountIdentifiers was set, but we couldn't log the account information. Error:", e.message ); } } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { IdentityClient, getIdentityClientAuthorityHost }); //# sourceMappingURL=identityClient.js.map