feat: ability to refresh tokens for oauth flows (#4302)
Co-authored-by: jamesgeorge007 <25279263+jamesgeorge007@users.noreply.github.com>
This commit is contained in:
@@ -159,6 +159,30 @@ describe("hopp test [options] <file_path_or_id>", () => {
|
|||||||
|
|
||||||
expect(error).toBeNull();
|
expect(error).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("OAuth 2 Authorization type with Authorization Code Grant Type", () => {
|
||||||
|
test("Successfully translates the authorization information to headers/query params and sends it along with the request", async () => {
|
||||||
|
const args = `test ${getTestJsonFilePath(
|
||||||
|
"oauth2-auth-code-coll.json",
|
||||||
|
"collection"
|
||||||
|
)}`;
|
||||||
|
const { error } = await runCLI(args);
|
||||||
|
|
||||||
|
expect(error).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("multipart/form-data content type", () => {
|
||||||
|
test("Successfully derives the relevant headers based and sends the form data in the request body", async () => {
|
||||||
|
const args = `test ${getTestJsonFilePath(
|
||||||
|
"oauth2-auth-code-coll.json",
|
||||||
|
"collection"
|
||||||
|
)}`;
|
||||||
|
const { error } = await runCLI(args);
|
||||||
|
|
||||||
|
expect(error).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Test `hopp test <file_path_or_id> --env <file_path_or_id>` command:", () => {
|
describe("Test `hopp test <file_path_or_id> --env <file_path_or_id>` command:", () => {
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"v": 3,
|
||||||
|
"name": "Multpart form data content type - Collection",
|
||||||
|
"folders": [],
|
||||||
|
"requests": [
|
||||||
|
{
|
||||||
|
"v": "7",
|
||||||
|
"endpoint": "https://echo.hoppscotch.io",
|
||||||
|
"name": "multipart-form-data-sample-req",
|
||||||
|
"params": [],
|
||||||
|
"headers": [],
|
||||||
|
"method": "POST",
|
||||||
|
"auth": {
|
||||||
|
"authType": "none",
|
||||||
|
"authActive": true,
|
||||||
|
"addTo": "HEADERS",
|
||||||
|
"grantTypeInfo": {
|
||||||
|
"authEndpoint": "test-authorization-endpoint",
|
||||||
|
"tokenEndpoint": "test-token-endpont",
|
||||||
|
"clientID": "test-client-id",
|
||||||
|
"clientSecret": "test-client-secret",
|
||||||
|
"isPKCE": true,
|
||||||
|
"codeVerifierMethod": "S256",
|
||||||
|
"grantType": "AUTHORIZATION_CODE",
|
||||||
|
"token": "test-token"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"preRequestScript": "",
|
||||||
|
"testScript": "pw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test(\"Successfully derives the relevant headers based on the content type\", () => {\n pw.expect(pw.response.body.headers['content-type']).toInclude(\"multipart/form-data\");\n});\n\npw.test(\"Successfully sends the form data in the request body\", () => {\n // Dynamic value\n pw.expect(pw.response.body.data).toBeType(\"string\");\n});",
|
||||||
|
"body": {
|
||||||
|
"contentType": "multipart/form-data",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"key": "key1",
|
||||||
|
"value": "value1",
|
||||||
|
"active": true,
|
||||||
|
"isFile": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "key2",
|
||||||
|
"value": [{}],
|
||||||
|
"active": true,
|
||||||
|
"isFile": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"requestVariables": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"auth": {
|
||||||
|
"authType": "none",
|
||||||
|
"authActive": true
|
||||||
|
},
|
||||||
|
"headers": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"v": 3,
|
||||||
|
"name": "OAuth2 Authorization Code Grant Type - Collection",
|
||||||
|
"folders": [],
|
||||||
|
"requests": [
|
||||||
|
{
|
||||||
|
"v": "7",
|
||||||
|
"endpoint": "https://echo.hoppscotch.io",
|
||||||
|
"name": "oauth2-auth-code-sample-req-pass-by-headers",
|
||||||
|
"params": [],
|
||||||
|
"headers": [],
|
||||||
|
"method": "GET",
|
||||||
|
"auth": {
|
||||||
|
"authType": "oauth-2",
|
||||||
|
"authActive": true,
|
||||||
|
"addTo": "HEADERS",
|
||||||
|
"grantTypeInfo": {
|
||||||
|
"authEndpoint": "test-authorization-endpoint",
|
||||||
|
"tokenEndpoint": "test-token-endpont",
|
||||||
|
"clientID": "test-client-id",
|
||||||
|
"clientSecret": "test-client-secret",
|
||||||
|
"isPKCE": true,
|
||||||
|
"codeVerifierMethod": "S256",
|
||||||
|
"grantType": "AUTHORIZATION_CODE",
|
||||||
|
"token": "test-token"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"preRequestScript": "",
|
||||||
|
"testScript": "pw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test(\"Successfully derives Authorization header from the supplied fields\", ()=> {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBeType(\"string\");\n});",
|
||||||
|
"body": {
|
||||||
|
"contentType": null,
|
||||||
|
"body": null
|
||||||
|
},
|
||||||
|
"requestVariables": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"v": "7",
|
||||||
|
"endpoint": "https://echo.hoppscotch.io",
|
||||||
|
"name": "oauth2-auth-code-sample-req-pass-by-query-params",
|
||||||
|
"params": [],
|
||||||
|
"headers": [],
|
||||||
|
"method": "GET",
|
||||||
|
"auth": {
|
||||||
|
"authType": "oauth-2",
|
||||||
|
"authActive": true,
|
||||||
|
"addTo": "HEADERS",
|
||||||
|
"grantTypeInfo": {
|
||||||
|
"authEndpoint": "test-authorization-endpoint",
|
||||||
|
"tokenEndpoint": "test-token-endpont",
|
||||||
|
"clientID": "test-client-id",
|
||||||
|
"clientSecret": "test-client-secret",
|
||||||
|
"isPKCE": true,
|
||||||
|
"codeVerifierMethod": "S256",
|
||||||
|
"grantType": "AUTHORIZATION_CODE",
|
||||||
|
"token": "test-token"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"preRequestScript": "",
|
||||||
|
"testScript": "pw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test(\"Successfully derives Authorization header from the supplied fields\", ()=> {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBeType(\"string\");\n});",
|
||||||
|
"body": {
|
||||||
|
"contentType": null,
|
||||||
|
"body": null
|
||||||
|
},
|
||||||
|
"requestVariables": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"auth": {
|
||||||
|
"authType": "none",
|
||||||
|
"authActive": true
|
||||||
|
},
|
||||||
|
"headers": []
|
||||||
|
}
|
||||||
@@ -15,7 +15,6 @@ describe("requestRunner", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
SAMPLE_REQUEST_CONFIG.supported = false;
|
|
||||||
SAMPLE_REQUEST_CONFIG.url = "https://example.com";
|
SAMPLE_REQUEST_CONFIG.url = "https://example.com";
|
||||||
SAMPLE_REQUEST_CONFIG.method = "GET";
|
SAMPLE_REQUEST_CONFIG.method = "GET";
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
@@ -70,7 +69,6 @@ describe("requestRunner", () => {
|
|||||||
|
|
||||||
it("Should handle axios-error with request info.", () => {
|
it("Should handle axios-error with request info.", () => {
|
||||||
jest.spyOn(axios, "isAxiosError").mockReturnValue(true);
|
jest.spyOn(axios, "isAxiosError").mockReturnValue(true);
|
||||||
SAMPLE_REQUEST_CONFIG.supported = true;
|
|
||||||
(axios as unknown as jest.Mock).mockRejectedValueOnce(<AxiosError>{
|
(axios as unknown as jest.Mock).mockRejectedValueOnce(<AxiosError>{
|
||||||
name: "name",
|
name: "name",
|
||||||
message: "message",
|
message: "message",
|
||||||
@@ -91,7 +89,6 @@ describe("requestRunner", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Should successfully execute.", () => {
|
it("Should successfully execute.", () => {
|
||||||
SAMPLE_REQUEST_CONFIG.supported = true;
|
|
||||||
(axios as unknown as jest.Mock).mockResolvedValue(<AxiosResponse>{
|
(axios as unknown as jest.Mock).mockResolvedValue(<AxiosResponse>{
|
||||||
data: "data",
|
data: "data",
|
||||||
status: 200,
|
status: 200,
|
||||||
|
|||||||
@@ -20,8 +20,7 @@ export interface RequestStack {
|
|||||||
* @property {boolean} supported - Boolean check for supported or unsupported requests.
|
* @property {boolean} supported - Boolean check for supported or unsupported requests.
|
||||||
*/
|
*/
|
||||||
export interface RequestConfig extends AxiosRequestConfig {
|
export interface RequestConfig extends AxiosRequestConfig {
|
||||||
supported: boolean;
|
displayUrl?: string;
|
||||||
displayUrl?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
|
export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
|
||||||
@@ -32,7 +31,17 @@ export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
|
|||||||
*/
|
*/
|
||||||
effectiveFinalURL: string;
|
effectiveFinalURL: string;
|
||||||
effectiveFinalDisplayURL?: string;
|
effectiveFinalDisplayURL?: string;
|
||||||
effectiveFinalHeaders: { key: string; value: string; active: boolean }[];
|
effectiveFinalHeaders: {
|
||||||
effectiveFinalParams: { key: string; value: string; active: boolean }[];
|
key: string;
|
||||||
|
value: string;
|
||||||
|
active: boolean;
|
||||||
|
description: string;
|
||||||
|
}[];
|
||||||
|
effectiveFinalParams: {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
active: boolean;
|
||||||
|
description: string;
|
||||||
|
}[];
|
||||||
effectiveFinalBody: FormData | string | null;
|
effectiveFinalBody: FormData | string | null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,11 +72,12 @@ export const getEffectiveFinalMetaData = (
|
|||||||
* Selecting only non-empty and active pairs.
|
* Selecting only non-empty and active pairs.
|
||||||
*/
|
*/
|
||||||
A.filter(({ key, active }) => !S.isEmpty(key) && active),
|
A.filter(({ key, active }) => !S.isEmpty(key) && active),
|
||||||
A.map(({ key, value }) => {
|
A.map(({ key, value, description }) => {
|
||||||
return {
|
return {
|
||||||
active: true,
|
active: true,
|
||||||
key: parseTemplateStringE(key, resolvedVariables),
|
key: parseTemplateStringE(key, resolvedVariables),
|
||||||
value: parseTemplateStringE(value, resolvedVariables),
|
value: parseTemplateStringE(value, resolvedVariables),
|
||||||
|
description,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
E.fromPredicate(
|
E.fromPredicate(
|
||||||
@@ -91,9 +92,14 @@ export const getEffectiveFinalMetaData = (
|
|||||||
/**
|
/**
|
||||||
* Filtering and mapping only right-eithers for each key-value as [string, string].
|
* Filtering and mapping only right-eithers for each key-value as [string, string].
|
||||||
*/
|
*/
|
||||||
A.filterMap(({ key, value }) =>
|
A.filterMap(({ key, value, description }) =>
|
||||||
E.isRight(key) && E.isRight(value)
|
E.isRight(key) && E.isRight(value)
|
||||||
? O.some({ active: true, key: key.right, value: value.right })
|
? O.some({
|
||||||
|
active: true,
|
||||||
|
key: key.right,
|
||||||
|
value: value.right,
|
||||||
|
description,
|
||||||
|
})
|
||||||
: O.none
|
: O.none
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -123,12 +123,14 @@ export function getEffectiveRESTRequest(
|
|||||||
active: true,
|
active: true,
|
||||||
key: "Authorization",
|
key: "Authorization",
|
||||||
value: `Basic ${btoa(`${username}:${password}`)}`,
|
value: `Basic ${btoa(`${username}:${password}`)}`,
|
||||||
|
description: "",
|
||||||
});
|
});
|
||||||
} else if (request.auth.authType === "bearer") {
|
} else if (request.auth.authType === "bearer") {
|
||||||
effectiveFinalHeaders.push({
|
effectiveFinalHeaders.push({
|
||||||
active: true,
|
active: true,
|
||||||
key: "Authorization",
|
key: "Authorization",
|
||||||
value: `Bearer ${parseTemplateString(request.auth.token, resolvedVariables)}`,
|
value: `Bearer ${parseTemplateString(request.auth.token, resolvedVariables)}`,
|
||||||
|
description: "",
|
||||||
});
|
});
|
||||||
} else if (request.auth.authType === "oauth-2") {
|
} else if (request.auth.authType === "oauth-2") {
|
||||||
const { addTo } = request.auth;
|
const { addTo } = request.auth;
|
||||||
@@ -138,6 +140,7 @@ export function getEffectiveRESTRequest(
|
|||||||
active: true,
|
active: true,
|
||||||
key: "Authorization",
|
key: "Authorization",
|
||||||
value: `Bearer ${parseTemplateString(request.auth.grantTypeInfo.token, resolvedVariables)}`,
|
value: `Bearer ${parseTemplateString(request.auth.grantTypeInfo.token, resolvedVariables)}`,
|
||||||
|
description: "",
|
||||||
});
|
});
|
||||||
} else if (addTo === "QUERY_PARAMS") {
|
} else if (addTo === "QUERY_PARAMS") {
|
||||||
effectiveFinalParams.push({
|
effectiveFinalParams.push({
|
||||||
@@ -147,6 +150,7 @@ export function getEffectiveRESTRequest(
|
|||||||
request.auth.grantTypeInfo.token,
|
request.auth.grantTypeInfo.token,
|
||||||
resolvedVariables
|
resolvedVariables
|
||||||
),
|
),
|
||||||
|
description: "",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (request.auth.authType === "api-key") {
|
} else if (request.auth.authType === "api-key") {
|
||||||
@@ -156,12 +160,14 @@ export function getEffectiveRESTRequest(
|
|||||||
active: true,
|
active: true,
|
||||||
key: parseTemplateString(key, resolvedVariables),
|
key: parseTemplateString(key, resolvedVariables),
|
||||||
value: parseTemplateString(value, resolvedVariables),
|
value: parseTemplateString(value, resolvedVariables),
|
||||||
|
description: "",
|
||||||
});
|
});
|
||||||
} else if (addTo === "QUERY_PARAMS") {
|
} else if (addTo === "QUERY_PARAMS") {
|
||||||
effectiveFinalParams.push({
|
effectiveFinalParams.push({
|
||||||
active: true,
|
active: true,
|
||||||
key: parseTemplateString(key, resolvedVariables),
|
key: parseTemplateString(key, resolvedVariables),
|
||||||
value: parseTemplateString(value, resolvedVariables),
|
value: parseTemplateString(value, resolvedVariables),
|
||||||
|
description: "",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,6 +193,7 @@ export function getEffectiveRESTRequest(
|
|||||||
active: true,
|
active: true,
|
||||||
key: "Content-Type",
|
key: "Content-Type",
|
||||||
value: request.body.contentType,
|
value: request.body.contentType,
|
||||||
|
description: "",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,8 +32,6 @@ import { getDurationInSeconds, getMetaDataPairs } from "./getters";
|
|||||||
import { preRequestScriptRunner } from "./pre-request";
|
import { preRequestScriptRunner } from "./pre-request";
|
||||||
import { getTestScriptParams, hasFailedTestCases, testRunner } from "./test";
|
import { getTestScriptParams, hasFailedTestCases, testRunner } from "./test";
|
||||||
|
|
||||||
// !NOTE: The `config.supported` checks are temporary until OAuth2 and Multipart Forms are supported
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes given variable, which includes checking for secret variables
|
* Processes given variable, which includes checking for secret variables
|
||||||
* and getting value from system environment
|
* and getting value from system environment
|
||||||
@@ -75,46 +73,20 @@ const processEnvs = (envs: Partial<HoppEnvs>) => {
|
|||||||
*/
|
*/
|
||||||
export const createRequest = (req: EffectiveHoppRESTRequest): RequestConfig => {
|
export const createRequest = (req: EffectiveHoppRESTRequest): RequestConfig => {
|
||||||
const config: RequestConfig = {
|
const config: RequestConfig = {
|
||||||
supported: true,
|
|
||||||
displayUrl: req.effectiveFinalDisplayURL,
|
displayUrl: req.effectiveFinalDisplayURL,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { finalBody, finalEndpoint, finalHeaders, finalParams } = getRequest;
|
const { finalBody, finalEndpoint, finalHeaders, finalParams } = getRequest;
|
||||||
|
|
||||||
const reqParams = finalParams(req);
|
const reqParams = finalParams(req);
|
||||||
const reqHeaders = finalHeaders(req);
|
const reqHeaders = finalHeaders(req);
|
||||||
|
|
||||||
config.url = finalEndpoint(req);
|
config.url = finalEndpoint(req);
|
||||||
config.method = req.method as Method;
|
config.method = req.method as Method;
|
||||||
config.params = getMetaDataPairs(reqParams);
|
config.params = getMetaDataPairs(reqParams);
|
||||||
config.headers = getMetaDataPairs(reqHeaders);
|
config.headers = getMetaDataPairs(reqHeaders);
|
||||||
if (req.auth.authActive) {
|
|
||||||
switch (req.auth.authType) {
|
|
||||||
case "oauth-2": {
|
|
||||||
// TODO: OAuth2 Request Parsing
|
|
||||||
// !NOTE: Temporary `config.supported` check
|
|
||||||
config.supported = false;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const resolvedContentType =
|
config.data = finalBody(req);
|
||||||
config.headers["Content-Type"] ?? req.body.contentType;
|
|
||||||
|
|
||||||
if (resolvedContentType) {
|
|
||||||
switch (resolvedContentType) {
|
|
||||||
case "multipart/form-data": {
|
|
||||||
// TODO: Parse Multipart Form Data
|
|
||||||
// !NOTE: Temporary `config.supported` check
|
|
||||||
config.supported = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
config.data = finalBody(req);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
};
|
};
|
||||||
@@ -149,13 +121,6 @@ export const requestRunner =
|
|||||||
duration: 0,
|
duration: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
// !NOTE: Temporary `config.supported` check
|
|
||||||
if ((config as RequestConfig).supported === false) {
|
|
||||||
status = 501;
|
|
||||||
runnerResponse.status = status;
|
|
||||||
runnerResponse.statusText = responseErrors[status];
|
|
||||||
}
|
|
||||||
|
|
||||||
const end = hrtime(start);
|
const end = hrtime(start);
|
||||||
const duration = getDurationInSeconds(end);
|
const duration = getDurationInSeconds(end);
|
||||||
runnerResponse.duration = duration;
|
runnerResponse.duration = duration;
|
||||||
@@ -182,10 +147,6 @@ export const requestRunner =
|
|||||||
runnerResponse.statusText = statusText;
|
runnerResponse.statusText = statusText;
|
||||||
runnerResponse.status = status;
|
runnerResponse.status = status;
|
||||||
runnerResponse.headers = headers;
|
runnerResponse.headers = headers;
|
||||||
} else if ((e.config as RequestConfig).supported === false) {
|
|
||||||
status = 501;
|
|
||||||
runnerResponse.status = status;
|
|
||||||
runnerResponse.statusText = responseErrors[status];
|
|
||||||
} else if (e.request) {
|
} else if (e.request) {
|
||||||
return E.left(error({ code: "REQUEST_ERROR", data: E.toError(e) }));
|
return E.left(error({ code: "REQUEST_ERROR", data: E.toError(e) }));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,6 +126,7 @@
|
|||||||
},
|
},
|
||||||
"authorization": {
|
"authorization": {
|
||||||
"generate_token": "Generate Token",
|
"generate_token": "Generate Token",
|
||||||
|
"refresh_token": "Refresh Token",
|
||||||
"graphql_headers": "Authorization Headers are sent as part of the payload to connection_init",
|
"graphql_headers": "Authorization Headers are sent as part of the payload to connection_init",
|
||||||
"include_in_url": "Include in URL",
|
"include_in_url": "Include in URL",
|
||||||
"inherited_from": "Inherited {auth} from parent collection {collection} ",
|
"inherited_from": "Inherited {auth} from parent collection {collection} ",
|
||||||
@@ -148,6 +149,9 @@
|
|||||||
"token_fetched_successfully": "Token fetched successfully",
|
"token_fetched_successfully": "Token fetched successfully",
|
||||||
"token_fetch_failed": "Failed to fetch token",
|
"token_fetch_failed": "Failed to fetch token",
|
||||||
"validation_failed": "Validation Failed, please check the form fields",
|
"validation_failed": "Validation Failed, please check the form fields",
|
||||||
|
"no_refresh_token_present": "No Refresh Token present. Please run the token generation flow again",
|
||||||
|
"refresh_token_request_failed": "Refresh token request failed",
|
||||||
|
"token_refreshed_successfully": "Token refreshed successfully",
|
||||||
"label_authorization_endpoint": "Authorization Endpoint",
|
"label_authorization_endpoint": "Authorization Endpoint",
|
||||||
"label_client_id": "Client ID",
|
"label_client_id": "Client ID",
|
||||||
"label_client_secret": "Client Secret",
|
"label_client_secret": "Client Secret",
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ onMounted(() => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { context, source, token }: PersistedOAuthConfig =
|
const { context, source, token, refresh_token }: PersistedOAuthConfig =
|
||||||
JSON.parse(localOAuthTempConfig)
|
JSON.parse(localOAuthTempConfig)
|
||||||
|
|
||||||
if (source === "REST") {
|
if (source === "REST") {
|
||||||
@@ -279,6 +279,10 @@ onMounted(() => {
|
|||||||
const grantTypeInfo = auth.grantTypeInfo
|
const grantTypeInfo = auth.grantTypeInfo
|
||||||
|
|
||||||
grantTypeInfo && (grantTypeInfo.token = token ?? "")
|
grantTypeInfo && (grantTypeInfo.token = token ?? "")
|
||||||
|
|
||||||
|
if (refresh_token && grantTypeInfo.grantType === "AUTHORIZATION_CODE") {
|
||||||
|
grantTypeInfo.refreshToken = refresh_token
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
editingProperties.value = unsavedCollectionProperties
|
editingProperties.value = unsavedCollectionProperties
|
||||||
|
|||||||
@@ -428,7 +428,7 @@ onMounted(() => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { context, source, token }: PersistedOAuthConfig =
|
const { context, source, token, refresh_token }: PersistedOAuthConfig =
|
||||||
JSON.parse(localOAuthTempConfig)
|
JSON.parse(localOAuthTempConfig)
|
||||||
|
|
||||||
if (source === "GraphQL") {
|
if (source === "GraphQL") {
|
||||||
@@ -452,6 +452,10 @@ onMounted(() => {
|
|||||||
const grantTypeInfo = auth.grantTypeInfo
|
const grantTypeInfo = auth.grantTypeInfo
|
||||||
|
|
||||||
grantTypeInfo && (grantTypeInfo.token = token ?? "")
|
grantTypeInfo && (grantTypeInfo.token = token ?? "")
|
||||||
|
|
||||||
|
if (refresh_token && grantTypeInfo.grantType === "AUTHORIZATION_CODE") {
|
||||||
|
grantTypeInfo.refreshToken = refresh_token
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
editingProperties.value = unsavedCollectionProperties
|
editingProperties.value = unsavedCollectionProperties
|
||||||
|
|||||||
@@ -165,12 +165,18 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="p-2">
|
<div class="p-2 gap-1 flex">
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
filled
|
filled
|
||||||
:label="`${t('authorization.generate_token')}`"
|
:label="`${t('authorization.generate_token')}`"
|
||||||
@click="generateOAuthToken()"
|
@click="generateOAuthToken()"
|
||||||
/>
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-if="runTokenRefresh"
|
||||||
|
filled
|
||||||
|
:label="`${t('authorization.refresh_token')}`"
|
||||||
|
@click="refreshOauthToken()"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -192,6 +198,7 @@ import { getCombinedEnvVariables } from "~/helpers/preRequest"
|
|||||||
import { AggregateEnvironment } from "~/newstore/environments"
|
import { AggregateEnvironment } from "~/newstore/environments"
|
||||||
import authCode, {
|
import authCode, {
|
||||||
AuthCodeOauthFlowParams,
|
AuthCodeOauthFlowParams,
|
||||||
|
AuthCodeOauthRefreshParams,
|
||||||
getDefaultAuthCodeOauthFlowParams,
|
getDefaultAuthCodeOauthFlowParams,
|
||||||
} from "~/services/oauth/flows/authCode"
|
} from "~/services/oauth/flows/authCode"
|
||||||
import clientCredentials, {
|
import clientCredentials, {
|
||||||
@@ -356,6 +363,48 @@ const supportedGrantTypes = [
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const refreshToken = async () => {
|
||||||
|
const grantTypeInfo = auth.value.grantTypeInfo
|
||||||
|
|
||||||
|
if (!("refreshToken" in grantTypeInfo)) {
|
||||||
|
return E.left("NO_REFRESH_TOKEN_PRESENT" as const)
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshToken = grantTypeInfo.refreshToken
|
||||||
|
|
||||||
|
if (!refreshToken) {
|
||||||
|
return E.left("NO_REFRESH_TOKEN_PRESENT" as const)
|
||||||
|
}
|
||||||
|
|
||||||
|
const params: AuthCodeOauthRefreshParams = {
|
||||||
|
clientID: clientID.value,
|
||||||
|
clientSecret: clientSecret.value,
|
||||||
|
tokenEndpoint: tokenEndpoint.value,
|
||||||
|
refreshToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
const unwrappedParams = replaceTemplateStringsInObjectValues(params)
|
||||||
|
|
||||||
|
const refreshTokenFunc = authCode.refreshToken
|
||||||
|
|
||||||
|
if (!refreshTokenFunc) {
|
||||||
|
return E.left("REFRESH_TOKEN_FUNCTION_NOT_DEFINED" as const)
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await refreshTokenFunc(unwrappedParams)
|
||||||
|
|
||||||
|
if (E.isLeft(res)) {
|
||||||
|
return E.left("OAUTH_REFRESH_TOKEN_FAILED" as const)
|
||||||
|
}
|
||||||
|
|
||||||
|
setAccessTokenInActiveContext(
|
||||||
|
res.right.access_token,
|
||||||
|
res.right.refresh_token
|
||||||
|
)
|
||||||
|
|
||||||
|
return E.right(undefined)
|
||||||
|
}
|
||||||
|
|
||||||
const runAction = () => {
|
const runAction = () => {
|
||||||
const params: AuthCodeOauthFlowParams = {
|
const params: AuthCodeOauthFlowParams = {
|
||||||
authEndpoint: authEndpoint.value,
|
authEndpoint: authEndpoint.value,
|
||||||
@@ -456,6 +505,7 @@ const supportedGrantTypes = [
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
runAction,
|
runAction,
|
||||||
|
refreshToken,
|
||||||
elements,
|
elements,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@@ -854,13 +904,28 @@ const selectedGrantType = computed(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const setAccessTokenInActiveContext = (accessToken?: string) => {
|
const setAccessTokenInActiveContext = (
|
||||||
|
accessToken?: string,
|
||||||
|
refreshToken?: string
|
||||||
|
) => {
|
||||||
if (props.isCollectionProperty && accessToken) {
|
if (props.isCollectionProperty && accessToken) {
|
||||||
auth.value.grantTypeInfo = {
|
auth.value.grantTypeInfo = {
|
||||||
...auth.value.grantTypeInfo,
|
...auth.value.grantTypeInfo,
|
||||||
token: accessToken,
|
token: accessToken,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set the refresh token if provided
|
||||||
|
// we also make sure the grantTypes supporting refreshTokens are
|
||||||
|
if (
|
||||||
|
refreshToken &&
|
||||||
|
auth.value.grantTypeInfo.grantType === "AUTHORIZATION_CODE"
|
||||||
|
) {
|
||||||
|
auth.value.grantTypeInfo = {
|
||||||
|
...auth.value.grantTypeInfo,
|
||||||
|
refreshToken,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -874,6 +939,16 @@ const setAccessTokenInActiveContext = (accessToken?: string) => {
|
|||||||
tabService.currentActiveTab.value.document.request.auth.grantTypeInfo.token =
|
tabService.currentActiveTab.value.document.request.auth.grantTypeInfo.token =
|
||||||
accessToken
|
accessToken
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
refreshToken &&
|
||||||
|
tabService.currentActiveTab.value.document.request.auth.authType ===
|
||||||
|
"oauth-2"
|
||||||
|
) {
|
||||||
|
// @ts-expect-error - todo: narrow the grantType to only supporting refresh tokens
|
||||||
|
tabService.currentActiveTab.value.document.request.auth.grantTypeInfo.refreshToken =
|
||||||
|
refreshToken
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const changeSelectedGrantType = (
|
const changeSelectedGrantType = (
|
||||||
@@ -905,10 +980,53 @@ const runAction = computed(() => {
|
|||||||
return selectedGrantType.value?.formElements.value?.runAction
|
return selectedGrantType.value?.formElements.value?.runAction
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const runTokenRefresh = computed(() => {
|
||||||
|
// the only grant type that supports refresh tokens is the authCode grant type
|
||||||
|
if (selectedGrantType.value?.id === "authCode") {
|
||||||
|
return selectedGrantType.value?.formElements.value?.refreshToken
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
const currentOAuthGrantTypeFormElements = computed(() => {
|
const currentOAuthGrantTypeFormElements = computed(() => {
|
||||||
return selectedGrantType.value?.formElements.value?.elements.value
|
return selectedGrantType.value?.formElements.value?.elements.value
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const refreshOauthToken = async () => {
|
||||||
|
if (!runTokenRefresh.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await runTokenRefresh.value()
|
||||||
|
|
||||||
|
if (E.isLeft(res)) {
|
||||||
|
const errorMessages = {
|
||||||
|
NO_REFRESH_TOKEN_PRESENT: t(
|
||||||
|
"authorization.oauth.no_refresh_token_present"
|
||||||
|
),
|
||||||
|
REFRESH_TOKEN_FUNCTION_NOT_DEFINED: t(
|
||||||
|
"authorization.oauth.refresh_token_request_failed"
|
||||||
|
),
|
||||||
|
OAUTH_REFRESH_TOKEN_FAILED: t(
|
||||||
|
"authorization.oauth.refresh_token_request_failed"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
const isKnownError = res.left in errorMessages
|
||||||
|
|
||||||
|
if (!isKnownError) {
|
||||||
|
toast.error(t("authorization.oauth.refresh_token_failed"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.error(errorMessages[res.left])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success(t("authorization.oauth.token_refreshed_successfully"))
|
||||||
|
}
|
||||||
|
|
||||||
const generateOAuthToken = async () => {
|
const generateOAuthToken = async () => {
|
||||||
if (
|
if (
|
||||||
grantTypesInvolvingRedirect.includes(auth.value.grantTypeInfo.grantType)
|
grantTypesInvolvingRedirect.includes(auth.value.grantTypeInfo.grantType)
|
||||||
|
|||||||
@@ -97,6 +97,11 @@ onMounted(async () => {
|
|||||||
...persistedOAuthConfig,
|
...persistedOAuthConfig,
|
||||||
token: tokenInfo.right.access_token,
|
token: tokenInfo.right.access_token,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tokenInfo.right.refresh_token) {
|
||||||
|
authConfig.refresh_token = tokenInfo.right.refresh_token
|
||||||
|
}
|
||||||
|
|
||||||
persistenceService.setLocalConfig(
|
persistenceService.setLocalConfig(
|
||||||
"oauth_temp_config",
|
"oauth_temp_config",
|
||||||
JSON.stringify(authConfig)
|
JSON.stringify(authConfig)
|
||||||
@@ -118,6 +123,14 @@ onMounted(async () => {
|
|||||||
tabService.currentActiveTab.value.document.request.auth.grantTypeInfo.token =
|
tabService.currentActiveTab.value.document.request.auth.grantTypeInfo.token =
|
||||||
tokenInfo.right.access_token
|
tokenInfo.right.access_token
|
||||||
|
|
||||||
|
if (
|
||||||
|
tabService.currentActiveTab.value.document.request.auth.grantTypeInfo
|
||||||
|
.grantType === "AUTHORIZATION_CODE"
|
||||||
|
) {
|
||||||
|
tabService.currentActiveTab.value.document.request.auth.grantTypeInfo.refreshToken =
|
||||||
|
tokenInfo.right.refresh_token
|
||||||
|
}
|
||||||
|
|
||||||
toast.success(t("authorization.oauth.token_fetched_successfully"))
|
toast.success(t("authorization.oauth.token_fetched_successfully"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,13 @@ export type AuthCodeOauthFlowParams = z.infer<
|
|||||||
typeof AuthCodeOauthFlowParamsSchema
|
typeof AuthCodeOauthFlowParamsSchema
|
||||||
>
|
>
|
||||||
|
|
||||||
|
export type AuthCodeOauthRefreshParams = {
|
||||||
|
tokenEndpoint: string
|
||||||
|
clientID: string
|
||||||
|
clientSecret?: string
|
||||||
|
refreshToken: string
|
||||||
|
}
|
||||||
|
|
||||||
export const getDefaultAuthCodeOauthFlowParams =
|
export const getDefaultAuthCodeOauthFlowParams =
|
||||||
(): AuthCodeOauthFlowParams => ({
|
(): AuthCodeOauthFlowParams => ({
|
||||||
authEndpoint: "",
|
authEndpoint: "",
|
||||||
@@ -233,6 +240,7 @@ const handleRedirectForAuthCodeOauthFlow = async (localConfig: string) => {
|
|||||||
|
|
||||||
const withAccessTokenSchema = z.object({
|
const withAccessTokenSchema = z.object({
|
||||||
access_token: z.string(),
|
access_token: z.string(),
|
||||||
|
refresh_token: z.string().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const parsedTokenResponse = withAccessTokenSchema.safeParse(
|
const parsedTokenResponse = withAccessTokenSchema.safeParse(
|
||||||
@@ -284,9 +292,60 @@ const encodeArrayBufferAsUrlEncodedBase64 = (buffer: ArrayBuffer) => {
|
|||||||
return hashBase64URL
|
return hashBase64URL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const refreshToken = async ({
|
||||||
|
tokenEndpoint,
|
||||||
|
clientID,
|
||||||
|
refreshToken,
|
||||||
|
clientSecret,
|
||||||
|
}: AuthCodeOauthRefreshParams) => {
|
||||||
|
const formData = new URLSearchParams()
|
||||||
|
formData.append("grant_type", "refresh_token")
|
||||||
|
formData.append("refresh_token", refreshToken)
|
||||||
|
formData.append("client_id", clientID)
|
||||||
|
if (clientSecret) {
|
||||||
|
formData.append("client_secret", clientSecret)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { response } = interceptorService.runRequest({
|
||||||
|
url: tokenEndpoint,
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
data: formData.toString(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await response
|
||||||
|
|
||||||
|
if (E.isLeft(res)) {
|
||||||
|
return E.left("AUTH_TOKEN_REQUEST_FAILED" as const)
|
||||||
|
}
|
||||||
|
|
||||||
|
const responsePayload = decodeResponseAsJSON(res.right)
|
||||||
|
|
||||||
|
if (E.isLeft(responsePayload)) {
|
||||||
|
return E.left("AUTH_TOKEN_REQUEST_FAILED" as const)
|
||||||
|
}
|
||||||
|
|
||||||
|
const withAccessTokenAndRefreshTokenSchema = z.object({
|
||||||
|
access_token: z.string(),
|
||||||
|
refresh_token: z.string().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const parsedTokenResponse = withAccessTokenAndRefreshTokenSchema.safeParse(
|
||||||
|
responsePayload.right
|
||||||
|
)
|
||||||
|
|
||||||
|
return parsedTokenResponse.success
|
||||||
|
? E.right(parsedTokenResponse.data)
|
||||||
|
: E.left("AUTH_TOKEN_REQUEST_INVALID_RESPONSE" as const)
|
||||||
|
}
|
||||||
|
|
||||||
export default createFlowConfig(
|
export default createFlowConfig(
|
||||||
"AUTHORIZATION_CODE" as const,
|
"AUTHORIZATION_CODE" as const,
|
||||||
AuthCodeOauthFlowParamsSchema,
|
AuthCodeOauthFlowParamsSchema,
|
||||||
initAuthCodeOauthFlow,
|
initAuthCodeOauthFlow,
|
||||||
handleRedirectForAuthCodeOauthFlow
|
handleRedirectForAuthCodeOauthFlow,
|
||||||
|
refreshToken
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export type PersistedOAuthConfig = {
|
|||||||
state: string
|
state: string
|
||||||
}
|
}
|
||||||
token?: string
|
token?: string
|
||||||
|
refresh_token?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const persistenceService = getService(PersistenceService)
|
const persistenceService = getService(PersistenceService)
|
||||||
@@ -66,6 +67,7 @@ export function createFlowConfig<
|
|||||||
Flow extends string,
|
Flow extends string,
|
||||||
AuthParams extends Record<string, unknown>,
|
AuthParams extends Record<string, unknown>,
|
||||||
InitFuncReturnObject extends Record<string, unknown>,
|
InitFuncReturnObject extends Record<string, unknown>,
|
||||||
|
RefreshTokenParams extends Record<string, unknown>,
|
||||||
>(
|
>(
|
||||||
flow: Flow,
|
flow: Flow,
|
||||||
params: ZodType<AuthParams>,
|
params: ZodType<AuthParams>,
|
||||||
@@ -81,8 +83,14 @@ export function createFlowConfig<
|
|||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
access_token: string
|
access_token: string
|
||||||
|
refresh_token?: string
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
>,
|
||||||
|
refreshToken?: (
|
||||||
|
params: RefreshTokenParams
|
||||||
|
) => Promise<
|
||||||
|
E.Either<string, { access_token: string; refresh_token?: string }>
|
||||||
>
|
>
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
@@ -90,6 +98,7 @@ export function createFlowConfig<
|
|||||||
params,
|
params,
|
||||||
init,
|
init,
|
||||||
onRedirectReceived,
|
onRedirectReceived,
|
||||||
|
refreshToken,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,14 @@ import { GQLHeader as GQLHeaderV1 } from "../../graphql/v/1"
|
|||||||
import { GQLHeader as GQLHeaderV2 } from "../../graphql/v/6"
|
import { GQLHeader as GQLHeaderV2 } from "../../graphql/v/6"
|
||||||
import { HoppRESTHeaders as V1_HoppRESTHeaders } from "../../rest/v/1"
|
import { HoppRESTHeaders as V1_HoppRESTHeaders } from "../../rest/v/1"
|
||||||
import { HoppRESTHeaders as V2_HoppRESTHeaders } from "../../rest/v/7"
|
import { HoppRESTHeaders as V2_HoppRESTHeaders } from "../../rest/v/7"
|
||||||
|
import { HoppRESTAuth } from "../../rest/v/7"
|
||||||
|
import { HoppGQLAuth } from "../../graphql/v/6"
|
||||||
import { v2_baseCollectionSchema, V2_SCHEMA } from "./2"
|
import { v2_baseCollectionSchema, V2_SCHEMA } from "./2"
|
||||||
|
|
||||||
const v3_baseCollectionSchema = v2_baseCollectionSchema.extend({
|
const v3_baseCollectionSchema = v2_baseCollectionSchema.extend({
|
||||||
v: z.literal(3),
|
v: z.literal(3),
|
||||||
headers: z.union([V2_HoppRESTHeaders, z.array(GQLHeaderV2)]),
|
headers: z.union([V2_HoppRESTHeaders, z.array(GQLHeaderV2)]),
|
||||||
|
auth: z.union([HoppRESTAuth, HoppGQLAuth]),
|
||||||
})
|
})
|
||||||
|
|
||||||
type Input = z.input<typeof v3_baseCollectionSchema> & {
|
type Input = z.input<typeof v3_baseCollectionSchema> & {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export {
|
|||||||
} from "./v/2"
|
} from "./v/2"
|
||||||
export { GQLHeader } from "./v/6"
|
export { GQLHeader } from "./v/6"
|
||||||
|
|
||||||
export { HoppGQLAuth, HoppGQLAuthOAuth2 } from "./v/5"
|
export { HoppGQLAuth, HoppGQLAuthOAuth2 } from "./v/6"
|
||||||
|
|
||||||
export { HoppGQLAuthAPIKey } from "./v/4"
|
export { HoppGQLAuthAPIKey } from "./v/4"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,33 @@
|
|||||||
import { defineVersion } from "verzod"
|
import { defineVersion } from "verzod"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
import { V5_SCHEMA } from "./5"
|
import { V5_SCHEMA } from "./5"
|
||||||
|
import { HoppRESTAuthOAuth2 } from "./../../rest/v/7"
|
||||||
|
import {
|
||||||
|
HoppGQLAuthBasic,
|
||||||
|
HoppGQLAuthBearer,
|
||||||
|
HoppGQLAuthInherit,
|
||||||
|
HoppGQLAuthNone,
|
||||||
|
} from "./2"
|
||||||
|
import { HoppGQLAuthAPIKey } from "./4"
|
||||||
|
|
||||||
|
export { HoppRESTAuthOAuth2 as HoppGQLAuthOAuth2 } from "../../rest/v/7"
|
||||||
|
|
||||||
|
export const HoppGQLAuth = z
|
||||||
|
.discriminatedUnion("authType", [
|
||||||
|
HoppGQLAuthNone,
|
||||||
|
HoppGQLAuthInherit,
|
||||||
|
HoppGQLAuthBasic,
|
||||||
|
HoppGQLAuthBearer,
|
||||||
|
HoppGQLAuthAPIKey,
|
||||||
|
HoppRESTAuthOAuth2, // both rest and gql have the same auth type for oauth2
|
||||||
|
])
|
||||||
|
.and(
|
||||||
|
z.object({
|
||||||
|
authActive: z.boolean(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
export type HoppGQLAuth = z.infer<typeof HoppGQLAuth>
|
||||||
|
|
||||||
export const GQLHeader = z.object({
|
export const GQLHeader = z.object({
|
||||||
key: z.string().catch(""),
|
key: z.string().catch(""),
|
||||||
@@ -15,6 +41,7 @@ export type GQLHeader = z.infer<typeof GQLHeader>
|
|||||||
export const V6_SCHEMA = V5_SCHEMA.extend({
|
export const V6_SCHEMA = V5_SCHEMA.extend({
|
||||||
v: z.literal(6),
|
v: z.literal(6),
|
||||||
headers: z.array(GQLHeader).catch([]),
|
headers: z.array(GQLHeader).catch([]),
|
||||||
|
auth: HoppGQLAuth,
|
||||||
})
|
})
|
||||||
|
|
||||||
export default defineVersion({
|
export default defineVersion({
|
||||||
|
|||||||
@@ -13,12 +13,10 @@ import V3_VERSION from "./v/3"
|
|||||||
import V4_VERSION from "./v/4"
|
import V4_VERSION from "./v/4"
|
||||||
import V5_VERSION from "./v/5"
|
import V5_VERSION from "./v/5"
|
||||||
import V6_VERSION, { HoppRESTReqBody } from "./v/6"
|
import V6_VERSION, { HoppRESTReqBody } from "./v/6"
|
||||||
|
import V7_VERSION, { HoppRESTAuth } from "./v/7"
|
||||||
|
|
||||||
import { HoppRESTHeaders, HoppRESTParams } from "./v/7"
|
import { HoppRESTHeaders, HoppRESTParams } from "./v/7"
|
||||||
import V7_VERSION from "./v/7"
|
|
||||||
|
|
||||||
import { HoppRESTRequestVariables } from "./v/2"
|
import { HoppRESTRequestVariables } from "./v/2"
|
||||||
import { HoppRESTAuth } from "./v/5"
|
|
||||||
|
|
||||||
export * from "./content-types"
|
export * from "./content-types"
|
||||||
|
|
||||||
@@ -37,11 +35,8 @@ export {
|
|||||||
PasswordGrantTypeParams,
|
PasswordGrantTypeParams,
|
||||||
} from "./v/3"
|
} from "./v/3"
|
||||||
|
|
||||||
export {
|
export { AuthCodeGrantTypeParams } from "./v/5"
|
||||||
AuthCodeGrantTypeParams,
|
export { HoppRESTAuthOAuth2, HoppRESTAuth } from "./v/7"
|
||||||
HoppRESTAuth,
|
|
||||||
HoppRESTAuthOAuth2,
|
|
||||||
} from "./v/5"
|
|
||||||
|
|
||||||
export { HoppRESTAuthAPIKey } from "./v/4"
|
export { HoppRESTAuthAPIKey } from "./v/4"
|
||||||
|
|
||||||
@@ -159,6 +154,7 @@ export function safelyExtractRESTRequest(
|
|||||||
const result = HoppRESTAuth.safeParse(x.auth)
|
const result = HoppRESTAuth.safeParse(x.auth)
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
// @ts-ignore
|
||||||
req.auth = result.data
|
req.auth = result.data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,56 @@ import { z } from "zod"
|
|||||||
|
|
||||||
import { V6_SCHEMA } from "./6"
|
import { V6_SCHEMA } from "./6"
|
||||||
|
|
||||||
|
import { AuthCodeGrantTypeParams as AuthCodeGrantTypeParamsOld } from "./5"
|
||||||
|
|
||||||
|
import {
|
||||||
|
ClientCredentialsGrantTypeParams,
|
||||||
|
ImplicitOauthFlowParams,
|
||||||
|
PasswordGrantTypeParams,
|
||||||
|
} from "./3"
|
||||||
|
import {
|
||||||
|
HoppRESTAuthAPIKey,
|
||||||
|
HoppRESTAuthBasic,
|
||||||
|
HoppRESTAuthBearer,
|
||||||
|
HoppRESTAuthInherit,
|
||||||
|
HoppRESTAuthNone,
|
||||||
|
} from "./1"
|
||||||
|
|
||||||
|
// Add refreshToken to all grant types except Implicit
|
||||||
|
export const AuthCodeGrantTypeParams = AuthCodeGrantTypeParamsOld.extend({
|
||||||
|
refreshToken: z.string().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const HoppRESTAuthOAuth2 = z.object({
|
||||||
|
authType: z.literal("oauth-2"),
|
||||||
|
grantTypeInfo: z.discriminatedUnion("grantType", [
|
||||||
|
AuthCodeGrantTypeParams,
|
||||||
|
ClientCredentialsGrantTypeParams,
|
||||||
|
PasswordGrantTypeParams,
|
||||||
|
ImplicitOauthFlowParams,
|
||||||
|
]),
|
||||||
|
addTo: z.enum(["HEADERS", "QUERY_PARAMS"]).catch("HEADERS"),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type HoppRESTAuthOAuth2 = z.infer<typeof HoppRESTAuthOAuth2>
|
||||||
|
|
||||||
|
export const HoppRESTAuth = z
|
||||||
|
.discriminatedUnion("authType", [
|
||||||
|
HoppRESTAuthNone,
|
||||||
|
HoppRESTAuthInherit,
|
||||||
|
HoppRESTAuthBasic,
|
||||||
|
HoppRESTAuthBearer,
|
||||||
|
HoppRESTAuthOAuth2,
|
||||||
|
HoppRESTAuthAPIKey,
|
||||||
|
])
|
||||||
|
.and(
|
||||||
|
z.object({
|
||||||
|
authActive: z.boolean(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
export type HoppRESTAuth = z.infer<typeof HoppRESTAuth>
|
||||||
|
|
||||||
export const HoppRESTParams = z.array(
|
export const HoppRESTParams = z.array(
|
||||||
z.object({
|
z.object({
|
||||||
key: z.string().catch(""),
|
key: z.string().catch(""),
|
||||||
@@ -29,6 +79,7 @@ export const V7_SCHEMA = V6_SCHEMA.extend({
|
|||||||
v: z.literal("7"),
|
v: z.literal("7"),
|
||||||
params: HoppRESTParams,
|
params: HoppRESTParams,
|
||||||
headers: HoppRESTHeaders,
|
headers: HoppRESTHeaders,
|
||||||
|
auth: HoppRESTAuth,
|
||||||
})
|
})
|
||||||
|
|
||||||
export default defineVersion({
|
export default defineVersion({
|
||||||
@@ -54,6 +105,7 @@ export default defineVersion({
|
|||||||
v: "7" as const,
|
v: "7" as const,
|
||||||
params,
|
params,
|
||||||
headers,
|
headers,
|
||||||
|
// no need to update anything for HoppRESTAuth, because the newly added refreshToken is optional
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user