Skip to content

Commit

Permalink
Merge branch 'v3' into sm/org-create-with-alias
Browse files Browse the repository at this point in the history
  • Loading branch information
maggiben authored Jul 14, 2022
2 parents b34e355 + 5cb4960 commit be22660
Show file tree
Hide file tree
Showing 11 changed files with 1,216 additions and 1,212 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

### [3.23.9](https://github.com/forcedotcom/sfdx-core/compare/v3.23.8...v3.23.9) (2022-07-14)

### [3.23.8](https://github.com/forcedotcom/sfdx-core/compare/v3.23.7...v3.23.8) (2022-07-13)

### Bug Fixes
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@salesforce/core",
"version": "3.23.8",
"version": "3.23.9",
"description": "Core libraries to interact with SFDX projects, orgs, and APIs.",
"main": "lib/exported",
"types": "lib/exported.d.ts",
Expand Down Expand Up @@ -49,7 +49,7 @@
"form-data": "^4.0.0",
"graceful-fs": "^4.2.9",
"js2xmlparser": "^4.0.1",
"jsforce": "2.0.0-beta.14",
"jsforce": "beta",
"jsonwebtoken": "8.5.1",
"mkdirp": "1.0.4",
"ts-retry-promise": "^0.6.0"
Expand Down
6 changes: 2 additions & 4 deletions src/deviceOauthService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@

import Transport from 'jsforce/lib/transport';
import { AsyncCreatable, Duration, parseJsonMap } from '@salesforce/kit';
import { OAuth2Config } from 'jsforce/lib/oauth2';
import { HttpRequest } from 'jsforce';
import { HttpRequest, OAuth2Config } from 'jsforce';
import { ensureString, JsonMap, Nullable } from '@salesforce/ts-types';
import * as FormData from 'form-data';
import { Logger } from './logger';
import { AuthInfo, DEFAULT_CONNECTED_APP_INFO } from './org/authInfo';
import { AuthInfo, DEFAULT_CONNECTED_APP_INFO, SFDX_HTTP_HEADERS } from './org';
import { SfError } from './sfError';
import { SFDX_HTTP_HEADERS } from './org/connection';
import { Messages } from './messages';

Messages.importMessagesDirectory(__dirname);
Expand Down
54 changes: 11 additions & 43 deletions src/org/authInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
Nullable,
Optional,
} from '@salesforce/ts-types';
import { OAuth2, OAuth2Config as JsforceOAuth2Config, QueryResult, TokenResponse } from 'jsforce';
import { JwtOAuth2, JwtOAuth2Config, OAuth2, QueryResult, TokenResponse } from 'jsforce';
import Transport from 'jsforce/lib/transport';
import * as jwt from 'jsonwebtoken';
import { Config } from '../config/config';
Expand Down Expand Up @@ -52,17 +52,6 @@ const messages = Messages.load('@salesforce/core', 'core', [
'missingClientId',
]);

// These should these be brought into jsforce, especially all the jwt stuff.
// See https://github.com/jsforce/jsforce/issues/896
export type OAuth2Config = JsforceOAuth2Config & {
privateKey?: string;
privateKeyFile?: string;
authCode?: string;
refreshToken?: string;
loginUrl?: string;
username?: string;
};

/**
* Fields for authorization, org, and local information.
*/
Expand Down Expand Up @@ -148,7 +137,7 @@ type User = AnyJson & {
Username: string;
};

type AuthOptions = OAuth2Config & AccessTokenOptions;
type AuthOptions = JwtOAuth2Config & AccessTokenOptions;

/**
* A function to update a refresh token when the access token is expired.
Expand All @@ -165,29 +154,13 @@ export type ConnectionOptions = AuthFields & {
/**
* OAuth options.
*/
oauth2?: Partial<OAuth2Config>;
oauth2?: Partial<JwtOAuth2Config>;
/**
* Refresh token callback.
*/
refreshFn?: RefreshFn;
};

// Extend OAuth2 to add JWT Bearer Token Flow support.
class JwtOAuth2 extends OAuth2 {
public constructor(options: OAuth2Config) {
super(options);
}

public jwtAuthorize(innerToken: string): Promise<AnyJson> {
// @ts-ignore
return super._postParams({
// eslint-disable-next-line camelcase
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: innerToken,
});
}
}

