@jsforce/jsforce-node
Version:
Salesforce API Library for JavaScript
245 lines (244 loc) • 8.88 kB
JavaScript
"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;