Skip to content

Changes for jwt validation and sfap url #71

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Nov 18, 2024
19 changes: 14 additions & 5 deletions src/commands/data-seeding/generate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import { Messages, PollingClient, SfError, StatusResult } from '@salesforce/core';
import { Duration } from '@salesforce/kit';
import { initiateDataSeed, pollSeedStatus, PollSeedResponse } from '../../../utils/api.js';
import { initiateDataSeed, pollSeedStatus, PollSeedResponse, initiateJWTMint } from '../../../utils/api.js';
import { getSeedGenerateMso, getSeedGenerateStage as getStage } from '../../../utils/mso.js';
import { DataSeedingGenerateResult } from '../../../utils/types.js';
import { GenerateRequestCache } from '../../../utils/cache.js';
Expand All @@ -23,12 +23,12 @@ export default class DataSeedingGenerate extends SfCommand<DataSeedingGenerateRe

public static readonly flags = {
// TODO: The org flags will need to use Flags.requiredOrg() once auth is finalized
'target-org': Flags.string({
'target-org': Flags.requiredOrg({
summary: messages.getMessage('flags.target-org.summary'),
char: 'o',
required: true,
}),
'source-org': Flags.string({
'source-org': Flags.requiredOrg({
summary: messages.getMessage('flags.source-org.summary'),
char: 's',
required: true,
Expand All @@ -55,10 +55,19 @@ export default class DataSeedingGenerate extends SfCommand<DataSeedingGenerateRe

public async run(): Promise<DataSeedingGenerateResult> {
const { flags } = await this.parse(DataSeedingGenerate);
const { async, 'config-file': configFile, 'source-org': sourceOrg, 'target-org': targetOrg, wait } = flags;
const { async, 'config-file': configFile, 'source-org': srcOrgObj, 'target-org': tgtOrgObj, wait } = flags;

const { request_id: jobId } = await initiateDataSeed(configFile, 'data-generation');
const sourceOrg = srcOrgObj.getOrgId();
const srcAccessToken = srcOrgObj.getConnection().accessToken as string;
const srcOrgInstUrl = srcOrgObj.getConnection().instanceUrl;

const targetOrg = tgtOrgObj.getOrgId();
const tgtAccessToken = tgtOrgObj.getConnection().accessToken as string;
const tgtOrgInstUrl = tgtOrgObj.getConnection().instanceUrl;

// Fetch Valid JWT with Data Seed Org Perm
const { jwt: jwtValue } = await initiateJWTMint(srcOrgInstUrl, srcAccessToken, tgtOrgInstUrl, tgtAccessToken);
const { request_id: jobId } = await initiateDataSeed(configFile, 'data-generation', jwtValue);
const reportMessage = messages.getMessage('report.suggestion', [jobId]);

if (!jobId) throw new Error('Failed to receive job id');
Expand Down
21 changes: 16 additions & 5 deletions src/commands/data-seeding/migrate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import { Duration } from '@salesforce/kit';
import { Messages, PollingClient, StatusResult, SfError } from '@salesforce/core';
import { initiateDataSeed, PollSeedResponse, pollSeedStatus } from '../../../utils/api.js';
import { initiateDataSeed, PollSeedResponse, pollSeedStatus, initiateJWTMint } from '../../../utils/api.js';
import { DataSeedingMigrateResult } from '../../../utils/types.js';
import { getSeedMigrateMso, getSeedMigrateStage as getStage } from '../../../utils/mso.js';
import { MigrateRequestCache } from '../../../utils/cache.js';
Expand All @@ -23,12 +23,12 @@ export default class DataSeedingMigrate extends SfCommand<DataSeedingMigrateResu

public static readonly flags = {
// TODO: The org flags will need to use Flags.requiredOrg() once auth is finalized
'target-org': Flags.string({
'target-org': Flags.requiredOrg({
summary: messages.getMessage('flags.target-org.summary'),
char: 'o',
required: true,
}),
'source-org': Flags.string({
'source-org': Flags.requiredOrg({
summary: messages.getMessage('flags.source-org.summary'),
char: 's',
required: true,
Expand All @@ -55,9 +55,20 @@ export default class DataSeedingMigrate extends SfCommand<DataSeedingMigrateResu

public async run(): Promise<DataSeedingMigrateResult> {
const { flags } = await this.parse(DataSeedingMigrate);
const { async, 'config-file': configFile, 'source-org': sourceOrg, 'target-org': targetOrg, wait } = flags;
const { async, 'config-file': configFile, 'source-org': sourceOrgObj, 'target-org': targetOrgObj, wait } = flags;

const { request_id: jobId } = await initiateDataSeed(configFile, 'data-copy');
const sourceOrg = sourceOrgObj.getOrgId();
const srcAccessToken = sourceOrgObj.getConnection().accessToken as string;
const srcOrgInstUrl = sourceOrgObj.getConnection().instanceUrl;

const targetOrg = targetOrgObj.getOrgId();
const tgtAccessToken = targetOrgObj.getConnection().accessToken as string;
const tgtOrgInstUrl = targetOrgObj.getConnection().instanceUrl;

// Fetch Valid JWT with Data Seed Org Perm
const { jwt: jwtValue } = await initiateJWTMint(srcOrgInstUrl, srcAccessToken, tgtOrgInstUrl, tgtAccessToken);

const { request_id: jobId } = await initiateDataSeed(configFile, 'data-copy', jwtValue);

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

Expand Down
57 changes: 49 additions & 8 deletions src/utils/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import { SfError, Logger } from '@salesforce/core';
export type SeedResponse = {
request_id: string;
};
export type ServletResponse = {
jwt: string;
};

export type PollSeedResponse = {
execution_end_time: string;
Expand All @@ -26,7 +29,8 @@ export type PollSeedResponse = {

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

const baseUrl = process.env.SF_DATA_SEEDING_URL ?? 'https://data-seed-scratchpad5.sfdc-3vx9f4.svc.sfdcfc.net';
// TODO Change to SFAP Endpoint
const baseUrl = process.env.SF_DATA_SEEDING_URL ?? 'https://data-seed-gid.sfdc-yfeipo.svc.sfdcfc.net';
const csrfUrl = `${baseUrl}/get-csrf-token`;
const seedUrl = `${baseUrl}/data-seed`;
const pollUrl = `${baseUrl}/status`;
Expand All @@ -44,23 +48,27 @@ export const getCsrfToken = (cookieJar: CookieJar): string => {
return csrfToken;
};

export const initiateDataSeed = async (config: string, operation: DataSeedingOperation): Promise<SeedResponse> => {
const cookieJar = await getCookieJar();
const csrf = getCsrfToken(cookieJar);

export const initiateDataSeed = async (
config: string,
operation: DataSeedingOperation,
jwt: string
): Promise<SeedResponse> => {
// const cookieJar = await getCookieJar();
// const csrf = getCsrfToken(cookieJar);
const form = new FormData();
form.append('config_file', fs.createReadStream(config));
// TODO : Remove credential file once SFAP is active and dataseed endpoint accepts orgurl and token
form.append('credentials_file', fs.createReadStream('ignore/credentials.txt'));
form.append('operation', operation);

// TODO: Update to use .json() instead of JSON.parse once the Error response is changed to be JSON
// Update the return type as well
const response = await got.post(seedUrl, {
throwHttpErrors: false,
cookieJar,
// cookieJar,
headers: {
...form.getHeaders(),
'X-CSRFToken': csrf,
// 'X-CSRFToken': csrf,
Authorization: 'Bearer ' + jwt,
},
body: form,
});
Expand All @@ -72,6 +80,39 @@ export const initiateDataSeed = async (config: string, operation: DataSeedingOpe
return JSON.parse(response.body) as SeedResponse;
};

export const initiateJWTMint = async (
srcOrgUrl: string,
srcAccessToken: string,
tgtOrgUrl: string,
tgtAccessToken: string
): Promise<ServletResponse> => {
const srcServletUrl = srcOrgUrl + '/dataseed/auth';
const responseSrc = await got.post(srcServletUrl, {
throwHttpErrors: false,
headers: {
Authorization: 'Bearer ' + srcAccessToken,
},
});

if (responseSrc.statusCode !== 200) {
const tgtServletUrl = tgtOrgUrl + '/dataseed/auth';
const responseTgt = await got.post(tgtServletUrl, {
throwHttpErrors: false,
headers: {
Authorization: 'Bearer ' + tgtAccessToken,
},
});
if (responseTgt.statusCode !== 200) {
throw new SfError(
`Org permission for data seed not found in source & target org.\nSource Response: Error Code : ${responseSrc.statusCode} - ${responseSrc.body}. \nTarget Response: Error Code : ${responseTgt.statusCode} - ${responseTgt.body}`
);
}
return JSON.parse(responseTgt.body) as ServletResponse;
}

return JSON.parse(responseSrc.body) as ServletResponse;
};

export const pollSeedStatus = async (jobId: string): Promise<PollSeedResponse> => {
const logger = await Logger.child('PollSeedStatus');

Expand Down
Loading