UNPKG

@salesforce/core

Version:

Core libraries to interact with SFDX projects, orgs, and APIs.

187 lines 6.96 kB
"use strict"; /* * Copyright (c) 2020, salesforce.com, inc. * All rights reserved. * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ /* eslint-disable camelcase */ /* eslint-disable @typescript-eslint/ban-types */ Object.defineProperty(exports, "__esModule", { value: true }); exports.DeviceOauthService = void 0; // @ts-ignore const Transport = require("jsforce/lib/transport"); const kit_1 = require("@salesforce/kit"); const ts_types_1 = require("@salesforce/ts-types"); const logger_1 = require("./logger"); const authInfo_1 = require("./authInfo"); const sfdxError_1 = require("./sfdxError"); const connection_1 = require("./connection"); async function wait(ms = 1000) { return new Promise((resolve) => { setTimeout(resolve, ms); }); } async function makeRequest(options) { const rawResponse = await new Transport().httpRequest(options); const response = kit_1.parseJsonMap(rawResponse.body); if (response.error) { const err = new sfdxError_1.SfdxError('Request Failed.'); err.data = Object.assign(response, { status: rawResponse.statusCode }); throw err; } else { return response; } } /** * Handles device based login flows * * Usage: * ``` * const oauthConfig = { * loginUrl: this.flags.instanceurl, * clientId: this.flags.clientid, * }; * const deviceOauthService = await DeviceOauthService.create(oauthConfig); * const loginData = await deviceOauthService.requestDeviceLogin(); * console.log(loginData); * const approval = await deviceOauthService.awaitDeviceApproval(loginData); * const authInfo = await deviceOauthService.authorizeAndSave(approval); * ``` */ class DeviceOauthService extends kit_1.AsyncCreatable { constructor(options) { super(options); this.pollingCount = 0; this.options = options; if (!this.options.clientId) this.options.clientId = authInfo_1.DEFAULT_CONNECTED_APP_INFO.clientId; if (!this.options.loginUrl) this.options.loginUrl = authInfo_1.AuthInfo.getDefaultInstanceUrl(); } /** * Begin the authorization flow by requesting the login * * @returns {Promise<DeviceCodeResponse>} */ async requestDeviceLogin() { const deviceFlowRequestUrl = this.getDeviceFlowRequestUrl(); const loginOptions = this.getLoginOptions(deviceFlowRequestUrl); return makeRequest(loginOptions); } /** * Polls the server until successful response OR max attempts have been made * * @returns {Promise<Nullable<DeviceCodePollingResponse>>} */ async awaitDeviceApproval(loginData) { const deviceFlowRequestUrl = this.getDeviceFlowRequestUrl(); const pollingOptions = this.getPollingOptions(deviceFlowRequestUrl, loginData.device_code); const interval = kit_1.Duration.seconds(loginData.interval).milliseconds; const response = await this.pollForDeviceApproval(pollingOptions, interval); return response; } /** * Creates and saves new AuthInfo * * @returns {Promise<AuthInfo>} */ async authorizeAndSave(approval) { const authInfo = await authInfo_1.AuthInfo.create({ oauth2Options: { loginUrl: approval.instance_url, refreshToken: approval.refresh_token, clientSecret: this.options.clientSecret, clientId: this.options.clientId, }, }); await authInfo.save(); return authInfo; } async init() { this.logger = await logger_1.Logger.child(this.constructor.name); this.logger.debug(`this.options.clientId: ${this.options.clientId}`); this.logger.debug(`this.options.loginUrl: ${this.options.loginUrl}`); } getLoginOptions(url) { return { url, headers: connection_1.SFDX_HTTP_HEADERS, method: 'POST', form: { client_id: ts_types_1.ensureString(this.options.clientId), response_type: DeviceOauthService.RESPONSE_TYPE, scope: DeviceOauthService.SCOPE, }, }; } getPollingOptions(url, code) { return { url, headers: connection_1.SFDX_HTTP_HEADERS, method: 'POST', form: { code, grant_type: DeviceOauthService.GRANT_TYPE, client_id: ts_types_1.ensureString(this.options.clientId), }, }; } getDeviceFlowRequestUrl() { return `${ts_types_1.ensureString(this.options.loginUrl)}/services/oauth2/token`; } async poll(pollingOptions) { this.logger.debug(`polling for device approval (attempt ${this.pollingCount} of ${DeviceOauthService.POLLING_COUNT_MAX})`); try { return await makeRequest(pollingOptions); } catch (e) { const err = e.data; if (err.error && err.status === 400 && err.error === 'authorization_pending') { // do nothing because we're still waiting } else { if (err.error && err.error_description) { this.logger.error(`Polling error: ${err.error}: ${err.error_description}`); } else { this.logger.error('Unknown Polling Error:'); this.logger.error(err); } throw err; } } } shouldContinuePolling() { return this.pollingCount < DeviceOauthService.POLLING_COUNT_MAX; } async pollForDeviceApproval(pollingOptions, interval) { this.logger.debug('BEGIN POLLING FOR DEVICE APPROVAL'); let result; while (this.shouldContinuePolling()) { result = await this.poll(pollingOptions); if (result) { this.logger.debug('POLLING FOR DEVICE APPROVAL SUCCESS'); break; } else { this.logger.debug(`waiting ${interval} ms...`); await wait(interval); this.pollingCount += 1; } } if (this.pollingCount >= DeviceOauthService.POLLING_COUNT_MAX) { // stop polling, the user has likely abandoned the command... this.logger.error(`Polling timed out because max polling was hit: ${this.pollingCount}`); throw sfdxError_1.SfdxError.create('@salesforce/core', 'auth', 'pollingTimeout'); } return result; } } exports.DeviceOauthService = DeviceOauthService; DeviceOauthService.RESPONSE_TYPE = 'device_code'; DeviceOauthService.GRANT_TYPE = 'device'; DeviceOauthService.SCOPE = 'refresh_token web api'; DeviceOauthService.POLLING_COUNT_MAX = 100; //# sourceMappingURL=deviceOauthService.js.map