From b82e1dafa535a9b20afd9424b87ff92aa7c7536c Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 13 Nov 2024 08:03:39 -0600 Subject: [PATCH] feat: sfdxAuthUrl support port in instanceUrl --- src/org/authInfo.ts | 18 ++--- test/unit/org/authInfo.test.ts | 125 ++++++++++++++++++++++++--------- 2 files changed, 100 insertions(+), 43 deletions(-) diff --git a/src/org/authInfo.ts b/src/org/authInfo.ts index 15558cfc6..dda82cb5d 100644 --- a/src/org/authInfo.ts +++ b/src/org/authInfo.ts @@ -363,7 +363,7 @@ export class AuthInfo extends AsyncOptionalCreatable { sfdxAuthUrl: string ): Pick { const match = sfdxAuthUrl.match( - /^force:\/\/([a-zA-Z0-9._-]+={0,2}):([a-zA-Z0-9._-]*={0,2}):([a-zA-Z0-9._-]+={0,2})@([a-zA-Z0-9._-]+)/ + /^force:\/\/([a-zA-Z0-9._-]+={0,2}):([a-zA-Z0-9._-]*={0,2}):([a-zA-Z0-9._-]+={0,2})@([a-zA-Z0-9:._-]+)/ ); if (!match) { @@ -687,16 +687,12 @@ export class AuthInfo extends AsyncOptionalCreatable { * **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) */ public getSfdxAuthUrl(): string { - const decryptedFields = this.getFields(true); - const instanceUrl = ensure(decryptedFields.instanceUrl, 'undefined instanceUrl').replace(/^https?:\/\//, ''); - let sfdxAuthUrl = 'force://'; - - if (decryptedFields.clientId) { - sfdxAuthUrl += `${decryptedFields.clientId}:${decryptedFields.clientSecret ?? ''}:`; - } - - sfdxAuthUrl += `${ensure(decryptedFields.refreshToken, 'undefined refreshToken')}@${instanceUrl}`; - return sfdxAuthUrl; + const { clientId, clientSecret, refreshToken, instanceUrl } = this.getFields(true); + // host includes an optional port on the instanceUrl + const url = new URL(ensure(instanceUrl, 'undefined instanceUrl')).host; + const clientIdAndSecret = clientId ? `${clientId}:${clientSecret ?? ''}` : ''; + const token = ensure(refreshToken, 'undefined refreshToken'); + return `force://${clientIdAndSecret}:${token}@${url}`; } /** diff --git a/test/unit/org/authInfo.test.ts b/test/unit/org/authInfo.test.ts index baee1b1a2..dbd3ce709 100644 --- a/test/unit/org/authInfo.test.ts +++ b/test/unit/org/authInfo.test.ts @@ -15,7 +15,7 @@ import jwt from 'jsonwebtoken'; import { env, includes } from '@salesforce/kit'; import { spyMethod, stubMethod } from '@salesforce/ts-sinon'; import { AnyJson, getJsonMap, JsonMap, toJsonMap } from '@salesforce/ts-types'; -import { expect } from 'chai'; +import { expect, config as chaiConfig } from 'chai'; import { Transport } from '@jsforce/jsforce-node/lib/transport'; import { OAuth2 } from '@jsforce/jsforce-node'; @@ -34,6 +34,7 @@ import { SfdcUrl } from '../../../src/util/sfdcUrl'; import * as suggestion from '../../../src/util/findSuggestion'; import { SfError } from '../../../src'; +chaiConfig.truncateThreshold = 0; class AuthInfoMockOrg extends MockTestOrgData { public privateKey = 'authInfoTest/jwt/server.key'; public expirationDate = '12-02-20'; @@ -1273,13 +1274,15 @@ describe('AuthInfo', () => { ); }); - it('should handle undefined client secret', async () => { + it('should handle instance url with a port on it', async () => { stubMethod($$.SANDBOX, AuthInfo.prototype, 'determineIfDevHub').resolves(false); stubMethod($$.SANDBOX, AuthInfo.prototype, 'getNamespacePrefix').resolves(); + const url = new URL(`${testOrg.instanceUrl}:6101`); + const authResponse = { access_token: testOrg.accessToken, - instance_url: testOrg.instanceUrl, + instance_url: url.toString(), id: '00DAuthInfoTest_orgId/005AuthInfoTest_userId', }; @@ -1291,17 +1294,17 @@ describe('AuthInfo', () => { username: testOrg.username, oauth2Options: { refreshToken: testOrg.refreshToken, - loginUrl: testOrg.loginUrl, + loginUrl: url.toString(), }, }); - // delete the client secret - authInfo.update({ clientSecret: undefined }); - const instanceUrl = testOrg.instanceUrl.replace('https://', ''); - expect(authInfo.getSfdxAuthUrl()).to.contain(`force://PlatformCLI::${testOrg.refreshToken}@${instanceUrl}`); + // host will include the port + const result = authInfo.getSfdxAuthUrl(); + expect(result).to.contain(url.port); + expect(result).to.contain(url.host); }); - it('should handle undefined refresh token', async () => { + it('should handle undefined client secret', async () => { stubMethod($$.SANDBOX, AuthInfo.prototype, 'determineIfDevHub').resolves(false); stubMethod($$.SANDBOX, AuthInfo.prototype, 'getNamespacePrefix').resolves(); @@ -1323,37 +1326,67 @@ describe('AuthInfo', () => { }, }); - // delete the refresh token - authInfo.update({ ...authInfo.getFields(), refreshToken: undefined }); - expect(() => authInfo.getSfdxAuthUrl()).to.throw('undefined refreshToken'); + // delete the client secret + authInfo.update({ clientSecret: undefined }); + const instanceUrl = testOrg.instanceUrl.replace('https://', ''); + expect(authInfo.getSfdxAuthUrl()).to.contain(`force://PlatformCLI::${testOrg.refreshToken}@${instanceUrl}`); }); - it('should handle undefined instance url', async () => { - stubMethod($$.SANDBOX, AuthInfo.prototype, 'determineIfDevHub').resolves(false); - stubMethod($$.SANDBOX, AuthInfo.prototype, 'getNamespacePrefix').resolves(); + describe('error conditions', () => { + it('should handle undefined refresh token', async () => { + stubMethod($$.SANDBOX, AuthInfo.prototype, 'determineIfDevHub').resolves(false); + stubMethod($$.SANDBOX, AuthInfo.prototype, 'getNamespacePrefix').resolves(); - const authResponse = { - access_token: testOrg.accessToken, - instance_url: testOrg.instanceUrl, - id: '00DAuthInfoTest_orgId/005AuthInfoTest_userId', - }; + const authResponse = { + access_token: testOrg.accessToken, + instance_url: testOrg.instanceUrl, + id: '00DAuthInfoTest_orgId/005AuthInfoTest_userId', + }; - // Stub the http request (OAuth2.refreshToken()) - postParamsStub.resolves(authResponse); + // Stub the http request (OAuth2.refreshToken()) + postParamsStub.resolves(authResponse); - // Create the refresh token AuthInfo instance - const authInfo = await AuthInfo.create({ - username: testOrg.username, - oauth2Options: { - refreshToken: testOrg.refreshToken, - loginUrl: testOrg.loginUrl, - }, + // Create the refresh token AuthInfo instance + const authInfo = await AuthInfo.create({ + username: testOrg.username, + oauth2Options: { + refreshToken: testOrg.refreshToken, + loginUrl: testOrg.loginUrl, + }, + }); + + // delete the refresh token + authInfo.update({ ...authInfo.getFields(), refreshToken: undefined }); + expect(() => authInfo.getSfdxAuthUrl()).to.throw('undefined refreshToken'); }); - // delete the instance url - authInfo.update({ ...authInfo.getFields(), instanceUrl: undefined }); + it('should handle undefined instance url', async () => { + stubMethod($$.SANDBOX, AuthInfo.prototype, 'determineIfDevHub').resolves(false); + stubMethod($$.SANDBOX, AuthInfo.prototype, 'getNamespacePrefix').resolves(); - expect(() => authInfo.getSfdxAuthUrl()).to.throw('undefined instanceUrl'); + const authResponse = { + access_token: testOrg.accessToken, + instance_url: testOrg.instanceUrl, + id: '00DAuthInfoTest_orgId/005AuthInfoTest_userId', + }; + + // Stub the http request (OAuth2.refreshToken()) + postParamsStub.resolves(authResponse); + + // Create the refresh token AuthInfo instance + const authInfo = await AuthInfo.create({ + username: testOrg.username, + oauth2Options: { + refreshToken: testOrg.refreshToken, + loginUrl: testOrg.loginUrl, + }, + }); + + // delete the instance url + authInfo.update({ ...authInfo.getFields(), instanceUrl: undefined }); + + expect(() => authInfo.getSfdxAuthUrl()).to.throw('undefined instanceUrl'); + }); }); }); @@ -1703,6 +1736,19 @@ describe('AuthInfo', () => { expect(options.loginUrl).to.equal('https://test.my.salesforce.com'); }); + it('should parse the correct url with a port on the end', () => { + const options = AuthInfo.parseSfdxAuthUrl( + 'force://PlatformCLI::5Aep861_OKMvio5gy8xCNsXxybPdupY9fVEZyeVOvb4kpOZx5Z1QLB7k7n5flEqEWKcwUQEX1I.O5DCFwjlYUB.@test.my.salesforce.com:6101' + ); + + expect(options.refreshToken).to.equal( + '5Aep861_OKMvio5gy8xCNsXxybPdupY9fVEZyeVOvb4kpOZx5Z1QLB7k7n5flEqEWKcwUQEX1I.O5DCFwjlYUB.' + ); + expect(options.clientId).to.equal('PlatformCLI'); + expect(options.clientSecret).to.equal(''); + expect(options.loginUrl).to.equal('https://test.my.salesforce.com:6101'); + }); + it('should parse an id, secret, and token that include = for padding', () => { const options = AuthInfo.parseSfdxAuthUrl( 'force://3MVG9SemV5D80oBfPBCgboxuJ9cOMLWNM1DDOZ8zgvJGsz13H3J66coUBCFF3N0zEgLYijlkqeWk4ot_Q2.4o=:438437816653243682==:5Aep861_OKMvio5gy8xCNsXxybPdupY9fVEZyeVOvb4kpOZx5Z1QLB7k7n5flEqEWKcwUQEX1I.O5DCFwjlYU==@test.my.salesforce.com' @@ -1718,6 +1764,21 @@ describe('AuthInfo', () => { expect(options.loginUrl).to.equal('https://test.my.salesforce.com'); }); + it('should parse an id, secret, and token that include = for padding and a port', () => { + const options = AuthInfo.parseSfdxAuthUrl( + 'force://3MVG9SemV5D80oBfPBCgboxuJ9cOMLWNM1DDOZ8zgvJGsz13H3J66coUBCFF3N0zEgLYijlkqeWk4ot_Q2.4o=:438437816653243682==:5Aep861_OKMvio5gy8xCNsXxybPdupY9fVEZyeVOvb4kpOZx5Z1QLB7k7n5flEqEWKcwUQEX1I.O5DCFwjlYU==@test.my.salesforce.com:6101' + ); + + expect(options.refreshToken).to.equal( + '5Aep861_OKMvio5gy8xCNsXxybPdupY9fVEZyeVOvb4kpOZx5Z1QLB7k7n5flEqEWKcwUQEX1I.O5DCFwjlYU==' + ); + expect(options.clientId).to.equal( + '3MVG9SemV5D80oBfPBCgboxuJ9cOMLWNM1DDOZ8zgvJGsz13H3J66coUBCFF3N0zEgLYijlkqeWk4ot_Q2.4o=' + ); + expect(options.clientSecret).to.equal('438437816653243682=='); + expect(options.loginUrl).to.equal('https://test.my.salesforce.com:6101'); + }); + it('should parse the correct url with client secret', () => { const options = AuthInfo.parseSfdxAuthUrl( 'force://3MVG9SemV5D80oBfPBCgboxuJ9cOMLWNM1DDOZ8zgvJGsz13H3J66coUBCFF3N0zEgLYijlkqeWk4ot_Q2.4o:438437816653243682:5Aep861_OKMvio5gy8xCNsXxybPdupY9fVEZyeVOvb4kpOZx5Z1QLB7k7n5flEqEWKcwUQEX1I.O5DCFwjlYUB.@test.my.salesforce.com'