feat: added masking of secrets in cli in url
This commit is contained in:
@@ -14,9 +14,10 @@ import { isHoppCLIError } from "../utils/checks";
|
|||||||
export const test = (path: string, options: TestCmdOptions) => async () => {
|
export const test = (path: string, options: TestCmdOptions) => async () => {
|
||||||
try {
|
try {
|
||||||
const delay = options.delay ? parseDelayOption(options.delay) : 0
|
const delay = options.delay ? parseDelayOption(options.delay) : 0
|
||||||
const envs = options.env ? await parseEnvsData(options.env) : <HoppEnvs>{ global: [], selected: [] }
|
const envName = options.envName
|
||||||
|
const envs = options.env ? await parseEnvsData(options.env, envName) : <HoppEnvs>{ global: [], selected: [] }
|
||||||
const collections = await parseCollectionData(path)
|
const collections = await parseCollectionData(path)
|
||||||
|
|
||||||
const report = await collectionsRunner({collections, envs, delay})
|
const report = await collectionsRunner({collections, envs, delay})
|
||||||
const hasSucceeded = collectionsRunnerResult(report)
|
const hasSucceeded = collectionsRunnerResult(report)
|
||||||
collectionsRunnerExit(hasSucceeded)
|
collectionsRunnerExit(hasSucceeded)
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ export const handleError = <T extends HoppErrorCode>(error: HoppError<T>) => {
|
|||||||
case "MALFORMED_COLLECTION":
|
case "MALFORMED_COLLECTION":
|
||||||
ERROR_MSG = `${error.path}\n${parseErrorData(error.data)}`;
|
ERROR_MSG = `${error.path}\n${parseErrorData(error.data)}`;
|
||||||
break;
|
break;
|
||||||
|
case "ENVIRONMENT_NAME_NOT_FOUND":
|
||||||
|
ERROR_MSG = `\n${parseErrorData(error.data)}`;
|
||||||
|
break;
|
||||||
case "NO_FILE_PATH":
|
case "NO_FILE_PATH":
|
||||||
ERROR_MSG = `Please provide a hoppscotch-collection file path.`;
|
ERROR_MSG = `Please provide a hoppscotch-collection file path.`;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ program
|
|||||||
"path to a hoppscotch collection.json file for CI testing"
|
"path to a hoppscotch collection.json file for CI testing"
|
||||||
)
|
)
|
||||||
.option("-e, --env <file_path>", "path to an environment variables json file")
|
.option("-e, --env <file_path>", "path to an environment variables json file")
|
||||||
|
.option("-eN, --envName <environment_name>","Specific Name of the environment")
|
||||||
.option(
|
.option(
|
||||||
"-d, --delay <delay_in_ms>",
|
"-d, --delay <delay_in_ms>",
|
||||||
"delay in milliseconds(ms) between consecutive requests within a collection"
|
"delay in milliseconds(ms) between consecutive requests within a collection"
|
||||||
|
|||||||
@@ -33,4 +33,5 @@ export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
|
|||||||
effectiveFinalHeaders: { key: string; value: string; active: boolean }[];
|
effectiveFinalHeaders: { key: string; value: string; active: boolean }[];
|
||||||
effectiveFinalParams: { key: string; value: string; active: boolean }[];
|
effectiveFinalParams: { key: string; value: string; active: boolean }[];
|
||||||
effectiveFinalBody: FormData | string | null;
|
effectiveFinalBody: FormData | string | null;
|
||||||
|
effectiveFinalMaskedURL: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,23 +5,46 @@ import { readJsonFile } from "../../utils/mutators";
|
|||||||
/**
|
/**
|
||||||
* Parses env json file for given path and validates the parsed env json object.
|
* Parses env json file for given path and validates the parsed env json object.
|
||||||
* @param path Path of env.json file to be parsed.
|
* @param path Path of env.json file to be parsed.
|
||||||
|
* @param envName Name of the environment that should be used. If undefined first environment is used.
|
||||||
* @returns For successful parsing we get HoppEnvs object.
|
* @returns For successful parsing we get HoppEnvs object.
|
||||||
*/
|
*/
|
||||||
export async function parseEnvsData(path: string) {
|
export async function parseEnvsData(path: string, envName: string | undefined) {
|
||||||
const contents = await readJsonFile(path)
|
const contents = await readJsonFile(path)
|
||||||
|
if(!(contents && typeof contents === "object" && Array.isArray(contents))) {
|
||||||
if(!(contents && typeof contents === "object" && !Array.isArray(contents))) {
|
|
||||||
throw error({ code: "MALFORMED_ENV_FILE", path, data: null })
|
throw error({ code: "MALFORMED_ENV_FILE", path, data: null })
|
||||||
}
|
}
|
||||||
|
|
||||||
const envPairs: Array<HoppEnvPair> = []
|
const envPairs: Array<HoppEnvPair> = []
|
||||||
|
|
||||||
for( const [key,value] of Object.entries(contents)) {
|
const contentEntries = Object.entries(contents)
|
||||||
if(typeof value !== "string") {
|
let environmentFound = false;
|
||||||
throw error({ code: "MALFORMED_ENV_FILE", path, data: {value: value} })
|
|
||||||
}
|
|
||||||
|
|
||||||
envPairs.push({key, value})
|
for(const [key, obj] of contentEntries) {
|
||||||
|
if(!(typeof obj === "object" && "name" in obj && "variables" in obj)) {
|
||||||
|
throw error({ code: "MALFORMED_ENV_FILE", path, data: { value: obj } })
|
||||||
|
}
|
||||||
|
if(envName && envName !== obj.name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
environmentFound = true;
|
||||||
|
for(const variable of obj.variables) {
|
||||||
|
if(
|
||||||
|
!(
|
||||||
|
typeof variable === "object" &&
|
||||||
|
"key" in variable &&
|
||||||
|
"value" in variable &&
|
||||||
|
"secret" in variable
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw error({ code: "MALFORMED_ENV_FILE", path, data: { value: variable } });
|
||||||
|
}
|
||||||
|
const { key, value, secret } = variable;
|
||||||
|
envPairs.push({ key: key, value: value, secret: secret });
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if(envName && !environmentFound) {
|
||||||
|
throw error({ code: "ENVIRONMENT_NAME_NOT_FOUND", data: envName });
|
||||||
}
|
}
|
||||||
return <HoppEnvs>{ global: [], selected: envPairs }
|
return <HoppEnvs>{ global: [], selected: envPairs }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export type TestCmdOptions = {
|
export type TestCmdOptions = {
|
||||||
env: string | undefined;
|
env: string | undefined;
|
||||||
delay: string | undefined;
|
delay: string | undefined;
|
||||||
|
envName: string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type HOPP_ENV_FILE_EXT = "json";
|
export type HOPP_ENV_FILE_EXT = "json";
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ type HoppErrors = {
|
|||||||
FILE_NOT_FOUND: HoppErrorPath;
|
FILE_NOT_FOUND: HoppErrorPath;
|
||||||
UNKNOWN_COMMAND: HoppErrorCmd;
|
UNKNOWN_COMMAND: HoppErrorCmd;
|
||||||
MALFORMED_COLLECTION: HoppErrorPath & HoppErrorData;
|
MALFORMED_COLLECTION: HoppErrorPath & HoppErrorData;
|
||||||
|
ENVIRONMENT_NAME_NOT_FOUND: HoppErrorData;
|
||||||
NO_FILE_PATH: {};
|
NO_FILE_PATH: {};
|
||||||
PRE_REQUEST_SCRIPT_ERROR: HoppErrorData;
|
PRE_REQUEST_SCRIPT_ERROR: HoppErrorData;
|
||||||
PARSING_ERROR: HoppErrorData;
|
PARSING_ERROR: HoppErrorData;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { bold } from "chalk";
|
import { bold } from "chalk";
|
||||||
import { groupEnd, group, log } from "console";
|
import { groupEnd, group, log } from "console";
|
||||||
import { handleError } from "../handlers/error";
|
import { handleError } from "../handlers/error";
|
||||||
import { RequestConfig } from "../interfaces/request";
|
import { Method } from "axios";
|
||||||
import { RequestRunnerResponse, TestReport } from "../interfaces/response";
|
import { RequestRunnerResponse, TestReport } from "../interfaces/response";
|
||||||
import { HoppCLIError } from "../types/errors";
|
import { HoppCLIError } from "../types/errors";
|
||||||
import {
|
import {
|
||||||
@@ -172,11 +172,12 @@ export const printFailedTestsReport = (
|
|||||||
export const printRequestRunner = {
|
export const printRequestRunner = {
|
||||||
/**
|
/**
|
||||||
* Request-runner starting message.
|
* Request-runner starting message.
|
||||||
* @param requestConfig Provides request's method and url.
|
* @param requestMethod Provides request's method
|
||||||
|
* @param maskedURL Provides the URL with secrets masked with asterisks
|
||||||
*/
|
*/
|
||||||
start: (requestConfig: RequestConfig) => {
|
start: (requestMethod: Method | undefined, maskedURL: string) => {
|
||||||
const METHOD = BG_INFO(` ${requestConfig.method} `);
|
const METHOD = BG_INFO(` ${requestMethod} `);
|
||||||
const ENDPOINT = requestConfig.url;
|
const ENDPOINT = maskedURL;
|
||||||
|
|
||||||
process.stdout.write(`${METHOD} ${ENDPOINT}`);
|
process.stdout.write(`${METHOD} ${ENDPOINT}`);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -50,9 +50,9 @@ export const preRequestScriptRunner = (
|
|||||||
isHoppCLIError(reason)
|
isHoppCLIError(reason)
|
||||||
? reason
|
? reason
|
||||||
: error({
|
: error({
|
||||||
code: "PRE_REQUEST_SCRIPT_ERROR",
|
code: "PRE_REQUEST_SCRIPT_ERROR",
|
||||||
data: reason,
|
data: reason,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -151,6 +151,12 @@ export function getEffectiveRESTRequest(
|
|||||||
request.endpoint,
|
request.endpoint,
|
||||||
envVariables
|
envVariables
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const maskedEnvVariables = setAllEnvironmentValuesToAsterisk(envVariables)
|
||||||
|
const _effectiveFinalMaskedURL = parseTemplateStringE(
|
||||||
|
request.endpoint,
|
||||||
|
maskedEnvVariables)
|
||||||
|
|
||||||
if (E.isLeft(_effectiveFinalURL)) {
|
if (E.isLeft(_effectiveFinalURL)) {
|
||||||
return E.left(
|
return E.left(
|
||||||
error({
|
error({
|
||||||
@@ -160,6 +166,7 @@ export function getEffectiveRESTRequest(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
const effectiveFinalURL = _effectiveFinalURL.right;
|
const effectiveFinalURL = _effectiveFinalURL.right;
|
||||||
|
const effectiveFinalMaskedURL = E.isLeft(_effectiveFinalMaskedURL) ? request.endpoint : _effectiveFinalMaskedURL.right;
|
||||||
|
|
||||||
return E.right({
|
return E.right({
|
||||||
...request,
|
...request,
|
||||||
@@ -167,6 +174,7 @@ export function getEffectiveRESTRequest(
|
|||||||
effectiveFinalHeaders,
|
effectiveFinalHeaders,
|
||||||
effectiveFinalParams,
|
effectiveFinalParams,
|
||||||
effectiveFinalBody,
|
effectiveFinalBody,
|
||||||
|
effectiveFinalMaskedURL,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,15 +250,15 @@ function getFinalBodyFromRequest(
|
|||||||
arrayFlatMap((x) =>
|
arrayFlatMap((x) =>
|
||||||
x.isFile
|
x.isFile
|
||||||
? x.value.map((v) => ({
|
? x.value.map((v) => ({
|
||||||
key: parseTemplateString(x.key, envVariables),
|
key: parseTemplateString(x.key, envVariables),
|
||||||
value: v as string | Blob,
|
value: v as string | Blob,
|
||||||
}))
|
}))
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
key: parseTemplateString(x.key, envVariables),
|
key: parseTemplateString(x.key, envVariables),
|
||||||
value: parseTemplateString(x.value, envVariables),
|
value: parseTemplateString(x.value, envVariables),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
toFormData,
|
toFormData,
|
||||||
E.right
|
E.right
|
||||||
@@ -287,3 +295,22 @@ export const getPreRequestMetrics = (
|
|||||||
hasPreReqErrors ? { failed: 1, passed: 0 } : { failed: 0, passed: 1 },
|
hasPreReqErrors ? { failed: 1, passed: 0 } : { failed: 0, passed: 1 },
|
||||||
(scripts) => <PreRequestMetrics>{ scripts, duration }
|
(scripts) => <PreRequestMetrics>{ scripts, duration }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mask all environment values with asterisks
|
||||||
|
* @param variables Environment variable array
|
||||||
|
* @returns Environment variable array with masked values
|
||||||
|
*/
|
||||||
|
const setAllEnvironmentValuesToAsterisk = (
|
||||||
|
variables: Environment["variables"]
|
||||||
|
): Environment["variables"] => {
|
||||||
|
const envVariables: Environment["variables"] = [];
|
||||||
|
for (const variable of variables) {
|
||||||
|
let value = variable.value
|
||||||
|
if (variable.secret) {
|
||||||
|
value = "******"
|
||||||
|
}
|
||||||
|
envVariables.push({ key: variable.key, secret: variable.secret, value: value })
|
||||||
|
}
|
||||||
|
return envVariables
|
||||||
|
}
|
||||||
|
|||||||
@@ -220,6 +220,7 @@ export const processRequest =
|
|||||||
effectiveFinalHeaders: [],
|
effectiveFinalHeaders: [],
|
||||||
effectiveFinalParams: [],
|
effectiveFinalParams: [],
|
||||||
effectiveFinalURL: "",
|
effectiveFinalURL: "",
|
||||||
|
effectiveFinalMaskedURL:"",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Executing pre-request-script
|
// Executing pre-request-script
|
||||||
@@ -237,8 +238,8 @@ export const processRequest =
|
|||||||
|
|
||||||
// Creating request-config for request-runner.
|
// Creating request-config for request-runner.
|
||||||
const requestConfig = createRequest(effectiveRequest);
|
const requestConfig = createRequest(effectiveRequest);
|
||||||
|
|
||||||
printRequestRunner.start(requestConfig);
|
printRequestRunner.start(requestConfig.method, effectiveRequest.effectiveFinalMaskedURL);
|
||||||
|
|
||||||
// Default value for request-runner's response.
|
// Default value for request-runner's response.
|
||||||
let _requestRunnerRes: RequestRunnerResponse = {
|
let _requestRunnerRes: RequestRunnerResponse = {
|
||||||
|
|||||||
@@ -95,22 +95,16 @@ declare module 'vue' {
|
|||||||
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
|
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
|
||||||
HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand']
|
HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand']
|
||||||
HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip']
|
HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip']
|
||||||
HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput']
|
|
||||||
HoppSmartIntersection: typeof import('@hoppscotch/ui')['HoppSmartIntersection']
|
|
||||||
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
|
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
|
||||||
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
|
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
|
||||||
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
|
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
|
||||||
HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture']
|
HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture']
|
||||||
HoppSmartPlaceholder: typeof import('@hoppscotch/ui')['HoppSmartPlaceholder']
|
|
||||||
HoppSmartProgressRing: typeof import('@hoppscotch/ui')['HoppSmartProgressRing']
|
HoppSmartProgressRing: typeof import('@hoppscotch/ui')['HoppSmartProgressRing']
|
||||||
HoppSmartRadio: typeof import('@hoppscotch/ui')['HoppSmartRadio']
|
|
||||||
HoppSmartRadioGroup: typeof import('@hoppscotch/ui')['HoppSmartRadioGroup']
|
HoppSmartRadioGroup: typeof import('@hoppscotch/ui')['HoppSmartRadioGroup']
|
||||||
HoppSmartSlideOver: typeof import('@hoppscotch/ui')['HoppSmartSlideOver']
|
HoppSmartSlideOver: typeof import('@hoppscotch/ui')['HoppSmartSlideOver']
|
||||||
HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner']
|
HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner']
|
||||||
HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab']
|
HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab']
|
||||||
HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs']
|
HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs']
|
||||||
HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle']
|
|
||||||
HoppSmartTree: typeof import('@hoppscotch/ui')['HoppSmartTree']
|
|
||||||
HoppSmartWindow: typeof import('@hoppscotch/ui')['HoppSmartWindow']
|
HoppSmartWindow: typeof import('@hoppscotch/ui')['HoppSmartWindow']
|
||||||
HoppSmartWindows: typeof import('@hoppscotch/ui')['HoppSmartWindows']
|
HoppSmartWindows: typeof import('@hoppscotch/ui')['HoppSmartWindows']
|
||||||
HttpAuthorization: typeof import('./components/http/Authorization.vue')['default']
|
HttpAuthorization: typeof import('./components/http/Authorization.vue')['default']
|
||||||
|
|||||||
Reference in New Issue
Block a user