diff --git a/packages/hoppscotch-cli/src/__tests__/unit/fixtures/workspace-access.mock.ts b/packages/hoppscotch-cli/src/__tests__/unit/fixtures/workspace-access.mock.ts
index acb7fd61..3fc497a7 100644
--- a/packages/hoppscotch-cli/src/__tests__/unit/fixtures/workspace-access.mock.ts
+++ b/packages/hoppscotch-cli/src/__tests__/unit/fixtures/workspace-access.mock.ts
@@ -485,17 +485,17 @@ export const WORKSPACE_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Worksp
export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: HoppCollection[] =
[
{
- v: 8,
+ v: 9,
id: "clx1f86hv000010f8szcfya0t",
name: "Multiple child collections with authorization & headers set at each level",
folders: [
{
- v: 8,
+ v: 9,
id: "clx1fjgah000110f8a5bs68gd",
name: "folder-1",
folders: [
{
- v: 8,
+ v: 9,
id: "clx1fjwmm000410f8l1gkkr1a",
name: "folder-11",
folders: [],
@@ -537,7 +537,7 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp
],
},
{
- v: 8,
+ v: 9,
id: "clx1fjyxm000510f8pv90dt43",
name: "folder-12",
folders: [],
@@ -595,7 +595,7 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp
],
},
{
- v: 8,
+ v: 9,
id: "clx1fk1cv000610f88kc3aupy",
name: "folder-13",
folders: [],
@@ -707,12 +707,12 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp
],
},
{
- v: 8,
+ v: 9,
id: "clx1fjk9o000210f8j0573pls",
name: "folder-2",
folders: [
{
- v: 8,
+ v: 9,
id: "clx1fk516000710f87sfpw6bo",
name: "folder-21",
folders: [],
@@ -752,7 +752,7 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp
],
},
{
- v: 8,
+ v: 9,
id: "clx1fk72t000810f8gfwkpi5y",
name: "folder-22",
folders: [],
@@ -810,7 +810,7 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp
],
},
{
- v: 8,
+ v: 9,
id: "clx1fk95g000910f8bunhaoo8",
name: "folder-23",
folders: [],
@@ -915,12 +915,12 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp
],
},
{
- v: 8,
+ v: 9,
id: "clx1fjmlq000310f86o4d3w2o",
name: "folder-3",
folders: [
{
- v: 8,
+ v: 9,
id: "clx1iwq0p003e10f8u8zg0p85",
name: "folder-31",
folders: [],
@@ -960,7 +960,7 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp
],
},
{
- v: 8,
+ v: 9,
id: "clx1izut7003m10f894ip59zg",
name: "folder-32",
folders: [],
@@ -1018,7 +1018,7 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp
],
},
{
- v: 8,
+ v: 9,
id: "clx1j2ka9003q10f8cdbzpgpg",
name: "folder-33",
folders: [],
diff --git a/packages/hoppscotch-cli/src/utils/workspace-access.ts b/packages/hoppscotch-cli/src/utils/workspace-access.ts
index 2ab6c7ac..2c408803 100644
--- a/packages/hoppscotch-cli/src/utils/workspace-access.ts
+++ b/packages/hoppscotch-cli/src/utils/workspace-access.ts
@@ -76,6 +76,71 @@ const normalizeEnvironmentVariable = (variable: HoppEnvPair): HoppEnvPair => {
};
};
+/**
+ * Transforms the given `HoppRESTAuth` object to ensure it conforms to the latest
+ * OAuth 2.0 authentication structure. Depending on the `grantType` within the
+ * `grantTypeInfo` property, this function adds or initializes specific fields
+ * such as `clientAuthentication`, `authRequestParams`, `tokenRequestParams`,
+ * and `refreshRequestParams` to maintain compatibility with updated schema
+ * requirements.
+ *
+ * - For "CLIENT_CREDENTIALS" grant type, sets `clientAuthentication` to "IN_BODY"
+ * and initializes `tokenRequestParams` and `refreshRequestParams` as empty arrays.
+ * - For "AUTHORIZATION_CODE" grant type, initializes `authRequestParams`,
+ * `tokenRequestParams`, and `refreshRequestParams` as empty arrays.
+ * - For "PASSWORD" grant type, initializes `tokenRequestParams` and
+ * `refreshRequestParams` as empty arrays.
+ * - For "IMPLICIT" grant type, initializes `authRequestParams` and
+ * `refreshRequestParams` as empty arrays.
+ *
+ * If the `authType` is not "oauth-2", the original `auth` object is returned unchanged.
+ *
+ * @param {HoppRESTAuth} auth - The authentication object to transform.
+ * @returns {HoppRESTAuth} The transformed authentication object with updated grant type information.
+ */
+const transformAuth = (auth: HoppRESTAuth): HoppRESTAuth => {
+ if (auth.authType === "oauth-2") {
+ const oldGrantTypeInfo = auth.grantTypeInfo;
+ let newGrantTypeInfo = oldGrantTypeInfo;
+
+ // Add clientAuthentication for CLIENT_CREDENTIALS
+ if (oldGrantTypeInfo.grantType === "CLIENT_CREDENTIALS") {
+ newGrantTypeInfo = {
+ ...oldGrantTypeInfo,
+ clientAuthentication: "IN_BODY",
+ tokenRequestParams: [],
+ refreshRequestParams: [],
+ };
+ } else if (oldGrantTypeInfo.grantType === "AUTHORIZATION_CODE") {
+ newGrantTypeInfo = {
+ ...oldGrantTypeInfo,
+ authRequestParams: [],
+ tokenRequestParams: [],
+ refreshRequestParams: [],
+ };
+ } else if (oldGrantTypeInfo.grantType === "PASSWORD") {
+ newGrantTypeInfo = {
+ ...oldGrantTypeInfo,
+ tokenRequestParams: [],
+ refreshRequestParams: [],
+ };
+ } else if (oldGrantTypeInfo.grantType === "IMPLICIT") {
+ newGrantTypeInfo = {
+ ...oldGrantTypeInfo,
+ authRequestParams: [],
+ refreshRequestParams: [],
+ };
+ }
+
+ return {
+ ...auth,
+ grantTypeInfo: newGrantTypeInfo,
+ };
+ }
+
+ return auth;
+};
+
/**
* Transforms workspace environment data to the `HoppEnvironment` format.
*
@@ -115,21 +180,9 @@ export const transformWorkspaceCollections = (
const { auth = { authType: "inherit", authActive: true }, headers = [] } =
parsedData;
- const migratedAuth: HoppRESTAuth =
- auth.authType === "oauth-2"
- ? {
- ...auth,
- grantTypeInfo:
- auth.grantTypeInfo.grantType === "CLIENT_CREDENTIALS"
- ? {
- ...auth.grantTypeInfo,
- clientAuthentication: "IN_BODY",
- }
- : auth.grantTypeInfo,
- }
- : auth;
+ const transformedAuth = transformAuth(auth);
- const migratedHeaders = headers.map((header) =>
+ const transformedHeaders = headers.map((header) =>
header.description ? header : { ...header, description: "" }
);
@@ -142,8 +195,8 @@ export const transformWorkspaceCollections = (
name: title,
folders: transformWorkspaceCollections(folders),
requests: transformWorkspaceRequests(requests),
- auth: migratedAuth,
- headers: migratedHeaders,
+ auth: transformedAuth,
+ headers: transformedHeaders,
};
});
};
diff --git a/packages/hoppscotch-common/locales/en.json b/packages/hoppscotch-common/locales/en.json
index ff27fdec..ae0e76bb 100644
--- a/packages/hoppscotch-common/locales/en.json
+++ b/packages/hoppscotch-common/locales/en.json
@@ -241,7 +241,11 @@
"label_send_as": "Client Authentication",
"label_send_in_body": "Send Credentials in Body",
"label_send_as_basic_auth": "Send Credentials as Basic Auth",
- "enter_value": "Enter value"
+ "enter_value": "Enter value",
+ "auth_request": "Auth Request",
+ "token_request": "Token Request",
+ "refresh_request": "Refresh Request",
+ "send_in": "Send In"
},
"pass_key_by": "Pass by",
"pass_by_query_params_label": "Query Parameters",
diff --git a/packages/hoppscotch-common/src/components.d.ts b/packages/hoppscotch-common/src/components.d.ts
index 22747b90..3392c2b4 100644
--- a/packages/hoppscotch-common/src/components.d.ts
+++ b/packages/hoppscotch-common/src/components.d.ts
@@ -211,7 +211,9 @@ declare module 'vue' {
IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default']
IconLucideBrush: typeof import('~icons/lucide/brush')['default']
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
+ IconLucideChevronDown: typeof import('~icons/lucide/chevron-down')['default']
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
+ IconLucideChevronUp: typeof import('~icons/lucide/chevron-up')['default']
IconLucideCircleCheck: typeof import('~icons/lucide/circle-check')['default']
IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
IconLucideHelpCircle: typeof import('~icons/lucide/help-circle')['default']
@@ -221,7 +223,6 @@ declare module 'vue' {
IconLucideListEnd: typeof import('~icons/lucide/list-end')['default']
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
IconLucidePlusCircle: typeof import('~icons/lucide/plus-circle')['default']
- IconLucideRss: typeof import('~icons/lucide/rss')['default']
IconLucideSearch: typeof import('~icons/lucide/search')['default']
IconLucideTriangleAlert: typeof import('~icons/lucide/triangle-alert')['default']
IconLucideUsers: typeof import('~icons/lucide/users')['default']
@@ -254,6 +255,7 @@ declare module 'vue' {
LensesRenderersVideoLensRenderer: typeof import('./components/lenses/renderers/VideoLensRenderer.vue')['default']
LensesRenderersXMLLensRenderer: typeof import('./components/lenses/renderers/XMLLensRenderer.vue')['default']
LensesResponseBodyRenderer: typeof import('./components/lenses/ResponseBodyRenderer.vue')['default']
+ MonacoScriptEditor: typeof import('./components/MonacoScriptEditor.vue')['default']
ProfileUserDelete: typeof import('./components/profile/UserDelete.vue')['default']
RealtimeCommunication: typeof import('./components/realtime/Communication.vue')['default']
RealtimeConnectionConfig: typeof import('./components/realtime/ConnectionConfig.vue')['default']
diff --git a/packages/hoppscotch-common/src/components/http/KeyValue.vue b/packages/hoppscotch-common/src/components/http/KeyValue.vue
index f2200648..e8e5622c 100644
--- a/packages/hoppscotch-common/src/components/http/KeyValue.vue
+++ b/packages/hoppscotch-common/src/components/http/KeyValue.vue
@@ -23,7 +23,7 @@
:model-value="name"
:placeholder="t('count.key')"
:auto-complete-source="keyAutoCompleteSource"
- :auto-complete-env="true"
+ :auto-complete-env="autoCompleteEnv"
:envs="envs"
:inspection-results="inspectionKeyResult"
@update:model-value="emit('update:name', $event)"
@@ -41,7 +41,7 @@
:class="{ 'opacity-50': !entityActive }"
:model-value="value"
:placeholder="t('count.value')"
- :auto-complete-env="true"
+ :auto-complete-env="autoCompleteEnv"
:envs="envs"
:inspection-results="inspectionValueResult"
@update:model-value="emit('update:value', $event)"
@@ -56,7 +56,10 @@
"
/>
+
+
()
+withDefaults(
+ defineProps<{
+ showDescription?: boolean
+ total: number
+ index: number
+ entityId: number
+ isActive: boolean
+ entityActive: boolean
+ name: string
+ value: string
+ inspectionKeyResult?: InspectorResult[]
+ inspectionValueResult?: InspectorResult[]
+ description?: string
+ envs?: AggregateEnvironment[]
+ autoCompleteEnv?: boolean
+ keyAutoCompleteSource?: string[]
+ }>(),
+ {
+ showDescription: true,
+ description: "",
+ inspectionKeyResult: () => [],
+ inspectionValueResult: () => [],
+ envs: () => [],
+ autoCompleteEnv: true,
+ keyAutoCompleteSource: () => [],
+ }
+)
const emit = defineEmits<{
(e: "update:name", value: string): void
diff --git a/packages/hoppscotch-common/src/components/http/authorization/OAuth2.vue b/packages/hoppscotch-common/src/components/http/authorization/OAuth2.vue
index d0d96fab..8f1c2f55 100644
--- a/packages/hoppscotch-common/src/components/http/authorization/OAuth2.vue
+++ b/packages/hoppscotch-common/src/components/http/authorization/OAuth2.vue
@@ -172,6 +172,364 @@
+
+
+ {{ t("authorization.advance_config") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t("count.key") }}
+
+
+ {{ t("count.value") }}
+
+
+
+
+
+
+
+
+
+ {{ t("empty.parameters") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t("count.key") }}
+
+
+ {{ t("count.value") }}
+
+
+ {{ t("authorization.oauth.send_in") }}
+
+
+
+
+
+
+
+
+
+ {{ t("empty.parameters") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ updateTokenRequestParam(index, {
+ ...param,
+ sendIn: option.value as
+ | 'headers'
+ | 'body'
+ | 'url',
+ })
+ hide()
+ }
+ "
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t("count.key") }}
+
+
+ {{ t("count.value") }}
+
+
+ {{ t("authorization.oauth.send_in") }}
+
+
+
+
+
+
+
+
+
+ {{ t("empty.parameters") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ updateRefreshRequestParam(index, {
+ ...param,
+ sendIn: option.value as
+ | 'headers'
+ | 'body'
+ | 'url',
+ })
+ hide()
+ }
+ "
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
p.active && p.key && p.value)
+ .map((p) => ({
+ id: p.id,
+ key: replaceTemplateString(p.key),
+ value: replaceTemplateString(p.value),
+ active: p.active,
+ sendIn: p.sendIn,
+ })),
+ tokenRequestParams: workingTokenRequestParams.value
+ .filter((p) => p.active && p.key && p.value)
+ .map((p) => ({
+ id: p.id,
+ key: replaceTemplateString(p.key),
+ value: replaceTemplateString(p.value),
+ active: p.active,
+ sendIn: p.sendIn,
+ })),
+ refreshRequestParams: workingRefreshRequestParams.value
+ .filter((p) => p.active && p.key && p.value)
+ .map((p) => ({
+ id: p.id,
+ key: replaceTemplateString(p.key),
+ value: replaceTemplateString(p.value),
+ active: p.active,
+ sendIn: p.sendIn,
+ })),
}
const unwrappedParams = replaceTemplateStringsInObjectValues(params)
@@ -998,7 +1395,7 @@ const setAccessTokenInActiveContext = (
tabService.currentActiveTab.value.document.request.auth.authType ===
"oauth-2"
) {
- // @ts-expect-error - todo: narrow the grantType to only supporting refresh tokens
+ // @ts-expect-error - TODO: narrow the grantType to only supporting refresh tokens
tabService.currentActiveTab.value.document.request.auth.grantTypeInfo.refreshToken =
refreshToken
}
@@ -1133,4 +1530,305 @@ const grantTypeTippyActions = ref(null)
const pkceTippyActions = ref(null)
const authTippyActions = ref(null)
const clientAuthenticationTippyActions = ref(null)
+
+// Advanced Configuration state
+const isAdvancedConfigExpanded = ref(false)
+
+const toggleAdvancedConfig = () => {
+ isAdvancedConfigExpanded.value = !isAdvancedConfigExpanded.value
+}
+
+// Advanced Configuration: Auth Request Parameters
+type OAuth2AdvancedParam = {
+ id: number
+ key: string
+ value: string
+ active: boolean
+ sendIn?: "headers" | "url" | "body"
+}
+
+let paramsIdCounter = 1000
+
+// Initialize working auth request params
+const workingAuthRequestParams = ref([
+ { id: paramsIdCounter++, key: "", value: "", active: true },
+])
+
+// Watch for changes in working auth request params
+watch(
+ workingAuthRequestParams,
+ (newParams: OAuth2AdvancedParam[]) => {
+ // Auto-add empty row when the last row is filled
+ if (newParams.length > 0 && newParams[newParams.length - 1].key !== "") {
+ workingAuthRequestParams.value.push({
+ id: paramsIdCounter++,
+ key: "",
+ value: "",
+ active: true,
+ })
+ }
+
+ // Update auth.value.grantTypeInfo with non-empty params
+ const nonEmptyParams = newParams.filter(
+ (p: OAuth2AdvancedParam) => p.key !== "" || p.value !== ""
+ )
+
+ if ("authRequestParams" in auth.value.grantTypeInfo) {
+ auth.value.grantTypeInfo.authRequestParams = nonEmptyParams.map(
+ (param) => ({
+ id: param.id,
+ key: param.key,
+ value: param.value,
+ active: param.active,
+ })
+ )
+ }
+ },
+ { deep: true }
+)
+
+// Functions for auth request params management
+const addAuthRequestParam = () => {
+ workingAuthRequestParams.value.push({
+ id: paramsIdCounter++,
+ key: "",
+ value: "",
+ active: true,
+ })
+}
+
+const updateAuthRequestParam = (
+ index: number,
+ payload: OAuth2AdvancedParam
+) => {
+ workingAuthRequestParams.value[index] = payload
+}
+
+const deleteAuthRequestParam = (index: number) => {
+ // Only delete if it's not the last empty row, or if there are multiple rows
+ if (workingAuthRequestParams.value.length > 1) {
+ workingAuthRequestParams.value.splice(index, 1)
+ }
+}
+
+// Token Request Parameters
+interface OAuth2TokenParam {
+ id: number
+ key: string
+ value: string
+ sendIn: "headers" | "body" | "url"
+ active: boolean
+}
+
+// Initialize working token request params
+const workingTokenRequestParams = ref([
+ {
+ id: paramsIdCounter++,
+ key: "",
+ value: "",
+ sendIn: "body",
+ active: true,
+ },
+])
+
+// Watch for changes in working token request params
+watch(
+ workingTokenRequestParams,
+ (newParams: OAuth2TokenParam[]) => {
+ // Auto-add empty row when the last row is filled
+ if (newParams.length > 0 && newParams[newParams.length - 1].key !== "") {
+ workingTokenRequestParams.value.push({
+ id: paramsIdCounter++,
+ key: "",
+ value: "",
+ sendIn: "body",
+ active: true,
+ })
+ }
+
+ // Update auth.value.grantTypeInfo with non-empty params
+ const nonEmptyParams = newParams.filter(
+ (p: OAuth2TokenParam) => p.key !== "" || p.value !== ""
+ )
+
+ if ("tokenRequestParams" in auth.value.grantTypeInfo) {
+ auth.value.grantTypeInfo.tokenRequestParams = nonEmptyParams.map(
+ (param) => ({
+ id: param.id,
+ key: param.key,
+ value: param.value,
+ sendIn: param.sendIn,
+ active: param.active,
+ })
+ )
+ }
+ },
+ { deep: true }
+)
+
+// Functions for token request params management
+const addTokenRequestParam = () => {
+ workingTokenRequestParams.value.push({
+ id: paramsIdCounter++,
+ key: "",
+ value: "",
+ sendIn: "body",
+ active: true,
+ })
+}
+
+const updateTokenRequestParam = (index: number, payload: OAuth2TokenParam) => {
+ workingTokenRequestParams.value[index] = payload
+}
+
+const deleteTokenRequestParam = (index: number) => {
+ // Only delete if it's not the last empty row, or if there are multiple rows
+ if (workingTokenRequestParams.value.length > 1) {
+ workingTokenRequestParams.value.splice(index, 1)
+ }
+}
+
+// Refresh Request Parameters
+interface OAuth2RefreshParam {
+ id: number
+ key: string
+ value: string
+ sendIn: "headers" | "body" | "url"
+ active: boolean
+}
+
+// Initialize working refresh request params
+const workingRefreshRequestParams = ref([
+ {
+ id: paramsIdCounter++,
+ key: "",
+ value: "",
+ sendIn: "body",
+ active: true,
+ },
+])
+
+// Watch for changes in working refresh request params
+watch(
+ workingRefreshRequestParams,
+ (newParams: OAuth2RefreshParam[]) => {
+ // Auto-add empty row when the last row is filled
+ if (newParams.length > 0 && newParams[newParams.length - 1].key !== "") {
+ workingRefreshRequestParams.value.push({
+ id: paramsIdCounter++,
+ key: "",
+ value: "",
+ sendIn: "body",
+ active: true,
+ })
+ }
+
+ // Update auth.value.grantTypeInfo with non-empty params
+ const nonEmptyParams = newParams.filter(
+ (p: OAuth2RefreshParam) => p.key !== "" || p.value !== ""
+ )
+
+ if ("refreshRequestParams" in auth.value.grantTypeInfo) {
+ auth.value.grantTypeInfo.refreshRequestParams = nonEmptyParams.map(
+ (param) => ({
+ id: param.id,
+ key: param.key,
+ value: param.value,
+ sendIn: param.sendIn,
+ active: param.active,
+ })
+ )
+ }
+ },
+ { deep: true }
+)
+
+// Functions for refresh request params management
+const addRefreshRequestParam = () => {
+ workingRefreshRequestParams.value.push({
+ id: paramsIdCounter++,
+ key: "",
+ value: "",
+ sendIn: "body",
+ active: true,
+ })
+}
+
+const updateRefreshRequestParam = (
+ index: number,
+ payload: OAuth2RefreshParam
+) => {
+ workingRefreshRequestParams.value[index] = payload
+}
+
+const deleteRefreshRequestParam = (index: number) => {
+ // Only delete if it's not the last empty row, or if there are multiple rows
+ if (workingRefreshRequestParams.value.length > 1) {
+ workingRefreshRequestParams.value.splice(index, 1)
+ }
+}
+
+// Initialize advanced parameters from the auth object when component mounts
+onMounted(() => {
+ if (
+ "authRequestParams" in auth.value.grantTypeInfo &&
+ auth.value.grantTypeInfo.authRequestParams &&
+ auth.value.grantTypeInfo.authRequestParams.length > 0
+ ) {
+ workingAuthRequestParams.value =
+ auth.value.grantTypeInfo.authRequestParams.map((param) => ({
+ id: param.id || paramsIdCounter++,
+ key: param.key,
+ value: param.value,
+ active: param.active,
+ }))
+ }
+
+ if (
+ "tokenRequestParams" in auth.value.grantTypeInfo &&
+ auth.value.grantTypeInfo.tokenRequestParams &&
+ auth.value.grantTypeInfo.tokenRequestParams.length > 0
+ ) {
+ workingTokenRequestParams.value = [
+ ...auth.value.grantTypeInfo.tokenRequestParams.map((param) => ({
+ id: param.id || paramsIdCounter++,
+ key: param.key,
+ value: param.value,
+ sendIn: param.sendIn || "body",
+ active: param.active,
+ })),
+
+ {
+ id: paramsIdCounter++,
+ key: "",
+ value: "",
+ sendIn: "body",
+ active: true,
+ },
+ ]
+ }
+
+ if (
+ "refreshRequestParams" in auth.value.grantTypeInfo &&
+ auth.value.grantTypeInfo.refreshRequestParams &&
+ auth.value.grantTypeInfo.refreshRequestParams.length > 0
+ ) {
+ workingRefreshRequestParams.value = [
+ ...auth.value.grantTypeInfo.refreshRequestParams.map((param) => ({
+ id: param.id || paramsIdCounter++,
+ key: param.key,
+ value: param.value,
+ sendIn: param.sendIn || "body",
+ active: param.active,
+ })),
+ {
+ id: paramsIdCounter++,
+ key: "",
+ value: "",
+ sendIn: "body",
+ active: true,
+ },
+ ]
+ }
+})
diff --git a/packages/hoppscotch-common/src/helpers/auth/index.ts b/packages/hoppscotch-common/src/helpers/auth/index.ts
index 554711eb..f3402385 100644
--- a/packages/hoppscotch-common/src/helpers/auth/index.ts
+++ b/packages/hoppscotch-common/src/helpers/auth/index.ts
@@ -56,3 +56,7 @@ export const replaceTemplateStringsInObjectValues = <
return newObj as T
}
+
+export const replaceTemplateString = (str: string): string => {
+ return replaceTemplateStringsInObjectValues({ value: str }).value
+}
diff --git a/packages/hoppscotch-common/src/helpers/graphql/connection.ts b/packages/hoppscotch-common/src/helpers/graphql/connection.ts
index b5036d2d..2e97c8ec 100644
--- a/packages/hoppscotch-common/src/helpers/graphql/connection.ts
+++ b/packages/hoppscotch-common/src/helpers/graphql/connection.ts
@@ -401,7 +401,7 @@ export const runGQLOperation = async (options: RunQueryOptions) => {
.forEach(({ key, value }) => (finalHeaders[key] = value))
const gqlRequest: HoppGQLRequest = {
- v: 8,
+ v: 9,
name: options.name || "Untitled Request",
url: finalUrl,
headers: request.headers,
diff --git a/packages/hoppscotch-common/src/helpers/kernel/__tests__/kernel.spec.ts b/packages/hoppscotch-common/src/helpers/kernel/__tests__/kernel.spec.ts
index de00eec4..3e3c1095 100644
--- a/packages/hoppscotch-common/src/helpers/kernel/__tests__/kernel.spec.ts
+++ b/packages/hoppscotch-common/src/helpers/kernel/__tests__/kernel.spec.ts
@@ -136,7 +136,7 @@ describe("GraphQL Response Transformation", () => {
describe("GraphQL Request Transformation", () => {
const baseRequest: HoppGQLRequest = {
- v: 8,
+ v: 9,
name: "Test Query",
url: "https://api.example.com/graphql",
headers: [],
@@ -235,7 +235,7 @@ describe("REST Response Transformation", () => {
}
const originalRequest: HoppRESTRequest = {
- v: "11",
+ v: "15",
endpoint: "https://api.example.com",
name: "Test Request",
method: "GET",
@@ -244,7 +244,7 @@ describe("REST Response Transformation", () => {
preRequestScript: "",
testScript: "",
auth: { authType: "none", authActive: true },
- body: { contentmediaType: null, body: null },
+ body: { contentType: null, body: null },
requestVariables: [],
responses: {},
}
@@ -271,7 +271,7 @@ describe("REST Response Transformation", () => {
}
const originalRequest: HoppRESTRequest = {
- v: "11",
+ v: "15",
endpoint: "https://api.example.com",
name: "Test Request",
method: "GET",
@@ -280,7 +280,7 @@ describe("REST Response Transformation", () => {
preRequestScript: "",
testScript: "",
auth: { authType: "none", authActive: true },
- body: { contentmediaType: null, body: null },
+ body: { contentType: null, body: null },
requestVariables: [],
responses: {},
}
@@ -300,7 +300,7 @@ describe("REST Response Transformation", () => {
describe("REST Request Transformation", () => {
const baseEffectiveRequest: EffectiveHoppRESTRequest = {
- v: "11",
+ v: "15",
name: "Test Request",
method: "GET",
endpoint: "https://api.example.com",
@@ -310,7 +310,7 @@ describe("REST Request Transformation", () => {
preRequestScript: "",
testScript: "",
auth: { authType: "none", authActive: true },
- body: { contentmediaType: null, body: null },
+ body: { contentType: null, body: null },
requestVariables: [],
responses: {},
effectiveFinalHeaders: [],
diff --git a/packages/hoppscotch-common/src/helpers/oauth2Params.ts b/packages/hoppscotch-common/src/helpers/oauth2Params.ts
new file mode 100644
index 00000000..ca5e6801
--- /dev/null
+++ b/packages/hoppscotch-common/src/helpers/oauth2Params.ts
@@ -0,0 +1,59 @@
+export const commonOAuth2AuthParams = [
+ "audience",
+ "scope",
+ "state",
+ "nonce",
+ "prompt",
+ "max_age",
+ "ui_locales",
+ "id_token_hint",
+ "login_hint",
+ "acr_values",
+ "response_mode",
+ "display",
+ "claims",
+ "request",
+ "request_uri",
+]
+
+export const commonOAuth2TokenParams = [
+ "grant_type",
+ "code",
+ "redirect_uri",
+ "client_id",
+ "client_secret",
+ "code_verifier",
+ "username",
+ "password",
+ "scope",
+ "audience",
+ "resource",
+ "assertion",
+ "assertion_type",
+ "refresh_token",
+]
+
+export const commonOAuth2RefreshParams = [
+ "grant_type",
+ "refresh_token",
+ "client_id",
+ "client_secret",
+ "scope",
+ "audience",
+ "resource",
+]
+
+export const sendInOptions = [
+ {
+ label: "Request Body",
+ value: "body",
+ },
+ {
+ label: "Request URL",
+ value: "url",
+ },
+ {
+ label: "Request Headers",
+ value: "headers",
+ },
+]
diff --git a/packages/hoppscotch-common/src/services/oauth/flows/authCode.ts b/packages/hoppscotch-common/src/services/oauth/flows/authCode.ts
index be2e182e..0a792fb0 100644
--- a/packages/hoppscotch-common/src/services/oauth/flows/authCode.ts
+++ b/packages/hoppscotch-common/src/services/oauth/flows/authCode.ts
@@ -10,21 +10,47 @@ import { z } from "zod"
import { getService } from "~/modules/dioc"
import * as E from "fp-ts/Either"
import { KernelInterceptorService } from "~/services/kernel-interceptor.service"
-import { AuthCodeGrantTypeParams } from "@hoppscotch/data"
import { content } from "@hoppscotch/kernel"
const persistenceService = getService(PersistenceService)
const interceptorService = getService(KernelInterceptorService)
-const AuthCodeOauthFlowParamsSchema = AuthCodeGrantTypeParams.pick({
- authEndpoint: true,
- tokenEndpoint: true,
- clientID: true,
- clientSecret: true,
- scopes: true,
- isPKCE: true,
- codeVerifierMethod: true,
-})
+const AuthCodeOauthFlowParamsSchema = z
+ .object({
+ authEndpoint: z.string(),
+ tokenEndpoint: z.string(),
+ clientID: z.string(),
+ clientSecret: z.string().optional(),
+ scopes: z.string().optional(),
+ isPKCE: z.boolean(),
+ codeVerifierMethod: z.enum(["plain", "S256"]).optional(),
+ authRequestParams: z.array(
+ z.object({
+ id: z.number(),
+ key: z.string(),
+ value: z.string(),
+ active: z.boolean(),
+ })
+ ),
+ refreshRequestParams: z.array(
+ z.object({
+ id: z.number(),
+ key: z.string(),
+ value: z.string(),
+ active: z.boolean(),
+ sendIn: z.enum(["headers", "url", "body"]).optional(),
+ })
+ ),
+ tokenRequestParams: z.array(
+ z.object({
+ id: z.number(),
+ key: z.string(),
+ value: z.string(),
+ active: z.boolean(),
+ sendIn: z.enum(["headers", "url", "body"]).optional(),
+ })
+ ),
+ })
.refine(
(params) => {
return (
@@ -63,6 +89,9 @@ export const getDefaultAuthCodeOauthFlowParams =
scopes: undefined,
isPKCE: false,
codeVerifierMethod: "S256",
+ authRequestParams: [],
+ refreshRequestParams: [],
+ tokenRequestParams: [],
})
const initAuthCodeOauthFlow = async ({
@@ -73,6 +102,9 @@ const initAuthCodeOauthFlow = async ({
authEndpoint,
isPKCE,
codeVerifierMethod,
+ authRequestParams,
+ refreshRequestParams,
+ tokenRequestParams,
}: AuthCodeOauthFlowParams) => {
const state = generateRandomString()
@@ -99,6 +131,24 @@ const initAuthCodeOauthFlow = async ({
codeVerifierMethod?: string
codeChallenge?: string
scopes?: string
+ authRequestParams?: Array<{
+ key: string
+ value: string
+ active: boolean
+ sendIn?: string
+ }>
+ refreshRequestParams?: Array<{
+ key: string
+ value: string
+ active: boolean
+ sendIn?: string
+ }>
+ tokenRequestParams?: Array<{
+ key: string
+ value: string
+ active: boolean
+ sendIn?: string
+ }>
} = {
state,
grant_type: "AUTHORIZATION_CODE",
@@ -109,6 +159,9 @@ const initAuthCodeOauthFlow = async ({
isPKCE,
codeVerifierMethod,
scopes,
+ authRequestParams,
+ refreshRequestParams,
+ tokenRequestParams,
}
if (codeVerifier && codeChallenge) {
@@ -160,6 +213,14 @@ const initAuthCodeOauthFlow = async ({
url.searchParams.set("code_challenge_method", codeVerifierMethod)
}
+ if (authRequestParams.length > 0) {
+ authRequestParams.forEach((param) => {
+ if (param.active && param.key && param.value) {
+ url.searchParams.set(param.key, param.value)
+ }
+ })
+ }
+
// Redirect to the authorization server
window.location.assign(url.toString())
diff --git a/packages/hoppscotch-common/src/services/persistence/__tests__/__mocks__/index.ts b/packages/hoppscotch-common/src/services/persistence/__tests__/__mocks__/index.ts
index 66334fff..b24c8ab1 100644
--- a/packages/hoppscotch-common/src/services/persistence/__tests__/__mocks__/index.ts
+++ b/packages/hoppscotch-common/src/services/persistence/__tests__/__mocks__/index.ts
@@ -25,7 +25,7 @@ const DEFAULT_SETTINGS = getDefaultSettings()
export const REST_COLLECTIONS_MOCK: HoppCollection[] = [
{
- v: 8,
+ v: 9,
name: "Echo",
requests: [
{
@@ -57,11 +57,11 @@ export const REST_COLLECTIONS_MOCK: HoppCollection[] = [
export const GQL_COLLECTIONS_MOCK: HoppCollection[] = [
{
- v: 8,
+ v: 9,
name: "Echo",
requests: [
{
- v: 8,
+ v: 9,
name: "Echo test",
url: "https://echo.hoppscotch.io/graphql",
headers: [],
@@ -182,7 +182,7 @@ export const GQL_HISTORY_MOCK: GQLHistoryEntry[] = [
{
v: 1,
request: {
- v: 8,
+ v: 9,
name: "Untitled",
url: "https://echo.hoppscotch.io/graphql",
query: "query Request { url }",
@@ -203,7 +203,7 @@ export const GQL_TAB_STATE_MOCK: PersistableTabState = {
tabID: "5edbe8d4-65c9-4381-9354-5f1bf05d8ccc",
doc: {
request: {
- v: 8,
+ v: 9,
name: "Untitled",
url: "https://echo.hoppscotch.io/graphql",
headers: [],
diff --git a/packages/hoppscotch-data/src/collection/index.ts b/packages/hoppscotch-data/src/collection/index.ts
index 5f9a009c..5542d51c 100644
--- a/packages/hoppscotch-data/src/collection/index.ts
+++ b/packages/hoppscotch-data/src/collection/index.ts
@@ -8,6 +8,7 @@ import V5_VERSION from "./v/5"
import V6_VERSION from "./v/6"
import V7_VERSION from "./v/7"
import V8_VERSION from "./v/8"
+import V9_VERSION from "./v/9"
import { z } from "zod"
import { translateToNewRequest } from "../rest"
@@ -19,7 +20,7 @@ const versionedObject = z.object({
})
export const HoppCollection = createVersionedEntity({
- latestVersion: 8,
+ latestVersion: 9,
versionMap: {
1: V1_VERSION,
2: V2_VERSION,
@@ -29,6 +30,7 @@ export const HoppCollection = createVersionedEntity({
6: V6_VERSION,
7: V7_VERSION,
8: V8_VERSION,
+ 9: V9_VERSION,
},
getVersion(data) {
const versionCheck = versionedObject.safeParse(data)
@@ -44,7 +46,7 @@ export const HoppCollection = createVersionedEntity({
export type HoppCollection = InferredEntity
-export const CollectionSchemaVersion = 8
+export const CollectionSchemaVersion = 9
/**
* Generates a Collection object. This ignores the version number object
diff --git a/packages/hoppscotch-data/src/collection/v/9.ts b/packages/hoppscotch-data/src/collection/v/9.ts
new file mode 100644
index 00000000..7fea6afa
--- /dev/null
+++ b/packages/hoppscotch-data/src/collection/v/9.ts
@@ -0,0 +1,92 @@
+import { defineVersion, entityRefUptoVersion } from "verzod"
+import { z } from "zod"
+
+import { HoppGQLAuth } from "../../graphql/v/9"
+import { HoppRESTAuth } from "../../rest/v/15/auth"
+
+import { HoppCollection } from ".."
+import { v8_baseCollectionSchema, V8_SCHEMA } from "./8"
+
+export const v9_baseCollectionSchema = v8_baseCollectionSchema.extend({
+ v: z.literal(9),
+ auth: z.union([HoppRESTAuth, HoppGQLAuth]),
+})
+
+type Input = z.input & {
+ folders: Input[]
+}
+
+type Output = z.output & {
+ folders: Output[]
+}
+
+export const V9_SCHEMA = v9_baseCollectionSchema.extend({
+ folders: z.lazy(() => z.array(entityRefUptoVersion(HoppCollection, 9))),
+}) as z.ZodType