feat: oauth revamp + support for multiple grant types in oauth (#3885)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
>
|
||||
<template #body>
|
||||
<HoppSmartTabs
|
||||
v-model="selectedOptionTab"
|
||||
v-model="activeTab"
|
||||
styles="sticky overflow-x-auto flex-shrink-0 bg-primary top-0 z-10 !-py-4"
|
||||
render-inactive-tabs
|
||||
>
|
||||
@@ -16,7 +16,6 @@
|
||||
<HttpHeaders
|
||||
v-model="editableCollection"
|
||||
:is-collection-property="true"
|
||||
@change-tab="changeOptionTab"
|
||||
/>
|
||||
<div
|
||||
class="bg-bannerInfo px-4 py-2 flex items-center sticky bottom-0"
|
||||
@@ -34,6 +33,7 @@
|
||||
:is-collection-property="true"
|
||||
:is-root-collection="editingProperties?.isRootCollection"
|
||||
:inherited-properties="editingProperties?.inheritedProperties"
|
||||
:source="source"
|
||||
/>
|
||||
<div
|
||||
class="bg-bannerInfo px-4 py-2 flex items-center sticky bottom-0"
|
||||
@@ -64,16 +64,20 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { watch, ref } from "vue"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { HoppCollection, HoppRESTAuth, HoppRESTHeaders } from "@hoppscotch/data"
|
||||
import { RESTOptionTabs } from "../http/RequestOptions.vue"
|
||||
import { clone } from "lodash-es"
|
||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||
import { PersistenceService } from "~/services/persistence"
|
||||
import { useService } from "dioc/vue"
|
||||
import { ref, watch } from "vue"
|
||||
|
||||
import { useVModel } from "@vueuse/core"
|
||||
|
||||
const persistenceService = useService(PersistenceService)
|
||||
const t = useI18n()
|
||||
|
||||
type EditingProperties = {
|
||||
export type EditingProperties = {
|
||||
collection: Partial<HoppCollection> | null
|
||||
isRootCollection: boolean
|
||||
path: string
|
||||
@@ -85,6 +89,8 @@ const props = withDefaults(
|
||||
show: boolean
|
||||
loadingState: boolean
|
||||
editingProperties: EditingProperties | null
|
||||
source: "REST" | "GraphQL"
|
||||
modelValue: string
|
||||
}>(),
|
||||
{
|
||||
show: false,
|
||||
@@ -99,6 +105,7 @@ const emit = defineEmits<{
|
||||
newCollection: Omit<EditingProperties, "inheritedProperties">
|
||||
): void
|
||||
(e: "hide-modal"): void
|
||||
(e: "update:modelValue"): void
|
||||
}>()
|
||||
|
||||
const editableCollection = ref<{
|
||||
@@ -112,11 +119,27 @@ const editableCollection = ref<{
|
||||
},
|
||||
})
|
||||
|
||||
const selectedOptionTab = ref("headers")
|
||||
watch(
|
||||
editableCollection,
|
||||
(updatedEditableCollection) => {
|
||||
if (props.show) {
|
||||
persistenceService.setLocalConfig(
|
||||
"unsaved_collection_properties",
|
||||
JSON.stringify(<EditingProperties>{
|
||||
collection: updatedEditableCollection,
|
||||
isRootCollection: props.editingProperties?.isRootCollection,
|
||||
path: props.editingProperties?.path,
|
||||
inheritedProperties: props.editingProperties?.inheritedProperties,
|
||||
})
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
)
|
||||
|
||||
const changeOptionTab = (tab: RESTOptionTabs) => {
|
||||
selectedOptionTab.value = tab
|
||||
}
|
||||
const activeTab = useVModel(props, "modelValue", emit)
|
||||
|
||||
watch(
|
||||
() => props.show,
|
||||
@@ -136,6 +159,8 @@ watch(
|
||||
authActive: false,
|
||||
},
|
||||
}
|
||||
|
||||
persistenceService.removeLocalConfig("unsaved_collection_properties")
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -152,9 +177,11 @@ const saveEditedCollection = () => {
|
||||
isRootCollection: props.editingProperties.isRootCollection,
|
||||
}
|
||||
emit("set-collection-properties", collection as EditingProperties)
|
||||
persistenceService.removeLocalConfig("unsaved_collection_properties")
|
||||
}
|
||||
|
||||
const hideModal = () => {
|
||||
persistenceService.removeLocalConfig("unsaved_collection_properties")
|
||||
emit("hide-modal")
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -146,8 +146,10 @@
|
||||
@hide-modal="displayModalImportExport(false)"
|
||||
/>
|
||||
<CollectionsProperties
|
||||
v-model="collectionPropertiesModalActiveTab"
|
||||
:show="showModalEditProperties"
|
||||
:editing-properties="editingProperties"
|
||||
source="GraphQL"
|
||||
@hide-modal="displayModalEditProperties(false)"
|
||||
@set-collection-properties="setCollectionProperties"
|
||||
/>
|
||||
@@ -155,7 +157,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { nextTick, ref } from "vue"
|
||||
import { nextTick, onMounted, ref } from "vue"
|
||||
import { clone, cloneDeep } from "lodash-es"
|
||||
import {
|
||||
graphqlCollections$,
|
||||
@@ -178,6 +180,7 @@ import { GQLTabService } from "~/services/tab/graphql"
|
||||
import { computed } from "vue"
|
||||
import {
|
||||
HoppCollection,
|
||||
HoppGQLAuth,
|
||||
HoppGQLRequest,
|
||||
makeGQLRequest,
|
||||
} from "@hoppscotch/data"
|
||||
@@ -186,6 +189,10 @@ import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||
import { updateInheritedPropertiesForAffectedRequests } from "~/helpers/collection/collection"
|
||||
import { useToast } from "~/composables/toast"
|
||||
import { getRequestsByPath } from "~/helpers/collection/request"
|
||||
import { PersistenceService } from "~/services/persistence"
|
||||
import { PersistedOAuthConfig } from "~/services/oauth/oauth.service"
|
||||
import { GQLOptionTabs } from "~/components/graphql/RequestOptions.vue"
|
||||
import { EditingProperties } from "../Properties.vue"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
@@ -232,6 +239,52 @@ const editingProperties = ref<{
|
||||
|
||||
const filterText = ref("")
|
||||
|
||||
const persistenceService = useService(PersistenceService)
|
||||
|
||||
const collectionPropertiesModalActiveTab = ref<GQLOptionTabs>("headers")
|
||||
|
||||
onMounted(() => {
|
||||
const localOAuthTempConfig =
|
||||
persistenceService.getLocalConfig("oauth_temp_config")
|
||||
|
||||
if (!localOAuthTempConfig) {
|
||||
return
|
||||
}
|
||||
|
||||
const { context, source, token }: PersistedOAuthConfig =
|
||||
JSON.parse(localOAuthTempConfig)
|
||||
|
||||
if (source === "REST") {
|
||||
return
|
||||
}
|
||||
|
||||
if (context?.type === "collection-properties") {
|
||||
// load the unsaved editing properties
|
||||
const unsavedCollectionPropertiesString = persistenceService.getLocalConfig(
|
||||
"unsaved_collection_properties"
|
||||
)
|
||||
|
||||
if (unsavedCollectionPropertiesString) {
|
||||
const unsavedCollectionProperties: EditingProperties<"GraphQL"> =
|
||||
JSON.parse(unsavedCollectionPropertiesString)
|
||||
|
||||
const auth = unsavedCollectionProperties.collection?.auth
|
||||
|
||||
if (auth?.authType === "oauth-2") {
|
||||
const grantTypeInfo = auth.grantTypeInfo
|
||||
|
||||
grantTypeInfo && (grantTypeInfo.token = token ?? "")
|
||||
}
|
||||
|
||||
editingProperties.value = unsavedCollectionProperties
|
||||
}
|
||||
|
||||
persistenceService.removeLocalConfig("oauth_temp_config")
|
||||
collectionPropertiesModalActiveTab.value = "authorization"
|
||||
showModalEditProperties.value = true
|
||||
}
|
||||
})
|
||||
|
||||
const filteredCollections = computed(() => {
|
||||
const collectionsClone = clone(collections.value)
|
||||
|
||||
|
||||
@@ -161,8 +161,10 @@
|
||||
@hide-modal="displayTeamModalAdd(false)"
|
||||
/>
|
||||
<CollectionsProperties
|
||||
v-model="collectionPropertiesModalActiveTab"
|
||||
:show="showModalEditProperties"
|
||||
:editing-properties="editingProperties"
|
||||
source="REST"
|
||||
@hide-modal="displayModalEditProperties(false)"
|
||||
@set-collection-properties="setCollectionProperties"
|
||||
/>
|
||||
@@ -170,7 +172,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, PropType, ref, watch } from "vue"
|
||||
import { computed, nextTick, onMounted, PropType, ref, watch } from "vue"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { Picked } from "~/helpers/types/HoppPicked"
|
||||
@@ -248,6 +250,10 @@ import { useService } from "dioc/vue"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||
import { TeamSearchService } from "~/helpers/teams/TeamsSearch.service"
|
||||
import { PersistenceService } from "~/services/persistence"
|
||||
import { PersistedOAuthConfig } from "~/services/oauth/oauth.service"
|
||||
import { RESTOptionTabs } from "../http/RequestOptions.vue"
|
||||
import { EditingProperties } from "./Properties.vue"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
@@ -299,12 +305,7 @@ const editingRequestName = ref("")
|
||||
const editingRequestIndex = ref<number | null>(null)
|
||||
const editingRequestID = ref<string | null>(null)
|
||||
|
||||
const editingProperties = ref<{
|
||||
collection: Partial<HoppCollection> | null
|
||||
isRootCollection: boolean
|
||||
path: string
|
||||
inheritedProperties?: HoppInheritedProperty
|
||||
}>({
|
||||
const editingProperties = ref<EditingProperties>({
|
||||
collection: null,
|
||||
isRootCollection: false,
|
||||
path: "",
|
||||
@@ -387,6 +388,55 @@ watch(
|
||||
immediate: true,
|
||||
}
|
||||
)
|
||||
const persistenceService = useService(PersistenceService)
|
||||
|
||||
const collectionPropertiesModalActiveTab = ref<RESTOptionTabs>("headers")
|
||||
|
||||
onMounted(() => {
|
||||
const localOAuthTempConfig =
|
||||
persistenceService.getLocalConfig("oauth_temp_config")
|
||||
|
||||
if (!localOAuthTempConfig) {
|
||||
return
|
||||
}
|
||||
|
||||
const { context, source, token }: PersistedOAuthConfig =
|
||||
JSON.parse(localOAuthTempConfig)
|
||||
|
||||
if (source === "GraphQL") {
|
||||
return
|
||||
}
|
||||
|
||||
if (context?.type === "collection-properties") {
|
||||
// load the unsaved editing properties
|
||||
const unsavedCollectionPropertiesString = persistenceService.getLocalConfig(
|
||||
"unsaved_collection_properties"
|
||||
)
|
||||
|
||||
if (unsavedCollectionPropertiesString) {
|
||||
const unsavedCollectionProperties: EditingProperties<"REST"> = JSON.parse(
|
||||
unsavedCollectionPropertiesString
|
||||
)
|
||||
|
||||
// casting because the type `EditingProperties["collection"]["auth"] and the usage in Properties.vue is different. there it's casted as an any.
|
||||
// FUTURE-TODO: look into this
|
||||
// @ts-expect-error because of the above reason
|
||||
const auth = unsavedCollectionProperties.collection?.auth as HoppRESTAuth
|
||||
|
||||
if (auth?.authType === "oauth-2") {
|
||||
const grantTypeInfo = auth.grantTypeInfo
|
||||
|
||||
grantTypeInfo && (grantTypeInfo.token = token ?? "")
|
||||
}
|
||||
|
||||
editingProperties.value = unsavedCollectionProperties
|
||||
}
|
||||
|
||||
persistenceService.removeLocalConfig("oauth_temp_config")
|
||||
collectionPropertiesModalActiveTab.value = "authorization"
|
||||
showModalEditProperties.value = true
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => myTeams.value,
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
:active="authName === 'OAuth 2.0'"
|
||||
@click="
|
||||
() => {
|
||||
auth.authType = 'oauth-2'
|
||||
selectOAuth2AuthType()
|
||||
hide()
|
||||
}
|
||||
"
|
||||
@@ -189,12 +189,12 @@
|
||||
<div v-if="auth.authType === 'oauth-2'">
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput
|
||||
v-model="auth.token"
|
||||
v-model="auth.grantTypeInfo.token"
|
||||
:environment-highlights="false"
|
||||
placeholder="Token"
|
||||
/>
|
||||
</div>
|
||||
<HttpOAuth2Authorization v-model="auth" />
|
||||
<HttpOAuth2Authorization v-model="auth" source="GraphQL" />
|
||||
</div>
|
||||
<div v-if="auth.authType === 'api-key'">
|
||||
<HttpAuthorizationApiKey v-model="auth" />
|
||||
@@ -220,19 +220,22 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { pluckRef } from "@composables/ref"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import { HoppGQLAuth, HoppGQLAuthOAuth2 } from "@hoppscotch/data"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { computed, onMounted, ref } from "vue"
|
||||
|
||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||
|
||||
import IconCircle from "~icons/lucide/circle"
|
||||
import IconCircleDot from "~icons/lucide/circle-dot"
|
||||
import IconExternalLink from "~icons/lucide/external-link"
|
||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import IconExternalLink from "~icons/lucide/external-link"
|
||||
import IconCircleDot from "~icons/lucide/circle-dot"
|
||||
import IconCircle from "~icons/lucide/circle"
|
||||
import { computed, ref } from "vue"
|
||||
import { HoppGQLAuth } from "@hoppscotch/data"
|
||||
import { pluckRef } from "@composables/ref"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||
import { onMounted } from "vue"
|
||||
|
||||
import { getDefaultAuthCodeOauthFlowParams } from "~/services/oauth/flows/authCode"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
@@ -280,6 +283,30 @@ const getAuthName = (type: HoppGQLAuth["authType"] | undefined) => {
|
||||
return AUTH_KEY_NAME[type] ? AUTH_KEY_NAME[type] : "None"
|
||||
}
|
||||
|
||||
const selectOAuth2AuthType = () => {
|
||||
const defaultGrantTypeInfo: HoppGQLAuthOAuth2["grantTypeInfo"] = {
|
||||
...getDefaultAuthCodeOauthFlowParams(),
|
||||
grantType: "AUTHORIZATION_CODE",
|
||||
token: "",
|
||||
}
|
||||
|
||||
// @ts-expect-error - the existing grantTypeInfo might be in the auth object, typescript doesnt know that
|
||||
const existingGrantTypeInfo = auth.value.grantTypeInfo as
|
||||
| HoppGQLAuthOAuth2["grantTypeInfo"]
|
||||
| undefined
|
||||
|
||||
const grantTypeInfo = existingGrantTypeInfo
|
||||
? existingGrantTypeInfo
|
||||
: defaultGrantTypeInfo
|
||||
|
||||
auth.value = <HoppGQLAuth>{
|
||||
...auth.value,
|
||||
authType: "oauth-2",
|
||||
addTo: "HEADERS",
|
||||
grantTypeInfo: grantTypeInfo,
|
||||
}
|
||||
}
|
||||
|
||||
const authActive = pluckRef(auth, "authActive")
|
||||
|
||||
const clearContent = () => {
|
||||
|
||||
@@ -579,12 +579,18 @@ const getComputedAuthHeaders = (
|
||||
})
|
||||
} else if (
|
||||
request.auth.authType === "bearer" ||
|
||||
request.auth.authType === "oauth-2"
|
||||
(request.auth.authType === "oauth-2" && request.auth.addTo === "HEADERS")
|
||||
) {
|
||||
const requestAuth = request.auth
|
||||
|
||||
const isOAuth2 = requestAuth.authType === "oauth-2"
|
||||
|
||||
const token = isOAuth2 ? requestAuth.grantTypeInfo.token : requestAuth.token
|
||||
|
||||
headers.push({
|
||||
active: true,
|
||||
key: "Authorization",
|
||||
value: `Bearer ${request.auth.token}`,
|
||||
value: `Bearer ${token}`,
|
||||
})
|
||||
} else if (request.auth.authType === "api-key") {
|
||||
const { key, addTo } = request.auth
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
:active="authName === 'OAuth 2.0'"
|
||||
@click="
|
||||
() => {
|
||||
auth.authType = 'oauth-2'
|
||||
selectOAuth2AuthType()
|
||||
hide()
|
||||
}
|
||||
"
|
||||
@@ -177,15 +177,24 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="auth.authType === 'oauth-2'">
|
||||
<div v-if="auth.authType === 'oauth-2'" class="w-full">
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<!-- Ensure a new object is assigned here to avoid reactivity issues -->
|
||||
<SmartEnvInput
|
||||
v-model="auth.token"
|
||||
:model-value="auth.grantTypeInfo.token"
|
||||
placeholder="Token"
|
||||
:envs="envs"
|
||||
@update:model-value="
|
||||
auth.grantTypeInfo = { ...auth.grantTypeInfo, token: $event }
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<HttpOAuth2Authorization v-model="auth" :envs="envs" />
|
||||
<HttpOAuth2Authorization
|
||||
v-model="auth"
|
||||
:is-collection-property="isCollectionProperty"
|
||||
:envs="envs"
|
||||
:source="source"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="auth.authType === 'api-key'">
|
||||
<HttpAuthorizationApiKey v-model="auth" :envs="envs" />
|
||||
@@ -217,7 +226,7 @@ import IconExternalLink from "~icons/lucide/external-link"
|
||||
import IconCircleDot from "~icons/lucide/circle-dot"
|
||||
import IconCircle from "~icons/lucide/circle"
|
||||
import { computed, ref } from "vue"
|
||||
import { HoppRESTAuth } from "@hoppscotch/data"
|
||||
import { HoppRESTAuth, HoppRESTAuthOAuth2 } from "@hoppscotch/data"
|
||||
import { pluckRef } from "@composables/ref"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
@@ -226,17 +235,27 @@ import { onMounted } from "vue"
|
||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||
import { AggregateEnvironment } from "~/newstore/environments"
|
||||
|
||||
import { getDefaultAuthCodeOauthFlowParams } from "~/services/oauth/flows/authCode"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: HoppRESTAuth
|
||||
isCollectionProperty?: boolean
|
||||
isRootCollection?: boolean
|
||||
inheritedProperties?: HoppInheritedProperty
|
||||
envs?: AggregateEnvironment[]
|
||||
}>()
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue: HoppRESTAuth
|
||||
isCollectionProperty?: boolean
|
||||
isRootCollection?: boolean
|
||||
inheritedProperties?: HoppInheritedProperty
|
||||
envs?: AggregateEnvironment[]
|
||||
source?: "REST" | "GraphQL"
|
||||
}>(),
|
||||
{
|
||||
source: "REST",
|
||||
envs: undefined,
|
||||
inheritedProperties: undefined,
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", value: HoppRESTAuth): void
|
||||
@@ -272,6 +291,30 @@ const getAuthName = (type: HoppRESTAuth["authType"] | undefined) => {
|
||||
return AUTH_KEY_NAME[type] ? AUTH_KEY_NAME[type] : "None"
|
||||
}
|
||||
|
||||
const selectOAuth2AuthType = () => {
|
||||
const defaultGrantTypeInfo: HoppRESTAuthOAuth2["grantTypeInfo"] = {
|
||||
...getDefaultAuthCodeOauthFlowParams(),
|
||||
grantType: "AUTHORIZATION_CODE",
|
||||
token: "",
|
||||
}
|
||||
|
||||
// @ts-expect-error - the existing grantTypeInfo might be in the auth object, typescript doesnt know that
|
||||
const existingGrantTypeInfo = auth.value.grantTypeInfo as
|
||||
| HoppRESTAuthOAuth2["grantTypeInfo"]
|
||||
| undefined
|
||||
|
||||
const grantTypeInfo = existingGrantTypeInfo
|
||||
? existingGrantTypeInfo
|
||||
: defaultGrantTypeInfo
|
||||
|
||||
auth.value = <HoppRESTAuth>{
|
||||
...auth.value,
|
||||
authType: "oauth-2",
|
||||
addTo: "HEADERS",
|
||||
grantTypeInfo: grantTypeInfo,
|
||||
}
|
||||
}
|
||||
|
||||
const authActive = pluckRef(auth, "authActive")
|
||||
|
||||
const clearContent = () => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user