UNPKG

27.8 kBJavaScriptView Raw
1"use strict";
2/*
3 * Copyright (c) 2018, salesforce.com, inc.
4 * All rights reserved.
5 * SPDX-License-Identifier: BSD-3-Clause
6 * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
7 */
8Object.defineProperty(exports, "__esModule", { value: true });
9const kit_1 = require("@salesforce/kit");
10const ts_types_1 = require("@salesforce/ts-types");
11const crypto_1 = require("crypto");
12const dns = require("dns");
13const jsforce_1 = require("jsforce");
14// @ts-ignore No typings directly available for jsforce/lib/transport
15const Transport = require("jsforce/lib/transport");
16const jwt = require("jsonwebtoken");
17const url_1 = require("url");
18const authInfoConfig_1 = require("./config/authInfoConfig");
19const configAggregator_1 = require("./config/configAggregator");
20const connection_1 = require("./connection");
21const crypto_2 = require("./crypto");
22const global_1 = require("./global");
23const logger_1 = require("./logger");
24const sfdxError_1 = require("./sfdxError");
25const fs_1 = require("./util/fs");
26// Extend OAuth2 to add JWT Bearer Token Flow support.
27class JwtOAuth2 extends jsforce_1.OAuth2 {
28 constructor(options) {
29 super(options);
30 }
31 async jwtAuthorize(innerToken, callback) {
32 // tslint:disable-line:no-any
33 // @ts-ignore TODO: need better typings for jsforce
34 return super._postParams({
35 grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
36 assertion: innerToken
37 }, callback);
38 }
39}
40// Extend OAuth2 to add code verifier support for the auth code (web auth) flow
41class AuthCodeOAuth2 extends jsforce_1.OAuth2 {
42 constructor(options) {
43 super(options);
44 // Set a code verifier string for OAuth authorization
45 this.codeVerifier = base64UrlEscape(crypto_1.randomBytes(Math.ceil(128)).toString('base64'));
46 }
47 /**
48 * Overrides jsforce.OAuth2.getAuthorizationUrl. Get Salesforce OAuth2 authorization page
49 * URL to redirect user agent, adding a verification code for added security.
50 *
51 * @param params
52 */
53 getAuthorizationUrl(params) {
54 // code verifier must be a base 64 url encoded hash of 128 bytes of random data. Our random data is also
55 // base 64 url encoded. See Connection.create();
56 const codeChallenge = base64UrlEscape(crypto_1.createHash('sha256')
57 .update(this.codeVerifier)
58 .digest('base64'));
59 kit_1.set(params, 'code_challenge', codeChallenge);
60 return super.getAuthorizationUrl(params);
61 }
62 async requestToken(code, callback) {
63 return super.requestToken(code, callback);
64 }
65 /**
66 * Overrides jsforce.OAuth2._postParams because jsforce's oauth impl doesn't support
67 * coder_verifier and code_challenge. This enables the server to disallow trading a one-time auth code
68 * for an access/refresh token when the verifier and challenge are out of alignment.
69 *
70 * See https://github.com/jsforce/jsforce/issues/665
71 */
72 // tslint:disable-next-line:no-unused-variable
73 async _postParams(params, callback) {
74 kit_1.set(params, 'code_verifier', this.codeVerifier);
75 // @ts-ignore TODO: need better typings for jsforce
76 return super._postParams(params, callback);
77 }
78}
79/**
80 * Salesforce URLs.
81 */
82var SfdcUrl;
83(function (SfdcUrl) {
84 SfdcUrl["SANDBOX"] = "https://test.salesforce.com";
85 SfdcUrl["PRODUCTION"] = "https://login.salesforce.com";
86})(SfdcUrl = exports.SfdcUrl || (exports.SfdcUrl = {}));
87const INTERNAL_URL_PARTS = [
88 '.internal.',
89 '.vpod.',
90 'stm.salesforce.com',
91 '.blitz.salesforce.com',
92 'mobile1.t.salesforce.com'
93];
94function isInternalUrl(loginUrl = '') {
95 return loginUrl.startsWith('https://gs1.') || INTERNAL_URL_PARTS.some(part => loginUrl.includes(part));
96}
97function getJwtAudienceUrl(options) {
98 // default audience must be...
99 let audienceUrl = SfdcUrl.PRODUCTION;
100 const loginUrl = ts_types_1.getString(options, 'loginUrl', '');
101 const createdOrgInstance = ts_types_1.getString(options, 'createdOrgInstance', '')
102 .trim()
103 .toLowerCase();
104 if (process.env.SFDX_AUDIENCE_URL) {
105 audienceUrl = process.env.SFDX_AUDIENCE_URL;
106 }
107 else if (isInternalUrl(loginUrl)) {
108 // This is for internal developers when just doing authorize;
109 audienceUrl = loginUrl;
110 }
111 else if (createdOrgInstance.startsWith('cs') || url_1.parse(loginUrl).hostname === 'test.salesforce.com') {
112 audienceUrl = SfdcUrl.SANDBOX;
113 }
114 else if (createdOrgInstance.startsWith('gs1')) {
115 audienceUrl = 'https://gs1.salesforce.com';
116 }
117 return audienceUrl;
118}
119// parses the id field returned from jsForce oauth2 methods to get
120// user ID and org ID.
121function _parseIdUrl(idUrl) {
122 const idUrls = idUrl.split('/');
123 const userId = idUrls.pop();
124 const orgId = idUrls.pop();
125 return {
126 userId,
127 orgId,
128 url: idUrl
129 };
130}
131// Legacy. The connected app info is owned by the thing that
132// creates new AuthInfos. Currently that is the auth:* commands which
133// aren't owned by this core library. These values need to be here
134// for any old auth files where the id and secret aren't stored.
135//
136// Ideally, this would be removed at some point in the distant future
137// when all auth files now have the clientId stored in it.
138const DEFAULT_CONNECTED_APP_INFO = {
139 legacyClientId: 'SalesforceDevelopmentExperience',
140 legacyClientSecret: '1384510088588713504'
141};
142class AuthInfoCrypto extends crypto_2.Crypto {
143 decryptFields(fields) {
144 return this._crypt(fields, 'decrypt');
145 }
146 encryptFields(fields) {
147 return this._crypt(fields, 'encrypt');
148 }
149 _crypt(fields, method) {
150 const copy = {};
151 for (const key of ts_types_1.keysOf(fields)) {
152 const rawValue = fields[key];
153 if (rawValue !== undefined) {
154 if (ts_types_1.isString(rawValue) && AuthInfoCrypto.encryptedFields.includes(key)) {
155 copy[key] = this[method](ts_types_1.asString(rawValue));
156 }
157 else {
158 copy[key] = rawValue;
159 }
160 }
161 }
162 return copy;
163 }
164}
165AuthInfoCrypto.encryptedFields = [
166 'accessToken',
167 'refreshToken',
168 'password',
169 'clientSecret'
170];
171// Makes a nodejs base64 encoded string compatible with rfc4648 alternative encoding for urls.
172// @param base64Encoded a nodejs base64 encoded string
173function base64UrlEscape(base64Encoded) {
174 // builtin node js base 64 encoding is not 64 url compatible.
175 // See https://toolsn.ietf.org/html/rfc4648#section-5
176 return base64Encoded
177 .replace(/\+/g, '-')
178 .replace(/\//g, '_')
179 .replace(/=/g, '');
180}
181/**
182 * Handles persistence and fetching of user authentication information using
183 * JWT, OAuth, or refresh tokens. Sets up the refresh flows that jsForce will
184 * use to keep tokens active. An AuthInfo can also be created with an access
185 * token, but AuthInfos created with access tokens can't be persisted to disk.
186 *
187 * **See** [Authorization](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_auth.htm)
188 *
189 * **See** [Salesforce DX Usernames and Orgs](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_cli_usernames_orgs.htm)
190 *
191 * ```
192 * // Creating a new authentication file.
193 * const authInfo = await AuthInfo.create({
194 * username: myAdminUsername,
195 * oauth2Options: {
196 * loginUrl, authCode, clientId, clientSecret
197 * }
198 * );
199 * authInfo.save();
200 *
201 * // Creating an authorization info with an access token.
202 * const authInfo = await AuthInfo.create({
203 * username: accessToken
204 * });
205 *
206 * // Using an existing authentication file.
207 * const authInfo = await AuthInfo.create({
208 * username: myAdminUsername
209 * });
210 *
211 * // Using the AuthInfo
212 * const connection = await Connection.create({ authInfo });
213 * ```
214 */
215class AuthInfo extends kit_1.AsyncCreatable {
216 /**
217 * Constructor
218 * **Do not directly construct instances of this class -- use {@link AuthInfo.create} instead.**
219 * @param options The options for the class instance
220 */
221 constructor(options) {
222 super(options);
223 // All sensitive fields are encrypted
224 this.fields = {};
225 // Possibly overridden in create
226 this.usingAccessToken = false;
227 this.options = options;
228 }
229 /**
230 * Get a list of all auth files stored in the global directory.
231 * @returns {Promise<string[]>}
232 */
233 static async listAllAuthFiles() {
234 const globalFiles = await fs_1.fs.readdir(global_1.Global.DIR);
235 const authFiles = globalFiles.filter(file => file.match(AuthInfo.authFilenameFilterRegEx));
236 // Want to throw a clean error if no files are found.
237 if (kit_1.isEmpty(authFiles)) {
238 const errConfig = new sfdxError_1.SfdxErrorConfig('@salesforce/core', 'core', 'NoAuthInfoFound');
239 throw sfdxError_1.SfdxError.create(errConfig);
240 }
241 // At least one auth file is in the global dir.
242 return authFiles;
243 }
244 /**
245 * Returns true if one or more authentications are persisted.
246 */
247 static async hasAuthentications() {
248 try {
249 const authFiles = await this.listAllAuthFiles();
250 return !kit_1.isEmpty(authFiles);
251 }
252 catch (err) {
253 if (err.name === 'OrgDataNotAvailableError' || err.code === 'ENOENT') {
254 return false;
255 }
256 throw err;
257 }
258 }
259 /**
260 * Get the authorization URL.
261 * @param options The options to generate the URL.
262 */
263 static getAuthorizationUrl(options) {
264 const oauth2 = new AuthCodeOAuth2(options);
265 // The state parameter allows the redirectUri callback listener to ignore request
266 // that don't contain the state value.
267 const params = {
268 state: crypto_1.randomBytes(Math.ceil(6)).toString('hex'),
269 prompt: 'login',
270 scope: 'refresh_token api web'
271 };
272 return oauth2.getAuthorizationUrl(params);
273 }
274 /**
275 * Forces the auth file to be re-read from disk for a given user. Returns `true` if a value was removed.
276 * @param username The username for the auth info to re-read.
277 */
278 static clearCache(username) {
279 if (username) {
280 return AuthInfo.cache.delete(username);
281 }
282 return false;
283 }
284 /**
285 * Get the username.
286 */
287 getUsername() {
288 return this.fields.username;
289 }
290 /**
291 * Returns true if `this` is using the JWT flow.
292 */
293 isJwt() {
294 const { refreshToken, privateKey } = this.fields;
295 return !refreshToken && !!privateKey;
296 }
297 /**
298 * Returns true if `this` is using an access token flow.
299 */
300 isAccessTokenFlow() {
301 const { refreshToken, privateKey } = this.fields;
302 return !refreshToken && !privateKey;
303 }
304 /**
305 * Returns true if `this` is using the oauth flow.
306 */
307 isOauth() {
308 return !this.isAccessTokenFlow() && !this.isJwt();
309 }
310 /**
311 * Returns true if `this` is using the refresh token flow.
312 */
313 isRefreshTokenFlow() {
314 const { refreshToken, authCode } = this.fields;
315 return !authCode && !!refreshToken;
316 }
317 /**
318 * Updates the cache and persists the authentication fields (encrypted).
319 * @param authData New data to save.
320 */
321 async save(authData) {
322 this.update(authData);
323 const username = ts_types_1.ensure(this.getUsername());
324 AuthInfo.cache.set(username, this.fields);
325 const dataToSave = kit_1.cloneJson(this.fields);
326 this.logger.debug(dataToSave);
327 const config = await authInfoConfig_1.AuthInfoConfig.create(Object.assign({}, authInfoConfig_1.AuthInfoConfig.getOptions(username), { throwOnNotFound: false }));
328 config.setContentsFromObject(dataToSave);
329 await config.write();
330 this.logger.info(`Saved auth info for username: ${this.getUsername()}`);
331 return this;
332 }
333 /**
334 * Update the authorization fields, encrypting sensitive fields, but do not persist.
335 * For convenience `this` object is returned.
336 *
337 * @param authData Authorization fields to update.
338 * @param encrypt Encrypt the fields.
339 */
340 update(authData, encrypt = true) {
341 if (authData && ts_types_1.isPlainObject(authData)) {
342 let copy = kit_1.cloneJson(authData);
343 if (encrypt) {
344 copy = this.authInfoCrypto.encryptFields(copy);
345 }
346 Object.assign(this.fields, copy);
347 this.logger.info(`Updated auth info for username: ${this.getUsername()}`);
348 }
349 return this;
350 }
351 /**
352 * Get the auth fields (decrypted) needed to make a connection.
353 */
354 getConnectionOptions() {
355 let opts;
356 const { accessToken, instanceUrl } = this.fields;
357 if (this.isAccessTokenFlow()) {
358 this.logger.info('Returning fields for a connection using access token.');
359 // Just auth with the accessToken
360 opts = { accessToken, instanceUrl };
361 }
362 else if (this.isJwt()) {
363 this.logger.info('Returning fields for a connection using JWT config.');
364 opts = {
365 accessToken,
366 instanceUrl,
367 refreshFn: this.refreshFn.bind(this)
368 };
369 }
370 else {
371 // @TODO: figure out loginUrl and redirectUri (probably get from config class)
372 //
373 // redirectUri: org.config.getOauthCallbackUrl()
374 // loginUrl: this.fields.instanceUrl || this.config.getAppConfig().sfdcLoginUrl
375 this.logger.info('Returning fields for a connection using OAuth config.');
376 // Decrypt a user provided client secret or use the default.
377 opts = {
378 oauth2: {
379 loginUrl: instanceUrl || 'https://login.salesforce.com',
380 clientId: this.fields.clientId || DEFAULT_CONNECTED_APP_INFO.legacyClientId,
381 redirectUri: 'http://localhost:1717/OauthRedirect'
382 },
383 accessToken,
384 instanceUrl,
385 refreshFn: this.refreshFn.bind(this)
386 };
387 }
388 // decrypt the fields
389 return this.authInfoCrypto.decryptFields(opts);
390 }
391 /**
392 * Get the authorization fields.
393 */
394 getFields() {
395 return this.fields;
396 }
397 /**
398 * Returns true if this org is using access token auth.
399 */
400 isUsingAccessToken() {
401 return this.usingAccessToken;
402 }
403 /**
404 * Get the SFDX Auth URL.
405 *
406 * **See** [SFDX Authorization](https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_reference.meta/sfdx_cli_reference/cli_reference_force_auth.htm#cli_reference_force_auth)
407 */
408 getSfdxAuthUrl() {
409 const decryptedFields = this.authInfoCrypto.decryptFields(this.fields);
410 const instanceUrl = ts_types_1.ensure(decryptedFields.instanceUrl).replace(/^https?:\/\//, '');
411 let sfdxAuthUrl = 'force://';
412 if (decryptedFields.clientId) {
413 sfdxAuthUrl += `${decryptedFields.clientId}:${decryptedFields.clientSecret}:`;
414 }
415 sfdxAuthUrl += `${decryptedFields.refreshToken}@${instanceUrl}`;
416 return sfdxAuthUrl;
417 }
418 /**
419 * Initializes an instance of the AuthInfo class.
420 */
421 async init() {
422 // Must specify either username and/or options
423 const options = this.options.oauth2Options || this.options.accessTokenOptions;
424 if (!this.options.username && !(this.options.oauth2Options || this.options.accessTokenOptions)) {
425 throw sfdxError_1.SfdxError.create('@salesforce/core', 'core', 'AuthInfoCreationError');
426 }
427 this.fields.username = this.options.username || ts_types_1.getString(options, 'username') || undefined;
428 // If the username is an access token, use that for auth and don't persist
429 const accessTokenMatch = ts_types_1.isString(this.fields.username) && this.fields.username.match(/^(00D\w{12,15})![\.\w]*$/);
430 if (accessTokenMatch) {
431 // Need to initAuthOptions the logger and authInfoCrypto since we don't call init()
432 this.logger = await logger_1.Logger.child('AuthInfo');
433 this.authInfoCrypto = await AuthInfoCrypto.create({
434 noResetOnClose: true
435 });
436 const aggregator = await configAggregator_1.ConfigAggregator.create();
437 const instanceUrl = aggregator.getPropertyValue('instanceUrl') || SfdcUrl.PRODUCTION;
438 this.update({
439 accessToken: this.options.username,
440 instanceUrl,
441 orgId: accessTokenMatch[1]
442 });
443 this.usingAccessToken = true;
444 }
445 else {
446 await this.initAuthOptions(options);
447 }
448 }
449 /**
450 * Initialize this AuthInfo instance with the specified options. If options are not provided, initialize it from cache
451 * or by reading from the persistence store. For convenience `this` object is returned.
452 * @param options Options to be used for creating an OAuth2 instance.
453 *
454 * **Throws** *{@link SfdxError}{ name: 'NamedOrgNotFound' }* Org information does not exist.
455 * @returns {Promise<AuthInfo>}
456 */
457 async initAuthOptions(options) {
458 this.logger = await logger_1.Logger.child('AuthInfo');
459 this.authInfoCrypto = await AuthInfoCrypto.create();
460 // If options were passed, use those before checking cache and reading an auth file.
461 let authConfig;
462 if (options) {
463 options = kit_1.cloneJson(options);
464 if (this.isTokenOptions(options)) {
465 authConfig = options;
466 }
467 else {
468 // jwt flow
469 // Support both sfdx and jsforce private key values
470 if (!options.privateKey && options.privateKeyFile) {
471 options.privateKey = options.privateKeyFile;
472 }
473 if (options.privateKey) {
474 authConfig = await this.buildJwtConfig(options);
475 }
476 else if (!options.authCode && options.refreshToken) {
477 // refresh token flow (from sfdxUrl or OAuth refreshFn)
478 authConfig = await this.buildRefreshTokenConfig(options);
479 }
480 else {
481 // authcode exchange / web auth flow
482 authConfig = await this.buildWebAuthConfig(options);
483 }
484 }
485 // Update the auth fields WITH encryption
486 this.update(authConfig);
487 }
488 else {
489 const username = ts_types_1.ensure(this.getUsername());
490 if (AuthInfo.cache.has(username)) {
491 authConfig = ts_types_1.ensure(AuthInfo.cache.get(username));
492 }
493 else {
494 // Fetch from the persisted auth file
495 try {
496 const config = await authInfoConfig_1.AuthInfoConfig.create(Object.assign({}, authInfoConfig_1.AuthInfoConfig.getOptions(username), { throwOnNotFound: true }));
497 authConfig = config.toObject();
498 }
499 catch (e) {
500 if (e.code === 'ENOENT') {
501 throw sfdxError_1.SfdxError.create('@salesforce/core', 'core', 'NamedOrgNotFound', [username]);
502 }
503 else {
504 throw e;
505 }
506 }
507 }
508 // Update the auth fields WITHOUT encryption (already encrypted)
509 this.update(authConfig, false);
510 }
511 // Cache the fields by username (fields are encrypted)
512 AuthInfo.cache.set(ts_types_1.ensure(this.getUsername()), this.fields);
513 return this;
514 }
515 isTokenOptions(options) {
516 // Although OAuth2Options does not contain refreshToken, privateKey, or privateKeyFile, a JS consumer could still pass those in
517 // which WILL have an access token as well, but it should be considered an OAuth2Options at that point.
518 return ('accessToken' in options &&
519 !('refreshToken' in options) &&
520 !('privateKey' in options) &&
521 !('privateKeyFile' in options) &&
522 !('authCode' in options));
523 }
524 // A callback function for a connection to refresh an access token. This is used
525 // both for a JWT connection and an OAuth connection.
526 async refreshFn(conn, callback) {
527 this.logger.info('Access token has expired. Updating...');
528 try {
529 const fields = this.authInfoCrypto.decryptFields(this.fields);
530 await this.initAuthOptions(fields);
531 await this.save();
532 return await callback(null, fields.accessToken);
533 }
534 catch (err) {
535 if (err.message && err.message.includes('Data Not Available')) {
536 const errConfig = new sfdxError_1.SfdxErrorConfig('@salesforce/core', 'core', 'OrgDataNotAvailableError', [
537 this.getUsername()
538 ]);
539 for (let i = 1; i < 5; i++) {
540 errConfig.addAction(`OrgDataNotAvailableErrorAction${i}`);
541 }
542 return await callback(sfdxError_1.SfdxError.create(errConfig));
543 }
544 return await callback(err);
545 }
546 }
547 // Build OAuth config for a JWT auth flow
548 async buildJwtConfig(options) {
549 const privateKeyContents = await fs_1.fs.readFile(ts_types_1.ensure(options.privateKey), 'utf8');
550 const audienceUrl = getJwtAudienceUrl(options);
551 const jwtToken = await jwt.sign({
552 iss: options.clientId,
553 sub: this.getUsername(),
554 aud: audienceUrl,
555 exp: Date.now() + 300
556 }, privateKeyContents, {
557 algorithm: 'RS256'
558 });
559 const oauth2 = new JwtOAuth2({ loginUrl: options.loginUrl });
560 let _authFields;
561 try {
562 _authFields = ts_types_1.ensureJsonMap(await oauth2.jwtAuthorize(jwtToken));
563 }
564 catch (err) {
565 throw sfdxError_1.SfdxError.create('@salesforce/core', 'core', 'JWTAuthError', [err.message]);
566 }
567 const authFields = {
568 accessToken: ts_types_1.asString(_authFields.access_token),
569 orgId: _parseIdUrl(ts_types_1.ensureString(_authFields.id)).orgId,
570 loginUrl: options.loginUrl,
571 privateKey: options.privateKey
572 };
573 const instanceUrl = ts_types_1.ensureString(_authFields.instance_url);
574 const parsedUrl = url_1.parse(instanceUrl);
575 try {
576 // Check if the url is resolvable. This can fail when my-domains have not been replicated.
577 await this.lookup(ts_types_1.ensure(parsedUrl.hostname));
578 authFields.instanceUrl = instanceUrl;
579 }
580 catch (err) {
581 this.logger.debug(`Instance URL [${_authFields.instance_url}] is not available. DNS lookup failed. Using loginUrl [${options.loginUrl}] instead. This may result in a "Destination URL not reset" error.`);
582 authFields.instanceUrl = options.loginUrl;
583 }
584 return authFields;
585 }
586 // Build OAuth config for a refresh token auth flow
587 async buildRefreshTokenConfig(options) {
588 // Ideally, this would be removed at some point in the distant future when all auth files
589 // now have the clientId stored in it.
590 if (!options.clientId) {
591 options.clientId = DEFAULT_CONNECTED_APP_INFO.legacyClientId;
592 options.clientSecret = DEFAULT_CONNECTED_APP_INFO.legacyClientSecret;
593 }
594 const oauth2 = new jsforce_1.OAuth2(options);
595 let _authFields;
596 try {
597 _authFields = await oauth2.refreshToken(ts_types_1.ensure(options.refreshToken));
598 }
599 catch (err) {
600 throw sfdxError_1.SfdxError.create('@salesforce/core', 'core', 'RefreshTokenAuthError', [err.message]);
601 }
602 return {
603 accessToken: _authFields.access_token,
604 // @ts-ignore TODO: need better typings for jsforce
605 instanceUrl: _authFields.instance_url,
606 // @ts-ignore TODO: need better typings for jsforce
607 orgId: _parseIdUrl(_authFields.id).orgId,
608 // @ts-ignore TODO: need better typings for jsforce
609 loginUrl: options.loginUrl || _authFields.instance_url,
610 refreshToken: options.refreshToken,
611 clientId: options.clientId,
612 clientSecret: options.clientSecret
613 };
614 }
615 // build an OAuth config given an auth code.
616 async buildWebAuthConfig(options) {
617 const oauth2 = new AuthCodeOAuth2(options);
618 // Exchange the auth code for an access token and refresh token.
619 let _authFields;
620 try {
621 this.logger.info(`Exchanging auth code for access token using loginUrl: ${options.loginUrl}`);
622 _authFields = await oauth2.requestToken(ts_types_1.ensure(options.authCode));
623 }
624 catch (err) {
625 throw sfdxError_1.SfdxError.create('@salesforce/core', 'core', 'AuthCodeExchangeError', [err.message]);
626 }
627 // @ts-ignore TODO: need better typings for jsforce
628 const { userId, orgId } = _parseIdUrl(_authFields.id);
629 // Make a REST call for the username directly. Normally this is done via a connection
630 // but we don't want to create circular dependencies or lots of snowflakes
631 // within this file to support it.
632 const apiVersion = 'v42.0'; // hardcoding to v42.0 just for this call is okay.
633 const instance = ts_types_1.ensure(ts_types_1.getString(_authFields, 'instance_url'));
634 const url = `${instance}/services/data/${apiVersion}/sobjects/User/${userId}`;
635 const headers = Object.assign({ Authorization: `Bearer ${_authFields.access_token}` }, connection_1.SFDX_HTTP_HEADERS);
636 let username;
637 try {
638 this.logger.info(`Sending request for Username after successful auth code exchange to URL: ${url}`);
639 const response = await new Transport().httpRequest({ url, headers });
640 username = ts_types_1.asString(kit_1.parseJsonMap(response.body).Username);
641 }
642 catch (err) {
643 throw sfdxError_1.SfdxError.create('@salesforce/core', 'core', 'AuthCodeUsernameRetrievalError', [orgId, err.message]);
644 }
645 return {
646 accessToken: _authFields.access_token,
647 // @ts-ignore TODO: need better typings for jsforce
648 instanceUrl: _authFields.instance_url,
649 orgId,
650 username,
651 // @ts-ignore TODO: need better typings for jsforce
652 loginUrl: options.loginUrl || _authFields.instance_url,
653 refreshToken: _authFields.refresh_token
654 };
655 }
656 // See https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback
657 async lookup(host) {
658 return new Promise((resolve, reject) => {
659 dns.lookup(host, (err, address, family) => {
660 if (err) {
661 reject(err);
662 }
663 else {
664 resolve({ address, family });
665 }
666 });
667 });
668 }
669}
670// The regular expression that filters files stored in $HOME/.sfdx
671AuthInfo.authFilenameFilterRegEx = /^[^.][^@]*@[^.]+(\.[^.\s]+)+\.json$/;
672// Cache of auth fields by username.
673AuthInfo.cache = new Map();
674exports.AuthInfo = AuthInfo;
675//# sourceMappingURL=authInfo.js.map
\No newline at end of file