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 () => {
|
||||
try {
|
||||
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 report = await collectionsRunner({collections, envs, delay})
|
||||
const hasSucceeded = collectionsRunnerResult(report)
|
||||
collectionsRunnerExit(hasSucceeded)
|
||||
|
||||
@@ -51,6 +51,9 @@ export const handleError = <T extends HoppErrorCode>(error: HoppError<T>) => {
|
||||
case "MALFORMED_COLLECTION":
|
||||
ERROR_MSG = `${error.path}\n${parseErrorData(error.data)}`;
|
||||
break;
|
||||
case "ENVIRONMENT_NAME_NOT_FOUND":
|
||||
ERROR_MSG = `\n${parseErrorData(error.data)}`;
|
||||
break;
|
||||
case "NO_FILE_PATH":
|
||||
ERROR_MSG = `Please provide a hoppscotch-collection file path.`;
|
||||
break;
|
||||
|
||||
@@ -50,6 +50,7 @@ program
|
||||
"path to a hoppscotch collection.json file for CI testing"
|
||||
)
|
||||
.option("-e, --env <file_path>", "path to an environment variables json file")
|
||||
.option("-eN, --envName <environment_name>","Specific Name of the environment")
|
||||
.option(
|
||||
"-d, --delay <delay_in_ms>",
|
||||
"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 }[];
|
||||
effectiveFinalParams: { key: string; value: string; active: boolean }[];
|
||||
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.
|
||||
* @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.
|
||||
*/
|
||||
export async function parseEnvsData(path: string) {
|
||||
export async function parseEnvsData(path: string, envName: string | undefined) {
|
||||
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 })
|
||||
}
|
||||
|
||||
const envPairs: Array<HoppEnvPair> = []
|
||||
|
||||
for( const [key,value] of Object.entries(contents)) {
|
||||
if(typeof value !== "string") {
|
||||
throw error({ code: "MALFORMED_ENV_FILE", path, data: {value: value} })
|
||||
}
|
||||
const contentEntries = Object.entries(contents)
|
||||
let environmentFound = false;
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export type TestCmdOptions = {
|
||||
env: string | undefined;
|
||||
delay: string | undefined;
|
||||
envName: string | undefined;
|
||||
};
|
||||
|
||||
export type HOPP_ENV_FILE_EXT = "json";
|
||||
|
||||
@@ -15,6 +15,7 @@ type HoppErrors = {
|
||||
FILE_NOT_FOUND: HoppErrorPath;
|
||||
UNKNOWN_COMMAND: HoppErrorCmd;
|
||||
MALFORMED_COLLECTION: HoppErrorPath & HoppErrorData;
|
||||
ENVIRONMENT_NAME_NOT_FOUND: HoppErrorData;
|
||||
NO_FILE_PATH: {};
|
||||
PRE_REQUEST_SCRIPT_ERROR: HoppErrorData;
|
||||
PARSING_ERROR: HoppErrorData;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { bold } from "chalk";
|
||||
import { groupEnd, group, log } from "console";
|
||||
import { handleError } from "../handlers/error";
|
||||
import { RequestConfig } from "../interfaces/request";
|
||||
import { Method } from "axios";
|
||||
import { RequestRunnerResponse, TestReport } from "../interfaces/response";
|
||||
import { HoppCLIError } from "../types/errors";
|
||||
import {
|
||||
@@ -172,11 +172,12 @@ export const printFailedTestsReport = (
|
||||
export const printRequestRunner = {
|
||||
/**
|
||||
* 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) => {
|
||||
const METHOD = BG_INFO(` ${requestConfig.method} `);
|
||||
const ENDPOINT = requestConfig.url;
|
||||
start: (requestMethod: Method | undefined, maskedURL: string) => {
|
||||
const METHOD = BG_INFO(` ${requestMethod} `);
|
||||
const ENDPOINT = maskedURL;
|
||||
|
||||
process.stdout.write(`${METHOD} ${ENDPOINT}`);
|
||||
},
|
||||
|
||||
@@ -50,9 +50,9 @@ export const preRequestScriptRunner = (
|
||||
isHoppCLIError(reason)
|
||||
? reason
|
||||
: error({
|
||||
code: "PRE_REQUEST_SCRIPT_ERROR",
|
||||
data: reason,
|
||||
})
|
||||
code: "PRE_REQUEST_SCRIPT_ERROR",
|
||||
data: reason,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
@@ -151,6 +151,12 @@ export function getEffectiveRESTRequest(
|
||||
request.endpoint,
|
||||
envVariables
|
||||
);
|
||||
|
||||
const maskedEnvVariables = setAllEnvironmentValuesToAsterisk(envVariables)
|
||||
const _effectiveFinalMaskedURL = parseTemplateStringE(
|
||||
request.endpoint,
|
||||
maskedEnvVariables)
|
||||
|
||||
if (E.isLeft(_effectiveFinalURL)) {
|
||||
return E.left(
|
||||
error({
|
||||
@@ -160,6 +166,7 @@ export function getEffectiveRESTRequest(
|
||||
);
|
||||
}
|
||||
const effectiveFinalURL = _effectiveFinalURL.right;
|
||||
const effectiveFinalMaskedURL = E.isLeft(_effectiveFinalMaskedURL) ? request.endpoint : _effectiveFinalMaskedURL.right;
|
||||
|
||||
return E.right({
|
||||
...request,
|
||||
@@ -167,6 +174,7 @@ export function getEffectiveRESTRequest(
|
||||
effectiveFinalHeaders,
|
||||
effectiveFinalParams,
|
||||
effectiveFinalBody,
|
||||
effectiveFinalMaskedURL,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -242,15 +250,15 @@ function getFinalBodyFromRequest(
|
||||
arrayFlatMap((x) =>
|
||||
x.isFile
|
||||
? x.value.map((v) => ({
|
||||
key: parseTemplateString(x.key, envVariables),
|
||||
value: v as string | Blob,
|
||||
}))
|
||||
key: parseTemplateString(x.key, envVariables),
|
||||
value: v as string | Blob,
|
||||
}))
|
||||
: [
|
||||
{
|
||||
key: parseTemplateString(x.key, envVariables),
|
||||
value: parseTemplateString(x.value, envVariables),
|
||||
},
|
||||
]
|
||||
{
|
||||
key: parseTemplateString(x.key, envVariables),
|
||||
value: parseTemplateString(x.value, envVariables),
|
||||
},
|
||||
]
|
||||
),
|
||||
toFormData,
|
||||
E.right
|
||||
@@ -287,3 +295,22 @@ export const getPreRequestMetrics = (
|
||||
hasPreReqErrors ? { failed: 1, passed: 0 } : { failed: 0, passed: 1 },
|
||||
(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: [],
|
||||
effectiveFinalParams: [],
|
||||
effectiveFinalURL: "",
|
||||
effectiveFinalMaskedURL:"",
|
||||
};
|
||||
|
||||
// Executing pre-request-script
|
||||
@@ -237,8 +238,8 @@ export const processRequest =
|
||||
|
||||
// Creating request-config for request-runner.
|
||||
const requestConfig = createRequest(effectiveRequest);
|
||||
|
||||
printRequestRunner.start(requestConfig);
|
||||
|
||||
printRequestRunner.start(requestConfig.method, effectiveRequest.effectiveFinalMaskedURL);
|
||||
|
||||
// Default value for request-runner's response.
|
||||
let _requestRunnerRes: RequestRunnerResponse = {
|
||||
|
||||
@@ -95,22 +95,16 @@ declare module 'vue' {
|
||||
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
|
||||
HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand']
|
||||
HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip']
|
||||
HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput']
|
||||
HoppSmartIntersection: typeof import('@hoppscotch/ui')['HoppSmartIntersection']
|
||||
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
|
||||
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
|
||||
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
|
||||
HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture']
|
||||
HoppSmartPlaceholder: typeof import('@hoppscotch/ui')['HoppSmartPlaceholder']
|
||||
HoppSmartProgressRing: typeof import('@hoppscotch/ui')['HoppSmartProgressRing']
|
||||
HoppSmartRadio: typeof import('@hoppscotch/ui')['HoppSmartRadio']
|
||||
HoppSmartRadioGroup: typeof import('@hoppscotch/ui')['HoppSmartRadioGroup']
|
||||
HoppSmartSlideOver: typeof import('@hoppscotch/ui')['HoppSmartSlideOver']
|
||||
HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner']
|
||||
HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab']
|
||||
HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs']
|
||||
HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle']
|
||||
HoppSmartTree: typeof import('@hoppscotch/ui')['HoppSmartTree']
|
||||
HoppSmartWindow: typeof import('@hoppscotch/ui')['HoppSmartWindow']
|
||||
HoppSmartWindows: typeof import('@hoppscotch/ui')['HoppSmartWindows']
|
||||
HttpAuthorization: typeof import('./components/http/Authorization.vue')['default']
|
||||
|
||||
Reference in New Issue
Block a user