Skip to content

Commit

Permalink
fix: tolerate broken auth files (with warning) during readAll (#869)
Browse files Browse the repository at this point in the history
* fix: tolerate broken auth files (with warning) during readAll

* fix: bump jsforce

* fix: throw good error for bad json config files

---------

Co-authored-by: Cristian Dominguez <[email protected]>
  • Loading branch information
mshanemc and cristiand391 authored Jun 26, 2023
1 parent fbe375b commit 4cea657
Show file tree
Hide file tree
Showing 10 changed files with 51 additions and 25 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"faye": "^1.4.0",
"form-data": "^4.0.0",
"js2xmlparser": "^4.0.1",
"jsforce": "^2.0.0-beta.24",
"jsforce": "^2.0.0-beta.25",
"jsonwebtoken": "9.0.0",
"jszip": "3.10.1",
"proper-lockfile": "^4.1.2",
Expand Down Expand Up @@ -171,4 +171,4 @@
]
}
}
}
}
10 changes: 6 additions & 4 deletions src/config/configFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,22 +161,24 @@ export class ConfigFile<
// internally and updated persistently via write().
if (!this.hasRead || force) {
this.logger.info(`Reading config file: ${this.getPath()}`);
const obj = parseJsonMap(await fs.promises.readFile(this.getPath(), 'utf8'));
const obj = parseJsonMap(await fs.promises.readFile(this.getPath(), 'utf8'), this.getPath());
this.setContentsFromObject(obj);
}
// Necessarily set this even when an error happens to avoid infinite re-reading.
// To attempt another read, pass `force=true`.
this.hasRead = true;
return this.getContents();
} catch (err) {
this.hasRead = true;
if ((err as SfError).code === 'ENOENT') {
if (!throwOnNotFound) {
this.setContents();
return this.getContents();
}
}
throw err;
} finally {
// Necessarily set this even when an error happens to avoid infinite re-reading.
// To attempt another read, pass `force=true`.
this.hasRead = true;
throw err;
}
}

Expand Down
22 changes: 17 additions & 5 deletions src/org/authInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
Nullable,
Optional,
} from '@salesforce/ts-types';
import { JwtOAuth2, JwtOAuth2Config, OAuth2, TokenResponse } from 'jsforce';
import { OAuth2Config, OAuth2, TokenResponse } from 'jsforce';
import Transport from 'jsforce/lib/transport';
import * as jwt from 'jsonwebtoken';
import { Config } from '../config/config';
Expand Down Expand Up @@ -111,6 +111,14 @@ export type AuthSideEffects = {
setTracksSource?: boolean;
};

export type JwtOAuth2Config = OAuth2Config & {
privateKey?: string;
privateKeyFile?: string;
authCode?: string;
refreshToken?: string;
username?: string;
};

type UserInfo = AnyJson & {
username: string;
organizationId: string;
Expand Down Expand Up @@ -938,10 +946,14 @@ export class AuthInfo extends AsyncOptionalCreatable<AuthInfo.Options> {
}
);

const oauth2 = new JwtOAuth2({ loginUrl });
// jsforce has it types as any
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
return ensureJsonMap(await oauth2.jwtAuthorize(jwtToken));
const oauth2 = new OAuth2({ loginUrl });
return ensureJsonMap(
await oauth2.requestToken({
// eslint-disable-next-line camelcase
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: jwtToken,
})
);
}

// Build OAuth config for a refresh token auth flow
Expand Down
3 changes: 2 additions & 1 deletion src/org/scratchOrgInfoApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@

import { env, Duration, upperFirst } from '@salesforce/kit';
import { AnyJson } from '@salesforce/ts-types';
import { OAuth2Config, JwtOAuth2Config, SaveResult } from 'jsforce';
import { OAuth2Config, SaveResult } from 'jsforce';
import { retryDecorator, RetryError } from 'ts-retry-promise';
import { JwtOAuth2Config } from '../org/authInfo';
import { Logger } from '../logger';
import { Messages } from '../messages';
import { SfError } from '../sfError';
Expand Down
8 changes: 4 additions & 4 deletions src/org/scratchOrgLifecycleEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const scratchOrgLifecycleStages = [
'done',
] as const;
export interface ScratchOrgLifecycleEvent {
stage: typeof scratchOrgLifecycleStages[number];
stage: (typeof scratchOrgLifecycleStages)[number];
scratchOrgInfo?: ScratchOrgInfo;
}

Expand All @@ -41,10 +41,10 @@ const postOrgCreateHookFields = [
'username',
] as const;

type PostOrgCreateHook = Pick<AuthFields, typeof postOrgCreateHookFields[number]>;
type PostOrgCreateHook = Pick<AuthFields, (typeof postOrgCreateHookFields)[number]>;

