feat: add support for AWS Signature auth type (#4142)

Co-authored-by: jamesgeorge007 <25279263+jamesgeorge007@users.noreply.github.com>
Co-authored-by: nivedin <nivedinp@gmail.com>
This commit is contained in:
Anwarul Islam
2024-08-30 14:30:13 +06:00
committed by GitHub
parent 5a2eed60c9
commit 703b71de2c
26 changed files with 1499 additions and 666 deletions

View File

@@ -32,68 +32,14 @@
@keyup.escape="hide()"
>
<HoppSmartItem
v-if="!isRootCollection"
label="Inherit"
:icon="authName === 'Inherit' ? IconCircleDot : IconCircle"
:active="authName === 'Inherit'"
v-for="item in authTypes"
:key="item.key"
:label="item.label"
:icon="item.key === authType ? IconCircleDot : IconCircle"
:active="item.key === authType"
@click="
() => {
auth.authType = 'inherit'
hide()
}
"
/>
<HoppSmartItem
label="None"
:icon="authName === 'None' ? IconCircleDot : IconCircle"
:active="authName === 'None'"
@click="
() => {
auth.authType = 'none'
hide()
}
"
/>
<HoppSmartItem
label="Basic Auth"
:icon="authName === 'Basic Auth' ? IconCircleDot : IconCircle"
:active="authName === 'Basic Auth'"
@click="
() => {
auth.authType = 'basic'
hide()
}
"
/>
<HoppSmartItem
label="Bearer Token"
:icon="authName === 'Bearer' ? IconCircleDot : IconCircle"
:active="authName === 'Bearer'"
@click="
() => {
auth.authType = 'bearer'
hide()
}
"
/>
<HoppSmartItem
label="OAuth 2.0"
:icon="authName === 'OAuth 2.0' ? IconCircleDot : IconCircle"
:active="authName === 'OAuth 2.0'"
@click="
() => {
selectOAuth2AuthType()
hide()
}
"
/>
<HoppSmartItem
label="API key"
:icon="authName === 'API key' ? IconCircleDot : IconCircle"
:active="authName === 'API key'"
@click="
() => {
auth.authType = 'api-key'
item.handler ? item.handler() : (auth.authType = item.key)
hide()
}
"
@@ -168,13 +114,17 @@
</div>
<div v-if="auth.authType === 'inherit'" class="p-4">
<span v-if="inheritedProperties?.auth">
Inherited
{{ getAuthName(inheritedProperties.auth.inheritedAuth.authType) }}
from Parent Collection {{ inheritedProperties?.auth.parentName }}
{{
t("authorization.inherited_from", {
auth: getAuthName(
inheritedProperties.auth.inheritedAuth.authType
),
collection: inheritedProperties?.auth.parentName,
})
}}
</span>
<span v-else>
Please save this request in any collection to inherit the
authorization
{{ t("authorization.save_to_inherit") }}
</span>
</div>
<div v-if="auth.authType === 'bearer'">
@@ -194,11 +144,14 @@
placeholder="Token"
/>
</div>
<HttpOAuth2Authorization v-model="auth" source="GraphQL" />
<HttpAuthorizationOAuth2 v-model="auth" source="GraphQL" />
</div>
<div v-if="auth.authType === 'api-key'">
<HttpAuthorizationApiKey v-model="auth" />
</div>
<div v-if="auth.authType === 'aws-signature'">
<HttpAuthorizationAWSSign v-model="auth" />
</div>
</div>
<div
class="z-[9] sticky top-upperTertiaryStickyFold h-full min-w-[12rem] max-w-1/3 flex-shrink-0 overflow-auto overflow-x-auto bg-primary p-4"
@@ -237,6 +190,12 @@ import IconTrash2 from "~icons/lucide/trash-2"
import { getDefaultAuthCodeOauthFlowParams } from "~/services/oauth/flows/authCode"
type AuthType = {
key: HoppGQLAuth["authType"]
label: string
handler?: () => void
}
const t = useI18n()
const colorMode = useColorMode()
@@ -263,26 +222,6 @@ onMounted(() => {
const auth = useVModel(props, "modelValue", emit)
const AUTH_KEY_NAME = {
basic: "Basic Auth",
bearer: "Bearer",
"oauth-2": "OAuth 2.0",
"api-key": "API key",
none: "None",
inherit: "Inherit",
} as const
const authType = pluckRef(auth, "authType")
const authName = computed(() =>
AUTH_KEY_NAME[authType.value] ? AUTH_KEY_NAME[authType.value] : "None"
)
const getAuthName = (type: HoppGQLAuth["authType"] | undefined) => {
if (!type) return "None"
return AUTH_KEY_NAME[type] ? AUTH_KEY_NAME[type] : "None"
}
const selectOAuth2AuthType = () => {
const defaultGrantTypeInfo: HoppGQLAuthOAuth2["grantTypeInfo"] = {
...getDefaultAuthCodeOauthFlowParams(),
@@ -307,6 +246,59 @@ const selectOAuth2AuthType = () => {
}
}
const authTypes: AuthType[] = [
{
key: "inherit",
label: "Inherit",
},
{
key: "none",
label: "None",
},
{
key: "basic",
label: "Basic Auth",
},
{
key: "bearer",
label: "Bearer",
},
{
key: "oauth-2",
label: "OAuth 2.0",
handler: selectOAuth2AuthType,
},
{
key: "api-key",
label: "API Key",
},
{
key: "aws-signature",
label: "AWS Signature",
},
]
const AUTH_KEY_NAME: Record<HoppGQLAuth["authType"], string> = {
basic: "Basic Auth",
bearer: "Bearer",
"oauth-2": "OAuth 2.0",
"api-key": "API key",
none: "None",
inherit: "Inherit",
"aws-signature": "AWS Signature",
}
const authType = pluckRef(auth, "authType")
const authName = computed(() =>
AUTH_KEY_NAME[authType.value] ? AUTH_KEY_NAME[authType.value] : "None"
)
const getAuthName = (type: HoppGQLAuth["authType"] | undefined) => {
if (!type) return "None"
return AUTH_KEY_NAME[type] ? AUTH_KEY_NAME[type] : "None"
}
const authActive = pluckRef(auth, "authActive")
const clearContent = () => {

View File

@@ -231,26 +231,25 @@ import { useColorMode } from "@composables/theming"
import { useToast } from "@composables/toast"
import {
GQLHeader,
HoppGQLAuth,
HoppGQLRequest,
parseRawKeyValueEntriesE,
rawKeyValueEntriesToString,
RawKeyValueEntry,
} from "@hoppscotch/data"
import { useVModel } from "@vueuse/core"
import { computedAsync, useVModel } from "@vueuse/core"
import { AwsV4Signer } from "aws4fetch"
import * as A from "fp-ts/Array"
import * as E from "fp-ts/Either"
import * as O from "fp-ts/Option"
import * as RA from "fp-ts/ReadonlyArray"
import { flow, pipe } from "fp-ts/function"
import { clone, cloneDeep, isEqual } from "lodash-es"
import { computed, reactive, ref, toRef, watch } from "vue"
import { reactive, ref, toRef, watch } from "vue"
import draggable from "vuedraggable-es"
import { useNestedSetting } from "~/composables/settings"
import { throwError } from "~/helpers/functional/error"
import { objRemoveKey } from "~/helpers/functional/object"
import { HoppGQLHeader } from "~/helpers/graphql"
import { commonHeaders } from "~/helpers/headers"
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
import { toggleNestedSetting } from "~/newstore/settings"
@@ -524,7 +523,7 @@ const clearContent = () => {
bulkHeaders.value = ""
}
const getComputedAuthHeaders = (
const getComputedAuthHeaders = async (
req?: HoppGQLRequest,
auth?: HoppGQLRequest["auth"]
) => {
@@ -537,7 +536,7 @@ const getComputedAuthHeaders = (
if (!request.auth || !request.auth.authActive) return []
const headers: HoppGQLHeader[] = []
const headers: GQLHeader[] = []
// TODO: Support a better b64 implementation than btoa ?
if (request.auth.authType === "basic") {
@@ -548,6 +547,7 @@ const getComputedAuthHeaders = (
active: true,
key: "Authorization",
value: `Basic ${btoa(`${username}:${password}`)}`,
description: "",
})
} else if (
request.auth.authType === "bearer" ||
@@ -563,6 +563,7 @@ const getComputedAuthHeaders = (
active: true,
key: "Authorization",
value: `Bearer ${token}`,
description: "",
})
} else if (request.auth.authType === "api-key") {
const { key, addTo } = request.auth
@@ -572,6 +573,35 @@ const getComputedAuthHeaders = (
active: true,
key,
value: request.auth.value ?? "",
description: "",
})
}
} else if (request.auth.authType === "aws-signature") {
const { addTo } = request.auth
if (addTo === "HEADERS") {
const currentDate = new Date()
const amzDate = currentDate.toISOString().replace(/[:-]|\.\d{3}/g, "")
const { url } = req as HoppGQLRequest
const signer = new AwsV4Signer({
datetime: amzDate,
accessKeyId: request.auth.accessKey,
secretAccessKey: request.auth.secretKey,
region: request.auth.region ?? "us-east-1",
service: request.auth.serviceName,
url,
})
const sign = await signer.sign()
sign.headers.forEach((x, k) => {
headers.push({
active: true,
key: k,
value: x,
description: "",
})
})
}
}
@@ -579,23 +609,23 @@ const getComputedAuthHeaders = (
return headers
}
const getComputedHeaders = (req: HoppGQLRequest) => {
const getComputedHeaders = async (req: HoppGQLRequest) => {
return [
...getComputedAuthHeaders(req).map((header) => ({
...(await getComputedAuthHeaders(req)).map((header) => ({
source: "auth" as const,
header,
})),
]
}
const computedHeaders = computed(() =>
getComputedHeaders(request.value).map((header, index) => ({
const computedHeaders = computedAsync(async () =>
(await getComputedHeaders(request.value)).map((header, index) => ({
id: `header-${index}`,
...header,
}))
)
const inheritedProperties = computed(() => {
const inheritedProperties = computedAsync(async () => {
if (!props.inheritedProperties?.auth || !props.inheritedProperties.headers)
return []
@@ -642,10 +672,10 @@ const inheritedProperties = computed(() => {
}
}[]
const computedAuthHeader = getComputedAuthHeaders(
const [computedAuthHeader] = await getComputedAuthHeaders(
request.value,
props.inheritedProperties.auth.inheritedAuth as HoppGQLAuth
)[0]
props.inheritedProperties.auth.inheritedAuth
)
if (
computedAuthHeader &&

View File

@@ -1,58 +1,56 @@
<template>
<div class="h-full">
<HoppSmartTabs
v-model="selectedOptionTab"
styles="sticky top-0 bg-primary z-10 border-b-0"
:render-inactive-tabs="true"
<HoppSmartTabs
v-model="selectedOptionTab"
styles="sticky bg-primary top-0 z-10 border-b-0"
:render-inactive-tabs="true"
>
<HoppSmartTab
:id="'query'"
:label="`${t('tab.query')}`"
:indicator="request.query && request.query.length > 0 ? true : false"
>
<HoppSmartTab
:id="'query'"
:label="`${t('tab.query')}`"
:indicator="request.query && request.query.length > 0 ? true : false"
>
<GraphqlQuery
v-model="request.query"
@run-query="runQuery"
@save-request="saveRequest"
/>
</HoppSmartTab>
<HoppSmartTab
:id="'variables'"
:label="`${t('tab.variables')}`"
:indicator="
request.variables && request.variables.length > 0 ? true : false
"
>
<GraphqlVariable
v-model="request.variables"
@run-query="runQuery"
@save-request="saveRequest"
/>
</HoppSmartTab>
<HoppSmartTab
:id="'headers'"
:label="`${t('tab.headers')}`"
:info="activeGQLHeadersCount === 0 ? null : `${activeGQLHeadersCount}`"
>
<GraphqlHeaders
v-model="request"
:inherited-properties="inheritedProperties"
@change-tab="changeOptionTab"
/>
</HoppSmartTab>
<HoppSmartTab :id="'authorization'" :label="`${t('tab.authorization')}`">
<GraphqlAuthorization
v-model="request.auth"
:inherited-properties="inheritedProperties"
/>
</HoppSmartTab>
</HoppSmartTabs>
<CollectionsSaveRequest
mode="graphql"
:show="showSaveRequestModal"
@hide-modal="hideRequestModal"
/>
</div>
<GraphqlQuery
v-model="request.query"
@run-query="runQuery"
@save-request="saveRequest"
/>
</HoppSmartTab>
<HoppSmartTab
:id="'variables'"
:label="`${t('tab.variables')}`"
:indicator="
request.variables && request.variables.length > 0 ? true : false
"
>
<GraphqlVariable
v-model="request.variables"
@run-query="runQuery"
@save-request="saveRequest"
/>
</HoppSmartTab>
<HoppSmartTab
:id="'headers'"
:label="`${t('tab.headers')}`"
:info="activeGQLHeadersCount === 0 ? null : `${activeGQLHeadersCount}`"
>
<GraphqlHeaders
v-model="request"
:inherited-properties="inheritedProperties"
@change-tab="changeOptionTab"
/>
</HoppSmartTab>
<HoppSmartTab :id="'authorization'" :label="`${t('tab.authorization')}`">
<GraphqlAuthorization
v-model="request.auth"
:inherited-properties="inheritedProperties"
/>
</HoppSmartTab>
</HoppSmartTabs>
<CollectionsSaveRequest
mode="graphql"
:show="showSaveRequestModal"
@hide-modal="hideRequestModal"
/>
</template>
<script setup lang="ts">