Merge pull request #45 from gangjun06/refactor2

Edit frontend .prettierrc config
This commit is contained in:
mv-turtle
2022-11-27 08:29:20 -05:00
committed by GitHub
115 changed files with 13340 additions and 9491 deletions
+2 -2
View File
@@ -1,4 +1,4 @@
{ {
"tabWidth": 4, "tabWidth": 2,
"useTabs": true "useTabs": false
} }
+67 -68
View File
@@ -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>
); );
} }
} }
+14 -9
View File
@@ -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;
}; };
+11 -13
View File
@@ -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>
);
} }
+95 -97
View File
@@ -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);
+96 -93
View File
@@ -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> );
);
} }
+89 -89
View File
@@ -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;
+101 -105
View File
@@ -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
View File
@@ -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>
); );
} }
+23 -23
View File
@@ -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;
+199 -230
View File
@@ -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;
+77 -86
View File
@@ -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);
+161 -162
View File
@@ -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;
+268 -300
View File
@@ -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>
);
} }
+42 -42
View File
@@ -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>
); );
} }
+18 -19
View File
@@ -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);
} }
} }
} }
+141 -167
View File
@@ -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;
+11 -9
View File
@@ -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;
+26 -26
View File
@@ -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
+17 -17
View File
@@ -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
View File
@@ -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`,
]; ];
+3
View File
@@ -8,6 +8,9 @@
"~/utilities/*": [ "~/utilities/*": [
"components/utilities/*" "components/utilities/*"
], ],
"~/*": [
"const"
],
"~/pages/*": [ "~/pages/*": [
"pages/*" "pages/*"
], ],
+48 -44
View File
@@ -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,
}, },
]; ];
}, },
}; };
+4429 -28
View File
File diff suppressed because it is too large Load Diff
+63 -63
View File
@@ -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
View File
@@ -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"
/> />
+27 -28
View File
@@ -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;
+13 -14
View File
@@ -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;
+11 -11
View File
@@ -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;
+19 -20
View File
@@ -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;
+22 -23
View File
@@ -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;
+16 -17
View File
@@ -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;
+14 -15
View File
@@ -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;
+12 -12
View File
@@ -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;
+23 -24
View File
@@ -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;
+19 -20
View File
@@ -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;
+13 -17
View File
@@ -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;
+16 -17
View File
@@ -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;
+17 -18
View File
@@ -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;
+13 -14
View File
@@ -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;
+19 -20
View File
@@ -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;
+13 -14
View File
@@ -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;
+17 -18
View File
@@ -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;
+13 -14
View File
@@ -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;
+13 -14
View File
@@ -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;
+19 -20
View File
@@ -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;
+20 -21
View File
@@ -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
View File
@@ -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;
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