fix: graphql authorization headers (#3136)
This commit is contained in:
committed by
Andrew Bastin
parent
7db3c6d290
commit
780dd8a713
@@ -135,12 +135,14 @@
|
||||
<div v-if="authType === 'basic'">
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput
|
||||
:environmentHighlights="false"
|
||||
v-model="basicUsername"
|
||||
:placeholder="t('authorization.username')"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput
|
||||
:environmentHighlights="false"
|
||||
v-model="basicPassword"
|
||||
:placeholder="t('authorization.password')"
|
||||
/>
|
||||
@@ -148,21 +150,37 @@
|
||||
</div>
|
||||
<div v-if="authType === 'bearer'">
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="bearerToken" placeholder="Token" />
|
||||
<SmartEnvInput
|
||||
:environmentHighlights="false"
|
||||
v-model="bearerToken"
|
||||
placeholder="Token"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="authType === 'oauth-2'">
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="oauth2Token" placeholder="Token" />
|
||||
<SmartEnvInput
|
||||
:environmentHighlights="false"
|
||||
v-model="oauth2Token"
|
||||
placeholder="Token"
|
||||
/>
|
||||
</div>
|
||||
<HttpOAuth2Authorization />
|
||||
</div>
|
||||
<div v-if="authType === 'api-key'">
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="apiKey" placeholder="Key" />
|
||||
<SmartEnvInput
|
||||
:environmentHighlights="false"
|
||||
v-model="apiKey"
|
||||
placeholder="Key"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="apiValue" placeholder="Value" />
|
||||
<SmartEnvInput
|
||||
:environmentHighlights="false"
|
||||
v-model="apiValue"
|
||||
placeholder="Value"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center border-b border-dividerLight">
|
||||
<span class="flex items-center">
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<HoppButtonPrimary
|
||||
id="get"
|
||||
name="get"
|
||||
:loading="isLoading"
|
||||
:label="!connected ? t('action.connect') : t('action.disconnect')"
|
||||
class="w-32"
|
||||
@click="onConnectClick"
|
||||
@@ -31,7 +32,12 @@ import { GQLConnection } from "~/helpers/GQLConnection"
|
||||
import { getCurrentStrategyID } from "~/helpers/network"
|
||||
import { useReadonlyStream, useStream } from "@composables/stream"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { gqlHeaders$, gqlURL$, setGQLURL } from "~/newstore/GQLSession"
|
||||
import {
|
||||
gqlAuth$,
|
||||
gqlHeaders$,
|
||||
gqlURL$,
|
||||
setGQLURL,
|
||||
} from "~/newstore/GQLSession"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
@@ -40,13 +46,18 @@ const props = defineProps<{
|
||||
}>()
|
||||
|
||||
const connected = useReadonlyStream(props.conn.connected$, false)
|
||||
const isLoading = useReadonlyStream(props.conn.isLoading$, false)
|
||||
const headers = useReadonlyStream(gqlHeaders$, [])
|
||||
const auth = useReadonlyStream(gqlAuth$, {
|
||||
authType: "none",
|
||||
authActive: true,
|
||||
})
|
||||
|
||||
const url = useStream(gqlURL$, "", setGQLURL)
|
||||
|
||||
const onConnectClick = () => {
|
||||
if (!connected.value) {
|
||||
props.conn.connect(url.value, headers.value as any)
|
||||
props.conn.connect(url.value, headers.value as any, auth.value)
|
||||
|
||||
platform.analytics?.logHoppRequestRunToAnalytics({
|
||||
platform: "graphql-schema",
|
||||
|
||||
@@ -44,6 +44,7 @@ const props = withDefaults(
|
||||
envs?: { key: string; value: string; source: string }[] | null
|
||||
focus?: boolean
|
||||
selectTextOnMount?: boolean
|
||||
environmentHighlights?: boolean
|
||||
readonly?: boolean
|
||||
}>(),
|
||||
{
|
||||
@@ -53,6 +54,7 @@ const props = withDefaults(
|
||||
envs: null,
|
||||
focus: false,
|
||||
readonly: false,
|
||||
environmentHighlights: true,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -142,7 +144,7 @@ const initView = (el: any) => {
|
||||
tooltips({
|
||||
position: "absolute",
|
||||
}),
|
||||
envTooltipPlugin,
|
||||
props.environmentHighlights ? envTooltipPlugin : [],
|
||||
placeholderExt(props.placeholder),
|
||||
EditorView.domEventHandlers({
|
||||
paste(ev) {
|
||||
|
||||
@@ -99,7 +99,7 @@ export class GQLConnection {
|
||||
|
||||
private timeoutSubscription: any
|
||||
|
||||
public connect(url: string, headers: GQLHeader[]) {
|
||||
public connect(url: string, headers: GQLHeader[], auth: HoppGQLAuth) {
|
||||
if (this.connected$.value) {
|
||||
throw new Error(
|
||||
"A connection is already running. Close it before starting another."
|
||||
@@ -110,7 +110,7 @@ export class GQLConnection {
|
||||
this.connected$.next(true)
|
||||
|
||||
const poll = async () => {
|
||||
await this.getSchema(url, headers)
|
||||
await this.getSchema(url, headers, auth)
|
||||
this.timeoutSubscription = setTimeout(() => {
|
||||
poll()
|
||||
}, GQL_SCHEMA_POLL_INTERVAL)
|
||||
@@ -135,7 +135,11 @@ export class GQLConnection {
|
||||
this.schema$.next(null)
|
||||
}
|
||||
|
||||
private async getSchema(url: string, headers: GQLHeader[]) {
|
||||
private async getSchema(
|
||||
url: string,
|
||||
reqHeaders: GQLHeader[],
|
||||
auth: HoppGQLAuth
|
||||
) {
|
||||
try {
|
||||
this.isLoading$.next(true)
|
||||
|
||||
@@ -143,10 +147,38 @@ export class GQLConnection {
|
||||
query: getIntrospectionQuery(),
|
||||
})
|
||||
|
||||
const headers = reqHeaders.filter((x) => x.active && x.key !== "")
|
||||
|
||||
// TODO: Support a better b64 implementation than btoa ?
|
||||
if (auth.authType === "basic") {
|
||||
const username = auth.username
|
||||
const password = auth.password
|
||||
|
||||
headers.push({
|
||||
active: true,
|
||||
key: "Authorization",
|
||||
value: `Basic ${btoa(`${username}:${password}`)}`,
|
||||
})
|
||||
} else if (auth.authType === "bearer" || auth.authType === "oauth-2") {
|
||||
headers.push({
|
||||
active: true,
|
||||
key: "Authorization",
|
||||
value: `Bearer ${auth.token}`,
|
||||
})
|
||||
} else if (auth.authType === "api-key") {
|
||||
const { key, value, addTo } = auth
|
||||
|
||||
if (addTo === "Headers") {
|
||||
headers.push({
|
||||
active: true,
|
||||
key,
|
||||
value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const finalHeaders: Record<string, string> = {}
|
||||
headers
|
||||
.filter((x) => x.active && x.key !== "")
|
||||
.forEach((x) => (finalHeaders[x.key] = x.value))
|
||||
headers.forEach((x) => (finalHeaders[x.key] = x.value))
|
||||
|
||||
const reqOptions = {
|
||||
method: "POST",
|
||||
|
||||
@@ -275,7 +275,4 @@ export const gqlResponse$ = gqlSessionStore.subject$.pipe(
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
export const gqlAuth$ = gqlSessionStore.subject$.pipe(
|
||||
pluck("request", "auth"),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
export const gqlAuth$ = gqlSessionStore.subject$.pipe(pluck("request", "auth"))
|
||||
|
||||
@@ -47,6 +47,8 @@ import {
|
||||
loadTabsFromPersistedState,
|
||||
persistableTabState,
|
||||
} from "~/helpers/rest/tab"
|
||||
import { debounceTime } from "rxjs"
|
||||
import { gqlSessionStore, setGQLSession } from "./GQLSession"
|
||||
|
||||
function checkAndMigrateOldSettings() {
|
||||
if (window.localStorage.getItem("selectedEnvIndex")) {
|
||||
@@ -333,12 +335,35 @@ export function setupRESTTabsPersistence() {
|
||||
)
|
||||
}
|
||||
|
||||
// temporary persistence for GQL session
|
||||
export function setupGQLPersistence() {
|
||||
try {
|
||||
const state = window.localStorage.getItem("gqlState")
|
||||
if (state) {
|
||||
const data = JSON.parse(state)
|
||||
data["schema"] = ""
|
||||
data["response"] = ""
|
||||
setGQLSession(data)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`Failed parsing persisted GraphQL state, state:`,
|
||||
window.localStorage.getItem("gqlState")
|
||||
)
|
||||
}
|
||||
|
||||
gqlSessionStore.subject$.pipe(debounceTime(500)).subscribe((state) => {
|
||||
window.localStorage.setItem("gqlState", JSON.stringify(state))
|
||||
})
|
||||
}
|
||||
|
||||
export function setupLocalPersistence() {
|
||||
checkAndMigrateOldSettings()
|
||||
|
||||
setupLocalStatePersistence()
|
||||
setupSettingsPersistence()
|
||||
setupRESTTabsPersistence()
|
||||
setupGQLPersistence()
|
||||
setupHistoryPersistence()
|
||||
setupCollectionsPersistence()
|
||||
setupGlobalEnvsPersistence()
|
||||
|
||||
@@ -14,12 +14,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, watch } from "vue"
|
||||
import { useReadonlyStream } from "@composables/stream"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { usePageHead } from "@composables/head"
|
||||
import { startPageProgress, completePageProgress } from "@modules/loadingbar"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { GQLConnection } from "@helpers/GQLConnection"
|
||||
import { computed, onBeforeUnmount } from "vue"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
@@ -28,16 +26,10 @@ usePageHead({
|
||||
})
|
||||
|
||||
const gqlConn = new GQLConnection()
|
||||
const isLoading = useReadonlyStream(gqlConn.isLoading$, false)
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (gqlConn.connected$.value) {
|
||||
gqlConn.disconnect()
|
||||
}
|
||||
})
|
||||
|
||||
watch(isLoading, () => {
|
||||
if (isLoading.value) startPageProgress()
|
||||
else completePageProgress()
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user