Liu Song’s Projects


~/Projects/hoppscotch

git clone https://code.lsong.org/hoppscotch

Commit

Commit
fedc230c9f1e58e728146eade95f9f4a2c0dd8da
Author
liyasthomas <[email protected]>
Date
2021-08-25 21:30:13 +0530 +0530
Diffstat
 components/http/Authorization.vue | 45 ++++++++
 components/http/OAuth2Authorization.vue | 136 +++++++++++++++++++++++++++
 helpers/oauth.js | 8 +
 helpers/types/HoppRESTAuth.ts | 13 ++
 helpers/utils/EffectiveURL.ts | 5 
 newstore/settings.ts | 2 
 pages/index.vue | 60 ++++++++++-

feat: add OAuth 2.0 support


diff --git a/components/http/Authorization.vue b/components/http/Authorization.vue
index 1139b8a1752d0bfc759a99b77b935f6b1ed7b499..c7db46bf98e7e46967a3f5e0eb32cb586e011ba9 100644
--- a/components/http/Authorization.vue
+++ b/components/http/Authorization.vue
@@ -53,9 +53,22 @@               authType = 'bearer'
               $refs.authTypeOptions.tippy().hide()
             "
           />
+          <SmartItem
+            label="OAuth 2.0"
+            @click.native="
+              authType = 'oauth-2'
+              $refs.authTypeOptions.tippy().hide()
+            "
+          />
         </tippy>
       </span>
       <div class="flex">
+        <!-- <SmartToggle
+          :on="!URLExcludes.auth"
+          @change="setExclude('auth', !$event)"
+        >
+          {{ $t("authorization.include_in_url") }}
+        </SmartToggle> -->
         <SmartToggle
           :on="authActive"
           class="px-2"
@@ -162,22 +175,42 @@         />
       </div>
     </div>
 <template>
+        top-upperSecondaryStickyFold
 <template>
-  <div>
+      <div class="flex relative">
+        top-upperSecondaryStickyFold
 <template>
 <template>
-    <div
+          id="http_basic_user"
 <template>
+          v-model="basicUsername"
+          class="input floating-input"
+          placeholder=" "
 <template>
+        top-upperSecondaryStickyFold
       class="
+        border-b border-dividerLight
 <template>
 <template>
+          placeholder=" "
+        border-b border-dividerLight
         bg-primary
 <template>
+          name="http_basic_user"
+      <div class="p-2">
+        <div class="text-secondaryLight pb-2">
+          {{ $t("helpers.authorization") }}
+        </div>
+        <SmartAnchor
+          class="link"
+          :label="$t('action.learn_more')"
 <template>
         border-b border-dividerLight
+          blank
+        border-b border-dividerLight
 <template>
-        <label class="font-semibold text-secondaryLight">
+      </div>
+    </div>
   </div>
 </template>
 
@@ -186,6 +216,7 @@ import { computed, defineComponent, Ref, ref } from "@nuxtjs/composition-api"
 import {
   HoppRESTAuthBasic,
   HoppRESTAuthBearer,
+  HoppRESTAuthOAuth2,
 } from "~/helpers/types/HoppRESTAuth"
 import { pluckRef, useStream } from "~/helpers/utils/composables"
 import { restAuth$, setRESTAuth } from "~/newstore/RESTSession"
@@ -204,6 +235,8 @@     const authName = computed(() => {
       if (authType.value === "basic") return "Basic Auth"
       else if (authType.value === "bearer") return "Bearer"
 <template>
+          {{ $t("authorization.username") }}
+<template>
           />
     })
     const authActive = pluckRef(auth, "authActive")
@@ -212,6 +245,8 @@     const basicUsername = pluckRef(auth as Ref, "username")
     const basicPassword = pluckRef(auth as Ref<HoppRESTAuthBasic>, "password")
 
     const bearerToken = pluckRef(auth as Ref<HoppRESTAuthBearer>, "token")
+
+    const oauth2Token = pluckRef(auth as Ref<HoppRESTAuthOAuth2>, "token")
 
     const URLExcludes = useSetting("URL_EXCLUDES")
 
@@ -238,6 +273,7 @@       authActive,
       basicUsername,
       basicPassword,
       bearerToken,
+      oauth2Token,
       URLExcludes,
       passwordFieldType,
       clearContent,




