feat: collection level headers and authorization (#3505)
Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
This commit is contained in:
@@ -95,13 +95,41 @@ export function runRESTRequest$(
|
||||
return E.left("script_fail" as const)
|
||||
}
|
||||
|
||||
const effectiveRequest = getEffectiveRESTRequest(
|
||||
tab.value.document.request,
|
||||
{
|
||||
name: "Env",
|
||||
variables: combineEnvVariables(envs.right),
|
||||
}
|
||||
)
|
||||
const requestAuth =
|
||||
tab.value.document.request.auth.authType === "inherit" &&
|
||||
tab.value.document.request.auth.authActive
|
||||
? tab.value.document.inheritedProperties?.auth.inheritedAuth
|
||||
: tab.value.document.request.auth
|
||||
|
||||
let requestHeaders
|
||||
|
||||
const inheritedHeaders =
|
||||
tab.value.document.inheritedProperties?.headers.map((header) => {
|
||||
if (header.inheritedHeader) {
|
||||
return header.inheritedHeader
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
if (inheritedHeaders) {
|
||||
requestHeaders = [
|
||||
...inheritedHeaders,
|
||||
...tab.value.document.request.headers,
|
||||
]
|
||||
} else {
|
||||
requestHeaders = [...tab.value.document.request.headers]
|
||||
}
|
||||
|
||||
const finalRequest = {
|
||||
...tab.value.document.request,
|
||||
auth: requestAuth ?? { authType: "none", authActive: false },
|
||||
headers: requestHeaders,
|
||||
}
|
||||
|
||||
const effectiveRequest = getEffectiveRESTRequest(finalRequest, {
|
||||
name: "Env",
|
||||
variables: combineEnvVariables(envs.right),
|
||||
})
|
||||
|
||||
const [stream, cancelRun] = createRESTNetworkRequestStream(effectiveRequest)
|
||||
cancelFunc = cancelRun
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
mutation UpdateTeamCollection(
|
||||
$collectionID: ID!
|
||||
$newTitle: String
|
||||
$data: String
|
||||
) {
|
||||
updateTeamCollection(
|
||||
collectionID: $collectionID
|
||||
newTitle: $newTitle
|
||||
data: $data
|
||||
) {
|
||||
id
|
||||
title
|
||||
data
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ query GetCollectionChildren($collectionID: ID!, $cursor: ID) {
|
||||
children(cursor: $cursor) {
|
||||
id
|
||||
title
|
||||
data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
query GetCollectionTitle($collectionID: ID!) {
|
||||
collection(collectionID: $collectionID) {
|
||||
title
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
query GetCollectionTitleAndData($collectionID: ID!) {
|
||||
collection(collectionID: $collectionID) {
|
||||
title
|
||||
data
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ query GetSingleCollection($collectionID: ID!) {
|
||||
collection(collectionID: $collectionID) {
|
||||
id
|
||||
title
|
||||
data
|
||||
parent {
|
||||
id
|
||||
}
|
||||
|
||||
@@ -2,5 +2,6 @@ query RootCollectionsOfTeam($teamID: ID!, $cursor: ID) {
|
||||
rootCollectionsOfTeam(teamID: $teamID, cursor: $cursor) {
|
||||
id
|
||||
title
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ subscription TeamCollectionAdded($teamID: ID!) {
|
||||
teamCollectionAdded(teamID: $teamID) {
|
||||
id
|
||||
title
|
||||
data
|
||||
parent {
|
||||
id
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ subscription TeamCollectionUpdated($teamID: ID!) {
|
||||
teamCollectionUpdated(teamID: $teamID) {
|
||||
id
|
||||
title
|
||||
data
|
||||
parent {
|
||||
id
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe, flow } from "fp-ts/function"
|
||||
import {
|
||||
HoppCollection,
|
||||
HoppRESTRequest,
|
||||
makeCollection,
|
||||
translateToNewRequest,
|
||||
} from "@hoppscotch/data"
|
||||
@@ -15,7 +14,7 @@ import {
|
||||
ExportAsJsonDocument,
|
||||
GetCollectionChildrenIDsDocument,
|
||||
GetCollectionRequestsDocument,
|
||||
GetCollectionTitleDocument,
|
||||
GetCollectionTitleAndDataDocument,
|
||||
} from "./graphql"
|
||||
|
||||
export const BACKEND_PAGE_SIZE = 10
|
||||
@@ -85,16 +84,19 @@ export const getCompleteCollectionTree = (
|
||||
pipe(
|
||||
TE.Do,
|
||||
|
||||
TE.bind("title", () =>
|
||||
TE.bind("titleAndData", () =>
|
||||
pipe(
|
||||
() =>
|
||||
runGQLQuery({
|
||||
query: GetCollectionTitleDocument,
|
||||
query: GetCollectionTitleAndDataDocument,
|
||||
variables: {
|
||||
collectionID: collID,
|
||||
},
|
||||
}),
|
||||
TE.map((x) => x.collection!.title)
|
||||
TE.map((result) => ({
|
||||
title: result.collection!.title,
|
||||
data: result.collection!.data,
|
||||
}))
|
||||
)
|
||||
),
|
||||
TE.bind("children", () =>
|
||||
@@ -108,24 +110,36 @@ export const getCompleteCollectionTree = (
|
||||
TE.bind("requests", () => () => getCollectionRequests(collID)),
|
||||
|
||||
TE.map(
|
||||
({ title, children, requests }) =>
|
||||
({ titleAndData, children, requests }) =>
|
||||
<TeamCollection>{
|
||||
id: collID,
|
||||
children,
|
||||
requests,
|
||||
title,
|
||||
title: titleAndData.title,
|
||||
data: titleAndData.data,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
export const teamCollToHoppRESTColl = (
|
||||
coll: TeamCollection
|
||||
): HoppCollection<HoppRESTRequest> =>
|
||||
makeCollection({
|
||||
): HoppCollection => {
|
||||
const data =
|
||||
coll.data && coll.data !== "null"
|
||||
? JSON.parse(coll.data)
|
||||
: {
|
||||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
}
|
||||
|
||||
return makeCollection({
|
||||
name: coll.title,
|
||||
folders: coll.children?.map(teamCollToHoppRESTColl) ?? [],
|
||||
requests: coll.requests?.map((x) => x.request) ?? [],
|
||||
auth: data.auth ?? { authType: "inherit", authActive: true },
|
||||
headers: data.headers ?? [],
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JSON string of all the collection of the specified team
|
||||
|
||||
@@ -21,6 +21,9 @@ import {
|
||||
UpdateCollectionOrderDocument,
|
||||
UpdateCollectionOrderMutation,
|
||||
UpdateCollectionOrderMutationVariables,
|
||||
UpdateTeamCollectionDocument,
|
||||
UpdateTeamCollectionMutation,
|
||||
UpdateTeamCollectionMutationVariables,
|
||||
} from "../graphql"
|
||||
|
||||
type CreateNewRootCollectionError = "team_coll/short_title"
|
||||
@@ -122,3 +125,18 @@ export const importJSONToTeam = (collectionJSON: string, teamID: string) =>
|
||||
teamID,
|
||||
}
|
||||
)
|
||||
|
||||
export const updateTeamCollection = (
|
||||
collectionID: string,
|
||||
data?: string,
|
||||
newTitle?: string
|
||||
) =>
|
||||
runMutation<
|
||||
UpdateTeamCollectionMutation,
|
||||
UpdateTeamCollectionMutationVariables,
|
||||
""
|
||||
>(UpdateTeamCollectionDocument, {
|
||||
collectionID,
|
||||
data,
|
||||
newTitle,
|
||||
})
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { HoppCollection } from "@hoppscotch/data"
|
||||
import { getAffectedIndexes } from "./affectedIndex"
|
||||
import { GetSingleRequestDocument } from "../backend/graphql"
|
||||
import { runGQLQuery } from "../backend/GQLClient"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { getService } from "~/modules/dioc"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { HoppInheritedProperty } from "../types/HoppInheritedProperties"
|
||||
import { GQLTabService } from "~/services/tab/graphql"
|
||||
|
||||
/**
|
||||
* Resolve save context on reorder
|
||||
@@ -108,6 +110,135 @@ export function updateSaveContextForAffectedRequests(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to check the new folder path is close to the save context folder path or not
|
||||
* @param folderPathCurrent The path saved as the inherited path in the inherited properties
|
||||
* @param newFolderPath The incomming path
|
||||
* @param saveContextPath The save context of the request
|
||||
* @returns The path which is close to saveContext.folderPath
|
||||
*/
|
||||
function folderPathCloseToSaveContext(
|
||||
folderPathCurrent: string | undefined,
|
||||
newFolderPath: string,
|
||||
saveContextPath: string
|
||||
) {
|
||||
if (!folderPathCurrent) return newFolderPath
|
||||
const folderPathCurrentArray = folderPathCurrent.split("/")
|
||||
const newFolderPathArray = newFolderPath.split("/")
|
||||
|
||||
const saveContextFolderPathArray = saveContextPath.split("/")
|
||||
|
||||
let folderPathCurrentMatch = 0
|
||||
|
||||
for (let i = 0; i < folderPathCurrentArray.length; i++) {
|
||||
if (folderPathCurrentArray[i] === saveContextFolderPathArray[i]) {
|
||||
folderPathCurrentMatch++
|
||||
}
|
||||
}
|
||||
|
||||
let newFolderPathMatch = 0
|
||||
|
||||
for (let i = 0; i < newFolderPathArray.length; i++) {
|
||||
if (newFolderPathArray[i] === saveContextFolderPathArray[i]) {
|
||||
newFolderPathMatch++
|
||||
}
|
||||
}
|
||||
|
||||
if (folderPathCurrentMatch > newFolderPathMatch) {
|
||||
return folderPathCurrent
|
||||
}
|
||||
return newFolderPath
|
||||
}
|
||||
|
||||
export function updateInheritedPropertiesForAffectedRequests(
|
||||
path: string,
|
||||
inheritedProperties: HoppInheritedProperty,
|
||||
type: "rest" | "graphql",
|
||||
workspace: "personal" | "team" = "personal"
|
||||
) {
|
||||
const tabService =
|
||||
type === "rest" ? getService(RESTTabService) : getService(GQLTabService)
|
||||
|
||||
let tabs
|
||||
if (workspace === "personal") {
|
||||
tabs = tabService.getTabsRefTo((tab) => {
|
||||
return (
|
||||
tab.document.saveContext?.originLocation === "user-collection" &&
|
||||
tab.document.saveContext.folderPath.startsWith(path)
|
||||
)
|
||||
})
|
||||
} else {
|
||||
tabs = tabService.getTabsRefTo((tab) => {
|
||||
return (
|
||||
tab.document.saveContext?.originLocation === "team-collection" &&
|
||||
tab.document.saveContext.collectionID?.startsWith(path)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const tabsEffectedByAuth = tabs.filter((tab) => {
|
||||
if (workspace === "personal") {
|
||||
return (
|
||||
tab.value.document.saveContext?.originLocation === "user-collection" &&
|
||||
tab.value.document.saveContext.folderPath.startsWith(path) &&
|
||||
path ===
|
||||
folderPathCloseToSaveContext(
|
||||
tab.value.document.inheritedProperties?.auth.parentID,
|
||||
path,
|
||||
tab.value.document.saveContext.folderPath
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
tab.value.document.saveContext?.originLocation === "team-collection" &&
|
||||
tab.value.document.saveContext.collectionID?.startsWith(path) &&
|
||||
path ===
|
||||
folderPathCloseToSaveContext(
|
||||
tab.value.document.inheritedProperties?.auth.parentID,
|
||||
path,
|
||||
tab.value.document.saveContext.collectionID
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
const tabsEffectedByHeaders = tabs.filter((tab) => {
|
||||
return (
|
||||
tab.value.document.inheritedProperties &&
|
||||
tab.value.document.inheritedProperties.headers.some(
|
||||
(header) => header.parentID === path
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
for (const tab of tabsEffectedByAuth) {
|
||||
tab.value.document.inheritedProperties = inheritedProperties
|
||||
}
|
||||
|
||||
for (const tab of tabsEffectedByHeaders) {
|
||||
const headers = tab.value.document.inheritedProperties?.headers.map(
|
||||
(header) => {
|
||||
if (header.parentID === path) {
|
||||
return {
|
||||
...header,
|
||||
inheritedHeader: inheritedProperties.headers.find(
|
||||
(inheritedHeader) =>
|
||||
inheritedHeader.inheritedHeader?.key ===
|
||||
header.inheritedHeader?.key
|
||||
)?.inheritedHeader,
|
||||
}
|
||||
}
|
||||
return header
|
||||
}
|
||||
)
|
||||
|
||||
tab.value.document.inheritedProperties = {
|
||||
...tab.value.document.inheritedProperties,
|
||||
headers,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resetSaveContextForAffectedRequests(folderPath: string) {
|
||||
const tabService = getService(RESTTabService)
|
||||
const tabs = tabService.getTabsRefTo((tab) => {
|
||||
@@ -152,9 +283,9 @@ export async function resetTeamRequestsContext() {
|
||||
}
|
||||
|
||||
export function getFoldersByPath(
|
||||
collections: HoppCollection<HoppRESTRequest>[],
|
||||
collections: HoppCollection[],
|
||||
path: string
|
||||
): HoppCollection<HoppRESTRequest>[] {
|
||||
): HoppCollection[] {
|
||||
if (!path) return collections
|
||||
|
||||
// path will be like this "0/0/1" these are the indexes of the folders
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import {
|
||||
HoppCollection,
|
||||
HoppGQLRequest,
|
||||
HoppRESTRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import { getAffectedIndexes } from "./affectedIndex"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { getService } from "~/modules/dioc"
|
||||
@@ -53,9 +57,9 @@ export function resolveSaveContextOnRequestReorder(payload: {
|
||||
}
|
||||
|
||||
export function getRequestsByPath(
|
||||
collections: HoppCollection<HoppRESTRequest>[],
|
||||
collections: HoppCollection[],
|
||||
path: string
|
||||
): HoppRESTRequest[] {
|
||||
): HoppRESTRequest[] | HoppGQLRequest[] {
|
||||
// path will be like this "0/0/1" these are the indexes of the folders
|
||||
const pathArray = path.split("/").map((index) => parseInt(index))
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ export const getDefaultGQLRequest = (): HoppGQLRequest => ({
|
||||
}`,
|
||||
query: DEFAULT_QUERY,
|
||||
auth: {
|
||||
authType: "none",
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { HoppGQLRequest } from "@hoppscotch/data"
|
||||
import { GQLResponseEvent } from "./connection"
|
||||
import { GQLOptionTabs } from "~/components/graphql/RequestOptions.vue"
|
||||
import { HoppInheritedProperty } from "../types/HoppInheritedProperties"
|
||||
|
||||
export type HoppGQLSaveContext =
|
||||
| {
|
||||
@@ -73,4 +74,10 @@ export type HoppGQLDocument = {
|
||||
* Options tab preference for the current tab's document
|
||||
*/
|
||||
optionTabPreference?: GQLOptionTabs
|
||||
|
||||
/**
|
||||
* The inherited properties from the parent collection
|
||||
* (if any)
|
||||
*/
|
||||
inheritedProperties?: HoppInheritedProperty
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { HoppCollection, HoppGQLRequest } from "@hoppscotch/data"
|
||||
import { HoppCollection } from "@hoppscotch/data"
|
||||
|
||||
export const gqlCollectionsExporter = (
|
||||
gqlCollections: HoppCollection<HoppGQLRequest>[]
|
||||
) => {
|
||||
export const gqlCollectionsExporter = (gqlCollections: HoppCollection[]) => {
|
||||
return JSON.stringify(gqlCollections, null, 2)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { HoppCollection } from "@hoppscotch/data"
|
||||
|
||||
export const myCollectionsExporter = (
|
||||
myCollections: HoppCollection<HoppRESTRequest>[]
|
||||
) => {
|
||||
export const myCollectionsExporter = (myCollections: HoppCollection[]) => {
|
||||
return JSON.stringify(myCollections, null, 2)
|
||||
}
|
||||
|
||||
@@ -2,15 +2,12 @@ import { pipe, flow } from "fp-ts/function"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import * as O from "fp-ts/Option"
|
||||
import * as RA from "fp-ts/ReadonlyArray"
|
||||
import {
|
||||
translateToNewRESTCollection,
|
||||
HoppCollection,
|
||||
HoppRESTRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import { translateToNewRESTCollection, HoppCollection } from "@hoppscotch/data"
|
||||
import { isPlainObject as _isPlainObject } from "lodash-es"
|
||||
|
||||
import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
||||
import { safeParseJSON } from "~/helpers/functional/json"
|
||||
import { translateToNewGQLCollection } from "@hoppscotch/data"
|
||||
|
||||
export const hoppRESTImporter = (content: string) =>
|
||||
pipe(
|
||||
@@ -33,12 +30,10 @@ const isPlainObject = (value: any): value is object => _isPlainObject(value)
|
||||
|
||||
/**
|
||||
* checks if a collection matches the schema for a hoppscotch collection.
|
||||
* as of now we are only checking if the collection has a "v" key in it.
|
||||
* here 2 is the latest version of the schema.
|
||||
*/
|
||||
const isValidCollection = (
|
||||
collection: unknown
|
||||
): collection is HoppCollection<HoppRESTRequest> =>
|
||||
isPlainObject(collection) && "v" in collection
|
||||
const isValidCollection = (collection: unknown): collection is HoppCollection =>
|
||||
isPlainObject(collection) && "v" in collection && collection.v === 2
|
||||
|
||||
/**
|
||||
* checks if a collection is a valid hoppscotch collection.
|
||||
@@ -56,3 +51,29 @@ const validateCollection = (collection: unknown) => {
|
||||
*/
|
||||
const makeCollectionsArray = (collections: unknown | unknown[]): unknown[] =>
|
||||
Array.isArray(collections) ? collections : [collections]
|
||||
|
||||
export const hoppGQLImporter = (content: string) =>
|
||||
pipe(
|
||||
safeParseJSON(content),
|
||||
O.chain(
|
||||
flow(
|
||||
makeCollectionsArray,
|
||||
RA.map(validateGQLCollection),
|
||||
O.sequenceArray,
|
||||
O.map(RA.toArray)
|
||||
)
|
||||
),
|
||||
TE.fromOption(() => IMPORTER_INVALID_FILE_FORMAT)
|
||||
)
|
||||
|
||||
/**
|
||||
*
|
||||
* @param collection the collection to validate
|
||||
* @returns the collection if it is valid, else a translated version of the collection
|
||||
*/
|
||||
export const validateGQLCollection = (collection: unknown) => {
|
||||
if (isValidCollection(collection)) {
|
||||
return O.some(collection)
|
||||
}
|
||||
return O.some(translateToNewGQLCollection(collection))
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { HoppCollection, HoppGQLRequest } from "@hoppscotch/data"
|
||||
import { HoppCollection } from "@hoppscotch/data"
|
||||
import * as E from "fp-ts/Either"
|
||||
|
||||
// TODO: add zod validation
|
||||
export const hoppGqlCollectionsImporter = (
|
||||
content: string
|
||||
): E.Either<"INVALID_JSON", HoppCollection<HoppGQLRequest>[]> => {
|
||||
): E.Either<"INVALID_JSON", HoppCollection[]> => {
|
||||
return E.tryCatch(
|
||||
() => JSON.parse(content) as HoppCollection<HoppGQLRequest>[],
|
||||
() => JSON.parse(content) as HoppCollection[],
|
||||
() => "INVALID_JSON"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ const getHoppRequest = (req: InsomniaRequestResource): HoppRESTRequest =>
|
||||
const getHoppFolder = (
|
||||
folderRes: InsomniaFolderResource,
|
||||
resources: InsomniaResource[]
|
||||
): HoppCollection<HoppRESTRequest> =>
|
||||
): HoppCollection =>
|
||||
makeCollection({
|
||||
name: folderRes.name ?? "",
|
||||
folders: getFoldersIn(folderRes, resources).map((f) =>
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
HoppRESTHeader,
|
||||
HoppRESTParam,
|
||||
HoppRESTReqBody,
|
||||
HoppRESTRequest,
|
||||
knownContentTypes,
|
||||
makeRESTRequest,
|
||||
HoppCollection,
|
||||
@@ -581,7 +580,7 @@ const convertPathToHoppReqs = (
|
||||
|
||||
const convertOpenApiDocToHopp = (
|
||||
doc: OpenAPI.Document
|
||||
): TE.TaskEither<never, HoppCollection<HoppRESTRequest>[]> => {
|
||||
): TE.TaskEither<never, HoppCollection[]> => {
|
||||
const name = doc.info.title
|
||||
|
||||
const paths = Object.entries(doc.paths ?? {})
|
||||
@@ -589,7 +588,7 @@ const convertOpenApiDocToHopp = (
|
||||
.flat()
|
||||
|
||||
return TE.of([
|
||||
makeCollection<HoppRESTRequest>({
|
||||
makeCollection({
|
||||
name,
|
||||
folders: [],
|
||||
requests: paths,
|
||||
|
||||
@@ -283,7 +283,7 @@ const getHoppRequest = (item: Item): HoppRESTRequest => {
|
||||
})
|
||||
}
|
||||
|
||||
const getHoppFolder = (ig: ItemGroup<Item>): HoppCollection<HoppRESTRequest> =>
|
||||
const getHoppFolder = (ig: ItemGroup<Item>): HoppCollection =>
|
||||
makeCollection({
|
||||
name: ig.name,
|
||||
folders: pipe(
|
||||
|
||||
@@ -2,6 +2,7 @@ import { HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { HoppRESTResponse } from "../types/HoppRESTResponse"
|
||||
import { HoppTestResult } from "../types/HoppTestResult"
|
||||
import { RESTOptionTabs } from "~/components/http/RequestOptions.vue"
|
||||
import { HoppInheritedProperty } from "../types/HoppInheritedProperties"
|
||||
|
||||
export type HoppRESTSaveContext =
|
||||
| {
|
||||
@@ -80,4 +81,10 @@ export type HoppRESTDocument = {
|
||||
* Options tab preference for the current tab's document
|
||||
*/
|
||||
optionTabPreference?: RESTOptionTabs
|
||||
|
||||
/**
|
||||
* The inherited properties from the parent collection
|
||||
* (if any)
|
||||
*/
|
||||
inheritedProperties?: HoppInheritedProperty
|
||||
}
|
||||
|
||||
@@ -8,4 +8,5 @@ export interface TeamCollection {
|
||||
title: string
|
||||
children: TeamCollection[] | null
|
||||
requests: TeamRequest[] | null
|
||||
data: string | null
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import * as E from "fp-ts/Either"
|
||||
import { BehaviorSubject, Subscription } from "rxjs"
|
||||
import { translateToNewRequest } from "@hoppscotch/data"
|
||||
import {
|
||||
HoppRESTAuth,
|
||||
HoppRESTHeader,
|
||||
translateToNewRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import { pull, remove } from "lodash-es"
|
||||
import { Subscription as WSubscription } from "wonka"
|
||||
import { runGQLQuery, runGQLSubscription } from "../backend/GQLClient"
|
||||
@@ -21,6 +25,7 @@ import {
|
||||
TeamRequestOrderUpdatedDocument,
|
||||
TeamCollectionOrderUpdatedDocument,
|
||||
} from "~/helpers/backend/graphql"
|
||||
import { HoppInheritedProperty } from "../types/HoppInheritedProperties"
|
||||
|
||||
const TEAMS_BACKEND_PAGE_SIZE = 10
|
||||
|
||||
@@ -542,6 +547,7 @@ export default class NewTeamCollectionAdapter {
|
||||
children: null,
|
||||
requests: null,
|
||||
title: title,
|
||||
data: null,
|
||||
},
|
||||
parentID ?? null
|
||||
)
|
||||
@@ -693,6 +699,7 @@ export default class NewTeamCollectionAdapter {
|
||||
children: null,
|
||||
requests: null,
|
||||
title: result.right.teamCollectionAdded.title,
|
||||
data: result.right.teamCollectionAdded.data ?? null,
|
||||
},
|
||||
result.right.teamCollectionAdded.parent?.id ?? null
|
||||
)
|
||||
@@ -715,6 +722,7 @@ export default class NewTeamCollectionAdapter {
|
||||
this.updateCollection({
|
||||
id: result.right.teamCollectionUpdated.id,
|
||||
title: result.right.teamCollectionUpdated.title,
|
||||
data: result.right.teamCollectionUpdated.data,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -931,6 +939,7 @@ export default class NewTeamCollectionAdapter {
|
||||
<TeamCollection>{
|
||||
id: el.id,
|
||||
title: el.title,
|
||||
data: el.data,
|
||||
children: null,
|
||||
requests: null,
|
||||
}
|
||||
@@ -1024,4 +1033,104 @@ export default class NewTeamCollectionAdapter {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public cascadeParentCollectionForHeaderAuth(folderPath: string) {
|
||||
let auth: HoppInheritedProperty["auth"] = {
|
||||
parentID: folderPath ?? "",
|
||||
parentName: "",
|
||||
inheritedAuth: {
|
||||
authType: "none",
|
||||
authActive: true,
|
||||
},
|
||||
}
|
||||
const headers: HoppInheritedProperty["headers"] = []
|
||||
|
||||
if (!folderPath) return { auth, headers }
|
||||
|
||||
const path = folderPath.split("/")
|
||||
|
||||
// Check if the path is empty or invalid
|
||||
if (!path || path.length === 0) {
|
||||
console.error("Invalid path:", folderPath)
|
||||
return { auth, headers }
|
||||
}
|
||||
|
||||
// Loop through the path and get the last parent folder with authType other than 'inherit'
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
const parentFolder = findCollInTree(this.collections$.value, path[i])
|
||||
|
||||
// Check if parentFolder is undefined or null
|
||||
if (!parentFolder) {
|
||||
console.error("Parent folder not found for path:", path)
|
||||
return { auth, headers }
|
||||
}
|
||||
|
||||
const data: {
|
||||
auth: HoppRESTAuth
|
||||
headers: HoppRESTHeader[]
|
||||
} = parentFolder.data
|
||||
? JSON.parse(parentFolder.data)
|
||||
: {
|
||||
auth: null,
|
||||
headers: null,
|
||||
}
|
||||
|
||||
if (!data.auth) {
|
||||
data.auth = {
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
}
|
||||
auth.parentID = [...path.slice(0, i + 1)].join("/")
|
||||
auth.parentName = parentFolder.title
|
||||
}
|
||||
|
||||
if (!data.headers) data.headers = []
|
||||
|
||||
const parentFolderAuth = data.auth
|
||||
const parentFolderHeaders = data.headers
|
||||
|
||||
if (parentFolderAuth?.authType === "inherit" && path.length === 1) {
|
||||
auth = {
|
||||
parentID: [...path.slice(0, i + 1)].join("/"),
|
||||
parentName: parentFolder.title,
|
||||
inheritedAuth: auth.inheritedAuth,
|
||||
}
|
||||
}
|
||||
|
||||
if (parentFolderAuth?.authType !== "inherit") {
|
||||
auth = {
|
||||
parentID: [...path.slice(0, i + 1)].join("/"),
|
||||
parentName: parentFolder.title,
|
||||
inheritedAuth: parentFolderAuth,
|
||||
}
|
||||
}
|
||||
|
||||
// Update headers, overwriting duplicates by key
|
||||
if (parentFolderHeaders) {
|
||||
const activeHeaders = parentFolderHeaders.filter((h) => h.active)
|
||||
activeHeaders.forEach((header) => {
|
||||
const index = headers.findIndex(
|
||||
(h) => h.inheritedHeader?.key === header.key
|
||||
)
|
||||
const currentPath = [...path.slice(0, i + 1)].join("/")
|
||||
if (index !== -1) {
|
||||
// Replace the existing header with the same key
|
||||
headers[index] = {
|
||||
parentID: currentPath,
|
||||
parentName: parentFolder.title,
|
||||
inheritedHeader: header,
|
||||
}
|
||||
} else {
|
||||
headers.push({
|
||||
parentID: currentPath,
|
||||
parentName: parentFolder.title,
|
||||
inheritedHeader: header,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return { auth, headers }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import {
|
||||
GQLHeader,
|
||||
HoppGQLAuth,
|
||||
HoppRESTHeader,
|
||||
HoppRESTAuth,
|
||||
} from "@hoppscotch/data"
|
||||
|
||||
export type HoppInheritedProperty = {
|
||||
auth: {
|
||||
parentID: string
|
||||
parentName: string
|
||||
inheritedAuth: HoppRESTAuth | HoppGQLAuth
|
||||
}
|
||||
headers: {
|
||||
parentID: string
|
||||
parentName: string
|
||||
inheritedHeader: HoppRESTHeader | GQLHeader
|
||||
}[]
|
||||
}
|
||||
@@ -42,22 +42,26 @@ export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
|
||||
* @param envVars Currently active environment variables
|
||||
* @returns The list of headers
|
||||
*/
|
||||
const getComputedAuthHeaders = (
|
||||
req: HoppRESTRequest,
|
||||
envVars: Environment["variables"]
|
||||
export const getComputedAuthHeaders = (
|
||||
envVars: Environment["variables"],
|
||||
req?: HoppRESTRequest,
|
||||
auth?: HoppRESTRequest["auth"]
|
||||
) => {
|
||||
const request = auth ? { auth: auth ?? { authActive: false } } : req
|
||||
// If Authorization header is also being user-defined, that takes priority
|
||||
if (req.headers.find((h) => h.key.toLowerCase() === "authorization"))
|
||||
if (req && req.headers.find((h) => h.key.toLowerCase() === "authorization"))
|
||||
return []
|
||||
|
||||
if (!req.auth.authActive) return []
|
||||
if (!request) return []
|
||||
|
||||
if (!request.auth || !request.auth.authActive) return []
|
||||
|
||||
const headers: HoppRESTHeader[] = []
|
||||
|
||||
// TODO: Support a better b64 implementation than btoa ?
|
||||
if (req.auth.authType === "basic") {
|
||||
const username = parseTemplateString(req.auth.username, envVars)
|
||||
const password = parseTemplateString(req.auth.password, envVars)
|
||||
if (request.auth.authType === "basic") {
|
||||
const username = parseTemplateString(request.auth.username, envVars)
|
||||
const password = parseTemplateString(request.auth.password, envVars)
|
||||
|
||||
headers.push({
|
||||
active: true,
|
||||
@@ -65,22 +69,21 @@ const getComputedAuthHeaders = (
|
||||
value: `Basic ${btoa(`${username}:${password}`)}`,
|
||||
})
|
||||
} else if (
|
||||
req.auth.authType === "bearer" ||
|
||||
req.auth.authType === "oauth-2"
|
||||
request.auth.authType === "bearer" ||
|
||||
request.auth.authType === "oauth-2"
|
||||
) {
|
||||
headers.push({
|
||||
active: true,
|
||||
key: "Authorization",
|
||||
value: `Bearer ${parseTemplateString(req.auth.token, envVars)}`,
|
||||
value: `Bearer ${parseTemplateString(request.auth.token, envVars)}`,
|
||||
})
|
||||
} else if (req.auth.authType === "api-key") {
|
||||
const { key, value, addTo } = req.auth
|
||||
|
||||
if (addTo === "Headers") {
|
||||
} else if (request.auth.authType === "api-key") {
|
||||
const { key, addTo } = request.auth
|
||||
if (addTo === "Headers" && key) {
|
||||
headers.push({
|
||||
active: true,
|
||||
key: parseTemplateString(key, envVars),
|
||||
value: parseTemplateString(value, envVars),
|
||||
value: parseTemplateString(request.auth.value ?? "", envVars),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -131,16 +134,18 @@ export type ComputedHeader = {
|
||||
export const getComputedHeaders = (
|
||||
req: HoppRESTRequest,
|
||||
envVars: Environment["variables"]
|
||||
): ComputedHeader[] => [
|
||||
...getComputedAuthHeaders(req, envVars).map((header) => ({
|
||||
source: "auth" as const,
|
||||
header,
|
||||
})),
|
||||
...getComputedBodyHeaders(req).map((header) => ({
|
||||
source: "body" as const,
|
||||
header,
|
||||
})),
|
||||
]
|
||||
): ComputedHeader[] => {
|
||||
return [
|
||||
...getComputedAuthHeaders(envVars, req).map((header) => ({
|
||||
source: "auth" as const,
|
||||
header,
|
||||
})),
|
||||
...getComputedBodyHeaders(req).map((header) => ({
|
||||
source: "body" as const,
|
||||
header,
|
||||
})),
|
||||
]
|
||||
}
|
||||
|
||||
export type ComputedParam = {
|
||||
source: "auth"
|
||||
@@ -160,7 +165,7 @@ export const getComputedParams = (
|
||||
): ComputedParam[] => {
|
||||
// When this gets complex, its best to split this function off (like with getComputedHeaders)
|
||||
// API-key auth can be added to query params
|
||||
if (!req.auth.authActive) return []
|
||||
if (!req.auth || !req.auth.authActive) return []
|
||||
if (req.auth.authType !== "api-key") return []
|
||||
if (req.auth.addTo !== "Query params") return []
|
||||
|
||||
|
||||
Reference in New Issue
Block a user