Skip to content

Commit 3b3d4af

Browse files
authored
Merge pull request #71 from salesforcecli/changes-for-jwt-validation-and-sfap-url
Changes for jwt validation and sfap url
2 parents 96af6e7 + 841c062 commit 3b3d4af

File tree

8 files changed

+146
-60
lines changed

8 files changed

+146
-60
lines changed

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@
1111
"@salesforce/kit": "^3.2.2",
1212
"@salesforce/sf-plugins-core": "^11.3.12",
1313
"form-data": "^4.0.1",
14-
"got": "^14.4.4",
15-
"tough-cookie": "^4.1.4"
14+
"got": "^14.4.4"
1615
},
1716
"devDependencies": {
1817
"@oclif/plugin-command-snapshot": "^5.2.22",

src/commands/data-seeding/generate/index.ts

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
99
import { Messages, PollingClient, SfError, StatusResult } from '@salesforce/core';
1010
import { Duration } from '@salesforce/kit';
11-
import { initiateDataSeed, pollSeedStatus, PollSeedResponse } from '../../../utils/api.js';
11+
import { initiateDataSeed, pollSeedStatus, PollSeedResponse, initiateJWTMint } from '../../../utils/api.js';
1212
import { getSeedGenerateMso, getSeedGenerateStage as getStage } from '../../../utils/mso.js';
1313
import { DataSeedingGenerateResult } from '../../../utils/types.js';
1414
import { GenerateRequestCache } from '../../../utils/cache.js';
@@ -23,12 +23,12 @@ export default class DataSeedingGenerate extends SfCommand<DataSeedingGenerateRe
2323

2424
public static readonly flags = {
2525
// TODO: The org flags will need to use Flags.requiredOrg() once auth is finalized
26-
'target-org': Flags.string({
26+
'target-org': Flags.requiredOrg({
2727
summary: messages.getMessage('flags.target-org.summary'),
2828
char: 'o',
2929
required: true,
3030
}),
31-
'source-org': Flags.string({
31+
'source-org': Flags.requiredOrg({
3232
summary: messages.getMessage('flags.source-org.summary'),
3333
char: 's',
3434
required: true,
@@ -55,10 +55,28 @@ export default class DataSeedingGenerate extends SfCommand<DataSeedingGenerateRe
5555

5656
public async run(): Promise<DataSeedingGenerateResult> {
5757
const { flags } = await this.parse(DataSeedingGenerate);
58-
const { async, 'config-file': configFile, 'source-org': sourceOrg, 'target-org': targetOrg, wait } = flags;
59-
60-
const { request_id: jobId } = await initiateDataSeed(configFile, 'data-generation');
61-
58+
const { async, 'config-file': configFile, 'source-org': srcOrgObj, 'target-org': tgtOrgObj, wait } = flags;
59+
60+
const sourceOrg = srcOrgObj.getOrgId();
61+
const srcAccessToken = srcOrgObj.getConnection().accessToken as string;
62+
const srcOrgInstUrl = srcOrgObj.getConnection().instanceUrl;
63+
64+
const targetOrg = tgtOrgObj.getOrgId();
65+
const tgtAccessToken = tgtOrgObj.getConnection().accessToken as string;
66+
const tgtOrgInstUrl = tgtOrgObj.getConnection().instanceUrl;
67+
68+
// Fetch Valid JWT with Data Seed Org Perm
69+
const { jwt: jwtValue } = await initiateJWTMint(srcOrgInstUrl, srcAccessToken, tgtOrgInstUrl, tgtAccessToken);
70+
const { request_id: jobId } = await initiateDataSeed(
71+
configFile,
72+
'data-generation',
73+
jwtValue,
74+
srcOrgInstUrl,
75+
srcAccessToken,
76+
tgtOrgInstUrl,
77+
tgtAccessToken,
78+
sourceOrg
79+
);
6280
const reportMessage = messages.getMessage('report.suggestion', [jobId]);
6381

6482
if (!jobId) throw new Error('Failed to receive job id');
@@ -83,7 +101,13 @@ export default class DataSeedingGenerate extends SfCommand<DataSeedingGenerateRe
83101

84102
const options: PollingClient.Options = {
85103
poll: async (): Promise<StatusResult> => {
86-
const response = await pollSeedStatus(jobId);
104+
const { jwt: jwtValueNew } = await initiateJWTMint(
105+
srcOrgInstUrl,
106+
srcAccessToken,
107+
tgtOrgInstUrl,
108+
tgtAccessToken
109+
);
110+
const response = await pollSeedStatus(jobId, jwtValueNew);
87111

88112
mso.goto(getStage(response.step), {
89113
startTime: response.execution_start_time,
@@ -134,7 +158,8 @@ export default class DataSeedingGenerate extends SfCommand<DataSeedingGenerateRe
134158
throw err;
135159
}
136160
} else {
137-
const response = await pollSeedStatus(jobId);
161+
const { jwt: jwtValueNew } = await initiateJWTMint(srcOrgInstUrl, srcAccessToken, tgtOrgInstUrl, tgtAccessToken);
162+
const response = await pollSeedStatus(jobId, jwtValueNew);
138163

139164
const mso = getSeedGenerateMso({
140165
jsonEnabled: this.jsonEnabled(),

src/commands/data-seeding/generate/report.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export default class DataSeedingGenerateReport extends SfCommand<DataSeedingRepo
4343

4444
if (!jobId) throw new SfError('No job ID provided or found in cache');
4545

46-
const response = await pollSeedStatus(jobId);
46+
const response = await pollSeedStatus(jobId, '');
4747

4848
const data = {
4949
jobId,

src/commands/data-seeding/migrate/index.ts

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
99
import { Duration } from '@salesforce/kit';
1010
import { Messages, PollingClient, StatusResult, SfError } from '@salesforce/core';
11-
import { initiateDataSeed, PollSeedResponse, pollSeedStatus } from '../../../utils/api.js';
11+
import { initiateDataSeed, PollSeedResponse, pollSeedStatus, initiateJWTMint } from '../../../utils/api.js';
1212
import { DataSeedingMigrateResult } from '../../../utils/types.js';
1313
import { getSeedMigrateMso, getSeedMigrateStage as getStage } from '../../../utils/mso.js';
1414
import { MigrateRequestCache } from '../../../utils/cache.js';
@@ -23,12 +23,12 @@ export default class DataSeedingMigrate extends SfCommand<DataSeedingMigrateResu
2323

2424
public static readonly flags = {
2525
// TODO: The org flags will need to use Flags.requiredOrg() once auth is finalized
26-
'target-org': Flags.string({
26+
'target-org': Flags.requiredOrg({
2727
summary: messages.getMessage('flags.target-org.summary'),
2828
char: 'o',
2929
required: true,
3030
}),
31-
'source-org': Flags.string({
31+
'source-org': Flags.requiredOrg({
3232
summary: messages.getMessage('flags.source-org.summary'),
3333
char: 's',
3434
required: true,
@@ -55,9 +55,28 @@ export default class DataSeedingMigrate extends SfCommand<DataSeedingMigrateResu
5555

5656
public async run(): Promise<DataSeedingMigrateResult> {
5757
const { flags } = await this.parse(DataSeedingMigrate);
58-
const { async, 'config-file': configFile, 'source-org': sourceOrg, 'target-org': targetOrg, wait } = flags;
59-
60-
const { request_id: jobId } = await initiateDataSeed(configFile, 'data-copy');
58+
const { async, 'config-file': configFile, 'source-org': sourceOrgObj, 'target-org': targetOrgObj, wait } = flags;
59+
60+
const sourceOrg = sourceOrgObj.getOrgId();
61+
const srcAccessToken = sourceOrgObj.getConnection().accessToken as string;
62+
const srcOrgInstUrl = sourceOrgObj.getConnection().instanceUrl;
63+
64+
const targetOrg = targetOrgObj.getOrgId();
65+
const tgtAccessToken = targetOrgObj.getConnection().accessToken as string;
66+
const tgtOrgInstUrl = targetOrgObj.getConnection().instanceUrl;
67+
68+
// Fetch Valid JWT with Data Seed Org Perm
69+
const { jwt: jwtValue } = await initiateJWTMint(srcOrgInstUrl, srcAccessToken, tgtOrgInstUrl, tgtAccessToken);
70+
const { request_id: jobId } = await initiateDataSeed(
71+
configFile,
72+
'data-copy',
73+
jwtValue,
74+
srcOrgInstUrl,
75+
srcAccessToken,
76+
tgtOrgInstUrl,
77+
tgtAccessToken,
78+
sourceOrg
79+
);
6180

6281
if (!jobId) throw new Error('Failed to receive job id');
6382

@@ -83,7 +102,13 @@ export default class DataSeedingMigrate extends SfCommand<DataSeedingMigrateResu
83102

84103
const options: PollingClient.Options = {
85104
poll: async (): Promise<StatusResult> => {
86-
const response = await pollSeedStatus(jobId);
105+
const { jwt: jwtValueNew } = await initiateJWTMint(
106+
srcOrgInstUrl,
107+
srcAccessToken,
108+
tgtOrgInstUrl,
109+
tgtAccessToken
110+
);
111+
const response = await pollSeedStatus(jobId, jwtValueNew);
87112

88113
mso.goto(getStage(response.step), {
89114
startTime: response.execution_start_time,
@@ -136,7 +161,8 @@ export default class DataSeedingMigrate extends SfCommand<DataSeedingMigrateResu
136161
throw err;
137162
}
138163
} else {
139-
const response = await pollSeedStatus(jobId);
164+
const { jwt: jwtValueNew } = await initiateJWTMint(srcOrgInstUrl, srcAccessToken, tgtOrgInstUrl, tgtAccessToken);
165+
const response = await pollSeedStatus(jobId, jwtValueNew);
140166

141167
const mso = getSeedMigrateMso({
142168
jsonEnabled: this.jsonEnabled(),

src/commands/data-seeding/migrate/report.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { getSeedMigrateMso, getSeedMigrateStage as getStage } from '../../../uti
1212
import { DataSeedingReportResult } from '../../../utils/types.js';
1313
import { MigrateRequestCache } from '../../../utils/cache.js';
1414

15-
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url)
15+
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
1616
const messages = Messages.loadMessages('@salesforce/plugin-data-seeding', 'data-seeding.migrate.report');
1717

1818
export default class DataSeedingMigrateReport extends SfCommand<DataSeedingReportResult> {
@@ -40,7 +40,7 @@ export default class DataSeedingMigrateReport extends SfCommand<DataSeedingRepor
4040

4141
if (!jobId) throw new SfError('No job ID provided or found in cache');
4242

43-
const response = await pollSeedStatus(jobId);
43+
const response = await pollSeedStatus(jobId, '');
4444

4545
const data = {
4646
jobId,
@@ -77,4 +77,4 @@ export default class DataSeedingMigrateReport extends SfCommand<DataSeedingRepor
7777
...data,
7878
};
7979
}
80-
}
80+
}

src/utils/api.ts

Lines changed: 72 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,19 @@
77

88
import fs from 'node:fs';
99
import got from 'got';
10-
import { CookieJar } from 'tough-cookie';
1110
import FormData from 'form-data';
1211
import { SfError, Logger } from '@salesforce/core';
1312

1413
export type SeedResponse = {
1514
request_id: string;
1615
};
16+
export type ServletResponse = {
17+
jwt: string;
18+
};
19+
export type AuthServletResponse = {
20+
statusCode: string;
21+
body: string;
22+
};
1723

1824
export type PollSeedResponse = {
1925
execution_end_time: string;
@@ -26,41 +32,36 @@ export type PollSeedResponse = {
2632

2733
export type DataSeedingOperation = 'data-generation' | 'data-copy';
2834

29-
const baseUrl = process.env.SF_DATA_SEEDING_URL ?? 'https://data-seed-scratchpad5.sfdc-3vx9f4.svc.sfdcfc.net';
30-
const csrfUrl = `${baseUrl}/get-csrf-token`;
35+
const baseUrl = 'https://api.salesforce.com/platform/data-seed/v1';
3136
const seedUrl = `${baseUrl}/data-seed`;
3237
const pollUrl = `${baseUrl}/status`;
33-
34-
export const getCookieJar = async (): Promise<CookieJar> => {
35-
const cookieJar = new CookieJar();
36-
await got(csrfUrl, { cookieJar });
37-
return cookieJar;
38-
};
39-
40-
export const getCsrfToken = (cookieJar: CookieJar): string => {
41-
const csrfToken = cookieJar.getCookiesSync(csrfUrl).find((cookie) => cookie.key === 'csrf_token')?.value;
42-
if (!csrfToken) throw new SfError('Failed to obtain CSRF token');
43-
44-
return csrfToken;
45-
};
46-
47-
export const initiateDataSeed = async (config: string, operation: DataSeedingOperation): Promise<SeedResponse> => {
48-
const cookieJar = await getCookieJar();
49-
const csrf = getCsrfToken(cookieJar);
50-
38+
const sfRegion = 'us-east-1'
39+
export const initiateDataSeed = async (
40+
config: string,
41+
operation: DataSeedingOperation,
42+
jwt: string,
43+
srcOrgUrl: string,
44+
srcAccessToken: string,
45+
tgtOrgUrl: string,
46+
tgtAccessToken: string,
47+
srcOrgId: string
48+
): Promise<SeedResponse> => {
5149
const form = new FormData();
5250
form.append('config_file', fs.createReadStream(config));
53-
form.append('credentials_file', fs.createReadStream('ignore/credentials.txt'));
5451
form.append('operation', operation);
55-
52+
form.append('source_access_token', srcAccessToken);
53+
form.append('source_instance_url', srcOrgUrl);
54+
form.append('target_access_token', tgtAccessToken);
55+
form.append('target_instance_url', tgtOrgUrl);
56+
form.append('source_org_id',srcOrgId);
5657
// TODO: Update to use .json() instead of JSON.parse once the Error response is changed to be JSON
5758
// Update the return type as well
5859
const response = await got.post(seedUrl, {
5960
throwHttpErrors: false,
60-
cookieJar,
6161
headers: {
6262
...form.getHeaders(),
63-
'X-CSRFToken': csrf,
63+
Authorization: `Bearer ${jwt}`,
64+
'x-salesforce-region':sfRegion,
6465
},
6566
body: form,
6667
});
@@ -72,12 +73,56 @@ export const initiateDataSeed = async (config: string, operation: DataSeedingOpe
7273
return JSON.parse(response.body) as SeedResponse;
7374
};
7475

75-
export const pollSeedStatus = async (jobId: string): Promise<PollSeedResponse> => {
76+
export const initiateJWTMint = async (
77+
srcOrgUrl: string,
78+
srcAccessToken: string,
79+
tgtOrgUrl: string,
80+
tgtAccessToken: string
81+
): Promise<ServletResponse> => {
82+
const srcServletUrl = `${srcOrgUrl}/dataseed/auth`;
83+
const tgtServletUrl = `${tgtOrgUrl}/dataseed/auth`;
84+
85+
const [responseSrc, responseTgt] = await Promise.all([
86+
callAuthServlet(srcServletUrl, srcAccessToken),
87+
callAuthServlet(tgtServletUrl, tgtAccessToken),
88+
]);
89+
90+
if (responseSrc.statusCode === '200') {
91+
return JSON.parse(responseSrc.body) as ServletResponse;
92+
}
93+
94+
if (responseTgt.statusCode === '200') {
95+
return JSON.parse(responseTgt.body) as ServletResponse;
96+
}
97+
98+
throw new SfError(
99+
`Org permission for data seed not found in either the source or target org.\nSource Response: Error Code : ${responseSrc.statusCode} - ${responseSrc.body}. \nTarget Response: Error Code : ${responseTgt.statusCode} - ${responseTgt.body}`
100+
);
101+
};
102+
103+
const callAuthServlet = async (url: string, accessToken: string): Promise<AuthServletResponse> => {
104+
const response = await got.post(url, {
105+
throwHttpErrors: false,
106+
headers: {
107+
Authorization: `Bearer ${accessToken}`,
108+
},
109+
});
110+
return {
111+
statusCode: response.statusCode.toString(), // Convert to string
112+
body: response.body,
113+
};
114+
};
115+
116+
export const pollSeedStatus = async (jobId: string, jwt: string): Promise<PollSeedResponse> => {
76117
const logger = await Logger.child('PollSeedStatus');
77118

78119
// TODO: Update to use .json() instead of JSON.parse once the Error response is changed to be JSON
79120
// Update the return type as well
80-
const response = await got.get(`${pollUrl}/${jobId}`, { throwHttpErrors: false });
121+
const headers = {
122+
Authorization: `Bearer ${jwt}`,
123+
'x-salesforce-region': sfRegion,
124+
};
125+
const response = await got.get(`${pollUrl}/${jobId}`, { throwHttpErrors: false, headers });
81126

82127
if (response.statusCode !== 200) {
83128
// TODO: Print error body once the Error response is changed to be JSON

src/utils/mso.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type MsoGet = string | undefined;
2828
// - They have been converted to lowercase for later comparison
2929
// The values in this Map are used as the stage names in mso
3030
const seedGenerateStagesMap = new Map<string, string>([
31+
['init','Initializing'],
3132
['querying source org', 'Querying Source Org'],
3233
['data generation', 'Data Generation'],
3334
['populating target org', 'Populating Target Org'],

yarn.lock

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7396,16 +7396,6 @@ tough-cookie@*:
73967396
universalify "^0.2.0"
73977397
url-parse "^1.5.3"
73987398

7399-
tough-cookie@^4.1.4:
7400-
version "4.1.4"
7401-
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.4.tgz#945f1461b45b5a8c76821c33ea49c3ac192c1b36"
7402-
integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==
7403-
dependencies:
7404-
psl "^1.1.33"
7405-
punycode "^2.1.1"
7406-
universalify "^0.2.0"
7407-
url-parse "^1.5.3"
7408-
74097399
tr46@~0.0.3:
74107400
version "0.0.3"
74117401
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"

0 commit comments

Comments
 (0)