Compare commits
7 Commits
feat/coll-
...
fix/auth-h
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a6451a601 | ||
|
|
8d5ad2411b | ||
|
|
b521604b66 | ||
|
|
9bc81a6d67 | ||
|
|
c47e2e7767 | ||
|
|
5209c0a8ca | ||
|
|
47e009267b |
@@ -1,63 +1,64 @@
|
||||
import { ExecException } from "child_process";
|
||||
|
||||
import { HoppErrorCode } from "../../types/errors";
|
||||
import { execAsync, getErrorCode, getTestJsonFilePath } from "../utils";
|
||||
import { runCLI, getErrorCode, getTestJsonFilePath } from "../utils";
|
||||
|
||||
describe("Test 'hopp test <file>' command:", () => {
|
||||
test("No collection file path provided.", async () => {
|
||||
const cmd = `node ./bin/hopp test`;
|
||||
const { stderr } = await execAsync(cmd);
|
||||
const out = getErrorCode(stderr);
|
||||
const args = "test";
|
||||
const { stderr } = await runCLI(args);
|
||||
|
||||
const out = getErrorCode(stderr);
|
||||
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
|
||||
});
|
||||
|
||||
test("Collection file not found.", async () => {
|
||||
const cmd = `node ./bin/hopp test notfound.json`;
|
||||
const { stderr } = await execAsync(cmd);
|
||||
const out = getErrorCode(stderr);
|
||||
const args = "test notfound.json";
|
||||
const { stderr } = await runCLI(args);
|
||||
|
||||
const out = getErrorCode(stderr);
|
||||
expect(out).toBe<HoppErrorCode>("FILE_NOT_FOUND");
|
||||
});
|
||||
|
||||
test("Collection file is invalid JSON.", async () => {
|
||||
const cmd = `node ./bin/hopp test ${getTestJsonFilePath(
|
||||
const args = `test ${getTestJsonFilePath(
|
||||
"malformed-collection.json"
|
||||
)}`;
|
||||
const { stderr } = await execAsync(cmd);
|
||||
const out = getErrorCode(stderr);
|
||||
const { stderr } = await runCLI(args);
|
||||
|
||||
const out = getErrorCode(stderr);
|
||||
expect(out).toBe<HoppErrorCode>("UNKNOWN_ERROR");
|
||||
});
|
||||
|
||||
test("Malformed collection file.", async () => {
|
||||
const cmd = `node ./bin/hopp test ${getTestJsonFilePath(
|
||||
const args = `test ${getTestJsonFilePath(
|
||||
"malformed-collection2.json"
|
||||
)}`;
|
||||
const { stderr } = await execAsync(cmd);
|
||||
const out = getErrorCode(stderr);
|
||||
const { stderr } = await runCLI(args);
|
||||
|
||||
const out = getErrorCode(stderr);
|
||||
expect(out).toBe<HoppErrorCode>("MALFORMED_COLLECTION");
|
||||
});
|
||||
|
||||
test("Invalid arguement.", async () => {
|
||||
const cmd = `node ./bin/hopp invalid-arg`;
|
||||
const { stderr } = await execAsync(cmd);
|
||||
const out = getErrorCode(stderr);
|
||||
const args = "invalid-arg";
|
||||
const { stderr } = await runCLI(args);
|
||||
|
||||
const out = getErrorCode(stderr);
|
||||
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
|
||||
});
|
||||
|
||||
test("Collection file not JSON type.", async () => {
|
||||
const cmd = `node ./bin/hopp test ${getTestJsonFilePath("notjson.txt")}`;
|
||||
const { stderr } = await execAsync(cmd);
|
||||
const out = getErrorCode(stderr);
|
||||
const args = `test ${getTestJsonFilePath("notjson.txt")}`;
|
||||
const { stderr } = await runCLI(args);
|
||||
|
||||
const out = getErrorCode(stderr);
|
||||
expect(out).toBe<HoppErrorCode>("INVALID_FILE_TYPE");
|
||||
});
|
||||
|
||||
test("Some errors occured (exit code 1).", async () => {
|
||||
const cmd = `node ./bin/hopp test ${getTestJsonFilePath("fails.json")}`;
|
||||
const { error } = await execAsync(cmd);
|
||||
const args = `test ${getTestJsonFilePath("fails.json")}`;
|
||||
const { error } = await runCLI(args);
|
||||
|
||||
expect(error).not.toBeNull();
|
||||
expect(error).toMatchObject(<ExecException>{
|
||||
@@ -66,75 +67,83 @@ describe("Test 'hopp test <file>' command:", () => {
|
||||
});
|
||||
|
||||
test("No errors occured (exit code 0).", async () => {
|
||||
const cmd = `node ./bin/hopp test ${getTestJsonFilePath("passes.json")}`;
|
||||
const { error } = await execAsync(cmd);
|
||||
const args = `test ${getTestJsonFilePath("passes.json")}`;
|
||||
const { error } = await runCLI(args);
|
||||
|
||||
expect(error).toBeNull();
|
||||
});
|
||||
|
||||
test("Supports inheriting headers and authorization set at the root collection", async () => {
|
||||
const args = `test ${getTestJsonFilePath("collection-level-headers-auth.json")}`;
|
||||
const { error } = await runCLI(args);
|
||||
|
||||
expect(error).toBeNull();
|
||||
})
|
||||
});
|
||||
|
||||
describe("Test 'hopp test <file> --env <file>' command:", () => {
|
||||
const VALID_TEST_CMD = `node ./bin/hopp test ${getTestJsonFilePath(
|
||||
const VALID_TEST_ARGS = `test ${getTestJsonFilePath(
|
||||
"passes.json"
|
||||
)}`;
|
||||
|
||||
test("No env file path provided.", async () => {
|
||||
const cmd = `${VALID_TEST_CMD} --env`;
|
||||
const { stderr } = await execAsync(cmd);
|
||||
const out = getErrorCode(stderr);
|
||||
const args = `${VALID_TEST_ARGS} --env`;
|
||||
const { stderr } = await runCLI(args);
|
||||
|
||||
const out = getErrorCode(stderr);
|
||||
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
|
||||
});
|
||||
|
||||
test("ENV file not JSON type.", async () => {
|
||||
const cmd = `${VALID_TEST_CMD} --env ${getTestJsonFilePath("notjson.txt")}`;
|
||||
const { stderr } = await execAsync(cmd);
|
||||
const out = getErrorCode(stderr);
|
||||
const args = `${VALID_TEST_ARGS} --env ${getTestJsonFilePath("notjson.txt")}`;
|
||||
const { stderr } = await runCLI(args);
|
||||
|
||||
const out = getErrorCode(stderr);
|
||||
expect(out).toBe<HoppErrorCode>("INVALID_FILE_TYPE");
|
||||
});
|
||||
|
||||
test("ENV file not found.", async () => {
|
||||
const cmd = `${VALID_TEST_CMD} --env notfound.json`;
|
||||
const { stderr } = await execAsync(cmd);
|
||||
const out = getErrorCode(stderr);
|
||||
const args = `${VALID_TEST_ARGS} --env notfound.json`;
|
||||
const { stderr } = await runCLI(args);
|
||||
|
||||
const out = getErrorCode(stderr);
|
||||
expect(out).toBe<HoppErrorCode>("FILE_NOT_FOUND");
|
||||
});
|
||||
|
||||
test("No errors occured (exit code 0).", async () => {
|
||||
const TESTS_PATH = getTestJsonFilePath("env-flag-tests.json");
|
||||
const ENV_PATH = getTestJsonFilePath("env-flag-envs.json");
|
||||
const cmd = `node ./bin/hopp test ${TESTS_PATH} --env ${ENV_PATH}`;
|
||||
const { error, stdout } = await execAsync(cmd);
|
||||
const args = `test ${TESTS_PATH} --env ${ENV_PATH}`;
|
||||
|
||||
const { error } = await runCLI(args);
|
||||
expect(error).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Test 'hopp test <file> --delay <delay_in_ms>' command:", () => {
|
||||
const VALID_TEST_CMD = `node ./bin/hopp test ${getTestJsonFilePath(
|
||||
const VALID_TEST_ARGS = `test ${getTestJsonFilePath(
|
||||
"passes.json"
|
||||
)}`;
|
||||
|
||||
test("No value passed to delay flag.", async () => {
|
||||
const cmd = `${VALID_TEST_CMD} --delay`;
|
||||
const { stderr } = await execAsync(cmd);
|
||||
const out = getErrorCode(stderr);
|
||||
const args = `${VALID_TEST_ARGS} --delay`;
|
||||
const { stderr } = await runCLI(args);
|
||||
|
||||
const out = getErrorCode(stderr);
|
||||
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
|
||||
});
|
||||
|
||||
test("Invalid value passed to delay flag.", async () => {
|
||||
const cmd = `${VALID_TEST_CMD} --delay 'NaN'`;
|
||||
const { stderr } = await execAsync(cmd);
|
||||
const args = `${VALID_TEST_ARGS} --delay 'NaN'`;
|
||||
const { stderr } = await runCLI(args);
|
||||
|
||||
const out = getErrorCode(stderr);
|
||||
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
|
||||
});
|
||||
|
||||
test("Valid value passed to delay flag.", async () => {
|
||||
const cmd = `${VALID_TEST_CMD} --delay 1`;
|
||||
const { error } = await execAsync(cmd);
|
||||
const args = `${VALID_TEST_ARGS} --delay 1`;
|
||||
const { error } = await runCLI(args);
|
||||
|
||||
expect(error).toBeNull();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
[
|
||||
{
|
||||
"v": 1,
|
||||
"name": "CollectionA",
|
||||
"folders": [
|
||||
{
|
||||
"v": 1,
|
||||
"name": "FolderA",
|
||||
"folders": [
|
||||
{
|
||||
"v": 1,
|
||||
"name": "FolderB",
|
||||
"folders": [
|
||||
{
|
||||
"v": 1,
|
||||
"name": "FolderC",
|
||||
"folders": [],
|
||||
"requests": [
|
||||
{
|
||||
"v": "1",
|
||||
"endpoint": "https://echo.hoppscotch.io",
|
||||
"name": "RequestD",
|
||||
"params": [],
|
||||
"headers": [
|
||||
{
|
||||
"active": true,
|
||||
"key": "X-Test-Header",
|
||||
"value": "Overriden at RequestD"
|
||||
}
|
||||
],
|
||||
"method": "GET",
|
||||
"auth": {
|
||||
"authType": "basic",
|
||||
"authActive": true,
|
||||
"username": "username",
|
||||
"password": "password"
|
||||
},
|
||||
"preRequestScript": "",
|
||||
"testScript": "pw.test(\"Overrides auth and headers set at the parent folder\", ()=> {\n pw.expect(pw.response.body.headers[\"x-test-header\"]).toBe(\"Overriden at RequestD\");\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Basic dXNlcm5hbWU6cGFzc3dvcmQ=\");\n});",
|
||||
"body": {
|
||||
"contentType": null,
|
||||
"body": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"auth": {
|
||||
"authType": "inherit",
|
||||
"authActive": true
|
||||
},
|
||||
"headers": []
|
||||
}
|
||||
],
|
||||
"requests": [
|
||||
{
|
||||
"v": "1",
|
||||
"endpoint": "https://echo.hoppscotch.io",
|
||||
"name": "RequestC",
|
||||
"params": [],
|
||||
"headers": [],
|
||||
"method": "GET",
|
||||
"auth": {
|
||||
"authType": "inherit",
|
||||
"authActive": true
|
||||
},
|
||||
"preRequestScript": "",
|
||||
"testScript": "pw.test(\"Correctly inherits auth and headers from the parent folder\", ()=> {\n pw.expect(pw.response.body.headers[\"x-test-header\"]).toBe(\"Overriden at FolderB\");\n pw.expect(pw.response.body.headers[\"key\"]).toBe(\"test-key\");\n});",
|
||||
"body": {
|
||||
"contentType": null,
|
||||
"body": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"auth": {
|
||||
"authType": "api-key",
|
||||
"authActive": true,
|
||||
"addTo": "Headers",
|
||||
"key": "key",
|
||||
"value": "test-key"
|
||||
},
|
||||
"headers": [
|
||||
{
|
||||
"active": true,
|
||||
"key": "X-Test-Header",
|
||||
"value": "Overriden at FolderB"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"requests": [
|
||||
{
|
||||
"v": "1",
|
||||
"endpoint": "https://echo.hoppscotch.io",
|
||||
"name": "RequestB",
|
||||
"params": [],
|
||||
"headers": [],
|
||||
"method": "GET",
|
||||
"auth": {
|
||||
"authType": "inherit",
|
||||
"authActive": true
|
||||
},
|
||||
"preRequestScript": "",
|
||||
"testScript": "pw.test(\"Correctly inherits auth and headers from the parent folder\", ()=> {\n pw.expect(pw.response.body.headers[\"x-test-header\"]).toBe(\"Set at root collection\");\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer BearerToken\");\n});",
|
||||
"body": {
|
||||
"contentType": null,
|
||||
"body": null
|
||||
},
|
||||
"id": "clpttpdq00003qp16kut6doqv"
|
||||
}
|
||||
],
|
||||
"auth": {
|
||||
"authType": "inherit",
|
||||
"authActive": true
|
||||
},
|
||||
"headers": []
|
||||
}
|
||||
],
|
||||
"requests": [
|
||||
{
|
||||
"v": "1",
|
||||
"endpoint": "https://echo.hoppscotch.io",
|
||||
"name": "RequestA",
|
||||
"params": [],
|
||||
"headers": [],
|
||||
"method": "GET",
|
||||
"auth": {
|
||||
"authType": "inherit",
|
||||
"authActive": true
|
||||
},
|
||||
"preRequestScript": "",
|
||||
"testScript": "pw.test(\"Correctly inherits auth and headers from the root collection\", ()=> {\n pw.expect(pw.response.body.headers[\"x-test-header\"]).toBe(\"Set at root collection\");\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer BearerToken\");\n});",
|
||||
"body": {
|
||||
"contentType": null,
|
||||
"body": null
|
||||
},
|
||||
"id": "clpttpdq00003qp16kut6doqv"
|
||||
}
|
||||
],
|
||||
"headers": [
|
||||
{
|
||||
"active": true,
|
||||
"key": "X-Test-Header",
|
||||
"value": "Set at root collection"
|
||||
}
|
||||
],
|
||||
"auth": {
|
||||
"authType": "bearer",
|
||||
"authActive": true,
|
||||
"token": "BearerToken"
|
||||
}
|
||||
},
|
||||
{
|
||||
"v": 1,
|
||||
"name": "CollectionB",
|
||||
"folders": [
|
||||
{
|
||||
"v": 1,
|
||||
"name": "FolderA",
|
||||
"folders": [],
|
||||
"requests": [
|
||||
{
|
||||
"v": "1",
|
||||
"endpoint": "https://echo.hoppscotch.io",
|
||||
"name": "RequestB",
|
||||
"params": [],
|
||||
"headers": [],
|
||||
"method": "GET",
|
||||
"auth": {
|
||||
"authType": "inherit",
|
||||
"authActive": true
|
||||
},
|
||||
"preRequestScript": "",
|
||||
"testScript": "pw.test(\"Correctly inherits auth and headers from the parent folder\", ()=> {\n pw.expect(pw.response.body.headers[\"x-test-header\"]).toBe(\"Set at root collection\");\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer BearerToken\");\n});",
|
||||
"body": {
|
||||
"contentType": null,
|
||||
"body": null
|
||||
},
|
||||
"id": "clpttpdq00003qp16kut6doqv"
|
||||
}
|
||||
],
|
||||
"auth": {
|
||||
"authType": "inherit",
|
||||
"authActive": true
|
||||
},
|
||||
"headers": []
|
||||
}
|
||||
],
|
||||
"requests": [
|
||||
{
|
||||
"v": "1",
|
||||
"endpoint": "https://echo.hoppscotch.io",
|
||||
"name": "RequestA",
|
||||
"params": [],
|
||||
"headers": [],
|
||||
"method": "GET",
|
||||
"auth": {
|
||||
"authType": "inherit",
|
||||
"authActive": true
|
||||
},
|
||||
"preRequestScript": "",
|
||||
"testScript": "pw.test(\"Correctly inherits auth and headers from the root collection\", ()=> {\n pw.expect(pw.response.body.headers[\"x-test-header\"]).toBe(\"Set at root collection\");\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer BearerToken\");\n});",
|
||||
"body": {
|
||||
"contentType": null,
|
||||
"body": null
|
||||
},
|
||||
"id": "clpttpdq00003qp16kut6doqv"
|
||||
}
|
||||
],
|
||||
"headers": [
|
||||
{
|
||||
"active": true,
|
||||
"key": "X-Test-Header",
|
||||
"value": "Set at root collection"
|
||||
}
|
||||
],
|
||||
"auth": {
|
||||
"authType": "bearer",
|
||||
"authActive": true,
|
||||
"token": "BearerToken"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -1,10 +1,17 @@
|
||||
import { exec } from "child_process";
|
||||
import { resolve } from "path";
|
||||
|
||||
import { ExecResponse } from "./types";
|
||||
|
||||
export const execAsync = (command: string): Promise<ExecResponse> =>
|
||||
new Promise((resolve) =>
|
||||
exec(command, (error, stdout, stderr) => resolve({ error, stdout, stderr }))
|
||||
);
|
||||
export const runCLI = (args: string): Promise<ExecResponse> =>
|
||||
{
|
||||
const CLI_PATH = resolve(__dirname, "../../bin/hopp");
|
||||
const command = `node ${CLI_PATH} ${args}`
|
||||
|
||||
return new Promise((resolve) =>
|
||||
exec(command, (error, stdout, stderr) => resolve({ error, stdout, stderr }))
|
||||
);
|
||||
}
|
||||
|
||||
export const trimAnsi = (target: string) => {
|
||||
const ansiRegex =
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
import * as A from "fp-ts/Array";
|
||||
import { pipe } from "fp-ts/function";
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
|
||||
import { bold } from "chalk";
|
||||
import { log } from "console";
|
||||
import * as A from "fp-ts/Array";
|
||||
import { pipe } from "fp-ts/function";
|
||||
import round from "lodash/round";
|
||||
import { HoppCollection } from "@hoppscotch/data";
|
||||
|
||||
import { CollectionRunnerParam } from "../types/collections";
|
||||
import {
|
||||
HoppEnvs,
|
||||
CollectionStack,
|
||||
RequestReport,
|
||||
HoppEnvs,
|
||||
ProcessRequestParams,
|
||||
RequestReport,
|
||||
} from "../types/request";
|
||||
import {
|
||||
getRequestMetrics,
|
||||
preProcessRequest,
|
||||
processRequest,
|
||||
} from "./request";
|
||||
import { exceptionColors } from "./getters";
|
||||
PreRequestMetrics,
|
||||
RequestMetrics,
|
||||
TestMetrics,
|
||||
} from "../types/response";
|
||||
import { DEFAULT_DURATION_PRECISION } from "./constants";
|
||||
import {
|
||||
printErrorsReport,
|
||||
printFailedTestsReport,
|
||||
@@ -23,15 +25,14 @@ import {
|
||||
printRequestsMetrics,
|
||||
printTestsMetrics,
|
||||
} from "./display";
|
||||
import {
|
||||
PreRequestMetrics,
|
||||
RequestMetrics,
|
||||
TestMetrics,
|
||||
} from "../types/response";
|
||||
import { getTestMetrics } from "./test";
|
||||
import { DEFAULT_DURATION_PRECISION } from "./constants";
|
||||
import { exceptionColors } from "./getters";
|
||||
import { getPreRequestMetrics } from "./pre-request";
|
||||
import { CollectionRunnerParam } from "../types/collections";
|
||||
import {
|
||||
getRequestMetrics,
|
||||
preProcessRequest,
|
||||
processRequest,
|
||||
} from "./request";
|
||||
import { getTestMetrics } from "./test";
|
||||
|
||||
const { WARN, FAIL } = exceptionColors;
|
||||
|
||||
@@ -55,19 +56,19 @@ export const collectionsRunner = async (
|
||||
// Pop out top-most collection from stack to be processed.
|
||||
const { collection, path } = <CollectionStack>collectionStack.pop();
|
||||
|
||||
// Processing each request in collection
|
||||
for (const request of collection.requests) {
|
||||
const _request = preProcessRequest(request);
|
||||
const requestPath = `${path}/${_request.name}`;
|
||||
const processRequestParams: ProcessRequestParams = {
|
||||
path: requestPath,
|
||||
request: _request,
|
||||
envs,
|
||||
delay,
|
||||
};
|
||||
// Processing each request in collection
|
||||
for (const request of collection.requests) {
|
||||
const _request = preProcessRequest(request as HoppRESTRequest, collection);
|
||||
const requestPath = `${path}/${_request.name}`;
|
||||
const processRequestParams: ProcessRequestParams = {
|
||||
path: requestPath,
|
||||
request: _request,
|
||||
envs,
|
||||
delay,
|
||||
};
|
||||
|
||||
// Request processing initiated message.
|
||||
log(WARN(`\nRunning: ${bold(requestPath)}`));
|
||||
// Request processing initiated message.
|
||||
log(WARN(`\nRunning: ${bold(requestPath)}`));
|
||||
|
||||
// Processing current request.
|
||||
const result = await processRequest(processRequestParams)();
|
||||
@@ -77,19 +78,34 @@ export const collectionsRunner = async (
|
||||
envs.global = global;
|
||||
envs.selected = selected;
|
||||
|
||||
// Storing current request's report.
|
||||
const requestReport = result.report;
|
||||
requestsReport.push(requestReport);
|
||||
}
|
||||
// Storing current request's report.
|
||||
const requestReport = result.report;
|
||||
requestsReport.push(requestReport);
|
||||
}
|
||||
|
||||
// Pushing remaining folders realted collection to stack.
|
||||
for (const folder of collection.folders) {
|
||||
collectionStack.push({
|
||||
path: `${path}/${folder.name}`,
|
||||
collection: folder,
|
||||
});
|
||||
// Pushing remaining folders realted collection to stack.
|
||||
for (const folder of collection.folders) {
|
||||
const updatedFolder: HoppCollection = { ...folder }
|
||||
|
||||
if (updatedFolder.auth?.authType === "inherit") {
|
||||
updatedFolder.auth = collection.auth;
|
||||
}
|
||||
|
||||
if (collection.headers?.length) {
|
||||
// Filter out header entries present in the parent collection under the same name
|
||||
// This ensures the folder headers take precedence over the collection headers
|
||||
const filteredHeaders = collection.headers.filter((collectionHeaderEntries) => {
|
||||
return !updatedFolder.headers.some((folderHeaderEntries) => folderHeaderEntries.key === collectionHeaderEntries.key)
|
||||
})
|
||||
updatedFolder.headers.push(...filteredHeaders);
|
||||
}
|
||||
|
||||
collectionStack.push({
|
||||
path: `${path}/${updatedFolder.name}`,
|
||||
collection: updatedFolder,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return requestsReport;
|
||||
};
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
|
||||
import axios, { Method } from "axios";
|
||||
import { URL } from "url";
|
||||
import * as S from "fp-ts/string";
|
||||
import * as A from "fp-ts/Array";
|
||||
import * as T from "fp-ts/Task";
|
||||
import * as E from "fp-ts/Either";
|
||||
import * as T from "fp-ts/Task";
|
||||
import * as TE from "fp-ts/TaskEither";
|
||||
import { HoppRESTRequest } from "@hoppscotch/data";
|
||||
import { responseErrors } from "./constants";
|
||||
import { getDurationInSeconds, getMetaDataPairs } from "./getters";
|
||||
import { testRunner, getTestScriptParams, hasFailedTestCases } from "./test";
|
||||
import { RequestConfig, EffectiveHoppRESTRequest } from "../interfaces/request";
|
||||
import { pipe } from "fp-ts/function";
|
||||
import * as S from "fp-ts/string";
|
||||
import { hrtime } from "process";
|
||||
import { URL } from "url";
|
||||
import { EffectiveHoppRESTRequest, RequestConfig } from "../interfaces/request";
|
||||
import { RequestRunnerResponse } from "../interfaces/response";
|
||||
import { preRequestScriptRunner } from "./pre-request";
|
||||
import { HoppCLIError, error } from "../types/errors";
|
||||
import {
|
||||
HoppEnvs,
|
||||
ProcessRequestParams,
|
||||
RequestReport,
|
||||
} from "../types/request";
|
||||
import { RequestMetrics } from "../types/response";
|
||||
import { responseErrors } from "./constants";
|
||||
import {
|
||||
printPreRequestRunner,
|
||||
printRequestRunner,
|
||||
printTestRunner,
|
||||
} from "./display";
|
||||
import { error, HoppCLIError } from "../types/errors";
|
||||
import { hrtime } from "process";
|
||||
import { RequestMetrics } from "../types/response";
|
||||
import { pipe } from "fp-ts/function";
|
||||
import { getDurationInSeconds, getMetaDataPairs } from "./getters";
|
||||
import { preRequestScriptRunner } from "./pre-request";
|
||||
import { getTestScriptParams, hasFailedTestCases, testRunner } from "./test";
|
||||
|
||||
// !NOTE: The `config.supported` checks are temporary until OAuth2 and Multipart Forms are supported
|
||||
|
||||
@@ -309,9 +309,12 @@ export const processRequest =
|
||||
* @returns Updated request object free of invalid/missing data.
|
||||
*/
|
||||
export const preProcessRequest = (
|
||||
request: HoppRESTRequest
|
||||
request: HoppRESTRequest,
|
||||
collection: HoppCollection,
|
||||
): HoppRESTRequest => {
|
||||
const tempRequest = Object.assign({}, request);
|
||||
const { headers: parentHeaders, auth: parentAuth } = collection;
|
||||
|
||||
if (!tempRequest.v) {
|
||||
tempRequest.v = "1";
|
||||
}
|
||||
@@ -327,18 +330,31 @@ export const preProcessRequest = (
|
||||
if (!tempRequest.params) {
|
||||
tempRequest.params = [];
|
||||
}
|
||||
if (!tempRequest.headers) {
|
||||
|
||||
if (parentHeaders?.length) {
|
||||
// Filter out header entries present in the parent (folder/collection) under the same name
|
||||
// This ensures the child headers take precedence over the parent headers
|
||||
const filteredEntries = parentHeaders.filter((parentHeaderEntries) => {
|
||||
return !tempRequest.headers.some((reqHeaderEntries) => reqHeaderEntries.key === parentHeaderEntries.key)
|
||||
})
|
||||
tempRequest.headers.push(...filteredEntries);
|
||||
} else if (!tempRequest.headers) {
|
||||
tempRequest.headers = [];
|
||||
}
|
||||
|
||||
if (!tempRequest.preRequestScript) {
|
||||
tempRequest.preRequestScript = "";
|
||||
}
|
||||
if (!tempRequest.testScript) {
|
||||
tempRequest.testScript = "";
|
||||
}
|
||||
if (!tempRequest.auth) {
|
||||
|
||||
if (tempRequest.auth?.authType === "inherit") {
|
||||
tempRequest.auth = parentAuth;
|
||||
} else if (!tempRequest.auth) {
|
||||
tempRequest.auth = { authActive: false, authType: "none" };
|
||||
}
|
||||
|
||||
if (!tempRequest.body) {
|
||||
tempRequest.body = { contentType: null, body: null };
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
--lower-tertiary-sticky-fold: 7.125rem;
|
||||
--lower-fourth-sticky-fold: 9.188rem;
|
||||
--sidebar-primary-sticky-fold: 2rem;
|
||||
--properties-primary-sticky-fold: 2.05rem;
|
||||
--properties-primary-sticky-fold: 2.063rem;
|
||||
}
|
||||
|
||||
@mixin light-theme {
|
||||
|
||||
@@ -139,9 +139,11 @@
|
||||
"generate_token": "Generate Token",
|
||||
"graphql_headers": "Authorization Headers are sent as part of the payload to connection_init",
|
||||
"include_in_url": "Include in URL",
|
||||
"inherited_from": "Inherited from {auth} from Parent Collection {collection} ",
|
||||
"learn": "Learn how",
|
||||
"pass_key_by": "Pass by",
|
||||
"password": "Password",
|
||||
"save_to_inherit": "Please save this request in any collection to inherit the authorization",
|
||||
"token": "Token",
|
||||
"type": "Authorization Type",
|
||||
"username": "Username",
|
||||
@@ -173,7 +175,7 @@
|
||||
"name_length_insufficient": "Collection name should be at least 3 characters long",
|
||||
"new": "New Collection",
|
||||
"order_changed": "Collection Order Updated",
|
||||
"properties":"Colection Properties",
|
||||
"properties":"Collection Properties",
|
||||
"properties_updated": "Collection Properties Updated",
|
||||
"renamed": "Collection renamed",
|
||||
"request_in_use": "Request in use",
|
||||
@@ -308,7 +310,8 @@
|
||||
"proxy_error": "Proxy error",
|
||||
"script_fail": "Could not execute pre-request script",
|
||||
"something_went_wrong": "Something went wrong",
|
||||
"test_script_fail": "Could not execute post-request script"
|
||||
"test_script_fail": "Could not execute post-request script",
|
||||
"authproviders_load_error": "Unable to load auth providers"
|
||||
},
|
||||
"export": {
|
||||
"as_json": "Export as JSON",
|
||||
@@ -974,4 +977,4 @@
|
||||
"team": "Team Workspace",
|
||||
"title": "Workspaces"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<AppShortcuts :show="showShortcuts" @close="showShortcuts = false" />
|
||||
<AppShare :show="showShare" @hide-modal="showShare = false" />
|
||||
<FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" />
|
||||
<FirebaseLogin v-if="showLogin" @hide-modal="showLogin = false" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -18,12 +18,11 @@
|
||||
:is-collection-property="true"
|
||||
@change-tab="changeOptionTab"
|
||||
/>
|
||||
<div class="bg-bannerInfo p-2 flex items-center">
|
||||
<div
|
||||
class="bg-bannerInfo px-4 py-2 flex items-center sticky bottom-0"
|
||||
>
|
||||
<icon-lucide-info class="svg-icons mr-2" />
|
||||
{{ t("helpers.collection_properties_header") }}
|
||||
<a href="hopp.sh" target="_blank" class="underline">{{
|
||||
t("action.learn_more")
|
||||
}}</a>
|
||||
</div>
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
@@ -36,12 +35,11 @@
|
||||
:is-root-collection="editingProperties?.isRootCollection"
|
||||
:inherited-properties="editingProperties?.inheritedProperties"
|
||||
/>
|
||||
<div class="bg-bannerInfo p-2 flex items-center">
|
||||
<div
|
||||
class="bg-bannerInfo px-4 py-2 flex items-center sticky bottom-0"
|
||||
>
|
||||
<icon-lucide-info class="svg-icons mr-2" />
|
||||
{{ t("helpers.collection_properties_authorization") }}
|
||||
<a href="hopp.sh" target="_blank" class="underline">{{
|
||||
t("action.learn_more")
|
||||
}}</a>
|
||||
</div>
|
||||
</HoppSmartTab>
|
||||
</HoppSmartTabs>
|
||||
|
||||
@@ -1,64 +1,71 @@
|
||||
<template>
|
||||
<HoppSmartModal
|
||||
v-if="show"
|
||||
dialog
|
||||
:title="`${t('auth.login_to_hoppscotch')}`"
|
||||
styles="sm:max-w-md"
|
||||
@close="hideModal"
|
||||
>
|
||||
<template #body>
|
||||
<div v-if="mode === 'sign-in'" class="flex flex-col space-y-2">
|
||||
<HoppSmartItem
|
||||
v-for="provider in allowedAuthProviders"
|
||||
:key="provider.id"
|
||||
:loading="provider.isLoading.value"
|
||||
:icon="provider.icon"
|
||||
:label="provider.label"
|
||||
@click="provider.action"
|
||||
/>
|
||||
|
||||
<hr v-if="additonalLoginItems.length > 0" />
|
||||
|
||||
<HoppSmartItem
|
||||
v-for="loginItem in additonalLoginItems"
|
||||
:key="loginItem.id"
|
||||
:icon="loginItem.icon"
|
||||
:label="loginItem.text(t)"
|
||||
@click="doAdditionalLoginItemClickAction(loginItem)"
|
||||
/>
|
||||
</div>
|
||||
<form
|
||||
v-if="mode === 'email'"
|
||||
class="flex flex-col space-y-2"
|
||||
@submit.prevent="signInWithEmail"
|
||||
>
|
||||
<HoppSmartInput
|
||||
v-model="form.email"
|
||||
type="email"
|
||||
placeholder=" "
|
||||
:label="t('auth.email')"
|
||||
input-styles="floating-input"
|
||||
/>
|
||||
|
||||
<HoppButtonPrimary
|
||||
:loading="signingInWithEmail"
|
||||
type="submit"
|
||||
:label="`${t('auth.send_magic_link')}`"
|
||||
/>
|
||||
</form>
|
||||
<div v-if="mode === 'email-sent'" class="flex flex-col px-4">
|
||||
<div class="flex max-w-md flex-col items-center justify-center">
|
||||
<icon-lucide-inbox class="h-6 w-6 text-accent" />
|
||||
<h3 class="my-2 text-center text-lg">
|
||||
{{ t("auth.we_sent_magic_link") }}
|
||||
</h3>
|
||||
<p class="text-center">
|
||||
{{
|
||||
t("auth.we_sent_magic_link_description", { email: form.email })
|
||||
}}
|
||||
</p>
|
||||
<template v-if="isLoadingAllowedAuthProviders">
|
||||
<div class="flex justify-center">
|
||||
<HoppSmartSpinner />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div v-if="mode === 'sign-in'" class="flex flex-col space-y-2">
|
||||
<HoppSmartItem
|
||||
v-for="provider in allowedAuthProviders"
|
||||
:key="provider.id"
|
||||
:loading="provider.isLoading.value"
|
||||
:icon="provider.icon"
|
||||
:label="provider.label"
|
||||
@click="provider.action"
|
||||
/>
|
||||
|
||||
<hr v-if="additonalLoginItems.length > 0" />
|
||||
|
||||
<HoppSmartItem
|
||||
v-for="loginItem in additonalLoginItems"
|
||||
:key="loginItem.id"
|
||||
:icon="loginItem.icon"
|
||||
:label="loginItem.text(t)"
|
||||
@click="doAdditionalLoginItemClickAction(loginItem)"
|
||||
/>
|
||||
</div>
|
||||
<form
|
||||
v-if="mode === 'email'"
|
||||
class="flex flex-col space-y-2"
|
||||
@submit.prevent="signInWithEmail"
|
||||
>
|
||||
<HoppSmartInput
|
||||
v-model="form.email"
|
||||
type="email"
|
||||
placeholder=" "
|
||||
:label="t('auth.email')"
|
||||
input-styles="floating-input"
|
||||
/>
|
||||
|
||||
<HoppButtonPrimary
|
||||
:loading="signingInWithEmail"
|
||||
type="submit"
|
||||
:label="`${t('auth.send_magic_link')}`"
|
||||
/>
|
||||
</form>
|
||||
<div v-if="mode === 'email-sent'" class="flex flex-col px-4">
|
||||
<div class="flex max-w-md flex-col items-center justify-center">
|
||||
<icon-lucide-inbox class="h-6 w-6 text-accent" />
|
||||
<h3 class="my-2 text-center text-lg">
|
||||
{{ t("auth.we_sent_magic_link") }}
|
||||
</h3>
|
||||
<p class="text-center">
|
||||
{{
|
||||
t("auth.we_sent_magic_link_description", { email: form.email })
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div
|
||||
@@ -109,7 +116,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Ref, computed, onMounted, ref } from "vue"
|
||||
import { Ref, onMounted, ref } from "vue"
|
||||
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useStreamSubscriber } from "@composables/stream"
|
||||
@@ -127,9 +134,7 @@ import { useService } from "dioc/vue"
|
||||
import { LoginItemDef } from "~/platform/auth"
|
||||
import { PersistenceService } from "~/services/persistence"
|
||||
|
||||
defineProps<{
|
||||
show: boolean
|
||||
}>()
|
||||
import * as E from "fp-ts/Either"
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
@@ -145,6 +150,8 @@ const form = {
|
||||
email: "",
|
||||
}
|
||||
|
||||
const isLoadingAllowedAuthProviders = ref(true)
|
||||
|
||||
const signingInWithGoogle = ref(false)
|
||||
const signingInWithGitHub = ref(false)
|
||||
const signingInWithMicrosoft = ref(false)
|
||||
@@ -162,21 +169,42 @@ type AuthProviderItem = {
|
||||
isLoading: Ref<boolean>
|
||||
}
|
||||
|
||||
const additonalLoginItems = computed(
|
||||
() => platform.auth.additionalLoginItems ?? []
|
||||
)
|
||||
let allowedAuthProviders: AuthProviderItem[] = []
|
||||
let additonalLoginItems: LoginItemDef[] = []
|
||||
|
||||
const doAdditionalLoginItemClickAction = async (item: LoginItemDef) => {
|
||||
await item.onClick()
|
||||
emit("hide-modal")
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
const currentUser$ = platform.auth.getCurrentUserStream()
|
||||
|
||||
subscribeToStream(currentUser$, (user) => {
|
||||
if (user) hideModal()
|
||||
})
|
||||
|
||||
const res = await platform.auth.getAllowedAuthProviders()
|
||||
|
||||
if (E.isLeft(res)) {
|
||||
toast.error(`${t("error.authproviders_load_error")}`)
|
||||
isLoadingAllowedAuthProviders.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// setup the normal auth providers
|
||||
const enabledAuthProviders = authProvidersAvailable.filter((provider) =>
|
||||
res.right.includes(provider.id)
|
||||
)
|
||||
allowedAuthProviders = enabledAuthProviders
|
||||
|
||||
// setup the additional login items
|
||||
additonalLoginItems =
|
||||
platform.auth.additionalLoginItems?.filter((item) =>
|
||||
res.right.includes(item.id)
|
||||
) ?? []
|
||||
|
||||
isLoadingAllowedAuthProviders.value = false
|
||||
})
|
||||
|
||||
const showLoginSuccess = () => {
|
||||
@@ -275,14 +303,7 @@ const signInWithEmail = async () => {
|
||||
})
|
||||
}
|
||||
|
||||
const hideModal = () => {
|
||||
mode.value = "sign-in"
|
||||
toast.clear()
|
||||
|
||||
emit("hide-modal")
|
||||
}
|
||||
|
||||
const authProviders: AuthProviderItem[] = [
|
||||
const authProvidersAvailable: AuthProviderItem[] = [
|
||||
{
|
||||
id: "GITHUB",
|
||||
icon: IconGithub,
|
||||
@@ -315,19 +336,10 @@ const authProviders: AuthProviderItem[] = [
|
||||
},
|
||||
]
|
||||
|
||||
// Do not format the `import.meta.env.VITE_ALLOWED_AUTH_PROVIDERS` call into multiple lines!
|
||||
// prettier-ignore
|
||||
const allowedAuthProvidersIDsString =
|
||||
import.meta.env.VITE_ALLOWED_AUTH_PROVIDERS
|
||||
const hideModal = () => {
|
||||
mode.value = "sign-in"
|
||||
toast.clear()
|
||||
|
||||
const allowedAuthProvidersIDs = allowedAuthProvidersIDsString
|
||||
? allowedAuthProvidersIDsString.split(",")
|
||||
: []
|
||||
|
||||
const allowedAuthProviders =
|
||||
allowedAuthProvidersIDs.length > 0
|
||||
? authProviders.filter((provider) =>
|
||||
allowedAuthProvidersIDs.includes(provider.id)
|
||||
)
|
||||
: authProviders
|
||||
emit("hide-modal")
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<template>
|
||||
<div class="flex flex-1 flex-col">
|
||||
<div
|
||||
class="sticky top-sidebarPrimaryStickyFold z-10 flex items-center justify-between border-y border-dividerLight bg-primary pl-4"
|
||||
class="sticky z-10 flex items-center justify-between border-y border-dividerLight bg-primary pl-4"
|
||||
:class="[
|
||||
isCollectionProperty
|
||||
? 'top-propertiesPrimaryStickyFold'
|
||||
: 'top-sidebarPrimaryStickyFold',
|
||||
]"
|
||||
>
|
||||
<span class="flex items-center">
|
||||
<label class="truncate font-semibold text-secondaryLight">
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<template>
|
||||
<div
|
||||
class="sticky top-sidebarPrimaryStickyFold z-10 flex items-center justify-between border-y border-dividerLight bg-primary pl-4"
|
||||
class="sticky z-10 flex items-center justify-between border-y border-dividerLight bg-primary pl-4"
|
||||
:class="[
|
||||
isCollectionProperty
|
||||
? 'top-propertiesPrimaryStickyFold'
|
||||
: 'top-sidebarPrimaryStickyFold',
|
||||
]"
|
||||
>
|
||||
<label class="font-semibold text-secondaryLight">
|
||||
{{ t("tab.headers") }}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<div class="flex flex-1 flex-col">
|
||||
<div
|
||||
class="sticky z-10 flex flex-shrink-0 items-center justify-between overflow-x-auto border-b border-dividerLight bg-primary pl-4"
|
||||
:class="{
|
||||
'top-upperMobileSecondaryStickyFold sm:top-upperSecondaryStickyFold':
|
||||
!isCollectionProperty,
|
||||
'top-propertiesPrimaryStickyFold': isCollectionProperty,
|
||||
}"
|
||||
:class="[
|
||||
isCollectionProperty
|
||||
? 'top-propertiesPrimaryStickyFold'
|
||||
: 'top-upperMobileSecondaryStickyFold sm:top-upperSecondaryStickyFold',
|
||||
]"
|
||||
>
|
||||
<span class="flex items-center">
|
||||
<label class="truncate font-semibold text-secondaryLight">
|
||||
@@ -154,13 +154,17 @@
|
||||
</div>
|
||||
<div v-if="auth.authType === 'inherit'" class="p-4">
|
||||
<span v-if="inheritedProperties?.auth">
|
||||
Inherited
|
||||
{{ getAuthName(inheritedProperties.auth.inheritedAuth.authType) }}
|
||||
from Parent Collection {{ inheritedProperties?.auth.parentName }}
|
||||
{{
|
||||
t("authorization.inherited_from", {
|
||||
auth: getAuthName(
|
||||
inheritedProperties.auth.inheritedAuth.authType
|
||||
),
|
||||
collection: inheritedProperties?.auth.parentName,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span v-else>
|
||||
Please save this request in any collection to inherit the
|
||||
authorization
|
||||
{{ t("authorization.save_to_inherit") }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="auth.authType === 'bearer'">
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<div class="flex flex-1 flex-col">
|
||||
<div
|
||||
class="sticky z-10 flex flex-shrink-0 items-center justify-between overflow-x-auto border-b border-dividerLight bg-primary pl-4"
|
||||
:class="{
|
||||
'top-upperMobileSecondaryStickyFold sm:top-upperSecondaryStickyFold':
|
||||
!isCollectionProperty,
|
||||
'top-propertiesPrimaryStickyFold': isCollectionProperty,
|
||||
}"
|
||||
:class="[
|
||||
isCollectionProperty
|
||||
? 'top-propertiesPrimaryStickyFold'
|
||||
: 'top-upperMobileSecondaryStickyFold sm:top-upperSecondaryStickyFold',
|
||||
]"
|
||||
>
|
||||
<label class="truncate font-semibold text-secondaryLight">
|
||||
{{ t("request.header_list") }}
|
||||
|
||||
@@ -199,6 +199,8 @@ const getHoppFolder = (
|
||||
getHoppFolder(f, resources)
|
||||
),
|
||||
requests: getRequestsIn(folderRes, resources).map(getHoppRequest),
|
||||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
})
|
||||
|
||||
const getHoppCollections = (doc: InsomniaDoc) =>
|
||||
|
||||
@@ -592,6 +592,8 @@ const convertOpenApiDocToHopp = (
|
||||
name,
|
||||
folders: [],
|
||||
requests: paths,
|
||||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -292,6 +292,8 @@ const getHoppFolder = (ig: ItemGroup<Item>): HoppCollection =>
|
||||
A.map(getHoppFolder)
|
||||
),
|
||||
requests: pipe(ig.items.all(), A.filter(isPMItem), A.map(getHoppRequest)),
|
||||
auth: { authType: "inherit", authActive: true },
|
||||
headers: [],
|
||||
})
|
||||
|
||||
export const getHoppCollection = (coll: PMCollection) => getHoppFolder(coll)
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ClientOptions } from "@urql/core"
|
||||
import { Observable } from "rxjs"
|
||||
import { Component } from "vue"
|
||||
import { getI18n } from "~/modules/i18n"
|
||||
import * as E from "fp-ts/Either"
|
||||
|
||||
/**
|
||||
* A common (and required) set of fields that describe a user.
|
||||
@@ -222,6 +223,11 @@ export type AuthPlatformDef = {
|
||||
*/
|
||||
setDisplayName: (name: string) => Promise<void>
|
||||
|
||||
/**
|
||||
* Returns the list of allowed auth providers for the platform ( the currently supported ones are GOOGLE, GITHUB, EMAIL, MICROSOFT, SAML )
|
||||
*/
|
||||
getAllowedAuthProviders: () => Promise<E.Either<string, string[]>>
|
||||
|
||||
/**
|
||||
* Defines the additional login items that should be shown in the login screen
|
||||
*/
|
||||
|
||||
@@ -37,7 +37,8 @@
|
||||
"stream-browserify": "^3.0.0",
|
||||
"util": "^0.12.5",
|
||||
"vue": "^3.3.8",
|
||||
"workbox-window": "^7.0.0"
|
||||
"workbox-window": "^7.0.0",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "^5.0.0",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createHoppApp } from "@hoppscotch/common"
|
||||
import { def as authDef } from "./platform/auth"
|
||||
import { def as authDef } from "./platform/auth/auth.platform"
|
||||
import { def as environmentsDef } from "./platform/environments/environments.platform"
|
||||
import { def as collectionsDef } from "./platform/collections/collections.platform"
|
||||
import { def as settingsDef } from "./platform/settings/settings.platform"
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import axios from "axios"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { z } from "zod"
|
||||
|
||||
const expectedAllowedProvidersSchema = z.object({
|
||||
// currently supported values are "GOOGLE", "GITHUB", "EMAIL", "MICROSOFT", "SAML"
|
||||
// keeping it as string to avoid backend accidentally breaking frontend when adding new providers
|
||||
providers: z.array(z.string()),
|
||||
})
|
||||
|
||||
export const getAllowedAuthProviders = async () => {
|
||||
try {
|
||||
const res = await axios.get(
|
||||
`${import.meta.env.VITE_BACKEND_API_URL}/auth/providers`,
|
||||
{
|
||||
withCredentials: true,
|
||||
}
|
||||
)
|
||||
|
||||
const parseResult = expectedAllowedProvidersSchema.safeParse(res.data)
|
||||
|
||||
if (!parseResult.success) {
|
||||
return E.left("SOMETHING_WENT_WRONG")
|
||||
}
|
||||
|
||||
return E.right(parseResult.data.providers)
|
||||
} catch (_) {
|
||||
return E.left("SOMETHING_WENT_WRONG")
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { PersistenceService } from "@hoppscotch/common/services/persistence"
|
||||
import axios from "axios"
|
||||
import { BehaviorSubject, Subject } from "rxjs"
|
||||
import { Ref, ref, watch } from "vue"
|
||||
import { getAllowedAuthProviders } from "./auth.api"
|
||||
|
||||
export const authEvents$ = new Subject<AuthEvent | { event: "token_refresh" }>()
|
||||
const currentUser$ = new BehaviorSubject<HoppUser | null>(null)
|
||||
@@ -341,4 +342,5 @@ export const def: AuthPlatformDef = {
|
||||
window.location.href = "/"
|
||||
}
|
||||
},
|
||||
getAllowedAuthProviders,
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { authEvents$, def as platformAuth } from "@platform/auth"
|
||||
import { authEvents$, def as platformAuth } from "@platform/auth/auth.platform"
|
||||
import { CollectionsPlatformDef } from "@hoppscotch/common/platform/collections"
|
||||
import { runDispatchWithOutSyncing } from "../../lib/sync"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { authEvents$, def as platformAuth } from "@platform/auth"
|
||||
import { authEvents$, def as platformAuth } from "@platform/auth/auth.platform"
|
||||
import {
|
||||
createEnvironment,
|
||||
deleteEnvironment,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { authEvents$, def as platformAuth } from "@platform/auth"
|
||||
import { authEvents$, def as platformAuth } from "@platform/auth/auth.platform"
|
||||
import {
|
||||
restHistoryStore,
|
||||
RESTHistoryEntry,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { SettingsPlatformDef } from "@hoppscotch/common/platform/settings"
|
||||
import { settingsSyncer } from "./settings.sync"
|
||||
import { authEvents$, def as platformAuth } from "@platform/auth"
|
||||
import { authEvents$, def as platformAuth } from "@platform/auth/auth.platform"
|
||||
import {
|
||||
createUserSettings,
|
||||
getUserSettings,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { PersistableTabState } from "@hoppscotch/common/services/tab"
|
||||
import { HoppRESTDocument } from "@hoppscotch/common/helpers/rest/document"
|
||||
import { HoppUser } from "@hoppscotch/common/platform/auth"
|
||||
import { TabStatePlatformDef } from "@hoppscotch/common/platform/tab"
|
||||
import { def as platformAuth } from "@platform/auth"
|
||||
import { def as platformAuth } from "@platform/auth/auth.platform"
|
||||
import { getCurrentRestSession, updateUserSession } from "./tabState.api"
|
||||
import { SessionType } from "../../api/generated/graphql"
|
||||
import * as E from "fp-ts/Either"
|
||||
|
||||
@@ -26,6 +26,7 @@ export default {
|
||||
lowerFourthStickyFold: "var(--lower-fourth-sticky-fold)",
|
||||
sidebarPrimaryStickyFold: "var(--sidebar-primary-sticky-fold)",
|
||||
sidebarSecondaryStickyFold: "var(--line-height-body)",
|
||||
propertiesPrimaryStickyFold: "var(--properties-primary-sticky-fold)",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
"send_magic_link": "Send magic link",
|
||||
"sign_in_agreement": "By signing in, you are agreeing to our",
|
||||
"sign_in_options": "All sign in option",
|
||||
"sign_out": "sign out",
|
||||
"sign_out": "Sign out",
|
||||
"team_name_long": "Team name should be atleast 6 characters long!!",
|
||||
"user_not_found": "User not found in the infra!!"
|
||||
},
|
||||
|
||||
@@ -30,7 +30,6 @@ declare module '@vue/runtime-core' {
|
||||
HoppSmartTable: typeof import('@hoppscotch/ui')['HoppSmartTable']
|
||||
HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs']
|
||||
HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle']
|
||||
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
||||
IconLucideChevronDown: typeof import('~icons/lucide/chevron-down')['default']
|
||||
IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
|
||||
SettingsAuthProvider: typeof import('./components/settings/AuthProvider.vue')['default']
|
||||
|
||||
@@ -184,6 +184,10 @@ const nonAdminUser = ref(false);
|
||||
const allowedAuthProviders = ref<string[]>([]);
|
||||
|
||||
onMounted(async () => {
|
||||
const user = auth.getCurrentUser();
|
||||
if (user && !user.isAdmin) {
|
||||
nonAdminUser.value = true;
|
||||
}
|
||||
allowedAuthProviders.value = await getAllowedAuthProviders();
|
||||
});
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
--lower-tertiary-sticky-fold: 7.125rem;
|
||||
--lower-fourth-sticky-fold: 9.188rem;
|
||||
--sidebar-primary-sticky-fold: 2rem;
|
||||
--properties-primary-sticky-fold: 2.05rem;
|
||||
--properties-primary-sticky-fold: 2.063rem;
|
||||
}
|
||||
|
||||
@mixin dark-theme {
|
||||
|
||||
28
pnpm-lock.yaml
generated
28
pnpm-lock.yaml
generated
@@ -1099,6 +1099,9 @@ importers:
|
||||
workbox-window:
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.0
|
||||
zod:
|
||||
specifier: ^3.22.4
|
||||
version: 3.22.4
|
||||
devDependencies:
|
||||
'@graphql-codegen/add':
|
||||
specifier: ^5.0.0
|
||||
@@ -7645,7 +7648,7 @@ packages:
|
||||
dependencies:
|
||||
'@intlify/bundle-utils': 7.4.0(vue-i18n@9.2.2)
|
||||
'@intlify/shared': 9.4.1
|
||||
'@rollup/pluginutils': 5.0.3(rollup@2.79.1)
|
||||
'@rollup/pluginutils': 5.0.3(rollup@3.29.4)
|
||||
'@vue/compiler-sfc': 3.3.10
|
||||
debug: 4.3.4(supports-color@9.2.2)
|
||||
fast-glob: 3.3.1
|
||||
@@ -9365,6 +9368,7 @@ packages:
|
||||
estree-walker: 2.0.2
|
||||
picomatch: 2.3.1
|
||||
rollup: 2.79.1
|
||||
dev: true
|
||||
|
||||
/@rollup/pluginutils@5.0.3(rollup@3.29.4):
|
||||
resolution: {integrity: sha512-hfllNN4a80rwNQ9QCxhxuHCGHMAvabXqxNdaChUSSadMre7t4iEUI6fFAhBOn/eIYTgYVhBv7vCLsAJ4u3lf3g==}
|
||||
@@ -9379,7 +9383,6 @@ packages:
|
||||
estree-walker: 2.0.2
|
||||
picomatch: 2.3.1
|
||||
rollup: 3.29.4
|
||||
dev: true
|
||||
|
||||
/@rollup/pluginutils@5.1.0(rollup@2.79.1):
|
||||
resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==}
|
||||
@@ -11264,7 +11267,7 @@ packages:
|
||||
regenerator-runtime: 0.13.11
|
||||
systemjs: 6.14.2
|
||||
terser: 5.24.0
|
||||
vite: 4.5.0(@types/node@17.0.27)(sass@1.69.5)(terser@5.24.0)
|
||||
vite: 4.5.0(@types/node@17.0.27)(sass@1.53.0)(terser@5.24.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -11297,7 +11300,7 @@ packages:
|
||||
vite: ^4.0.0 || ^5.0.0
|
||||
vue: ^3.2.25
|
||||
dependencies:
|
||||
vite: 4.5.0(@types/node@17.0.27)(sass@1.69.5)(terser@5.24.0)
|
||||
vite: 4.5.0(@types/node@17.0.27)(sass@1.53.0)(terser@5.24.0)
|
||||
vue: 3.3.9(typescript@5.3.2)
|
||||
dev: true
|
||||
|
||||
@@ -11857,7 +11860,7 @@ packages:
|
||||
dependencies:
|
||||
'@vue/compiler-ssr': 3.3.9
|
||||
'@vue/shared': 3.3.9
|
||||
vue: 3.3.9(typescript@4.9.5)
|
||||
vue: 3.3.9(typescript@5.3.2)
|
||||
|
||||
/@vue/shared@3.2.45:
|
||||
resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==}
|
||||
@@ -23454,6 +23457,7 @@ packages:
|
||||
chokidar: 3.5.3
|
||||
immutable: 4.3.2
|
||||
source-map-js: 1.0.2
|
||||
dev: true
|
||||
|
||||
/sax@1.2.4:
|
||||
resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==}
|
||||
@@ -25457,7 +25461,7 @@ packages:
|
||||
dependencies:
|
||||
fast-glob: 3.3.1
|
||||
unplugin: 1.4.0
|
||||
vite: 4.5.0(@types/node@17.0.27)(sass@1.69.5)(terser@5.24.0)
|
||||
vite: 4.5.0(@types/node@17.0.27)(sass@1.53.0)(terser@5.24.0)
|
||||
dev: true
|
||||
|
||||
/unplugin-icons@0.14.9(@vue/compiler-sfc@3.2.45)(vite@3.2.4):
|
||||
@@ -26279,7 +26283,7 @@ packages:
|
||||
vite: ^2.0.0 || ^3.0.0 || ^4.0.0
|
||||
dependencies:
|
||||
fast-glob: 3.3.1
|
||||
vite: 4.5.0(@types/node@17.0.27)(sass@1.69.5)(terser@5.24.0)
|
||||
vite: 4.5.0(@types/node@17.0.27)(sass@1.53.0)(terser@5.24.0)
|
||||
dev: true
|
||||
|
||||
/vite-plugin-html-config@1.0.10(vite@3.2.4):
|
||||
@@ -26297,7 +26301,7 @@ packages:
|
||||
peerDependencies:
|
||||
vite: '>=2.0.0'
|
||||
dependencies:
|
||||
vite: 4.5.0(@types/node@17.0.27)(sass@1.69.5)(terser@5.24.0)
|
||||
vite: 4.5.0(@types/node@17.0.27)(sass@1.53.0)(terser@5.24.0)
|
||||
dev: true
|
||||
|
||||
/vite-plugin-inspect@0.7.38(rollup@2.79.1)(vite@4.5.0):
|
||||
@@ -26538,7 +26542,7 @@ packages:
|
||||
debug: 4.3.4(supports-color@9.2.2)
|
||||
fast-glob: 3.3.2
|
||||
pretty-bytes: 6.1.1
|
||||
vite: 4.5.0(@types/node@17.0.27)(sass@1.69.5)(terser@5.24.0)
|
||||
vite: 4.5.0(@types/node@17.0.27)(sass@1.53.0)(terser@5.24.0)
|
||||
workbox-build: 7.0.0
|
||||
workbox-window: 7.0.0
|
||||
transitivePeerDependencies:
|
||||
@@ -26632,7 +26636,7 @@ packages:
|
||||
'@vue/compiler-sfc': 3.3.8
|
||||
debug: 4.3.4(supports-color@9.2.2)
|
||||
fast-glob: 3.3.1
|
||||
vite: 4.5.0(@types/node@17.0.27)(sass@1.69.5)(terser@5.24.0)
|
||||
vite: 4.5.0(@types/node@17.0.27)(sass@1.53.0)(terser@5.24.0)
|
||||
vue: 3.3.9(typescript@5.3.2)
|
||||
vue-router: 4.2.5(vue@3.3.9)
|
||||
transitivePeerDependencies:
|
||||
@@ -26758,7 +26762,6 @@ packages:
|
||||
terser: 5.24.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
dev: true
|
||||
|
||||
/vite@4.5.0(@types/node@17.0.27)(sass@1.69.5)(terser@5.24.0):
|
||||
resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==}
|
||||
@@ -26790,12 +26793,13 @@ packages:
|
||||
dependencies:
|
||||
'@types/node': 17.0.27
|
||||
esbuild: 0.18.20
|
||||
postcss: 8.4.31
|
||||
postcss: 8.4.32
|
||||
rollup: 3.29.4
|
||||
sass: 1.69.5
|
||||
terser: 5.24.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
dev: true
|
||||
|
||||
/vite@4.5.0(@types/node@18.18.8)(sass@1.69.5)(terser@5.24.0):
|
||||
resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==}
|
||||
|
||||
Reference in New Issue
Block a user