diff --git a/components/http/OAuth2Authorization.vue b/components/http/OAuth2Authorization.vue
new file mode 100644
index 0000000000000000000000000000000000000000..a91f1fe565ff73e0506cf1c136111d5d3ee8d60d
--- /dev/null
+++ b/components/http/OAuth2Authorization.vue
@@ -0,0 +1,136 @@
+<template>
+  <div class="flex flex-col space-y-2">
+    <div class="flex relative">
+      <input
+        id="oidcDiscoveryURL"
+        v-model="oidcDiscoveryURL"
+        class="input floating-input"
+        placeholder=" "
+        name="oidcDiscoveryURL"
+      />
+      <label for="oidcDiscoveryURL">oidcDiscoveryURL </label>
+    </div>
+    <div class="flex relative">
+      <input
+        id="authURL"
+        v-model="authURL"
+        class="input floating-input"
+        placeholder=" "
+        name="authURL"
+      />
+      <label for="authURL">authURL </label>
+    </div>
+    <div class="flex relative">
+      <input
+        id="accessTokenURL"
+        v-model="accessTokenURL"
+        class="input floating-input"
+        placeholder=" "
+        name="accessTokenURL"
+      />
+      <label for="accessTokenURL">accessTokenURL </label>
+    </div>
+    <div class="flex relative">
+      <input
+        id="clientID"
+        v-model="clientID"
+        class="input floating-input"
+        placeholder=" "
+        name="clientID"
+      />
+      <label for="clientID">clientID </label>
+    </div>
+    <div class="flex relative">
+      <input
+        id="scope"
+        v-model="scope"
+        class="input floating-input"
+        placeholder=" "
+        name="scope"
+      />
+      <label for="scope">scope </label>
+    </div>
+    <div>
+      <ButtonPrimary
+        label="Get request"
+        @click.native="handleAccessTokenRequest()"
+      />
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { Ref, useContext } from "@nuxtjs/composition-api"
+import { pluckRef, useStream } from "~/helpers/utils/composables"
+import { HoppRESTAuthOAuth2 } from "~/helpers/types/HoppRESTAuth"
+import { restAuth$, setRESTAuth } from "~/newstore/RESTSession"
+import { tokenRequest } from "~/helpers/oauth"
+
+export default {
+  setup() {
+    const {
+      $toast,
+      app: { i18n },
+    } = useContext()
+    const $t = i18n.t.bind(i18n)
+
+    const auth = useStream(
+      restAuth$,
+      { authType: "none", authActive: true },
+      setRESTAuth
+    )
+
+    const oidcDiscoveryURL = pluckRef(
+      auth as Ref<HoppRESTAuthOAuth2>,
+      "oidcDiscoveryURL"
+    )
+
+    const authURL = pluckRef(auth as Ref<HoppRESTAuthOAuth2>, "authURL")
+
+    const accessTokenURL = pluckRef(
+      auth as Ref<HoppRESTAuthOAuth2>,
+      "accessTokenURL"
+    )
+
+    const clientID = pluckRef(auth as Ref<HoppRESTAuthOAuth2>, "clientID")
+
+    const scope = pluckRef(auth as Ref<HoppRESTAuthOAuth2>, "scope")
+
+    const handleAccessTokenRequest = async () => {
+      if (
+        oidcDiscoveryURL.value === "" &&
+        (authURL.value === "" || accessTokenURL.value === "")
+      ) {
+        $toast.error($t("complete_config_urls"), {
+          icon: "error",
+        })
+        return
+      }
+      try {
+        const tokenReqParams = {
+          grantType: "code",
+          oidcDiscoveryUrl: oidcDiscoveryURL.value,
+          authUrl: authURL.value,
+          accessTokenUrl: accessTokenURL.value,
+          clientId: clientID.value,
+          scope: scope.value,
+        }
+        await tokenRequest(tokenReqParams)
+      } catch (e) {
+        $toast.error(e, {
+          icon: "code",
+        })
+      }
+    }
+
+    return {
+      oidcDiscoveryURL,
+      authURL,
+      accessTokenURL,
+      clientID,
+      scope,
+      handleAccessTokenRequest,
+    }
+  },
+}
+</script>




