@salesforce/core
Version:
Core libraries to interact with SFDX projects, orgs, and APIs.
493 lines • 32.6 kB
JavaScript
"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 class-methods-use-this */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
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;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebServer = exports.WebOAuthServer = void 0;
const http = __importStar(require("node:http"));
const node_querystring_1 = require("node:querystring");
const node_url_1 = require("node:url");
const node_net_1 = require("node:net");
const node_events_1 = require("node:events");
const jsforce_node_1 = require("@jsforce/jsforce-node");
const kit_1 = require("@salesforce/kit");
const ts_types_1 = require("@salesforce/ts-types");
const logger_1 = require("./logger/logger");
const authInfo_1 = require("./org/authInfo");
const sfError_1 = require("./sfError");
const messages_1 = require("./messages");
const sfProject_1 = require("./sfProject");
;
const messages = new messages_1.Messages('@salesforce/core', 'auth', new Map([["targetOrgNotSet", "A default user is not set."], ["targetOrgNotSet.actions", ["Run the \"sfdx auth\" commands with --setdefaultusername to connect to an org and set it as your default org.", "Run \"force:org:create\" with --setdefaultusername to create a scratch org and set it as your default org.", "Run \"sfdx config:set defaultusername=<username>\" to set your default username."]], ["portInUse", "Cannot start the OAuth redirect server on port %s."], ["portInUse.actions", ["Kill the process running on port %s or use a custom connected app and update OauthLocalPort in the sfdx-project.json file."]], ["invalidRequestMethod", "Invalid request method: %s"], ["invalidRequestUri", "Invalid request uri: %s"], ["error.HttpApi", "HTTP response contains html content. Check that the org exists and can be reached."], ["pollingTimeout", "The device authorization request timed out. After executing force:auth:device:login, you must approve access to the device within 10 minutes. This can happen if the URL wasn\u2019t copied into the browser, login was not attempted, or the 2FA process was not completed within 10 minutes. Request authorization again."], ["serverErrorHTMLResponse", "<html><head><style>body {background-color:#F4F6F9; font-family: Arial, sans-serif; font-size: 0.8125rem; line-height: 1.5rem; color: #16325c;} #center {margin: auto; width: 370px; padding: 100px 0px 20px;} #logo-container {margin-left: auto; margin-right: auto; text-align: center;} #logo {max-width: 180px; max-height: 113px; margin-bottom: 2rem; border: 0;} #header {font-size: 1.5rem; text-align: center; margin-bottom: 1rem;} #message {background-color: #FFFFFF; margin: 0px auto; padding: 1.25rem; border-radius: 0.25rem; border: 1px solid #D8DDE6;} #footer {height: 24px; width: 370px; text-align: center; font-size: .75rem; position: absolute; bottom: 10;}</style></head><body><div id=\"center\"><div id=\"logo-container\"><img id=\"logo\" aria-hidden=\"true\" name=\"logo\" alt=\"Salesforce\" src=\"data:image/svg+xml;base64,%s\"></div><div id=\"header\">%s</div><div id=\"message\">%s<br/><br/>This is most likely <b>not</b> an error with the Salesforce CLI. Please ensure all information is accurate and try again.</div><div id=\"footer\">© %s Salesforce, Inc. All rights reserved.</div></div></body></html>"], ["missingAuthCode", "No authentication code found on login response."], ["serverSuccessHTMLResponse", "<html><head><style>body {background-color:#F4F6F9; font-family: Arial, sans-serif; font-size: 0.8125rem; line-height: 1.5rem; color: #16325c;} #center {margin: auto; width: 300px; padding: 100px 0px 20px;} #logo-container {margin-left: auto; margin-right: auto; text-align: center;} #logo {max-width: 180px; max-height: 113px; margin-bottom: 2rem; border: 0;} #header {font-size: 1.5rem; text-align: center; margin-bottom: 1rem;} #message {background-color: #FFFFFF; margin: 0px auto; padding: 1.25rem; border-radius: 0.25rem; border: 1px solid #D8DDE6;} #footer {height: 24px; width: 300px; text-align: center; font-size: .75rem; position: absolute; bottom: 10;}</style></head><body><div id=\"center\"><div id=\"logo-container\"><img id=\"logo\" aria-hidden=\"true\" name=\"logo\" alt=\"Salesforce\" src=\"data:image/svg+xml;base64,%s\"></div><div id=\"header\">Authentication Successful</div><div id=\"message\">You've successfully logged in. You can now close this browser tab or window.</div><div id=\"footer\">© %s Salesforce, Inc. All rights reserved.</div></div></body></html>"], ["serverSfdcImage", "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDIxLjEuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAyNjIgMTg0IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAyNjIgMTg0OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6IzAwQTFFMDt9Cgkuc3Qxe2ZpbGw6I0ZGRkZGRjt9Cjwvc3R5bGU+Cjx0aXRsZT5sb2dvLXNhbGVzZm9yY2U8L3RpdGxlPgo8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4KPGcgaWQ9IlRlc3QtQiI+Cgk8ZyBpZD0iTW9iaWxlLU5hdi0tLVRlc3QtQi1feDI4XzBfeDI5XyI+CgkJPGcgaWQ9Ikdyb3VwIj4KCQkJPGcgaWQ9ImxvZ28tc2FsZXNmb3JjZSI+CgkJCQk8cGF0aCBpZD0iRmlsbC0xIiBjbGFzcz0ic3QwIiBkPSJNMTA5LjIsMjAuOWM4LjQtOC43LDIwLjEtMTQuMiwzMy0xNC4yYzE3LjIsMCwzMi4xLDkuNiw0MC4xLDIzLjhjNi45LTMuMSwxNC42LTQuOCwyMi43LTQuOAoJCQkJCWMzMSwwLDU2LDI1LjMsNTYsNTYuNXMtMjUuMSw1Ni41LTU2LDU2LjVjLTMuOCwwLTcuNS0wLjQtMTEtMS4xYy03LDEyLjUtMjAuNCwyMS0zNS44LDIxYy02LjQsMC0xMi41LTEuNS0xNy45LTQuMQoJCQkJCWMtNy4xLDE2LjctMjMuNywyOC41LTQzLDI4LjVjLTIwLjEsMC0zNy4zLTEyLjctNDMuOS0zMC42Yy0yLjksMC42LTUuOSwwLjktOC45LDAuOWMtMjQsMC00My40LTE5LjYtNDMuNC00My45CgkJCQkJYzAtMTYuMiw4LjctMzAuNCwyMS43LTM4Yy0yLjctNi4xLTQuMi0xMi45LTQuMi0yMC4xQzE4LjUsMjMuNiw0MS4yLDEsNjksMUM4NS40LDEsMTAwLDguOCwxMDkuMiwyMC45Ii8+CgkJCQk8cGF0aCBpZD0iQ29tYmluZWQtU2hhcGUiIGNsYXNzPSJzdDEiIGQ9Ik0zOC43LDk1LjRsMS4xLTIuOWMwLjItMC41LDAuNS0wLjMsMC43LTAuMmMwLjMsMC4yLDAuNSwwLjMsMC45LDAuNmMzLjEsMiw2LDIsNi45LDIKCQkJCQljMi4zLDAsMy44LTEuMiwzLjgtMi45di0wLjFjMC0xLjgtMi4yLTIuNS00LjgtMy4zbC0wLjYtMC4yYy0zLjUtMS03LjMtMi41LTcuMy02Ljl2LTAuMWMwLTQuMiwzLjQtNy4yLDguMy03LjJsMC41LDAKCQkJCQljMi45LDAsNS42LDAuOCw3LjYsMi4xYzAuMiwwLjEsMC40LDAuMywwLjMsMC42Yy0wLjEsMC4zLTEsMi42LTEuMSwyLjljLTAuMiwwLjUtMC43LDAuMi0wLjcsMC4yYy0xLjgtMS00LjUtMS43LTYuOC0xLjcKCQkJCQljLTIuMSwwLTMuNCwxLjEtMy40LDIuNnYwLjFjMCwxLjcsMi4zLDIuNSw0LjksMy4zbDAuNSwwLjFjMy41LDEuMSw3LjIsMi42LDcuMiw2Ljl2MC4xYzAsNC42LTMuMyw3LjQtOC42LDcuNAoJCQkJCWMtMi42LDAtNS4xLTAuNC03LjgtMS44Yy0wLjUtMC4zLTEtMC41LTEuNS0wLjlDMzguNyw5NS45LDM4LjUsOTUuOCwzOC43LDk1LjR6IE0xMTYuNyw5NS40bDEuMS0yLjljMC4yLTAuNSwwLjYtMC4zLDAuNy0wLjIKCQkJCQljMC4zLDAuMiwwLjUsMC4zLDAuOSwwLjZjMy4xLDIsNiwyLDYuOSwyYzIuMywwLDMuOC0xLjIsMy44LTIuOXYtMC4xYzAtMS44LTIuMi0yLjUtNC44LTMuM2wtMC42LTAuMmMtMy41LTEtNy4zLTIuNS03LjMtNi45CgkJCQkJdi0wLjFjMC00LjIsMy40LTcuMiw4LjMtNy4ybDAuNSwwYzIuOSwwLDUuNiwwLjgsNy42LDIuMWMwLjIsMC4xLDAuNCwwLjMsMC4zLDAuNmMtMC4xLDAuMy0xLDIuNi0xLjEsMi45CgkJCQkJYy0wLjIsMC41LTAuNywwLjItMC43LDAuMmMtMS44LTEtNC41LTEuNy02LjgtMS43Yy0yLjEsMC0zLjQsMS4xLTMuNCwyLjZ2MC4xYzAsMS43LDIuMywyLjUsNC45LDMuM2wwLjUsMC4xCgkJCQkJYzMuNSwxLjEsNy4yLDIuNiw3LjIsNi45djAuMWMwLDQuNi0zLjMsNy40LTguNiw3LjRjLTIuNiwwLTUuMS0wLjQtNy44LTEuOGMtMC41LTAuMy0xLTAuNS0xLjUtMC45CgkJCQkJQzExNi44LDk1LjksMTE2LjYsOTUuOCwxMTYuNyw5NS40eiBNMTc0LjUsODEuN2MwLjQsMS41LDAuNywzLjEsMC43LDQuOHMtMC4yLDMuMy0wLjcsNC44Yy0wLjQsMS41LTEuMSwyLjgtMiwzLjkKCQkJCQljLTAuOSwxLjEtMi4xLDItMy40LDIuNmMtMS40LDAuNi0zLDAuOS00LjgsMC45Yy0xLjgsMC0zLjQtMC4zLTQuOC0wLjljLTEuNC0wLjYtMi41LTEuNS0zLjQtMi42Yy0wLjktMS4xLTEuNi0yLjQtMi0zLjkKCQkJCQljLTAuNC0xLjUtMC43LTMuMS0wLjctNC44YzAtMS43LDAuMi0zLjMsMC43LTQuOGMwLjQtMS41LDEuMS0yLjgsMi0zLjljMC45LTEuMSwyLjEtMiwzLjQtMi42YzEuNC0wLjYsMy0xLDQuOC0xCgkJCQkJYzEuOCwwLDMuNCwwLjMsNC44LDFjMS40LDAuNiwyLjUsMS41LDMuNCwyLjZDMTczLjQsNzguOSwxNzQuMSw4MC4yLDE3NC41LDgxLjd6IE0xNzAsODYuNGMwLTIuNi0wLjUtNC42LTEuNC02CgkJCQkJYy0wLjktMS40LTIuNC0yLjEtNC4zLTIuMWMtMiwwLTMuNCwwLjctNC4zLDIuMWMtMC45LDEuNC0xLjQsMy40LTEuNCw2YzAsMi42LDAuNSw0LjYsMS40LDYuMWMwLjksMS40LDIuMywyLjEsNC4zLDIuMQoJCQkJCWMyLDAsMy40LTAuNyw0LjMtMi4xQzE2OS42LDkxLjEsMTcwLDg5LDE3MCw4Ni40eiBNMjExLjEsOTMuOWwxLjEsM2MwLjEsMC40LTAuMiwwLjUtMC4yLDAuNWMtMS43LDAuNy00LDEuMS02LjMsMS4xCgkJCQkJYy0zLjksMC02LjgtMS4xLTguOC0zLjNjLTItMi4yLTMtNS4yLTMtOC45YzAtMS43LDAuMi0zLjMsMC43LTQuOGMwLjUtMS41LDEuMi0yLjgsMi4yLTMuOWMxLTEuMSwyLjItMiwzLjYtMi42CgkJCQkJYzEuNC0wLjYsMy4xLTEsNS0xYzEuMywwLDIuNCwwLjEsMy4zLDAuMmMxLDAuMiwyLjQsMC41LDMsMC44YzAuMSwwLDAuNCwwLjIsMC4zLDAuNWMtMC40LDEuMi0wLjcsMi0xLjEsMwoJCQkJCWMtMC4yLDAuNS0wLjUsMC4zLTAuNSwwLjNjLTEuNS0wLjUtMi45LTAuNy00LjctMC43Yy0yLjIsMC0zLjksMC43LTQuOSwyLjJjLTEuMSwxLjQtMS43LDMuMy0xLjcsNS45YzAsMi44LDAuNyw0LjgsMS45LDYuMQoJCQkJCWMxLjIsMS4zLDIuOSwxLjksNS4xLDEuOWMwLjksMCwxLjctMC4xLDIuNC0wLjJjMC43LTAuMSwxLjQtMC4zLDIuMS0wLjZDMjEwLjUsOTMuNiwyMTAuOSw5My41LDIxMS4xLDkzLjl6IE0yMzMuOCw4MC44CgkJCQkJYzEsMy40LDAuNSw2LjMsMC40LDYuNWMwLDAuNC0wLjQsMC40LTAuNCwwLjRsLTE1LjEsMGMwLjEsMi4zLDAuNiwzLjksMS44LDVjMS4xLDEuMSwyLjgsMS44LDUuMiwxLjhjMy42LDAsNS4xLTAuNyw2LjItMS4xCgkJCQkJYzAsMCwwLjQtMC4xLDAuNiwwLjNsMSwyLjhjMC4yLDAuNSwwLDAuNi0wLjEsMC43Yy0wLjksMC41LTMuMiwxLjUtNy42LDEuNWMtMi4xLDAtNC0wLjMtNS41LTAuOWMtMS41LTAuNi0yLjgtMS40LTMuOC0yLjUKCQkJCQljLTEtMS4xLTEuNy0yLjQtMi4yLTMuOGMtMC41LTEuNS0wLjctMy4xLTAuNy00LjhjMC0xLjcsMC4yLTMuMywwLjctNC44YzAuNC0xLjUsMS4xLTIuOCwyLTMuOWMwLjktMS4xLDIuMS0yLDMuNS0yLjYKCQkJCQljMS40LTAuNywzLjEtMSw1LTFjMS42LDAsMy4xLDAuMyw0LjMsMC45YzAuOSwwLjQsMS45LDEuMSwyLjksMi4yQzIzMi41LDc3LjksMjMzLjQsNzkuNCwyMzMuOCw4MC44eiBNMjE4LjgsODRoMTAuNwoJCQkJCWMtMC4xLTEuNC0wLjQtMi42LTEtMy42Yy0wLjktMS40LTIuMi0yLjItNC4yLTIuMmMtMiwwLTMuNCwwLjgtNC4zLDIuMkMyMTkuNCw4MS4zLDIxOS4xLDgyLjUsMjE4LjgsODR6IE0xMTMuMSw4MC44CgkJCQkJYzEsMy40LDAuNSw2LjMsMC41LDYuNWMwLDAuNC0wLjQsMC40LTAuNCwwLjRsLTE1LjEsMGMwLjEsMi4zLDAuNiwzLjksMS44LDVjMS4xLDEuMSwyLjgsMS44LDUuMiwxLjhjMy42LDAsNS4xLTAuNyw2LjItMS4xCgkJCQkJYzAsMCwwLjQtMC4xLDAuNiwwLjNsMSwyLjhjMC4yLDAuNSwwLDAuNi0wLjEsMC43Yy0wLjksMC41LTMuMiwxLjUtNy42LDEuNWMtMi4xLDAtNC0wLjMtNS41LTAuOWMtMS41LTAuNi0yLjgtMS40LTMuOC0yLjUKCQkJCQljLTEtMS4xLTEuNy0yLjQtMi4yLTMuOGMtMC41LTEuNS0wLjctMy4xLTAuNy00LjhjMC0xLjcsMC4yLTMuMywwLjctNC44YzAuNC0xLjUsMS4xLTIuOCwyLTMuOWMwLjktMS4xLDIuMS0yLDMuNS0yLjYKCQkJCQljMS40LTAuNywzLjEtMSw1LTFjMS42LDAsMy4xLDAuMyw0LjMsMC45YzAuOSwwLjQsMS45LDEuMSwyLjksMi4yQzExMS44LDc3LjksMTEyLjgsNzkuNCwxMTMuMSw4MC44eiBNOTguMSw4NGgxMC44CgkJCQkJYy0wLjEtMS40LTAuNC0yLjYtMS0zLjZjLTAuOS0xLjQtMi4yLTIuMi00LjItMi4yYy0yLDAtMy40LDAuOC00LjMsMi4yQzk4LjcsODEuMyw5OC40LDgyLjUsOTguMSw4NHogTTcxLjYsODMuMgoJCQkJCWMwLDAsMS4yLDAuMSwyLjUsMC4zdi0wLjZjMC0yLTAuNC0zLTEuMi0zLjZjLTAuOC0wLjYtMi4xLTEtMy43LTFjMCwwLTMuNywwLTYuNiwxLjVjLTAuMSwwLjEtMC4yLDAuMS0wLjIsMC4xCgkJCQkJcy0wLjQsMC4xLTAuNS0wLjJsLTEuMS0yLjljLTAuMi0wLjQsMC4xLTAuNiwwLjEtMC42YzEuNC0xLjEsNC42LTEuNyw0LjYtMS43YzEuMS0wLjIsMi45LTAuNCw0LTAuNGMzLDAsNS4zLDAuNyw2LjksMi4xCgkJCQkJYzEuNiwxLjQsMi40LDMuNiwyLjQsNi43bDAsMTMuOGMwLDAsMCwwLjQtMC4zLDAuNWMwLDAtMC42LDAuMi0xLjEsMC4zYy0wLjUsMC4xLTIuMywwLjUtMy44LDAuN2MtMS41LDAuMy0zLDAuNC00LjYsMC40CgkJCQkJYy0xLjUsMC0yLjgtMC4xLTQtMC40Yy0xLjItMC4zLTIuMi0wLjctMy4xLTEuM2MtMC44LTAuNi0xLjUtMS40LTItMi40Yy0wLjUtMC45LTAuNy0yLjEtMC43LTMuNGMwLTEuMywwLjMtMi41LDAuOC0zLjUKCQkJCQljMC41LTEsMS4zLTEuOCwyLjItMi41YzAuOS0wLjcsMi0xLjEsMy4xLTEuNWMxLjItMC4zLDIuNC0wLjUsMy43LTAuNUM3MC4yLDgzLjIsNzEsODMuMiw3MS42LDgzLjJ6IE02NS42LDkzLjgKCQkJCQljMCwwLDEuNCwxLjEsNC40LDAuOWMyLjItMC4xLDQuMS0wLjUsNC4xLTAuNXYtNi45YzAsMC0xLjktMC4zLTQuMS0wLjNjLTMuMSwwLTQuNCwxLjEtNC40LDEuMWMtMC45LDAuNi0xLjMsMS42LTEuMywyLjkKCQkJCQljMCwwLjgsMC4yLDEuNSwwLjUsMkM2NC45LDkzLjIsNjUsOTMuNCw2NS42LDkzLjh6IE0xOTMuMSw3NS41Yy0wLjEsMC40LTAuOSwyLjUtMS4xLDMuMmMtMC4xLDAuMy0wLjMsMC40LTAuNiwwLjQKCQkJCQljMCwwLTAuOS0wLjItMS43LTAuMmMtMC41LDAtMS4zLDAuMS0yLDAuM2MtMC43LDAuMi0xLjMsMC42LTEuOSwxLjFjLTAuNiwwLjUtMSwxLjMtMS4zLDIuMmMtMC4zLDAuOS0wLjUsMi40LTAuNSw0djExLjIKCQkJCQljMCwwLjMtMC4yLDAuNS0wLjUsMC41aC00Yy0wLjMsMC0wLjUtMC4yLTAuNS0wLjVWNzUuMmMwLTAuMywwLjItMC41LDAuNC0wLjVoMy45YzAuMywwLDAuNCwwLjIsMC40LDAuNVY3NwoJCQkJCWMwLjYtMC44LDEuNi0xLjUsMi41LTEuOWMwLjktMC40LDItMC43LDMuOS0wLjZjMSwwLjEsMi4zLDAuMywyLjUsMC40QzE5Myw3NSwxOTMuMiw3NS4xLDE5My4xLDc1LjV6IE0xNTYsNjUuMQoJCQkJCWMwLjEsMCwwLjQsMC4yLDAuMywwLjVsLTEuMiwzLjJjLTAuMSwwLjItMC4yLDAuNC0wLjcsMC4yYy0wLjEsMC0wLjMtMC4xLTAuOC0wLjJjLTAuMy0wLjEtMC44LTAuMS0xLjItMC4xCgkJCQkJYy0wLjYsMC0xLjEsMC4xLTEuNiwwLjJjLTAuNSwwLjEtMC45LDAuNC0xLjMsMC44Yy0wLjQsMC40LTAuOCwwLjktMS4xLDEuNmMtMC42LDEuNi0wLjgsMy4zLTAuOCwzLjRoNC44CgkJCQkJYzAuNCwwLDAuNSwwLjIsMC41LDAuNWwtMC42LDMuMWMtMC4xLDAuNS0wLjUsMC40LTAuNSwwLjRoLTVMMTQzLjYsOThjLTAuNCwyLTAuOCwzLjctMS4zLDUuMWMtMC41LDEuNC0xLjEsMi40LTIsMy40CgkJCQkJYy0wLjgsMC45LTEuNywxLjYtMi44LDEuOWMtMSwwLjQtMi4zLDAuNi0zLjcsMC42Yy0wLjcsMC0xLjQsMC0yLjItMC4yYy0wLjYtMC4xLTAuOS0wLjItMS40LTAuNGMtMC4yLTAuMS0wLjMtMC4zLTAuMi0wLjYKCQkJCQljMC4xLTAuMywxLTIuNywxLjEtMy4xYzAuMi0wLjQsMC41LTAuMiwwLjUtMC4yYzAuMywwLjEsMC41LDAuMiwwLjgsMC4zYzAuNCwwLjEsMC44LDAuMSwxLjIsMC4xYzAuNywwLDEuMy0wLjEsMS44LTAuMwoJCQkJCWMwLjYtMC4yLDEtMC42LDEuNC0xLjFjMC40LTAuNSwwLjctMS4yLDEuMS0yLjFjMC4zLTAuOSwwLjYtMi4yLDAuOS0zLjdsMy40LTE4LjloLTMuM2MtMC40LDAtMC41LTAuMi0wLjUtMC41bDAuNi0zLjEKCQkJCQljMC4xLTAuNSwwLjUtMC40LDAuNS0wLjRoMy40bDAuMi0xYzAuNS0zLDEuNS01LjMsMy02LjhjMS41LTEuNSwzLjctMi4zLDYuNC0yLjNjMC44LDAsMS41LDAuMSwyLjEsMC4yCgkJCQkJQzE1NSw2NC44LDE1NS41LDY0LjksMTU2LDY1LjF6IE04OC42LDk3LjZjMCwwLjMtMC4yLDAuNS0wLjQsMC41aC00Yy0wLjMsMC0wLjQtMC4yLTAuNC0wLjVWNjUuNWMwLTAuMiwwLjItMC41LDAuNC0wLjVoNAoJCQkJCWMwLjMsMCwwLjQsMC4yLDAuNCwwLjVWOTcuNnoiLz4KCQkJPC9nPgoJCTwvZz4KCTwvZz4KPC9nPgo8L3N2Zz4K"]]));
// Server ignores requests for site icons
const iconPaths = ['/favicon.ico', '/apple-touch-icon-precomposed.png'];
/**
* Handles the creation of a web server for web based login flows.
*
* Usage:
* ```
* const oauthConfig = {
* loginUrl: this.flags.instanceurl,
* clientId: this.flags.clientid,
* };
*
* const oauthServer = await WebOAuthServer.create({ oauthConfig });
* await oauthServer.start();
* await open(oauthServer.getAuthorizationUrl(), { wait: false });
* const authInfo = await oauthServer.authorizeAndSave();
* ```
*/
class WebOAuthServer extends kit_1.AsyncCreatable {
static DEFAULT_PORT = 1717;
authUrl;
logger;
webServer;
oauth2;
oauthConfig;
oauthError = new Error('Oauth Error');
constructor(options) {
super(options);
this.oauthConfig = options.oauthConfig;
}
/**
* Returns the configured oauthLocalPort or the WebOAuthServer.DEFAULT_PORT
*
* @returns {Promise<number>}
*/
static async determineOauthPort() {
try {
const sfProject = await sfProject_1.SfProjectJson.create();
return sfProject.get('oauthLocalPort') || WebOAuthServer.DEFAULT_PORT;
}
catch {
return WebOAuthServer.DEFAULT_PORT;
}
}
/**
* Returns the authorization url that's used for the login flow
*
* @returns {string}
*/
getAuthorizationUrl() {
return this.authUrl;
}
/**
* Executes the oauth request and creates a new AuthInfo when successful
*
* @returns {Promise<AuthInfo>}
*/
async authorizeAndSave() {
if (!this.webServer.server)
await this.start();
return new Promise((resolve, reject) => {
const handler = () => {
this.logger.debug(`OAuth web login service listening on port: ${this.webServer.port}`);
this.executeOauthRequest()
.then(async (response) => {
try {
const authInfo = await authInfo_1.AuthInfo.create({
oauth2Options: this.oauthConfig,
oauth2: this.oauth2,
});
await authInfo.save();
await this.webServer.handleSuccess(response);
response.end();
resolve(authInfo);
}
catch (err) {
this.oauthError = err;
await this.webServer.handleError(response);
reject(err);
}
})
.catch((err) => {
this.logger.debug('error reported, closing server connection and re-throwing');
reject(err);
})
.finally(() => {
this.logger.debug('closing server connection');
this.webServer.close();
});
};
// if the server is already listening the listening event won't be fired anymore so execute handler() directly
if (this.webServer.server.listening) {
handler();
}
else {
this.webServer.server.once('listening', handler);
}
});
}
/**
* Starts the web server
*/
async start() {
await this.webServer.start();
}
async init() {
this.logger = await logger_1.Logger.child(this.constructor.name);
const port = await WebOAuthServer.determineOauthPort();
if (!this.oauthConfig.clientId)
this.oauthConfig.clientId = authInfo_1.DEFAULT_CONNECTED_APP_INFO.clientId;
if (!this.oauthConfig.loginUrl)
this.oauthConfig.loginUrl = authInfo_1.AuthInfo.getDefaultInstanceUrl();
if (!this.oauthConfig.redirectUri)
this.oauthConfig.redirectUri = `http://localhost:${port}/OauthRedirect`;
// Unless explicitly turned off, use a code verifier as a best practice
if (this.oauthConfig.useVerifier !== false)
this.oauthConfig.useVerifier = true;
this.webServer = await WebServer.create({ port });
this.oauth2 = new jsforce_node_1.OAuth2(this.oauthConfig);
this.authUrl = authInfo_1.AuthInfo.getAuthorizationUrl(this.oauthConfig, this.oauth2);
}
/**
* Executes the oauth request
*
* @returns {Promise<AuthInfo>}
*/
async executeOauthRequest() {
return new Promise((resolve, reject) => {
this.logger.debug('Starting web auth flow');
// - async method when sync expected
// eslint-disable-next-line @typescript-eslint/no-misused-promises
this.webServer.server.on('request', async (request, response) => {
if (request.url) {
const url = (0, node_url_1.parse)(request.url);
this.logger.debug(`processing request for uri: ${url.pathname}`);
if (request.method === 'GET') {
if (url.pathname?.startsWith('/OauthRedirect') && url.query) {
// eslint-disable-next-line no-param-reassign
request.query = (0, node_querystring_1.parse)(url.query);
if (request.query.error) {
const errorName = typeof request.query.error_description === 'string'
? request.query.error_description
: request.query.error;
this.oauthError = new sfError_1.SfError(errorName, request.query.error);
await this.webServer.handleError(response);
return reject(this.oauthError);
}
this.logger.debug(`request.query.state: ${request.query.state}`);
try {
this.oauthConfig.authCode = (0, ts_types_1.asString)(this.parseAuthCodeFromRequest(response, request));
resolve(response);
}
catch (err) {
reject(err);
}
}
else if (url.pathname === '/OauthSuccess') {
this.webServer.reportSuccess(response);
}
else if (url.pathname === '/OauthError') {
this.webServer.reportError(this.oauthError, response);
}
else if (iconPaths.includes(url.pathname ?? '')) {
this.logger.debug(`Ignoring request for icon path: ${url.pathname}`);
}
else {
this.webServer.sendError(404, 'Resource not found', response);
const errName = 'invalidRequestUri';
const errMessage = messages.getMessage(errName, [url.pathname]);
reject(new sfError_1.SfError(errMessage, errName));
}
}
else if (request.method === 'OPTIONS' &&
request.headers['access-control-request-private-network'] === 'true' &&
request.headers['access-control-request-method']) {
this.webServer.handlePreflightRequest(response);
}
else {
this.webServer.sendError(405, 'Unsupported http methods', response);
const errName = 'invalidRequestMethod';
const errMessage = messages.getMessage(errName, [request.method]);
reject(new sfError_1.SfError(errMessage, errName));
}
}
});
});
}
/**
* Parses the auth code from the request url
*
* @param response the http response
* @param request the http request
* @returns {Nullable<string>}
*/
parseAuthCodeFromRequest(response, request) {
if (!this.validateState(request)) {
const error = new sfError_1.SfError('urlStateMismatch');
this.webServer.sendError(400, error.message, response);
this.closeRequest(request);
this.logger.warn('urlStateMismatchAttempt detected.');
if (!(0, ts_types_1.get)(this.webServer.server, 'urlStateMismatchAttempt')) {
this.logger.error(error.message);
(0, kit_1.set)(this.webServer.server, 'urlStateMismatchAttempt', true);
}
}
else {
const authCode = request.query.code;
if (authCode && authCode.length > 4) {
// AuthCodes are generally long strings. For security purposes we will just log the last 4 of the auth code.
this.logger.debug(`Successfully obtained auth code: ...${authCode.substring(authCode.length - 5)}`);
}
else {
this.logger.debug('Expected an auth code but could not find one.');
throw messages.createError('missingAuthCode');
}
this.logger.debug(`oauthConfig.loginUrl: ${this.oauthConfig.loginUrl}`);
this.logger.debug(`oauthConfig.clientId: ${this.oauthConfig.clientId}`);
this.logger.debug(`oauthConfig.redirectUri: ${this.oauthConfig.redirectUri}`);
this.logger.debug(`oauthConfig.useVerifier: ${this.oauthConfig.useVerifier}`);
return authCode;
}
return null;
}
/**
* Closes the request
*
* @param request the http request
*/
closeRequest(request) {
request.connection.end();
request.connection.destroy();
}
/**
* Validates that the state param in the auth url matches the state
* param in the http request
*
* @param request the http request
*/
validateState(request) {
const state = request.query.state;
const query = (0, node_url_1.parse)(this.authUrl, true).query;
return !!(state && state === query.state);
}
}
exports.WebOAuthServer = WebOAuthServer;
/**
* Handles the actions specific to the http server
*/
class WebServer extends kit_1.AsyncCreatable {
static DEFAULT_CLIENT_SOCKET_TIMEOUT = 20_000;
server;
port = WebOAuthServer.DEFAULT_PORT;
host = 'localhost';
logger;
sockets = [];
redirectStatus = new node_events_1.EventEmitter();
constructor(options) {
super(options);
if (options.port)
this.port = options.port;
if (options.host)
this.host = options.host;
}
/**
* Starts the http server after checking that the port is open
*/
async start() {
try {
this.logger.debug('Starting web server');
await this.checkOsPort();
this.logger.debug(`Nothing listening on host: localhost port: ${this.port} - good!`);
this.server = http.createServer();
this.server.on('connection', (socket) => {
this.logger.debug(`socket connection initialized from ${socket.remoteAddress}`);
this.sockets.push(socket);
});
this.server.listen(this.port, this.host);
}
catch (err) {
if (err.name === 'EADDRINUSE') {
throw messages.createError('portInUse', [this.port], [this.port]);
}
else {
throw err;
}
}
}
/**
* Closes the http server and all open sockets
*/
close() {
this.sockets.forEach((socket) => {
socket.end();
socket.destroy();
});
this.server.getConnections((_, num) => {
this.logger.debug(`number of connections open: ${num}`);
});
this.server.close();
}
/**
* sends a response error.
*
* @param status the statusCode for the response.
* @param message the message for the http body.
* @param response the response to write the error to.
*/
sendError(status, message, response) {
// eslint-disable-next-line no-param-reassign
response.statusMessage = message;
// eslint-disable-next-line no-param-reassign
response.statusCode = status;
response.end();
}
/**
* sends a response redirect.
*
* @param status the statusCode for the response.
* @param url the url to redirect to.
* @param response the response to write the redirect to.
*/
doRedirect(status, url, response) {
this.logger.debug(`Redirecting to ${url}`);
response.setHeader('Content-Type', 'text/plain');
const body = `${status} - Redirecting to ${url}`;
response.setHeader('Content-Length', Buffer.byteLength(body));
response.writeHead(status, { Location: url });
response.end(body);
}
/**
* sends a response to the browser reporting an error.
*
* @param error the oauth error
* @param response the HTTP response.
*/
reportError(error, response) {
response.setHeader('Content-Type', 'text/html');
const currentYear = new Date().getFullYear();
const encodedImg = messages.getMessage('serverSfdcImage');
const body = messages.getMessage('serverErrorHTMLResponse', [encodedImg, error.name, error.message, currentYear]);
response.setHeader('Content-Length', Buffer.byteLength(body, 'utf8'));
response.end(body);
if (error.stack) {
this.logger.debug(error.stack);
}
this.redirectStatus.emit('complete');
}
/**
* sends a response to the browser reporting the success.
*
* @param response the HTTP response.
*/
reportSuccess(response) {
response.setHeader('Content-Type', 'text/html');
const currentYear = new Date().getFullYear();
const encodedImg = messages.getMessage('serverSfdcImage');
const body = messages.getMessage('serverSuccessHTMLResponse', [encodedImg, currentYear]);
response.setHeader('Content-Length', Buffer.byteLength(body, 'utf8'));
response.end(body);
this.redirectStatus.emit('complete');
}
/**
* Preflight request:
*
* https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request
* https://www.w3.org/TR/2020/SPSD-cors-20200602/#resource-preflight-requests
*/
handlePreflightRequest(response) {
// We don't validate the origin here because:
// 1. The default login URL (login.salesforce.com) will not match after a redirect or if user choose a custom domain in login.
// 2. There's no fixed list of auth URLs we could check against.
// eslint-disable-next-line no-param-reassign
response.statusCode = 204; // No Content response
response.setHeader('Access-Control-Allow-Methods', 'GET');
response.setHeader('Access-Control-Request-Headers', 'GET');
response.end();
}
async handleSuccess(response) {
return this.handleRedirect(response, '/OauthSuccess');
}
async handleError(response) {
return this.handleRedirect(response, '/OauthError');
}
async init() {
this.logger = await logger_1.Logger.child(this.constructor.name);
}
async handleRedirect(response, url) {
return new Promise((resolve) => {
this.redirectStatus.on('complete', () => {
this.logger.debug('Redirect complete');
resolve();
});
this.doRedirect(303, url, response);
});
}
/**
* Make sure we can't open a socket on the localhost/host port. It's important because we don't want to send
* auth tokens to a random strange port listener. We want to make sure we can startup our server first.
*
* @private
*/
async checkOsPort() {
return new Promise((resolve, reject) => {
const clientConfig = { port: this.port, host: this.host };
const socket = new node_net_1.Socket();
socket.setTimeout(this.getSocketTimeout(), () => {
socket.destroy();
const error = new sfError_1.SfError('timeout', 'SOCKET_TIMEOUT');
reject(error);
});
// An existing connection, means that the port is occupied
socket.connect(clientConfig, () => {
socket.destroy();
const error = new sfError_1.SfError('Address in use', 'EADDRINUSE');
error.data = {
port: clientConfig.port,
address: clientConfig.host,
};
reject(error);
});
// An error means that no existing connection exists, which is what we want
socket.on('error', () => {
// eslint-disable-next-line no-console
socket.destroy();
resolve(this.port);
});
});
}
/**
* check and get the socket timeout form what was set in process.env.SFDX_HTTP_SOCKET_TIMEOUT
*
* @returns {number} - represents the socket timeout in ms
* @private
*/
getSocketTimeout() {
const env = new kit_1.Env();
const socketTimeout = (0, kit_1.toNumber)(env.getNumber('SFDX_HTTP_SOCKET_TIMEOUT'));
return Number.isInteger(socketTimeout) && socketTimeout > 0
? socketTimeout
: WebServer.DEFAULT_CLIENT_SOCKET_TIMEOUT;
}
}
exports.WebServer = WebServer;
//# sourceMappingURL=webOAuthServer.js.map