diff --git a/packages/hoppscotch-common/src/components/http/OAuth2Authorization.vue b/packages/hoppscotch-common/src/components/http/OAuth2Authorization.vue index fbd157170..ee9d2f1c0 100644 --- a/packages/hoppscotch-common/src/components/http/OAuth2Authorization.vue +++ b/packages/hoppscotch-common/src/components/http/OAuth2Authorization.vue @@ -3,14 +3,25 @@
- +
- +
@@ -44,6 +55,7 @@ import { useToast } from "@composables/toast" import { tokenRequest } from "~/helpers/oauth" import { getCombinedEnvVariables } from "~/helpers/preRequest" import * as E from "fp-ts/Either" +import { computed } from "vue" const t = useI18n() const toast = useToast() @@ -66,10 +78,16 @@ watch( ) const oidcDiscoveryURL = pluckRef(auth, "oidcDiscoveryURL") +const hasOIDCURL = computed(() => { + return oidcDiscoveryURL.value +}) const authURL = pluckRef(auth, "authURL") const accessTokenURL = pluckRef(auth, "accessTokenURL") +const hasAccessTokenOrAuthURL = computed(() => { + return accessTokenURL.value || authURL.value +}) const clientID = pluckRef(auth, "clientID") @@ -88,13 +106,11 @@ function translateTokenRequestError(error: string) { } const handleAccessTokenRequest = async () => { - if ( - oidcDiscoveryURL.value === "" && - (authURL.value === "" || accessTokenURL.value === "") - ) { + if (!oidcDiscoveryURL.value && !(authURL.value || accessTokenURL.value)) { toast.error(`${t("error.incomplete_config_urls")}`) return } + const envs = getCombinedEnvVariables() const envVars = [...envs.selected, ...envs.global] diff --git a/packages/hoppscotch-common/src/helpers/oauth.ts b/packages/hoppscotch-common/src/helpers/oauth.ts index db52ef5f0..ce45432be 100644 --- a/packages/hoppscotch-common/src/helpers/oauth.ts +++ b/packages/hoppscotch-common/src/helpers/oauth.ts @@ -3,41 +3,20 @@ import { PersistenceService } from "~/services/persistence" import * as E from "fp-ts/Either" import { z } from "zod" +import { InterceptorService } from "~/services/interceptor.service" +import { ExtensionInterceptorService } from "~/platform/std/interceptors/extension" + +import { AxiosRequestConfig } from "axios" +import { until } from "@vueuse/core" const redirectUri = `${window.location.origin}/oauth` +const interceptorService = getService(InterceptorService) const persistenceService = getService(PersistenceService) +const extensionService = getService(ExtensionInterceptorService) // GENERAL HELPER FUNCTIONS -/** - * Makes a POST request and parse the response as JSON - * - * @param {String} url - The resource - * @param {Object} params - Configuration options - * @returns {Object} - */ - -const sendPostRequest = async (url: string, params: Record) => { - const body = Object.keys(params) - .map((key) => `${key}=${params[key]}`) - .join("&") - const options = { - method: "POST", - headers: { - "Content-type": "application/x-www-form-urlencoded; charset=UTF-8", - }, - body, - } - try { - const response = await fetch(url, options) - const data = await response.json() - return E.right(data) - } catch (e) { - return E.left("AUTH_TOKEN_REQUEST_FAILED") - } -} - /** * Parse a query string into an object * @@ -71,9 +50,16 @@ const getTokenConfiguration = async (endpoint: string) => { }, } try { - const response = await fetch(endpoint, options) - const config = await response.json() - return E.right(config) + const res = await runRequestThroughInterceptor({ + url: endpoint, + ...options, + }) + + if (E.isLeft(res)) { + return E.left("OIDC_DISCOVERY_FAILED") + } + + return E.right(JSON.parse(res.right)) } catch (e) { return E.left("OIDC_DISCOVERY_FAILED") } @@ -166,8 +152,7 @@ const tokenRequest = async ({ clientSecret, scope, }: TokenRequestParams) => { - // Check oauth configuration - if (oidcDiscoveryUrl !== "") { + if (oidcDiscoveryUrl) { const res = await getTokenConfiguration(oidcDiscoveryUrl) const OIDCConfigurationSchema = z.object({ @@ -269,17 +254,19 @@ const handleOAuthRedirect = async () => { } // Exchange the authorization code for an access token - const tokenResponse: E.Either = await sendPostRequest( - tokenEndpoint, - { + const tokenResponse = await runRequestThroughInterceptor({ + url: tokenEndpoint, + data: JSON.stringify({ grant_type: "authorization_code", code: queryParams.code, client_id: clientID, client_secret: clientSecret, redirect_uri: redirectUri, code_verifier: codeVerifier, - } - ) + }), + method: "POST", + headers: {}, + }) // Clean these up since we don't need them anymore clearPKCEState() @@ -293,7 +280,7 @@ const handleOAuthRedirect = async () => { }) const parsedTokenResponse = withAccessTokenSchema.safeParse( - tokenResponse.right + JSON.parse(tokenResponse.right) ) return parsedTokenResponse.success @@ -309,4 +296,20 @@ const clearPKCEState = () => { persistenceService.removeLocalConfig("client_secret") } +async function runRequestThroughInterceptor(config: AxiosRequestConfig) { + const res = await interceptorService.runRequest(config).response + + if (E.isLeft(res)) { + return E.left("REQUEST_FAILED") + } + + // convert ArrayBuffer to string + if (!(res.right.data instanceof ArrayBuffer)) { + return E.left("REQUEST_FAILED") + } + + const data = new TextDecoder().decode(res.right.data).replace(/\0+$/, "") + return E.right(data) +} + export { tokenRequest, handleOAuthRedirect } diff --git a/packages/hoppscotch-common/src/platform/std/interceptors/extension.ts b/packages/hoppscotch-common/src/platform/std/interceptors/extension.ts index 033e23865..a6095c0bc 100644 --- a/packages/hoppscotch-common/src/platform/std/interceptors/extension.ts +++ b/packages/hoppscotch-common/src/platform/std/interceptors/extension.ts @@ -13,6 +13,7 @@ import { browserIsChrome, browserIsFirefox } from "~/helpers/utils/userAgent" import SettingsExtension from "~/components/settings/Extension.vue" import InterceptorsExtensionSubtitle from "~/components/interceptors/ExtensionSubtitle.vue" import InterceptorsErrorPlaceholder from "~/components/interceptors/ErrorPlaceholder.vue" +import { until } from "@vueuse/core" export const defineSubscribableObject = (obj: T) => { const proxyObject = { @@ -206,6 +207,14 @@ export class ExtensionInterceptorService private async runRequestOnExtension( req: AxiosRequestConfig ): RequestRunResult["response"] { + // wait for the extension to resolve + await until(this.extensionStatus).toMatch( + (status) => status !== "waiting", + { + timeout: 1000, + } + ) + const extensionHook = window.__POSTWOMAN_EXTENSION_HOOK__ if (!extensionHook) { return E.left({