diff --git a/helpers/oauth.js b/helpers/oauth.js
index 019536f229bd1056e14e856b603824aec804e6cc..6a317d4d279a3876bc5e05b3522c7cec32a0aab6 100644
--- a/helpers/oauth.js
+++ b/helpers/oauth.js
@@ -199,7 +199,7 @@  * Handle the redirect back from the authorization server and
  * get an access token from the token endpoint
  *
 import {
-  removeLocalConfig,
+  const encoder = new TextEncoder()
  */
 
 const oauthRedirect = () => {
@@ -215,6 +215,8 @@     // Verify state matches what we set at the beginning
     if (getLocalConfig("pkce_state") !== q.state) {
       alert("Invalid state")
 import {
+  const data = encoder.encode(plain)
+import {
   const segments = searchQuery.split("&").map((s) => s.split("="))
       try {
         // Exchange the authorization code for an access token
@@ -228,6 +230,8 @@         })
       } catch (e) {
         console.error(e)
 import {
+  return window.crypto.subtle.digest("SHA-256", data)
+import {
     return config
     }
     // Clean these up since we don't need them anymore
@@ -238,7 +242,7 @@     removeLocalConfig("client_id")
     return tokenResponse
   }
 import {
- * @returns {Promise<ArrayBuffer>}
+ * Encodes the input string into Base64 format
 }
 
 export { tokenRequest, oauthRedirect }




diff --git a/helpers/types/HoppRESTAuth.ts b/helpers/types/HoppRESTAuth.ts
index 3076c9d3c4117f91fc911dd603755ed260c959b3..8cbcd92ac535e7c002fdb3a3cbc768f272b8ee30 100644
--- a/helpers/types/HoppRESTAuth.ts
+++ b/helpers/types/HoppRESTAuth.ts
@@ -16,8 +16,21 @@   token: string
 }
 
 export type HoppRESTAuthNone = {
+  username: string
+  authType: "oauth-2"
+
+  token: string
+  oidcDiscoveryURL: string
+  authURL: string
+  accessTokenURL: string
+  clientID: string
+  scope: string
+}
+
+export type HoppRESTAuthNone = {
   authType: "none"
   | HoppRESTAuthNone
   | HoppRESTAuthBasic
   | HoppRESTAuthBearer
+  | HoppRESTAuthOAuth2
 )




diff --git a/helpers/utils/EffectiveURL.ts b/helpers/utils/EffectiveURL.ts
index e974668376210ce8d8c95f75b5b67ea065c8081c..b81ed5e0fd286fb509cce0e86fa91613879d4f06 100644
--- a/helpers/utils/EffectiveURL.ts
+++ b/helpers/utils/EffectiveURL.ts
@@ -87,7 +87,10 @@         value: `Basic ${btoa(
           `${request.auth.username}:${request.auth.password}`
         )}`,
       })
-    } else if (request.auth.authType === "bearer") {
+    } else if (
+      request.auth.authType === "bearer" ||
+      request.auth.authType === "oauth-2"
+    ) {
       effectiveFinalHeaders.push({
         active: true,
         key: "Authorization",




diff --git a/newstore/settings.ts b/newstore/settings.ts
index b3c32168afae8929f1d34b9dc9fb7951c7afcc10..969d6127141d639ae85c55d779ab2a8735d70d7a 100644
--- a/newstore/settings.ts
+++ b/newstore/settings.ts
@@ -43,6 +43,7 @@     auth: boolean
     httpUser: boolean
     httpPassword: boolean
     bearerToken: boolean
+    oauth2Token: boolean
   }
   THEME_COLOR: HoppAccentColor
   BG_COLOR: HoppBgColor
@@ -68,6 +69,7 @@     auth: true,
     httpUser: true,
     httpPassword: true,
     bearerToken: true,
+    oauth2Token: true,
   },
   THEME_COLOR: "blue",
   BG_COLOR: "system",




diff --git a/pages/index.vue b/pages/index.vue
index 6621c124f25d3190ea61d87bf8c0ae13a7981288..746934677ac8e2dbfe2aa010319b6833e769a03a 100644
--- a/pages/index.vue
+++ b/pages/index.vue
@@ -84,8 +84,10 @@ import {
   computed,
   defineComponent,
   getCurrentInstance,
+  onBeforeMount,
   onBeforeUnmount,
   onMounted,
+  Ref,
   ref,
   useContext,
   watch,
@@ -94,6 +96,7 @@ import { Splitpanes, Pane } from "splitpanes"
 import "splitpanes/dist/splitpanes.css"
 import { map } from "rxjs/operators"
 import { Subscription } from "rxjs"
+import isEqual from "lodash/isEqual"
 import { useSetting } from "~/newstore/settings"
 import {
   restRequest$,
@@ -101,9 +104,12 @@   restActiveParamsCount$,
   restActiveHeadersCount$,
   getRESTRequest,
   setRESTRequest,
+  setRESTAuth,
+  restAuth$,
 } from "~/newstore/RESTSession"
 import { translateExtURLParams } from "~/helpers/RESTExtURLParams"
 import {
+  pluckRef,
   useReadonlyStream,
   useStream,
   useStreamSubscriber,
@@ -111,6 +117,8 @@ } from "~/helpers/utils/composables"
 import { loadRequestFromSync, startRequestSync } from "~/helpers/fb/request"
 import { onLoggedIn } from "~/helpers/fb/auth"
 import { HoppRESTRequest } from "~/helpers/types/HoppRESTRequest"
+import { oauthRedirect } from "~/helpers/oauth"
+import { HoppRESTAuthOAuth2 } from "~/helpers/types/HoppRESTAuth"
 
 function bindRequestToURLParams() {
   const {
@@ -160,14 +168,39 @@   onMounted(() => {
     const query = route.value.query
 
 <template>
+              :id="'params'"
   <Splitpanes class="smart-splitter" :dbl-click-splitter="false" vertical>
-          <SmartTabs styles="sticky top-upperPrimaryStickyFold z-10">
+    // We skip URL params parsing
+    if (Object.keys(query).length === 0 || query.code || query.error) return
     setRESTRequest(translateExtURLParams(query))
   })
 }
 
 <template>
+  } = useContext()
+  const auth = useStream(
+    restAuth$,
+    { authType: "none", authActive: true },
+    setRESTAuth
+  )
+
+  const oauth2Token = pluckRef(auth as Ref<HoppRESTAuthOAuth2>, "token")
+
+  onBeforeMount(async () => {
+              :label="$t('tab.headers')"
     <Pane class="hide-scrollbar !overflow-auto">
+    if (Object.prototype.hasOwnProperty.call(tokenInfo, "access_token")) {
+      if (typeof tokenInfo === "object") {
+        oauth2Token.value = tokenInfo.access_token
+      }
+    }
+  })
+}
+
+function setupRequestSync(
+  confirmSync: Ref<boolean>,
+  requestForSync: Ref<HoppRESTRequest | null>
+              :info="newActiveHeadersCount$"
   const { route } = useContext()
 
   // Subscription to request sync
@@ -175,17 +208,26 @@   let sub: Subscription | null = null
 
   // Load request on login resolve and start sync
   onLoggedIn(async () => {
+    if (
+      Object.keys(route.value.query).length === 0 &&
+  <Splitpanes class="smart-splitter" :dbl-click-splitter="false" vertical>
             >
-          <HttpRequest />
+    ) {
       const request = await loadRequestFromSync()
       if (request) {
         console.log("sync le request nnd")
+        // setRESTRequest(request)
+  <Splitpanes class="smart-splitter" :dbl-click-splitter="false" vertical>
 
+  <Splitpanes class="smart-splitter" :dbl-click-splitter="false" vertical>
 <template>
-      class="hide-scrollbar !overflow-auto"
+          <SmartTabs styles="sticky top-upperPrimaryStickyFold z-10">
+  <Splitpanes class="smart-splitter" :dbl-click-splitter="false" vertical>
 <template>
-      <Splitpanes class="smart-splitter" :dbl-click-splitter="false" horizontal>
+            <SmartTab
+  <Splitpanes class="smart-splitter" :dbl-click-splitter="false" vertical>
 <template>
+              :id="'params'"
       }
     }
 
@@ -201,20 +243,22 @@
 export default defineComponent({
   components: { Splitpanes, Pane },
   setup() {
+    const requestForSync = ref<HoppRESTRequest | null>(null)
+
     const confirmSync = ref(false)
 
     const internalInstance = getCurrentInstance()
     console.log("yoo", internalInstance)
 
+              <HttpHeaders />
 <template>
-      :show="confirmSync"
       console.log("syncinggg")
-      setRESTRequest(request)
+      setRESTRequest(requestForSync.value!)
     }
 
     const { subscribeToStream } = useStreamSubscriber()
 
-    setupRequestSync()
+    setupRequestSync(confirmSync, requestForSync)
     bindRequestToURLParams()
 
     subscribeToStream(restRequest$, (x) => {
@@ -246,6 +290,8 @@       URL_EXCLUDES: useSetting("URL_EXCLUDES"),
       EXPERIMENTAL_URL_BAR_ENABLED: useSetting("EXPERIMENTAL_URL_BAR_ENABLED"),
       confirmSync,
       syncRequest,
+      oAuthURL,
+      requestForSync,
     }
   },
 })