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:
@@ -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 = () => {
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user