Add an authorization tab for GraphQL (#2125)
Co-authored-by: Rishabh Agarwal <rishabh2001agarwal@gmail.com> Co-authored-by: Rishabh Agarwal <45998880+RishabhAgarwal-2001@users.noreply.github.com> Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
This commit is contained in:
@@ -123,6 +123,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType, ref } from "@nuxtjs/composition-api"
|
import { defineComponent, PropType, ref } from "@nuxtjs/composition-api"
|
||||||
import { HoppGQLRequest, makeGQLRequest } from "@hoppscotch/data"
|
import { HoppGQLRequest, makeGQLRequest } from "@hoppscotch/data"
|
||||||
|
import { cloneDeep } from "lodash"
|
||||||
import { removeGraphqlRequest } from "~/newstore/collections"
|
import { removeGraphqlRequest } from "~/newstore/collections"
|
||||||
import { setGQLSession } from "~/newstore/GQLSession"
|
import { setGQLSession } from "~/newstore/GQLSession"
|
||||||
|
|
||||||
@@ -177,13 +178,16 @@ export default defineComponent({
|
|||||||
this.pick()
|
this.pick()
|
||||||
} else {
|
} else {
|
||||||
setGQLSession({
|
setGQLSession({
|
||||||
request: makeGQLRequest({
|
request: cloneDeep(
|
||||||
name: this.$props.request.name,
|
makeGQLRequest({
|
||||||
url: this.$props.request.url,
|
name: this.$props.request.name,
|
||||||
query: this.$props.request.query,
|
url: this.$props.request.url,
|
||||||
headers: this.$props.request.headers,
|
query: this.$props.request.query,
|
||||||
variables: this.$props.request.variables,
|
headers: this.$props.request.headers,
|
||||||
}),
|
variables: this.$props.request.variables,
|
||||||
|
auth: this.$props.request.auth,
|
||||||
|
})
|
||||||
|
),
|
||||||
schema: "",
|
schema: "",
|
||||||
response: "",
|
response: "",
|
||||||
})
|
})
|
||||||
|
|||||||
322
packages/hoppscotch-app/components/graphql/Authorization.vue
Normal file
322
packages/hoppscotch-app/components/graphql/Authorization.vue
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col flex-1">
|
||||||
|
<div
|
||||||
|
class="sticky z-10 flex items-center justify-between pl-4 border-b bg-primary border-dividerLight top-upperSecondaryStickyFold"
|
||||||
|
>
|
||||||
|
<span class="flex items-center">
|
||||||
|
<label class="font-semibold text-secondaryLight">
|
||||||
|
{{ $t("authorization.type") }}
|
||||||
|
</label>
|
||||||
|
<tippy
|
||||||
|
ref="authTypeOptions"
|
||||||
|
interactive
|
||||||
|
trigger="click"
|
||||||
|
theme="popover"
|
||||||
|
arrow
|
||||||
|
>
|
||||||
|
<template #trigger>
|
||||||
|
<span class="select-wrapper">
|
||||||
|
<ButtonSecondary
|
||||||
|
class="pr-8 ml-2 rounded-none"
|
||||||
|
:label="authName"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<SmartItem
|
||||||
|
label="None"
|
||||||
|
:icon="
|
||||||
|
authName === 'None'
|
||||||
|
? 'radio_button_checked'
|
||||||
|
: 'radio_button_unchecked'
|
||||||
|
"
|
||||||
|
:active="authName === 'None'"
|
||||||
|
@click.native="
|
||||||
|
() => {
|
||||||
|
authType = 'none'
|
||||||
|
authTypeOptions.tippy().hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<SmartItem
|
||||||
|
label="Basic Auth"
|
||||||
|
:icon="
|
||||||
|
authName === 'Basic Auth'
|
||||||
|
? 'radio_button_checked'
|
||||||
|
: 'radio_button_unchecked'
|
||||||
|
"
|
||||||
|
:active="authName === 'Basic Auth'"
|
||||||
|
@click.native="
|
||||||
|
() => {
|
||||||
|
authType = 'basic'
|
||||||
|
authTypeOptions.tippy().hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<SmartItem
|
||||||
|
label="Bearer Token"
|
||||||
|
:icon="
|
||||||
|
authName === 'Bearer'
|
||||||
|
? 'radio_button_checked'
|
||||||
|
: 'radio_button_unchecked'
|
||||||
|
"
|
||||||
|
:active="authName === 'Bearer'"
|
||||||
|
@click.native="
|
||||||
|
() => {
|
||||||
|
authType = 'bearer'
|
||||||
|
authTypeOptions.tippy().hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<SmartItem
|
||||||
|
label="OAuth 2.0"
|
||||||
|
:icon="
|
||||||
|
authName === 'OAuth 2.0'
|
||||||
|
? 'radio_button_checked'
|
||||||
|
: 'radio_button_unchecked'
|
||||||
|
"
|
||||||
|
:active="authName === 'OAuth 2.0'"
|
||||||
|
@click.native="
|
||||||
|
() => {
|
||||||
|
authType = 'oauth-2'
|
||||||
|
authTypeOptions.tippy().hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<SmartItem
|
||||||
|
label="API key"
|
||||||
|
:icon="
|
||||||
|
authName === 'API key'
|
||||||
|
? 'radio_button_checked'
|
||||||
|
: 'radio_button_unchecked'
|
||||||
|
"
|
||||||
|
:active="authName === 'API key'"
|
||||||
|
@click.native="
|
||||||
|
() => {
|
||||||
|
authType = 'api-key'
|
||||||
|
authTypeOptions.tippy().hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</tippy>
|
||||||
|
</span>
|
||||||
|
<div class="flex">
|
||||||
|
<!-- <SmartCheckbox
|
||||||
|
:on="!URLExcludes.auth"
|
||||||
|
@change="setExclude('auth', !$event)"
|
||||||
|
>
|
||||||
|
{{ $t("authorization.include_in_url") }}
|
||||||
|
</SmartCheckbox> -->
|
||||||
|
<SmartCheckbox
|
||||||
|
:on="authActive"
|
||||||
|
class="px-2"
|
||||||
|
@change="authActive = !authActive"
|
||||||
|
>
|
||||||
|
{{ $t("state.enabled") }}
|
||||||
|
</SmartCheckbox>
|
||||||
|
<ButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
to="https://docs.hoppscotch.io/features/authorization"
|
||||||
|
blank
|
||||||
|
:title="$t('app.wiki')"
|
||||||
|
svg="help-circle"
|
||||||
|
/>
|
||||||
|
<ButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="$t('action.clear')"
|
||||||
|
svg="trash-2"
|
||||||
|
@click.native="clearContent"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="authType === 'none'"
|
||||||
|
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="`/images/states/${$colorMode.value}/login.svg`"
|
||||||
|
loading="lazy"
|
||||||
|
class="inline-flex flex-col object-contain object-center w-16 h-16 my-4"
|
||||||
|
:alt="`${$t('empty.authorization')}`"
|
||||||
|
/>
|
||||||
|
<span class="pb-4 text-center">
|
||||||
|
{{ $t("empty.authorization") }}
|
||||||
|
</span>
|
||||||
|
<ButtonSecondary
|
||||||
|
outline
|
||||||
|
:label="$t('app.documentation')"
|
||||||
|
to="https://docs.hoppscotch.io/features/authorization"
|
||||||
|
blank
|
||||||
|
svg="external-link"
|
||||||
|
reverse
|
||||||
|
class="mb-4"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex flex-1 border-b border-dividerLight">
|
||||||
|
<div class="w-2/3 border-r border-dividerLight">
|
||||||
|
<div v-if="authType === 'basic'">
|
||||||
|
<div class="flex flex-1 border-b border-dividerLight">
|
||||||
|
<SmartEnvInput
|
||||||
|
v-model="basicUsername"
|
||||||
|
:placeholder="$t('authorization.username')"
|
||||||
|
styles="bg-transparent flex flex-1 py-1 px-4"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-1 border-b border-dividerLight">
|
||||||
|
<SmartEnvInput
|
||||||
|
v-model="basicPassword"
|
||||||
|
:placeholder="$t('authorization.password')"
|
||||||
|
styles="bg-transparent flex flex-1 py-1 px-4"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="authType === 'bearer'">
|
||||||
|
<div class="flex flex-1 border-b border-dividerLight">
|
||||||
|
<SmartEnvInput
|
||||||
|
v-model="bearerToken"
|
||||||
|
placeholder="Token"
|
||||||
|
styles="bg-transparent flex flex-1 py-1 px-4"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="authType === 'oauth-2'">
|
||||||
|
<div class="flex flex-1 border-b border-dividerLight">
|
||||||
|
<SmartEnvInput
|
||||||
|
v-model="oauth2Token"
|
||||||
|
placeholder="Token"
|
||||||
|
styles="bg-transparent flex flex-1 py-1 px-4"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<HttpOAuth2Authorization />
|
||||||
|
</div>
|
||||||
|
<div v-if="authType === 'api-key'">
|
||||||
|
<div class="flex flex-1 border-b border-dividerLight">
|
||||||
|
<SmartEnvInput
|
||||||
|
v-model="apiKey"
|
||||||
|
placeholder="Key"
|
||||||
|
styles="bg-transparent flex flex-1 py-1 px-4"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-1 border-b border-dividerLight">
|
||||||
|
<SmartEnvInput
|
||||||
|
v-model="apiValue"
|
||||||
|
placeholder="Value"
|
||||||
|
styles="bg-transparent flex flex-1 py-1 px-4"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center border-b border-dividerLight">
|
||||||
|
<label class="ml-4 text-secondaryLight">
|
||||||
|
{{ $t("authorization.pass_key_by") }}
|
||||||
|
</label>
|
||||||
|
<tippy
|
||||||
|
ref="addToOptions"
|
||||||
|
interactive
|
||||||
|
trigger="click"
|
||||||
|
theme="popover"
|
||||||
|
arrow
|
||||||
|
>
|
||||||
|
<template #trigger>
|
||||||
|
<span class="select-wrapper">
|
||||||
|
<ButtonSecondary
|
||||||
|
:label="addTo || $t('state.none')"
|
||||||
|
class="pr-8 ml-2 rounded-none"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<SmartItem
|
||||||
|
:icon="
|
||||||
|
addTo === 'Headers'
|
||||||
|
? 'radio_button_checked'
|
||||||
|
: 'radio_button_unchecked'
|
||||||
|
"
|
||||||
|
:active="addTo === 'Headers'"
|
||||||
|
:label="'Headers'"
|
||||||
|
@click.native="
|
||||||
|
() => {
|
||||||
|
addTo = 'Headers'
|
||||||
|
addToOptions.tippy().hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<SmartItem
|
||||||
|
:icon="
|
||||||
|
addTo === 'Query params'
|
||||||
|
? 'radio_button_checked'
|
||||||
|
: 'radio_button_unchecked'
|
||||||
|
"
|
||||||
|
:active="addTo === 'Query params'"
|
||||||
|
:label="'Query params'"
|
||||||
|
@click.native="
|
||||||
|
() => {
|
||||||
|
addTo = 'Query params'
|
||||||
|
addToOptions.tippy().hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</tippy>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="sticky h-full p-4 overflow-auto bg-primary top-upperTertiaryStickyFold min-w-46 max-w-1/3 z-9"
|
||||||
|
>
|
||||||
|
<div class="pb-2 text-secondaryLight">
|
||||||
|
{{ $t("helpers.authorization") }}
|
||||||
|
</div>
|
||||||
|
<SmartAnchor
|
||||||
|
class="link"
|
||||||
|
:label="`${$t('authorization.learn')} \xA0 →`"
|
||||||
|
to="https://docs.hoppscotch.io/features/authorization"
|
||||||
|
blank
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, Ref } from "@nuxtjs/composition-api"
|
||||||
|
import {
|
||||||
|
HoppGQLAuthAPIKey,
|
||||||
|
HoppGQLAuthBasic,
|
||||||
|
HoppGQLAuthBearer,
|
||||||
|
HoppGQLAuthOAuth2,
|
||||||
|
} from "@hoppscotch/data"
|
||||||
|
import { pluckRef, useStream } from "~/helpers/utils/composables"
|
||||||
|
import { gqlAuth$, setGQLAuth } from "~/newstore/GQLSession"
|
||||||
|
|
||||||
|
const auth = useStream(
|
||||||
|
gqlAuth$,
|
||||||
|
{ authType: "none", authActive: true },
|
||||||
|
setGQLAuth
|
||||||
|
)
|
||||||
|
const authType = pluckRef(auth, "authType")
|
||||||
|
const authName = computed(() => {
|
||||||
|
if (authType.value === "basic") return "Basic Auth"
|
||||||
|
else if (authType.value === "bearer") return "Bearer"
|
||||||
|
else if (authType.value === "oauth-2") return "OAuth 2.0"
|
||||||
|
else if (authType.value === "api-key") return "API key"
|
||||||
|
else return "None"
|
||||||
|
})
|
||||||
|
const authActive = pluckRef(auth, "authActive")
|
||||||
|
const basicUsername = pluckRef(auth as Ref<HoppGQLAuthBasic>, "username")
|
||||||
|
const basicPassword = pluckRef(auth as Ref<HoppGQLAuthBasic>, "password")
|
||||||
|
const bearerToken = pluckRef(auth as Ref<HoppGQLAuthBearer>, "token")
|
||||||
|
const oauth2Token = pluckRef(auth as Ref<HoppGQLAuthOAuth2>, "token")
|
||||||
|
const apiKey = pluckRef(auth as Ref<HoppGQLAuthAPIKey>, "key")
|
||||||
|
const apiValue = pluckRef(auth as Ref<HoppGQLAuthAPIKey>, "value")
|
||||||
|
const addTo = pluckRef(auth as Ref<HoppGQLAuthAPIKey>, "addTo")
|
||||||
|
if (typeof addTo.value === "undefined") {
|
||||||
|
addTo.value = "Headers"
|
||||||
|
apiKey.value = ""
|
||||||
|
apiValue.value = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearContent = () => {
|
||||||
|
auth.value = {
|
||||||
|
authType: "none",
|
||||||
|
authActive: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const authTypeOptions = ref<any | null>(null)
|
||||||
|
const addToOptions = ref<any | null>(null)
|
||||||
|
</script>
|
||||||
@@ -260,6 +260,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</SmartTab>
|
</SmartTab>
|
||||||
|
<SmartTab :id="'authorization'" :label="`${t('tab.authorization')}`">
|
||||||
|
<GraphqlAuthorization />
|
||||||
|
</SmartTab>
|
||||||
</SmartTabs>
|
</SmartTabs>
|
||||||
<CollectionsSaveRequest
|
<CollectionsSaveRequest
|
||||||
mode="graphql"
|
mode="graphql"
|
||||||
@@ -296,11 +299,13 @@ import {
|
|||||||
useToast,
|
useToast,
|
||||||
} from "~/helpers/utils/composables"
|
} from "~/helpers/utils/composables"
|
||||||
import {
|
import {
|
||||||
|
gqlAuth$,
|
||||||
gqlHeaders$,
|
gqlHeaders$,
|
||||||
gqlQuery$,
|
gqlQuery$,
|
||||||
gqlResponse$,
|
gqlResponse$,
|
||||||
gqlURL$,
|
gqlURL$,
|
||||||
gqlVariables$,
|
gqlVariables$,
|
||||||
|
setGQLAuth,
|
||||||
setGQLHeaders,
|
setGQLHeaders,
|
||||||
setGQLQuery,
|
setGQLQuery,
|
||||||
setGQLResponse,
|
setGQLResponse,
|
||||||
@@ -353,6 +358,12 @@ useCodemirror(bulkEditor, bulkHeaders, {
|
|||||||
// The functional headers list (the headers actually in the system)
|
// The functional headers list (the headers actually in the system)
|
||||||
const headers = useStream(gqlHeaders$, [], setGQLHeaders) as Ref<GQLHeader[]>
|
const headers = useStream(gqlHeaders$, [], setGQLHeaders) as Ref<GQLHeader[]>
|
||||||
|
|
||||||
|
const auth = useStream(
|
||||||
|
gqlAuth$,
|
||||||
|
{ authType: "none", authActive: true },
|
||||||
|
setGQLAuth
|
||||||
|
)
|
||||||
|
|
||||||
// The UI representation of the headers list (has the empty end header)
|
// The UI representation of the headers list (has the empty end header)
|
||||||
const workingHeaders = ref<Array<GQLHeader & { id: number }>>([
|
const workingHeaders = ref<Array<GQLHeader & { id: number }>>([
|
||||||
{
|
{
|
||||||
@@ -602,12 +613,14 @@ const runQuery = async () => {
|
|||||||
const runHeaders = clone(headers.value)
|
const runHeaders = clone(headers.value)
|
||||||
const runQuery = clone(gqlQueryString.value)
|
const runQuery = clone(gqlQueryString.value)
|
||||||
const runVariables = clone(variableString.value)
|
const runVariables = clone(variableString.value)
|
||||||
|
const runAuth = clone(auth.value)
|
||||||
|
|
||||||
const responseText = await props.conn.runQuery(
|
const responseText = await props.conn.runQuery(
|
||||||
runURL,
|
runURL,
|
||||||
runHeaders,
|
runHeaders,
|
||||||
runQuery,
|
runQuery,
|
||||||
runVariables
|
runVariables,
|
||||||
|
runAuth
|
||||||
)
|
)
|
||||||
const duration = Date.now() - startTime
|
const duration = Date.now() - startTime
|
||||||
|
|
||||||
@@ -623,6 +636,7 @@ const runQuery = async () => {
|
|||||||
query: runQuery,
|
query: runQuery,
|
||||||
headers: runHeaders,
|
headers: runHeaders,
|
||||||
variables: runVariables,
|
variables: runVariables,
|
||||||
|
auth: runAuth,
|
||||||
}),
|
}),
|
||||||
response: response.value,
|
response: response.value,
|
||||||
star: false,
|
star: false,
|
||||||
|
|||||||
@@ -210,6 +210,7 @@ import {
|
|||||||
useToast,
|
useToast,
|
||||||
} from "~/helpers/utils/composables"
|
} from "~/helpers/utils/composables"
|
||||||
import {
|
import {
|
||||||
|
setGQLAuth,
|
||||||
setGQLHeaders,
|
setGQLHeaders,
|
||||||
setGQLQuery,
|
setGQLQuery,
|
||||||
setGQLResponse,
|
setGQLResponse,
|
||||||
@@ -451,6 +452,10 @@ const handleUseHistory = (entry: GQLHistoryEntry) => {
|
|||||||
setGQLQuery(gqlQueryString)
|
setGQLQuery(gqlQueryString)
|
||||||
setGQLVariables(variableString)
|
setGQLVariables(variableString)
|
||||||
setGQLResponse(responseText)
|
setGQLResponse(responseText)
|
||||||
|
setGQLAuth({
|
||||||
|
authType: "none",
|
||||||
|
authActive: true,
|
||||||
|
})
|
||||||
props.conn.reset()
|
props.conn.reset()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -58,6 +58,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from "@nuxtjs/composition-api"
|
import { computed, ref } from "@nuxtjs/composition-api"
|
||||||
import { makeGQLRequest } from "@hoppscotch/data"
|
import { makeGQLRequest } from "@hoppscotch/data"
|
||||||
|
import { cloneDeep } from "lodash"
|
||||||
import { setGQLSession } from "~/newstore/GQLSession"
|
import { setGQLSession } from "~/newstore/GQLSession"
|
||||||
import { GQLHistoryEntry } from "~/newstore/history"
|
import { GQLHistoryEntry } from "~/newstore/history"
|
||||||
|
|
||||||
@@ -79,13 +80,16 @@ const query = computed(() =>
|
|||||||
|
|
||||||
const useEntry = () => {
|
const useEntry = () => {
|
||||||
setGQLSession({
|
setGQLSession({
|
||||||
request: makeGQLRequest({
|
request: cloneDeep(
|
||||||
name: props.entry.request.name,
|
makeGQLRequest({
|
||||||
url: props.entry.request.url,
|
name: props.entry.request.name,
|
||||||
headers: props.entry.request.headers,
|
url: props.entry.request.url,
|
||||||
query: props.entry.request.query,
|
headers: props.entry.request.headers,
|
||||||
variables: props.entry.request.variables,
|
query: props.entry.request.query,
|
||||||
}),
|
variables: props.entry.request.variables,
|
||||||
|
auth: props.entry.request.auth,
|
||||||
|
})
|
||||||
|
),
|
||||||
schema: "",
|
schema: "",
|
||||||
response: props.entry.response,
|
response: props.entry.response,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
GraphQLInterfaceType,
|
GraphQLInterfaceType,
|
||||||
} from "graphql"
|
} from "graphql"
|
||||||
import { distinctUntilChanged, map } from "rxjs/operators"
|
import { distinctUntilChanged, map } from "rxjs/operators"
|
||||||
import { GQLHeader } from "@hoppscotch/data"
|
import { GQLHeader, HoppGQLAuth } from "@hoppscotch/data"
|
||||||
import { sendNetworkRequest } from "./network"
|
import { sendNetworkRequest } from "./network"
|
||||||
|
|
||||||
const GQL_SCHEMA_POLL_INTERVAL = 7000
|
const GQL_SCHEMA_POLL_INTERVAL = 7000
|
||||||
@@ -182,15 +182,36 @@ export class GQLConnection {
|
|||||||
url: string,
|
url: string,
|
||||||
headers: GQLHeader[],
|
headers: GQLHeader[],
|
||||||
query: string,
|
query: string,
|
||||||
variables: string
|
variables: string,
|
||||||
|
auth: HoppGQLAuth
|
||||||
) {
|
) {
|
||||||
const finalHeaders: Record<string, string> = {}
|
const finalHeaders: Record<string, string> = {}
|
||||||
|
|
||||||
|
const parsedVariables = JSON.parse(variables || "{}")
|
||||||
|
|
||||||
|
const params: Record<string, string> = {}
|
||||||
|
|
||||||
|
if (auth.authActive) {
|
||||||
|
if (auth.authType === "basic") {
|
||||||
|
const username = auth.username
|
||||||
|
const password = auth.password
|
||||||
|
finalHeaders.Authorization = `Basic ${btoa(`${username}:${password}`)}`
|
||||||
|
} else if (auth.authType === "bearer" || auth.authType === "oauth-2") {
|
||||||
|
finalHeaders.Authorization = `Bearer ${auth.token}`
|
||||||
|
} else if (auth.authType === "api-key") {
|
||||||
|
const { key, value, addTo } = auth
|
||||||
|
if (addTo === "Headers") {
|
||||||
|
finalHeaders[key] = value
|
||||||
|
} else if (addTo === "Query params") {
|
||||||
|
params[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
headers
|
headers
|
||||||
.filter((item) => item.active && item.key !== "")
|
.filter((item) => item.active && item.key !== "")
|
||||||
.forEach(({ key, value }) => (finalHeaders[key] = value))
|
.forEach(({ key, value }) => (finalHeaders[key] = value))
|
||||||
|
|
||||||
const parsedVariables = JSON.parse(variables || "{}")
|
|
||||||
|
|
||||||
const reqOptions = {
|
const reqOptions = {
|
||||||
method: "post",
|
method: "post",
|
||||||
url,
|
url,
|
||||||
@@ -202,6 +223,9 @@ export class GQLConnection {
|
|||||||
query,
|
query,
|
||||||
variables: parsedVariables,
|
variables: parsedVariables,
|
||||||
}),
|
}),
|
||||||
|
params: {
|
||||||
|
...params,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await sendNetworkRequest(reqOptions)
|
const res = await sendNetworkRequest(reqOptions)
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { distinctUntilChanged, pluck } from "rxjs/operators"
|
import { distinctUntilChanged, pluck } from "rxjs/operators"
|
||||||
import { GQLHeader, HoppGQLRequest, makeGQLRequest } from "@hoppscotch/data"
|
import {
|
||||||
|
GQLHeader,
|
||||||
|
HoppGQLRequest,
|
||||||
|
makeGQLRequest,
|
||||||
|
HoppGQLAuth,
|
||||||
|
} from "@hoppscotch/data"
|
||||||
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
|
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
|
||||||
import { useStream } from "~/helpers/utils/composables"
|
import { useStream } from "~/helpers/utils/composables"
|
||||||
|
|
||||||
@@ -26,6 +31,10 @@ export const defaultGQLSession: GQLSession = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
auth: {
|
||||||
|
authType: "none",
|
||||||
|
authActive: true,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
schema: "",
|
schema: "",
|
||||||
response: "",
|
response: "",
|
||||||
@@ -112,6 +121,14 @@ const dispatchers = defineDispatchers({
|
|||||||
response: newResponse,
|
response: newResponse,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
setAuth(curr: GQLSession, { newAuth }: { newAuth: HoppGQLAuth }) {
|
||||||
|
return {
|
||||||
|
request: {
|
||||||
|
...curr.request,
|
||||||
|
auth: newAuth,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const gqlSessionStore = new DispatchingStore(
|
export const gqlSessionStore = new DispatchingStore(
|
||||||
@@ -223,6 +240,15 @@ export function useGQLRequestName() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setGQLAuth(newAuth: HoppGQLAuth) {
|
||||||
|
gqlSessionStore.dispatch({
|
||||||
|
dispatcher: "setAuth",
|
||||||
|
payload: {
|
||||||
|
newAuth,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const gqlName$ = gqlSessionStore.subject$.pipe(
|
export const gqlName$ = gqlSessionStore.subject$.pipe(
|
||||||
pluck("request", "name"),
|
pluck("request", "name"),
|
||||||
distinctUntilChanged()
|
distinctUntilChanged()
|
||||||
@@ -248,3 +274,8 @@ export const gqlResponse$ = gqlSessionStore.subject$.pipe(
|
|||||||
pluck("response"),
|
pluck("response"),
|
||||||
distinctUntilChanged()
|
distinctUntilChanged()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const gqlAuth$ = gqlSessionStore.subject$.pipe(
|
||||||
|
pluck("request", "auth"),
|
||||||
|
distinctUntilChanged()
|
||||||
|
)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
translateToNewRequest,
|
translateToNewRequest,
|
||||||
HoppGQLRequest,
|
HoppGQLRequest,
|
||||||
translateToGQLRequest,
|
translateToGQLRequest,
|
||||||
|
GQL_REQ_SCHEMA_VERSION,
|
||||||
} from "@hoppscotch/data"
|
} from "@hoppscotch/data"
|
||||||
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
|
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
|
||||||
import { completedRESTResponse$ } from "./RESTSession"
|
import { completedRESTResponse$ } from "./RESTSession"
|
||||||
@@ -83,10 +84,12 @@ export function translateToNewRESTHistory(x: any): RESTHistoryEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function translateToNewGQLHistory(x: any): GQLHistoryEntry {
|
export function translateToNewGQLHistory(x: any): GQLHistoryEntry {
|
||||||
if (x.v === 1) return x
|
if (x.v === 1 && x.request.v === GQL_REQ_SCHEMA_VERSION) return x
|
||||||
|
|
||||||
// Legacy
|
// Legacy
|
||||||
const request = translateToGQLRequest(x)
|
const request = x.request
|
||||||
|
? translateToGQLRequest(x.request)
|
||||||
|
: translateToGQLRequest(x)
|
||||||
const star = x.star ?? false
|
const star = x.star ?? false
|
||||||
const response = x.response ?? ""
|
const response = x.response ?? ""
|
||||||
const updatedOn = x.updatedOn ?? ""
|
const updatedOn = x.updatedOn ?? ""
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hoppscotch/data",
|
"name": "@hoppscotch/data",
|
||||||
"version": "0.4.0",
|
"version": "0.4.1",
|
||||||
"description": "Data Types, Validations and Migrations for Hoppscotch Public Data Structures",
|
"description": "Data Types, Validations and Migrations for Hoppscotch Public Data Structures",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"module": "true",
|
"module": "true",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { HoppGQLRequest, translateToGQLRequest } from "../graphql";
|
import { GQL_REQ_SCHEMA_VERSION, HoppGQLRequest, translateToGQLRequest } from "../graphql";
|
||||||
import { HoppRESTRequest, translateToNewRequest } from "../rest";
|
import { HoppRESTRequest, translateToNewRequest } from "../rest";
|
||||||
|
|
||||||
const CURRENT_COLL_SCHEMA_VER = 1
|
const CURRENT_COLL_SCHEMA_VER = 1
|
||||||
@@ -65,7 +65,7 @@ export function translateToNewRESTCollection(
|
|||||||
export function translateToNewGQLCollection(
|
export function translateToNewGQLCollection(
|
||||||
x: any
|
x: any
|
||||||
): HoppCollection<HoppGQLRequest> {
|
): HoppCollection<HoppGQLRequest> {
|
||||||
if (x.v && x.v === 1) return x
|
if (x.v && x.v === GQL_REQ_SCHEMA_VERSION) return x
|
||||||
|
|
||||||
// Legacy
|
// Legacy
|
||||||
const name = x.name ?? "Untitled"
|
const name = x.name ?? "Untitled"
|
||||||
|
|||||||
43
packages/hoppscotch-data/src/graphql/HoppGQLAuth.ts
Normal file
43
packages/hoppscotch-data/src/graphql/HoppGQLAuth.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
export type HoppGQLAuthNone = {
|
||||||
|
authType: "none"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HoppGQLAuthBasic = {
|
||||||
|
authType: "basic"
|
||||||
|
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HoppGQLAuthBearer = {
|
||||||
|
authType: "bearer"
|
||||||
|
|
||||||
|
token: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HoppGQLAuthOAuth2 = {
|
||||||
|
authType: "oauth-2"
|
||||||
|
|
||||||
|
token: string
|
||||||
|
oidcDiscoveryURL: string
|
||||||
|
authURL: string
|
||||||
|
accessTokenURL: string
|
||||||
|
clientID: string
|
||||||
|
scope: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HoppGQLAuthAPIKey = {
|
||||||
|
authType: "api-key"
|
||||||
|
|
||||||
|
key: string
|
||||||
|
value: string
|
||||||
|
addTo: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HoppGQLAuth = { authActive: boolean } & (
|
||||||
|
| HoppGQLAuthNone
|
||||||
|
| HoppGQLAuthBasic
|
||||||
|
| HoppGQLAuthBearer
|
||||||
|
| HoppGQLAuthOAuth2
|
||||||
|
| HoppGQLAuthAPIKey
|
||||||
|
)
|
||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import { HoppGQLAuth } from "./HoppGQLAuth"
|
||||||
|
|
||||||
|
export * from "./HoppGQLAuth"
|
||||||
|
|
||||||
|
export const GQL_REQ_SCHEMA_VERSION = 2
|
||||||
|
|
||||||
export type GQLHeader = {
|
export type GQLHeader = {
|
||||||
key: string
|
key: string
|
||||||
value: string
|
value: string
|
||||||
@@ -11,10 +17,11 @@ export type HoppGQLRequest = {
|
|||||||
headers: GQLHeader[]
|
headers: GQLHeader[]
|
||||||
query: string
|
query: string
|
||||||
variables: string
|
variables: string
|
||||||
|
auth: HoppGQLAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
export function translateToGQLRequest(x: any): HoppGQLRequest {
|
export function translateToGQLRequest(x: any): HoppGQLRequest {
|
||||||
if (x.v && x.v === 1) return x
|
if (x.v && x.v === GQL_REQ_SCHEMA_VERSION) return x
|
||||||
|
|
||||||
// Old request
|
// Old request
|
||||||
const name = x.name ?? "Untitled"
|
const name = x.name ?? "Untitled"
|
||||||
@@ -22,20 +29,25 @@ export function translateToGQLRequest(x: any): HoppGQLRequest {
|
|||||||
const headers = x.headers ?? []
|
const headers = x.headers ?? []
|
||||||
const query = x.query ?? ""
|
const query = x.query ?? ""
|
||||||
const variables = x.variables ?? []
|
const variables = x.variables ?? []
|
||||||
|
const auth = x.auth ?? {
|
||||||
|
authType: "none",
|
||||||
|
authActive: true,
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
v: 1,
|
v: GQL_REQ_SCHEMA_VERSION,
|
||||||
name,
|
name,
|
||||||
url,
|
url,
|
||||||
headers,
|
headers,
|
||||||
query,
|
query,
|
||||||
variables,
|
variables,
|
||||||
|
auth
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeGQLRequest(x: Omit<HoppGQLRequest, "v">) {
|
export function makeGQLRequest(x: Omit<HoppGQLRequest, "v">): HoppGQLRequest {
|
||||||
return {
|
return {
|
||||||
v: 1,
|
v: GQL_REQ_SCHEMA_VERSION,
|
||||||
...x,
|
...x,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user