mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 18:41:21 +00:00
Merge pull request #45 from gangjun06/refactor2
Edit frontend .prettierrc config
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"tabWidth": 4,
|
"tabWidth": 2,
|
||||||
"useTabs": true
|
"useTabs": false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,85 +1,84 @@
|
|||||||
import { useEffect,useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
|
import { publicPaths } from "~/const";
|
||||||
import checkAuth from "~/pages/api/auth/CheckAuth";
|
import checkAuth from "~/pages/api/auth/CheckAuth";
|
||||||
|
|
||||||
import { publicPaths } from "../const";
|
|
||||||
|
|
||||||
// #TODO: finish spinner only when the data loads fully
|
// #TODO: finish spinner only when the data loads fully
|
||||||
// #TODO: Redirect somewhere if the page does not exist
|
// #TODO: Redirect somewhere if the page does not exist
|
||||||
|
|
||||||
export default function RouteGuard({ children }) {
|
export default function RouteGuard({ children }) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [authorized, setAuthorized] = useState(false);
|
const [authorized, setAuthorized] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// on initial load - run auth check
|
// on initial load - run auth check
|
||||||
(async () => {
|
(async () => {
|
||||||
await authCheck(router.asPath);
|
await authCheck(router.asPath);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// on route change start - hide page content by setting authorized to false
|
// on route change start - hide page content by setting authorized to false
|
||||||
// #TODO: add the loading page when not yet authorized.
|
// #TODO: add the loading page when not yet authorized.
|
||||||
const hideContent = () => setAuthorized(false);
|
const hideContent = () => setAuthorized(false);
|
||||||
// const onError = () => setAuthorized(true)
|
// const onError = () => setAuthorized(true)
|
||||||
router.events.on("routeChangeStart", hideContent);
|
router.events.on("routeChangeStart", hideContent);
|
||||||
// router.events.on("routeChangeError", onError);
|
// router.events.on("routeChangeError", onError);
|
||||||
|
|
||||||
// on route change complete - run auth check
|
// on route change complete - run auth check
|
||||||
router.events.on("routeChangeComplete", authCheck);
|
router.events.on("routeChangeComplete", authCheck);
|
||||||
|
|
||||||
// unsubscribe from events in useEffect return function
|
// unsubscribe from events in useEffect return function
|
||||||
return () => {
|
return () => {
|
||||||
router.events.off("routeChangeStart", hideContent);
|
router.events.off("routeChangeStart", hideContent);
|
||||||
router.events.off("routeChangeComplete", authCheck);
|
router.events.off("routeChangeComplete", authCheck);
|
||||||
// router.events.off("routeChangeError", onError);
|
// router.events.off("routeChangeError", onError);
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* redirect to login page if accessing a private page and not logged in
|
* redirect to login page if accessing a private page and not logged in
|
||||||
* @param {*} url - the url of the page we are trying to go to
|
* @param {*} url - the url of the page we are trying to go to
|
||||||
*/
|
*/
|
||||||
async function authCheck(url) {
|
async function authCheck(url) {
|
||||||
// Make sure that we don't redirect when the user is on the following pages.
|
// Make sure that we don't redirect when the user is on the following pages.
|
||||||
const path = "/" + url.split("?")[0].split("/")[1];
|
const path = "/" + url.split("?")[0].split("/")[1];
|
||||||
|
|
||||||
// Check if the user is authenticated
|
// Check if the user is authenticated
|
||||||
const response = await checkAuth();
|
const response = await checkAuth();
|
||||||
// #TODO: figure our why sometimes it doesn't output a response
|
// #TODO: figure our why sometimes it doesn't output a response
|
||||||
if (!publicPaths.includes(path)) {
|
if (!publicPaths.includes(path)) {
|
||||||
try {
|
try {
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
router.push("/login");
|
router.push("/login");
|
||||||
console.log("Unauthorized to access.");
|
console.log("Unauthorized to access.");
|
||||||
setAuthorized(false);
|
setAuthorized(false);
|
||||||
} else {
|
} else {
|
||||||
setAuthorized(true);
|
setAuthorized(true);
|
||||||
console.log("Authorized to access.");
|
console.log("Authorized to access.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(
|
console.log(
|
||||||
"Error (probably the authCheck route is stuck again...):",
|
"Error (probably the authCheck route is stuck again...):",
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authorized) {
|
if (authorized) {
|
||||||
return children;
|
return children;
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div className="w-screen h-screen bg-bunker-800 flex items-center justify-center">
|
<div className="w-screen h-screen bg-bunker-800 flex items-center justify-center">
|
||||||
<Image
|
<Image
|
||||||
src="/images/loading/loading.gif"
|
src="/images/loading/loading.gif"
|
||||||
height={70}
|
height={70}
|
||||||
width={120}
|
width={120}
|
||||||
alt="google logo"
|
alt="google logo"
|
||||||
></Image>
|
></Image>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
import posthog from "posthog-js";
|
import posthog from "posthog-js";
|
||||||
|
|
||||||
import { ENV, POSTHOG_API_KEY, POSTHOG_HOST, TELEMETRY_ENABLED } from "../utilities/config";
|
import {
|
||||||
|
ENV,
|
||||||
|
POSTHOG_API_KEY,
|
||||||
|
POSTHOG_HOST,
|
||||||
|
TELEMETRY_ENABLED,
|
||||||
|
} from "../utilities/config";
|
||||||
|
|
||||||
export const initPostHog = () => {
|
export const initPostHog = () => {
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
if (ENV == "production" && TELEMETRY_ENABLED) {
|
if (ENV == "production" && TELEMETRY_ENABLED) {
|
||||||
posthog.init(POSTHOG_API_KEY, {
|
posthog.init(POSTHOG_API_KEY, {
|
||||||
api_host: POSTHOG_HOST,
|
api_host: POSTHOG_HOST,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return posthog;
|
return posthog;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,17 +3,15 @@ import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons";
|
|||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
export default function Error({ text }) {
|
export default function Error({ text }) {
|
||||||
return (
|
return (
|
||||||
<div className="relative bg-red-500 opacity-100 border flex flex-row justify-center m-auto items-center w-fit rounded-full mb-4">
|
<div className="relative bg-red-500 opacity-100 border flex flex-row justify-center m-auto items-center w-fit rounded-full mb-4">
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faExclamationTriangle}
|
icon={faExclamationTriangle}
|
||||||
className="text-white mt-1.5 mb-2 mx-2"
|
className="text-white mt-1.5 mb-2 mx-2"
|
||||||
/>
|
/>
|
||||||
{text && (
|
{text && (
|
||||||
<p className="relative top-0 text-white mr-2 text-sm py-1">
|
<p className="relative top-0 text-white mr-2 text-sm py-1">{text}</p>
|
||||||
{text}
|
)}
|
||||||
</p>
|
</div>
|
||||||
)}
|
);
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import React from "react";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import {
|
import {
|
||||||
faCircle,
|
faCircle,
|
||||||
faCircleExclamation,
|
faCircleExclamation,
|
||||||
faE,
|
faE,
|
||||||
faEye,
|
faEye,
|
||||||
faEyeSlash,
|
faEyeSlash,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
@@ -14,36 +14,36 @@ import guidGenerator from "../utilities/randomId";
|
|||||||
import Error from "./Error";
|
import Error from "./Error";
|
||||||
|
|
||||||
const InputField = (props) => {
|
const InputField = (props) => {
|
||||||
const [passwordVisible, setPasswordVisible] = useState(false);
|
const [passwordVisible, setPasswordVisible] = useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
if (props.static === true) {
|
if (props.static === true) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col my-2 md:my-4 justify-center w-full max-w-md">
|
<div className="flex flex-col my-2 md:my-4 justify-center w-full max-w-md">
|
||||||
<p className="text-sm font-semibold text-gray-400 mb-0.5">
|
<p className="text-sm font-semibold text-gray-400 mb-0.5">
|
||||||
{props.label}
|
{props.label}
|
||||||
</p>
|
</p>
|
||||||
{props.text && (
|
{props.text && (
|
||||||
<p className="text-xs text-gray-400 mb-2">{props.text}</p>
|
<p className="text-xs text-gray-400 mb-2">{props.text}</p>
|
||||||
)}
|
)}
|
||||||
<input
|
<input
|
||||||
onChange={(e) => props.onChangeHandler(e.target.value)}
|
onChange={(e) => props.onChangeHandler(e.target.value)}
|
||||||
type={props.type}
|
type={props.type}
|
||||||
placeholder={props.placeholder}
|
placeholder={props.placeholder}
|
||||||
value={props.value}
|
value={props.value}
|
||||||
required={props.isRequired}
|
required={props.isRequired}
|
||||||
className="bg-bunker-800 text-gray-400 border border-gray-600 rounded-md text-md p-2 w-full min-w-16 outline-none"
|
className="bg-bunker-800 text-gray-400 border border-gray-600 rounded-md text-md p-2 w-full min-w-16 outline-none"
|
||||||
name={props.name}
|
name={props.name}
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div className="flex-col w-full">
|
<div className="flex-col w-full">
|
||||||
<div className="flex flex-row text-mineshaft-300 items-center mb-0.5">
|
<div className="flex flex-row text-mineshaft-300 items-center mb-0.5">
|
||||||
<p className="text-sm font-semibold mr-1">{props.label}</p>
|
<p className="text-sm font-semibold mr-1">{props.label}</p>
|
||||||
{/* {props.label == "Password" && router.asPath != "/login" && (
|
{/* {props.label == "Password" && router.asPath != "/login" && (
|
||||||
<div className="mb-0.5 relative inline-block text-gray-400 underline hover:text-primary duration-200">
|
<div className="mb-0.5 relative inline-block text-gray-400 underline hover:text-primary duration-200">
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faCircleExclamation}
|
icon={faCircleExclamation}
|
||||||
@@ -59,73 +59,71 @@ const InputField = (props) => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)} */}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`group relative flex flex-col justify-center w-full max-w-2xl border ${
|
className={`group relative flex flex-col justify-center w-full max-w-2xl border ${
|
||||||
props.error ? "border-red" : "border-mineshaft-500"
|
props.error ? "border-red" : "border-mineshaft-500"
|
||||||
} rounded-md`}
|
} rounded-md`}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
onChange={(e) => props.onChangeHandler(e.target.value)}
|
onChange={(e) => props.onChangeHandler(e.target.value)}
|
||||||
type={passwordVisible == false ? props.type : "text"}
|
type={passwordVisible == false ? props.type : "text"}
|
||||||
placeholder={props.placeholder}
|
placeholder={props.placeholder}
|
||||||
value={props.value}
|
value={props.value}
|
||||||
required={props.isRequired}
|
required={props.isRequired}
|
||||||
className={`${
|
className={`${
|
||||||
props.blurred
|
props.blurred
|
||||||
? "text-bunker-800 group-hover:text-gray-400 focus:text-gray-400 active:text-gray-400"
|
? "text-bunker-800 group-hover:text-gray-400 focus:text-gray-400 active:text-gray-400"
|
||||||
: ""
|
: ""
|
||||||
} ${
|
} ${
|
||||||
props.error
|
props.error ? "focus:ring-red/50" : "focus:ring-primary/50"
|
||||||
? "focus:ring-red/50"
|
} relative peer bg-bunker-800 rounded-md text-gray-400 text-md p-2 w-full min-w-16 outline-none focus:ring-4 duration-200`}
|
||||||
: "focus:ring-primary/50"
|
name={props.name}
|
||||||
} relative peer bg-bunker-800 rounded-md text-gray-400 text-md p-2 w-full min-w-16 outline-none focus:ring-4 duration-200`}
|
spellCheck="false"
|
||||||
name={props.name}
|
/>
|
||||||
spellCheck="false"
|
{props.label?.includes("Password") && (
|
||||||
/>
|
<button
|
||||||
{props.label?.includes("Password") && (
|
onClick={() => {
|
||||||
<button
|
setPasswordVisible(!passwordVisible);
|
||||||
onClick={() => {
|
}}
|
||||||
setPasswordVisible(!passwordVisible);
|
className="absolute self-end mr-3 text-gray-400 cursor-pointer"
|
||||||
}}
|
>
|
||||||
className="absolute self-end mr-3 text-gray-400 cursor-pointer"
|
{passwordVisible ? (
|
||||||
>
|
<FontAwesomeIcon icon={faEyeSlash} />
|
||||||
{passwordVisible ? (
|
) : (
|
||||||
<FontAwesomeIcon icon={faEyeSlash} />
|
<FontAwesomeIcon icon={faEye} />
|
||||||
) : (
|
)}
|
||||||
<FontAwesomeIcon icon={faEye} />
|
</button>
|
||||||
)}
|
)}
|
||||||
</button>
|
{props.blurred && (
|
||||||
)}
|
<div className="peer group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible absolute h-10 w-fit max-w-xl rounded-md flex items-center text-gray-400/50 text-clip overflow-hidden">
|
||||||
{props.blurred && (
|
<p className="ml-2"></p>
|
||||||
<div className="peer group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible absolute h-10 w-fit max-w-xl rounded-md flex items-center text-gray-400/50 text-clip overflow-hidden">
|
{props.value
|
||||||
<p className="ml-2"></p>
|
.split("")
|
||||||
{props.value
|
.slice(0, 54)
|
||||||
.split("")
|
.map(() => (
|
||||||
.slice(0, 54)
|
<FontAwesomeIcon
|
||||||
.map(() => (
|
key={guidGenerator()}
|
||||||
<FontAwesomeIcon
|
className="text-xxs mx-0.5"
|
||||||
key={guidGenerator()}
|
icon={faCircle}
|
||||||
className="text-xxs mx-0.5"
|
/>
|
||||||
icon={faCircle}
|
))}
|
||||||
/>
|
</div>
|
||||||
))}
|
)}
|
||||||
</div>
|
{/* {props.error && (
|
||||||
)}
|
|
||||||
{/* {props.error && (
|
|
||||||
<div className="absolute z-20 flex items-end justify-end mt-4 mr-1.5 self-end">
|
<div className="absolute z-20 flex items-end justify-end mt-4 mr-1.5 self-end">
|
||||||
<Error />
|
<Error />
|
||||||
</div>
|
</div>
|
||||||
)} */}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
{props.error && (
|
{props.error && (
|
||||||
<p className="text-red text-xs mt-0.5 mx-0 mb-2 max-w-xs">
|
<p className="text-red text-xs mt-0.5 mx-0 mb-2 max-w-xs">
|
||||||
{props.errorText}
|
{props.errorText}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(InputField);
|
export default React.memo(InputField);
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { faAngleDown,faCheck, faPlus } from "@fortawesome/free-solid-svg-icons";
|
import {
|
||||||
|
faAngleDown,
|
||||||
|
faCheck,
|
||||||
|
faPlus,
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { Listbox, Transition } from "@headlessui/react";
|
import { Listbox, Transition } from "@headlessui/react";
|
||||||
|
|
||||||
@@ -11,98 +15,97 @@ import { Listbox, Transition } from "@headlessui/react";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export default function ListBox({
|
export default function ListBox({
|
||||||
selected,
|
selected,
|
||||||
onChange,
|
onChange,
|
||||||
data,
|
data,
|
||||||
text,
|
text,
|
||||||
buttonAction,
|
buttonAction,
|
||||||
width,
|
width,
|
||||||
workspaceMapping = [],
|
workspaceMapping = [],
|
||||||
}) {
|
}) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Listbox value={selected} onChange={onChange}>
|
<Listbox value={selected} onChange={onChange}>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Listbox.Button
|
<Listbox.Button
|
||||||
className={`text-gray-400 relative ${
|
className={`text-gray-400 relative ${
|
||||||
width == "full" ? "w-full" : "w-52"
|
width == "full" ? "w-full" : "w-52"
|
||||||
} cursor-default rounded-md bg-white/[0.07] hover:bg-white/[0.11] duration-200 py-2.5 pl-3 pr-10 text-left shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm`}
|
} cursor-default rounded-md bg-white/[0.07] hover:bg-white/[0.11] duration-200 py-2.5 pl-3 pr-10 text-left shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm`}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
{text}
|
{text}
|
||||||
<span className="ml-1 cursor-pointer block truncate font-semibold text-gray-300">
|
<span className="ml-1 cursor-pointer block truncate font-semibold text-gray-300">
|
||||||
{" "}
|
{" "}
|
||||||
{selected}
|
{selected}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{data && (
|
{data && (
|
||||||
<div className="cursor-pointer pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
<div className="cursor-pointer pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||||
<FontAwesomeIcon icon={faAngleDown} className="text-md mr-1.5"/>
|
<FontAwesomeIcon icon={faAngleDown} className="text-md mr-1.5" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Listbox.Button>
|
</Listbox.Button>
|
||||||
{data && (
|
{data && (
|
||||||
<Transition
|
<Transition
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
leave="transition ease-in duration-100"
|
leave="transition ease-in duration-100"
|
||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<Listbox.Options className="border border-mineshaft-700 z-50 p-2 absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-bunker text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
<Listbox.Options className="border border-mineshaft-700 z-50 p-2 absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-bunker text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
||||||
{data.map((person, personIdx) => (
|
{data.map((person, personIdx) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={personIdx}
|
key={personIdx}
|
||||||
className={({ active, selected }) =>
|
className={({ active, selected }) =>
|
||||||
`my-0.5 relative cursor-default select-none py-2 pl-10 pr-4 rounded-md ${
|
`my-0.5 relative cursor-default select-none py-2 pl-10 pr-4 rounded-md ${
|
||||||
selected
|
selected ? "bg-white/10 text-gray-400 font-bold" : ""
|
||||||
? "bg-white/10 text-gray-400 font-bold"
|
} ${
|
||||||
: ""
|
active & !selected
|
||||||
} ${
|
? "bg-white/5 text-mineshaft-200 cursor-pointer"
|
||||||
active & !selected
|
: "text-gray-400"
|
||||||
? "bg-white/5 text-mineshaft-200 cursor-pointer"
|
} `
|
||||||
: "text-gray-400"
|
}
|
||||||
} `
|
value={person}
|
||||||
}
|
>
|
||||||
value={person}
|
{({ selected }) => (
|
||||||
>
|
<>
|
||||||
{({ selected }) => (
|
<span
|
||||||
<>
|
className={`block truncate text-primary${
|
||||||
<span
|
selected ? "font-medium" : "font-normal"
|
||||||
className={`block truncate text-primary${
|
}`}
|
||||||
selected
|
>
|
||||||
? "font-medium"
|
{person}
|
||||||
: "font-normal"
|
</span>
|
||||||
}`}
|
{selected ? (
|
||||||
>
|
<span className="text-primary rounded-lg absolute inset-y-0 left-0 flex items-center pl-3">
|
||||||
{person}
|
<FontAwesomeIcon
|
||||||
</span>
|
icon={faCheck}
|
||||||
{selected ? (
|
className="text-md ml-1"
|
||||||
<span className="text-primary rounded-lg absolute inset-y-0 left-0 flex items-center pl-3">
|
/>
|
||||||
<FontAwesomeIcon icon={faCheck} className="text-md ml-1"/>
|
</span>
|
||||||
</span>
|
) : null}
|
||||||
) : null}
|
</>
|
||||||
</>
|
)}
|
||||||
)}
|
</Listbox.Option>
|
||||||
</Listbox.Option>
|
))}
|
||||||
))}
|
{buttonAction && (
|
||||||
{buttonAction && (
|
<button
|
||||||
<button
|
onClick={buttonAction}
|
||||||
onClick={buttonAction}
|
className="cursor-pointer w-full"
|
||||||
className="cursor-pointer w-full"
|
>
|
||||||
>
|
<div className="my-0.5 relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-lime-300 duration-200 hover:text-black hover:font-semibold mt-2">
|
||||||
<div className="my-0.5 relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-lime-300 duration-200 hover:text-black hover:font-semibold mt-2">
|
<span className="rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4">
|
||||||
<span className="rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4">
|
<FontAwesomeIcon icon={faPlus} className="text-lg" />
|
||||||
<FontAwesomeIcon icon={faPlus} className="text-lg"/>
|
</span>
|
||||||
</span>
|
Add Project
|
||||||
Add Project
|
</div>
|
||||||
</div>
|
</button>
|
||||||
</button>
|
)}
|
||||||
)}
|
</Listbox.Options>
|
||||||
</Listbox.Options>
|
</Transition>
|
||||||
</Transition>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
</Listbox>
|
||||||
</Listbox>
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,105 +7,105 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
var classNames = require("classnames");
|
var classNames = require("classnames");
|
||||||
|
|
||||||
export default function Button({
|
export default function Button({
|
||||||
text,
|
text,
|
||||||
onButtonPressed,
|
onButtonPressed,
|
||||||
link,
|
link,
|
||||||
loading,
|
loading,
|
||||||
color,
|
color,
|
||||||
size,
|
size,
|
||||||
icon,
|
icon,
|
||||||
active = true,
|
active = true,
|
||||||
iconDisabled,
|
iconDisabled,
|
||||||
textDisabled
|
textDisabled,
|
||||||
}) {
|
}) {
|
||||||
let styleButton = classNames(
|
let styleButton = classNames(
|
||||||
"group m-auto md:m-0 inline-block rounded-md duration-200",
|
"group m-auto md:m-0 inline-block rounded-md duration-200",
|
||||||
|
|
||||||
// Setting background colors and hover modes
|
// Setting background colors and hover modes
|
||||||
color == "mineshaft" && active && "bg-mineshaft-700 hover:bg-primary",
|
color == "mineshaft" && active && "bg-mineshaft-700 hover:bg-primary",
|
||||||
color == "mineshaft" && !active && "bg-mineshaft",
|
color == "mineshaft" && !active && "bg-mineshaft",
|
||||||
(color == "primary" || !color) && active && "bg-primary hover:opacity-80",
|
(color == "primary" || !color) && active && "bg-primary hover:opacity-80",
|
||||||
(color == "primary" || !color) && !active && "bg-primary",
|
(color == "primary" || !color) && !active && "bg-primary",
|
||||||
color == "red" && "bg-red",
|
color == "red" && "bg-red",
|
||||||
|
|
||||||
// Changing the opacity when active vs when not
|
// Changing the opacity when active vs when not
|
||||||
active ? "opacity-100 cursor-pointer" : "opacity-40",
|
active ? "opacity-100 cursor-pointer" : "opacity-40",
|
||||||
|
|
||||||
// Setting the button sizes
|
// Setting the button sizes
|
||||||
size == "md" && "h-10 w-full px-2 md:px-4",
|
size == "md" && "h-10 w-full px-2 md:px-4",
|
||||||
size == "lg" && "h-12 w-full px-2 md:px-8",
|
size == "lg" && "h-12 w-full px-2 md:px-8",
|
||||||
!size && "md:py-1 px-3 md:px-8",
|
!size && "md:py-1 px-3 md:px-8",
|
||||||
size == "icon-md" && "h-10 w-10 flex items-center justify-center",
|
size == "icon-md" && "h-10 w-10 flex items-center justify-center",
|
||||||
size == "icon-sm" && "h-9 w-9 flex items-center justify-center",
|
size == "icon-sm" && "h-9 w-9 flex items-center justify-center"
|
||||||
);
|
);
|
||||||
|
|
||||||
let styleMainDiv = classNames(
|
let styleMainDiv = classNames(
|
||||||
"relative font-medium flex items-center",
|
"relative font-medium flex items-center",
|
||||||
|
|
||||||
// Setting the text color for the text and icon
|
// Setting the text color for the text and icon
|
||||||
color == "mineshaft" && "text-gray-400",
|
color == "mineshaft" && "text-gray-400",
|
||||||
color != "mineshaft" && color != "red" && "text-black",
|
color != "mineshaft" && color != "red" && "text-black",
|
||||||
color == "red" && "text-gray-200",
|
color == "red" && "text-gray-200",
|
||||||
active && color != "red" ? "group-hover:text-black" : "",
|
active && color != "red" ? "group-hover:text-black" : "",
|
||||||
|
|
||||||
size == "icon" && "flex items-center justify-center",
|
size == "icon" && "flex items-center justify-center"
|
||||||
);
|
);
|
||||||
|
|
||||||
let textStyle = classNames(
|
let textStyle = classNames(
|
||||||
"relative duration-200",
|
"relative duration-200",
|
||||||
|
|
||||||
// Show the loading sign if the loading indicator is on
|
// Show the loading sign if the loading indicator is on
|
||||||
loading == true ? "opacity-0" : "opacity-100",
|
loading == true ? "opacity-0" : "opacity-100",
|
||||||
size == "md" && "text-sm",
|
size == "md" && "text-sm",
|
||||||
size == "lg" && "text-lg"
|
size == "lg" && "text-lg"
|
||||||
);
|
);
|
||||||
|
|
||||||
const button = (
|
const button = (
|
||||||
<button
|
<button
|
||||||
disabled={!active}
|
disabled={!active}
|
||||||
onClick={onButtonPressed}
|
onClick={onButtonPressed}
|
||||||
className={styleButton}
|
className={styleButton}
|
||||||
>
|
>
|
||||||
<div className={styleMainDiv}>
|
<div className={styleMainDiv}>
|
||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
loading == true ? "opacity-100" : "opacity-0"
|
loading == true ? "opacity-100" : "opacity-0"
|
||||||
} absolute flex items-center px-2 duration-200`}
|
} absolute flex items-center px-2 duration-200`}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
src="/images/loading/loadingblack.gif"
|
src="/images/loading/loadingblack.gif"
|
||||||
height={25}
|
height={25}
|
||||||
width={42}
|
width={42}
|
||||||
alt="loading animation"
|
alt="loading animation"
|
||||||
className={`rounded-xl`}
|
className={`rounded-xl`}
|
||||||
></Image>
|
></Image>
|
||||||
</div>
|
</div>
|
||||||
{icon &&
|
{icon && (
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={icon}
|
icon={icon}
|
||||||
className={`flex my-auto font-extrabold ${size == "icon-sm" ? "text-sm" : "text-md"} ${(text || textDisabled) && "mr-2"}`}
|
className={`flex my-auto font-extrabold ${
|
||||||
/>
|
size == "icon-sm" ? "text-sm" : "text-md"
|
||||||
}
|
} ${(text || textDisabled) && "mr-2"}`}
|
||||||
{iconDisabled &&
|
/>
|
||||||
<FontAwesomeIcon
|
)}
|
||||||
icon={iconDisabled}
|
{iconDisabled && (
|
||||||
className={`flex my-auto font-extrabold ${size == "icon-sm" ? "text-sm" : "text-md"} ${(text || textDisabled) && "mr-2"}`}
|
<FontAwesomeIcon
|
||||||
/>
|
icon={iconDisabled}
|
||||||
}
|
className={`flex my-auto font-extrabold ${
|
||||||
{(text || textDisabled) &&
|
size == "icon-sm" ? "text-sm" : "text-md"
|
||||||
<p
|
} ${(text || textDisabled) && "mr-2"}`}
|
||||||
className={textStyle}
|
/>
|
||||||
>
|
)}
|
||||||
{active ? text : textDisabled}
|
{(text || textDisabled) && (
|
||||||
</p>
|
<p className={textStyle}>{active ? text : textDisabled}</p>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (link) {
|
if (link) {
|
||||||
return <Link href={link}>{button}</Link>;
|
return <Link href={link}>{button}</Link>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,91 +7,92 @@ import Button from "../buttons/Button";
|
|||||||
import InputField from "../InputField";
|
import InputField from "../InputField";
|
||||||
|
|
||||||
const AddIncidentContactDialog = ({
|
const AddIncidentContactDialog = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
closeModal,
|
closeModal,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
incidentContacts,
|
incidentContacts,
|
||||||
setIncidentContacts,
|
setIncidentContacts,
|
||||||
}) => {
|
}) => {
|
||||||
let [incidentContactEmail, setIncidentContactEmail] = useState("");
|
let [incidentContactEmail, setIncidentContactEmail] = useState("");
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
setIncidentContacts(
|
setIncidentContacts(
|
||||||
incidentContacts?.length > 0
|
incidentContacts?.length > 0
|
||||||
? incidentContacts.concat([incidentContactEmail])
|
? incidentContacts.concat([incidentContactEmail])
|
||||||
: [incidentContactEmail]
|
: [incidentContactEmail]
|
||||||
);
|
);
|
||||||
addIncidentContact(localStorage.getItem("orgData.id"), incidentContactEmail);
|
addIncidentContact(
|
||||||
closeModal();
|
localStorage.getItem("orgData.id"),
|
||||||
};
|
incidentContactEmail
|
||||||
return (
|
);
|
||||||
<div>
|
closeModal();
|
||||||
<Transition appear show={isOpen} as={Fragment}>
|
};
|
||||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
return (
|
||||||
<Transition.Child
|
<div>
|
||||||
as={Fragment}
|
<Transition appear show={isOpen} as={Fragment}>
|
||||||
enter="ease-out duration-300"
|
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
||||||
enterFrom="opacity-0"
|
<Transition.Child
|
||||||
enterTo="opacity-100"
|
as={Fragment}
|
||||||
leave="ease-in duration-200"
|
enter="ease-out duration-300"
|
||||||
leaveFrom="opacity-100"
|
enterFrom="opacity-0"
|
||||||
leaveTo="opacity-0"
|
enterTo="opacity-100"
|
||||||
>
|
leave="ease-in duration-200"
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-70" />
|
leaveFrom="opacity-100"
|
||||||
</Transition.Child>
|
leaveTo="opacity-0"
|
||||||
|
>
|
||||||
|
<div className="fixed inset-0 bg-black bg-opacity-70" />
|
||||||
|
</Transition.Child>
|
||||||
|
|
||||||
<div className="fixed inset-0 overflow-y-auto">
|
<div className="fixed inset-0 overflow-y-auto">
|
||||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
enterFrom="opacity-0 scale-95"
|
enterFrom="opacity-0 scale-95"
|
||||||
enterTo="opacity-100 scale-100"
|
enterTo="opacity-100 scale-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100 scale-100"
|
leaveFrom="opacity-100 scale-100"
|
||||||
leaveTo="opacity-0 scale-95"
|
leaveTo="opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
||||||
<Dialog.Title
|
<Dialog.Title
|
||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-medium leading-6 text-gray-400"
|
className="text-lg font-medium leading-6 text-gray-400"
|
||||||
>
|
>
|
||||||
Add an Incident Contact
|
Add an Incident Contact
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-2 mb-2">
|
<div className="mt-2 mb-2">
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
This contact will be notified in the
|
This contact will be notified in the unlikely event of a
|
||||||
unlikely event of a severe incident.
|
severe incident.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="max-h-28">
|
<div className="max-h-28">
|
||||||
<InputField
|
<InputField
|
||||||
label="Email"
|
label="Email"
|
||||||
onChangeHandler={
|
onChangeHandler={setIncidentContactEmail}
|
||||||
setIncidentContactEmail
|
type="varName"
|
||||||
}
|
value={incidentContactEmail}
|
||||||
type="varName"
|
placeholder=""
|
||||||
value={incidentContactEmail}
|
isRequired
|
||||||
placeholder=""
|
/>
|
||||||
isRequired
|
</div>
|
||||||
/>
|
<div className="mt-6 max-w-max">
|
||||||
</div>
|
<Button
|
||||||
<div className="mt-6 max-w-max">
|
onButtonPressed={submit}
|
||||||
<Button
|
color="mineshaft"
|
||||||
onButtonPressed={submit}
|
text="Add Incident Contact"
|
||||||
color="mineshaft"
|
size="md"
|
||||||
text="Add Incident Contact"
|
/>
|
||||||
size="md"
|
</div>
|
||||||
/>
|
</Dialog.Panel>
|
||||||
</div>
|
</Transition.Child>
|
||||||
</Dialog.Panel>
|
</div>
|
||||||
</Transition.Child>
|
</div>
|
||||||
</div>
|
</Dialog>
|
||||||
</div>
|
</Transition>
|
||||||
</Dialog>
|
</div>
|
||||||
</Transition>
|
);
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AddIncidentContactDialog;
|
export default AddIncidentContactDialog;
|
||||||
|
|||||||
@@ -6,146 +6,133 @@ import Button from "../buttons/Button";
|
|||||||
import ListBox from "../Listbox";
|
import ListBox from "../Listbox";
|
||||||
|
|
||||||
const AddProjectMemberDialog = ({
|
const AddProjectMemberDialog = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
closeModal,
|
closeModal,
|
||||||
submitModal,
|
submitModal,
|
||||||
data,
|
data,
|
||||||
email,
|
email,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
setEmail,
|
setEmail,
|
||||||
}) => {
|
}) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="z-50">
|
<div className="z-50">
|
||||||
<Transition appear show={isOpen} as={Fragment}>
|
<Transition appear show={isOpen} as={Fragment}>
|
||||||
<Dialog as="div" className="relative" onClose={closeModal}>
|
<Dialog as="div" className="relative" onClose={closeModal}>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
enterFrom="opacity-0"
|
enterFrom="opacity-0"
|
||||||
enterTo="opacity-100"
|
enterTo="opacity-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-70" />
|
<div className="fixed inset-0 bg-black bg-opacity-70" />
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
|
|
||||||
<div className="fixed inset-0 overflow-y-auto">
|
<div className="fixed inset-0 overflow-y-auto">
|
||||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
enterFrom="opacity-0 scale-95"
|
enterFrom="opacity-0 scale-95"
|
||||||
enterTo="opacity-100 scale-100"
|
enterTo="opacity-100 scale-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100 scale-100"
|
leaveFrom="opacity-100 scale-100"
|
||||||
leaveTo="opacity-0 scale-95"
|
leaveTo="opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
||||||
{data?.length > 0 ? (
|
{data?.length > 0 ? (
|
||||||
<Dialog.Title
|
<Dialog.Title
|
||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||||
>
|
>
|
||||||
Add a member to your project
|
Add a member to your project
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
) : (
|
) : (
|
||||||
<Dialog.Title
|
<Dialog.Title
|
||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||||
>
|
>
|
||||||
All the users in your organization
|
All the users in your organization are already invited.
|
||||||
are already invited.
|
</Dialog.Title>
|
||||||
</Dialog.Title>
|
)}
|
||||||
)}
|
<div className="mt-2 mb-4">
|
||||||
<div className="mt-2 mb-4">
|
{data?.length > 0 ? (
|
||||||
{data?.length > 0 ? (
|
<div className="flex flex-col">
|
||||||
<div className="flex flex-col">
|
<p className="text-sm text-gray-500">
|
||||||
<p className="text-sm text-gray-500">
|
The user will receive an email with the instructions.
|
||||||
The user will receive an email
|
</p>
|
||||||
with the instructions.
|
<div className="">
|
||||||
</p>
|
<button
|
||||||
<div className="">
|
type="button"
|
||||||
<button
|
className="inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||||
type="button"
|
onClick={() =>
|
||||||
className="inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
router.push("/settings/org/" + router.query.id)
|
||||||
onClick={() =>
|
}
|
||||||
router.push(
|
>
|
||||||
"/settings/org/" +
|
If you are looking to add users to your org,
|
||||||
router.query.id
|
</button>
|
||||||
)
|
<button
|
||||||
}
|
type="button"
|
||||||
>
|
className="ml-1 inline-flex justify-center rounded-md py-1 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||||
If you are looking to add users to your org,
|
onClick={() =>
|
||||||
</button>
|
router.push(
|
||||||
<button
|
"/settings/org/" + router.query.id + "?invite"
|
||||||
type="button"
|
)
|
||||||
className="ml-1 inline-flex justify-center rounded-md py-1 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
}
|
||||||
onClick={() =>
|
>
|
||||||
router.push(
|
click here.
|
||||||
"/settings/org/" +
|
</button>
|
||||||
router.query.id +
|
</div>
|
||||||
"?invite"
|
</div>
|
||||||
)
|
) : (
|
||||||
}
|
<p className="text-sm text-gray-500">
|
||||||
>
|
Add more users to the organization first.
|
||||||
click here.
|
</p>
|
||||||
</button>
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="max-h-28">
|
||||||
) : (
|
{data?.length > 0 && (
|
||||||
<p className="text-sm text-gray-500">
|
<ListBox
|
||||||
Add more users to the
|
selected={email ? email : data[0]}
|
||||||
organization first.
|
onChange={setEmail}
|
||||||
</p>
|
data={data}
|
||||||
)}
|
width="full"
|
||||||
</div>
|
/>
|
||||||
<div className="max-h-28">
|
)}
|
||||||
{data?.length > 0 && (
|
</div>
|
||||||
<ListBox
|
<div className="max-w-max">
|
||||||
selected={
|
{data?.length > 0 ? (
|
||||||
email ? email : data[0]
|
<div className="mt-6 flex flex-col justify-start w-max">
|
||||||
}
|
<Button
|
||||||
onChange={setEmail}
|
onButtonPressed={submitModal}
|
||||||
data={data}
|
color="mineshaft"
|
||||||
width="full"
|
text="Add Member"
|
||||||
/>
|
size="md"
|
||||||
)}
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="max-w-max">
|
) : (
|
||||||
{data?.length > 0 ? (
|
<Button
|
||||||
<div className="mt-6 flex flex-col justify-start w-max">
|
onButtonPressed={() =>
|
||||||
<Button
|
router.push("/settings/org/" + router.query.id)
|
||||||
onButtonPressed={submitModal}
|
}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
text="Add Member"
|
text="Add Users to Organization"
|
||||||
size="md"
|
size="md"
|
||||||
/>
|
/>
|
||||||
</div>
|
)}
|
||||||
) : (
|
</div>
|
||||||
<Button
|
</Dialog.Panel>
|
||||||
onButtonPressed={() =>
|
</Transition.Child>
|
||||||
router.push(
|
</div>
|
||||||
"/settings/org/" +
|
</div>
|
||||||
router.query.id
|
</Dialog>
|
||||||
)
|
</Transition>
|
||||||
}
|
</div>
|
||||||
color="mineshaft"
|
);
|
||||||
text="Add Users to Organization"
|
|
||||||
size="md"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Dialog.Panel>
|
|
||||||
</Transition.Child>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AddProjectMemberDialog;
|
export default AddProjectMemberDialog;
|
||||||
|
|||||||
@@ -8,276 +8,254 @@ import nacl from "tweetnacl";
|
|||||||
import addServiceToken from "~/pages/api/serviceToken/addServiceToken";
|
import addServiceToken from "~/pages/api/serviceToken/addServiceToken";
|
||||||
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
|
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
|
||||||
|
|
||||||
import { decryptAssymmetric, encryptAssymmetric } from "../../utilities/cryptography/crypto";
|
import {
|
||||||
|
decryptAssymmetric,
|
||||||
|
encryptAssymmetric,
|
||||||
|
} from "../../utilities/cryptography/crypto";
|
||||||
import Button from "../buttons/Button";
|
import Button from "../buttons/Button";
|
||||||
import InputField from "../InputField";
|
import InputField from "../InputField";
|
||||||
import ListBox from "../Listbox";
|
import ListBox from "../Listbox";
|
||||||
|
|
||||||
const envMapping = {
|
const envMapping = {
|
||||||
Development: "dev",
|
Development: "dev",
|
||||||
Staging: "staging",
|
Staging: "staging",
|
||||||
Production: "prod",
|
Production: "prod",
|
||||||
Testing: "test",
|
Testing: "test",
|
||||||
};
|
};
|
||||||
|
|
||||||
const expiryMapping = {
|
const expiryMapping = {
|
||||||
"1 day": 86400,
|
"1 day": 86400,
|
||||||
"7 days": 604800,
|
"7 days": 604800,
|
||||||
"1 month": 2592000,
|
"1 month": 2592000,
|
||||||
};
|
};
|
||||||
|
|
||||||
const AddServiceTokenDialog = ({
|
const AddServiceTokenDialog = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
closeModal,
|
closeModal,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
workspaceName,
|
workspaceName,
|
||||||
}) => {
|
}) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [serviceToken, setServiceToken] = useState("");
|
const [serviceToken, setServiceToken] = useState("");
|
||||||
const [serviceTokenName, setServiceTokenName] = useState("");
|
const [serviceTokenName, setServiceTokenName] = useState("");
|
||||||
const [serviceTokenEnv, setServiceTokenEnv] = useState("Development");
|
const [serviceTokenEnv, setServiceTokenEnv] = useState("Development");
|
||||||
const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState("1 day");
|
const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState("1 day");
|
||||||
const [serviceTokenCopied, setServiceTokenCopied] = useState(false);
|
const [serviceTokenCopied, setServiceTokenCopied] = useState(false);
|
||||||
|
|
||||||
const generateServiceToken = async () => {
|
const generateServiceToken = async () => {
|
||||||
const latestFileKey = await getLatestFileKey(workspaceId);
|
const latestFileKey = await getLatestFileKey(workspaceId);
|
||||||
|
|
||||||
const key = decryptAssymmetric({
|
const key = decryptAssymmetric({
|
||||||
ciphertext: latestFileKey.latestKey.encryptedKey,
|
ciphertext: latestFileKey.latestKey.encryptedKey,
|
||||||
nonce: latestFileKey.latestKey.nonce,
|
nonce: latestFileKey.latestKey.nonce,
|
||||||
publicKey: latestFileKey.latestKey.sender.publicKey,
|
publicKey: latestFileKey.latestKey.sender.publicKey,
|
||||||
privateKey: localStorage.getItem("PRIVATE_KEY"),
|
privateKey: localStorage.getItem("PRIVATE_KEY"),
|
||||||
});
|
});
|
||||||
|
|
||||||
// generate new public/private key pair
|
// generate new public/private key pair
|
||||||
const pair = nacl.box.keyPair();
|
const pair = nacl.box.keyPair();
|
||||||
const publicKey = nacl.util.encodeBase64(pair.publicKey);
|
const publicKey = nacl.util.encodeBase64(pair.publicKey);
|
||||||
const privateKey = nacl.util.encodeBase64(pair.secretKey);
|
const privateKey = nacl.util.encodeBase64(pair.secretKey);
|
||||||
|
|
||||||
// encrypt workspace key under newly-generated public key
|
// encrypt workspace key under newly-generated public key
|
||||||
const { ciphertext: encryptedKey, nonce } = encryptAssymmetric({
|
const { ciphertext: encryptedKey, nonce } = encryptAssymmetric({
|
||||||
plaintext: key,
|
plaintext: key,
|
||||||
publicKey,
|
publicKey,
|
||||||
privateKey,
|
privateKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
let newServiceToken = await addServiceToken({
|
let newServiceToken = await addServiceToken({
|
||||||
name: serviceTokenName,
|
name: serviceTokenName,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment: envMapping[serviceTokenEnv],
|
environment: envMapping[serviceTokenEnv],
|
||||||
expiresIn: expiryMapping[serviceTokenExpiresIn],
|
expiresIn: expiryMapping[serviceTokenExpiresIn],
|
||||||
publicKey,
|
publicKey,
|
||||||
encryptedKey,
|
encryptedKey,
|
||||||
nonce,
|
nonce,
|
||||||
});
|
});
|
||||||
|
|
||||||
const serviceToken = newServiceToken + "," + privateKey;
|
const serviceToken = newServiceToken + "," + privateKey;
|
||||||
setServiceToken(serviceToken);
|
setServiceToken(serviceToken);
|
||||||
};
|
};
|
||||||
|
|
||||||
function copyToClipboard() {
|
function copyToClipboard() {
|
||||||
// Get the text field
|
// Get the text field
|
||||||
var copyText = document.getElementById("serviceToken");
|
var copyText = document.getElementById("serviceToken");
|
||||||
|
|
||||||
// Select the text field
|
// Select the text field
|
||||||
copyText.select();
|
copyText.select();
|
||||||
copyText.setSelectionRange(0, 99999); // For mobile devices
|
copyText.setSelectionRange(0, 99999); // For mobile devices
|
||||||
|
|
||||||
// Copy the text inside the text field
|
// Copy the text inside the text field
|
||||||
navigator.clipboard.writeText(copyText.value);
|
navigator.clipboard.writeText(copyText.value);
|
||||||
|
|
||||||
setServiceTokenCopied(true);
|
setServiceTokenCopied(true);
|
||||||
setTimeout(() => setServiceTokenCopied(false), 2000);
|
setTimeout(() => setServiceTokenCopied(false), 2000);
|
||||||
// Alert the copied text
|
// Alert the copied text
|
||||||
// alert("Copied the text: " + copyText.value);
|
// alert("Copied the text: " + copyText.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeAddServiceTokenModal = () => {
|
const closeAddServiceTokenModal = () => {
|
||||||
closeModal();
|
closeModal();
|
||||||
setServiceTokenName("");
|
setServiceTokenName("");
|
||||||
setServiceToken("");
|
setServiceToken("");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="z-50">
|
<div className="z-50">
|
||||||
<Transition appear show={isOpen} as={Fragment}>
|
<Transition appear show={isOpen} as={Fragment}>
|
||||||
<Dialog as="div" className="relative" onClose={closeModal}>
|
<Dialog as="div" className="relative" onClose={closeModal}>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
enterFrom="opacity-0"
|
enterFrom="opacity-0"
|
||||||
enterTo="opacity-100"
|
enterTo="opacity-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<div className="fixed inset-0 bg-bunker-700 bg-opacity-80" />
|
<div className="fixed inset-0 bg-bunker-700 bg-opacity-80" />
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
|
|
||||||
<div className="fixed inset-0 overflow-y-auto">
|
<div className="fixed inset-0 overflow-y-auto">
|
||||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
enterFrom="opacity-0 scale-95"
|
enterFrom="opacity-0 scale-95"
|
||||||
enterTo="opacity-100 scale-100"
|
enterTo="opacity-100 scale-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100 scale-100"
|
leaveFrom="opacity-100 scale-100"
|
||||||
leaveTo="opacity-0 scale-95"
|
leaveTo="opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
{serviceToken == "" ? (
|
{serviceToken == "" ? (
|
||||||
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
||||||
<Dialog.Title
|
<Dialog.Title
|
||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||||
>
|
>
|
||||||
Add a service token for{" "}
|
Add a service token for {workspaceName}
|
||||||
{workspaceName}
|
</Dialog.Title>
|
||||||
</Dialog.Title>
|
<div className="mt-2 mb-4">
|
||||||
<div className="mt-2 mb-4">
|
<div className="flex flex-col">
|
||||||
<div className="flex flex-col">
|
<p className="text-sm text-gray-500">
|
||||||
<p className="text-sm text-gray-500">
|
Specify the name, environment, and expiry period. When
|
||||||
Specify the name,
|
a token is generated, you will only be able to see it
|
||||||
environment, and expiry
|
once before it disappears. Make sure to save it
|
||||||
period. When a token is
|
somewhere.
|
||||||
generated, you will only be
|
</p>
|
||||||
able to see it once before
|
</div>
|
||||||
it disappears. Make sure to
|
</div>
|
||||||
save it somewhere.
|
<div className="max-h-28 mb-2">
|
||||||
</p>
|
<InputField
|
||||||
</div>
|
label="Service Token Name"
|
||||||
</div>
|
onChangeHandler={setServiceTokenName}
|
||||||
<div className="max-h-28 mb-2">
|
type="varName"
|
||||||
<InputField
|
value={serviceTokenName}
|
||||||
label="Service Token Name"
|
placeholder=""
|
||||||
onChangeHandler={
|
isRequired
|
||||||
setServiceTokenName
|
/>
|
||||||
}
|
</div>
|
||||||
type="varName"
|
<div className="max-h-28 mb-2">
|
||||||
value={serviceTokenName}
|
<ListBox
|
||||||
placeholder=""
|
selected={serviceTokenEnv}
|
||||||
isRequired
|
onChange={setServiceTokenEnv}
|
||||||
/>
|
data={[
|
||||||
</div>
|
"Development",
|
||||||
<div className="max-h-28 mb-2">
|
"Staging",
|
||||||
<ListBox
|
"Production",
|
||||||
selected={serviceTokenEnv}
|
"Testing",
|
||||||
onChange={setServiceTokenEnv}
|
]}
|
||||||
data={[
|
width="full"
|
||||||
"Development",
|
text="Environment: "
|
||||||
"Staging",
|
/>
|
||||||
"Production",
|
</div>
|
||||||
"Testing",
|
<div className="max-h-28">
|
||||||
]}
|
<ListBox
|
||||||
width="full"
|
selected={serviceTokenExpiresIn}
|
||||||
text="Environment: "
|
onChange={setServiceTokenExpiresIn}
|
||||||
/>
|
data={["1 day", "7 days", "1 month"]}
|
||||||
</div>
|
width="full"
|
||||||
<div className="max-h-28">
|
text="Expires in: "
|
||||||
<ListBox
|
/>
|
||||||
selected={serviceTokenExpiresIn}
|
</div>
|
||||||
onChange={
|
<div className="max-w-max">
|
||||||
setServiceTokenExpiresIn
|
<div className="mt-6 flex flex-col justify-start w-max">
|
||||||
}
|
<Button
|
||||||
data={[
|
onButtonPressed={() => generateServiceToken()}
|
||||||
"1 day",
|
color="mineshaft"
|
||||||
"7 days",
|
text="Add Service Token"
|
||||||
"1 month",
|
textDisabled="Add Service Token"
|
||||||
]}
|
size="md"
|
||||||
width="full"
|
active={serviceTokenName == "" ? false : true}
|
||||||
text="Expires in: "
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="max-w-max">
|
</Dialog.Panel>
|
||||||
<div className="mt-6 flex flex-col justify-start w-max">
|
) : (
|
||||||
<Button
|
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
||||||
onButtonPressed={() =>
|
<Dialog.Title
|
||||||
generateServiceToken()
|
as="h3"
|
||||||
}
|
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||||
color="mineshaft"
|
>
|
||||||
text="Add Service Token"
|
Copy your service token
|
||||||
textDisabled="Add Service Token"
|
</Dialog.Title>
|
||||||
size="md"
|
<div className="mt-2 mb-4">
|
||||||
active={
|
<div className="flex flex-col">
|
||||||
serviceTokenName == ""
|
<p className="text-sm text-gray-500">
|
||||||
? false
|
Once you close this popup, you will never see your
|
||||||
: true
|
service token again
|
||||||
}
|
</p>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="w-full">
|
||||||
</Dialog.Panel>
|
<div className="flex justify-end items-center bg-white/[0.07] text-base mt-2 mr-2 rounded-md text-gray-400 w-full h-36">
|
||||||
) : (
|
<input
|
||||||
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
type="text"
|
||||||
<Dialog.Title
|
value={serviceToken}
|
||||||
as="h3"
|
id="serviceToken"
|
||||||
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
className="invisible bg-white/0 text-gray-400 py-2 w-full px-2 min-w-full outline-none"
|
||||||
>
|
></input>
|
||||||
Copy your service token
|
<div className="bg-white/0 max-w-md text-sm text-gray-400 py-2 w-full pl-14 pr-2 break-words outline-none">
|
||||||
</Dialog.Title>
|
{serviceToken}
|
||||||
<div className="mt-2 mb-4">
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="group font-normal h-full relative inline-block text-gray-400 underline hover:text-primary duration-200">
|
||||||
<p className="text-sm text-gray-500">
|
<button
|
||||||
Once you close this popup,
|
onClick={copyToClipboard}
|
||||||
you will never see your
|
className="h-full pl-3.5 pr-4 border-l border-white/20 py-2 hover:bg-white/[0.12] duration-200"
|
||||||
service token again
|
>
|
||||||
</p>
|
{serviceTokenCopied ? (
|
||||||
</div>
|
<FontAwesomeIcon
|
||||||
</div>
|
icon={faCheck}
|
||||||
<div className="w-full">
|
className="pr-0.5"
|
||||||
<div className="flex justify-end items-center bg-white/[0.07] text-base mt-2 mr-2 rounded-md text-gray-400 w-full h-36">
|
/>
|
||||||
<input
|
) : (
|
||||||
type="text"
|
<FontAwesomeIcon icon={faCopy} />
|
||||||
value={serviceToken}
|
)}
|
||||||
id="serviceToken"
|
</button>
|
||||||
className="invisible bg-white/0 text-gray-400 py-2 w-full px-2 min-w-full outline-none"
|
<span className="absolute hidden group-hover:flex group-hover:animate-popup duration-300 w-28 -left-8 -top-20 translate-y-full px-3 py-2 bg-chicago-900 rounded-md text-center text-gray-400 text-sm">
|
||||||
></input>
|
Click to Copy
|
||||||
<div className="bg-white/0 max-w-md text-sm text-gray-400 py-2 w-full pl-14 pr-2 break-words outline-none">
|
</span>
|
||||||
{serviceToken}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="group font-normal h-full relative inline-block text-gray-400 underline hover:text-primary duration-200">
|
</div>
|
||||||
<button
|
<div className="mt-6 flex flex-col justify-start w-max">
|
||||||
onClick={
|
<Button
|
||||||
copyToClipboard
|
onButtonPressed={() => closeAddServiceTokenModal()}
|
||||||
}
|
color="mineshaft"
|
||||||
className="h-full pl-3.5 pr-4 border-l border-white/20 py-2 hover:bg-white/[0.12] duration-200"
|
text="Close"
|
||||||
>
|
size="md"
|
||||||
{serviceTokenCopied ? (
|
/>
|
||||||
<FontAwesomeIcon
|
</div>
|
||||||
icon={faCheck}
|
</Dialog.Panel>
|
||||||
className="pr-0.5"
|
)}
|
||||||
/>
|
</Transition.Child>
|
||||||
) : (
|
</div>
|
||||||
<FontAwesomeIcon
|
</div>
|
||||||
icon={faCopy}
|
</Dialog>
|
||||||
/>
|
</Transition>
|
||||||
)}
|
</div>
|
||||||
</button>
|
);
|
||||||
<span className="absolute hidden group-hover:flex group-hover:animate-popup duration-300 w-28 -left-8 -top-20 translate-y-full px-3 py-2 bg-chicago-900 rounded-md text-center text-gray-400 text-sm">
|
|
||||||
Click to Copy
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-6 flex flex-col justify-start w-max">
|
|
||||||
<Button
|
|
||||||
onButtonPressed={() =>
|
|
||||||
closeAddServiceTokenModal()
|
|
||||||
}
|
|
||||||
color="mineshaft"
|
|
||||||
text="Close"
|
|
||||||
size="md"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Dialog.Panel>
|
|
||||||
)}
|
|
||||||
</Transition.Child>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AddServiceTokenDialog;
|
export default AddServiceTokenDialog;
|
||||||
|
|||||||
@@ -7,107 +7,103 @@ import Button from "../buttons/Button";
|
|||||||
import InputField from "../InputField";
|
import InputField from "../InputField";
|
||||||
|
|
||||||
const AddUserDialog = ({
|
const AddUserDialog = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
closeModal,
|
closeModal,
|
||||||
submitModal,
|
submitModal,
|
||||||
email,
|
email,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
setEmail,
|
setEmail,
|
||||||
currentPlan,
|
currentPlan,
|
||||||
orgName
|
orgName,
|
||||||
}) => {
|
}) => {
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
submitModal(email);
|
submitModal(email);
|
||||||
};
|
};
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="z-50">
|
<div className="z-50">
|
||||||
<Transition appear show={isOpen} as={Fragment}>
|
<Transition appear show={isOpen} as={Fragment}>
|
||||||
<Dialog as="div" className="relative" onClose={closeModal}>
|
<Dialog as="div" className="relative" onClose={closeModal}>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
enterFrom="opacity-0"
|
enterFrom="opacity-0"
|
||||||
enterTo="opacity-100"
|
enterTo="opacity-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-70" />
|
<div className="fixed inset-0 bg-black bg-opacity-70" />
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
|
|
||||||
<div className="fixed inset-0 overflow-y-auto">
|
<div className="fixed inset-0 overflow-y-auto">
|
||||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
enterFrom="opacity-0 scale-95"
|
enterFrom="opacity-0 scale-95"
|
||||||
enterTo="opacity-100 scale-100"
|
enterTo="opacity-100 scale-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100 scale-100"
|
leaveFrom="opacity-100 scale-100"
|
||||||
leaveTo="opacity-0 scale-95"
|
leaveTo="opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="w-full max-w-lg transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
<Dialog.Panel className="w-full max-w-lg transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
||||||
<Dialog.Title
|
<Dialog.Title
|
||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||||
>
|
>
|
||||||
Invite others to {orgName}
|
Invite others to {orgName}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-2 mb-4">
|
<div className="mt-2 mb-4">
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
An invite is specific to an email address
|
An invite is specific to an email address and expires
|
||||||
and expires after 1 day. For security reasons,
|
after 1 day. For security reasons, you will need to
|
||||||
you will need to separately add members to projects.
|
separately add members to projects.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="max-h-28">
|
<div className="max-h-28">
|
||||||
<InputField
|
<InputField
|
||||||
label="Email"
|
label="Email"
|
||||||
onChangeHandler={setEmail}
|
onChangeHandler={setEmail}
|
||||||
type="varName"
|
type="varName"
|
||||||
value={email}
|
value={email}
|
||||||
placeholder=""
|
placeholder=""
|
||||||
isRequired
|
isRequired
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{currentPlan == STRIPE_PRODUCT_STARTER && <div className="flex flex-row">
|
{currentPlan == STRIPE_PRODUCT_STARTER && (
|
||||||
<button
|
<div className="flex flex-row">
|
||||||
type="button"
|
<button
|
||||||
className="inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
type="button"
|
||||||
onClick={() =>
|
className="inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||||
router.push(
|
onClick={() =>
|
||||||
"/settings/billing/" +
|
router.push("/settings/billing/" + router.query.id)
|
||||||
router.query.id
|
}
|
||||||
)
|
>
|
||||||
}
|
You can add up to 5 members on a Free tier.
|
||||||
>
|
</button>
|
||||||
You can add up to 5 members on a Free tier.
|
<button
|
||||||
</button>
|
type="button"
|
||||||
<button
|
className="ml-1 inline-flex justify-center rounded-md py-1 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||||
type="button"
|
onClick={() =>
|
||||||
className="ml-1 inline-flex justify-center rounded-md py-1 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
router.push("/settings/billing/" + router.query.id)
|
||||||
onClick={() =>
|
}
|
||||||
router.push(
|
>
|
||||||
"/settings/billing/" +
|
Upgrade now.
|
||||||
router.query.id
|
</button>
|
||||||
)
|
</div>
|
||||||
}
|
)}
|
||||||
>
|
<div className="mt-4 max-w-max">
|
||||||
Upgrade now.
|
<Button
|
||||||
</button>
|
onButtonPressed={submit}
|
||||||
</div>}
|
color="mineshaft"
|
||||||
<div className="mt-4 max-w-max">
|
text="Invite"
|
||||||
<Button
|
size="md"
|
||||||
onButtonPressed={submit}
|
/>
|
||||||
color="mineshaft"
|
</div>
|
||||||
text="Invite"
|
</Dialog.Panel>
|
||||||
size="md"
|
{/* <Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Dialog.Panel>
|
|
||||||
{/* <Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
|
||||||
<Dialog.Title
|
<Dialog.Title
|
||||||
as="h3"
|
as="h3"
|
||||||
className="text-xl font-medium leading-6 text-gray-300 z-50"
|
className="text-xl font-medium leading-6 text-gray-300 z-50"
|
||||||
@@ -139,13 +135,13 @@ const AddUserDialog = ({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</Dialog.Panel> */}
|
</Dialog.Panel> */}
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AddUserDialog;
|
export default AddUserDialog;
|
||||||
|
|||||||
@@ -12,94 +12,93 @@ import { Checkbox } from "../table/Checkbox";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const AddWorkspaceDialog = ({
|
const AddWorkspaceDialog = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
closeModal,
|
closeModal,
|
||||||
submitModal,
|
submitModal,
|
||||||
workspaceName,
|
workspaceName,
|
||||||
setWorkspaceName,
|
setWorkspaceName,
|
||||||
error,
|
error,
|
||||||
loading,
|
loading,
|
||||||
}) => {
|
}) => {
|
||||||
const [addAllUsers, setAddAllUsers] = useState(true);
|
const [addAllUsers, setAddAllUsers] = useState(true);
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
submitModal(workspaceName, addAllUsers);
|
submitModal(workspaceName, addAllUsers);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Transition appear show={isOpen} as={Fragment}>
|
<Transition appear show={isOpen} as={Fragment}>
|
||||||
<Dialog as="div" className="relative z-20" onClose={closeModal}>
|
<Dialog as="div" className="relative z-20" onClose={closeModal}>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
enterFrom="opacity-0"
|
enterFrom="opacity-0"
|
||||||
enterTo="opacity-100"
|
enterTo="opacity-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-70" />
|
<div className="fixed inset-0 bg-black bg-opacity-70" />
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
|
|
||||||
<div className="fixed inset-0 overflow-y-auto z-50">
|
<div className="fixed inset-0 overflow-y-auto z-50">
|
||||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
enterFrom="opacity-0 scale-95"
|
enterFrom="opacity-0 scale-95"
|
||||||
enterTo="opacity-100 scale-100"
|
enterTo="opacity-100 scale-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100 scale-100"
|
leaveFrom="opacity-100 scale-100"
|
||||||
leaveTo="opacity-0 scale-95"
|
leaveTo="opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
||||||
<Dialog.Title
|
<Dialog.Title
|
||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-medium leading-6 text-gray-400"
|
className="text-lg font-medium leading-6 text-gray-400"
|
||||||
>
|
>
|
||||||
Create a new project
|
Create a new project
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
This project will contain your
|
This project will contain your environmental variables.
|
||||||
environmental variables.
|
</p>
|
||||||
</p>
|
</div>
|
||||||
</div>
|
<div className="max-h-28 mt-4">
|
||||||
<div className="max-h-28 mt-4">
|
<InputField
|
||||||
<InputField
|
label="Project Name"
|
||||||
label="Project Name"
|
onChangeHandler={setWorkspaceName}
|
||||||
onChangeHandler={setWorkspaceName}
|
type="varName"
|
||||||
type="varName"
|
value={workspaceName}
|
||||||
value={workspaceName}
|
placeholder=""
|
||||||
placeholder=""
|
isRequired
|
||||||
isRequired
|
error={error.length > 0}
|
||||||
error={error.length > 0}
|
errorText={error}
|
||||||
errorText={error}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<div className="mt-4 ml-1">
|
||||||
<div className="mt-4 ml-1">
|
<Checkbox
|
||||||
<Checkbox
|
addAllUsers={addAllUsers}
|
||||||
addAllUsers={addAllUsers}
|
setAddAllUsers={setAddAllUsers}
|
||||||
setAddAllUsers={setAddAllUsers}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<div className="mt-4 max-w-min">
|
||||||
<div className="mt-4 max-w-min">
|
<Button
|
||||||
<Button
|
onButtonPressed={submit}
|
||||||
onButtonPressed={submit}
|
loading={loading}
|
||||||
loading={loading}
|
color="mineshaft"
|
||||||
color="mineshaft"
|
text="Create"
|
||||||
text="Create"
|
size="md"
|
||||||
size="md"
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</Dialog.Panel>
|
||||||
</Dialog.Panel>
|
</Transition.Child>
|
||||||
</Transition.Child>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Dialog>
|
||||||
</Dialog>
|
</Transition>
|
||||||
</Transition>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AddWorkspaceDialog;
|
export default AddWorkspaceDialog;
|
||||||
|
|||||||
@@ -1,78 +1,83 @@
|
|||||||
import { Fragment, useState } from 'react'
|
import { Fragment, useState } from "react";
|
||||||
import { Dialog, Transition } from '@headlessui/react'
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
|
|
||||||
import InputField from '../InputField';
|
import InputField from "../InputField";
|
||||||
|
|
||||||
// #TODO: USE THIS. Currently it's not. Kinda complicated to set up because of state.
|
// #TODO: USE THIS. Currently it's not. Kinda complicated to set up because of state.
|
||||||
|
|
||||||
|
const DeleteUserDialog = ({
|
||||||
|
isOpen,
|
||||||
|
closeModal,
|
||||||
|
submitModal,
|
||||||
|
userIdToBeDeleted,
|
||||||
|
}) => {
|
||||||
|
const submit = () => {
|
||||||
|
submitModal(userIdToBeDeleted);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Transition appear show={isOpen} as={Fragment}>
|
||||||
|
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
||||||
|
<Transition.Child
|
||||||
|
as={Fragment}
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enterFrom="opacity-0"
|
||||||
|
enterTo="opacity-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0"
|
||||||
|
>
|
||||||
|
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||||
|
</Transition.Child>
|
||||||
|
|
||||||
const DeleteUserDialog = ({isOpen, closeModal, submitModal, userIdToBeDeleted}) => {
|
<div className="fixed inset-0 overflow-y-auto">
|
||||||
const submit = () => {
|
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||||
submitModal(userIdToBeDeleted);
|
<Transition.Child
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Transition appear show={isOpen} as={Fragment}>
|
|
||||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
|
||||||
<Transition.Child
|
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
enterFrom="opacity-0"
|
enterFrom="opacity-0 scale-95"
|
||||||
enterTo="opacity-100"
|
enterTo="opacity-100 scale-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100 scale-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-grey border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
||||||
</Transition.Child>
|
<Dialog.Title
|
||||||
|
as="h3"
|
||||||
<div className="fixed inset-0 overflow-y-auto">
|
className="text-lg font-medium leading-6 text-gray-400"
|
||||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
>
|
||||||
<Transition.Child
|
Are you sure you want to remove this user from the
|
||||||
as={Fragment}
|
workspace?
|
||||||
enter="ease-out duration-300"
|
</Dialog.Title>
|
||||||
enterFrom="opacity-0 scale-95"
|
<div className="mt-2">
|
||||||
enterTo="opacity-100 scale-100"
|
<p className="text-sm text-gray-500">
|
||||||
leave="ease-in duration-200"
|
This action is irrevertible.
|
||||||
leaveFrom="opacity-100 scale-100"
|
</p>
|
||||||
leaveTo="opacity-0 scale-95"
|
</div>
|
||||||
>
|
<div className="mt-6">
|
||||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-grey border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
<button
|
||||||
<Dialog.Title
|
type="button"
|
||||||
as="h3"
|
className="inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 hover:bg-alizarin hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||||
className="text-lg font-medium leading-6 text-gray-400"
|
onClick={submit}
|
||||||
>
|
>
|
||||||
Are you sure you want to remove this user from the workspace?
|
Delete
|
||||||
</Dialog.Title>
|
</button>
|
||||||
<div className="mt-2">
|
<button
|
||||||
<p className="text-sm text-gray-500">
|
type="button"
|
||||||
This action is irrevertible.
|
className="ml-2 inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 hover:border-white hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||||
</p>
|
onClick={submit}
|
||||||
</div>
|
>
|
||||||
<div className="mt-6">
|
Cancel
|
||||||
<button
|
</button>
|
||||||
type="button"
|
</div>
|
||||||
className="inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 hover:bg-alizarin hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
</Dialog.Panel>
|
||||||
onClick={submit}
|
</Transition.Child>
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="ml-2 inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 hover:border-white hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
|
||||||
onClick={submit}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</Dialog.Panel>
|
|
||||||
</Transition.Child>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</div>
|
||||||
</Transition>
|
</Dialog>
|
||||||
</div>
|
</Transition>
|
||||||
);
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DeleteUserDialog;
|
export default DeleteUserDialog;
|
||||||
+268
-302
@@ -3,11 +3,11 @@ import { useEffect, useState } from "react";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import {
|
import {
|
||||||
faGear,
|
faGear,
|
||||||
faHouse,
|
faHouse,
|
||||||
faLink,
|
faLink,
|
||||||
faMobile,
|
faMobile,
|
||||||
faUser,
|
faUser,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
@@ -20,319 +20,285 @@ import createWorkspace from "~/pages/api/workspace/createWorkspace";
|
|||||||
import getWorkspaces from "~/pages/api/workspace/getWorkspaces";
|
import getWorkspaces from "~/pages/api/workspace/getWorkspaces";
|
||||||
|
|
||||||
import NavBarDashboard from "../navigation/NavBarDashboard";
|
import NavBarDashboard from "../navigation/NavBarDashboard";
|
||||||
import { decryptAssymmetric, encryptAssymmetric } from "../utilities/cryptography/crypto";
|
import {
|
||||||
|
decryptAssymmetric,
|
||||||
|
encryptAssymmetric,
|
||||||
|
} from "../utilities/cryptography/crypto";
|
||||||
import Button from "./buttons/Button";
|
import Button from "./buttons/Button";
|
||||||
import AddWorkspaceDialog from "./dialog/AddWorkspaceDialog";
|
import AddWorkspaceDialog from "./dialog/AddWorkspaceDialog";
|
||||||
import Listbox from "./Listbox";
|
import Listbox from "./Listbox";
|
||||||
|
|
||||||
export default function Layout({ children }) {
|
export default function Layout({ children }) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [workspaceList, setWorkspaceList] = useState([]);
|
const [workspaceList, setWorkspaceList] = useState([]);
|
||||||
const [workspaceMapping, setWorkspaceMapping] = useState([{ 1: 2 }]);
|
const [workspaceMapping, setWorkspaceMapping] = useState([{ 1: 2 }]);
|
||||||
const [workspaceSelected, setWorkspaceSelected] = useState("∞");
|
const [workspaceSelected, setWorkspaceSelected] = useState("∞");
|
||||||
let [newWorkspaceName, setNewWorkspaceName] = useState("");
|
let [newWorkspaceName, setNewWorkspaceName] = useState("");
|
||||||
let [isOpen, setIsOpen] = useState(false);
|
let [isOpen, setIsOpen] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
|
|
||||||
function closeModal() {
|
function closeModal() {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: what to do about the fact that 2ids can have the same name
|
// TODO: what to do about the fact that 2ids can have the same name
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When a user creates a new workspace, redirect them to the page of the new workspace.
|
* When a user creates a new workspace, redirect them to the page of the new workspace.
|
||||||
* @param {*} workspaceName
|
* @param {*} workspaceName
|
||||||
*/
|
*/
|
||||||
async function submitModal(workspaceName, addAllUsers) {
|
async function submitModal(workspaceName, addAllUsers) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setTimeout(() => setLoading(false), 1500);
|
setTimeout(() => setLoading(false), 1500);
|
||||||
const workspaces = await getWorkspaces();
|
const workspaces = await getWorkspaces();
|
||||||
const currentWorkspaces = workspaces.map((workspace) => workspace.name);
|
const currentWorkspaces = workspaces.map((workspace) => workspace.name);
|
||||||
if (!currentWorkspaces.includes(workspaceName)) {
|
if (!currentWorkspaces.includes(workspaceName)) {
|
||||||
const newWorkspace = await createWorkspace(
|
const newWorkspace = await createWorkspace(
|
||||||
workspaceName,
|
workspaceName,
|
||||||
localStorage.getItem("orgData.id")
|
localStorage.getItem("orgData.id")
|
||||||
);
|
);
|
||||||
let newWorkspaceId;
|
let newWorkspaceId;
|
||||||
try {
|
try {
|
||||||
newWorkspaceId = newWorkspace._id;
|
newWorkspaceId = newWorkspace._id;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
if (addAllUsers) {
|
if (addAllUsers) {
|
||||||
let orgUsers = await getOrganizationUsers({
|
let orgUsers = await getOrganizationUsers({
|
||||||
orgId: localStorage.getItem("orgData.id"),
|
orgId: localStorage.getItem("orgData.id"),
|
||||||
});
|
});
|
||||||
orgUsers.map(async (user) => {
|
orgUsers.map(async (user) => {
|
||||||
if (user.status == "accepted") {
|
if (user.status == "accepted") {
|
||||||
let result = await addUserToWorkspace(
|
let result = await addUserToWorkspace(
|
||||||
user.user.email,
|
user.user.email,
|
||||||
newWorkspaceId
|
newWorkspaceId
|
||||||
);
|
);
|
||||||
if (result?.invitee && result?.latestKey) {
|
if (result?.invitee && result?.latestKey) {
|
||||||
const PRIVATE_KEY =
|
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
||||||
localStorage.getItem("PRIVATE_KEY");
|
|
||||||
|
|
||||||
// assymmetrically decrypt symmetric key with local private key
|
// assymmetrically decrypt symmetric key with local private key
|
||||||
const key = decryptAssymmetric({
|
const key = decryptAssymmetric({
|
||||||
ciphertext: result.latestKey.encryptedKey,
|
ciphertext: result.latestKey.encryptedKey,
|
||||||
nonce: result.latestKey.nonce,
|
nonce: result.latestKey.nonce,
|
||||||
publicKey: result.latestKey.sender.publicKey,
|
publicKey: result.latestKey.sender.publicKey,
|
||||||
privateKey: PRIVATE_KEY,
|
privateKey: PRIVATE_KEY,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { ciphertext, nonce } = encryptAssymmetric({
|
const { ciphertext, nonce } = encryptAssymmetric({
|
||||||
plaintext: key,
|
plaintext: key,
|
||||||
publicKey: result.invitee.publicKey,
|
publicKey: result.invitee.publicKey,
|
||||||
privateKey: PRIVATE_KEY,
|
privateKey: PRIVATE_KEY,
|
||||||
});
|
});
|
||||||
|
|
||||||
uploadKeys(
|
uploadKeys(newWorkspaceId, result.invitee._id, ciphertext, nonce);
|
||||||
newWorkspaceId,
|
}
|
||||||
result.invitee._id,
|
}
|
||||||
ciphertext,
|
});
|
||||||
nonce
|
}
|
||||||
);
|
router.push("/dashboard/" + newWorkspaceId + "?Development");
|
||||||
}
|
setIsOpen(false);
|
||||||
}
|
setNewWorkspaceName("");
|
||||||
});
|
} else {
|
||||||
}
|
setError("A project with this name already exists.");
|
||||||
router.push("/dashboard/" + newWorkspaceId + "?Development");
|
setLoading(false);
|
||||||
setIsOpen(false);
|
}
|
||||||
setNewWorkspaceName("");
|
}
|
||||||
} else {
|
|
||||||
setError("A project with this name already exists.");
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function openModal() {
|
function openModal() {
|
||||||
setIsOpen(true);
|
setIsOpen(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{
|
{
|
||||||
href:
|
href:
|
||||||
"/dashboard/" +
|
"/dashboard/" + workspaceMapping[workspaceSelected] + "?Development",
|
||||||
workspaceMapping[workspaceSelected] +
|
title: "Secrets",
|
||||||
"?Development",
|
emoji: <FontAwesomeIcon icon={faHouse} />,
|
||||||
title: "Secrets",
|
},
|
||||||
emoji: <FontAwesomeIcon icon={faHouse} />,
|
{
|
||||||
},
|
href: "/users/" + workspaceMapping[workspaceSelected],
|
||||||
{
|
title: "Members",
|
||||||
href: "/users/" + workspaceMapping[workspaceSelected],
|
emoji: <FontAwesomeIcon icon={faUser} />,
|
||||||
title: "Members",
|
},
|
||||||
emoji: <FontAwesomeIcon icon={faUser} />,
|
{
|
||||||
},
|
href: "/integrations/" + workspaceMapping[workspaceSelected],
|
||||||
{
|
title: "Integrations",
|
||||||
href: "/integrations/" + workspaceMapping[workspaceSelected],
|
emoji: <FontAwesomeIcon icon={faLink} />,
|
||||||
title: "Integrations",
|
},
|
||||||
emoji: <FontAwesomeIcon icon={faLink} />,
|
{
|
||||||
},
|
href: "/settings/project/" + workspaceMapping[workspaceSelected],
|
||||||
{
|
title: "Project Settings",
|
||||||
href: "/settings/project/" + workspaceMapping[workspaceSelected],
|
emoji: <FontAwesomeIcon icon={faGear} />,
|
||||||
title: "Project Settings",
|
},
|
||||||
emoji: <FontAwesomeIcon icon={faGear} />,
|
];
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
useEffect(async () => {
|
useEffect(async () => {
|
||||||
// Put a user in a workspace if they're not in one yet
|
// Put a user in a workspace if they're not in one yet
|
||||||
if (
|
if (
|
||||||
localStorage.getItem("orgData.id") == null ||
|
localStorage.getItem("orgData.id") == null ||
|
||||||
localStorage.getItem("orgData.id") == ""
|
localStorage.getItem("orgData.id") == ""
|
||||||
) {
|
) {
|
||||||
const userOrgs = await getOrganizations();
|
const userOrgs = await getOrganizations();
|
||||||
localStorage.setItem("orgData.id", userOrgs[0]._id);
|
localStorage.setItem("orgData.id", userOrgs[0]._id);
|
||||||
}
|
}
|
||||||
|
|
||||||
let orgUserProjects = await getOrganizationUserProjects({
|
let orgUserProjects = await getOrganizationUserProjects({
|
||||||
orgId: localStorage.getItem("orgData.id"),
|
orgId: localStorage.getItem("orgData.id"),
|
||||||
});
|
});
|
||||||
let userWorkspaces = orgUserProjects;
|
let userWorkspaces = orgUserProjects;
|
||||||
if (
|
if (
|
||||||
userWorkspaces.length == 0 &&
|
userWorkspaces.length == 0 &&
|
||||||
router.asPath != "/noprojects" &&
|
router.asPath != "/noprojects" &&
|
||||||
!router.asPath.includes("settings")
|
!router.asPath.includes("settings")
|
||||||
) {
|
) {
|
||||||
router.push("/noprojects");
|
router.push("/noprojects");
|
||||||
} else if (router.asPath != "/noprojects") {
|
} else if (router.asPath != "/noprojects") {
|
||||||
const intendedWorkspaceId = router.asPath
|
const intendedWorkspaceId = router.asPath
|
||||||
.split("/")
|
.split("/")
|
||||||
[router.asPath.split("/").length - 1].split("?")[0];
|
[router.asPath.split("/").length - 1].split("?")[0];
|
||||||
|
|
||||||
// If a user is not a member of a workspace they are trying to access, just push them to one of theirs
|
// If a user is not a member of a workspace they are trying to access, just push them to one of theirs
|
||||||
if (
|
if (
|
||||||
intendedWorkspaceId != "heroku" &&
|
intendedWorkspaceId != "heroku" &&
|
||||||
!userWorkspaces
|
!userWorkspaces
|
||||||
.map((workspace) => workspace._id)
|
.map((workspace) => workspace._id)
|
||||||
.includes(intendedWorkspaceId)
|
.includes(intendedWorkspaceId)
|
||||||
) {
|
) {
|
||||||
router.push(
|
router.push("/dashboard/" + userWorkspaces[0]._id + "?Development");
|
||||||
"/dashboard/" + userWorkspaces[0]._id + "?Development"
|
} else {
|
||||||
);
|
setWorkspaceList(userWorkspaces.map((workspace) => workspace.name));
|
||||||
} else {
|
setWorkspaceMapping(
|
||||||
setWorkspaceList(
|
Object.fromEntries(
|
||||||
userWorkspaces.map((workspace) => workspace.name)
|
userWorkspaces.map((workspace) => [workspace.name, workspace._id])
|
||||||
);
|
)
|
||||||
setWorkspaceMapping(
|
);
|
||||||
Object.fromEntries(
|
setWorkspaceSelected(
|
||||||
userWorkspaces.map((workspace) => [
|
Object.fromEntries(
|
||||||
workspace.name,
|
userWorkspaces.map((workspace) => [workspace._id, workspace.name])
|
||||||
workspace._id,
|
)[
|
||||||
])
|
router.asPath
|
||||||
)
|
.split("/")
|
||||||
);
|
[router.asPath.split("/").length - 1].split("?")[0]
|
||||||
setWorkspaceSelected(
|
]
|
||||||
Object.fromEntries(
|
);
|
||||||
userWorkspaces.map((workspace) => [
|
}
|
||||||
workspace._id,
|
}
|
||||||
workspace.name,
|
}, []);
|
||||||
])
|
|
||||||
)[
|
|
||||||
router.asPath
|
|
||||||
.split("/")
|
|
||||||
[router.asPath.split("/").length - 1].split("?")[0]
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
workspaceMapping[workspaceSelected] &&
|
workspaceMapping[workspaceSelected] &&
|
||||||
workspaceMapping[workspaceSelected] !==
|
workspaceMapping[workspaceSelected] !==
|
||||||
router.asPath
|
router.asPath
|
||||||
.split("/")
|
.split("/")
|
||||||
[router.asPath.split("/").length - 1].split("?")[0]
|
[router.asPath.split("/").length - 1].split("?")[0]
|
||||||
) {
|
) {
|
||||||
router.push(
|
router.push(
|
||||||
"/dashboard/" +
|
"/dashboard/" + workspaceMapping[workspaceSelected] + "?Development"
|
||||||
workspaceMapping[workspaceSelected] +
|
);
|
||||||
"?Development"
|
localStorage.setItem(
|
||||||
);
|
"projectData.id",
|
||||||
localStorage.setItem(
|
workspaceMapping[workspaceSelected]
|
||||||
"projectData.id",
|
);
|
||||||
workspaceMapping[workspaceSelected]
|
}
|
||||||
);
|
} catch (error) {
|
||||||
}
|
console.log(error);
|
||||||
} catch (error) {
|
}
|
||||||
console.log(error);
|
}, [workspaceSelected]);
|
||||||
}
|
|
||||||
}, [workspaceSelected]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="fixed w-full hidden md:block flex flex-col h-screen">
|
<div className="fixed w-full hidden md:block flex flex-col h-screen">
|
||||||
<NavBarDashboard />
|
<NavBarDashboard />
|
||||||
<div className="flex flex-col md:flex-row flex-1">
|
<div className="flex flex-col md:flex-row flex-1">
|
||||||
<aside className="bg-bunker-600 border-r border-mineshaft-500 w-full md:w-60 h-screen">
|
<aside className="bg-bunker-600 border-r border-mineshaft-500 w-full md:w-60 h-screen">
|
||||||
<nav>
|
<nav>
|
||||||
<div className="py-6"></div>
|
<div className="py-6"></div>
|
||||||
<div className="flex justify-center w-full mt-7 mb-8 bg-bunker-600 w-full h-full flex flex-col items-center px-4">
|
<div className="flex justify-center w-full mt-7 mb-8 bg-bunker-600 w-full h-full flex flex-col items-center px-4">
|
||||||
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
|
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
|
||||||
PROJECT
|
PROJECT
|
||||||
</div>
|
</div>
|
||||||
{workspaceList.length > 0 ? (
|
{workspaceList.length > 0 ? (
|
||||||
<Listbox
|
<Listbox
|
||||||
selected={workspaceSelected}
|
selected={workspaceSelected}
|
||||||
onChange={setWorkspaceSelected}
|
onChange={setWorkspaceSelected}
|
||||||
data={workspaceList}
|
data={workspaceList}
|
||||||
buttonAction={openModal}
|
buttonAction={openModal}
|
||||||
text=""
|
text=""
|
||||||
workspaceMapping={workspaceMapping}
|
workspaceMapping={workspaceMapping}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
text="Add Project"
|
text="Add Project"
|
||||||
onButtonPressed={openModal}
|
onButtonPressed={openModal}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
size="md"
|
size="md"
|
||||||
icon={faPlus}
|
icon={faPlus}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<ul>
|
<ul>
|
||||||
{workspaceList.length > 0 &&
|
{workspaceList.length > 0 &&
|
||||||
menuItems.map(({ href, title, emoji }) => (
|
menuItems.map(({ href, title, emoji }) => (
|
||||||
<li className="mt-1.5 mx-2" key={title}>
|
<li className="mt-1.5 mx-2" key={title}>
|
||||||
{router.asPath.split("/")[1] ===
|
{router.asPath.split("/")[1] === href.split("/")[1] &&
|
||||||
href.split("/")[1] &&
|
(["project", "billing", "org", "personal"].includes(
|
||||||
([
|
router.asPath.split("/")[2]
|
||||||
"project",
|
)
|
||||||
"billing",
|
? router.asPath.split("/")[2] === href.split("/")[2]
|
||||||
"org",
|
: true) ? (
|
||||||
"personal",
|
<div
|
||||||
].includes(
|
className={`flex p-2 text-white text-sm rounded cursor-pointer bg-mineshaft-50/10`}
|
||||||
router.asPath.split("/")[2]
|
>
|
||||||
)
|
<div className="bg-primary w-1 rounded-xl mr-1"></div>
|
||||||
? router.asPath.split(
|
<p className="ml-2 mr-4">{emoji}</p>
|
||||||
"/"
|
{title}
|
||||||
)[2] === href.split("/")[2]
|
</div>
|
||||||
: true) ? (
|
) : router.asPath == "/noprojects" ? (
|
||||||
<div
|
<div className={`flex p-2 text-white text-sm rounded`}>
|
||||||
className={`flex p-2 text-white text-sm rounded cursor-pointer bg-mineshaft-50/10`}
|
<p className="ml-2 mr-4">{emoji}</p>
|
||||||
>
|
{title}
|
||||||
<div className="bg-primary w-1 rounded-xl mr-1"></div>
|
</div>
|
||||||
<p className="ml-2 mr-4">
|
) : (
|
||||||
{emoji}
|
<Link href={href}>
|
||||||
</p>
|
<div
|
||||||
{title}
|
className={`flex p-2 text-white text-sm rounded cursor-pointer hover:bg-mineshaft-50/5`}
|
||||||
</div>
|
>
|
||||||
) : router.asPath ==
|
<p className="ml-2 mr-4">{emoji}</p>
|
||||||
"/noprojects" ? (
|
{title}
|
||||||
<div
|
</div>
|
||||||
className={`flex p-2 text-white text-sm rounded`}
|
</Link>
|
||||||
>
|
)}
|
||||||
<p className="ml-2 mr-4">
|
</li>
|
||||||
{emoji}
|
))}
|
||||||
</p>
|
</ul>
|
||||||
{title}
|
</nav>
|
||||||
</div>
|
</aside>
|
||||||
) : (
|
<AddWorkspaceDialog
|
||||||
<Link href={href}>
|
isOpen={isOpen}
|
||||||
<div
|
closeModal={closeModal}
|
||||||
className={`flex p-2 text-white text-sm rounded cursor-pointer hover:bg-mineshaft-50/5`}
|
submitModal={submitModal}
|
||||||
>
|
workspaceName={newWorkspaceName}
|
||||||
<p className="ml-2 mr-4">
|
setWorkspaceName={setNewWorkspaceName}
|
||||||
{emoji}
|
error={error}
|
||||||
</p>
|
loading={loading}
|
||||||
{title}
|
/>
|
||||||
</div>
|
<main className="flex-1 bg-bunker-800">{children}</main>
|
||||||
</Link>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</li>
|
<div className="block md:hidden bg-bunker-800 w-screen h-screen flex flex-col justify-center items-center">
|
||||||
))}
|
<FontAwesomeIcon
|
||||||
</ul>
|
icon={faMobile}
|
||||||
</nav>
|
className="text-gray-300 text-7xl mb-8"
|
||||||
</aside>
|
/>
|
||||||
<AddWorkspaceDialog
|
<p className="text-gray-200 px-6 text-center text-lg max-w-sm">
|
||||||
isOpen={isOpen}
|
{" "}
|
||||||
closeModal={closeModal}
|
To use Infisical, please log in through a device with larger
|
||||||
submitModal={submitModal}
|
dimensions.{" "}
|
||||||
workspaceName={newWorkspaceName}
|
</p>
|
||||||
setWorkspaceName={setNewWorkspaceName}
|
</div>
|
||||||
error={error}
|
</>
|
||||||
loading={loading}
|
);
|
||||||
/>
|
|
||||||
<main className="flex-1 bg-bunker-800">{children}</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="block md:hidden bg-bunker-800 w-screen h-screen flex flex-col justify-center items-center">
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={faMobile}
|
|
||||||
className="text-gray-300 text-7xl mb-8"
|
|
||||||
/>
|
|
||||||
<p className="text-gray-200 px-6 text-center text-lg max-w-sm">
|
|
||||||
{" "}
|
|
||||||
To use Infisical, please log in through a device with larger
|
|
||||||
dimensions.{" "}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,49 +3,49 @@ import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
|||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
export default function BottonRightPopup({
|
export default function BottonRightPopup({
|
||||||
buttonText,
|
buttonText,
|
||||||
buttonLink,
|
buttonLink,
|
||||||
titleText,
|
titleText,
|
||||||
emoji,
|
emoji,
|
||||||
textLine1,
|
textLine1,
|
||||||
textLine2,
|
textLine2,
|
||||||
setCheckDocsPopUpVisible,
|
setCheckDocsPopUpVisible,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class="z-50 drop-shadow-xl border-gray-600/50 border flex flex-col items-start bg-bunker max-w-xl text-gray-200 pt-3 pb-4 rounded-xl absolute bottom-0 right-0 mr-6 mb-6"
|
class="z-50 drop-shadow-xl border-gray-600/50 border flex flex-col items-start bg-bunker max-w-xl text-gray-200 pt-3 pb-4 rounded-xl absolute bottom-0 right-0 mr-6 mb-6"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center justify-between w-full border-b border-gray-600/70 pb-3 px-6">
|
<div className="flex flex-row items-center justify-between w-full border-b border-gray-600/70 pb-3 px-6">
|
||||||
<div className="font-bold text-xl mr-2 mt-0.5 flex flex-row">
|
<div className="font-bold text-xl mr-2 mt-0.5 flex flex-row">
|
||||||
<div>{titleText}</div>
|
<div>{titleText}</div>
|
||||||
<div class="ml-2.5">{emoji}</div>
|
<div class="ml-2.5">{emoji}</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
className="mt-1"
|
className="mt-1"
|
||||||
onClick={() => setCheckDocsPopUpVisible(false)}
|
onClick={() => setCheckDocsPopUpVisible(false)}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faXmark}
|
icon={faXmark}
|
||||||
className="text-gray-400 text-2xl hover:text-red duration-200 cursor-pointer"
|
className="text-gray-400 text-2xl hover:text-red duration-200 cursor-pointer"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="block sm:inline px-6 mt-4 mb-0.5 text-gray-300">
|
<div class="block sm:inline px-6 mt-4 mb-0.5 text-gray-300">
|
||||||
{textLine1}
|
{textLine1}
|
||||||
</div>
|
</div>
|
||||||
<div class="block sm:inline mb-4 px-6">{textLine2}</div>
|
<div class="block sm:inline mb-4 px-6">{textLine2}</div>
|
||||||
<div className="flex flex-row px-6 w-full">
|
<div className="flex flex-row px-6 w-full">
|
||||||
{/*eslint-disable-next-line react/jsx-no-target-blank */}
|
{/*eslint-disable-next-line react/jsx-no-target-blank */}
|
||||||
<a
|
<a
|
||||||
class="font-bold p-2 bg-white/10 rounded-md w-full hover:bg-primary duration-200 hover:text-black flex justify-center"
|
class="font-bold p-2 bg-white/10 rounded-md w-full hover:bg-primary duration-200 hover:text-black flex justify-center"
|
||||||
href={buttonLink}
|
href={buttonLink}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
>
|
>
|
||||||
{buttonText}
|
{buttonText}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export const Checkbox = ({ addAllUsers, setAddAllUsers }) => {
|
export const Checkbox = ({ addAllUsers, setAddAllUsers }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-row items-center">
|
<div className="flex flex-row items-center">
|
||||||
{addAllUsers == true ? (
|
{addAllUsers == true ? (
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="accent-primary h-4 w-4"
|
className="accent-primary h-4 w-4"
|
||||||
checked
|
checked
|
||||||
readOnly
|
readOnly
|
||||||
onClick={() => setAddAllUsers(!addAllUsers)}
|
onClick={() => setAddAllUsers(!addAllUsers)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className="h-4 w-4 bg-bunker border border-gray-600 rounded-sm"
|
className="h-4 w-4 bg-bunker border border-gray-600 rounded-sm"
|
||||||
onClick={() => setAddAllUsers(!addAllUsers)}
|
onClick={() => setAddAllUsers(!addAllUsers)}
|
||||||
></div>
|
></div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<label className="ml-2 text-gray-500 text-sm">
|
<label className="ml-2 text-gray-500 text-sm">
|
||||||
Add all members of my organization to this project.
|
Add all members of my organization to this project.
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import Button from "../buttons/Button";
|
|||||||
const roles = ["admin", "user"];
|
const roles = ["admin", "user"];
|
||||||
|
|
||||||
const reverseEnvMapping = {
|
const reverseEnvMapping = {
|
||||||
"dev": "Development",
|
dev: "Development",
|
||||||
"staging": "Staging",
|
staging: "Staging",
|
||||||
"prod": "Production",
|
prod: "Production",
|
||||||
"test": "Testing"
|
test: "Testing",
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the component that we utilize for the user table - in future, can reuse it for some other purposes too.
|
* This is the component that we utilize for the user table - in future, can reuse it for some other purposes too.
|
||||||
@@ -20,63 +20,66 @@ const reverseEnvMapping = {
|
|||||||
* @param {*} props
|
* @param {*} props
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const ServiceTokenTable = ({
|
const ServiceTokenTable = ({ data, workspaceName }) => {
|
||||||
data,
|
const router = useRouter();
|
||||||
workspaceName
|
|
||||||
}) => {
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="table-container w-full bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
|
<div className="table-container w-full bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
|
||||||
<div className="absolute rounded-t-md w-full h-12 bg-white/5"></div>
|
<div className="absolute rounded-t-md w-full h-12 bg-white/5"></div>
|
||||||
<table className="w-full my-1">
|
<table className="w-full my-1">
|
||||||
<thead className="text-bunker-300">
|
<thead className="text-bunker-300">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="text-left pl-6 pt-2.5 pb-2">Token name</th>
|
<th className="text-left pl-6 pt-2.5 pb-2">Token name</th>
|
||||||
<th className="text-left pl-6 pt-2.5 pb-2">Project</th>
|
<th className="text-left pl-6 pt-2.5 pb-2">Project</th>
|
||||||
<th className="text-left pl-6 pt-2.5 pb-2">Environment</th>
|
<th className="text-left pl-6 pt-2.5 pb-2">Environment</th>
|
||||||
<th className="text-left pl-6 pt-2.5 pb-2">Valid until</th>
|
<th className="text-left pl-6 pt-2.5 pb-2">Valid until</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{data?.length > 0
|
{data?.length > 0 ? (
|
||||||
? data
|
data.map((row, index) => {
|
||||||
.map((row, index) => {
|
return (
|
||||||
return (
|
<tr
|
||||||
<tr key={guidGenerator()} className="bg-bunker-800 hover:bg-bunker-800/5 duration-100">
|
key={guidGenerator()}
|
||||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
className="bg-bunker-800 hover:bg-bunker-800/5 duration-100"
|
||||||
{row.name}
|
>
|
||||||
</td>
|
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
{row.name}
|
||||||
{workspaceName}
|
</td>
|
||||||
</td>
|
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
{workspaceName}
|
||||||
{reverseEnvMapping[row.environment]}
|
</td>
|
||||||
</td>
|
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
{reverseEnvMapping[row.environment]}
|
||||||
{new Date(row.expiresAt).toUTCString()}
|
</td>
|
||||||
</td>
|
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||||
<td className="py-2 border-mineshaft-700 border-t">
|
{new Date(row.expiresAt).toUTCString()}
|
||||||
<div className="opacity-50 hover:opacity-100 duration-200 flex items-center">
|
</td>
|
||||||
<Button
|
<td className="py-2 border-mineshaft-700 border-t">
|
||||||
onButtonPressed={() => {}}
|
<div className="opacity-50 hover:opacity-100 duration-200 flex items-center">
|
||||||
color="red"
|
<Button
|
||||||
size="icon-sm"
|
onButtonPressed={() => {}}
|
||||||
icon={faX}
|
color="red"
|
||||||
/>
|
size="icon-sm"
|
||||||
</div>
|
icon={faX}
|
||||||
</td>
|
/>
|
||||||
</tr>
|
</div>
|
||||||
);
|
</td>
|
||||||
})
|
</tr>
|
||||||
: <tr>
|
);
|
||||||
<td colSpan="4" className="text-center pt-7 pb-4 text-bunker-400">No service tokens yet</td>
|
})
|
||||||
</tr>}
|
) : (
|
||||||
</tbody>
|
<tr>
|
||||||
</table>
|
<td colSpan="4" className="text-center pt-7 pb-4 text-bunker-400">
|
||||||
</div>
|
No service tokens yet
|
||||||
);
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ServiceTokenTable;
|
export default ServiceTokenTable;
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import Button from "../buttons/Button";
|
|||||||
import Listbox from "../Listbox";
|
import Listbox from "../Listbox";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
decryptAssymmetric,
|
decryptAssymmetric,
|
||||||
encryptAssymmetric,
|
encryptAssymmetric,
|
||||||
} = require("../../utilities/cryptography/crypto");
|
} = require("../../utilities/cryptography/crypto");
|
||||||
const nacl = require("tweetnacl");
|
const nacl = require("tweetnacl");
|
||||||
nacl.util = require("tweetnacl-util");
|
nacl.util = require("tweetnacl-util");
|
||||||
@@ -28,245 +28,214 @@ const roles = ["admin", "user"];
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const UserTable = ({
|
const UserTable = ({
|
||||||
userData,
|
userData,
|
||||||
changeData,
|
changeData,
|
||||||
myUser,
|
myUser,
|
||||||
filter,
|
filter,
|
||||||
resendInvite,
|
resendInvite,
|
||||||
isOrg,
|
isOrg,
|
||||||
onClick,
|
onClick,
|
||||||
deleteUser,
|
deleteUser,
|
||||||
setUserIdToBeDeleted,
|
setUserIdToBeDeleted,
|
||||||
}) => {
|
}) => {
|
||||||
const [roleSelected, setRoleSelected] = useState(
|
const [roleSelected, setRoleSelected] = useState(
|
||||||
Array(userData?.length).fill(userData.map((user) => user.role))
|
Array(userData?.length).fill(userData.map((user) => user.role))
|
||||||
);
|
);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [myRole, setMyRole] = useState("member");
|
const [myRole, setMyRole] = useState("member");
|
||||||
|
|
||||||
// Delete the row in the table (e.g. a user)
|
// Delete the row in the table (e.g. a user)
|
||||||
// #TODO: Add a pop-up that warns you that the user is going to be deleted.
|
// #TODO: Add a pop-up that warns you that the user is going to be deleted.
|
||||||
const handleDelete = (membershipId, index, e) => {
|
const handleDelete = (membershipId, index, e) => {
|
||||||
// setUserIdToBeDeleted(userId);
|
// setUserIdToBeDeleted(userId);
|
||||||
// onClick();
|
// onClick();
|
||||||
if (isOrg) {
|
if (isOrg) {
|
||||||
deleteUserFromOrganization(membershipId);
|
deleteUserFromOrganization(membershipId);
|
||||||
} else {
|
} else {
|
||||||
deleteUserFromWorkspace(membershipId);
|
deleteUserFromWorkspace(membershipId);
|
||||||
}
|
}
|
||||||
changeData(userData.filter((v, i) => i !== index));
|
changeData(userData.filter((v, i) => i !== index));
|
||||||
setRoleSelected([
|
setRoleSelected([
|
||||||
...roleSelected.slice(0, index),
|
...roleSelected.slice(0, index),
|
||||||
...roleSelected.slice(index + 1, userData?.length),
|
...roleSelected.slice(index + 1, userData?.length),
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the rold of a certain user
|
// Update the rold of a certain user
|
||||||
const handleRoleUpdate = (index, e) => {
|
const handleRoleUpdate = (index, e) => {
|
||||||
changeUserRoleInWorkspace(userData[index].membershipId, e);
|
changeUserRoleInWorkspace(userData[index].membershipId, e);
|
||||||
changeData([
|
changeData([
|
||||||
...userData.slice(0, index),
|
...userData.slice(0, index),
|
||||||
...[
|
...[
|
||||||
{
|
{
|
||||||
key: userData[index].key,
|
key: userData[index].key,
|
||||||
firstName: userData[index].firstName,
|
firstName: userData[index].firstName,
|
||||||
lastName: userData[index].lastName,
|
lastName: userData[index].lastName,
|
||||||
email: userData[index].email,
|
email: userData[index].email,
|
||||||
role: e,
|
role: e,
|
||||||
status: userData[index].status,
|
status: userData[index].status,
|
||||||
userId: userData[index].userId,
|
userId: userData[index].userId,
|
||||||
membershipId: userData[index].membershipId,
|
membershipId: userData[index].membershipId,
|
||||||
publicKey: userData[index].publicKey,
|
publicKey: userData[index].publicKey,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
...userData.slice(index + 1, userData?.length),
|
...userData.slice(index + 1, userData?.length),
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMyRole(userData.filter((user) => user.email == myUser)[0]?.role);
|
setMyRole(userData.filter((user) => user.email == myUser)[0]?.role);
|
||||||
}, [userData, myUser]);
|
}, [userData, myUser]);
|
||||||
|
|
||||||
const grantAccess = async (id, publicKey) => {
|
const grantAccess = async (id, publicKey) => {
|
||||||
let result = await getLatestFileKey(router.query.id);
|
let result = await getLatestFileKey(router.query.id);
|
||||||
|
|
||||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
||||||
|
|
||||||
// assymmetrically decrypt symmetric key with local private key
|
// assymmetrically decrypt symmetric key with local private key
|
||||||
const key = decryptAssymmetric({
|
const key = decryptAssymmetric({
|
||||||
ciphertext: result.latestKey.encryptedKey,
|
ciphertext: result.latestKey.encryptedKey,
|
||||||
nonce: result.latestKey.nonce,
|
nonce: result.latestKey.nonce,
|
||||||
publicKey: result.latestKey.sender.publicKey,
|
publicKey: result.latestKey.sender.publicKey,
|
||||||
privateKey: PRIVATE_KEY,
|
privateKey: PRIVATE_KEY,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { ciphertext, nonce } = encryptAssymmetric({
|
const { ciphertext, nonce } = encryptAssymmetric({
|
||||||
plaintext: key,
|
plaintext: key,
|
||||||
publicKey: publicKey,
|
publicKey: publicKey,
|
||||||
privateKey: PRIVATE_KEY,
|
privateKey: PRIVATE_KEY,
|
||||||
});
|
});
|
||||||
|
|
||||||
uploadKeys(router.query.id, id, ciphertext, nonce);
|
uploadKeys(router.query.id, id, ciphertext, nonce);
|
||||||
router.reload();
|
router.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteMembershipAndResendInvite = (email, membershipId) => {
|
const deleteMembershipAndResendInvite = (email, membershipId) => {
|
||||||
// deleteUserFromWorkspace(membershipId);
|
// deleteUserFromWorkspace(membershipId);
|
||||||
resendInvite(email);
|
resendInvite(email);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="table-container bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
|
<div className="table-container bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
|
||||||
<div className="absolute rounded-t-md w-full h-14 bg-white/5"></div>
|
<div className="absolute rounded-t-md w-full h-14 bg-white/5"></div>
|
||||||
<table className="w-full my-1">
|
<table className="w-full my-1">
|
||||||
<thead className="text-gray-400">
|
<thead className="text-gray-400">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="text-left pl-6 py-3.5">First Name</th>
|
<th className="text-left pl-6 py-3.5">First Name</th>
|
||||||
<th className="text-left pl-6 py-3.5">Last Name</th>
|
<th className="text-left pl-6 py-3.5">Last Name</th>
|
||||||
<th className="text-left pl-6 py-3.5">Email</th>
|
<th className="text-left pl-6 py-3.5">Email</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{userData?.filter(
|
{userData?.filter(
|
||||||
(user) =>
|
(user) =>
|
||||||
user.firstName?.toLowerCase().includes(filter) ||
|
user.firstName?.toLowerCase().includes(filter) ||
|
||||||
user.lastName?.toLowerCase().includes(filter) ||
|
user.lastName?.toLowerCase().includes(filter) ||
|
||||||
user.email?.toLowerCase().includes(filter)
|
user.email?.toLowerCase().includes(filter)
|
||||||
).length > 0 &&
|
).length > 0 &&
|
||||||
userData
|
userData
|
||||||
?.filter(
|
?.filter(
|
||||||
(user) =>
|
(user) =>
|
||||||
user.firstName
|
user.firstName?.toLowerCase().includes(filter) ||
|
||||||
?.toLowerCase()
|
user.lastName?.toLowerCase().includes(filter) ||
|
||||||
.includes(filter) ||
|
user.email?.toLowerCase().includes(filter)
|
||||||
user.lastName
|
)
|
||||||
?.toLowerCase()
|
.map((row, index) => {
|
||||||
.includes(filter) ||
|
return (
|
||||||
user.email?.toLowerCase().includes(filter)
|
<tr
|
||||||
)
|
key={guidGenerator()}
|
||||||
.map((row, index) => {
|
className="bg-bunker-800 hover:bg-bunker-800/5"
|
||||||
return (
|
>
|
||||||
<tr
|
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||||
key={guidGenerator()}
|
{row.firstName}
|
||||||
className="bg-bunker-800 hover:bg-bunker-800/5"
|
</td>
|
||||||
>
|
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
{row.lastName}
|
||||||
{row.firstName}
|
</td>
|
||||||
</td>
|
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
{row.email}
|
||||||
{row.lastName}
|
</td>
|
||||||
</td>
|
<td className="flex flex-row justify-end pr-8 py-2 border-t border-0.5 border-mineshaft-700">
|
||||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
<div className="flex justify-end mr-6 w-3/4 mx-2 w-full h-full flex flex-row items-center">
|
||||||
{row.email}
|
{row.status == "granted" &&
|
||||||
</td>
|
((myRole == "admin" && row.role != "owner") ||
|
||||||
<td className="flex flex-row justify-end pr-8 py-2 border-t border-0.5 border-mineshaft-700">
|
myRole == "owner") &&
|
||||||
<div className="flex justify-end mr-6 w-3/4 mx-2 w-full h-full flex flex-row items-center">
|
myUser !== row.email ? (
|
||||||
{row.status == "granted" &&
|
<Listbox
|
||||||
((myRole == "admin" &&
|
selected={row.role}
|
||||||
row.role != "owner") ||
|
onChange={(e) => handleRoleUpdate(index, e)}
|
||||||
myRole == "owner") &&
|
data={
|
||||||
myUser !== row.email ? (
|
myRole == "owner"
|
||||||
<Listbox
|
? ["owner", "admin", "member"]
|
||||||
selected={row.role}
|
: ["admin", "member"]
|
||||||
onChange={(e) =>
|
}
|
||||||
handleRoleUpdate(
|
text="Role: "
|
||||||
index,
|
membershipId={row.membershipId}
|
||||||
e
|
/>
|
||||||
)
|
) : (
|
||||||
}
|
row.status != "invited" &&
|
||||||
data={
|
row.status != "verified" && (
|
||||||
myRole == "owner"
|
<Listbox
|
||||||
? [
|
selected={row.role}
|
||||||
"owner",
|
text="Role: "
|
||||||
"admin",
|
membershipId={row.membershipId}
|
||||||
"member",
|
/>
|
||||||
]
|
)
|
||||||
: [
|
)}
|
||||||
"admin",
|
{(row.status == "invited" ||
|
||||||
"member",
|
row.status == "verified") && (
|
||||||
]
|
<div className="w-full pl-12">
|
||||||
}
|
<Button
|
||||||
text="Role: "
|
onButtonPressed={() =>
|
||||||
membershipId={
|
deleteMembershipAndResendInvite(
|
||||||
row.membershipId
|
row.email,
|
||||||
}
|
row.membershipId
|
||||||
/>
|
)
|
||||||
) : (
|
}
|
||||||
row.status != "invited" &&
|
color="mineshaft"
|
||||||
row.status !=
|
text="Resend Invite"
|
||||||
"verified" && (
|
size="md"
|
||||||
<Listbox
|
/>
|
||||||
selected={row.role}
|
</div>
|
||||||
text="Role: "
|
)}
|
||||||
membershipId={
|
{row.status == "completed" && myUser !== row.email && (
|
||||||
row.membershipId
|
<div className="border border-mineshaft-700 rounded-md bg-white/5 hover:bg-primary text-white hover:text-black duration-200">
|
||||||
}
|
<Button
|
||||||
/>
|
onButtonPressed={() =>
|
||||||
)
|
grantAccess(row.userId, row.publicKey)
|
||||||
)}
|
}
|
||||||
{(row.status == "invited" ||
|
color="mineshaft"
|
||||||
row.status ==
|
text="Grant Access"
|
||||||
"verified") && (
|
size="md"
|
||||||
<div className="w-full pl-12">
|
/>
|
||||||
<Button
|
</div>
|
||||||
onButtonPressed={() =>
|
)}
|
||||||
deleteMembershipAndResendInvite(
|
</div>
|
||||||
row.email,
|
{myUser !== row.email &&
|
||||||
row.membershipId
|
// row.role != "admin" &&
|
||||||
)
|
myRole != "member" ? (
|
||||||
}
|
<div className="opacity-50 hover:opacity-100 flex items-center">
|
||||||
color="mineshaft"
|
<Button
|
||||||
text="Resend Invite"
|
onButtonPressed={(e) =>
|
||||||
size="md"
|
handleDelete(row.membershipId, index, e)
|
||||||
/>
|
}
|
||||||
</div>
|
color="red"
|
||||||
)}
|
size="icon-sm"
|
||||||
{row.status == "completed" &&
|
icon={faX}
|
||||||
myUser !== row.email && (
|
/>
|
||||||
<div className="border border-mineshaft-700 rounded-md bg-white/5 hover:bg-primary text-white hover:text-black duration-200">
|
</div>
|
||||||
<Button
|
) : (
|
||||||
onButtonPressed={() =>
|
<div className="w-9 h-9"></div>
|
||||||
grantAccess(
|
)}
|
||||||
row.userId,
|
</td>
|
||||||
row.publicKey
|
</tr>
|
||||||
)
|
);
|
||||||
}
|
})}
|
||||||
color="mineshaft"
|
</tbody>
|
||||||
text="Grant Access"
|
</table>
|
||||||
size="md"
|
</div>
|
||||||
/>
|
);
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{myUser !== row.email &&
|
|
||||||
// row.role != "admin" &&
|
|
||||||
myRole != "member" ? (
|
|
||||||
<div className="opacity-50 hover:opacity-100 flex items-center">
|
|
||||||
<Button
|
|
||||||
onButtonPressed={(e) =>
|
|
||||||
handleDelete(
|
|
||||||
row.membershipId,
|
|
||||||
index,
|
|
||||||
e
|
|
||||||
)
|
|
||||||
}
|
|
||||||
color="red"
|
|
||||||
size="icon-sm"
|
|
||||||
icon={faX}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="w-9 h-9"></div>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default UserTable;
|
export default UserTable;
|
||||||
|
|||||||
@@ -3,91 +3,82 @@ import React from "react";
|
|||||||
import StripeRedirect from "~/pages/api/organization/StripeRedirect";
|
import StripeRedirect from "~/pages/api/organization/StripeRedirect";
|
||||||
|
|
||||||
export default function Plan({ plan }) {
|
export default function Plan({ plan }) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`relative flex flex-col justify-between border border-2 min-w-fit w-96 rounded-lg h-68 mr-4 bg-mineshaft-800 ${
|
className={`relative flex flex-col justify-between border border-2 min-w-fit w-96 rounded-lg h-68 mr-4 bg-mineshaft-800 ${
|
||||||
(plan.name != "Starter") & (plan.current == true)
|
(plan.name != "Starter") & (plan.current == true)
|
||||||
? "border-primary"
|
? "border-primary"
|
||||||
: "border-chicago-700"
|
: "border-chicago-700"
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="flex flex-row justify-between items-center relative z-10">
|
<div className="flex flex-row justify-between items-center relative z-10">
|
||||||
<p
|
<p className={`px-6 py-4 text-3xl font-semibold text-gray-400`}>
|
||||||
className={`px-6 py-4 text-3xl font-semibold text-gray-400`}
|
{plan.name}
|
||||||
>
|
</p>
|
||||||
{plan.name}
|
</div>
|
||||||
</p>
|
<div className="flex flwx-row items-end justify-start mb-4">
|
||||||
</div>
|
<p className="pl-6 text-3xl font-semibold text-primary">
|
||||||
<div className="flex flwx-row items-end justify-start mb-4">
|
{plan.price}
|
||||||
<p className="pl-6 text-3xl font-semibold text-primary">
|
</p>
|
||||||
{plan.price}
|
<p className="pl-3 mb-1 text-lg text-gray-400">
|
||||||
</p>
|
{plan.priceExplanation}
|
||||||
<p className="pl-3 mb-1 text-lg text-gray-400">
|
</p>
|
||||||
{plan.priceExplanation}
|
</div>
|
||||||
</p>
|
<p className="relative z-10 max-w-fit px-6 text-base text-gray-400">
|
||||||
</div>
|
{plan.text}
|
||||||
<p className="relative z-10 max-w-fit px-6 text-base text-gray-400">
|
</p>
|
||||||
{plan.text}
|
<p className="relative z-10 max-w-fit px-6 text-base text-gray-400">
|
||||||
</p>
|
{plan.subtext}
|
||||||
<p className="relative z-10 max-w-fit px-6 text-base text-gray-400">
|
</p>
|
||||||
{plan.subtext}
|
</div>
|
||||||
</p>
|
<div className="flex flex-row items-center">
|
||||||
</div>
|
{plan.current == false ? (
|
||||||
<div className="flex flex-row items-center">
|
<>
|
||||||
{plan.current == false ? (
|
{plan.buttonTextMain == "Schedule a Demo" ? (
|
||||||
<>
|
<a href="/scheduledemo" target='_blank rel="noopener"'>
|
||||||
{plan.buttonTextMain == "Schedule a Demo" ? (
|
<div className="relative z-10 mx-5 mt-3 mb-4 py-2 px-4 border border-1 border-gray-600 hover:text-black hover:border-primary text-gray-400 font-semibold hover:bg-primary bg-bunker duration-200 cursor-pointer rounded-md flex w-max">
|
||||||
<a
|
{plan.buttonTextMain}
|
||||||
href="/scheduledemo"
|
</div>
|
||||||
target='_blank rel="noopener"'
|
</a>
|
||||||
>
|
) : (
|
||||||
<div className="relative z-10 mx-5 mt-3 mb-4 py-2 px-4 border border-1 border-gray-600 hover:text-black hover:border-primary text-gray-400 font-semibold hover:bg-primary bg-bunker duration-200 cursor-pointer rounded-md flex w-max">
|
<div
|
||||||
{plan.buttonTextMain}
|
className={`relative z-10 mx-5 mt-3 mb-4 py-2 px-4 border border-1 border-gray-600 text-gray-400 font-semibold ${
|
||||||
</div>
|
plan.buttonTextMain == "Downgrade"
|
||||||
</a>
|
? "hover:bg-red hover:text-white hover:border-red"
|
||||||
) : (
|
: "hover:bg-primary hover:text-black hover:border-primary"
|
||||||
<div
|
} bg-bunker duration-200 cursor-pointer rounded-md flex w-max`}
|
||||||
className={`relative z-10 mx-5 mt-3 mb-4 py-2 px-4 border border-1 border-gray-600 text-gray-400 font-semibold ${
|
>
|
||||||
plan.buttonTextMain == "Downgrade"
|
<button
|
||||||
? "hover:bg-red hover:text-white hover:border-red"
|
onClick={() =>
|
||||||
: "hover:bg-primary hover:text-black hover:border-primary"
|
StripeRedirect({
|
||||||
} bg-bunker duration-200 cursor-pointer rounded-md flex w-max`}
|
orgId: localStorage.getItem("orgData.id"),
|
||||||
>
|
})
|
||||||
<button
|
}
|
||||||
onClick={() =>
|
>
|
||||||
StripeRedirect({
|
{plan.buttonTextMain}
|
||||||
orgId: localStorage.getItem(
|
</button>
|
||||||
"orgData.id"
|
</div>
|
||||||
),
|
)}
|
||||||
})
|
<a href="/pricing" target='_blank rel="noopener"'>
|
||||||
}
|
<div className="relative z-10 text-gray-400 font-semibold hover:text-primary duration-200 cursor-pointer mb-0.5">
|
||||||
>
|
{plan.buttonTextSecondary}
|
||||||
{plan.buttonTextMain}
|
</div>
|
||||||
</button>
|
</a>
|
||||||
</div>
|
</>
|
||||||
)}
|
) : (
|
||||||
<a href="/pricing" target='_blank rel="noopener"'>
|
<div
|
||||||
<div className="relative z-10 text-gray-400 font-semibold hover:text-primary duration-200 cursor-pointer mb-0.5">
|
className={`h-8 w-full rounded-b-md flex justify-center items-center z-10 ${
|
||||||
{plan.buttonTextSecondary}
|
(plan.name != "Starter") & (plan.current == true)
|
||||||
</div>
|
? "bg-primary"
|
||||||
</a>
|
: "bg-chicago-700"
|
||||||
</>
|
}`}
|
||||||
) : (
|
>
|
||||||
<div
|
<p className="text-xs text-black font-semibold">CURRENT PLAN</p>
|
||||||
className={`h-8 w-full rounded-b-md flex justify-center items-center z-10 ${
|
</div>
|
||||||
(plan.name != "Starter") & (plan.current == true)
|
)}
|
||||||
? "bg-primary"
|
</div>
|
||||||
: "bg-chicago-700"
|
</div>
|
||||||
}`}
|
);
|
||||||
>
|
|
||||||
<p className="text-xs text-black font-semibold">
|
|
||||||
CURRENT PLAN
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,17 +10,17 @@ import guidGenerator from "../utilities/randomId";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const findReferences = (text) => {
|
const findReferences = (text) => {
|
||||||
var splitText = text.split("${");
|
var splitText = text.split("${");
|
||||||
let textArray = [splitText[0]];
|
let textArray = [splitText[0]];
|
||||||
for (var i = 1; i < splitText.length; i++) {
|
for (var i = 1; i < splitText.length; i++) {
|
||||||
let insideBrackets = "${" + splitText[i].split("}")[0];
|
let insideBrackets = "${" + splitText[i].split("}")[0];
|
||||||
if (splitText[i].includes("}")) {
|
if (splitText[i].includes("}")) {
|
||||||
insideBrackets += "}";
|
insideBrackets += "}";
|
||||||
}
|
}
|
||||||
textArray.push(insideBrackets);
|
textArray.push(insideBrackets);
|
||||||
textArray.push(splitText[i].split("}")[1]);
|
textArray.push(splitText[i].split("}")[1]);
|
||||||
}
|
}
|
||||||
return textArray;
|
return textArray;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,110 +34,98 @@ const findReferences = (text) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const DashboardInputField = ({
|
const DashboardInputField = ({
|
||||||
index,
|
index,
|
||||||
onChangeHandler,
|
onChangeHandler,
|
||||||
type,
|
type,
|
||||||
value,
|
value,
|
||||||
blurred,
|
blurred,
|
||||||
}) => {
|
}) => {
|
||||||
if (type === "varName") {
|
if (type === "varName") {
|
||||||
return (
|
return (
|
||||||
<div className="flex-col w-full">
|
<div className="flex-col w-full">
|
||||||
<div
|
<div
|
||||||
className={`group relative flex flex-col justify-center w-full max-w-2xl border border-mineshaft-500 rounded-md`}
|
className={`group relative flex flex-col justify-center w-full max-w-2xl border border-mineshaft-500 rounded-md`}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
onChange={(e) => onChangeHandler(e.target.value, index)}
|
onChange={(e) => onChangeHandler(e.target.value, index)}
|
||||||
type={type}
|
type={type}
|
||||||
value={value}
|
value={value}
|
||||||
className="asolute z-10 peer font-mono ph-no-capture bg-bunker-800 rounded-md caret-white text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-200"
|
className="asolute z-10 peer font-mono ph-no-capture bg-bunker-800 rounded-md caret-white text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-200"
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (type === "value") {
|
} else if (type === "value") {
|
||||||
return (
|
return (
|
||||||
<div className="flex-col w-full">
|
<div className="flex-col w-full">
|
||||||
<div
|
<div
|
||||||
className={`group relative flex flex-col justify-center w-full max-w-2xl border border-mineshaft-500 rounded-md`}
|
className={`group relative flex flex-col justify-center w-full max-w-2xl border border-mineshaft-500 rounded-md`}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
onChange={(e) => onChangeHandler(e.target.value, index)}
|
onChange={(e) => onChangeHandler(e.target.value, index)}
|
||||||
type={type}
|
type={type}
|
||||||
value={value}
|
value={value}
|
||||||
className={`${
|
className={`${
|
||||||
blurred
|
blurred
|
||||||
? "text-transparent group-hover:text-transparent focus:text-transparent active:text-transparent"
|
? "text-transparent group-hover:text-transparent focus:text-transparent active:text-transparent"
|
||||||
: ""
|
: ""
|
||||||
} asolute z-10 peer font-mono ph-no-capture bg-transparent rounded-md caret-white text-transparent text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-200`}
|
} asolute z-10 peer font-mono ph-no-capture bg-transparent rounded-md caret-white text-transparent text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-200`}
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
blurred
|
blurred
|
||||||
? "text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-400 peer-active:text-gray-400"
|
? "text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-400 peer-active:text-gray-400"
|
||||||
: ""
|
: ""
|
||||||
} flex flex-row font-mono absolute z-0 ph-no-capture bg-bunker-800 h-9 rounded-md text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-100`}
|
} flex flex-row font-mono absolute z-0 ph-no-capture bg-bunker-800 h-9 rounded-md text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-100`}
|
||||||
>
|
>
|
||||||
{findReferences(value).map((texts, id) => {
|
{findReferences(value).map((texts, id) => {
|
||||||
if (id % 2 == 0 || texts.length <= 2) {
|
if (id % 2 == 0 || texts.length <= 2) {
|
||||||
return (
|
return (
|
||||||
<span className="ph-no-capture" key={id}>
|
<span className="ph-no-capture" key={id}>
|
||||||
{texts}
|
{texts}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<span
|
<span className="ph-no-capture text-yellow" key={id}>
|
||||||
className="ph-no-capture text-yellow"
|
{texts.slice(0, 2)}
|
||||||
key={id}
|
<span className="ph-no-capture text-yellow-200/80">
|
||||||
>
|
{texts.slice(2, texts.length - 1)}
|
||||||
{texts.slice(0, 2)}
|
</span>
|
||||||
<span className="ph-no-capture text-yellow-200/80">
|
{texts.slice(texts.length - 1, texts.length) == "}" ? (
|
||||||
{texts.slice(2, texts.length - 1)}
|
<span className="ph-no-capture text-yellow">
|
||||||
</span>
|
{texts.slice(texts.length - 1, texts.length)}{" "}
|
||||||
{texts.slice(
|
</span>
|
||||||
texts.length - 1,
|
) : (
|
||||||
texts.length
|
<span className="ph-no-capture text-yellow-400">
|
||||||
) == "}" ? (
|
{texts.slice(texts.length - 1, texts.length)}{" "}
|
||||||
<span className="ph-no-capture text-yellow">
|
</span>
|
||||||
{texts.slice(
|
)}
|
||||||
texts.length - 1,
|
</span>
|
||||||
texts.length
|
);
|
||||||
)}{" "}
|
})}
|
||||||
</span>
|
</div>
|
||||||
) : (
|
{blurred && (
|
||||||
<span className="ph-no-capture text-yellow-400">
|
<div className="z-20 peer pr-2 bg-bunker-800 group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible absolute h-9 w-fit max-w-xl rounded-md flex items-center text-gray-400/50 text-clip overflow-hidden">
|
||||||
{texts.slice(
|
<p className="ml-2"></p>
|
||||||
texts.length - 1,
|
{value
|
||||||
texts.length
|
.split("")
|
||||||
)}{" "}
|
.slice(0, 42)
|
||||||
</span>
|
.map(() => (
|
||||||
)}
|
<FontAwesomeIcon
|
||||||
</span>
|
key={guidGenerator()}
|
||||||
);
|
className="text-xxs mx-0.5"
|
||||||
})}
|
icon={faCircle}
|
||||||
</div>
|
/>
|
||||||
{blurred && (
|
))}
|
||||||
<div className="z-20 peer pr-2 bg-bunker-800 group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible absolute h-9 w-fit max-w-xl rounded-md flex items-center text-gray-400/50 text-clip overflow-hidden">
|
</div>
|
||||||
<p className="ml-2"></p>
|
)}
|
||||||
{value
|
</div>
|
||||||
.split("")
|
</div>
|
||||||
.slice(0, 42)
|
);
|
||||||
.map(() => (
|
}
|
||||||
<FontAwesomeIcon
|
|
||||||
key={guidGenerator()}
|
|
||||||
className="text-xxs mx-0.5"
|
|
||||||
icon={faCircle}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(DashboardInputField);
|
export default React.memo(DashboardInputField);
|
||||||
|
|||||||
@@ -9,178 +9,177 @@ import parse from "../utilities/file";
|
|||||||
import guidGenerator from "../utilities/randomId";
|
import guidGenerator from "../utilities/randomId";
|
||||||
|
|
||||||
const DropZone = ({
|
const DropZone = ({
|
||||||
setData,
|
setData,
|
||||||
setErrorDragAndDrop,
|
setErrorDragAndDrop,
|
||||||
createNewFile,
|
createNewFile,
|
||||||
errorDragAndDrop,
|
errorDragAndDrop,
|
||||||
addPresetRow,
|
addPresetRow,
|
||||||
setButtonReady,
|
setButtonReady,
|
||||||
keysExist,
|
keysExist,
|
||||||
numCurrentRows
|
numCurrentRows,
|
||||||
}) => {
|
}) => {
|
||||||
const handleDragEnter = (e) => {
|
const handleDragEnter = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDragLeave = (e) => {
|
const handleDragLeave = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDragOver = (e) => {
|
const handleDragOver = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
// set dropEffect to copy i.e copy of the source item
|
// set dropEffect to copy i.e copy of the source item
|
||||||
e.dataTransfer.dropEffect = "copy";
|
e.dataTransfer.dropEffect = "copy";
|
||||||
};
|
};
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
// This function function immediately parses the file after it is dropped
|
// This function function immediately parses the file after it is dropped
|
||||||
const handleDrop = async (e) => {
|
const handleDrop = async (e) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setTimeout(() => setLoading(false), 5000);
|
setTimeout(() => setLoading(false), 5000);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.dataTransfer.dropEffect = "copy";
|
e.dataTransfer.dropEffect = "copy";
|
||||||
|
|
||||||
var file = e.dataTransfer.files[0],
|
var file = e.dataTransfer.files[0],
|
||||||
reader = new FileReader();
|
reader = new FileReader();
|
||||||
reader.onload = function (event) {
|
reader.onload = function (event) {
|
||||||
const keyPairs = parse(event.target.result)
|
const keyPairs = parse(event.target.result);
|
||||||
const newData = Object.keys(keyPairs)
|
const newData = Object.keys(keyPairs).map((key, index) => [
|
||||||
.map((key, index) => [
|
guidGenerator(),
|
||||||
guidGenerator(),
|
numCurrentRows + index,
|
||||||
numCurrentRows + index,
|
key,
|
||||||
key,
|
keyPairs[key],
|
||||||
keyPairs[key],
|
"shared",
|
||||||
"shared",
|
]);
|
||||||
]);
|
setData(newData);
|
||||||
setData(newData);
|
setButtonReady(true);
|
||||||
setButtonReady(true);
|
};
|
||||||
};
|
|
||||||
|
|
||||||
// If something is wrong show an error
|
// If something is wrong show an error
|
||||||
try {
|
try {
|
||||||
reader.readAsText(file);
|
reader.readAsText(file);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setErrorDragAndDrop(true);
|
setErrorDragAndDrop(true);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// This function is used when the user manually selects a file from the in-browser dircetory (not drag and drop)
|
// This function is used when the user manually selects a file from the in-browser dircetory (not drag and drop)
|
||||||
const handleFileSelect = (e) => {
|
const handleFileSelect = (e) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setTimeout(() => setLoading(false), 5000);
|
setTimeout(() => setLoading(false), 5000);
|
||||||
var file = e.target.files[0],
|
var file = e.target.files[0],
|
||||||
reader = new FileReader();
|
reader = new FileReader();
|
||||||
reader.onload = function (event) {
|
reader.onload = function (event) {
|
||||||
const newData = event.target.result
|
const newData = event.target.result
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.map((line, index) => [
|
.map((line, index) => [
|
||||||
guidGenerator(),
|
guidGenerator(),
|
||||||
numCurrentRows + index,
|
numCurrentRows + index,
|
||||||
line.split("=")[0],
|
line.split("=")[0],
|
||||||
line.split("=").slice(1, line.split("=").length).join("="),
|
line.split("=").slice(1, line.split("=").length).join("="),
|
||||||
"shared",
|
"shared",
|
||||||
]);
|
]);
|
||||||
setData(newData);
|
setData(newData);
|
||||||
setButtonReady(true);
|
setButtonReady(true);
|
||||||
};
|
};
|
||||||
reader.readAsText(file);
|
reader.readAsText(file);
|
||||||
};
|
};
|
||||||
|
|
||||||
return loading ? (
|
return loading ? (
|
||||||
<div className="flex items-center justify-center pt-16 mb-16">
|
<div className="flex items-center justify-center pt-16 mb-16">
|
||||||
<Image
|
<Image
|
||||||
src="/images/loading/loading.gif"
|
src="/images/loading/loading.gif"
|
||||||
height={70}
|
height={70}
|
||||||
width={120}
|
width={120}
|
||||||
alt="google logo"
|
alt="google logo"
|
||||||
></Image>
|
></Image>
|
||||||
</div>
|
</div>
|
||||||
) : keysExist ? (
|
) : keysExist ? (
|
||||||
<div
|
<div
|
||||||
className="opacity-60 hover:opacity-100 duration-200 relative bg-bunker outline max-w-[calc(100%-1rem)] w-full outline-dashed outline-gray-600 rounded-md outline-2 flex flex-col items-center justify-center mb-16 mx-auto mt-1 py-8 px-2"
|
className="opacity-60 hover:opacity-100 duration-200 relative bg-bunker outline max-w-[calc(100%-1rem)] w-full outline-dashed outline-gray-600 rounded-md outline-2 flex flex-col items-center justify-center mb-16 mx-auto mt-1 py-8 px-2"
|
||||||
onDragEnter={(e) => handleDragEnter(e)}
|
onDragEnter={(e) => handleDragEnter(e)}
|
||||||
onDragOver={(e) => handleDragOver(e)}
|
onDragOver={(e) => handleDragOver(e)}
|
||||||
onDragLeave={(e) => handleDragLeave(e)}
|
onDragLeave={(e) => handleDragLeave(e)}
|
||||||
onDrop={(e) => handleDrop(e)}
|
onDrop={(e) => handleDrop(e)}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
id="fileSelect"
|
id="fileSelect"
|
||||||
type="file"
|
type="file"
|
||||||
className="opacity-0 absolute w-full h-full"
|
className="opacity-0 absolute w-full h-full"
|
||||||
accept=".txt,.env"
|
accept=".txt,.env"
|
||||||
onChange={(e) => handleFileSelect(e)}
|
onChange={(e) => handleFileSelect(e)}
|
||||||
/>
|
/>
|
||||||
{errorDragAndDrop ? (
|
{errorDragAndDrop ? (
|
||||||
<div className="my-3 max-w-xl opacity-80"></div>
|
<div className="my-3 max-w-xl opacity-80"></div>
|
||||||
) : (
|
) : (
|
||||||
<div className=""></div>
|
<div className=""></div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faUpload}
|
icon={faUpload}
|
||||||
className="text-gray-300 text-3xl mr-6"
|
className="text-gray-300 text-3xl mr-6"
|
||||||
/>
|
/>
|
||||||
<p className="text-gray-300 mt-1">
|
<p className="text-gray-300 mt-1">
|
||||||
Drag and drop your .env file here to add more keys.
|
Drag and drop your .env file here to add more keys.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{errorDragAndDrop ? (
|
{errorDragAndDrop ? (
|
||||||
<div className="mt-8 max-w-xl opacity-80">
|
<div className="mt-8 max-w-xl opacity-80">
|
||||||
<Error text="Something went wrong! Make sure you drag the file directly from the folder in which it is located (e.g., not VS code). Tip: click 'Reveal in Finder/Explorer'" />
|
<Error text="Something went wrong! Make sure you drag the file directly from the folder in which it is located (e.g., not VS code). Tip: click 'Reveal in Finder/Explorer'" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className="opacity-80 hover:opacity-100 duration-200 relative bg-bunker outline max-w-2xl w-full outline-dashed outline-gray-700 rounded-md outline-2 flex flex-col items-center justify-center pt-16 mb-16 px-4"
|
className="opacity-80 hover:opacity-100 duration-200 relative bg-bunker outline max-w-2xl w-full outline-dashed outline-gray-700 rounded-md outline-2 flex flex-col items-center justify-center pt-16 mb-16 px-4"
|
||||||
onDragEnter={(e) => handleDragEnter(e)}
|
onDragEnter={(e) => handleDragEnter(e)}
|
||||||
onDragOver={(e) => handleDragOver(e)}
|
onDragOver={(e) => handleDragOver(e)}
|
||||||
onDragLeave={(e) => handleDragLeave(e)}
|
onDragLeave={(e) => handleDragLeave(e)}
|
||||||
onDrop={(e) => handleDrop(e)}
|
onDrop={(e) => handleDrop(e)}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faUpload} className="text-7xl mb-8" />
|
<FontAwesomeIcon icon={faUpload} className="text-7xl mb-8" />
|
||||||
<p className="">Drag and drop your .env file here.</p>
|
<p className="">Drag and drop your .env file here.</p>
|
||||||
<input
|
<input
|
||||||
id="fileSelect"
|
id="fileSelect"
|
||||||
type="file"
|
type="file"
|
||||||
className="opacity-0 absolute w-full h-full"
|
className="opacity-0 absolute w-full h-full"
|
||||||
accept=".txt,.env"
|
accept=".txt,.env"
|
||||||
onChange={(e) => handleFileSelect(e)}
|
onChange={(e) => handleFileSelect(e)}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-row w-full items-center justify-center mb-6 mt-5">
|
<div className="flex flex-row w-full items-center justify-center mb-6 mt-5">
|
||||||
<div className="border-t border-gray-700 w-1/5"></div>
|
<div className="border-t border-gray-700 w-1/5"></div>
|
||||||
<p className="text-gray-400 text-xs mx-4">OR</p>
|
<p className="text-gray-400 text-xs mx-4">OR</p>
|
||||||
<div className="border-t border-gray-700 w-1/5"></div>
|
<div className="border-t border-gray-700 w-1/5"></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="z-10 mb-6">
|
<div className="z-10 mb-6">
|
||||||
<Button
|
<Button
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
text="Create a new .env file"
|
text="Create a new .env file"
|
||||||
onButtonPressed={createNewFile}
|
onButtonPressed={createNewFile}
|
||||||
size="md"
|
size="md"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errorDragAndDrop ? (
|
{errorDragAndDrop ? (
|
||||||
<div className="opacity-80">
|
<div className="opacity-80">
|
||||||
<Error text="Something went wrong! Make sure you drag the file directly from the folder in which it is located (e.g., not VS code). Tip: click 'Reveal in Finder/Explorer'" />
|
<Error text="Something went wrong! Make sure you drag the file directly from the folder in which it is located (e.g., not VS code). Tip: click 'Reveal in Finder/Explorer'" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="py-3">
|
<div className="py-3">
|
||||||
{/* <p className="text-xs text-gray-500"> If you are expecting to see a file here, contact your administrator for permission. </p> */}
|
{/* <p className="text-xs text-gray-500"> If you are expecting to see a file here, contact your administrator for permission. </p> */}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DropZone;
|
export default DropZone;
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
/* eslint-disable react/jsx-key */
|
/* eslint-disable react/jsx-key */
|
||||||
import React, { Fragment, useEffect,useState } from "react";
|
import React, { Fragment, useEffect, useState } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { faGithub,faSlack } from "@fortawesome/free-brands-svg-icons";
|
import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons";
|
||||||
import { faCircleQuestion } from "@fortawesome/free-regular-svg-icons";
|
import { faCircleQuestion } from "@fortawesome/free-regular-svg-icons";
|
||||||
import {
|
import {
|
||||||
faAngleDown,
|
faAngleDown,
|
||||||
faBook,
|
faBook,
|
||||||
faCoins,
|
faCoins,
|
||||||
faEnvelope,
|
faEnvelope,
|
||||||
faGear,
|
faGear,
|
||||||
faPlus,
|
faPlus,
|
||||||
faRightFromBracket,
|
faRightFromBracket,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { Menu, Transition } from "@headlessui/react";
|
import { Menu, Transition } from "@headlessui/react";
|
||||||
@@ -25,301 +25,269 @@ import getUser from "~/pages/api/user/getUser";
|
|||||||
import guidGenerator from "../utilities/randomId";
|
import guidGenerator from "../utilities/randomId";
|
||||||
|
|
||||||
const supportOptions = [
|
const supportOptions = [
|
||||||
[
|
[
|
||||||
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faSlack} />,
|
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faSlack} />,
|
||||||
"Join Slack Forum",
|
"Join Slack Forum",
|
||||||
"https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g",
|
"https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faBook} />,
|
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faBook} />,
|
||||||
"Read Docs",
|
"Read Docs",
|
||||||
"https://infisical.com/docs/getting-started/introduction",
|
"https://infisical.com/docs/getting-started/introduction",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faGithub} />,
|
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faGithub} />,
|
||||||
"Open a GitHub Issue",
|
"Open a GitHub Issue",
|
||||||
"https://github.com/Infisical/infisical-cli/issues",
|
"https://github.com/Infisical/infisical-cli/issues",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faEnvelope} />,
|
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faEnvelope} />,
|
||||||
"Send us an Email",
|
"Send us an Email",
|
||||||
"mailto:[email protected]",
|
"mailto:[email protected]",
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function Navbar({ onButtonPressed }) {
|
export default function Navbar({ onButtonPressed }) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [user, setUser] = useState({});
|
const [user, setUser] = useState({});
|
||||||
const [orgs, setOrgs] = useState([]);
|
const [orgs, setOrgs] = useState([]);
|
||||||
const [currentOrg, setCurrentOrg] = useState([]);
|
const [currentOrg, setCurrentOrg] = useState([]);
|
||||||
|
|
||||||
useEffect(async () => {
|
useEffect(async () => {
|
||||||
const userData = await getUser();
|
const userData = await getUser();
|
||||||
setUser(userData);
|
setUser(userData);
|
||||||
const orgsData = await getOrganizations();
|
const orgsData = await getOrganizations();
|
||||||
setOrgs(orgsData);
|
setOrgs(orgsData);
|
||||||
const currentOrg = await getOrganization({
|
const currentOrg = await getOrganization({
|
||||||
orgId: localStorage.getItem("orgData.id"),
|
orgId: localStorage.getItem("orgData.id"),
|
||||||
});
|
});
|
||||||
setCurrentOrg(currentOrg);
|
setCurrentOrg(currentOrg);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const closeApp = async () => {
|
const closeApp = async () => {
|
||||||
console.log("Logging out...");
|
console.log("Logging out...");
|
||||||
await logout();
|
await logout();
|
||||||
router.push("/");
|
router.push("/");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="absolute flex flex-row justify-between w-full bg-bunker text-white border-b border-mineshaft-500 z-50">
|
<div className="absolute flex flex-row justify-between w-full bg-bunker text-white border-b border-mineshaft-500 z-50">
|
||||||
<div className="m-auto flex justify-start items-center mx-4">
|
<div className="m-auto flex justify-start items-center mx-4">
|
||||||
<div className="flex flex-row items-center">
|
<div className="flex flex-row items-center">
|
||||||
<div className="flex justify-center py-4">
|
<div className="flex justify-center py-4">
|
||||||
<Image
|
<Image
|
||||||
src="/images/logotransparent.png"
|
src="/images/logotransparent.png"
|
||||||
height={23}
|
height={23}
|
||||||
width={57}
|
width={57}
|
||||||
alt="logo"
|
alt="logo"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<a className="text-2xl text-white font-semibold mx-2">
|
<a className="text-2xl text-white font-semibold mx-2">Infisical</a>
|
||||||
Infisical
|
</div>
|
||||||
</a>
|
</div>
|
||||||
</div>
|
<div className="relative flex justify-start items-center mx-2 z-40">
|
||||||
</div>
|
<Menu as="div" className="relative inline-block text-left">
|
||||||
<div className="relative flex justify-start items-center mx-2 z-40">
|
<div className="mr-4">
|
||||||
<Menu as="div" className="relative inline-block text-left">
|
<Menu.Button className="inline-flex w-full justify-center rounded-md px-2 py-2 text-sm font-medium text-gray-200 rounded-md hover:bg-white/10 duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">
|
||||||
<div className="mr-4">
|
<FontAwesomeIcon className="text-xl" icon={faCircleQuestion} />
|
||||||
<Menu.Button className="inline-flex w-full justify-center rounded-md px-2 py-2 text-sm font-medium text-gray-200 rounded-md hover:bg-white/10 duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">
|
</Menu.Button>
|
||||||
<FontAwesomeIcon
|
</div>
|
||||||
className="text-xl"
|
<Transition
|
||||||
icon={faCircleQuestion}
|
as={Fragment}
|
||||||
/>
|
enter="transition ease-out duration-100"
|
||||||
</Menu.Button>
|
enterFrom="transform opacity-0 scale-95"
|
||||||
</div>
|
enterTo="transform opacity-100 scale-100"
|
||||||
<Transition
|
leave="transition ease-in duration-75"
|
||||||
as={Fragment}
|
leaveFrom="transform opacity-100 scale-100"
|
||||||
enter="transition ease-out duration-100"
|
leaveTo="transform opacity-0 scale-95"
|
||||||
enterFrom="transform opacity-0 scale-95"
|
>
|
||||||
enterTo="transform opacity-100 scale-100"
|
<Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none px-2 py-1.5">
|
||||||
leave="transition ease-in duration-75"
|
{supportOptions.map((option) => (
|
||||||
leaveFrom="transform opacity-100 scale-100"
|
// eslint-disable-next-line react/jsx-no-target-blank
|
||||||
leaveTo="transform opacity-0 scale-95"
|
<a
|
||||||
>
|
key={guidGenerator()}
|
||||||
<Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none px-2 py-1.5">
|
target="_blank"
|
||||||
{supportOptions.map((option) => (
|
rel="noopener"
|
||||||
// eslint-disable-next-line react/jsx-no-target-blank
|
href={option[2]}
|
||||||
<a
|
className="font-normal text-gray-300 duration-200 rounded-md w-full flex items-center py-0.5"
|
||||||
key={guidGenerator()}
|
>
|
||||||
target="_blank"
|
<div className="relative flex justify-start items-center cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/10 duration-200 hover:text-gray-200 w-full">
|
||||||
rel="noopener"
|
{option[0]}
|
||||||
href={option[2]}
|
<div className="text-sm">{option[1]}</div>
|
||||||
className="font-normal text-gray-300 duration-200 rounded-md w-full flex items-center py-0.5"
|
</div>
|
||||||
>
|
</a>
|
||||||
<div className="relative flex justify-start items-center cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/10 duration-200 hover:text-gray-200 w-full">
|
))}
|
||||||
{option[0]}
|
</Menu.Items>
|
||||||
<div className="text-sm">
|
</Transition>
|
||||||
{option[1]}
|
</Menu>
|
||||||
</div>
|
<Menu as="div" className="relative inline-block text-left mr-4">
|
||||||
</div>
|
<div>
|
||||||
</a>
|
<Menu.Button className="inline-flex w-full justify-center rounded-md pr-2 pl-2 py-2 text-sm font-medium text-gray-200 rounded-md hover:bg-white/10 duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">
|
||||||
))}
|
{user?.firstName} {user?.lastName}
|
||||||
</Menu.Items>
|
<FontAwesomeIcon
|
||||||
</Transition>
|
icon={faAngleDown}
|
||||||
</Menu>
|
className="ml-2 mt-1 text-sm text-gray-300 hover:text-lime-100"
|
||||||
<Menu as="div" className="relative inline-block text-left mr-4">
|
/>
|
||||||
<div>
|
</Menu.Button>
|
||||||
<Menu.Button className="inline-flex w-full justify-center rounded-md pr-2 pl-2 py-2 text-sm font-medium text-gray-200 rounded-md hover:bg-white/10 duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">
|
</div>
|
||||||
{user?.firstName} {user?.lastName}
|
<Transition
|
||||||
<FontAwesomeIcon
|
as={Fragment}
|
||||||
icon={faAngleDown}
|
enter="transition ease-out duration-100"
|
||||||
className="ml-2 mt-1 text-sm text-gray-300 hover:text-lime-100"
|
enterFrom="transform opacity-0 scale-95"
|
||||||
/>
|
enterTo="transform opacity-100 scale-100"
|
||||||
</Menu.Button>
|
leave="transition ease-in duration-75"
|
||||||
</div>
|
leaveFrom="transform opacity-100 scale-100"
|
||||||
<Transition
|
leaveTo="transform opacity-0 scale-95"
|
||||||
as={Fragment}
|
>
|
||||||
enter="transition ease-out duration-100"
|
<Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right divide-y divide-gray-700 rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none">
|
||||||
enterFrom="transform opacity-0 scale-95"
|
<div className="px-1 py-1 ">
|
||||||
enterTo="transform opacity-100 scale-100"
|
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
|
||||||
leave="transition ease-in duration-75"
|
SIGNED IN AS
|
||||||
leaveFrom="transform opacity-100 scale-100"
|
</div>
|
||||||
leaveTo="transform opacity-0 scale-95"
|
<div
|
||||||
>
|
onClick={() =>
|
||||||
<Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right divide-y divide-gray-700 rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none">
|
router.push("/settings/personal/" + router.query.id)
|
||||||
<div className="px-1 py-1 ">
|
}
|
||||||
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
|
className="flex flex-row items-center px-1 mx-1 my-1 hover:bg-white/5 cursor-pointer rounded-md"
|
||||||
SIGNED IN AS
|
>
|
||||||
</div>
|
<div className="bg-white/10 h-8 w-9 rounded-full flex items-center justify-center text-gray-300">
|
||||||
<div
|
{user?.firstName?.charAt(0)}
|
||||||
onClick={() =>
|
</div>
|
||||||
router.push(
|
<div className="flex items-center justify-between w-full">
|
||||||
"/settings/personal/" +
|
<div>
|
||||||
router.query.id
|
<p className="text-gray-300 px-2 pt-1 text-sm">
|
||||||
)
|
{" "}
|
||||||
}
|
{user?.firstName} {user?.lastName}
|
||||||
className="flex flex-row items-center px-1 mx-1 my-1 hover:bg-white/5 cursor-pointer rounded-md"
|
</p>
|
||||||
>
|
<p className="text-gray-400 px-2 pb-1 text-xs">
|
||||||
<div className="bg-white/10 h-8 w-9 rounded-full flex items-center justify-center text-gray-300">
|
{" "}
|
||||||
{user?.firstName?.charAt(0)}
|
{user?.email}
|
||||||
</div>
|
</p>
|
||||||
<div className="flex items-center justify-between w-full">
|
</div>
|
||||||
<div>
|
<FontAwesomeIcon
|
||||||
<p className="text-gray-300 px-2 pt-1 text-sm">
|
icon={faGear}
|
||||||
{" "}
|
className="text-lg text-gray-400 p-2 mr-1 rounded-md cursor-pointer hover:bg-white/10"
|
||||||
{user?.firstName}{" "}
|
/>
|
||||||
{user?.lastName}
|
</div>
|
||||||
</p>
|
</div>
|
||||||
<p className="text-gray-400 px-2 pb-1 text-xs">
|
</div>
|
||||||
{" "}
|
<div className="px-2 pt-2">
|
||||||
{user?.email}
|
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
|
||||||
</p>
|
CURRENT ORGANIZATION
|
||||||
</div>
|
</div>
|
||||||
<FontAwesomeIcon
|
<div
|
||||||
icon={faGear}
|
onClick={() =>
|
||||||
className="text-lg text-gray-400 p-2 mr-1 rounded-md cursor-pointer hover:bg-white/10"
|
router.push("/settings/org/" + router.query.id)
|
||||||
/>
|
}
|
||||||
</div>
|
className="flex flex-row items-center px-2 mt-2 py-1 hover:bg-white/5 cursor-pointer rounded-md"
|
||||||
</div>
|
>
|
||||||
</div>
|
<div className="bg-white/10 h-7 w-8 rounded-md flex items-center justify-center text-gray-300">
|
||||||
<div className="px-2 pt-2">
|
{currentOrg?.name?.charAt(0)}
|
||||||
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
|
</div>
|
||||||
CURRENT ORGANIZATION
|
<div className="flex items-center justify-between w-full">
|
||||||
</div>
|
<p className="text-gray-300 px-2 text-sm">
|
||||||
<div
|
{currentOrg?.name}
|
||||||
onClick={() =>
|
</p>
|
||||||
router.push(
|
<FontAwesomeIcon
|
||||||
"/settings/org/" + router.query.id
|
icon={faGear}
|
||||||
)
|
className="text-lg text-gray-400 p-2 rounded-md cursor-pointer hover:bg-white/10"
|
||||||
}
|
/>
|
||||||
className="flex flex-row items-center px-2 mt-2 py-1 hover:bg-white/5 cursor-pointer rounded-md"
|
</div>
|
||||||
>
|
</div>
|
||||||
<div className="bg-white/10 h-7 w-8 rounded-md flex items-center justify-center text-gray-300">
|
<button
|
||||||
{currentOrg?.name?.charAt(0)}
|
// onClick={buttonAction}
|
||||||
</div>
|
className="cursor-pointer w-full"
|
||||||
<div className="flex items-center justify-between w-full">
|
>
|
||||||
<p className="text-gray-300 px-2 text-sm">
|
<div
|
||||||
{currentOrg?.name}
|
onClick={() =>
|
||||||
</p>
|
router.push("/settings/billing/" + router.query.id)
|
||||||
<FontAwesomeIcon
|
}
|
||||||
icon={faGear}
|
className="mt-1 relative flex justify-start cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/5 duration-200 hover:text-gray-200"
|
||||||
className="text-lg text-gray-400 p-2 rounded-md cursor-pointer hover:bg-white/10"
|
>
|
||||||
/>
|
<FontAwesomeIcon
|
||||||
</div>
|
className="text-lg pl-1.5 pr-3"
|
||||||
</div>
|
icon={faCoins}
|
||||||
<button
|
/>
|
||||||
// onClick={buttonAction}
|
<div className="text-sm">Usage & Billing</div>
|
||||||
className="cursor-pointer w-full"
|
</div>
|
||||||
>
|
</button>
|
||||||
<div
|
<button
|
||||||
onClick={() =>
|
// onClick={buttonAction}
|
||||||
router.push(
|
className="cursor-pointer w-full mb-2"
|
||||||
"/settings/billing/" +
|
>
|
||||||
router.query.id
|
<div
|
||||||
)
|
onClick={() =>
|
||||||
}
|
router.push(
|
||||||
className="mt-1 relative flex justify-start cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/5 duration-200 hover:text-gray-200"
|
"/settings/org/" + router.query.id + "?invite"
|
||||||
>
|
)
|
||||||
<FontAwesomeIcon
|
}
|
||||||
className="text-lg pl-1.5 pr-3"
|
className="relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-primary/100 duration-200 hover:text-black hover:font-semibold mt-1"
|
||||||
icon={faCoins}
|
>
|
||||||
/>
|
<span className="rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4">
|
||||||
<div className="text-sm">
|
<FontAwesomeIcon icon={faPlus} className="ml-1" />
|
||||||
Usage & Billing
|
</span>
|
||||||
</div>
|
<div className="text-sm ml-1">Invite Members</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<button
|
</div>
|
||||||
// onClick={buttonAction}
|
{orgs?.length > 1 && (
|
||||||
className="cursor-pointer w-full mb-2"
|
<div className="px-1 pt-1">
|
||||||
>
|
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
|
||||||
<div
|
OTHER ORGANIZATIONS
|
||||||
onClick={() =>
|
</div>
|
||||||
router.push(
|
<div className="flex flex-col items-start px-1 mt-3 mb-2">
|
||||||
"/settings/org/" +
|
{orgs
|
||||||
router.query.id +
|
.filter(
|
||||||
"?invite"
|
(org) => org._id != localStorage.getItem("orgData.id")
|
||||||
)
|
)
|
||||||
}
|
.map((org) => (
|
||||||
className="relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-primary/100 duration-200 hover:text-black hover:font-semibold mt-1"
|
<div
|
||||||
>
|
key={guidGenerator()}
|
||||||
<span className="rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4">
|
onClick={() => {
|
||||||
<FontAwesomeIcon
|
localStorage.setItem("orgData.id", org._id);
|
||||||
icon={faPlus}
|
router.reload();
|
||||||
className="ml-1"
|
}}
|
||||||
/>
|
className="flex flex-row justify-start items-center hover:bg-white/5 w-full p-1.5 cursor-pointer rounded-md"
|
||||||
</span>
|
>
|
||||||
<div className="text-sm ml-1">
|
<div className="bg-white/10 h-7 w-8 rounded-md flex items-center justify-center text-gray-300">
|
||||||
Invite Members
|
{org.name.charAt(0)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex items-center justify-between w-full">
|
||||||
</button>
|
<p className="text-gray-300 px-2 text-sm">
|
||||||
</div>
|
{org.name}
|
||||||
{orgs?.length > 1 && (
|
</p>
|
||||||
<div className="px-1 pt-1">
|
</div>
|
||||||
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
|
</div>
|
||||||
OTHER ORGANIZATIONS
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-start px-1 mt-3 mb-2">
|
</div>
|
||||||
{orgs
|
)}
|
||||||
.filter(
|
<div className="px-1 py-1">
|
||||||
(org) =>
|
<Menu.Item>
|
||||||
org._id !=
|
{({ active }) => (
|
||||||
localStorage.getItem(
|
<button
|
||||||
"orgData.id"
|
onClick={closeApp}
|
||||||
)
|
className={`${
|
||||||
)
|
active
|
||||||
.map((org) => (
|
? "bg-red font-semibold text-white"
|
||||||
<div
|
: "text-gray-400"
|
||||||
key={guidGenerator()}
|
} group flex w-full items-center rounded-md px-2 py-2 text-sm`}
|
||||||
onClick={() => {
|
>
|
||||||
localStorage.setItem(
|
<div className="relative flex justify-start items-center cursor-pointer select-none">
|
||||||
"orgData.id",
|
<FontAwesomeIcon
|
||||||
org._id
|
className="text-lg ml-1.5 mr-3"
|
||||||
);
|
icon={faRightFromBracket}
|
||||||
router.reload();
|
/>
|
||||||
}}
|
Log Out
|
||||||
className="flex flex-row justify-start items-center hover:bg-white/5 w-full p-1.5 cursor-pointer rounded-md"
|
</div>
|
||||||
>
|
</button>
|
||||||
<div className="bg-white/10 h-7 w-8 rounded-md flex items-center justify-center text-gray-300">
|
)}
|
||||||
{org.name.charAt(0)}
|
</Menu.Item>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between w-full">
|
</Menu.Items>
|
||||||
<p className="text-gray-300 px-2 text-sm">
|
</Transition>
|
||||||
{org.name}
|
</Menu>
|
||||||
</p>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="px-1 py-1">
|
|
||||||
<Menu.Item>
|
|
||||||
{({ active }) => (
|
|
||||||
<button
|
|
||||||
onClick={closeApp}
|
|
||||||
className={`${
|
|
||||||
active
|
|
||||||
? "bg-red font-semibold text-white"
|
|
||||||
: "text-gray-400"
|
|
||||||
} group flex w-full items-center rounded-md px-2 py-2 text-sm`}
|
|
||||||
>
|
|
||||||
<div className="relative flex justify-start items-center cursor-pointer select-none">
|
|
||||||
<FontAwesomeIcon
|
|
||||||
className="text-lg ml-1.5 mr-3"
|
|
||||||
icon={faRightFromBracket}
|
|
||||||
/>
|
|
||||||
Log Out
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</Menu.Item>
|
|
||||||
</div>
|
|
||||||
</Menu.Items>
|
|
||||||
</Transition>
|
|
||||||
</Menu>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import React, { useEffect, useState } from "react";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { faCcMastercard, faCcVisa } from "@fortawesome/free-brands-svg-icons";
|
import { faCcMastercard, faCcVisa } from "@fortawesome/free-brands-svg-icons";
|
||||||
import {
|
import {
|
||||||
faAngleRight,
|
faAngleRight,
|
||||||
faQuestionCircle,
|
faQuestionCircle,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { faCircle } from "@fortawesome/free-solid-svg-icons";
|
import { faCircle } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
@@ -12,46 +12,46 @@ import getOrganization from "~/pages/api/organization/GetOrg";
|
|||||||
import getWorkspaceInfo from "~/pages/api/workspace/getWorkspaceInfo";
|
import getWorkspaceInfo from "~/pages/api/workspace/getWorkspaceInfo";
|
||||||
|
|
||||||
export default function NavHeader({ pageName, isProjectRelated }) {
|
export default function NavHeader({ pageName, isProjectRelated }) {
|
||||||
const [orgName, setOrgName] = useState("");
|
const [orgName, setOrgName] = useState("");
|
||||||
const [workspaceName, setWorkspaceName] = useState("");
|
const [workspaceName, setWorkspaceName] = useState("");
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
let org = await getOrganization({
|
let org = await getOrganization({
|
||||||
orgId: localStorage.getItem("orgData.id"),
|
orgId: localStorage.getItem("orgData.id"),
|
||||||
});
|
});
|
||||||
setOrgName(org.name);
|
setOrgName(org.name);
|
||||||
let workspace = await getWorkspaceInfo({
|
let workspace = await getWorkspaceInfo({
|
||||||
workspaceId: router.query.id,
|
workspaceId: router.query.id,
|
||||||
});
|
});
|
||||||
setWorkspaceName(workspace.name);
|
setWorkspaceName(workspace.name);
|
||||||
})();
|
})();
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pt-20 ml-6 flex flex-row items-center">
|
<div className="pt-20 ml-6 flex flex-row items-center">
|
||||||
<div className="bg-primary-900 h-6 w-6 rounded-md flex items-center justify-center text-mineshaft-100 mr-2">
|
<div className="bg-primary-900 h-6 w-6 rounded-md flex items-center justify-center text-mineshaft-100 mr-2">
|
||||||
{orgName?.charAt(0)}
|
{orgName?.charAt(0)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-primary text-sm font-semibold">{orgName}</div>
|
<div className="text-primary text-sm font-semibold">{orgName}</div>
|
||||||
{isProjectRelated && (
|
{isProjectRelated && (
|
||||||
<>
|
<>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faAngleRight}
|
icon={faAngleRight}
|
||||||
className="ml-3 text-sm text-gray-400 mr-3"
|
className="ml-3 text-sm text-gray-400 mr-3"
|
||||||
/>
|
/>
|
||||||
<div className="font-semibold text-primary text-sm">
|
<div className="font-semibold text-primary text-sm">
|
||||||
{workspaceName}
|
{workspaceName}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faAngleRight}
|
icon={faAngleRight}
|
||||||
className="ml-3 text-sm text-gray-400 mr-3"
|
className="ml-3 text-sm text-gray-400 mr-3"
|
||||||
/>
|
/>
|
||||||
<div className="text-gray-400 text-sm">{pageName}</div>
|
<div className="text-gray-400 text-sm">{pageName}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,27 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import token from "~/pages/api/auth/Token";
|
import token from "~/pages/api/auth/Token";
|
||||||
|
|
||||||
import { PATH } from "../../const";
|
|
||||||
|
|
||||||
export default class SecurityClient {
|
export default class SecurityClient {
|
||||||
static authOrigins = [PATH];
|
static authOrigins = [PATH];
|
||||||
static #token = "";
|
static #token = "";
|
||||||
|
|
||||||
contructor() {}
|
contructor() {}
|
||||||
|
|
||||||
static setToken(token) {
|
static setToken(token) {
|
||||||
this.#token = token;
|
this.#token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fetchCall(resource, options) {
|
static async fetchCall(resource, options) {
|
||||||
let req = new Request(resource, options);
|
let req = new Request(resource, options);
|
||||||
const destOrigin = new URL(req.url).origin;
|
const destOrigin = new URL(req.url).origin;
|
||||||
|
|
||||||
if (this.#token == "") {
|
if (this.#token == "") {
|
||||||
this.setToken(await token());
|
this.setToken(await token());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.#token && this.authOrigins.includes(destOrigin)) {
|
if (this.#token && this.authOrigins.includes(destOrigin)) {
|
||||||
req.headers.set("Authorization", "Bearer " + this.#token);
|
req.headers.set("Authorization", "Bearer " + this.#token);
|
||||||
return fetch(req);
|
return fetch(req);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import getOrganizations from "~/pages/api/organization/getOrgs";
|
|||||||
import getOrganizationUserProjects from "~/pages/api/organization/GetOrgUserProjects";
|
import getOrganizationUserProjects from "~/pages/api/organization/GetOrgUserProjects";
|
||||||
|
|
||||||
import { initPostHog } from "../analytics/posthog";
|
import { initPostHog } from "../analytics/posthog";
|
||||||
import { ENV } from "./config";
|
|
||||||
import pushKeys from "./secrets/pushKeys";
|
import pushKeys from "./secrets/pushKeys";
|
||||||
|
import { ENV } from "./config";
|
||||||
import SecurityClient from "./SecurityClient";
|
import SecurityClient from "./SecurityClient";
|
||||||
|
|
||||||
const nacl = require("tweetnacl");
|
const nacl = require("tweetnacl");
|
||||||
@@ -24,184 +24,158 @@ const client = new jsrp.client();
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const attemptLogin = async (
|
const attemptLogin = async (
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
setErrorLogin,
|
setErrorLogin,
|
||||||
router,
|
router,
|
||||||
isSignUp,
|
isSignUp,
|
||||||
isLogin
|
isLogin
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
let userWorkspace, userOrg;
|
let userWorkspace, userOrg;
|
||||||
client.init(
|
client.init(
|
||||||
{
|
{
|
||||||
username: email,
|
username: email,
|
||||||
password: password,
|
password: password,
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
const clientPublicKey = client.getPublicKey();
|
const clientPublicKey = client.getPublicKey();
|
||||||
|
|
||||||
let serverPublicKey, salt;
|
let serverPublicKey, salt;
|
||||||
try {
|
try {
|
||||||
const res = await login1(email, clientPublicKey);
|
const res = await login1(email, clientPublicKey);
|
||||||
res = await res.json();
|
res = await res.json();
|
||||||
serverPublicKey = res.serverPublicKey;
|
serverPublicKey = res.serverPublicKey;
|
||||||
salt = res.salt;
|
salt = res.salt;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setErrorLogin(true);
|
setErrorLogin(true);
|
||||||
console.log("Wrong password", err);
|
console.log("Wrong password", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
let response;
|
let response;
|
||||||
try {
|
try {
|
||||||
client.setSalt(salt);
|
client.setSalt(salt);
|
||||||
client.setServerPublicKey(serverPublicKey);
|
client.setServerPublicKey(serverPublicKey);
|
||||||
const clientProof = client.getProof(); // called M1
|
const clientProof = client.getProof(); // called M1
|
||||||
response = await login2(email, clientProof);
|
response = await login2(email, clientProof);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setErrorLogin(true);
|
setErrorLogin(true);
|
||||||
console.log("Password verification failed");
|
console.log("Password verification failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
// if everything works, go the main dashboard page.
|
// if everything works, go the main dashboard page.
|
||||||
try {
|
try {
|
||||||
if (response.status == "200") {
|
if (response.status == "200") {
|
||||||
response = await response.json();
|
response = await response.json();
|
||||||
SecurityClient.setToken(response["token"]);
|
SecurityClient.setToken(response["token"]);
|
||||||
const publicKey = response["publicKey"];
|
const publicKey = response["publicKey"];
|
||||||
const encryptedPrivateKey =
|
const encryptedPrivateKey = response["encryptedPrivateKey"];
|
||||||
response["encryptedPrivateKey"];
|
const iv = response["iv"];
|
||||||
const iv = response["iv"];
|
const tag = response["tag"];
|
||||||
const tag = response["tag"];
|
|
||||||
|
|
||||||
const PRIVATE_KEY = Aes256Gcm.decrypt(
|
const PRIVATE_KEY = Aes256Gcm.decrypt(
|
||||||
encryptedPrivateKey,
|
encryptedPrivateKey,
|
||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
password
|
password
|
||||||
.slice(0, 32)
|
.slice(0, 32)
|
||||||
.padStart(
|
.padStart(
|
||||||
32 +
|
32 +
|
||||||
(password.slice(0, 32).length -
|
(password.slice(0, 32).length - new Blob([password]).size),
|
||||||
new Blob([password]).size),
|
"0"
|
||||||
"0"
|
)
|
||||||
)
|
);
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
localStorage.setItem("publicKey", publicKey);
|
localStorage.setItem("publicKey", publicKey);
|
||||||
localStorage.setItem(
|
localStorage.setItem("encryptedPrivateKey", encryptedPrivateKey);
|
||||||
"encryptedPrivateKey",
|
localStorage.setItem("iv", iv);
|
||||||
encryptedPrivateKey
|
localStorage.setItem("tag", tag);
|
||||||
);
|
localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY);
|
||||||
localStorage.setItem("iv", iv);
|
} catch (err) {
|
||||||
localStorage.setItem("tag", tag);
|
setErrorLogin(true);
|
||||||
localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY);
|
console.error(
|
||||||
} catch (err) {
|
"Unable to send the tokens in local storage:" + err.message
|
||||||
setErrorLogin(true);
|
);
|
||||||
console.error(
|
}
|
||||||
"Unable to send the tokens in local storage:" +
|
} else {
|
||||||
err.message
|
setErrorLogin(true);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setErrorLogin(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
const userOrgs = await getOrganizations();
|
const userOrgs = await getOrganizations();
|
||||||
const userOrgsData = userOrgs.map((org) => org._id);
|
const userOrgsData = userOrgs.map((org) => org._id);
|
||||||
|
|
||||||
let orgToLogin;
|
let orgToLogin;
|
||||||
if (
|
if (userOrgsData.includes(localStorage.getItem("orgData.id"))) {
|
||||||
userOrgsData.includes(
|
orgToLogin = localStorage.getItem("orgData.id");
|
||||||
localStorage.getItem("orgData.id")
|
} else {
|
||||||
)
|
orgToLogin = userOrgsData[0];
|
||||||
) {
|
localStorage.setItem("orgData.id", orgToLogin);
|
||||||
orgToLogin = localStorage.getItem("orgData.id");
|
}
|
||||||
} else {
|
|
||||||
orgToLogin = userOrgsData[0];
|
|
||||||
localStorage.setItem("orgData.id", orgToLogin);
|
|
||||||
}
|
|
||||||
|
|
||||||
let orgUserProjects = await getOrganizationUserProjects({
|
let orgUserProjects = await getOrganizationUserProjects({
|
||||||
orgId: orgToLogin,
|
orgId: orgToLogin,
|
||||||
});
|
});
|
||||||
|
|
||||||
orgUserProjects = orgUserProjects?.map(
|
orgUserProjects = orgUserProjects?.map((project) => project._id);
|
||||||
(project) => project._id
|
let projectToLogin;
|
||||||
);
|
if (
|
||||||
let projectToLogin;
|
orgUserProjects.includes(localStorage.getItem("projectData.id"))
|
||||||
if (
|
) {
|
||||||
orgUserProjects.includes(
|
projectToLogin = localStorage.getItem("projectData.id");
|
||||||
localStorage.getItem("projectData.id")
|
} else {
|
||||||
)
|
try {
|
||||||
) {
|
projectToLogin = orgUserProjects[0];
|
||||||
projectToLogin = localStorage.getItem("projectData.id");
|
localStorage.setItem("projectData.id", projectToLogin);
|
||||||
} else {
|
} catch (error) {
|
||||||
try {
|
console.log("ERROR: User likely has no projects. ", error);
|
||||||
projectToLogin = orgUserProjects[0];
|
}
|
||||||
localStorage.setItem(
|
}
|
||||||
"projectData.id",
|
|
||||||
projectToLogin
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.log(
|
|
||||||
"ERROR: User likely has no projects. ",
|
|
||||||
error
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If user is logging in for the first time, add the example keys
|
// If user is logging in for the first time, add the example keys
|
||||||
if (isSignUp) {
|
if (isSignUp) {
|
||||||
await pushKeys(
|
await pushKeys(
|
||||||
{
|
{
|
||||||
DATABASE_URL: [
|
DATABASE_URL: [
|
||||||
"mongodb+srv://this_is:[email protected]",
|
"mongodb+srv://this_is:[email protected]",
|
||||||
"personal",
|
"personal",
|
||||||
],
|
],
|
||||||
TWILIO_AUTH_TOKEN: [
|
TWILIO_AUTH_TOKEN: [
|
||||||
"hgSIwDAKvz8PJfkj6xkzYqzGmAP3HLuG",
|
"hgSIwDAKvz8PJfkj6xkzYqzGmAP3HLuG",
|
||||||
"shared",
|
"shared",
|
||||||
],
|
],
|
||||||
WEBSITE_URL: [
|
WEBSITE_URL: ["http://localhost:3000", "shared"],
|
||||||
"http://localhost:3000",
|
STRIPE_SECRET_KEY: ["sk_test_7348oyho4hfq398HIUOH78", "shared"],
|
||||||
"shared",
|
},
|
||||||
],
|
projectToLogin,
|
||||||
STRIPE_SECRET_KEY: [
|
"Development"
|
||||||
"sk_test_7348oyho4hfq398HIUOH78",
|
);
|
||||||
"shared",
|
}
|
||||||
],
|
try {
|
||||||
},
|
if (email) {
|
||||||
projectToLogin,
|
if (ENV == "production") {
|
||||||
"Development"
|
const posthog = initPostHog();
|
||||||
);
|
posthog.identify(email);
|
||||||
}
|
posthog.capture("User Logged In");
|
||||||
try {
|
}
|
||||||
if (email) {
|
}
|
||||||
if (ENV == "production") {
|
} catch (error) {
|
||||||
const posthog = initPostHog();
|
console.log("posthog", error);
|
||||||
posthog.identify(email);
|
}
|
||||||
posthog.capture("User Logged In");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log("posthog", error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLogin) {
|
if (isLogin) {
|
||||||
router.push("/dashboard/");
|
router.push("/dashboard/");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setErrorLogin(true);
|
setErrorLogin(true);
|
||||||
console.log("Login response not available");
|
console.log("Login response not available");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Something went wrong during authentication");
|
console.log("Something went wrong during authentication");
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default attemptLogin;
|
export default attemptLogin;
|
||||||
|
|||||||
@@ -7,59 +7,59 @@
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const passwordCheck = (
|
const passwordCheck = (
|
||||||
password,
|
password,
|
||||||
setPasswordErrorLength,
|
setPasswordErrorLength,
|
||||||
setPasswordErrorNumber,
|
setPasswordErrorNumber,
|
||||||
setPasswordErrorLowerCase,
|
setPasswordErrorLowerCase,
|
||||||
currentErrorCheck
|
currentErrorCheck
|
||||||
) => {
|
) => {
|
||||||
let errorCheck = currentErrorCheck;
|
let errorCheck = currentErrorCheck;
|
||||||
if (!password || password.length < 14) {
|
if (!password || password.length < 14) {
|
||||||
setPasswordErrorLength(true);
|
setPasswordErrorLength(true);
|
||||||
errorCheck = true;
|
errorCheck = true;
|
||||||
} else {
|
} else {
|
||||||
setPasswordErrorLength(false);
|
setPasswordErrorLength(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!/\d/.test(password)) {
|
if (!/\d/.test(password)) {
|
||||||
setPasswordErrorNumber(true);
|
setPasswordErrorNumber(true);
|
||||||
errorCheck = true;
|
errorCheck = true;
|
||||||
} else {
|
} else {
|
||||||
setPasswordErrorNumber(false);
|
setPasswordErrorNumber(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!/[a-z]/.test(password)) {
|
if (!/[a-z]/.test(password)) {
|
||||||
setPasswordErrorLowerCase(true);
|
setPasswordErrorLowerCase(true);
|
||||||
errorCheck = true;
|
errorCheck = true;
|
||||||
// } else if (/(.)(?:(?!\1).){1,2}/.test(password)) {
|
// } else if (/(.)(?:(?!\1).){1,2}/.test(password)) {
|
||||||
// console.log(111)
|
// console.log(111)
|
||||||
// setPasswordError(true);
|
// setPasswordError(true);
|
||||||
// setPasswordErrorMessage("Password should not contain repeating characters.");
|
// setPasswordErrorMessage("Password should not contain repeating characters.");
|
||||||
// errorCheck = true;
|
// errorCheck = true;
|
||||||
// } else if (RegExp(`[${email}]`).test(password)) {
|
// } else if (RegExp(`[${email}]`).test(password)) {
|
||||||
// console.log(222)
|
// console.log(222)
|
||||||
// setPasswordError(true);
|
// setPasswordError(true);
|
||||||
// setPasswordErrorMessage("Password should not contain your email.");
|
// setPasswordErrorMessage("Password should not contain your email.");
|
||||||
// errorCheck = true;
|
// errorCheck = true;
|
||||||
} else {
|
} else {
|
||||||
setPasswordErrorLowerCase(false);
|
setPasswordErrorLowerCase(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (!/[A-Z]/.test(password)) {
|
// if (!/[A-Z]/.test(password)) {
|
||||||
// setPasswordErrorUpperCase(true);
|
// setPasswordErrorUpperCase(true);
|
||||||
// errorCheck = true;
|
// errorCheck = true;
|
||||||
// } else {
|
// } else {
|
||||||
// setPasswordErrorUpperCase(false);
|
// setPasswordErrorUpperCase(false);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// if (!/(?=.*[!@#$%^&*])/.test(password)) {
|
// if (!/(?=.*[!@#$%^&*])/.test(password)) {
|
||||||
// setPasswordErrorSpecialChar(true);
|
// setPasswordErrorSpecialChar(true);
|
||||||
// // "Please add at least 1 special character (*, !, #, %)."
|
// // "Please add at least 1 special character (*, !, #, %)."
|
||||||
// errorCheck = true;
|
// errorCheck = true;
|
||||||
// } else {
|
// } else {
|
||||||
// setPasswordErrorSpecialChar(false);
|
// setPasswordErrorSpecialChar(false);
|
||||||
// }
|
// }
|
||||||
return errorCheck;
|
return errorCheck;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default passwordCheck;
|
export default passwordCheck;
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
const ENV = process.env.NEXT_PUBLIC_ENV! || 'development'; // investigate
|
const ENV = process.env.NEXT_PUBLIC_ENV! || "development"; // investigate
|
||||||
const POSTHOG_API_KEY = process.env.NEXT_PUBLIC_POSTHOG_API_KEY!;
|
const POSTHOG_API_KEY = process.env.NEXT_PUBLIC_POSTHOG_API_KEY!;
|
||||||
const POSTHOG_HOST = process.env.NEXT_PUBLIC_POSTHOG_HOST! || 'https://app.posthog.com';
|
const POSTHOG_HOST =
|
||||||
|
process.env.NEXT_PUBLIC_POSTHOG_HOST! || "https://app.posthog.com";
|
||||||
const STRIPE_PRODUCT_PRO = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_PRO!;
|
const STRIPE_PRODUCT_PRO = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_PRO!;
|
||||||
const STRIPE_PRODUCT_STARTER = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_STARTER!;
|
const STRIPE_PRODUCT_STARTER = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_STARTER!;
|
||||||
const TELEMETRY_ENABLED = (process.env.NEXT_PUBLIC_TELEMETRY_ENABLED! !== 'false');
|
const TELEMETRY_ENABLED =
|
||||||
|
process.env.NEXT_PUBLIC_TELEMETRY_ENABLED! !== "false";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ENV,
|
ENV,
|
||||||
POSTHOG_API_KEY,
|
POSTHOG_API_KEY,
|
||||||
POSTHOG_HOST,
|
POSTHOG_HOST,
|
||||||
STRIPE_PRODUCT_PRO,
|
STRIPE_PRODUCT_PRO,
|
||||||
STRIPE_PRODUCT_STARTER,
|
STRIPE_PRODUCT_STARTER,
|
||||||
TELEMETRY_ENABLED
|
TELEMETRY_ENABLED,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,51 +13,51 @@ const BLOCK_SIZE_BYTES = 16; // 128 bit
|
|||||||
* Provides easy encryption/decryption methods using AES 256 GCM.
|
* Provides easy encryption/decryption methods using AES 256 GCM.
|
||||||
*/
|
*/
|
||||||
class Aes256Gcm {
|
class Aes256Gcm {
|
||||||
/**
|
/**
|
||||||
* No need to run the constructor. The class only has static methods.
|
* No need to run the constructor. The class only has static methods.
|
||||||
*/
|
*/
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypts text with AES 256 GCM.
|
* Encrypts text with AES 256 GCM.
|
||||||
* @param {string} text - Cleartext to encode.
|
* @param {string} text - Cleartext to encode.
|
||||||
* @param {string} secret - Shared secret key, must be 32 bytes.
|
* @param {string} secret - Shared secret key, must be 32 bytes.
|
||||||
* @returns {object}
|
* @returns {object}
|
||||||
*/
|
*/
|
||||||
static encrypt(text, secret) {
|
static encrypt(text, secret) {
|
||||||
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES);
|
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES);
|
||||||
const cipher = crypto.createCipheriv(ALGORITHM, secret, iv);
|
const cipher = crypto.createCipheriv(ALGORITHM, secret, iv);
|
||||||
|
|
||||||
let ciphertext = cipher.update(text, "utf8", "base64");
|
let ciphertext = cipher.update(text, "utf8", "base64");
|
||||||
ciphertext += cipher.final("base64");
|
ciphertext += cipher.final("base64");
|
||||||
return {
|
return {
|
||||||
ciphertext,
|
ciphertext,
|
||||||
iv: iv.toString("base64"),
|
iv: iv.toString("base64"),
|
||||||
tag: cipher.getAuthTag().toString("base64"),
|
tag: cipher.getAuthTag().toString("base64"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypts AES 256 CGM encrypted text.
|
* Decrypts AES 256 CGM encrypted text.
|
||||||
* @param {string} ciphertext - Base64-encoded ciphertext.
|
* @param {string} ciphertext - Base64-encoded ciphertext.
|
||||||
* @param {string} iv - The base64-encoded initialization vector.
|
* @param {string} iv - The base64-encoded initialization vector.
|
||||||
* @param {string} tag - The base64-encoded authentication tag generated by getAuthTag().
|
* @param {string} tag - The base64-encoded authentication tag generated by getAuthTag().
|
||||||
* @param {string} secret - Shared secret key, must be 32 bytes.
|
* @param {string} secret - Shared secret key, must be 32 bytes.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
static decrypt(ciphertext, iv, tag, secret) {
|
static decrypt(ciphertext, iv, tag, secret) {
|
||||||
const decipher = crypto.createDecipheriv(
|
const decipher = crypto.createDecipheriv(
|
||||||
ALGORITHM,
|
ALGORITHM,
|
||||||
secret,
|
secret,
|
||||||
Buffer.from(iv, "base64")
|
Buffer.from(iv, "base64")
|
||||||
);
|
);
|
||||||
decipher.setAuthTag(Buffer.from(tag, "base64"));
|
decipher.setAuthTag(Buffer.from(tag, "base64"));
|
||||||
|
|
||||||
let cleartext = decipher.update(ciphertext, "base64", "utf8");
|
let cleartext = decipher.update(ciphertext, "base64", "utf8");
|
||||||
cleartext += decipher.final("utf8");
|
cleartext += decipher.final("utf8");
|
||||||
|
|
||||||
return cleartext;
|
return cleartext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Aes256Gcm;
|
module.exports = Aes256Gcm;
|
||||||
|
|||||||
@@ -19,109 +19,102 @@ const clientNewPassword = new jsrp.client();
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const changePassword = async (
|
const changePassword = async (
|
||||||
email,
|
email,
|
||||||
currentPassword,
|
currentPassword,
|
||||||
newPassword,
|
newPassword,
|
||||||
setCurrentPasswordError,
|
setCurrentPasswordError,
|
||||||
setPasswordChanged,
|
setPasswordChanged,
|
||||||
setCurrentPassword,
|
setCurrentPassword,
|
||||||
setNewPassword
|
setNewPassword
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
setPasswordChanged(false);
|
setPasswordChanged(false);
|
||||||
setCurrentPasswordError(false);
|
setCurrentPasswordError(false);
|
||||||
|
|
||||||
clientOldPassword.init(
|
clientOldPassword.init(
|
||||||
{
|
{
|
||||||
username: email,
|
username: email,
|
||||||
password: currentPassword,
|
password: currentPassword,
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
const clientPublicKey = clientOldPassword.getPublicKey();
|
const clientPublicKey = clientOldPassword.getPublicKey();
|
||||||
|
|
||||||
let serverPublicKey, salt;
|
let serverPublicKey, salt;
|
||||||
try {
|
try {
|
||||||
const res = await SRP1({
|
const res = await SRP1({
|
||||||
clientPublicKey: clientPublicKey,
|
clientPublicKey: clientPublicKey,
|
||||||
});
|
});
|
||||||
serverPublicKey = res.serverPublicKey;
|
serverPublicKey = res.serverPublicKey;
|
||||||
salt = res.salt;
|
salt = res.salt;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setCurrentPasswordError(true);
|
setCurrentPasswordError(true);
|
||||||
console.log("Wrong current password", err, 1);
|
console.log("Wrong current password", err, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
clientOldPassword.setSalt(salt);
|
clientOldPassword.setSalt(salt);
|
||||||
clientOldPassword.setServerPublicKey(serverPublicKey);
|
clientOldPassword.setServerPublicKey(serverPublicKey);
|
||||||
const clientProof = clientOldPassword.getProof(); // called M1
|
const clientProof = clientOldPassword.getProof(); // called M1
|
||||||
|
|
||||||
clientNewPassword.init(
|
clientNewPassword.init(
|
||||||
{
|
{
|
||||||
username: email,
|
username: email,
|
||||||
password: newPassword,
|
password: newPassword,
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
clientNewPassword.createVerifier(
|
clientNewPassword.createVerifier(async (err, result) => {
|
||||||
async (err, result) => {
|
// The Blob part here is needed to account for symbols that count as 2+ bytes (e.g., é, å, ø)
|
||||||
// The Blob part here is needed to account for symbols that count as 2+ bytes (e.g., é, å, ø)
|
let { ciphertext, iv, tag } = Aes256Gcm.encrypt(
|
||||||
let { ciphertext, iv, tag } = Aes256Gcm.encrypt(
|
localStorage.getItem("PRIVATE_KEY"),
|
||||||
localStorage.getItem("PRIVATE_KEY"),
|
newPassword
|
||||||
newPassword
|
.slice(0, 32)
|
||||||
.slice(0, 32)
|
.padStart(
|
||||||
.padStart(
|
32 +
|
||||||
32 +
|
(newPassword.slice(0, 32).length -
|
||||||
(newPassword.slice(0, 32)
|
new Blob([newPassword]).size),
|
||||||
.length -
|
"0"
|
||||||
new Blob([newPassword])
|
)
|
||||||
.size),
|
);
|
||||||
"0"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (ciphertext) {
|
if (ciphertext) {
|
||||||
localStorage.setItem(
|
localStorage.setItem("encryptedPrivateKey", ciphertext);
|
||||||
"encryptedPrivateKey",
|
localStorage.setItem("iv", iv);
|
||||||
ciphertext
|
localStorage.setItem("tag", tag);
|
||||||
);
|
|
||||||
localStorage.setItem("iv", iv);
|
|
||||||
localStorage.setItem("tag", tag);
|
|
||||||
|
|
||||||
let res;
|
let res;
|
||||||
try {
|
try {
|
||||||
res = await changePassword2({
|
res = await changePassword2({
|
||||||
encryptedPrivateKey: ciphertext,
|
encryptedPrivateKey: ciphertext,
|
||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
salt: result.salt,
|
salt: result.salt,
|
||||||
verifier: result.verifier,
|
verifier: result.verifier,
|
||||||
clientProof,
|
clientProof,
|
||||||
});
|
});
|
||||||
if (res.status == 400) {
|
if (res.status == 400) {
|
||||||
setCurrentPasswordError(true);
|
setCurrentPasswordError(true);
|
||||||
} else if (res.status == 200) {
|
} else if (res.status == 200) {
|
||||||
setPasswordChanged(true);
|
setPasswordChanged(true);
|
||||||
setCurrentPassword("");
|
setCurrentPassword("");
|
||||||
setNewPassword("");
|
setNewPassword("");
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setCurrentPasswordError(true);
|
setCurrentPasswordError(true);
|
||||||
console.log(err);
|
console.log(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
);
|
}
|
||||||
}
|
);
|
||||||
);
|
}
|
||||||
}
|
);
|
||||||
);
|
} catch (error) {
|
||||||
} catch (error) {
|
console.log(
|
||||||
console.log(
|
"Something went wrong during changing the password",
|
||||||
"Something went wrong during changing the password",
|
slat,
|
||||||
slat,
|
serverPublicKey
|
||||||
serverPublicKey
|
);
|
||||||
);
|
}
|
||||||
}
|
return true;
|
||||||
return true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default changePassword;
|
export default changePassword;
|
||||||
|
|||||||
@@ -14,18 +14,18 @@ const aes = require("./aes-256-gcm");
|
|||||||
* @returns {String} nonce - base64-encoded nonce
|
* @returns {String} nonce - base64-encoded nonce
|
||||||
*/
|
*/
|
||||||
const encryptAssymmetric = ({ plaintext, publicKey, privateKey }) => {
|
const encryptAssymmetric = ({ plaintext, publicKey, privateKey }) => {
|
||||||
const nonce = nacl.randomBytes(24);
|
const nonce = nacl.randomBytes(24);
|
||||||
const ciphertext = nacl.box(
|
const ciphertext = nacl.box(
|
||||||
nacl.util.decodeUTF8(plaintext),
|
nacl.util.decodeUTF8(plaintext),
|
||||||
nonce,
|
nonce,
|
||||||
nacl.util.decodeBase64(publicKey),
|
nacl.util.decodeBase64(publicKey),
|
||||||
nacl.util.decodeBase64(privateKey)
|
nacl.util.decodeBase64(privateKey)
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ciphertext: nacl.util.encodeBase64(ciphertext),
|
ciphertext: nacl.util.encodeBase64(ciphertext),
|
||||||
nonce: nacl.util.encodeBase64(nonce),
|
nonce: nacl.util.encodeBase64(nonce),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,14 +39,14 @@ const encryptAssymmetric = ({ plaintext, publicKey, privateKey }) => {
|
|||||||
* @param {String} plaintext - UTF8 plaintext
|
* @param {String} plaintext - UTF8 plaintext
|
||||||
*/
|
*/
|
||||||
const decryptAssymmetric = ({ ciphertext, nonce, publicKey, privateKey }) => {
|
const decryptAssymmetric = ({ ciphertext, nonce, publicKey, privateKey }) => {
|
||||||
const plaintext = nacl.box.open(
|
const plaintext = nacl.box.open(
|
||||||
nacl.util.decodeBase64(ciphertext),
|
nacl.util.decodeBase64(ciphertext),
|
||||||
nacl.util.decodeBase64(nonce),
|
nacl.util.decodeBase64(nonce),
|
||||||
nacl.util.decodeBase64(publicKey),
|
nacl.util.decodeBase64(publicKey),
|
||||||
nacl.util.decodeBase64(privateKey)
|
nacl.util.decodeBase64(privateKey)
|
||||||
);
|
);
|
||||||
|
|
||||||
return nacl.util.encodeUTF8(plaintext);
|
return nacl.util.encodeUTF8(plaintext);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,23 +56,23 @@ const decryptAssymmetric = ({ ciphertext, nonce, publicKey, privateKey }) => {
|
|||||||
* @param {String} obj.key - 16-byte hex key
|
* @param {String} obj.key - 16-byte hex key
|
||||||
*/
|
*/
|
||||||
const encryptSymmetric = ({ plaintext, key }) => {
|
const encryptSymmetric = ({ plaintext, key }) => {
|
||||||
let ciphertext, iv, tag;
|
let ciphertext, iv, tag;
|
||||||
try {
|
try {
|
||||||
const obj = aes.encrypt(plaintext, key);
|
const obj = aes.encrypt(plaintext, key);
|
||||||
ciphertext = obj.ciphertext;
|
ciphertext = obj.ciphertext;
|
||||||
iv = obj.iv;
|
iv = obj.iv;
|
||||||
tag = obj.tag;
|
tag = obj.tag;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("Failed to perform encryption");
|
console.log("Failed to perform encryption");
|
||||||
console.log(err);
|
console.log(err);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ciphertext,
|
ciphertext,
|
||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,20 +86,20 @@ const encryptSymmetric = ({ plaintext, key }) => {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
const decryptSymmetric = ({ ciphertext, iv, tag, key }) => {
|
const decryptSymmetric = ({ ciphertext, iv, tag, key }) => {
|
||||||
let plaintext;
|
let plaintext;
|
||||||
try {
|
try {
|
||||||
plaintext = aes.decrypt(ciphertext, iv, tag, key);
|
plaintext = aes.decrypt(ciphertext, iv, tag, key);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("Failed to perform decryption");
|
console.log("Failed to perform decryption");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return plaintext;
|
return plaintext;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
encryptAssymmetric,
|
encryptAssymmetric,
|
||||||
decryptAssymmetric,
|
decryptAssymmetric,
|
||||||
encryptSymmetric,
|
encryptSymmetric,
|
||||||
decryptSymmetric,
|
decryptSymmetric,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import issueBackupPrivateKey from "~/pages/api/auth/IssueBackupPrivateKey";
|
import issueBackupPrivateKey from "~/pages/api/auth/IssueBackupPrivateKey";
|
||||||
import SRP1 from "~/pages/api/auth/SRP1";
|
import SRP1 from "~/pages/api/auth/SRP1";
|
||||||
|
|
||||||
import Aes256Gcm from "./aes-256-gcm";
|
|
||||||
import generateBackupPDF from "../generateBackupPDF";
|
import generateBackupPDF from "../generateBackupPDF";
|
||||||
|
import Aes256Gcm from "./aes-256-gcm";
|
||||||
|
|
||||||
const nacl = require("tweetnacl");
|
const nacl = require("tweetnacl");
|
||||||
nacl.util = require("tweetnacl-util");
|
nacl.util = require("tweetnacl-util");
|
||||||
@@ -21,82 +21,78 @@ const crypto = require("crypto");
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const issueBackupKey = async ({
|
const issueBackupKey = async ({
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
personalName,
|
personalName,
|
||||||
setBackupKeyError,
|
setBackupKeyError,
|
||||||
setBackupKeyIssued,
|
setBackupKeyIssued,
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
setBackupKeyError(false);
|
setBackupKeyError(false);
|
||||||
setBackupKeyIssued(false);
|
setBackupKeyIssued(false);
|
||||||
clientPassword.init(
|
clientPassword.init(
|
||||||
{
|
{
|
||||||
username: email,
|
username: email,
|
||||||
password: password,
|
password: password,
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
const clientPublicKey = clientPassword.getPublicKey();
|
const clientPublicKey = clientPassword.getPublicKey();
|
||||||
|
|
||||||
let serverPublicKey, salt;
|
let serverPublicKey, salt;
|
||||||
try {
|
try {
|
||||||
const res = await SRP1({
|
const res = await SRP1({
|
||||||
clientPublicKey: clientPublicKey,
|
clientPublicKey: clientPublicKey,
|
||||||
});
|
});
|
||||||
serverPublicKey = res.serverPublicKey;
|
serverPublicKey = res.serverPublicKey;
|
||||||
salt = res.salt;
|
salt = res.salt;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setBackupKeyError(true);
|
setBackupKeyError(true);
|
||||||
console.log("Wrong current password", err, 1);
|
console.log("Wrong current password", err, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
clientPassword.setSalt(salt);
|
clientPassword.setSalt(salt);
|
||||||
clientPassword.setServerPublicKey(serverPublicKey);
|
clientPassword.setServerPublicKey(serverPublicKey);
|
||||||
const clientProof = clientPassword.getProof(); // called M1
|
const clientProof = clientPassword.getProof(); // called M1
|
||||||
|
|
||||||
const generatedKey = crypto.randomBytes(16).toString("hex");
|
const generatedKey = crypto.randomBytes(16).toString("hex");
|
||||||
|
|
||||||
clientKey.init(
|
clientKey.init(
|
||||||
{
|
{
|
||||||
username: email,
|
username: email,
|
||||||
password: generatedKey,
|
password: generatedKey,
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
clientKey.createVerifier(async (err, result) => {
|
clientKey.createVerifier(async (err, result) => {
|
||||||
let { ciphertext, iv, tag } = Aes256Gcm.encrypt(
|
let { ciphertext, iv, tag } = Aes256Gcm.encrypt(
|
||||||
localStorage.getItem("PRIVATE_KEY"),
|
localStorage.getItem("PRIVATE_KEY"),
|
||||||
generatedKey
|
generatedKey
|
||||||
);
|
);
|
||||||
|
|
||||||
const res = await issueBackupPrivateKey({
|
const res = await issueBackupPrivateKey({
|
||||||
encryptedPrivateKey: ciphertext,
|
encryptedPrivateKey: ciphertext,
|
||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
salt: result.salt,
|
salt: result.salt,
|
||||||
verifier: result.verifier,
|
verifier: result.verifier,
|
||||||
clientProof,
|
clientProof,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status == 400) {
|
if (res.status == 400) {
|
||||||
setBackupKeyError(true);
|
setBackupKeyError(true);
|
||||||
} else if (res.status == 200) {
|
} else if (res.status == 200) {
|
||||||
generateBackupPDF(
|
generateBackupPDF(personalName, email, generatedKey);
|
||||||
personalName,
|
setBackupKeyIssued(true);
|
||||||
email,
|
}
|
||||||
generatedKey
|
});
|
||||||
);
|
}
|
||||||
setBackupKeyIssued(true);
|
);
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
}
|
} catch (error) {
|
||||||
);
|
setBackupKeyError(true);
|
||||||
}
|
console.log("Failed to issue a backup key");
|
||||||
);
|
}
|
||||||
} catch (error) {
|
return true;
|
||||||
setBackupKeyError(true);
|
|
||||||
console.log("Failed to issue a backup key");
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default issueBackupKey;
|
export default issueBackupKey;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const LINE =
|
const LINE =
|
||||||
/(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;
|
/(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return text that is the buffer parsed
|
* Return text that is the buffer parsed
|
||||||
@@ -7,41 +7,41 @@ const LINE =
|
|||||||
* @returns {String} text - text of buffer
|
* @returns {String} text - text of buffer
|
||||||
*/
|
*/
|
||||||
function parse(src) {
|
function parse(src) {
|
||||||
const obj = {};
|
const obj = {};
|
||||||
|
|
||||||
// Convert buffer to string
|
// Convert buffer to string
|
||||||
let lines = src.toString();
|
let lines = src.toString();
|
||||||
|
|
||||||
// Convert line breaks to same format
|
// Convert line breaks to same format
|
||||||
lines = lines.replace(/\r\n?/gm, "\n");
|
lines = lines.replace(/\r\n?/gm, "\n");
|
||||||
|
|
||||||
let match;
|
let match;
|
||||||
while ((match = LINE.exec(lines)) != null) {
|
while ((match = LINE.exec(lines)) != null) {
|
||||||
const key = match[1];
|
const key = match[1];
|
||||||
|
|
||||||
// Default undefined or null to empty string
|
// Default undefined or null to empty string
|
||||||
let value = match[2] || "";
|
let value = match[2] || "";
|
||||||
|
|
||||||
// Remove whitespace
|
// Remove whitespace
|
||||||
value = value.trim();
|
value = value.trim();
|
||||||
|
|
||||||
// Check if double quoted
|
// Check if double quoted
|
||||||
const maybeQuote = value[0];
|
const maybeQuote = value[0];
|
||||||
|
|
||||||
// Remove surrounding quotes
|
// Remove surrounding quotes
|
||||||
value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2");
|
value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2");
|
||||||
|
|
||||||
// Expand newlines if double quoted
|
// Expand newlines if double quoted
|
||||||
if (maybeQuote === '"') {
|
if (maybeQuote === '"') {
|
||||||
value = value.replace(/\\n/g, "\n");
|
value = value.replace(/\\n/g, "\n");
|
||||||
value = value.replace(/\\r/g, "\r");
|
value = value.replace(/\\r/g, "\r");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to object
|
// Add to object
|
||||||
obj[key] = value;
|
obj[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default parse;
|
export default parse;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -3,23 +3,23 @@
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const guidGenerator = () => {
|
const guidGenerator = () => {
|
||||||
var S4 = function () {
|
var S4 = function () {
|
||||||
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
|
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
S4() +
|
S4() +
|
||||||
S4() +
|
S4() +
|
||||||
"-" +
|
"-" +
|
||||||
S4() +
|
S4() +
|
||||||
"-" +
|
"-" +
|
||||||
S4() +
|
S4() +
|
||||||
"-" +
|
"-" +
|
||||||
S4() +
|
S4() +
|
||||||
"-" +
|
"-" +
|
||||||
S4() +
|
S4() +
|
||||||
S4() +
|
S4() +
|
||||||
S4()
|
S4()
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default guidGenerator;
|
export default guidGenerator;
|
||||||
|
|||||||
@@ -3,103 +3,101 @@ import getSecrets from "~/pages/api/files/GetSecrets";
|
|||||||
import guidGenerator from "../randomId";
|
import guidGenerator from "../randomId";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
decryptAssymmetric,
|
decryptAssymmetric,
|
||||||
decryptSymmetric,
|
decryptSymmetric,
|
||||||
} = require("../cryptography/crypto");
|
} = require("../cryptography/crypto");
|
||||||
const nacl = require("tweetnacl");
|
const nacl = require("tweetnacl");
|
||||||
nacl.util = require("tweetnacl-util");
|
nacl.util = require("tweetnacl-util");
|
||||||
|
|
||||||
const envMapping = {
|
const envMapping = {
|
||||||
Development: "dev",
|
Development: "dev",
|
||||||
Staging: "staging",
|
Staging: "staging",
|
||||||
Production: "prod",
|
Production: "prod",
|
||||||
Testing: "test",
|
Testing: "test",
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSecretsForProject = async ({
|
const getSecretsForProject = async ({
|
||||||
env,
|
env,
|
||||||
setFileState,
|
setFileState,
|
||||||
setIsKeyAvailable,
|
setIsKeyAvailable,
|
||||||
setData,
|
setData,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
let file;
|
let file;
|
||||||
try {
|
try {
|
||||||
file = await getSecrets(workspaceId, envMapping[env]);
|
file = await getSecrets(workspaceId, envMapping[env]);
|
||||||
|
|
||||||
setFileState(file);
|
setFileState(file);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("ERROR: Not able to access the latest file");
|
console.log("ERROR: Not able to access the latest file");
|
||||||
}
|
}
|
||||||
// This is called isKeyAvilable but what it really means is if a person is able to create new key pairs
|
// This is called isKeyAvilable but what it really means is if a person is able to create new key pairs
|
||||||
setIsKeyAvailable(
|
setIsKeyAvailable(
|
||||||
!file.key ? (file.secrets.length == 0 ? true : false) : true
|
!file.key ? (file.secrets.length == 0 ? true : false) : true
|
||||||
);
|
);
|
||||||
|
|
||||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
||||||
|
|
||||||
let tempFileState = [];
|
let tempFileState = [];
|
||||||
if (file.key) {
|
if (file.key) {
|
||||||
// assymmetrically decrypt symmetric key with local private key
|
// assymmetrically decrypt symmetric key with local private key
|
||||||
const key = decryptAssymmetric({
|
const key = decryptAssymmetric({
|
||||||
ciphertext: file.key.encryptedKey,
|
ciphertext: file.key.encryptedKey,
|
||||||
nonce: file.key.nonce,
|
nonce: file.key.nonce,
|
||||||
publicKey: file.key.sender.publicKey,
|
publicKey: file.key.sender.publicKey,
|
||||||
privateKey: PRIVATE_KEY,
|
privateKey: PRIVATE_KEY,
|
||||||
});
|
});
|
||||||
|
|
||||||
file.secrets.map((secretPair) => {
|
file.secrets.map((secretPair) => {
|
||||||
// decrypt .env file with symmetric key
|
// decrypt .env file with symmetric key
|
||||||
const plainTextKey = decryptSymmetric({
|
const plainTextKey = decryptSymmetric({
|
||||||
ciphertext: secretPair.secretKey.ciphertext,
|
ciphertext: secretPair.secretKey.ciphertext,
|
||||||
iv: secretPair.secretKey.iv,
|
iv: secretPair.secretKey.iv,
|
||||||
tag: secretPair.secretKey.tag,
|
tag: secretPair.secretKey.tag,
|
||||||
key,
|
key,
|
||||||
});
|
});
|
||||||
|
|
||||||
const plainTextValue = decryptSymmetric({
|
const plainTextValue = decryptSymmetric({
|
||||||
ciphertext: secretPair.secretValue.ciphertext,
|
ciphertext: secretPair.secretValue.ciphertext,
|
||||||
iv: secretPair.secretValue.iv,
|
iv: secretPair.secretValue.iv,
|
||||||
tag: secretPair.secretValue.tag,
|
tag: secretPair.secretValue.tag,
|
||||||
key,
|
key,
|
||||||
});
|
});
|
||||||
tempFileState.push({
|
tempFileState.push({
|
||||||
key: plainTextKey,
|
key: plainTextKey,
|
||||||
value: plainTextValue,
|
value: plainTextValue,
|
||||||
type: secretPair.type,
|
type: secretPair.type,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setFileState(tempFileState);
|
setFileState(tempFileState);
|
||||||
|
|
||||||
setData(
|
setData(
|
||||||
tempFileState.map((line, index) => [
|
tempFileState.map((line, index) => [
|
||||||
guidGenerator(),
|
guidGenerator(),
|
||||||
index,
|
index,
|
||||||
line["key"],
|
line["key"],
|
||||||
line["value"],
|
line["value"],
|
||||||
line["type"],
|
line["type"],
|
||||||
])
|
])
|
||||||
// .sort((a, b) =>
|
// .sort((a, b) =>
|
||||||
// sortMethod == "alphabetical"
|
// sortMethod == "alphabetical"
|
||||||
// ? a[2].localeCompare(b[2])
|
// ? a[2].localeCompare(b[2])
|
||||||
// : b[2].localeCompare(a[2])
|
// : b[2].localeCompare(a[2])
|
||||||
// )
|
// )
|
||||||
);
|
);
|
||||||
return tempFileState.map((line, index) => [
|
return tempFileState.map((line, index) => [
|
||||||
guidGenerator(),
|
guidGenerator(),
|
||||||
index,
|
index,
|
||||||
line["key"],
|
line["key"],
|
||||||
line["value"],
|
line["value"],
|
||||||
line["type"],
|
line["type"],
|
||||||
]);
|
]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(
|
console.log("Something went wrong during accessing or decripting secrets.");
|
||||||
"Something went wrong during accessing or decripting secrets."
|
}
|
||||||
);
|
return true;
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getSecretsForProject;
|
export default getSecretsForProject;
|
||||||
|
|||||||
@@ -4,106 +4,103 @@ import getWorkspaceKeys from "~/pages/api/workspace/getWorkspaceKeys";
|
|||||||
|
|
||||||
const crypto = require("crypto");
|
const crypto = require("crypto");
|
||||||
const {
|
const {
|
||||||
decryptAssymmetric,
|
decryptAssymmetric,
|
||||||
decryptSymmetric,
|
decryptSymmetric,
|
||||||
encryptSymmetric,
|
encryptSymmetric,
|
||||||
encryptAssymmetric,
|
encryptAssymmetric,
|
||||||
} = require("../cryptography/crypto");
|
} = require("../cryptography/crypto");
|
||||||
const nacl = require("tweetnacl");
|
const nacl = require("tweetnacl");
|
||||||
nacl.util = require("tweetnacl-util");
|
nacl.util = require("tweetnacl-util");
|
||||||
|
|
||||||
const envMapping = {
|
const envMapping = {
|
||||||
Development: "dev",
|
Development: "dev",
|
||||||
Staging: "staging",
|
Staging: "staging",
|
||||||
Production: "prod",
|
Production: "prod",
|
||||||
Testing: "test",
|
Testing: "test",
|
||||||
};
|
};
|
||||||
|
|
||||||
const pushKeys = async (obj, workspaceId, env) => {
|
const pushKeys = async (obj, workspaceId, env) => {
|
||||||
let sharedKey = await getLatestFileKey(workspaceId);
|
let sharedKey = await getLatestFileKey(workspaceId);
|
||||||
|
|
||||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
||||||
|
|
||||||
let randomBytes;
|
let randomBytes;
|
||||||
if (Object.keys(sharedKey).length > 0) {
|
if (Object.keys(sharedKey).length > 0) {
|
||||||
// case: a (shared) key exists for the workspace
|
// case: a (shared) key exists for the workspace
|
||||||
randomBytes = decryptAssymmetric({
|
randomBytes = decryptAssymmetric({
|
||||||
ciphertext: sharedKey.latestKey.encryptedKey,
|
ciphertext: sharedKey.latestKey.encryptedKey,
|
||||||
nonce: sharedKey.latestKey.nonce,
|
nonce: sharedKey.latestKey.nonce,
|
||||||
publicKey: sharedKey.latestKey.sender.publicKey,
|
publicKey: sharedKey.latestKey.sender.publicKey,
|
||||||
privateKey: PRIVATE_KEY,
|
privateKey: PRIVATE_KEY,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// case: a (shared) key does not exist for the workspace
|
// case: a (shared) key does not exist for the workspace
|
||||||
randomBytes = crypto.randomBytes(16).toString("hex");
|
randomBytes = crypto.randomBytes(16).toString("hex");
|
||||||
}
|
}
|
||||||
|
|
||||||
const secrets = Object.keys(obj).map((key) => {
|
const secrets = Object.keys(obj).map((key) => {
|
||||||
// encrypt key
|
// encrypt key
|
||||||
const {
|
const {
|
||||||
ciphertext: ciphertextKey,
|
ciphertext: ciphertextKey,
|
||||||
iv: ivKey,
|
iv: ivKey,
|
||||||
tag: tagKey,
|
tag: tagKey,
|
||||||
} = encryptSymmetric({
|
} = encryptSymmetric({
|
||||||
plaintext: key,
|
plaintext: key,
|
||||||
key: randomBytes,
|
key: randomBytes,
|
||||||
});
|
});
|
||||||
|
|
||||||
// encrypt value
|
// encrypt value
|
||||||
const {
|
const {
|
||||||
ciphertext: ciphertextValue,
|
ciphertext: ciphertextValue,
|
||||||
iv: ivValue,
|
iv: ivValue,
|
||||||
tag: tagValue,
|
tag: tagValue,
|
||||||
} = encryptSymmetric({
|
} = encryptSymmetric({
|
||||||
plaintext: obj[key][0],
|
plaintext: obj[key][0],
|
||||||
key: randomBytes,
|
key: randomBytes,
|
||||||
});
|
});
|
||||||
|
|
||||||
const visibility = obj[key][1] != null ? obj[key][1] : "personal";
|
const visibility = obj[key][1] != null ? obj[key][1] : "personal";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ciphertextKey,
|
ciphertextKey,
|
||||||
ivKey,
|
ivKey,
|
||||||
tagKey,
|
tagKey,
|
||||||
hashKey: crypto.createHash("sha256").update(key).digest("hex"),
|
hashKey: crypto.createHash("sha256").update(key).digest("hex"),
|
||||||
ciphertextValue,
|
ciphertextValue,
|
||||||
ivValue,
|
ivValue,
|
||||||
tagValue,
|
tagValue,
|
||||||
hashValue: crypto
|
hashValue: crypto.createHash("sha256").update(obj[key][0]).digest("hex"),
|
||||||
.createHash("sha256")
|
type: visibility,
|
||||||
.update(obj[key][0])
|
};
|
||||||
.digest("hex"),
|
});
|
||||||
type: visibility,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// obtain public keys of all receivers (i.e. members in workspace)
|
// obtain public keys of all receivers (i.e. members in workspace)
|
||||||
const publicKeys = await getWorkspaceKeys({
|
const publicKeys = await getWorkspaceKeys({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
// assymmetrically encrypt key with each receiver public keys
|
// assymmetrically encrypt key with each receiver public keys
|
||||||
const keys = publicKeys.map((k) => {
|
const keys = publicKeys.map((k) => {
|
||||||
const { ciphertext, nonce } = encryptAssymmetric({
|
const { ciphertext, nonce } = encryptAssymmetric({
|
||||||
plaintext: randomBytes,
|
plaintext: randomBytes,
|
||||||
publicKey: k.publicKey,
|
publicKey: k.publicKey,
|
||||||
privateKey: PRIVATE_KEY,
|
privateKey: PRIVATE_KEY,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
encryptedKey: ciphertext,
|
encryptedKey: ciphertext,
|
||||||
nonce,
|
nonce,
|
||||||
userId: k.userId,
|
userId: k.userId,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// send payload
|
// send payload
|
||||||
await uploadSecrets({
|
await uploadSecrets({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
secrets,
|
secrets,
|
||||||
keys,
|
keys,
|
||||||
environment: envMapping[env],
|
environment: envMapping[env],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default pushKeys;
|
export default pushKeys;
|
||||||
|
|||||||
@@ -2,73 +2,73 @@ import publicKeyInfical from "~/pages/api/auth/publicKeyInfisical";
|
|||||||
import changeHerokuConfigVars from "~/pages/api/integrations/ChangeHerokuConfigVars";
|
import changeHerokuConfigVars from "~/pages/api/integrations/ChangeHerokuConfigVars";
|
||||||
|
|
||||||
const crypto = require("crypto");
|
const crypto = require("crypto");
|
||||||
const { encryptSymmetric, encryptAssymmetric } = require("../cryptography/crypto");
|
const {
|
||||||
|
encryptSymmetric,
|
||||||
|
encryptAssymmetric,
|
||||||
|
} = require("../cryptography/crypto");
|
||||||
const nacl = require("tweetnacl");
|
const nacl = require("tweetnacl");
|
||||||
nacl.util = require("tweetnacl-util");
|
nacl.util = require("tweetnacl-util");
|
||||||
|
|
||||||
const pushKeysIntegration = async ({ obj, integrationId }) => {
|
const pushKeysIntegration = async ({ obj, integrationId }) => {
|
||||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
||||||
|
|
||||||
let randomBytes = crypto.randomBytes(16).toString("hex");
|
let randomBytes = crypto.randomBytes(16).toString("hex");
|
||||||
|
|
||||||
const secrets = Object.keys(obj).map((key) => {
|
const secrets = Object.keys(obj).map((key) => {
|
||||||
// encrypt key
|
// encrypt key
|
||||||
const {
|
const {
|
||||||
ciphertext: ciphertextKey,
|
ciphertext: ciphertextKey,
|
||||||
iv: ivKey,
|
iv: ivKey,
|
||||||
tag: tagKey,
|
tag: tagKey,
|
||||||
} = encryptSymmetric({
|
} = encryptSymmetric({
|
||||||
plaintext: key,
|
plaintext: key,
|
||||||
key: randomBytes,
|
key: randomBytes,
|
||||||
});
|
});
|
||||||
|
|
||||||
// encrypt value
|
// encrypt value
|
||||||
const {
|
const {
|
||||||
ciphertext: ciphertextValue,
|
ciphertext: ciphertextValue,
|
||||||
iv: ivValue,
|
iv: ivValue,
|
||||||
tag: tagValue,
|
tag: tagValue,
|
||||||
} = encryptSymmetric({
|
} = encryptSymmetric({
|
||||||
plaintext: obj[key],
|
plaintext: obj[key],
|
||||||
key: randomBytes,
|
key: randomBytes,
|
||||||
});
|
});
|
||||||
|
|
||||||
const visibility = "shared";
|
const visibility = "shared";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ciphertextKey,
|
ciphertextKey,
|
||||||
ivKey,
|
ivKey,
|
||||||
tagKey,
|
tagKey,
|
||||||
hashKey: crypto.createHash("sha256").update(key).digest("hex"),
|
hashKey: crypto.createHash("sha256").update(key).digest("hex"),
|
||||||
ciphertextValue,
|
ciphertextValue,
|
||||||
ivValue,
|
ivValue,
|
||||||
tagValue,
|
tagValue,
|
||||||
hashValue: crypto
|
hashValue: crypto.createHash("sha256").update(obj[key]).digest("hex"),
|
||||||
.createHash("sha256")
|
type: visibility,
|
||||||
.update(obj[key])
|
};
|
||||||
.digest("hex"),
|
});
|
||||||
type: visibility,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// obtain public keys of all receivers (i.e. members in workspace)
|
// obtain public keys of all receivers (i.e. members in workspace)
|
||||||
let publicKeyInfisical = await publicKeyInfical();
|
let publicKeyInfisical = await publicKeyInfical();
|
||||||
|
|
||||||
publicKeyInfisical = (await publicKeyInfisical.json()).publicKey;
|
publicKeyInfisical = (await publicKeyInfisical.json()).publicKey;
|
||||||
|
|
||||||
// assymmetrically encrypt key with each receiver public keys
|
// assymmetrically encrypt key with each receiver public keys
|
||||||
|
|
||||||
const { ciphertext, nonce } = encryptAssymmetric({
|
const { ciphertext, nonce } = encryptAssymmetric({
|
||||||
plaintext: randomBytes,
|
plaintext: randomBytes,
|
||||||
publicKey: publicKeyInfisical,
|
publicKey: publicKeyInfisical,
|
||||||
privateKey: PRIVATE_KEY,
|
privateKey: PRIVATE_KEY,
|
||||||
});
|
});
|
||||||
|
|
||||||
const key = {
|
const key = {
|
||||||
encryptedKey: ciphertext,
|
encryptedKey: ciphertext,
|
||||||
nonce,
|
nonce,
|
||||||
};
|
};
|
||||||
|
|
||||||
changeHerokuConfigVars({ integrationId, key, secrets });
|
changeHerokuConfigVars({ integrationId, key, secrets });
|
||||||
};
|
};
|
||||||
|
|
||||||
export default pushKeysIntegration;
|
export default pushKeysIntegration;
|
||||||
|
|||||||
+16
-16
@@ -1,20 +1,20 @@
|
|||||||
export const PATH = process.env.NEXT_PUBLIC_WEBSITE_URL;
|
export const PATH = process.env.NEXT_PUBLIC_WEBSITE_URL;
|
||||||
|
|
||||||
export const publicPaths = [
|
export const publicPaths = [
|
||||||
`/`,
|
`/`,
|
||||||
// `/integrations`,
|
// `/integrations`,
|
||||||
`/signupinvite`,
|
`/signupinvite`,
|
||||||
`/pricing`,
|
`/pricing`,
|
||||||
`/signup`,
|
`/signup`,
|
||||||
`/login`,
|
`/login`,
|
||||||
`/blog`,
|
`/blog`,
|
||||||
`/docs`,
|
`/docs`,
|
||||||
`/changelog`,
|
`/changelog`,
|
||||||
`/security`,
|
`/security`,
|
||||||
`/scheduledemo`,
|
`/scheduledemo`,
|
||||||
`/blog/[slug]`,
|
`/blog/[slug]`,
|
||||||
`/faq`,
|
`/faq`,
|
||||||
`/privacy`,
|
`/privacy`,
|
||||||
`/terms`,
|
`/terms`,
|
||||||
`/subprocessors`,
|
`/subprocessors`,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
"~/utilities/*": [
|
"~/utilities/*": [
|
||||||
"components/utilities/*"
|
"components/utilities/*"
|
||||||
],
|
],
|
||||||
|
"~/*": [
|
||||||
|
"const"
|
||||||
|
],
|
||||||
"~/pages/*": [
|
"~/pages/*": [
|
||||||
"pages/*"
|
"pages/*"
|
||||||
],
|
],
|
||||||
|
|||||||
+48
-44
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
const ContentSecurityPolicy = `
|
const ContentSecurityPolicy = `
|
||||||
default-src ${process.env.NEXT_PUBLIC_WEBSITE_URL};
|
default-src ${process.env.NEXT_PUBLIC_WEBSITE_URL};
|
||||||
script-src ${process.env.NEXT_PUBLIC_WEBSITE_URL} https://app.posthog.com https://infisical.com https://assets.calendly.com/ https://js.stripe.com https://api.stripe.com 'unsafe-inline' 'unsafe-eval';
|
script-src ${
|
||||||
|
process.env.NEXT_PUBLIC_WEBSITE_URL
|
||||||
|
} https://app.posthog.com https://infisical.com https://assets.calendly.com/ https://js.stripe.com https://api.stripe.com 'unsafe-inline' 'unsafe-eval';
|
||||||
style-src 'self' https://rsms.me 'unsafe-inline';
|
style-src 'self' https://rsms.me 'unsafe-inline';
|
||||||
child-src https://infisical.com https://api.stripe.com;
|
child-src https://infisical.com https://api.stripe.com;
|
||||||
frame-src https://js.stripe.com/ https://api.stripe.com;
|
frame-src https://js.stripe.com/ https://api.stripe.com;
|
||||||
connect-src ws://${process.env.NEXT_PUBLIC_WEBSITE_URL?.split('//')[1]} ${process.env.NEXT_PUBLIC_WEBSITE_URL} https://api.github.com/repos/Infisical/infisical-cli https://api.heroku.com/ https://id.heroku.com/oauth/authorize https://id.heroku.com/oauth/token https://checkout.stripe.com https://app.posthog.com https://infisical.com https://api.stripe.com https://vitals.vercel-insights.com/v1/vitals;
|
connect-src ws://${process.env.NEXT_PUBLIC_WEBSITE_URL?.split("//")[1]} ${
|
||||||
|
process.env.NEXT_PUBLIC_WEBSITE_URL
|
||||||
|
} https://api.github.com/repos/Infisical/infisical-cli https://api.heroku.com/ https://id.heroku.com/oauth/authorize https://id.heroku.com/oauth/token https://checkout.stripe.com https://app.posthog.com https://infisical.com https://api.stripe.com https://vitals.vercel-insights.com/v1/vitals;
|
||||||
img-src 'self' https://*.stripe.com https://i.ytimg.com/ data:;
|
img-src 'self' https://*.stripe.com https://i.ytimg.com/ data:;
|
||||||
media-src;
|
media-src;
|
||||||
font-src 'self' https://maxcdn.bootstrapcdn.com https://rsms.me https://fonts.gstatic.com;
|
font-src 'self' https://maxcdn.bootstrapcdn.com https://rsms.me https://fonts.gstatic.com;
|
||||||
@@ -15,49 +19,49 @@ const ContentSecurityPolicy = `
|
|||||||
// You can choose which headers to add to the list
|
// You can choose which headers to add to the list
|
||||||
// after learning more below.
|
// after learning more below.
|
||||||
const securityHeaders = [
|
const securityHeaders = [
|
||||||
{
|
{
|
||||||
key: "X-DNS-Prefetch-Control",
|
key: "X-DNS-Prefetch-Control",
|
||||||
value: "on",
|
value: "on",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "Strict-Transport-Security",
|
key: "Strict-Transport-Security",
|
||||||
value: "max-age=63072000; includeSubDomains; preload",
|
value: "max-age=63072000; includeSubDomains; preload",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "X-XSS-Protection",
|
key: "X-XSS-Protection",
|
||||||
value: "1; mode=block",
|
value: "1; mode=block",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "X-Frame-Options",
|
key: "X-Frame-Options",
|
||||||
value: "SAMEORIGIN",
|
value: "SAMEORIGIN",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "Permissions-Policy",
|
key: "Permissions-Policy",
|
||||||
value: "camera=(), microphone=()",
|
value: "camera=(), microphone=()",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "X-Content-Type-Options",
|
key: "X-Content-Type-Options",
|
||||||
value: "nosniff",
|
value: "nosniff",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "Referrer-Policy",
|
key: "Referrer-Policy",
|
||||||
value: "strict-origin-when-cross-origin",
|
value: "strict-origin-when-cross-origin",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "Content-Security-Policy",
|
key: "Content-Security-Policy",
|
||||||
value: ContentSecurityPolicy.replace(/\s{2,}/g, " ").trim(),
|
value: ContentSecurityPolicy.replace(/\s{2,}/g, " ").trim(),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
output: 'standalone',
|
output: "standalone",
|
||||||
async headers() {
|
async headers() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
// Apply these headers to all routes in your application.
|
// Apply these headers to all routes in your application.
|
||||||
source: "/:path*",
|
source: "/:path*",
|
||||||
headers: securityHeaders,
|
headers: securityHeaders,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Generated
+4429
-28
File diff suppressed because it is too large
Load Diff
+63
-63
@@ -1,65 +1,65 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"start:docker": "next build && next start",
|
"start:docker": "next build && next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/css": "^11.10.0",
|
"@emotion/css": "^11.10.0",
|
||||||
"@emotion/server": "^11.10.0",
|
"@emotion/server": "^11.10.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.1.2",
|
"@fortawesome/fontawesome-svg-core": "^6.1.2",
|
||||||
"@fortawesome/free-brands-svg-icons": "^6.1.2",
|
"@fortawesome/free-brands-svg-icons": "^6.1.2",
|
||||||
"@fortawesome/free-regular-svg-icons": "^6.1.1",
|
"@fortawesome/free-regular-svg-icons": "^6.1.1",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.1.2",
|
"@fortawesome/free-solid-svg-icons": "^6.1.2",
|
||||||
"@fortawesome/react-fontawesome": "^0.1.19",
|
"@fortawesome/react-fontawesome": "^0.1.19",
|
||||||
"@headlessui/react": "^1.6.6",
|
"@headlessui/react": "^1.6.6",
|
||||||
"@heroicons/react": "^1.0.6",
|
"@heroicons/react": "^1.0.6",
|
||||||
"@reduxjs/toolkit": "^1.8.3",
|
"@reduxjs/toolkit": "^1.8.3",
|
||||||
"@stripe/react-stripe-js": "^1.10.0",
|
"@stripe/react-stripe-js": "^1.10.0",
|
||||||
"@stripe/stripe-js": "^1.35.0",
|
"@stripe/stripe-js": "^1.35.0",
|
||||||
"add": "^2.0.6",
|
"add": "^2.0.6",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
"axios-auth-refresh": "^3.3.3",
|
"axios-auth-refresh": "^3.3.3",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"cookies": "^0.8.0",
|
"cookies": "^0.8.0",
|
||||||
"fs": "^0.0.1-security",
|
"fs": "^0.0.1-security",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"http-proxy": "^1.18.1",
|
"http-proxy": "^1.18.1",
|
||||||
"jspdf": "^2.5.1",
|
"jspdf": "^2.5.1",
|
||||||
"jsrp": "^0.2.4",
|
"jsrp": "^0.2.4",
|
||||||
"markdown-it": "^13.0.1",
|
"markdown-it": "^13.0.1",
|
||||||
"next": "^12.2.5",
|
"next": "^12.2.5",
|
||||||
"posthog-js": "^1.34.0",
|
"posthog-js": "^1.34.0",
|
||||||
"query-string": "^7.1.1",
|
"query-string": "^7.1.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-beautiful-dnd": "^13.1.1",
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
"react-code-input": "^3.10.1",
|
"react-code-input": "^3.10.1",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-github-btn": "^1.4.0",
|
"react-github-btn": "^1.4.0",
|
||||||
"react-grid-layout": "^1.3.4",
|
"react-grid-layout": "^1.3.4",
|
||||||
"react-mailchimp-subscribe": "^2.1.3",
|
"react-mailchimp-subscribe": "^2.1.3",
|
||||||
"react-markdown": "^8.0.3",
|
"react-markdown": "^8.0.3",
|
||||||
"react-redux": "^8.0.2",
|
"react-redux": "^8.0.2",
|
||||||
"react-table": "^7.8.0",
|
"react-table": "^7.8.0",
|
||||||
"set-cookie-parser": "^2.5.1",
|
"set-cookie-parser": "^2.5.1",
|
||||||
"styled-components": "^5.3.5",
|
"styled-components": "^5.3.5",
|
||||||
"tweetnacl": "^1.0.3",
|
"tweetnacl": "^1.0.3",
|
||||||
"tweetnacl-util": "^0.15.1",
|
"tweetnacl-util": "^0.15.1",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"uuidv4": "^6.2.13"
|
"uuidv4": "^6.2.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/typography": "^0.5.4",
|
"@tailwindcss/typography": "^0.5.4",
|
||||||
"autoprefixer": "^10.4.7",
|
"autoprefixer": "^10.4.7",
|
||||||
"eslint": "^8.28.0",
|
"eslint": "^8.28.0",
|
||||||
"eslint-config-next": "^13.0.5",
|
"eslint-config-next": "^13.0.5",
|
||||||
"eslint-plugin-simple-import-sort": "^8.0.0",
|
"eslint-plugin-simple-import-sort": "^8.0.0",
|
||||||
"postcss": "^8.4.14",
|
"postcss": "^8.4.14",
|
||||||
"prettier": "2.7.1",
|
"prettier": "2.7.1",
|
||||||
"tailwindcss": "^3.1.4",
|
"tailwindcss": "^3.1.4",
|
||||||
"typescript": "^4.9.3"
|
"typescript": "^4.9.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-34
@@ -5,59 +5,58 @@ import { config } from "@fortawesome/fontawesome-svg-core";
|
|||||||
import { initPostHog } from "~/components/analytics/posthog";
|
import { initPostHog } from "~/components/analytics/posthog";
|
||||||
import Layout from "~/components/basic/layout";
|
import Layout from "~/components/basic/layout";
|
||||||
import RouteGuard from "~/components/RouteGuard";
|
import RouteGuard from "~/components/RouteGuard";
|
||||||
|
import { publicPaths } from "~/const";
|
||||||
import { ENV } from "~/utilities/config";
|
import { ENV } from "~/utilities/config";
|
||||||
|
|
||||||
import { publicPaths } from "../const.js";
|
|
||||||
|
|
||||||
import "@fortawesome/fontawesome-svg-core/styles.css";
|
import "@fortawesome/fontawesome-svg-core/styles.css";
|
||||||
import "../styles/globals.css";
|
import "../styles/globals.css";
|
||||||
|
|
||||||
config.autoAddCss = false;
|
config.autoAddCss = false;
|
||||||
|
|
||||||
const App = ({ Component, pageProps, ...appProps }) => {
|
const App = ({ Component, pageProps, ...appProps }) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const posthog = initPostHog();
|
const posthog = initPostHog();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Init for auto capturing
|
// Init for auto capturing
|
||||||
const posthog = initPostHog();
|
const posthog = initPostHog();
|
||||||
|
|
||||||
const handleRouteChange = () => {
|
const handleRouteChange = () => {
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
if (ENV == "production") {
|
if (ENV == "production") {
|
||||||
posthog.capture("$pageview");
|
posthog.capture("$pageview");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
router.events.on("routeChangeComplete", handleRouteChange);
|
router.events.on("routeChangeComplete", handleRouteChange);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
router.events.off("routeChangeComplete", handleRouteChange);
|
router.events.off("routeChangeComplete", handleRouteChange);
|
||||||
};
|
};
|
||||||
}, [router.events]);
|
}, [router.events]);
|
||||||
|
|
||||||
// If it's one of these routes, don't add the layout (e.g., these routes are external)
|
// If it's one of these routes, don't add the layout (e.g., these routes are external)
|
||||||
if (
|
if (
|
||||||
publicPaths.includes("/" + appProps.router.pathname.split("/")[1]) ||
|
publicPaths.includes("/" + appProps.router.pathname.split("/")[1]) ||
|
||||||
!Component.requireAuth
|
!Component.requireAuth
|
||||||
) {
|
) {
|
||||||
return <Component {...pageProps} />;
|
return <Component {...pageProps} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RouteGuard>
|
<RouteGuard>
|
||||||
<Layout>
|
<Layout>
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</Layout>
|
</Layout>
|
||||||
</RouteGuard>
|
</RouteGuard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
||||||
{
|
{
|
||||||
/* <Script
|
/* <Script
|
||||||
src="https://www.googletagmanager.com/gtag/js?id=G-DQ1XLJJGG1"
|
src="https://www.googletagmanager.com/gtag/js?id=G-DQ1XLJJGG1"
|
||||||
strategy="afterInteractive"
|
strategy="afterInteractive"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,40 +1,39 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the second step of the change password process (pake)
|
* This is the second step of the change password process (pake)
|
||||||
* @param {*} clientPublicKey
|
* @param {*} clientPublicKey
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const changePassword2 = ({
|
const changePassword2 = ({
|
||||||
encryptedPrivateKey,
|
encryptedPrivateKey,
|
||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
salt,
|
salt,
|
||||||
verifier,
|
verifier,
|
||||||
clientProof,
|
clientProof,
|
||||||
}) => {
|
}) => {
|
||||||
return SecurityClient.fetchCall(PATH + "/api/v1/password/change-password", {
|
return SecurityClient.fetchCall(PATH + "/api/v1/password/change-password", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
clientProof: clientProof,
|
clientProof: clientProof,
|
||||||
encryptedPrivateKey: encryptedPrivateKey,
|
encryptedPrivateKey: encryptedPrivateKey,
|
||||||
iv: iv,
|
iv: iv,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
salt: salt,
|
salt: salt,
|
||||||
verifier: verifier,
|
verifier: verifier,
|
||||||
}),
|
}),
|
||||||
}).then(async (res) => {
|
}).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to change the password");
|
console.log("Failed to change the password");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default changePassword2;
|
export default changePassword2;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient.js";
|
import SecurityClient from "~/utilities/SecurityClient.js";
|
||||||
|
|
||||||
import { PATH } from "../../../const.js";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is used to check if the user is authenticated.
|
* This function is used to check if the user is authenticated.
|
||||||
* To do that, we get their tokens from cookies, and verify if they are good.
|
* To do that, we get their tokens from cookies, and verify if they are good.
|
||||||
@@ -10,18 +9,18 @@ import { PATH } from "../../../const.js";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const checkAuth = async (req, res) => {
|
const checkAuth = async (req, res) => {
|
||||||
return SecurityClient.fetchCall(PATH + "/api/v1/auth/checkAuth", {
|
return SecurityClient.fetchCall(PATH + "/api/v1/auth/checkAuth", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
console.log("Not authorized");
|
console.log("Not authorized");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default checkAuth;
|
export default checkAuth;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PATH } from "../../../const.js";
|
import { PATH } from "~/const";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route check the verification code from the email that user just recieved
|
* This route check the verification code from the email that user just recieved
|
||||||
@@ -7,16 +7,16 @@ import { PATH } from "../../../const.js";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const checkEmailVerificationCode = (email, code) => {
|
const checkEmailVerificationCode = (email, code) => {
|
||||||
return fetch(PATH + "/api/v1/signup/email/verify", {
|
return fetch(PATH + "/api/v1/signup/email/verify", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
email: email,
|
email: email,
|
||||||
code: code,
|
code: code,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default checkEmailVerificationCode;
|
export default checkEmailVerificationCode;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PATH } from "../../../const";
|
import { PATH } from "~/const";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is called in the end of the signup process.
|
* This function is called in the end of the signup process.
|
||||||
@@ -16,37 +16,37 @@ import { PATH } from "../../../const";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const completeAccountInformationSignup = ({
|
const completeAccountInformationSignup = ({
|
||||||
email,
|
email,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
organizationName,
|
organizationName,
|
||||||
publicKey,
|
publicKey,
|
||||||
ciphertext,
|
ciphertext,
|
||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
salt,
|
salt,
|
||||||
verifier,
|
verifier,
|
||||||
token
|
token,
|
||||||
}) => {
|
}) => {
|
||||||
return fetch(PATH + "/api/v1/signup/complete-account/signup", {
|
return fetch(PATH + "/api/v1/signup/complete-account/signup", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Authorization": "Bearer " + token,
|
Authorization: "Bearer " + token,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
email,
|
email,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
publicKey,
|
publicKey,
|
||||||
encryptedPrivateKey: ciphertext,
|
encryptedPrivateKey: ciphertext,
|
||||||
organizationName,
|
organizationName,
|
||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
salt,
|
salt,
|
||||||
verifier,
|
verifier,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default completeAccountInformationSignup;
|
export default completeAccountInformationSignup;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PATH } from "../../../const";
|
import { PATH } from "~/const";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is called in the end of the signup process.
|
* This function is called in the end of the signup process.
|
||||||
@@ -15,35 +15,35 @@ import { PATH } from "../../../const";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const completeAccountInformationSignupInvite = ({
|
const completeAccountInformationSignupInvite = ({
|
||||||
email,
|
email,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
publicKey,
|
publicKey,
|
||||||
ciphertext,
|
ciphertext,
|
||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
salt,
|
salt,
|
||||||
verifier,
|
verifier,
|
||||||
token
|
token,
|
||||||
}) => {
|
}) => {
|
||||||
return fetch(PATH + "/api/v1/signup/complete-account/invite", {
|
return fetch(PATH + "/api/v1/signup/complete-account/invite", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Authorization": "Bearer " + token,
|
Authorization: "Bearer " + token,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
email: email,
|
email: email,
|
||||||
firstName: firstName,
|
firstName: firstName,
|
||||||
lastName: lastName,
|
lastName: lastName,
|
||||||
publicKey: publicKey,
|
publicKey: publicKey,
|
||||||
encryptedPrivateKey: ciphertext,
|
encryptedPrivateKey: ciphertext,
|
||||||
iv: iv,
|
iv: iv,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
salt: salt,
|
salt: salt,
|
||||||
verifier: verifier
|
verifier: verifier,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default completeAccountInformationSignupInvite;
|
export default completeAccountInformationSignupInvite;
|
||||||
|
|||||||
@@ -1,42 +1,41 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the route that issues a backup private key that will afterwards be added into a pdf
|
* This is the route that issues a backup private key that will afterwards be added into a pdf
|
||||||
*/
|
*/
|
||||||
const issueBackupPrivateKey = ({
|
const issueBackupPrivateKey = ({
|
||||||
encryptedPrivateKey,
|
encryptedPrivateKey,
|
||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
salt,
|
salt,
|
||||||
verifier,
|
verifier,
|
||||||
clientProof,
|
clientProof,
|
||||||
}) => {
|
}) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/password/backup-private-key",
|
PATH + "/api/v1/password/backup-private-key",
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
clientProof: clientProof,
|
clientProof: clientProof,
|
||||||
encryptedPrivateKey: encryptedPrivateKey,
|
encryptedPrivateKey: encryptedPrivateKey,
|
||||||
iv: iv,
|
iv: iv,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
salt: salt,
|
salt: salt,
|
||||||
verifier: verifier,
|
verifier: verifier,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
).then((res) => {
|
).then((res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
return res;
|
return res;
|
||||||
console.log("Failed to issue the backup key");
|
console.log("Failed to issue the backup key");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default issueBackupPrivateKey;
|
export default issueBackupPrivateKey;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PATH } from "../../../const.js";
|
import { PATH } from "~/const";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the first step of the login process (pake)
|
* This is the first step of the login process (pake)
|
||||||
@@ -7,16 +7,16 @@ import { PATH } from "../../../const.js";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const login1 = (email, clientPublicKey) => {
|
const login1 = (email, clientPublicKey) => {
|
||||||
return fetch(PATH + "/api/v1/auth/login1", {
|
return fetch(PATH + "/api/v1/auth/login1", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
email: email,
|
email: email,
|
||||||
clientPublicKey,
|
clientPublicKey,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default login1;
|
export default login1;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PATH } from "../../../const";
|
import { PATH } from "~/const";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the second step of the login process
|
* This is the second step of the login process
|
||||||
@@ -7,25 +7,24 @@ import { PATH } from "../../../const";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const login2 = (email, clientProof) => {
|
const login2 = (email, clientProof) => {
|
||||||
return fetch(PATH + "/api/v1/auth/login2", {
|
return fetch(PATH + "/api/v1/auth/login2", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
email: email,
|
email: email,
|
||||||
clientProof,
|
clientProof,
|
||||||
}),
|
}),
|
||||||
credentials: "include"
|
credentials: "include",
|
||||||
})
|
}).then((res) => {
|
||||||
.then(res => {
|
if (res.status == 200) {
|
||||||
if (res.status == 200) {
|
console.log("User logged in", res);
|
||||||
console.log("User logged in", res);
|
return res;
|
||||||
return res;
|
} else {
|
||||||
} else {
|
console.log("Failed to log in");
|
||||||
console.log("Failed to log in");
|
}
|
||||||
}
|
});
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default login2;
|
export default login2;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route logs the user out. Note: the user should authorized to do this.
|
* This route logs the user out. Note: the user should authorized to do this.
|
||||||
* We first try to log out - if the authorization fails (response.status = 401), we refetch the new token, and then retry
|
* We first try to log out - if the authorization fails (response.status = 401), we refetch the new token, and then retry
|
||||||
@@ -10,27 +9,27 @@ import { PATH } from "../../../const";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const logout = async (req, res) => {
|
const logout = async (req, res) => {
|
||||||
return SecurityClient.fetchCall(PATH + "/api/v1/auth/logout", {
|
return SecurityClient.fetchCall(PATH + "/api/v1/auth/logout", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
SecurityClient.setToken("");
|
SecurityClient.setToken("");
|
||||||
// Delete the cookie by not setting a value; Alternatively clear the local storage
|
// Delete the cookie by not setting a value; Alternatively clear the local storage
|
||||||
localStorage.setItem("publicKey", "");
|
localStorage.setItem("publicKey", "");
|
||||||
localStorage.setItem("encryptedPrivateKey", "");
|
localStorage.setItem("encryptedPrivateKey", "");
|
||||||
localStorage.setItem("iv", "");
|
localStorage.setItem("iv", "");
|
||||||
localStorage.setItem("tag", "");
|
localStorage.setItem("tag", "");
|
||||||
localStorage.setItem("PRIVATE_KEY", "");
|
localStorage.setItem("PRIVATE_KEY", "");
|
||||||
console.log("User logged out", res);
|
console.log("User logged out", res);
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to log out");
|
console.log("Failed to log out");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default logout;
|
export default logout;
|
||||||
|
|||||||
@@ -1,28 +1,27 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the first step of the change password process (pake)
|
* This is the first step of the change password process (pake)
|
||||||
* @param {*} clientPublicKey
|
* @param {*} clientPublicKey
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const SRP1 = ({ clientPublicKey }) => {
|
const SRP1 = ({ clientPublicKey }) => {
|
||||||
return SecurityClient.fetchCall(PATH + "/api/v1/password/srp1", {
|
return SecurityClient.fetchCall(PATH + "/api/v1/password/srp1", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
clientPublicKey,
|
clientPublicKey,
|
||||||
}),
|
}),
|
||||||
}).then(async (res) => {
|
}).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return await res.json();
|
return await res.json();
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to do the first step of SRP");
|
console.log("Failed to do the first step of SRP");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SRP1;
|
export default SRP1;
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import { PATH } from "../../../const.js";
|
import { PATH } from "~/const";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route send the verification email to the user's email (contains a 6-digit verification code)
|
* This route send the verification email to the user's email (contains a 6-digit verification code)
|
||||||
* @param {*} email
|
* @param {*} email
|
||||||
*/
|
*/
|
||||||
const sendVerificationEmail = (email) => {
|
const sendVerificationEmail = (email) => {
|
||||||
fetch(PATH + "/api/v1/signup/email/signup", {
|
fetch(PATH + "/api/v1/signup/email/signup", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
email: email,
|
email: email,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default sendVerificationEmail;
|
export default sendVerificationEmail;
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
import { PATH } from "../../../const.js";
|
import { PATH } from "~/const";
|
||||||
|
|
||||||
const token = async (req, res) => {
|
const token = async (req, res) => {
|
||||||
return fetch(PATH + "/api/v1/auth/token", {
|
return fetch(PATH + "/api/v1/auth/token", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
credentials: "include"
|
credentials: "include",
|
||||||
})
|
}).then(async (res) => {
|
||||||
.then(async res => {
|
if (res.status == 200) {
|
||||||
if (res.status == 200) {
|
return (await res.json()).token;
|
||||||
return (await res.json()).token;
|
} else {
|
||||||
} else {
|
console.log("Getting a new token failed");
|
||||||
console.log('Getting a new token failed');
|
}
|
||||||
}
|
});
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default token;
|
export default token;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PATH } from "../../../const";
|
import { PATH } from "~/const";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route verifies the signup invite link
|
* This route verifies the signup invite link
|
||||||
@@ -6,17 +6,17 @@ import { PATH } from "../../../const";
|
|||||||
* @param {*} code
|
* @param {*} code
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const verifySignupInvite = ({email, code}) => {
|
const verifySignupInvite = ({ email, code }) => {
|
||||||
return fetch(PATH + "/api/v1/invite-org/verify", {
|
return fetch(PATH + "/api/v1/invite-org/verify", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
email,
|
email,
|
||||||
code,
|
code,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default verifySignupInvite;
|
export default verifySignupInvite;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PATH } from "../../../const.js";
|
import { PATH } from "~/const";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route lets us get the public key of infisical. Th euser doesn't have to be authenticated since this is just the public key.
|
* This route lets us get the public key of infisical. Th euser doesn't have to be authenticated since this is just the public key.
|
||||||
@@ -7,12 +7,12 @@ import { PATH } from "../../../const.js";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const publicKeyInfisical = (req, res) => {
|
const publicKeyInfisical = (req, res) => {
|
||||||
return fetch(PATH + "/api/v1/key/publicKey/infisical", {
|
return fetch(PATH + "/api/v1/key/publicKey/infisical", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default publicKeyInfisical;
|
export default publicKeyInfisical;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient.js";
|
import SecurityClient from "~/utilities/SecurityClient.js";
|
||||||
|
|
||||||
import { PATH } from "../../../const.js";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function fetches the encrypted secrets from the .env file
|
* This function fetches the encrypted secrets from the .env file
|
||||||
* @param {*} workspaceId
|
* @param {*} workspaceId
|
||||||
@@ -9,28 +8,28 @@ import { PATH } from "../../../const.js";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const getSecrets = async (workspaceId, env) => {
|
const getSecrets = async (workspaceId, env) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH +
|
PATH +
|
||||||
"/api/v1/secret/" +
|
"/api/v1/secret/" +
|
||||||
workspaceId +
|
workspaceId +
|
||||||
"?" +
|
"?" +
|
||||||
new URLSearchParams({
|
new URLSearchParams({
|
||||||
environment: env,
|
environment: env,
|
||||||
channel: "web",
|
channel: "web",
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return await res.json();
|
return await res.json();
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to get project secrets");
|
console.log("Failed to get project secrets");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getSecrets;
|
export default getSecrets;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function uploads the encrypted .env file
|
* This function uploads the encrypted .env file
|
||||||
* @param {*} req
|
* @param {*} req
|
||||||
@@ -9,24 +8,24 @@ import { PATH } from "../../../const";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const uploadSecrets = async ({ workspaceId, secrets, keys, environment }) => {
|
const uploadSecrets = async ({ workspaceId, secrets, keys, environment }) => {
|
||||||
return SecurityClient.fetchCall(PATH + "/api/v1/secret/" + workspaceId, {
|
return SecurityClient.fetchCall(PATH + "/api/v1/secret/" + workspaceId, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
secrets,
|
secrets,
|
||||||
keys,
|
keys,
|
||||||
environment,
|
environment,
|
||||||
channel: "web",
|
channel: "web",
|
||||||
}),
|
}),
|
||||||
}).then(async (res) => {
|
}).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to push secrets");
|
console.log("Failed to push secrets");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default uploadSecrets;
|
export default uploadSecrets;
|
||||||
|
|||||||
@@ -1,27 +1,26 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
const changeHerokuConfigVars = ({ integrationId, key, secrets }) => {
|
const changeHerokuConfigVars = ({ integrationId, key, secrets }) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/integration/" + integrationId + "/sync",
|
PATH + "/api/v1/integration/" + integrationId + "/sync",
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
key,
|
key,
|
||||||
secrets,
|
secrets,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to sync secrets to Heroku");
|
console.log("Failed to sync secrets to Heroku");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default changeHerokuConfigVars;
|
export default changeHerokuConfigVars;
|
||||||
|
|||||||
@@ -1,28 +1,27 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route deletes an integration from a certain project
|
* This route deletes an integration from a certain project
|
||||||
* @param {*} integrationId
|
* @param {*} integrationId
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const deleteIntegration = ({ integrationId }) => {
|
const deleteIntegration = ({ integrationId }) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/integration/" + integrationId,
|
PATH + "/api/v1/integration/" + integrationId,
|
||||||
{
|
{
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return (await res.json()).workspace;
|
return (await res.json()).workspace;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to delete an integration");
|
console.log("Failed to delete an integration");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default deleteIntegration;
|
export default deleteIntegration;
|
||||||
|
|||||||
@@ -1,28 +1,27 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route deletes an integration authorization from a certain project
|
* This route deletes an integration authorization from a certain project
|
||||||
* @param {*} integrationAuthId
|
* @param {*} integrationAuthId
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const deleteIntegrationAuth = ({ integrationAuthId }) => {
|
const deleteIntegrationAuth = ({ integrationAuthId }) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/integration-auth/" + integrationAuthId,
|
PATH + "/api/v1/integration-auth/" + integrationAuthId,
|
||||||
{
|
{
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to delete an integration authorization");
|
console.log("Failed to delete an integration authorization");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default deleteIntegrationAuth;
|
export default deleteIntegrationAuth;
|
||||||
|
|||||||
@@ -1,23 +1,22 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
const getIntegrationApps = ({ integrationAuthId }) => {
|
const getIntegrationApps = ({ integrationAuthId }) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/integration-auth/" + integrationAuthId + "/apps",
|
PATH + "/api/v1/integration-auth/" + integrationAuthId + "/apps",
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return (await res.json()).apps;
|
return (await res.json()).apps;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to get available apps for an integration");
|
console.log("Failed to get available apps for an integration");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getIntegrationApps;
|
export default getIntegrationApps;
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
const getIntegrations = () => {
|
const getIntegrations = () => {
|
||||||
return SecurityClient.fetchCall(PATH + "/api/v1/integration/integrations", {
|
return SecurityClient.fetchCall(PATH + "/api/v1/integration/integrations", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}).then(async (res) => {
|
}).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return (await res.json()).integrations;
|
return (await res.json()).integrations;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to get project integrations");
|
console.log("Failed to get project integrations");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getIntegrations;
|
export default getIntegrations;
|
||||||
|
|||||||
@@ -1,35 +1,34 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route starts the integration after teh default one if gonna set up.
|
* This route starts the integration after teh default one if gonna set up.
|
||||||
* @param {*} integrationId
|
* @param {*} integrationId
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const startIntegration = ({ integrationId, appName, environment }) => {
|
const startIntegration = ({ integrationId, appName, environment }) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/integration/" + integrationId,
|
PATH + "/api/v1/integration/" + integrationId,
|
||||||
{
|
{
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
update: {
|
update: {
|
||||||
app: appName,
|
app: appName,
|
||||||
environment,
|
environment,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to start an integration");
|
console.log("Failed to start an integration");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default startIntegration;
|
export default startIntegration;
|
||||||
|
|||||||
@@ -1,33 +1,32 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the first step of the change password process (pake)
|
* This is the first step of the change password process (pake)
|
||||||
* @param {*} clientPublicKey
|
* @param {*} clientPublicKey
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const AuthorizeIntegration = ({ workspaceId, code, integration }) => {
|
const AuthorizeIntegration = ({ workspaceId, code, integration }) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/integration-auth/oauth-token",
|
PATH + "/api/v1/integration-auth/oauth-token",
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
code,
|
code,
|
||||||
integration,
|
integration,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to authorize the integration");
|
console.log("Failed to authorize the integration");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AuthorizeIntegration;
|
export default AuthorizeIntegration;
|
||||||
|
|||||||
@@ -1,28 +1,27 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route gets authorizations of a certain project (Heroku, etc.)
|
* This route gets authorizations of a certain project (Heroku, etc.)
|
||||||
* @param {*} workspaceId
|
* @param {*} workspaceId
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const getWorkspaceAuthorizations = ({ workspaceId }) => {
|
const getWorkspaceAuthorizations = ({ workspaceId }) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/workspace/" + workspaceId + "/authorizations",
|
PATH + "/api/v1/workspace/" + workspaceId + "/authorizations",
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return (await res.json()).authorizations;
|
return (await res.json()).authorizations;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to get project authorizations");
|
console.log("Failed to get project authorizations");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getWorkspaceAuthorizations;
|
export default getWorkspaceAuthorizations;
|
||||||
|
|||||||
@@ -1,28 +1,27 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route gets integrations of a certain project (Heroku, etc.)
|
* This route gets integrations of a certain project (Heroku, etc.)
|
||||||
* @param {*} workspaceId
|
* @param {*} workspaceId
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const getWorkspaceIntegrations = ({ workspaceId }) => {
|
const getWorkspaceIntegrations = ({ workspaceId }) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/workspace/" + workspaceId + "/integrations",
|
PATH + "/api/v1/workspace/" + workspaceId + "/integrations",
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return (await res.json()).integrations;
|
return (await res.json()).integrations;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to get the project integrations");
|
console.log("Failed to get the project integrations");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getWorkspaceIntegrations;
|
export default getWorkspaceIntegrations;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route lets us get info about a certain org
|
* This route lets us get info about a certain org
|
||||||
* @param {*} req
|
* @param {*} req
|
||||||
@@ -9,21 +8,18 @@ import { PATH } from "../../../const";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const getOrganization = (req, res) => {
|
const getOrganization = (req, res) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + req.orgId, {
|
||||||
PATH + "/api/v1/organization/" + req.orgId,
|
method: "GET",
|
||||||
{
|
headers: {
|
||||||
method: "GET",
|
"Content-Type": "application/json",
|
||||||
headers: {
|
},
|
||||||
"Content-Type": "application/json",
|
}).then(async (res) => {
|
||||||
},
|
if (res.status == 200) {
|
||||||
}
|
return (await res.json()).organization;
|
||||||
).then(async (res) => {
|
} else {
|
||||||
if (res.status == 200) {
|
console.log("Failed to get org info");
|
||||||
return (await res.json()).organization;
|
}
|
||||||
} else {
|
});
|
||||||
console.log("Failed to get org info");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getOrganization;
|
export default getOrganization;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route lets us get all the users in an org.
|
* This route lets us get all the users in an org.
|
||||||
* @param {*} req
|
* @param {*} req
|
||||||
@@ -9,21 +8,21 @@ import { PATH } from "../../../const";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const getOrganizationProjects = (req, res) => {
|
const getOrganizationProjects = (req, res) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/organization/" + req.orgId + "/workspaces",
|
PATH + "/api/organization/" + req.orgId + "/workspaces",
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return (await res.json()).workspaces;
|
return (await res.json()).workspaces;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to get projects for an org");
|
console.log("Failed to get projects for an org");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getOrganizationProjects;
|
export default getOrganizationProjects;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route lets us get the current subscription of an org.
|
* This route lets us get the current subscription of an org.
|
||||||
* @param {*} req
|
* @param {*} req
|
||||||
@@ -9,21 +8,21 @@ import { PATH } from "../../../const";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const getOrganizationSubscriptions = (req, res) => {
|
const getOrganizationSubscriptions = (req, res) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/organization/" + req.orgId + "/subscriptions",
|
PATH + "/api/v1/organization/" + req.orgId + "/subscriptions",
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return (await res.json()).subscriptions;
|
return (await res.json()).subscriptions;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to get org subscriptions");
|
console.log("Failed to get org subscriptions");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getOrganizationSubscriptions;
|
export default getOrganizationSubscriptions;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route lets us get all the projects of a certain user in an org.
|
* This route lets us get all the projects of a certain user in an org.
|
||||||
* @param {*} req
|
* @param {*} req
|
||||||
@@ -9,21 +8,21 @@ import { PATH } from "../../../const";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const getOrganizationUserProjects = (req, res) => {
|
const getOrganizationUserProjects = (req, res) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/organization/" + req.orgId + "/my-workspaces",
|
PATH + "/api/v1/organization/" + req.orgId + "/my-workspaces",
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return (await res.json()).workspaces;
|
return (await res.json()).workspaces;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to get projects of a user in an org");
|
console.log("Failed to get projects of a user in an org");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getOrganizationUserProjects;
|
export default getOrganizationUserProjects;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route lets us get all the users in an org.
|
* This route lets us get all the users in an org.
|
||||||
* @param {*} req
|
* @param {*} req
|
||||||
@@ -9,21 +8,21 @@ import { PATH } from "../../../const";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const getOrganizationUsers = (req, res) => {
|
const getOrganizationUsers = (req, res) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/organization/" + req.orgId + "/users",
|
PATH + "/api/v1/organization/" + req.orgId + "/users",
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return (await res.json()).users;
|
return (await res.json()).users;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to get org users");
|
console.log("Failed to get org users");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getOrganizationUsers;
|
export default getOrganizationUsers;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route redirects the user to the right stripe billing page.
|
* This route redirects the user to the right stripe billing page.
|
||||||
* @param {*} req
|
* @param {*} req
|
||||||
@@ -9,21 +8,21 @@ import { PATH } from "../../../const";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const StripeRedirect = ({ orgId }) => {
|
const StripeRedirect = ({ orgId }) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/organization/" + orgId + "/customer-portal-session",
|
PATH + "/api/v1/organization/" + orgId + "/customer-portal-session",
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return (window.location.href = (await res.json()).url);
|
return (window.location.href = (await res.json()).url);
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to redirect to Stripe");
|
console.log("Failed to redirect to Stripe");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default StripeRedirect;
|
export default StripeRedirect;
|
||||||
|
|||||||
@@ -1,31 +1,30 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route add an incident contact email to a certain organization
|
* This route add an incident contact email to a certain organization
|
||||||
* @param {*} param0
|
* @param {*} param0
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const addIncidentContact = (organizationId, email) => {
|
const addIncidentContact = (organizationId, email) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/organization/" + organizationId + "/incidentContactOrg",
|
PATH + "/api/v1/organization/" + organizationId + "/incidentContactOrg",
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
email: email,
|
email: email,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to add an incident contact");
|
console.log("Failed to add an incident contact");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default addIncidentContact;
|
export default addIncidentContact;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function sends an email invite to a user to join an org
|
* This function sends an email invite to a user to join an org
|
||||||
* @param {*} email
|
* @param {*} email
|
||||||
@@ -9,22 +8,22 @@ import { PATH } from "../../../const";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const addUserToOrg = (email, orgId) => {
|
const addUserToOrg = (email, orgId) => {
|
||||||
return SecurityClient.fetchCall(PATH + "/api/v1/invite-org/signup", {
|
return SecurityClient.fetchCall(PATH + "/api/v1/invite-org/signup", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
inviteeEmail: email,
|
inviteeEmail: email,
|
||||||
organizationId: orgId,
|
organizationId: orgId,
|
||||||
}),
|
}),
|
||||||
}).then(async (res) => {
|
}).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to add a user to an org");
|
console.log("Failed to add a user to an org");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default addUserToOrg;
|
export default addUserToOrg;
|
||||||
|
|||||||
@@ -1,31 +1,30 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route deletes an incident Contact from a certain organization
|
* This route deletes an incident Contact from a certain organization
|
||||||
* @param {*} param0
|
* @param {*} param0
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const deleteIncidentContact = (organizaionId, email) => {
|
const deleteIncidentContact = (organizaionId, email) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/organization/" + organizaionId + "/incidentContactOrg",
|
PATH + "/api/v1/organization/" + organizaionId + "/incidentContactOrg",
|
||||||
{
|
{
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
email: email,
|
email: email,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to delete an incident contact");
|
console.log("Failed to delete an incident contact");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default deleteIncidentContact;
|
export default deleteIncidentContact;
|
||||||
|
|||||||
@@ -1,28 +1,27 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function removes a certain member from a certain organization
|
* This function removes a certain member from a certain organization
|
||||||
* @param {*} membershipId
|
* @param {*} membershipId
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const deleteUserFromOrganization = (membershipId) => {
|
const deleteUserFromOrganization = (membershipId) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/membership-org/" + membershipId,
|
PATH + "/api/v1/membership-org/" + membershipId,
|
||||||
{
|
{
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to delete a user from an org");
|
console.log("Failed to delete a user from an org");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default deleteUserFromOrganization;
|
export default deleteUserFromOrganization;
|
||||||
|
|||||||
@@ -1,28 +1,27 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This routes gets all the incident contacts of a certain organization
|
* This routes gets all the incident contacts of a certain organization
|
||||||
* @param {*} workspaceId
|
* @param {*} workspaceId
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const getIncidentContacts = (organizationId) => {
|
const getIncidentContacts = (organizationId) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/organization/" + organizationId + "/incidentContactOrg",
|
PATH + "/api/v1/organization/" + organizationId + "/incidentContactOrg",
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return (await res.json()).incidentContactsOrg;
|
return (await res.json()).incidentContactsOrg;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to get incident contacts");
|
console.log("Failed to get incident contacts");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getIncidentContacts;
|
export default getIncidentContacts;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route lets us get the all the orgs of a certain user.
|
* This route lets us get the all the orgs of a certain user.
|
||||||
* @param {*} req
|
* @param {*} req
|
||||||
@@ -9,18 +8,18 @@ import { PATH } from "../../../const";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const getOrganizations = (req, res) => {
|
const getOrganizations = (req, res) => {
|
||||||
return SecurityClient.fetchCall(PATH + "/api/v1/organization", {
|
return SecurityClient.fetchCall(PATH + "/api/v1/organization", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}).then(async (res) => {
|
}).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return (await res.json()).organizations;
|
return (await res.json()).organizations;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to get orgs of a user");
|
console.log("Failed to get orgs of a user");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getOrganizations;
|
export default getOrganizations;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route lets us rename a certain org.
|
* This route lets us rename a certain org.
|
||||||
* @param {*} req
|
* @param {*} req
|
||||||
@@ -9,24 +8,24 @@ import { PATH } from "../../../const";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const renameOrg = (orgId, newOrgName) => {
|
const renameOrg = (orgId, newOrgName) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/organization/" + orgId + "/name",
|
PATH + "/api/v1/organization/" + orgId + "/name",
|
||||||
{
|
{
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
name: newOrgName,
|
name: newOrgName,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to rename an organization");
|
console.log("Failed to rename an organization");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default renameOrg;
|
export default renameOrg;
|
||||||
|
|||||||
@@ -1,42 +1,41 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route gets service tokens for a specific user in a project
|
* This route gets service tokens for a specific user in a project
|
||||||
* @param {*} param0
|
* @param {*} param0
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const addServiceToken = ({
|
const addServiceToken = ({
|
||||||
name,
|
name,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
expiresIn,
|
expiresIn,
|
||||||
publicKey,
|
publicKey,
|
||||||
encryptedKey,
|
encryptedKey,
|
||||||
nonce,
|
nonce,
|
||||||
}) => {
|
}) => {
|
||||||
return SecurityClient.fetchCall(PATH + "/api/v1/service-token/", {
|
return SecurityClient.fetchCall(PATH + "/api/v1/service-token/", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
name,
|
name,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
expiresIn,
|
expiresIn,
|
||||||
publicKey,
|
publicKey,
|
||||||
encryptedKey,
|
encryptedKey,
|
||||||
nonce,
|
nonce,
|
||||||
}),
|
}),
|
||||||
}).then(async (res) => {
|
}).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return (await res.json()).token;
|
return (await res.json()).token;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to add service tokens");
|
console.log("Failed to add service tokens");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default addServiceToken;
|
export default addServiceToken;
|
||||||
|
|||||||
@@ -1,28 +1,27 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route gets service tokens for a specific user in a project
|
* This route gets service tokens for a specific user in a project
|
||||||
* @param {*} param0
|
* @param {*} param0
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const getServiceTokens = ({ workspaceId }) => {
|
const getServiceTokens = ({ workspaceId }) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/workspace/" + workspaceId + "/service-tokens",
|
PATH + "/api/v1/workspace/" + workspaceId + "/service-tokens",
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return (await res.json()).serviceTokens;
|
return (await res.json()).serviceTokens;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to get service tokens");
|
console.log("Failed to get service tokens");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getServiceTokens;
|
export default getServiceTokens;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route gets the information about a specific user.
|
* This route gets the information about a specific user.
|
||||||
* @param {*} req
|
* @param {*} req
|
||||||
@@ -9,18 +8,18 @@ import { PATH } from "../../../const";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const getUser = (req, res) => {
|
const getUser = (req, res) => {
|
||||||
return SecurityClient.fetchCall(PATH + "/api/v1/user", {
|
return SecurityClient.fetchCall(PATH + "/api/v1/user", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}).then(async (res) => {
|
}).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return (await res.json()).user;
|
return (await res.json()).user;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to get user info");
|
console.log("Failed to get user info");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getUser;
|
export default getUser;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route registers a certain action for a user
|
* This route registers a certain action for a user
|
||||||
* @param {*} email
|
* @param {*} email
|
||||||
@@ -9,26 +8,26 @@ import { PATH } from "../../../const";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const checkUserAction = ({ action }) => {
|
const checkUserAction = ({ action }) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH +
|
PATH +
|
||||||
"/api/v1/user-action" +
|
"/api/v1/user-action" +
|
||||||
"?" +
|
"?" +
|
||||||
new URLSearchParams({
|
new URLSearchParams({
|
||||||
action,
|
action,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return (await res.json()).userAction;
|
return (await res.json()).userAction;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to check a user action");
|
console.log("Failed to check a user action");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default checkUserAction;
|
export default checkUserAction;
|
||||||
|
|||||||
@@ -1,28 +1,27 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route registers a certain action for a user
|
* This route registers a certain action for a user
|
||||||
* @param {*} action
|
* @param {*} action
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const registerUserAction = ({ action }) => {
|
const registerUserAction = ({ action }) => {
|
||||||
return SecurityClient.fetchCall(PATH + "/api/v1/user-action", {
|
return SecurityClient.fetchCall(PATH + "/api/v1/user-action", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
action,
|
action,
|
||||||
}),
|
}),
|
||||||
}).then(async (res) => {
|
}).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to register a user action");
|
console.log("Failed to register a user action");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default registerUserAction;
|
export default registerUserAction;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function adds a user to a project
|
* This function adds a user to a project
|
||||||
* @param {*} email
|
* @param {*} email
|
||||||
@@ -9,24 +8,24 @@ import { PATH } from "../../../const";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const addUserToWorkspace = (email, workspaceId) => {
|
const addUserToWorkspace = (email, workspaceId) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/workspace/" + workspaceId + "/invite-signup",
|
PATH + "/api/v1/workspace/" + workspaceId + "/invite-signup",
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
email: email,
|
email: email,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return await res.json();
|
return await res.json();
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to add a user to project");
|
console.log("Failed to add a user to project");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default addUserToWorkspace;
|
export default addUserToWorkspace;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function change the access of a user in a certain workspace
|
* This function change the access of a user in a certain workspace
|
||||||
* @param {*} membershipId
|
* @param {*} membershipId
|
||||||
@@ -9,24 +8,24 @@ import { PATH } from "../../../const";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const changeUserRoleInWorkspace = (membershipId, role) => {
|
const changeUserRoleInWorkspace = (membershipId, role) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/membership/" + membershipId + "/change-role",
|
PATH + "/api/v1/membership/" + membershipId + "/change-role",
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
role: role,
|
role: role,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to change the user role in a project");
|
console.log("Failed to change the user role in a project");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default changeUserRoleInWorkspace;
|
export default changeUserRoleInWorkspace;
|
||||||
|
|||||||
@@ -1,29 +1,28 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route creates a new workspace for a user.
|
* This route creates a new workspace for a user.
|
||||||
* @param {*} workspaceName
|
* @param {*} workspaceName
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const createWorkspace = (workspaceName, organizationId) => {
|
const createWorkspace = (workspaceName, organizationId) => {
|
||||||
return SecurityClient.fetchCall(PATH + "/api/v1/workspace", {
|
return SecurityClient.fetchCall(PATH + "/api/v1/workspace", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
workspaceName: workspaceName,
|
workspaceName: workspaceName,
|
||||||
organizationId: organizationId,
|
organizationId: organizationId,
|
||||||
}),
|
}),
|
||||||
}).then(async (res) => {
|
}).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return (await res.json()).workspace;
|
return (await res.json()).workspace;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to create a project");
|
console.log("Failed to create a project");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default createWorkspace;
|
export default createWorkspace;
|
||||||
|
|||||||
@@ -1,28 +1,24 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function removes a certain member from a certain workspace
|
* This function removes a certain member from a certain workspace
|
||||||
* @param {*} membershipId
|
* @param {*} membershipId
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const deleteUserFromWorkspace = (membershipId) => {
|
const deleteUserFromWorkspace = (membershipId) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(PATH + "/api/v1/membership/" + membershipId, {
|
||||||
PATH + "/api/v1/membership/" + membershipId,
|
method: "DELETE",
|
||||||
{
|
headers: {
|
||||||
method: "DELETE",
|
"Content-Type": "application/json",
|
||||||
headers: {
|
},
|
||||||
"Content-Type": "application/json",
|
}).then(async (res) => {
|
||||||
},
|
if (res.status == 200) {
|
||||||
}
|
return res;
|
||||||
).then(async (res) => {
|
} else {
|
||||||
if (res.status == 200) {
|
console.log("Failed to delete a user from a project");
|
||||||
return res;
|
}
|
||||||
} else {
|
});
|
||||||
console.log("Failed to delete a user from a project");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default deleteUserFromWorkspace;
|
export default deleteUserFromWorkspace;
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route deletes a specified workspace.
|
* This route deletes a specified workspace.
|
||||||
* @param {*} workspaceId
|
* @param {*} workspaceId
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const deleteWorkspace = (workspaceId) => {
|
const deleteWorkspace = (workspaceId) => {
|
||||||
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + workspaceId, {
|
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + workspaceId, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}).then(async (res) => {
|
}).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to delete a project");
|
console.log("Failed to delete a project");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default deleteWorkspace;
|
export default deleteWorkspace;
|
||||||
|
|||||||
@@ -1,30 +1,27 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the latest key pairs from a certain workspace
|
* Get the latest key pairs from a certain workspace
|
||||||
* @param {*} workspaceId
|
* @param {*} workspaceId
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const getLatestFileKey = (workspaceId) => {
|
const getLatestFileKey = (workspaceId) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/key/" + workspaceId + "/latest",
|
PATH + "/api/v1/key/" + workspaceId + "/latest",
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return await res.json();
|
return await res.json();
|
||||||
} else {
|
} else {
|
||||||
console.log(
|
console.log("Failed to get the latest key pairs for a certain project");
|
||||||
"Failed to get the latest key pairs for a certain project"
|
}
|
||||||
);
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getLatestFileKey;
|
export default getLatestFileKey;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route lets us get the information of a certain project.
|
* This route lets us get the information of a certain project.
|
||||||
* @param {*} req
|
* @param {*} req
|
||||||
@@ -9,21 +8,21 @@ import { PATH } from "../../../const";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const getWorkspaceInfo = (req, res) => {
|
const getWorkspaceInfo = (req, res) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/workspace/" + req.workspaceId,
|
PATH + "/api/v1/workspace/" + req.workspaceId,
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return (await res.json()).workspace;
|
return (await res.json()).workspace;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to get project info");
|
console.log("Failed to get project info");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getWorkspaceInfo;
|
export default getWorkspaceInfo;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route lets us get the public keys of everyone in your workspace.
|
* This route lets us get the public keys of everyone in your workspace.
|
||||||
* @param {*} req
|
* @param {*} req
|
||||||
@@ -9,23 +8,21 @@ import { PATH } from "../../../const";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const getWorkspaceKeys = (req, res) => {
|
const getWorkspaceKeys = (req, res) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/workspace/" + req.workspaceId + "/keys",
|
PATH + "/api/v1/workspace/" + req.workspaceId + "/keys",
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return (await res.json()).publicKeys;
|
return (await res.json()).publicKeys;
|
||||||
} else {
|
} else {
|
||||||
console.log(
|
console.log("Failed to get the public keys of everyone in the workspace");
|
||||||
"Failed to get the public keys of everyone in the workspace"
|
}
|
||||||
);
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getWorkspaceKeys;
|
export default getWorkspaceKeys;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route lets us get all the users in the workspace.
|
* This route lets us get all the users in the workspace.
|
||||||
* @param {*} req
|
* @param {*} req
|
||||||
@@ -9,21 +8,21 @@ import { PATH } from "../../../const";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const getWorkspaceUsers = (req, res) => {
|
const getWorkspaceUsers = (req, res) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/workspace/" + req.workspaceId + "/users",
|
PATH + "/api/v1/workspace/" + req.workspaceId + "/users",
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return (await res.json()).users;
|
return (await res.json()).users;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to get Project Users");
|
console.log("Failed to get Project Users");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getWorkspaceUsers;
|
export default getWorkspaceUsers;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route lets us get the public keys of everyone in your workspace.
|
* This route lets us get the public keys of everyone in your workspace.
|
||||||
* @param {*} req
|
* @param {*} req
|
||||||
@@ -9,18 +8,18 @@ import { PATH } from "../../../const";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const getWorkspaces = (req, res) => {
|
const getWorkspaces = (req, res) => {
|
||||||
return SecurityClient.fetchCall(PATH + "/api/v1/workspace", {
|
return SecurityClient.fetchCall(PATH + "/api/v1/workspace", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}).then(async (res) => {
|
}).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return (await res.json()).workspaces;
|
return (await res.json()).workspaces;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to get projects");
|
console.log("Failed to get projects");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getWorkspaces;
|
export default getWorkspaces;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route lets us rename a certain workspace.
|
* This route lets us rename a certain workspace.
|
||||||
* @param {*} req
|
* @param {*} req
|
||||||
@@ -9,24 +8,24 @@ import { PATH } from "../../../const";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const renameWorkspace = (workspaceId, newWorkspaceName) => {
|
const renameWorkspace = (workspaceId, newWorkspaceName) => {
|
||||||
return SecurityClient.fetchCall(
|
return SecurityClient.fetchCall(
|
||||||
PATH + "/api/v1/workspace/" + workspaceId + "/name",
|
PATH + "/api/v1/workspace/" + workspaceId + "/name",
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
name: newWorkspaceName,
|
name: newWorkspaceName,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
).then(async (res) => {
|
).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to rename a project");
|
console.log("Failed to rename a project");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default renameWorkspace;
|
export default renameWorkspace;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import { PATH } from "~/const";
|
||||||
import SecurityClient from "~/utilities/SecurityClient";
|
import SecurityClient from "~/utilities/SecurityClient";
|
||||||
|
|
||||||
import { PATH } from "../../../const";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This route uplods the keys in an encrypted format.
|
* This route uplods the keys in an encrypted format.
|
||||||
* @param {*} workspaceId
|
* @param {*} workspaceId
|
||||||
@@ -11,25 +10,25 @@ import { PATH } from "../../../const";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const uploadKeys = (workspaceId, userId, encryptedKey, nonce) => {
|
const uploadKeys = (workspaceId, userId, encryptedKey, nonce) => {
|
||||||
return SecurityClient.fetchCall(PATH + "/api/v1/key/" + workspaceId, {
|
return SecurityClient.fetchCall(PATH + "/api/v1/key/" + workspaceId, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
key: {
|
key: {
|
||||||
userId: userId,
|
userId: userId,
|
||||||
encryptedKey: encryptedKey,
|
encryptedKey: encryptedKey,
|
||||||
nonce: nonce,
|
nonce: nonce,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
}).then(async (res) => {
|
}).then(async (res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to upload keys for a new user");
|
console.log("Failed to upload keys for a new user");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default uploadKeys;
|
export default uploadKeys;
|
||||||
|
|||||||
+21
-23
@@ -5,31 +5,29 @@ import { useRouter } from "next/router";
|
|||||||
import getWorkspaces from "./api/workspace/getWorkspaces";
|
import getWorkspaces from "./api/workspace/getWorkspaces";
|
||||||
|
|
||||||
export default function DashboardRedirect() {
|
export default function DashboardRedirect() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Here we forward to the default workspace if a user opens this url
|
* Here we forward to the default workspace if a user opens this url
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
useEffect(async () => {
|
useEffect(async () => {
|
||||||
let userWorkspace;
|
let userWorkspace;
|
||||||
try {
|
try {
|
||||||
if (localStorage.getItem("projectData.id")) {
|
if (localStorage.getItem("projectData.id")) {
|
||||||
router.push(
|
router.push("/dashboard/" + localStorage.getItem("projectData.id"));
|
||||||
"/dashboard/" + localStorage.getItem("projectData.id")
|
} else {
|
||||||
);
|
const userWorkspaces = await getWorkspaces();
|
||||||
} else {
|
userWorkspace = userWorkspaces[0]._id;
|
||||||
const userWorkspaces = await getWorkspaces();
|
router.push("/dashboard/" + userWorkspace);
|
||||||
userWorkspace = userWorkspaces[0]._id;
|
}
|
||||||
router.push("/dashboard/" + userWorkspace);
|
} catch (error) {
|
||||||
}
|
console.log("Error - Not logged in yet");
|
||||||
} catch (error) {
|
}
|
||||||
console.log("Error - Not logged in yet");
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}
|
}, []);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return <div></div>;
|
return <div></div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
DashboardRedirect.requireAuth = true;
|
DashboardRedirect.requireAuth = true;
|
||||||
|
|||||||
+686
-754
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user