feat(cli): add support for AWS Signature Authorization type (#4305)
This commit is contained in:
@@ -42,6 +42,7 @@
|
||||
"private": false,
|
||||
"dependencies": {
|
||||
"axios": "1.7.5",
|
||||
"aws4fetch": "1.0.19",
|
||||
"chalk": "5.3.0",
|
||||
"commander": "11.1.0",
|
||||
"isolated-vm": "4.7.2",
|
||||
|
||||
@@ -385,7 +385,6 @@ describe("hopp test [options] <file_path_or_id>", () => {
|
||||
test("Supports the usage of request variables along with environment variables", async () => {
|
||||
const env = {
|
||||
...process.env,
|
||||
secretBasicAuthUsernameEnvVar: "username",
|
||||
secretBasicAuthPasswordEnvVar: "password",
|
||||
};
|
||||
|
||||
@@ -407,6 +406,30 @@ describe("hopp test [options] <file_path_or_id>", () => {
|
||||
expect(error).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("AWS Signature Authorization type", () => {
|
||||
test("Successfully translates the authorization information to headers/query params and sends it along with the request", async () => {
|
||||
const env = {
|
||||
...process.env,
|
||||
secretKey: "test-secret-key",
|
||||
serviceToken: "test-token",
|
||||
};
|
||||
|
||||
const COLL_PATH = getTestJsonFilePath(
|
||||
"aws-signature-auth-coll.json",
|
||||
"collection"
|
||||
);
|
||||
const ENVS_PATH = getTestJsonFilePath(
|
||||
"aws-signature-auth-envs.json",
|
||||
"environment"
|
||||
);
|
||||
|
||||
const args = `test ${COLL_PATH} -e ${ENVS_PATH}`;
|
||||
const { error } = await runCLI(args, { env });
|
||||
|
||||
expect(error).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Test `hopp test <file_path_or_id> --delay <delay_in_ms>` command:", () => {
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"v": 3,
|
||||
"name": "AWS Signature Auth - collection",
|
||||
"folders": [],
|
||||
"requests": [
|
||||
{
|
||||
"v": "7",
|
||||
"id": "cm0dm70cw000687bnxi830zz7",
|
||||
"auth": {
|
||||
"addTo": "HEADERS",
|
||||
"region": "<<awsRegion>>",
|
||||
"authType": "aws-signature",
|
||||
"accessKey": "<<accessKey>>",
|
||||
"secretKey": "<<secretVarKey>>",
|
||||
"authActive": true,
|
||||
"serviceName": "<<serviceName>>",
|
||||
"serviceToken": "",
|
||||
"grantTypeInfo": {
|
||||
"token": "",
|
||||
"isPKCE": false,
|
||||
"clientID": "",
|
||||
"grantType": "AUTHORIZATION_CODE",
|
||||
"authEndpoint": "",
|
||||
"clientSecret": "",
|
||||
"tokenEndpoint": "",
|
||||
"codeVerifierMethod": "S256"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"body": null,
|
||||
"contentType": null
|
||||
},
|
||||
"name": "aws-signature-auth-headers",
|
||||
"method": "GET",
|
||||
"params": [],
|
||||
"headers": [],
|
||||
"endpoint": "<<url>>",
|
||||
"testScript": "pw.test(\"Successfully sends relevant AWS signature information via headers\", ()=> {\n const { headers } = pw.response.body\n\n // Dynamic values, hence comparing the type.\n pw.expect(headers[\"authorization\"]).toBeType(\"string\");\n pw.expect(headers[\"x-amz-date\"]).toBeType(\"string\");\n \n pw.expect(headers[\"x-amz-content-sha256\"]).toBe(\"UNSIGNED-PAYLOAD\")\n \n // No session token supplied\n pw.expect(headers[\"x-amz-security-token\"]).toBe(undefined)\n \n});",
|
||||
"preRequestScript": "",
|
||||
"requestVariables": [
|
||||
{
|
||||
"key": "secretVarKey",
|
||||
"value": "<<secretKey>>",
|
||||
"active": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"v": "7",
|
||||
"id": "cm0dm70cw000687bnxi830zz7",
|
||||
"auth": {
|
||||
"addTo": "QUERY_PARAMS",
|
||||
"region": "<<awsRegion>>",
|
||||
"authType": "aws-signature",
|
||||
"accessKey": "<<accessKey>>",
|
||||
"secretKey": "<<secretKey>>",
|
||||
"authActive": true,
|
||||
"serviceName": "<<serviceName>>",
|
||||
"serviceToken": "<<serviceToken>>",
|
||||
"grantTypeInfo": {
|
||||
"token": "",
|
||||
"isPKCE": false,
|
||||
"clientID": "",
|
||||
"grantType": "AUTHORIZATION_CODE",
|
||||
"authEndpoint": "",
|
||||
"clientSecret": "",
|
||||
"tokenEndpoint": "",
|
||||
"codeVerifierMethod": "S256"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"body": null,
|
||||
"contentType": null
|
||||
},
|
||||
"name": "aws-signature-auth-query-params",
|
||||
"method": "GET",
|
||||
"params": [],
|
||||
"headers": [],
|
||||
"endpoint": "<<url>>",
|
||||
"testScript": "pw.test(\"Successfully sends relevant AWS signature information via query params\", ()=> {\n const { args } = pw.response.body\n pw.expect(args[\"X-Amz-Algorithm\"]).toBe(\"AWS4-HMAC-SHA256\");\n pw.expect(args[\"X-Amz-Algorithm\"]).toBe(\"AWS4-HMAC-SHA256\");\n pw.expect(args[\"X-Amz-Credential\"]).toInclude(\"test-access-key\");\n pw.expect(args[\"X-Amz-Credential\"]).toInclude(\"eu-west-1/s3\");\n\n // Dynamic values, hence comparing the type.\n pw.expect(args[\"X-Amz-Date\"]).toBeType(\"string\");\n pw.expect(args[\"X-Amz-Signature\"]).toBeType(\"string\");\n\n pw.expect(args[\"X-Amz-Expires\"]).toBe(\"86400\")\n pw.expect(args[\"X-Amz-SignedHeaders\"]).toBe(\"host\")\n pw.expect(args[\"X-Amz-Security-Token\"]).toBe(\"test-token\")\n \n});",
|
||||
"preRequestScript": "",
|
||||
"requestVariables": [
|
||||
{
|
||||
"key": "awsRegion",
|
||||
"value": "eu-west-1",
|
||||
"active": true
|
||||
},
|
||||
{
|
||||
"key": "secretKey",
|
||||
"value": "test-secret-key-overriden",
|
||||
"active": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"auth": {
|
||||
"authType": "inherit",
|
||||
"authActive": true
|
||||
},
|
||||
"headers": []
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"v": 1,
|
||||
"id": "cm0dsn3v70004p4qk3l9b7sjm",
|
||||
"name": "AWS Signature - environments",
|
||||
"variables": [
|
||||
{
|
||||
"key": "awsRegion",
|
||||
"value": "us-east-1",
|
||||
"secret": false
|
||||
},
|
||||
{
|
||||
"key": "serviceName",
|
||||
"value": "s3",
|
||||
"secret": false
|
||||
},
|
||||
{
|
||||
"key": "accessKey",
|
||||
"value": "test-access-key",
|
||||
"secret": true
|
||||
},
|
||||
{
|
||||
"key": "secretKey",
|
||||
"secret": true
|
||||
},
|
||||
{
|
||||
"key": "url",
|
||||
"value": "https://echo.hoppscotch.io",
|
||||
"secret": false
|
||||
},
|
||||
{
|
||||
"key": "serviceToken",
|
||||
"secret": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import * as TE from "fp-ts/TaskEither";
|
||||
import { flow, pipe } from "fp-ts/function";
|
||||
import * as S from "fp-ts/string";
|
||||
import qs from "qs";
|
||||
import { AwsV4Signer } from "aws4fetch";
|
||||
|
||||
import { EffectiveHoppRESTRequest } from "../interfaces/request";
|
||||
import { HoppCLIError, error } from "../types/errors";
|
||||
@@ -53,7 +54,13 @@ export const preRequestScriptRunner = (
|
||||
variables: [...(selected ?? []), ...(global ?? [])],
|
||||
}
|
||||
),
|
||||
TE.chainEitherKW((env) => getEffectiveRESTRequest(request, env)),
|
||||
TE.chainW((env) =>
|
||||
TE.tryCatch(
|
||||
() => getEffectiveRESTRequest(request, env),
|
||||
(reason) => error({ code: "PRE_REQUEST_SCRIPT_ERROR", data: reason })
|
||||
)
|
||||
),
|
||||
TE.chainEitherKW((effectiveRequest) => effectiveRequest),
|
||||
TE.mapLeft((reason) =>
|
||||
isHoppCLIError(reason)
|
||||
? reason
|
||||
@@ -72,12 +79,14 @@ export const preRequestScriptRunner = (
|
||||
*
|
||||
* @returns An object with extra fields defining a complete request
|
||||
*/
|
||||
export function getEffectiveRESTRequest(
|
||||
export async function getEffectiveRESTRequest(
|
||||
request: HoppRESTRequest,
|
||||
environment: Environment
|
||||
): E.Either<
|
||||
): Promise<
|
||||
E.Either<
|
||||
HoppCLIError,
|
||||
{ effectiveRequest: EffectiveHoppRESTRequest } & { updatedEnvs: HoppEnvs }
|
||||
>
|
||||
> {
|
||||
const envVariables = environment.variables;
|
||||
|
||||
@@ -170,6 +179,59 @@ export function getEffectiveRESTRequest(
|
||||
description: "",
|
||||
});
|
||||
}
|
||||
} else if (request.auth.authType === "aws-signature") {
|
||||
const { addTo } = request.auth;
|
||||
|
||||
const currentDate = new Date();
|
||||
const amzDate = currentDate.toISOString().replace(/[:-]|\.\d{3}/g, "");
|
||||
const { method, endpoint } = request;
|
||||
|
||||
const signer = new AwsV4Signer({
|
||||
method,
|
||||
datetime: amzDate,
|
||||
signQuery: addTo === "QUERY_PARAMS",
|
||||
accessKeyId: parseTemplateString(
|
||||
request.auth.accessKey,
|
||||
resolvedVariables
|
||||
),
|
||||
secretAccessKey: parseTemplateString(
|
||||
request.auth.secretKey,
|
||||
resolvedVariables
|
||||
),
|
||||
region:
|
||||
parseTemplateString(request.auth.region, resolvedVariables) ??
|
||||
"us-east-1",
|
||||
service: parseTemplateString(
|
||||
request.auth.serviceName,
|
||||
resolvedVariables
|
||||
),
|
||||
url: parseTemplateString(endpoint, resolvedVariables),
|
||||
sessionToken:
|
||||
request.auth.serviceToken &&
|
||||
parseTemplateString(request.auth.serviceToken, resolvedVariables),
|
||||
});
|
||||
|
||||
const sign = await signer.sign();
|
||||
|
||||
if (addTo === "HEADERS") {
|
||||
sign.headers.forEach((value, key) => {
|
||||
effectiveFinalHeaders.push({
|
||||
active: true,
|
||||
key,
|
||||
value,
|
||||
description: "",
|
||||
});
|
||||
});
|
||||
} else if (addTo === "QUERY_PARAMS") {
|
||||
sign.url.searchParams.forEach((value, key) => {
|
||||
effectiveFinalParams.push({
|
||||
active: true,
|
||||
key,
|
||||
value,
|
||||
description: "",
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -319,6 +319,9 @@ importers:
|
||||
|
||||
packages/hoppscotch-cli:
|
||||
dependencies:
|
||||
aws4fetch:
|
||||
specifier: 1.0.19
|
||||
version: 1.0.19
|
||||
axios:
|
||||
specifier: 1.7.5
|
||||
version: 1.7.5
|
||||
|
||||
Reference in New Issue
Block a user