// parses the id field returned from jsForce oauth2 methods to get
// user ID and org ID.
function parseIdUrl(idUrl: string) {
Expand Down Expand Up @@ -364,7 +337,7 @@ export class AuthInfo extends AsyncOptionalCreatable<AuthInfo.Options> {
*
* @param options The options to generate the URL.
*/
public static getAuthorizationUrl(options: OAuth2Config & { scope?: string }, oauth2?: OAuth2): string {
public static getAuthorizationUrl(options: JwtOAuth2Config & { scope?: string }, oauth2?: OAuth2): string {
// Always use a verifier for enhanced security
options.useVerifier = true;
const oauth2Verifier = oauth2 || new OAuth2(options);
Expand Down Expand Up @@ -718,7 +691,7 @@ export class AuthInfo extends AsyncOptionalCreatable<AuthInfo.Options> {
this.stateAggregator = await StateAggregator.getInstance();

const username = this.options.username;
const authOptions = (this.options.oauth2Options || this.options.accessTokenOptions) as AuthOptions;
const authOptions: AuthOptions = this.options.oauth2Options || (this.options.accessTokenOptions as AuthOptions);

// Must specify either username and/or options
if (!username && !authOptions) {
Expand Down Expand Up @@ -780,7 +753,7 @@ export class AuthInfo extends AsyncOptionalCreatable<AuthInfo.Options> {
* **Throws** *{@link SfError}{ name: 'NamedOrgNotFoundError' }* Org information does not exist.
* @returns {Promise<AuthInfo>}
*/
private async initAuthOptions(options?: OAuth2Config | AccessTokenOptions): Promise<AuthInfo> {
private async initAuthOptions(options?: JwtOAuth2Config | AccessTokenOptions): Promise<AuthInfo> {
this.logger = await Logger.child('AuthInfo');

// If options were passed, use those before checking cache and reading an auth file.
Expand Down Expand Up @@ -857,7 +830,7 @@ export class AuthInfo extends AsyncOptionalCreatable<AuthInfo.Options> {
return authInfo;
}

private isTokenOptions(options: OAuth2Config | AccessTokenOptions): options is AccessTokenOptions {
private isTokenOptions(options: JwtOAuth2Config | AccessTokenOptions): options is AccessTokenOptions {
// Although OAuth2Config does not contain refreshToken, privateKey, or privateKeyFile, a JS consumer could still pass those in
// which WILL have an access token as well, but it should be considered an OAuth2Config at that point.
return (
Expand Down Expand Up @@ -897,7 +870,7 @@ export class AuthInfo extends AsyncOptionalCreatable<AuthInfo.Options> {
}

// Build OAuth config for a JWT auth flow
private async authJwt(options: OAuth2Config): Promise<AuthFields> {
private async authJwt(options: JwtOAuth2Config): Promise<AuthFields> {
if (!options.clientId) {
throw messages.createError('missingClientId');
}
Expand Down Expand Up @@ -971,7 +944,7 @@ export class AuthInfo extends AsyncOptionalCreatable<AuthInfo.Options> {
}

// Build OAuth config for a refresh token auth flow
private async buildRefreshTokenConfig(options: OAuth2Config): Promise<AuthFields> {
private async buildRefreshTokenConfig(options: JwtOAuth2Config): Promise<AuthFields> {
// Ideally, this would be removed at some point in the distant future when all auth files
// now have the clientId stored in it.
if (!options.clientId) {
Expand Down Expand Up @@ -1003,9 +976,7 @@ export class AuthInfo extends AsyncOptionalCreatable<AuthInfo.Options> {
orgId,
username,
accessToken: authFieldsBuilder.access_token,
// @ts-ignore TODO: need better typings for jsforce
instanceUrl: authFieldsBuilder.instance_url,
// @ts-ignore TODO: need better typings for jsforce
loginUrl: options.loginUrl || authFieldsBuilder.instance_url,
refreshToken: options.refreshToken,
clientId: options.clientId,
Expand All @@ -1019,7 +990,7 @@ export class AuthInfo extends AsyncOptionalCreatable<AuthInfo.Options> {
* @param options The oauth options
* @param oauth2 The oauth2 extension that includes a code_challenge
*/
private async exchangeToken(options: OAuth2Config, oauth2: OAuth2 = new OAuth2(options)): Promise<AuthFields> {
private async exchangeToken(options: JwtOAuth2Config, oauth2: OAuth2 = new OAuth2(options)): Promise<AuthFields> {
if (!oauth2.redirectUri) {
oauth2.redirectUri = this.getRedirectUri();
}
Expand All @@ -1036,7 +1007,6 @@ export class AuthInfo extends AsyncOptionalCreatable<AuthInfo.Options> {
throw messages.createError('authCodeExchangeError', [(err as Error).message]);
}

// @ts-ignore TODO: need better typings for jsforce
const { orgId } = parseIdUrl(authFields.id);

let username: Optional<string> = this.getUsername();
Expand All @@ -1051,11 +1021,9 @@ export class AuthInfo extends AsyncOptionalCreatable<AuthInfo.Options> {

return {
accessToken: authFields.access_token,
// @ts-ignore TODO: need better typings for jsforce
instanceUrl: authFields.instance_url,
orgId,
username,
// @ts-ignore TODO: need better typings for jsforce
loginUrl: options.loginUrl || authFields.instance_url,
refreshToken: authFields.refresh_token,
clientId: options.clientId,
Expand Down Expand Up @@ -1157,7 +1125,7 @@ export namespace AuthInfo {
/**
* OAuth options.
*/
oauth2Options?: OAuth2Config;
oauth2Options?: JwtOAuth2Config;
/**
* Options for the access token auth.
*/
Expand Down
69 changes: 9 additions & 60 deletions src/org/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,18 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */

import { URL } from 'url';
import * as FormData from 'form-data';
import { AsyncResult, DeployOptions, DeployResultLocator } from 'jsforce/api/metadata';
import { Duration, maxBy, env } from '@salesforce/kit';
import { Duration, env, maxBy } from '@salesforce/kit';
import { asString, ensure, isString, JsonCollection, JsonMap, Nullable, Optional } from '@salesforce/ts-types';
import {
Connection as JSForceConnection,
ConnectionConfig,
HttpMethods,
HttpRequest,
QueryOptions,
QueryResult,
Record,
Schema,
HttpMethods,
} from 'jsforce';
import { Tooling as JSForceTooling } from 'jsforce/lib/api/tooling';
import { StreamPromise } from 'jsforce/lib/util/promise';
Expand Down Expand Up @@ -164,14 +163,10 @@ export class Connection<S extends Schema = Schema> extends JSForceConnection<S>
}

/**
* TODO: This should be moved into JSForce V2 once ready
* this is only a temporary solution to support both REST and SOAP APIs
*
* deploy a zipped buffer from the SDRL with REST or SOAP
*
* @param zipInput data to deploy
* @param options JSForce deploy options + a boolean for rest
* @param callback
*/
public async deploy(
zipInput: Buffer,
Expand All @@ -183,33 +178,7 @@ export class Connection<S extends Schema = Schema> extends JSForceConnection<S>
if (rest) {
this.logger.debug('deploy with REST');
await this.refreshAuth();
const headers: { [key: string]: string } = {
Authorization: this && `OAuth ${this.accessToken}`,
'Sforce-Call-Options': 'client=sfdx-core',
};
const client = this.oauth2 && this.oauth2.clientId;

if (client) {
headers.clientId = client;
}

const form = new FormData();

// Add the zip file
form.append('file', zipInput, {
contentType: 'application/zip',
filename: 'package.xml',
});

// Add the deploy options
form.append('entity_content', JSON.stringify({ deployOptions: options }), {
contentType: 'application/json',
});

const url = `${this.baseUrl()}/metadata/deployRequest`;

const httpRequest: HttpRequest = { method: 'POST', url, headers, body: form };
return this.request(httpRequest);
return this.metadata.deployRest(zipInput, options);
} else {
this.logger.debug('deploy with SOAP');
return this.metadata.deploy(zipInput, options);
Expand All @@ -227,7 +196,6 @@ export class Connection<S extends Schema = Schema> extends JSForceConnection<S>
const httpRequest: HttpRequest = isString(request) ? { method: 'GET', url: request } : request;
httpRequest.headers = Object.assign({}, SFDX_HTTP_HEADERS, httpRequest.headers);
this.logger.debug(`request: ${JSON.stringify(httpRequest)}`);
// The "as" is a workaround for the jsforce typings.
return super.request(httpRequest, options);
}

Expand All @@ -240,36 +208,17 @@ export class Connection<S extends Schema = Schema> extends JSForceConnection<S>
}

/**
* TODO: This should be moved into JSForce V2 once ready
* this is only a temporary solution to support both REST and SOAP APIs
*
* Will deploy a recently validated deploy request
* Will deploy a recently validated deploy request - directly calling jsforce now that this is supported.
* WARNING: will always return a string from jsforce, the type is JsonCollection to support backwards compatibility
*
* @param options.id = the deploy ID that's been validated already from a previous checkOnly deploy request
* @param options.rest = a boolean whether or not to use the REST API
* @deprecated use {@link Connection.metadata#deployRecentValidation} instead - the jsforce implementation, instead of this wrapper
*/
public async deployRecentValidation(options: recentValidationOptions): Promise<JsonCollection> {
const rest = options.rest;
delete options.rest;
if (rest) {
const url = `${this.baseUrl()}/metadata/deployRequest`;
const messageBody = JSON.stringify({
validatedDeployRequestId: options.id,
});
const requestInfo: HttpRequest = {
method: 'POST',
url,
body: messageBody,
};
const requestOptions = { headers: 'json' };
return this.request(requestInfo, requestOptions);
} else {
// the _invoke is private in jsforce, we can call the SOAP deployRecentValidation like this
// @ts-ignore
return this.metadata['_invoke']('deployRecentValidation', {
validationId: options.id,
}) as JsonCollection;
}
// REST returns an object with an id property, SOAP returns the id as a string directly. That is now handled
// in jsforce, so we have to cast a string as unkown as JsonCollection to support backwards compatibility.
return (await this.metadata.deployRecentValidation(options)) as unknown as JsonCollection;
}
/**
* Retrieves the highest api version that is supported by the target server instance.
Expand Down
5 changes: 2 additions & 3 deletions src/org/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@
import { EOL } from 'os';
import { AsyncCreatable, lowerFirst, mapKeys, omit, parseJsonMap, upperFirst } from '@salesforce/kit';
import { asJsonArray, asNumber, ensureJsonMap, ensureString, isJsonMap, Many } from '@salesforce/ts-types';
import type { QueryResult } from 'jsforce';
import type { HttpRequest, HttpResponse, QueryResult, Schema, SObjectUpdateRecord } from 'jsforce';
import { HttpApi } from 'jsforce/lib/http-api';
import type { HttpRequest, HttpResponse, Schema, SObjectUpdateRecord } from 'jsforce/lib/types';
import { Logger } from '../logger';
import { Messages } from '../messages';
import { SecureBuffer } from '../crypto/secureBuffer';
Expand Down Expand Up @@ -300,10 +299,10 @@ export class User extends AsyncCreatable<User.Options> {
const userConnection = await Connection.create({ authInfo: info });

return new Promise((resolve, reject) => {
// no promises in async method
// eslint-disable-next-line @typescript-eslint/no-misused-promises
password.value(async (buffer: Buffer) => {
try {
// @ts-ignore TODO: expose `soap` on Connection however appropriate
const soap = userConnection.soap;
await soap.setPassword(ensureString(info.getFields().userId), buffer.toString('utf8'));
this.logger.debug(`Set password for userId: ${info.getFields().userId}`);
Expand Down
13 changes: 6 additions & 7 deletions src/webOAuthServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ import * as http from 'http';
import { parse as parseQueryString } from 'querystring';
import { parse as parseUrl } from 'url';
import { Socket } from 'net';

import { AsyncCreatable, set, toNumber, Env } from '@salesforce/kit';
import { Nullable, get, asString } from '@salesforce/ts-types';
import { OAuth2 } from 'jsforce';
import { JwtOAuth2Config, OAuth2 } from 'jsforce';
import { AsyncCreatable, Env, set, toNumber } from '@salesforce/kit';
import { asString, get, Nullable } from '@salesforce/ts-types';
import { Logger } from './logger';
import { AuthInfo, DEFAULT_CONNECTED_APP_INFO, OAuth2Config } from './org/authInfo';
import { AuthInfo, DEFAULT_CONNECTED_APP_INFO } from './org';
import { SfError } from './sfError';
import { Messages } from './messages';
import { SfProjectJson } from './sfProject';
Expand Down Expand Up @@ -51,7 +50,7 @@ export class WebOAuthServer extends AsyncCreatable<WebOAuthServer.Options> {
private logger!: Logger;
private webServer!: WebServer;
private oauth2!: OAuth2;
private oauthConfig: OAuth2Config;
private oauthConfig: JwtOAuth2Config;

public constructor(options: WebOAuthServer.Options) {
super(options);
Expand Down Expand Up @@ -248,7 +247,7 @@ export class WebOAuthServer extends AsyncCreatable<WebOAuthServer.Options> {

export namespace WebOAuthServer {
export interface Options {
oauthConfig: OAuth2Config;
oauthConfig: JwtOAuth2Config;
}

export type Request = http.IncomingMessage & { query: { code: string; state: string } };
Expand Down
Loading

0 comments on commit be22660

Please sign in to comment.