UNPKG

@jsforce/jsforce-node

Version:

Salesforce API Library for JavaScript

245 lines (244 loc) 8.88 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.OAuth2 = void 0; /** * */ const crypto_1 = require("crypto"); const querystring_1 = __importDefault(require("querystring")); const transport_1 = __importStar(require("./transport")); const defaultOAuth2Config = { loginUrl: 'https://login.salesforce.com', }; // Makes a nodejs base64 encoded string compatible with rfc4648 alternative encoding for urls. // @param base64Encoded a nodejs base64 encoded string function base64UrlEscape(base64Encoded) { // builtin node js base 64 encoding is not 64 url compatible. // See https://toolsn.ietf.org/html/rfc4648#section-5 return base64Encoded .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=/g, ''); } /** * OAuth2 class */ class OAuth2 { loginUrl; authzServiceUrl; tokenServiceUrl; revokeServiceUrl; clientId; clientSecret; redirectUri; codeVerifier; _transport; /** * */ constructor(config) { const { loginUrl, authzServiceUrl, tokenServiceUrl, revokeServiceUrl, clientId, clientSecret, redirectUri, proxyUrl, httpProxy, useVerifier, } = config; if (authzServiceUrl && tokenServiceUrl) { this.loginUrl = authzServiceUrl.split('/').slice(0, 3).join('/'); this.authzServiceUrl = authzServiceUrl; this.tokenServiceUrl = tokenServiceUrl; this.revokeServiceUrl = revokeServiceUrl || `${this.loginUrl}/services/oauth2/revoke`; } else { this.loginUrl = loginUrl ?? defaultOAuth2Config.loginUrl; const maybeSlash = this.loginUrl.endsWith('/') ? '' : '/'; this.authzServiceUrl = `${this.loginUrl}${maybeSlash}services/oauth2/authorize`; this.tokenServiceUrl = `${this.loginUrl}${maybeSlash}services/oauth2/token`; this.revokeServiceUrl = `${this.loginUrl}${maybeSlash}services/oauth2/revoke`; } this.clientId = clientId; this.clientSecret = clientSecret; this.redirectUri = redirectUri; if (proxyUrl) { this._transport = new transport_1.XdProxyTransport(proxyUrl); } else if (httpProxy) { this._transport = new transport_1.HttpProxyTransport(httpProxy); } else { this._transport = new transport_1.default(); } if (useVerifier) { // Set a code verifier string for OAuth authorization this.codeVerifier = base64UrlEscape((0, crypto_1.randomBytes)(Math.ceil(128)).toString('base64')); } } /** * Get Salesforce OAuth2 authorization page URL to redirect user agent. */ getAuthorizationUrl(params = {}) { if (this.codeVerifier) { // code verifier must be a base 64 url encoded hash of 128 bytes of random data. Our random data is also // base 64 url encoded. See Connection.create(); params.code_challenge = base64UrlEscape((0, crypto_1.createHash)('sha256').update(this.codeVerifier).digest('base64')); } const _params = { ...params, response_type: 'code', client_id: this.clientId, redirect_uri: this.redirectUri, }; return (this.authzServiceUrl + (this.authzServiceUrl.includes('?') ? '&' : '?') + querystring_1.default.stringify(_params)); } /** * OAuth2 Refresh Token Flow */ async refreshToken(refreshToken) { if (!this.clientId) { throw new Error('No OAuth2 client id information is specified'); } const params = { grant_type: 'refresh_token', refresh_token: refreshToken, client_id: this.clientId, }; if (this.clientSecret) { params.client_secret = this.clientSecret; } const ret = await this._postParams(params); return ret; } /** * Send access token request to the token endpoint. * When a code (string) is passed in first argument, it will use Web Server Authentication Flow (Authorization Code Grant). * Otherwise, it will use the specified `grant_type` and pass parameters to the endpoint. */ async requestToken(codeOrParams, params = {}) { if (typeof codeOrParams === 'string' && (!this.clientId || !this.redirectUri)) { throw new Error('No OAuth2 client id or redirect uri configuration is specified'); } const _params = { ...params, ...(typeof codeOrParams === 'string' ? { grant_type: 'authorization_code', code: codeOrParams } : codeOrParams), }; if (this.clientId) { _params.client_id = this.clientId; } if (this.clientSecret) { _params.client_secret = this.clientSecret; } if (this.redirectUri) { _params.redirect_uri = this.redirectUri; } const ret = await this._postParams(_params); return ret; } /** * OAuth2 Username-Password Flow (Resource Owner Password Credentials) */ async authenticate(username, password) { if (!this.clientId || !this.clientSecret) { throw new Error('No valid OAuth2 client configuration set'); } const ret = await this._postParams({ grant_type: 'password', username, password, client_id: this.clientId, client_secret: this.clientSecret, }); return ret; } /** * OAuth2 Revoke Session Token */ async revokeToken(token) { const response = await this._transport.httpRequest({ method: 'POST', url: this.revokeServiceUrl, body: querystring_1.default.stringify({ token }), headers: { 'content-type': 'application/x-www-form-urlencoded', }, }); if (response.statusCode >= 400) { let res = querystring_1.default.parse(response.body); if (!res || !res.error) { res = { error: `ERROR_HTTP_${response.statusCode}`, error_description: response.body, }; } throw new (class extends Error { constructor({ error, error_description, }) { super(error_description); this.name = error; } })(res); } } /** * @private */ async _postParams(params) { if (this.codeVerifier) params.code_verifier = this.codeVerifier; const response = await this._transport.httpRequest({ method: 'POST', url: this.tokenServiceUrl, body: querystring_1.default.stringify(params), headers: { 'content-type': 'application/x-www-form-urlencoded', }, }); let res; try { res = JSON.parse(response.body); } catch (e) { /* eslint-disable no-empty */ } if (response.statusCode >= 400) { res = res || { error: `ERROR_HTTP_${response.statusCode}`, error_description: response.body, }; throw new (class extends Error { constructor({ error, error_description, }) { super(error_description); this.name = error; } })(res); } return res; } } exports.OAuth2 = OAuth2; exports.default = OAuth2;