Browse Source

fix: graphql authorization headers (#3136)

Anwarul Islam 1 year ago
parent
commit
780dd8a713

+ 22 - 4
packages/hoppscotch-common/src/components/graphql/Authorization.vue

@@ -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">

+ 13 - 2
packages/hoppscotch-common/src/components/graphql/Request.vue

@@ -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",

+ 3 - 1
packages/hoppscotch-common/src/components/smart/EnvInput.vue

@@ -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) {

+ 38 - 6
packages/hoppscotch-common/src/helpers/GQLConnection.ts

@@ -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",

+ 1 - 4
packages/hoppscotch-common/src/newstore/GQLSession.ts

@@ -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"))

+ 25 - 0
packages/hoppscotch-common/src/newstore/localpersistence.ts

@@ -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()

+ 2 - 10
packages/hoppscotch-common/src/pages/graphql.vue

@@ -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>