const isHookField = (key: string): key is typeof postOrgCreateHookFields[number] =>
postOrgCreateHookFields.includes(key as typeof postOrgCreateHookFields[number]);
const isHookField = (key: string): key is (typeof postOrgCreateHookFields)[number] =>
postOrgCreateHookFields.includes(key as (typeof postOrgCreateHookFields)[number]);

export const emitPostOrgCreate = async (authFields: AuthFields): Promise<void> => {
await emitter.emit(
Expand Down
12 changes: 10 additions & 2 deletions src/stateAggregator/accessors/orgAccessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { ConfigFile } from '../../config/configFile';
import { ConfigContents } from '../../config/configStore';
import { Logger } from '../../logger';
import { Messages } from '../../messages';
import { Lifecycle } from '../../lifecycleEvents';

function chunk<T>(array: T[], chunkSize: number): T[][] {
const final = [];
Expand All @@ -41,6 +42,9 @@ export abstract class BaseOrgAccessor<T extends ConfigFile, P extends ConfigCont
this.configs.set(username, config);
return this.get(username, decrypt);
} catch (err) {
if (err instanceof Error && err.name === 'JsonParseError') {
throw err;
}
return null;
}
}
Expand All @@ -55,8 +59,12 @@ export abstract class BaseOrgAccessor<T extends ConfigFile, P extends ConfigCont
for (const fileChunk of fileChunks) {
const promises = fileChunk.map(async (f) => {
const username = this.parseUsername(f);
const config = await this.initAuthFile(username);
this.configs.set(username, config);
try {
const config = await this.initAuthFile(username);
this.configs.set(username, config);
} catch (e) {
await Lifecycle.getInstance().emitWarning(`The auth file for ${username} is invalid.`);
}
});
// eslint-disable-next-line no-await-in-loop
await Promise.all(promises);
Expand Down
3 changes: 2 additions & 1 deletion src/util/jsonXmlTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ export const writeJSONasXML = async ({
return fs.writeFile(path, xml);
};

export const JsonAsXml = ({ json, type, options = standardOptions }: JSONasXML): string => jsToXml.parse(type, fixExistingDollarSign(json), options);
export const JsonAsXml = ({ json, type, options = standardOptions }: JSONasXML): string =>
jsToXml.parse(type, fixExistingDollarSign(json), options);

export const fixExistingDollarSign = (existing: WriteJSONasXMLInputs['json']): Record<string, unknown> => {
const existingCopy = { ...existing } as Record<string, unknown>;
Expand Down
3 changes: 2 additions & 1 deletion src/webOAuthServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ import { parse as parseQueryString } from 'querystring';
import { parse as parseUrl } from 'url';
import { Socket } from 'net';
import { EventEmitter } from 'events';
import { JwtOAuth2Config, OAuth2 } from 'jsforce';
import { 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 } from './org';
import { SfError } from './sfError';
import { Messages } from './messages';
import { SfProjectJson } from './sfProject';
import { JwtOAuth2Config } from './org/authInfo';

Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@salesforce/core', 'auth');
Expand Down
3 changes: 2 additions & 1 deletion test/unit/org/authInfoTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ import { AnyJson, getJsonMap, JsonMap, toJsonMap } from '@salesforce/ts-types';
import { expect } from 'chai';
import { Transport } from 'jsforce/lib/transport';

import { JwtOAuth2Config, OAuth2 } from 'jsforce';
import { OAuth2 } from 'jsforce';
import { SinonSpy, SinonStub } from 'sinon';
import { JwtOAuth2Config } from '../../../src/org/authInfo';
import { AuthFields, AuthInfo } from '../../../src/org';
import { MockTestOrgData, shouldThrow, shouldThrowSync, TestContext } from '../../../src/testSetup';
import { OrgConfigProperties } from '../../../src/org/orgConfigProperties';
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2976,10 +2976,10 @@ jsesc@^2.5.1:
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==

jsforce@^2.0.0-beta.24:
version "2.0.0-beta.24"
resolved "https://registry.yarnpkg.com/jsforce/-/jsforce-2.0.0-beta.24.tgz#fe054eb0f6f668eff10566e086892dd2387364f7"
integrity sha512-rbDC9Y054Ele3qlDyFZxFY6RRyqpH7DKPYhAwBM2TIzqOl9OG35EB4lnJLaIuv/MZVA2mvTIV/TwxVv8PiB1EA==
jsforce@^2.0.0-beta.25:
version "2.0.0-beta.25"
resolved "https://registry.yarnpkg.com/jsforce/-/jsforce-2.0.0-beta.25.tgz#120a3999babf96ae18ab8f1232003d56588a9ca5"
integrity sha512-ZtzwJErI4SSJYWrGAw0mHEHPZRB4Idz0RiXHakCtEgEjEWt6JIDR4sNbWRHUzWHdEO4O61z2YSBvdOuag1hkWg==
dependencies:
"@babel/runtime" "^7.12.5"
"@babel/runtime-corejs3" "^7.12.5"
Expand Down

0 comments on commit 4cea657

Please sign in to comment.