This commit is contained in:
Vladyslav Matsiiako
2022-11-26 10:38:51 -05:00
95 changed files with 5927 additions and 3627 deletions

46
frontend/.eslintrc.json Normal file
View File

@@ -0,0 +1,46 @@
{
"extends": "next/core-web-vitals",
"plugins": [
"simple-import-sort"
],
"rules": {
"react-hooks/exhaustive-deps": "off",
"simple-import-sort/exports": "warn",
"simple-import-sort/imports": [
"warn",
{
"groups": [
// Node.js builtins. You could also generate this regex if you use a `.js` config.
// For example: `^(${require("module").builtinModules.join("|")})(/|$)`
// Note that if you use the `node:` prefix for Node.js builtins,
// you can avoid this complexity: You can simply use "^node:".
[
"^(assert|buffer|child_process|cluster|console|constants|crypto|dgram|dns|domain|events|fs|http|https|module|net|os|path|punycode|querystring|readline|repl|stream|string_decoder|sys|timers|tls|tty|url|util|vm|zlib|freelist|v8|process|async_hooks|http2|perf_hooks)(/.*|$)"
],
// Packages `react` related packages
[
"^react",
"^next",
"^@?\\w"
],
// Internal packages.
[
"^~(/.*|$)"
],
// Relative imports
[
"^\\.\\.(?!/?$)",
"^\\.\\./?$",
"^\\./(?=.*/)(?!/?$)",
"^\\.(?!/?$)",
"^\\./?$"
],
// Style imports.
[
"^.+\\.?(css|scss)$"
]
]
}
]
}
}

View File

@@ -1,7 +1,9 @@
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import checkAuth from "../pages/api/auth/CheckAuth";
import { useEffect,useState } from "react";
import Image from "next/image";
import { useRouter } from "next/router";
import checkAuth from "~/pages/api/auth/CheckAuth";
import { publicPaths } from "../const";
// #TODO: finish spinner only when the data loads fully
@@ -11,9 +13,11 @@ export default function RouteGuard({ children }) {
const router = useRouter();
const [authorized, setAuthorized] = useState(false);
useEffect(async () => {
useEffect(() => {
// on initial load - run auth check
await authCheck(router.asPath);
(async () => {
await authCheck(router.asPath);
})();
// on route change start - hide page content by setting authorized to false
// #TODO: add the loading page when not yet authorized.
@@ -31,7 +35,6 @@ export default function RouteGuard({ children }) {
router.events.off("routeChangeComplete", authCheck);
// router.events.off("routeChangeError", onError);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -79,4 +82,4 @@ export default function RouteGuard({ children }) {
</div>
);
}
}
}

View File

@@ -1,4 +1,5 @@
import posthog from "posthog-js";
import { ENV, POSTHOG_API_KEY, POSTHOG_HOST, TELEMETRY_ENABLED } from "../utilities/config";
export const initPostHog = () => {

View File

@@ -1,6 +1,6 @@
import React, { useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
export default function Error({ text }) {
return (

View File

@@ -1,6 +1,6 @@
import React from "react";
import Error from "./Error";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useState } from "react";
import { useRouter } from "next/router";
import {
faCircle,
faCircleExclamation,
@@ -8,9 +8,10 @@ import {
faEye,
faEyeSlash,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import guidGenerator from "../utilities/randomId";
import { useState } from "react";
import { useRouter } from "next/router";
import Error from "./Error";
const InputField = (props) => {
const [passwordVisible, setPasswordVisible] = useState(false);

View File

@@ -1,9 +1,9 @@
import React from "react";
import { Listbox, Transition } from "@headlessui/react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheck, faPlus, faAngleDown } from "@fortawesome/free-solid-svg-icons";
import { Fragment } from "react";
import { useRouter } from "next/router";
import { faAngleDown,faCheck, faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Listbox, Transition } from "@headlessui/react";
/**
* This is the component that we use for drop down lists.

View File

@@ -1,8 +1,8 @@
import React from "react";
import Link from "next/link";
import Image from "next/image";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Link from "next/link";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
var classNames = require("classnames");

View File

@@ -1,8 +1,10 @@
import { Dialog, Transition } from "@headlessui/react";
import { Fragment, useState } from "react";
import InputField from "../InputField";
import addIncidentContact from "../../../pages/api/organization/addIncidentContact";
import { Dialog, Transition } from "@headlessui/react";
import addIncidentContact from "~/pages/api/organization/addIncidentContact";
import Button from "../buttons/Button";
import InputField from "../InputField";
const AddIncidentContactDialog = ({
isOpen,

View File

@@ -1,8 +1,9 @@
import { Dialog, Transition } from "@headlessui/react";
import { Fragment, useState } from "react";
import ListBox from "../Listbox";
import { useRouter } from "next/router";
import { Dialog, Transition } from "@headlessui/react";
import Button from "../buttons/Button";
import ListBox from "../Listbox";
const AddProjectMemberDialog = ({
isOpen,

View File

@@ -1,15 +1,17 @@
import { Dialog, Transition } from "@headlessui/react";
import { Fragment, useState } from "react";
import ListBox from "../Listbox";
import { useRouter } from "next/router";
import Button from "../buttons/Button";
import InputField from "../InputField";
import getLatestFileKey from "../../../pages/api/workspace/getLatestFileKey";
import { decryptAssymmetric, encryptAssymmetric } from "../../utilities/crypto";
import addServiceToken from "../../../pages/api/serviceToken/addServiceToken";
import nacl from "tweetnacl";
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Dialog, Transition } from "@headlessui/react";
import nacl from "tweetnacl";
import addServiceToken from "~/pages/api/serviceToken/addServiceToken";
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
import { decryptAssymmetric, encryptAssymmetric } from "../../utilities/crypto";
import Button from "../buttons/Button";
import InputField from "../InputField";
import ListBox from "../Listbox";
const envMapping = {
Development: "dev",
@@ -28,7 +30,7 @@ const AddServiceTokenDialog = ({
isOpen,
closeModal,
workspaceId,
workspaceName
workspaceName,
}) => {
const router = useRouter();
const [serviceToken, setServiceToken] = useState("");
@@ -38,52 +40,52 @@ const AddServiceTokenDialog = ({
const [serviceTokenCopied, setServiceTokenCopied] = useState(false);
const generateServiceToken = async () => {
const latestFileKey = await getLatestFileKey(workspaceId);
const latestFileKey = await getLatestFileKey(workspaceId);
const key = decryptAssymmetric({
ciphertext: latestFileKey.latestKey.encryptedKey,
nonce: latestFileKey.latestKey.nonce,
publicKey: latestFileKey.latestKey.sender.publicKey,
privateKey: localStorage.getItem("PRIVATE_KEY")
privateKey: localStorage.getItem("PRIVATE_KEY"),
});
// generate new public/private key pair
const pair = nacl.box.keyPair();
const publicKey = nacl.util.encodeBase64(pair.publicKey);
const privateKey = nacl.util.encodeBase64(pair.secretKey);
// encrypt workspace key under newly-generated public key
const { ciphertext: encryptedKey, nonce } = encryptAssymmetric({
plaintext: key,
publicKey,
privateKey
privateKey,
});
let newServiceToken = await addServiceToken({
name: serviceTokenName,
workspaceId,
name: serviceTokenName,
workspaceId,
environment: envMapping[serviceTokenEnv],
expiresIn: expiryMapping[serviceTokenExpiresIn],
publicKey,
expiresIn: expiryMapping[serviceTokenExpiresIn],
publicKey,
encryptedKey,
nonce
})
const serviceToken = newServiceToken + ',' + privateKey;
nonce,
});
const serviceToken = newServiceToken + "," + privateKey;
setServiceToken(serviceToken);
}
};
function copyToClipboard() {
// Get the text field
var copyText = document.getElementById("serviceToken");
// Select the text field
copyText.select();
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);
setServiceTokenCopied(true);
setTimeout(() => setServiceTokenCopied(false), 2000);
// Alert the copied text
@@ -94,7 +96,7 @@ const AddServiceTokenDialog = ({
closeModal();
setServiceTokenName("");
setServiceToken("");
}
};
return (
<div className="z-50">
@@ -123,18 +125,25 @@ const AddServiceTokenDialog = ({
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
{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">
{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.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-400 z-50"
>
Add a service token for {workspaceName}
Add a service token for{" "}
{workspaceName}
</Dialog.Title>
<div className="mt-2 mb-4">
<div className="flex flex-col">
<p className="text-sm text-gray-500">
Specify the name, environment, and expiry period. When a token is generated, you will only be able to see it once before it disappears. Make sure to save it somewhere.
Specify the name,
environment, and expiry
period. When a token is
generated, you will only be
able to see it once before
it disappears. Make sure to
save it somewhere.
</p>
</div>
</div>
@@ -154,7 +163,12 @@ const AddServiceTokenDialog = ({
<ListBox
selected={serviceTokenEnv}
onChange={setServiceTokenEnv}
data={["Development", "Staging", "Production", "Testing"]}
data={[
"Development",
"Staging",
"Production",
"Testing",
]}
width="full"
text="Environment: "
/>
@@ -162,8 +176,14 @@ const AddServiceTokenDialog = ({
<div className="max-h-28">
<ListBox
selected={serviceTokenExpiresIn}
onChange={setServiceTokenExpiresIn}
data={["1 day", "7 days", "1 month"]}
onChange={
setServiceTokenExpiresIn
}
data={[
"1 day",
"7 days",
"1 month",
]}
width="full"
text="Expires in: "
/>
@@ -171,17 +191,24 @@ const AddServiceTokenDialog = ({
<div className="max-w-max">
<div className="mt-6 flex flex-col justify-start w-max">
<Button
onButtonPressed={() => generateServiceToken()}
onButtonPressed={() =>
generateServiceToken()
}
color="mineshaft"
text="Add Service Token"
textDisabled="Add Service Token"
size="md"
active={serviceTokenName == "" ? false : true}
active={
serviceTokenName == ""
? false
: true
}
/>
</div>
</div>
</Dialog.Panel>
: <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
as="h3"
className="text-lg font-medium leading-6 text-gray-400 z-50"
@@ -191,17 +218,40 @@ const AddServiceTokenDialog = ({
<div className="mt-2 mb-4">
<div className="flex flex-col">
<p className="text-sm text-gray-500">
Once you close this popup, you will never see your service token again
Once you close this popup,
you will never see your
service token again
</p>
</div>
</div>
<div className="w-full">
<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" value={serviceToken} id="serviceToken" className="invisible bg-white/0 text-gray-400 py-2 w-full px-2 min-w-full outline-none"></input>
<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">{serviceToken}</div>
<input
type="text"
value={serviceToken}
id="serviceToken"
className="invisible bg-white/0 text-gray-400 py-2 w-full px-2 min-w-full outline-none"
></input>
<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">
{serviceToken}
</div>
<div className="group font-normal h-full relative inline-block text-gray-400 underline hover:text-primary duration-200">
<button onClick={copyToClipboard} className="h-full pl-3.5 pr-4 border-l border-white/20 py-2 hover:bg-white/[0.12] duration-200">
{serviceTokenCopied ? <FontAwesomeIcon icon={faCheck} className="pr-0.5"/> : <FontAwesomeIcon icon={faCopy} />}
<button
onClick={
copyToClipboard
}
className="h-full pl-3.5 pr-4 border-l border-white/20 py-2 hover:bg-white/[0.12] duration-200"
>
{serviceTokenCopied ? (
<FontAwesomeIcon
icon={faCheck}
className="pr-0.5"
/>
) : (
<FontAwesomeIcon
icon={faCopy}
/>
)}
</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
@@ -211,14 +261,16 @@ const AddServiceTokenDialog = ({
</div>
<div className="mt-6 flex flex-col justify-start w-max">
<Button
onButtonPressed={() => closeAddServiceTokenModal()}
onButtonPressed={() =>
closeAddServiceTokenModal()
}
color="mineshaft"
text="Close"
size="md"
/>
</div>
</Dialog.Panel>
}
)}
</Transition.Child>
</div>
</div>

View File

@@ -1,9 +1,10 @@
import { Dialog, Transition } from "@headlessui/react";
import { Fragment } from "react";
import InputField from "../InputField";
import { useRouter } from "next/router";
import Button from "../buttons/Button";
import { Dialog, Transition } from "@headlessui/react";
import { STRIPE_PRODUCT_STARTER } from "../../utilities/config";
import Button from "../buttons/Button";
import InputField from "../InputField";
const AddUserDialog = ({
isOpen,

View File

@@ -1,9 +1,10 @@
import { Dialog, Transition } from "@headlessui/react";
import { Fragment, useState } from "react";
import InputField from "../InputField";
import Image from "next/image";
import { Checkbox } from "../table/Checkbox";
import { Dialog, Transition } from "@headlessui/react";
import Button from "../buttons/Button";
import InputField from "../InputField";
import { Checkbox } from "../table/Checkbox";
/**
* The dialog modal for when the user wants to create a new workspace

View File

@@ -1,5 +1,6 @@
import { Dialog, Transition } from '@headlessui/react'
import { Fragment, useState } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import InputField from '../InputField';
// #TODO: USE THIS. Currently it's not. Kinda complicated to set up because of state.

View File

@@ -1,28 +1,29 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { useEffect, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import {
faGear,
faHouse,
faLink,
faMobile,
faUser,
} from "@fortawesome/free-solid-svg-icons";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import getOrganizations from "~/pages/api/organization/getOrgs";
import getOrganizationUserProjects from "~/pages/api/organization/GetOrgUserProjects";
import getOrganizationUsers from "~/pages/api/organization/GetOrgUsers";
import addUserToWorkspace from "~/pages/api/workspace/addUserToWorkspace";
import createWorkspace from "~/pages/api/workspace/createWorkspace";
import getWorkspaces from "~/pages/api/workspace/getWorkspaces";
import NavBarDashboard from "../navigation/NavBarDashboard";
import Listbox from "./Listbox";
import getWorkspaces from "../../pages/api/workspace/getWorkspaces";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faHouse,
faUser,
faGear,
faMobile,
faLink,
} from "@fortawesome/free-solid-svg-icons";
import AddWorkspaceDialog from "./dialog/AddWorkspaceDialog";
import createWorkspace from "../../pages/api/workspace/createWorkspace";
import getOrganizationUserProjects from "../../pages/api/organization/GetOrgUserProjects";
import getOrganizationUsers from "../../pages/api/organization/GetOrgUsers";
import addUserToWorkspace from "../../pages/api/workspace/addUserToWorkspace";
import getOrganizations from "../../pages/api/organization/getOrgs";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { decryptAssymmetric, encryptAssymmetric } from "../utilities/crypto";
import Button from "./buttons/Button";
import AddWorkspaceDialog from "./dialog/AddWorkspaceDialog";
import Listbox from "./Listbox";
export default function Layout({ children }) {
const router = useRouter();
@@ -48,9 +49,7 @@ export default function Layout({ children }) {
setLoading(true);
setTimeout(() => setLoading(false), 1500);
const workspaces = await getWorkspaces();
const currentWorkspaces = workspaces.map(
(workspace) => workspace.name
);
const currentWorkspaces = workspaces.map((workspace) => workspace.name);
if (!currentWorkspaces.includes(workspaceName)) {
const newWorkspace = await createWorkspace(
workspaceName,
@@ -141,7 +140,10 @@ export default function Layout({ children }) {
useEffect(async () => {
// Put a user in a workspace if they're not in one yet
if (localStorage.getItem("orgData.id") == null || localStorage.getItem("orgData.id") == "") {
if (
localStorage.getItem("orgData.id") == null ||
localStorage.getItem("orgData.id") == ""
) {
const userOrgs = await getOrganizations();
localStorage.setItem("orgData.id", userOrgs[0]._id);
}
@@ -150,7 +152,11 @@ export default function Layout({ children }) {
orgId: localStorage.getItem("orgData.id"),
});
let userWorkspaces = orgUserProjects;
if (userWorkspaces.length == 0 && (router.asPath != "/noprojects" && !router.asPath.includes("settings"))) {
if (
userWorkspaces.length == 0 &&
router.asPath != "/noprojects" &&
!router.asPath.includes("settings")
) {
router.push("/noprojects");
} else if (router.asPath != "/noprojects") {
const intendedWorkspaceId = router.asPath
@@ -158,7 +164,9 @@ export default function Layout({ children }) {
[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 (intendedWorkspaceId != "heroku" && !userWorkspaces
if (
intendedWorkspaceId != "heroku" &&
!userWorkspaces
.map((workspace) => workspace._id)
.includes(intendedWorkspaceId)
) {
@@ -207,7 +215,10 @@ export default function Layout({ children }) {
workspaceMapping[workspaceSelected] +
"?Development"
);
localStorage.setItem("projectData.id", workspaceMapping[workspaceSelected])
localStorage.setItem(
"projectData.id",
workspaceMapping[workspaceSelected]
);
}
} catch (error) {
console.log(error);
@@ -226,8 +237,8 @@ export default function Layout({ children }) {
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
PROJECT
</div>
{workspaceList.length>0
? <Listbox
{workspaceList.length > 0 ? (
<Listbox
selected={workspaceSelected}
onChange={setWorkspaceSelected}
data={workspaceList}
@@ -235,58 +246,67 @@ export default function Layout({ children }) {
text=""
workspaceMapping={workspaceMapping}
/>
: <Button
) : (
<Button
text="Add Project"
onButtonPressed={openModal}
color="mineshaft"
size="md"
icon={faPlus}
/>
}
)}
</div>
<ul>
{workspaceList.length > 0 && menuItems.map(({ href, title, emoji }) => (
<li className="mt-1.5 mx-2" key={title}>
{router.asPath.split("/")[1] ===
href.split("/")[1] &&
(["project", "billing", "org", "personal"].includes(
router.asPath.split("/")[2]
)
? router.asPath.split("/")[2] ===
href.split("/")[2]
: true) ? (
<div
className={`flex p-2 text-white text-sm rounded cursor-pointer bg-mineshaft-50/10`}
>
<div className="bg-primary w-1 rounded-xl mr-1"></div>
<p className="ml-2 mr-4">
{emoji}
</p>
{title}
</div>
) : router.asPath == "/noprojects" ? (
<div
className={`flex p-2 text-white text-sm rounded`}
>
<p className="ml-2 mr-4">
{emoji}
</p>
{title}
</div>
) : (
<Link href={href}>
{workspaceList.length > 0 &&
menuItems.map(({ href, title, emoji }) => (
<li className="mt-1.5 mx-2" key={title}>
{router.asPath.split("/")[1] ===
href.split("/")[1] &&
([
"project",
"billing",
"org",
"personal",
].includes(
router.asPath.split("/")[2]
)
? router.asPath.split(
"/"
)[2] === href.split("/")[2]
: true) ? (
<div
className={`flex p-2 text-white text-sm rounded cursor-pointer hover:bg-mineshaft-50/5`}
className={`flex p-2 text-white text-sm rounded cursor-pointer bg-mineshaft-50/10`}
>
<div className="bg-primary w-1 rounded-xl mr-1"></div>
<p className="ml-2 mr-4">
{emoji}
</p>
{title}
</div>
) : router.asPath ==
"/noprojects" ? (
<div
className={`flex p-2 text-white text-sm rounded`}
>
<p className="ml-2 mr-4">
{emoji}
</p>
{title}
</div>
</Link>
)}
</li>
))}
) : (
<Link href={href}>
<div
className={`flex p-2 text-white text-sm rounded cursor-pointer hover:bg-mineshaft-50/5`}
>
<p className="ml-2 mr-4">
{emoji}
</p>
{title}
</div>
</Link>
)}
</li>
))}
</ul>
</nav>
</aside>

View File

@@ -1,8 +1,6 @@
import React from "react";
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faXmark,
} from "@fortawesome/free-solid-svg-icons";
export default function BottonRightPopup({
buttonText,
@@ -11,22 +9,34 @@ export default function BottonRightPopup({
emoji,
textLine1,
textLine2,
setCheckDocsPopUpVisible
setCheckDocsPopUpVisible,
}) {
return (
<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" role="alert">
<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"
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="font-bold text-xl mr-2 mt-0.5 flex flex-row">
<div>{titleText}</div>
<div class="ml-2.5">{emoji}</div>
</div>
<button className="mt-1" onClick={() => setCheckDocsPopUpVisible(false)}>
<FontAwesomeIcon icon={faXmark} className="text-gray-400 text-2xl hover:text-red duration-200 cursor-pointer" />
<button
className="mt-1"
onClick={() => setCheckDocsPopUpVisible(false)}
>
<FontAwesomeIcon
icon={faXmark}
className="text-gray-400 text-2xl hover:text-red duration-200 cursor-pointer"
/>
</button>
</div>
<div class="block sm:inline px-6 mt-4 mb-0.5 text-gray-300">{textLine1}</div>
<div class="block sm:inline px-6 mt-4 mb-0.5 text-gray-300">
{textLine1}
</div>
<div class="block sm:inline mb-4 px-6">{textLine2}</div>
<div className="flex flex-row px-6 w-full">
{/*eslint-disable-next-line react/jsx-no-target-blank */}
<a
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}

View File

@@ -1,8 +1,9 @@
import React, { useEffect, useState } from "react";
import { useRouter } from "next/router";
import { faX } from "@fortawesome/free-solid-svg-icons";
import Button from "../buttons/Button";
import guidGenerator from "../../utilities/randomId";
import Button from "../buttons/Button";
const roles = ["admin", "user"];

View File

@@ -1,14 +1,16 @@
import React, { useEffect, useMemo, useState } from "react";
import { useRouter } from "next/router";
import Listbox from "../Listbox";
import uploadKeys from "../../../pages/api/workspace/uploadKeys";
import getLatestFileKey from "../../../pages/api/workspace/getLatestFileKey";
import deleteUserFromWorkspace from "../../../pages/api/workspace/deleteUserFromWorkspace";
import changeUserRoleInWorkspace from "../../../pages/api/workspace/changeUserRoleInWorkspace";
import deleteUserFromOrganization from "../../../pages/api/organization/deleteUserFromOrganization";
import { faX } from "@fortawesome/free-solid-svg-icons";
import Button from "../buttons/Button";
import deleteUserFromOrganization from "~/pages/api/organization/deleteUserFromOrganization";
import changeUserRoleInWorkspace from "~/pages/api/workspace/changeUserRoleInWorkspace";
import deleteUserFromWorkspace from "~/pages/api/workspace/deleteUserFromWorkspace";
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
import uploadKeys from "~/pages/api/workspace/uploadKeys";
import guidGenerator from "../../utilities/randomId";
import Button from "../buttons/Button";
import Listbox from "../Listbox";
const {
decryptAssymmetric,
@@ -81,7 +83,7 @@ const UserTable = ({
]);
};
useEffect(async () => {
useEffect(() => {
setMyRole(userData.filter((user) => user.email == myUser)[0]?.role);
}, [userData, myUser]);
@@ -145,7 +147,10 @@ const UserTable = ({
)
.map((row, index) => {
return (
<tr key={guidGenerator()} className="bg-bunker-800 hover:bg-bunker-800/5">
<tr
key={guidGenerator()}
className="bg-bunker-800 hover:bg-bunker-800/5"
>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{row.firstName}
</td>

View File

@@ -1,8 +1,8 @@
import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheck, faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
import { faCcMastercard, faCcVisa } from "@fortawesome/free-brands-svg-icons";
import { faCheck, faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
import { faCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
export default function Card({ card, changeSelectedCard, selected }) {
function creditCardBrandIcon(cc) {
@@ -37,24 +37,27 @@ export default function Card({ card, changeSelectedCard, selected }) {
<div className="flex flex-row items-center justify-between w-full">
<div className="flex flex-row items-center">
<p className="tracking-tighter mr-1 mt-0.5 flex">
{"****".split("").map(() => (
{"****".split("").map((_, index) => (
<FontAwesomeIcon
key={index}
className="text-xxxs mr-0.5"
icon={faCircle}
/>
))}
</p>
<p className="tracking-tighter mr-1 mt-0.5 flex">
{"****".split("").map(() => (
{"****".split("").map((_, index) => (
<FontAwesomeIcon
key={index}
className="text-xxxs mr-0.5"
icon={faCircle}
/>
))}
</p>
<p className="tracking-tighter mr-1 mt-0.5 flex">
{"****".split("").map(() => (
{"****".split("").map((_, index) => (
<FontAwesomeIcon
key={index}
className="text-xxxs mr-0.5"
icon={faCircle}
/>

View File

@@ -1,5 +1,6 @@
import React from "react";
import StripeRedirect from "../../pages/api/organization/StripeRedirect";
import StripeRedirect from "~/pages/api/organization/StripeRedirect";
export default function Plan({ plan }) {
return (
@@ -27,12 +28,12 @@ export default function Plan({ plan }) {
{plan.priceExplanation}
</p>
</div>
<p className="relative z-10 max-w-fit px-6 text-base text-gray-400">
{plan.text}
</p>
<p className="relative z-10 max-w-fit px-6 text-base text-gray-400">
{plan.subtext}
</p>
<p className="relative z-10 max-w-fit px-6 text-base text-gray-400">
{plan.text}
</p>
<p className="relative z-10 max-w-fit px-6 text-base text-gray-400">
{plan.subtext}
</p>
</div>
<div className="flex flex-row items-center">
{plan.current == false ? (
@@ -54,7 +55,17 @@ export default function Plan({ plan }) {
: "hover:bg-primary hover:text-black hover:border-primary"
} bg-bunker duration-200 cursor-pointer rounded-md flex w-max`}
>
<button onClick={() => StripeRedirect({orgId: localStorage.getItem("orgData.id")})}>{plan.buttonTextMain}</button>
<button
onClick={() =>
StripeRedirect({
orgId: localStorage.getItem(
"orgData.id"
),
})
}
>
{plan.buttonTextMain}
</button>
</div>
)}
<a href="/pricing" target='_blank rel="noopener"'>

View File

@@ -1,39 +1,45 @@
import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { Fragment } from "react";
import { faCircle } from "@fortawesome/free-solid-svg-icons";
import guidGenerator from "../utilities/randomId";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import guidGenerator from "../utilities/randomId";
/**
* This function splits the input of a dashboard field into the parts that are inside and outside of ${...}
* @param {string} text - the value of the input in the Dashboard Input Field
* @returns
* @returns
*/
const findReferences = (text) => {
var splitText = text.split('${');
var splitText = text.split("${");
let textArray = [splitText[0]];
for (var i = 1; i < splitText.length; i++) {
let insideBrackets = "${" + splitText[i].split('}')[0]
if (splitText[i].includes('}')) {
insideBrackets += "}"
let insideBrackets = "${" + splitText[i].split("}")[0];
if (splitText[i].includes("}")) {
insideBrackets += "}";
}
textArray.push(insideBrackets)
textArray.push(splitText[i].split('}')[1])
textArray.push(insideBrackets);
textArray.push(splitText[i].split("}")[1]);
}
return textArray;
}
};
/**
* This component renders the input fields on the dashboard
* @param {object} obj - the order number of a keyPair
* @param {number} obj.index - the order number of a keyPair
* @param {object} obj - the order number of a keyPair
* @param {number} obj.index - the order number of a keyPair
* @param {function} obj.onChangeHandler - what happens when the input is modified
* @param {string} obj.type - whether the input field is for a Key Name or for a Key Value
* @param {string} obj.value - value of the InputField
* @param {string} obj.value - value of the InputField
* @param {boolean} obj.blurred - whether the input field should be blurred (behind the gray dots) or not; this can be turned on/off in the dashboard
* @returns
* @returns
*/
const DashboardInputField = ({index, onChangeHandler, type, value, blurred}) => {
const DashboardInputField = ({
index,
onChangeHandler,
type,
value,
blurred,
}) => {
if (type === "varName") {
return (
<div className="flex-col w-full">
@@ -67,32 +73,58 @@ const DashboardInputField = ({index, onChangeHandler, type, value, blurred}) =>
} 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"
/>
<div
<div
className={`${
blurred
? "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 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((text, id) =>
id % 2 == 0 || text.length <= 2
? <span className="ph-no-capture">{text}</span>
: <span className="ph-no-capture text-yellow">
{text.slice(0, 2)}
<span className="ph-no-capture text-yellow-200/80">
{text.slice(2, text.length - 1)}
{findReferences(value).map((texts, id) => {
if (id % 2 == 0 || texts.length <= 2) {
return (
<span className="ph-no-capture" key={id}>
{texts}
</span>
);
}
return (
<span
className="ph-no-capture text-yellow"
key={id}
>
{texts.slice(0, 2)}
<span className="ph-no-capture text-yellow-200/80">
{texts.slice(2, texts.length - 1)}
</span>
{texts.slice(
texts.length - 1,
texts.length
) == "}" ? (
<span className="ph-no-capture text-yellow">
{texts.slice(
texts.length - 1,
texts.length
)}{" "}
</span>
) : (
<span className="ph-no-capture text-yellow-400">
{texts.slice(
texts.length - 1,
texts.length
)}{" "}
</span>
)}
</span>
{text.slice(text.length - 1, text.length) == "}"
? <span className="ph-no-capture text-yellow">{text.slice(text.length - 1, text.length)} </span>
: <span className="ph-no-capture text-yellow-400">{text.slice(text.length - 1, text.length)} </span>}
</span>)}
);
})}
</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">
<p className="ml-2"></p>
{value
.split("")
.slice(0,42)
.slice(0, 42)
.map(() => (
<FontAwesomeIcon
key={guidGenerator()}

View File

@@ -1,13 +1,12 @@
import { useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faUpload } from "@fortawesome/free-solid-svg-icons";
import guidGenerator from "../utilities/randomId";
import Error from "../basic/Error";
import Image from "next/image";
import parse from "../utilities/file";
import { faUpload } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Button from "../basic/buttons/Button";
import Error from "../basic/Error";
import parse from "../utilities/file";
import guidGenerator from "../utilities/randomId";
const DropZone = ({
setData,

View File

@@ -1,25 +1,27 @@
import React, { useState, Fragment, useEffect } from "react";
import { useRouter } from "next/router";
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable react/jsx-key */
import React, { Fragment, useEffect,useState } from "react";
import Image from "next/image";
import logout from "../../pages/api/auth/Logout";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useRouter } from "next/router";
import { faGithub,faSlack } from "@fortawesome/free-brands-svg-icons";
import { faCircleQuestion } from "@fortawesome/free-regular-svg-icons";
import {
faAngleDown,
faBook,
faGear,
faCoins,
faRightFromBracket,
faEnvelope,
faPlus,
faAngleDown
faGear,
faPlus,
faRightFromBracket,
} from "@fortawesome/free-solid-svg-icons";
import { faSlack, faGithub } from "@fortawesome/free-brands-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Menu, Transition } from "@headlessui/react";
import getUser from "../../pages/api/user/getUser";
import getOrganizations from "../../pages/api/organization/getOrgs";
import getOrganization from "../../pages/api/organization/GetOrg";
import logout from "~/pages/api/auth/Logout";
import getOrganization from "~/pages/api/organization/GetOrg";
import getOrganizations from "~/pages/api/organization/getOrgs";
import getUser from "~/pages/api/user/getUser";
import guidGenerator from "../utilities/randomId";
const supportOptions = [
@@ -87,7 +89,7 @@ export default function Navbar({ onButtonPressed }) {
</div>
<div className="relative flex justify-start items-center mx-2 z-40">
<Menu as="div" className="relative inline-block text-left">
<div className="mr-4">
<div className="mr-4">
<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">
<FontAwesomeIcon
className="text-xl"
@@ -106,6 +108,7 @@ export default function Navbar({ onButtonPressed }) {
>
<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">
{supportOptions.map((option) => (
// eslint-disable-next-line react/jsx-no-target-blank
<a
key={guidGenerator()}
target="_blank"
@@ -128,7 +131,10 @@ export default function Navbar({ onButtonPressed }) {
<div>
<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}
<FontAwesomeIcon icon={faAngleDown} className="ml-2 mt-1 text-sm text-gray-300 hover:text-lime-100"/>
<FontAwesomeIcon
icon={faAngleDown}
className="ml-2 mt-1 text-sm text-gray-300 hover:text-lime-100"
/>
</Menu.Button>
</div>
<Transition
@@ -145,13 +151,15 @@ export default function Navbar({ onButtonPressed }) {
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
SIGNED IN AS
</div>
<div
<div
onClick={() =>
router.push(
"/settings/personal/" + router.query.id
"/settings/personal/" +
router.query.id
)
}
className="flex flex-row items-center px-1 mx-1 my-1 hover:bg-white/5 cursor-pointer rounded-md">
className="flex flex-row items-center px-1 mx-1 my-1 hover:bg-white/5 cursor-pointer rounded-md"
>
<div className="bg-white/10 h-8 w-9 rounded-full flex items-center justify-center text-gray-300">
{user?.firstName?.charAt(0)}
</div>
@@ -159,7 +167,8 @@ export default function Navbar({ onButtonPressed }) {
<div>
<p className="text-gray-300 px-2 pt-1 text-sm">
{" "}
{user?.firstName} {user?.lastName}
{user?.firstName}{" "}
{user?.lastName}
</p>
<p className="text-gray-400 px-2 pb-1 text-xs">
{" "}
@@ -235,7 +244,10 @@ export default function Navbar({ onButtonPressed }) {
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"
>
<span className="rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4">
<FontAwesomeIcon icon={faPlus} className="ml-1" />
<FontAwesomeIcon
icon={faPlus}
className="ml-1"
/>
</span>
<div className="text-sm ml-1">
Invite Members
@@ -243,43 +255,45 @@ export default function Navbar({ onButtonPressed }) {
</div>
</button>
</div>
{orgs?.length > 1 && <div className="px-1 pt-1">
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
OTHER ORGANIZATIONS
</div>
<div className="flex flex-col items-start px-1 mt-3 mb-2">
{orgs
.filter(
(org) =>
org._id !=
localStorage.getItem(
"orgData.id"
)
)
.map((org) => (
<div
key={guidGenerator()}
onClick={() => {
localStorage.setItem(
"orgData.id",
org._id
);
router.reload();
}}
className="flex flex-row justify-start items-center hover:bg-white/5 w-full p-1.5 cursor-pointer rounded-md"
>
<div className="bg-white/10 h-7 w-8 rounded-md flex items-center justify-center text-gray-300">
{org.name.charAt(0)}
{orgs?.length > 1 && (
<div className="px-1 pt-1">
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
OTHER ORGANIZATIONS
</div>
<div className="flex flex-col items-start px-1 mt-3 mb-2">
{orgs
.filter(
(org) =>
org._id !=
localStorage.getItem(
"orgData.id"
)
)
.map((org) => (
<div
key={guidGenerator()}
onClick={() => {
localStorage.setItem(
"orgData.id",
org._id
);
router.reload();
}}
className="flex flex-row justify-start items-center hover:bg-white/5 w-full p-1.5 cursor-pointer rounded-md"
>
<div className="bg-white/10 h-7 w-8 rounded-md flex items-center justify-center text-gray-300">
{org.name.charAt(0)}
</div>
<div className="flex items-center justify-between w-full">
<p className="text-gray-300 px-2 text-sm">
{org.name}
</p>
</div>
</div>
<div className="flex items-center justify-between w-full">
<p className="text-gray-300 px-2 text-sm">
{org.name}
</p>
</div>
</div>
))}
))}
</div>
</div>
</div>}
)}
<div className="px-1 py-1">
<Menu.Item>
{({ active }) => (

View File

@@ -1,47 +1,57 @@
import React, { useEffect, useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faAngleRight, faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
import { faCcMastercard, faCcVisa } from "@fortawesome/free-brands-svg-icons";
import { faCircle } from "@fortawesome/free-solid-svg-icons";
import getOrganization from "../../pages/api/organization/GetOrg";
import getWorkspaceInfo from "../../pages/api/workspace/getWorkspaceInfo";
import { useRouter } from "next/router";
import { faCcMastercard, faCcVisa } from "@fortawesome/free-brands-svg-icons";
import {
faAngleRight,
faQuestionCircle,
} from "@fortawesome/free-solid-svg-icons";
import { faCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
export default function NavHeader({ pageName, isProjectRelated}) {
const [orgName, setOrgName] = useState("");
const [workspaceName, setWorkspaceName] = useState("");
const router = useRouter();
import getOrganization from "~/pages/api/organization/GetOrg";
import getWorkspaceInfo from "~/pages/api/workspace/getWorkspaceInfo";
useEffect(async () => {
let org = await getOrganization({
orgId: localStorage.getItem("orgData.id"),
});
setOrgName(org.name);
let workspace = await getWorkspaceInfo({
workspaceId: router.query.id,
});
setWorkspaceName(workspace.name);
}, []);
export default function NavHeader({ pageName, isProjectRelated }) {
const [orgName, setOrgName] = useState("");
const [workspaceName, setWorkspaceName] = useState("");
const router = useRouter();
useEffect(() => {
(async () => {
let org = await getOrganization({
orgId: localStorage.getItem("orgData.id"),
});
setOrgName(org.name);
let workspace = await getWorkspaceInfo({
workspaceId: router.query.id,
});
setWorkspaceName(workspace.name);
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<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">
{orgName?.charAt(0)}
</div>
<div className="text-primary text-sm font-semibold">
{orgName}
</div>
{isProjectRelated && <>
<FontAwesomeIcon icon={faAngleRight} className="ml-3 text-sm text-gray-400 mr-3" />
<div className="font-semibold text-primary text-sm">
{workspaceName}
</div>
</>
}
<FontAwesomeIcon icon={faAngleRight} className="ml-3 text-sm text-gray-400 mr-3" />
<div className="text-gray-400 text-sm">
{pageName}
</div>
</div>
<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">
{orgName?.charAt(0)}
</div>
<div className="text-primary text-sm font-semibold">{orgName}</div>
{isProjectRelated && (
<>
<FontAwesomeIcon
icon={faAngleRight}
className="ml-3 text-sm text-gray-400 mr-3"
/>
<div className="font-semibold text-primary text-sm">
{workspaceName}
</div>
</>
)}
<FontAwesomeIcon
icon={faAngleRight}
className="ml-3 text-sm text-gray-400 mr-3"
/>
<div className="text-gray-400 text-sm">{pageName}</div>
</div>
);
}

View File

@@ -1,29 +1,28 @@
import token from "../../pages/api/auth/Token"
import { PATH } from '../../const';
import token from "~/pages/api/auth/Token";
import { PATH } from "../../const";
export default class SecurityClient {
static authOrigins = [PATH]
static #token = '';
static authOrigins = [PATH];
static #token = "";
contructor() {
contructor() {}
}
static setToken(token) {
this.#token = token;
}
static setToken(token) {
this.#token = token;
}
static async fetchCall(resource, options) {
let req = new Request(resource, options);
const destOrigin = new URL(req.url).origin;
static async fetchCall(resource, options) {
let req = new Request(resource, options);
const destOrigin = new URL(req.url).origin;
if (this.#token == "") {
this.setToken(await token());
}
if (this.#token == "") {
this.setToken(await token())
}
if (this.#token && this.authOrigins.includes(destOrigin)) {
req.headers.set('Authorization', "Bearer " + this.#token);
return fetch(req);
}
}
}
if (this.#token && this.authOrigins.includes(destOrigin)) {
req.headers.set("Authorization", "Bearer " + this.#token);
return fetch(req);
}
}
}

View File

@@ -1,12 +1,13 @@
import login1 from "../../pages/api/auth/Login1";
import login2 from "../../pages/api/auth/Login2";
import Aes256Gcm from "../../components/aes-256-gcm";
import pushKeys from "./pushKeys";
import Aes256Gcm from "~/components/aes-256-gcm";
import login1 from "~/pages/api/auth/Login1";
import login2 from "~/pages/api/auth/Login2";
import getOrganizations from "~/pages/api/organization/getOrgs";
import getOrganizationUserProjects from "~/pages/api/organization/GetOrgUserProjects";
import { initPostHog } from "../analytics/posthog";
import getOrganizations from "../../pages/api/organization/getOrgs";
import getOrganizationUserProjects from "../../pages/api/organization/GetOrgUserProjects";
import SecurityClient from "./SecurityClient";
import { ENV } from "./config";
import pushKeys from "./pushKeys";
import SecurityClient from "./SecurityClient";
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
@@ -77,7 +78,14 @@ const attemptLogin = async (
encryptedPrivateKey,
iv,
tag,
password.slice(0, 32).padStart(32 + (password.slice(0, 32).length - new Blob([password]).size), "0")
password
.slice(0, 32)
.padStart(
32 +
(password.slice(0, 32).length -
new Blob([password]).size),
"0"
)
);
try {
@@ -101,10 +109,14 @@ const attemptLogin = async (
}
const userOrgs = await getOrganizations();
const userOrgsData = userOrgs.map(org => org._id);
const userOrgsData = userOrgs.map((org) => org._id);
let orgToLogin;
if (userOrgsData.includes(localStorage.getItem("orgData.id"))) {
if (
userOrgsData.includes(
localStorage.getItem("orgData.id")
)
) {
orgToLogin = localStorage.getItem("orgData.id");
} else {
orgToLogin = userOrgsData[0];
@@ -114,17 +126,29 @@ const attemptLogin = async (
let orgUserProjects = await getOrganizationUserProjects({
orgId: orgToLogin,
});
orgUserProjects = orgUserProjects?.map(project => project._id);
orgUserProjects = orgUserProjects?.map(
(project) => project._id
);
let projectToLogin;
if (orgUserProjects.includes(localStorage.getItem("projectData.id"))) {
if (
orgUserProjects.includes(
localStorage.getItem("projectData.id")
)
) {
projectToLogin = localStorage.getItem("projectData.id");
} else {
try {
projectToLogin = orgUserProjects[0];
localStorage.setItem("projectData.id", projectToLogin);
localStorage.setItem(
"projectData.id",
projectToLogin
);
} catch (error) {
console.log("ERROR: User likely has no projects. ", error)
console.log(
"ERROR: User likely has no projects. ",
error
);
}
}

View File

@@ -1,6 +1,7 @@
import changePassword2 from "~/pages/api/auth/ChangePassword2";
import SRP1 from "~/pages/api/auth/SRP1";
import Aes256Gcm from "../aes-256-gcm";
import SRP1 from "../../pages/api/auth/SRP1";
import changePassword2 from "../../pages/api/auth/ChangePassword2";
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
@@ -41,7 +42,7 @@ const changePassword = async (
let serverPublicKey, salt;
try {
const res = await SRP1({
clientPublicKey: clientPublicKey
clientPublicKey: clientPublicKey,
});
serverPublicKey = res.serverPublicKey;
salt = res.salt;
@@ -54,54 +55,70 @@ const changePassword = async (
clientOldPassword.setServerPublicKey(serverPublicKey);
const clientProof = clientOldPassword.getProof(); // called M1
clientNewPassword.init({
username: email,
password: newPassword
}, async () => {
clientNewPassword.createVerifier(async (err, result) => {
let { ciphertext, iv, tag } = Aes256Gcm.encrypt(
localStorage.getItem("PRIVATE_KEY"),
newPassword.slice(0, 32).padStart(32 + (newPassword.slice(0, 32).length - new Blob([newPassword]).size), "0")
);
clientNewPassword.init(
{
username: email,
password: newPassword,
},
async () => {
clientNewPassword.createVerifier(
async (err, result) => {
let { ciphertext, iv, tag } = Aes256Gcm.encrypt(
localStorage.getItem("PRIVATE_KEY"),
newPassword
.slice(0, 32)
.padStart(
32 +
(newPassword.slice(0, 32)
.length -
new Blob([newPassword])
.size),
"0"
)
);
if (ciphertext) {
localStorage.setItem(
"encryptedPrivateKey",
ciphertext
);
localStorage.setItem("iv", iv);
localStorage.setItem("tag", tag);
let res;
try {
res = await changePassword2({
encryptedPrivateKey: ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier,
clientProof
});
if (res.status == 400) {
setCurrentPasswordError(true);
} else if (res.status == 200) {
setPasswordChanged(true);
setCurrentPassword("");
setNewPassword("");
if (ciphertext) {
localStorage.setItem(
"encryptedPrivateKey",
ciphertext
);
localStorage.setItem("iv", iv);
localStorage.setItem("tag", tag);
let res;
try {
res = await changePassword2({
encryptedPrivateKey: ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier,
clientProof,
});
if (res.status == 400) {
setCurrentPasswordError(true);
} else if (res.status == 200) {
setPasswordChanged(true);
setCurrentPassword("");
setNewPassword("");
}
} catch (err) {
setCurrentPasswordError(true);
console.log(err);
}
}
} catch (err) {
setCurrentPasswordError(true)
console.log(err);
}
}
});
});
);
}
);
}
);
} catch (error) {
console.log("Something went wrong during changing the password", slat, serverPublicKey);
console.log(
"Something went wrong during changing the password",
slat,
serverPublicKey
);
}
return true;
};

View File

@@ -1,6 +1,6 @@
import getSecrets from "../../pages/api/files/GetSecrets";
import guidGenerator from "./randomId";
import getSecrets from "~/pages/api/files/GetSecrets";
import guidGenerator from "./randomId";
const {
decryptAssymmetric,
@@ -21,15 +21,12 @@ const getSecretsForProject = async ({
setFileState,
setIsKeyAvailable,
setData,
workspaceId
workspaceId,
}) => {
try {
let file;
try {
file = await getSecrets(
workspaceId,
envMapping[env]
);
file = await getSecrets(workspaceId, envMapping[env]);
setFileState(file);
} catch (error) {
@@ -98,7 +95,9 @@ const getSecretsForProject = async ({
line["type"],
]);
} catch (error) {
console.log("Something went wrong during accessing or decripting secrets.");
console.log(
"Something went wrong during accessing or decripting secrets."
);
}
return true;
};

View File

@@ -1,6 +1,7 @@
import issueBackupPrivateKey from "~/pages/api/auth/IssueBackupPrivateKey";
import SRP1 from "~/pages/api/auth/SRP1";
import Aes256Gcm from "../aes-256-gcm";
import SRP1 from "../../pages/api/auth/SRP1";
import issueBackupPrivateKey from "../../pages/api/auth/IssueBackupPrivateKey";
import generateBackupPDF from "./generateBackupPDF";
const nacl = require("tweetnacl");
@@ -24,7 +25,7 @@ const issueBackupKey = async ({
password,
personalName,
setBackupKeyError,
setBackupKeyIssued
setBackupKeyIssued,
}) => {
try {
setBackupKeyError(false);
@@ -40,7 +41,7 @@ const issueBackupKey = async ({
let serverPublicKey, salt;
try {
const res = await SRP1({
clientPublicKey: clientPublicKey
clientPublicKey: clientPublicKey,
});
serverPublicKey = res.serverPublicKey;
salt = res.salt;
@@ -55,35 +56,40 @@ const issueBackupKey = async ({
const generatedKey = crypto.randomBytes(16).toString("hex");
clientKey.init({
username: email,
password: generatedKey
}, async () => {
clientKey.createVerifier(async (err, result) => {
clientKey.init(
{
username: email,
password: generatedKey,
},
async () => {
clientKey.createVerifier(async (err, result) => {
let { ciphertext, iv, tag } = Aes256Gcm.encrypt(
localStorage.getItem("PRIVATE_KEY"),
generatedKey
);
const res = await issueBackupPrivateKey({
encryptedPrivateKey: ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier,
clientProof,
});
let { ciphertext, iv, tag } = Aes256Gcm.encrypt(
localStorage.getItem("PRIVATE_KEY"),
generatedKey
);
const res = await issueBackupPrivateKey({
encryptedPrivateKey: ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier,
clientProof
if (res.status == 400) {
setBackupKeyError(true);
} else if (res.status == 200) {
generateBackupPDF(
personalName,
email,
generatedKey
);
setBackupKeyIssued(true);
}
});
if (res.status == 400) {
setBackupKeyError(true);
} else if (res.status == 200) {
generateBackupPDF(personalName, email, generatedKey);
setBackupKeyIssued(true);
}
});
})
}
);
}
);
} catch (error) {

View File

@@ -1,6 +1,6 @@
import getLatestFileKey from "../../pages/api/workspace/getLatestFileKey";
import getWorkspaceKeys from "../../pages/api/workspace/getWorkspaceKeys";
import uploadSecrets from "../../pages/api/files/UploadSecrets";
import uploadSecrets from "~/pages/api/files/UploadSecrets";
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
import getWorkspaceKeys from "~/pages/api/workspace/getWorkspaceKeys";
const crypto = require("crypto");
const {

View File

@@ -1,11 +1,8 @@
import publicKeyInfical from "../../pages/api/auth/publicKeyInfisical"
import changeHerokuConfigVars from "../../pages/api/integrations/ChangeHerokuConfigVars";
import publicKeyInfical from "~/pages/api/auth/publicKeyInfisical";
import changeHerokuConfigVars from "~/pages/api/integrations/ChangeHerokuConfigVars";
const crypto = require("crypto");
const {
encryptSymmetric,
encryptAssymmetric,
} = require("./crypto");
const { encryptSymmetric, encryptAssymmetric } = require("./crypto");
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
@@ -71,7 +68,7 @@ const pushKeysIntegration = async ({ obj, integrationId }) => {
nonce,
};
changeHerokuConfigVars({integrationId, key, secrets})
changeHerokuConfigVars({ integrationId, key, secrets });
};
export default pushKeysIntegration;

16
frontend/jsconfig.json Normal file
View File

@@ -0,0 +1,16 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"~/components/*": [
"components/*"
],
"~/utilities/*": [
"components/utilities/*"
],
"~/pages/*": [
"pages/*"
],
}
}
}

View File

@@ -4,7 +4,8 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"start:docker": "next build && next start"
"start:docker": "next build && next start",
"lint": "next lint"
},
"dependencies": {
"@apihero/github": "^1.1.5",
@@ -55,8 +56,12 @@
"devDependencies": {
"@tailwindcss/typography": "^0.5.4",
"autoprefixer": "^10.4.7",
"eslint": "^8.28.0",
"eslint-config-next": "^13.0.5",
"eslint-plugin-simple-import-sort": "^8.0.0",
"postcss": "^8.4.14",
"prettier": "2.7.1",
"tailwindcss": "^3.1.4"
"tailwindcss": "^3.1.4",
"typescript": "^4.9.3"
}
}

View File

@@ -1,13 +1,16 @@
import "../styles/globals.css";
import "@fortawesome/fontawesome-svg-core/styles.css";
import { config } from "@fortawesome/fontawesome-svg-core";
import Layout from "../components/basic/layout";
import RouteGuard from "../components/RouteGuard";
import { publicPaths } from "../const.js";
import { useEffect } from "react";
import { useRouter } from "next/router";
import { initPostHog } from "../components/analytics/posthog";
import { ENV } from "../components/utilities/config";
import { config } from "@fortawesome/fontawesome-svg-core";
import { initPostHog } from "~/components/analytics/posthog";
import Layout from "~/components/basic/layout";
import RouteGuard from "~/components/RouteGuard";
import { ENV } from "~/utilities/config";
import { publicPaths } from "../const.js";
import "@fortawesome/fontawesome-svg-core/styles.css";
import "../styles/globals.css";
config.autoAddCss = false;
@@ -39,9 +42,7 @@ const App = ({ Component, pageProps, ...appProps }) => {
publicPaths.includes("/" + appProps.router.pathname.split("/")[1]) ||
!Component.requireAuth
) {
return (
<Component {...pageProps} />
)
return <Component {...pageProps} />;
}
return (

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -6,28 +7,34 @@ import { PATH } from "../../../const";
* @param {*} clientPublicKey
* @returns
*/
const changePassword2 = ({encryptedPrivateKey, iv, tag, salt, verifier, clientProof}) => {
const changePassword2 = ({
encryptedPrivateKey,
iv,
tag,
salt,
verifier,
clientProof,
}) => {
return SecurityClient.fetchCall(PATH + "/api/v1/password/change-password", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
"clientProof": clientProof,
"encryptedPrivateKey": encryptedPrivateKey,
"iv": iv,
"tag": tag,
"salt": salt,
"verifier": verifier
clientProof: clientProof,
encryptedPrivateKey: encryptedPrivateKey,
iv: iv,
tag: tag,
salt: salt,
verifier: verifier,
}),
})
.then(async res => {
}).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to change the password');
console.log("Failed to change the password");
}
})
});
};
export default changePassword2;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient.js";
import SecurityClient from "~/utilities/SecurityClient.js";
import { PATH } from "../../../const.js";
/**
@@ -14,14 +15,13 @@ const checkAuth = async (req, res) => {
headers: {
"Content-Type": "application/json",
},
})
.then(res => {
}).then((res) => {
if (res.status == 200) {
return res;
} else {
console.log("Not authorized");
}
})
});
};
export default checkAuth;

View File

@@ -1,32 +1,42 @@
import SecurityClient from "../../../components/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
*/
const issueBackupPrivateKey = ({encryptedPrivateKey, iv, tag, salt, verifier, clientProof}) => {
return SecurityClient.fetchCall(PATH + "/api/v1/password/backup-private-key", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
"clientProof": clientProof,
"encryptedPrivateKey": encryptedPrivateKey,
"iv": iv,
"tag": tag,
"salt": salt,
"verifier": verifier
}),
})
.then(res => {
const issueBackupPrivateKey = ({
encryptedPrivateKey,
iv,
tag,
salt,
verifier,
clientProof,
}) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/password/backup-private-key",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
clientProof: clientProof,
encryptedPrivateKey: encryptedPrivateKey,
iv: iv,
tag: tag,
salt: salt,
verifier: verifier,
}),
}
).then((res) => {
if (res.status == 200) {
return res;
} else {
return res;
console.log("Failed to issue the backup key");
}
})
});
};
export default issueBackupPrivateKey;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -14,9 +15,8 @@ const logout = async (req, res) => {
headers: {
"Content-Type": "application/json",
},
credentials: "include"
})
.then(res => {
credentials: "include",
}).then((res) => {
if (res.status == 200) {
SecurityClient.setToken("");
// Delete the cookie by not setting a value; Alternatively clear the local storage
@@ -30,7 +30,7 @@ const logout = async (req, res) => {
} else {
console.log("Failed to log out");
}
})
});
};
export default logout;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -6,7 +7,7 @@ import { PATH } from "../../../const";
* @param {*} clientPublicKey
* @returns
*/
const SRP1 = ({clientPublicKey}) => {
const SRP1 = ({ clientPublicKey }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/password/srp1", {
method: "POST",
headers: {
@@ -15,14 +16,13 @@ const SRP1 = ({clientPublicKey}) => {
body: JSON.stringify({
clientPublicKey,
}),
})
.then(async res => {
}).then(async (res) => {
if (res.status == 200) {
return (await res.json());
return await res.json();
} else {
console.log('Failed to do the first step of SRP');
console.log("Failed to do the first step of SRP");
}
})
});
};
export default SRP1;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient.js";
import SecurityClient from "~/utilities/SecurityClient.js";
import { PATH } from "../../../const.js";
/**
@@ -8,26 +9,28 @@ import { PATH } from "../../../const.js";
* @returns
*/
const getSecrets = async (workspaceId, env) => {
return SecurityClient.fetchCall(PATH + "/api/v1/secret/" +
workspaceId +
"?" +
new URLSearchParams({
environment: env,
channel: "web",
return SecurityClient.fetchCall(
PATH +
"/api/v1/secret/" +
workspaceId +
"?" +
new URLSearchParams({
environment: env,
channel: "web",
}),
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
), {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
).then(async (res) => {
if (res.status == 200) {
return (await res.json());
return await res.json();
} else {
console.log('Failed to get project secrets');
console.log("Failed to get project secrets");
}
})
});
};
export default getSecrets;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -19,14 +20,13 @@ const uploadSecrets = async ({ workspaceId, secrets, keys, environment }) => {
environment,
channel: "web",
}),
})
.then(async res => {
}).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to push secrets');
console.log("Failed to push secrets");
}
})
});
};
export default uploadSecrets;

View File

@@ -1,24 +1,27 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
const changeHerokuConfigVars = ({ integrationId, key, secrets }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/integration/" + integrationId + "/sync", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
key,
secrets,
})
})
.then(async res => {
return SecurityClient.fetchCall(
PATH + "/api/v1/integration/" + integrationId + "/sync",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
key,
secrets,
}),
}
).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to sync secrets to Heroku');
console.log("Failed to sync secrets to Heroku");
}
})
});
};
export default changeHerokuConfigVars;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -7,19 +8,21 @@ import { PATH } from "../../../const";
* @returns
*/
const deleteIntegration = ({ integrationId }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/integration/" + integrationId, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
return SecurityClient.fetchCall(
PATH + "/api/v1/integration/" + integrationId,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).workspace;
} else {
console.log('Failed to delete an integration');
console.log("Failed to delete an integration");
}
})
});
};
export default deleteIntegration;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -7,19 +8,21 @@ import { PATH } from "../../../const";
* @returns
*/
const deleteIntegrationAuth = ({ integrationAuthId }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/integration-auth/" + integrationAuthId, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
return SecurityClient.fetchCall(
PATH + "/api/v1/integration-auth/" + integrationAuthId,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to delete an integration authorization');
console.log("Failed to delete an integration authorization");
}
})
});
};
export default deleteIntegrationAuth;

View File

@@ -1,20 +1,23 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
const getIntegrationApps = ({ integrationAuthId }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/integration-auth/" + integrationAuthId + "/apps", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
return SecurityClient.fetchCall(
PATH + "/api/v1/integration-auth/" + integrationAuthId + "/apps",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).apps;
} else {
console.log('Failed to get available apps for an integration');
console.log("Failed to get available apps for an integration");
}
})
});
};
export default getIntegrationApps;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
const getIntegrations = () => {
@@ -7,14 +8,13 @@ const getIntegrations = () => {
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
}).then(async (res) => {
if (res.status == 200) {
return (await res.json()).integrations;
} else {
console.log('Failed to get project integrations');
console.log("Failed to get project integrations");
}
})
});
};
export default getIntegrations;

View File

@@ -1,32 +1,35 @@
import SecurityClient from "../../../components/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
* @returns
*/
const startIntegration = ({ integrationId, appName, environment }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/integration/" + integrationId, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
"update": {
app: appName,
environment,
isActive: true
}
})
})
.then(async res => {
return SecurityClient.fetchCall(
PATH + "/api/v1/integration/" + integrationId,
{
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
update: {
app: appName,
environment,
isActive: true,
},
}),
}
).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to start an integration');
console.log("Failed to start an integration");
}
})
});
};
export default startIntegration;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -7,24 +8,26 @@ import { PATH } from "../../../const";
* @returns
*/
const AuthorizeIntegration = ({ workspaceId, code, integration }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/integration-auth/oauth-token", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
workspaceId,
code,
integration
}),
})
.then(async res => {
return SecurityClient.fetchCall(
PATH + "/api/v1/integration-auth/oauth-token",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
workspaceId,
code,
integration,
}),
}
).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to authorize the integration');
console.log("Failed to authorize the integration");
}
})
});
};
export default AuthorizeIntegration;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -7,19 +8,21 @@ import { PATH } from "../../../const";
* @returns
*/
const getWorkspaceAuthorizations = ({ workspaceId }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + workspaceId + "/authorizations", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
return SecurityClient.fetchCall(
PATH + "/api/v1/workspace/" + workspaceId + "/authorizations",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).authorizations;
} else {
console.log('Failed to get project authorizations');
console.log("Failed to get project authorizations");
}
})
});
};
export default getWorkspaceAuthorizations;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -7,19 +8,21 @@ import { PATH } from "../../../const";
* @returns
*/
const getWorkspaceIntegrations = ({ workspaceId }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + workspaceId + "/integrations", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
return SecurityClient.fetchCall(
PATH + "/api/v1/workspace/" + workspaceId + "/integrations",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).integrations;
} else {
console.log('Failed to get the project integrations');
console.log("Failed to get the project integrations");
}
})
});
};
export default getWorkspaceIntegrations;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -8,19 +9,21 @@ import { PATH } from "../../../const";
* @returns
*/
const getOrganization = (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + req.orgId, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + req.orgId,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).organization;
} else {
console.log('Failed to get org info');
console.log("Failed to get org info");
}
})
});
};
export default getOrganization;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -8,19 +9,21 @@ import { PATH } from "../../../const";
* @returns
*/
const getOrganizationProjects = (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/organization/" + req.orgId + "/workspaces", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
return SecurityClient.fetchCall(
PATH + "/api/organization/" + req.orgId + "/workspaces",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).workspaces;
} else {
console.log('Failed to get projects for an org');
console.log("Failed to get projects for an org");
}
})
});
};
export default getOrganizationProjects;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -8,19 +9,21 @@ import { PATH } from "../../../const";
* @returns
*/
const getOrganizationSubscriptions = (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + req.orgId + "/subscriptions", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + req.orgId + "/subscriptions",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).subscriptions;
} else {
console.log('Failed to get org subscriptions');
console.log("Failed to get org subscriptions");
}
})
});
};
export default getOrganizationSubscriptions;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -8,19 +9,21 @@ import { PATH } from "../../../const";
* @returns
*/
const getOrganizationUserProjects = (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + req.orgId + "/my-workspaces", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + req.orgId + "/my-workspaces",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).workspaces;
} 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;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -8,19 +9,21 @@ import { PATH } from "../../../const";
* @returns
*/
const getOrganizationUsers = (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + req.orgId + "/users", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + req.orgId + "/users",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).users;
} else {
console.log('Failed to get org users');
console.log("Failed to get org users");
}
})
});
};
export default getOrganizationUsers;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -7,20 +8,22 @@ import { PATH } from "../../../const";
* @param {*} res
* @returns
*/
const StripeRedirect = ({orgId}) => {
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + orgId + "/customer-portal-session", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return window.location.href = (await res.json()).url;
} else {
console.log('Failed to redirect to Stripe');
const StripeRedirect = ({ orgId }) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + orgId + "/customer-portal-session",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
}
})
).then(async (res) => {
if (res.status == 200) {
return (window.location.href = (await res.json()).url);
} else {
console.log("Failed to redirect to Stripe");
}
});
};
export default StripeRedirect;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -7,22 +8,24 @@ import { PATH } from "../../../const";
* @returns
*/
const addIncidentContact = (organizationId, email) => {
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + organizationId + "/incidentContactOrg", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
}),
})
.then(async res => {
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + organizationId + "/incidentContactOrg",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
}),
}
).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to add an incident contact');
console.log("Failed to add an incident contact");
}
})
});
};
export default addIncidentContact;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -17,14 +18,13 @@ const addUserToOrg = (email, orgId) => {
inviteeEmail: email,
organizationId: orgId,
}),
})
.then(async res => {
}).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to add a user to an org');
console.log("Failed to add a user to an org");
}
})
});
};
export default addUserToOrg;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -7,22 +8,24 @@ import { PATH } from "../../../const";
* @returns
*/
const deleteIncidentContact = (organizaionId, email) => {
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + organizaionId + "/incidentContactOrg", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
}),
})
.then(async res => {
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + organizaionId + "/incidentContactOrg",
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
}),
}
).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to delete an incident contact');
console.log("Failed to delete an incident contact");
}
})
});
};
export default deleteIncidentContact;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -7,19 +8,21 @@ import { PATH } from "../../../const";
* @returns
*/
const deleteUserFromOrganization = (membershipId) => {
return SecurityClient.fetchCall(PATH + "/api/v1/membership-org/" + membershipId, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
return SecurityClient.fetchCall(
PATH + "/api/v1/membership-org/" + membershipId,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to delete a user from an org');
console.log("Failed to delete a user from an org");
}
})
});
};
export default deleteUserFromOrganization;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -7,19 +8,21 @@ import { PATH } from "../../../const";
* @returns
*/
const getIncidentContacts = (organizationId) => {
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + organizationId + "/incidentContactOrg", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + organizationId + "/incidentContactOrg",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).incidentContactsOrg;
} else {
console.log('Failed to get incident contacts');
console.log("Failed to get incident contacts");
}
})
});
};
export default getIncidentContacts;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -13,14 +14,13 @@ const getOrganizations = (req, res) => {
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
}).then(async (res) => {
if (res.status == 200) {
return (await res.json()).organizations;
} else {
console.log('Failed to get orgs of a user');
console.log("Failed to get orgs of a user");
}
})
});
};
export default getOrganizations;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -8,22 +9,24 @@ import { PATH } from "../../../const";
* @returns
*/
const renameOrg = (orgId, newOrgName) => {
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + orgId + "/name", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: newOrgName,
}),
})
.then(async res => {
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + orgId + "/name",
{
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: newOrgName,
}),
}
).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to rename an organization');
console.log("Failed to rename an organization");
}
})
});
};
export default renameOrg;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -6,29 +7,36 @@ import { PATH } from "../../../const";
* @param {*} param0
* @returns
*/
const addServiceToken = ({name, workspaceId, environment, expiresIn, publicKey, encryptedKey, nonce}) => {
const addServiceToken = ({
name,
workspaceId,
environment,
expiresIn,
publicKey,
encryptedKey,
nonce,
}) => {
return SecurityClient.fetchCall(PATH + "/api/v1/service-token/", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name,
workspaceId,
name,
workspaceId,
environment,
expiresIn,
publicKey,
expiresIn,
publicKey,
encryptedKey,
nonce
})
})
.then(async res => {
nonce,
}),
}).then(async (res) => {
if (res.status == 200) {
return (await res.json()).token;
} else {
console.log('Failed to add service tokens');
console.log("Failed to add service tokens");
}
})
});
};
export default addServiceToken;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -6,20 +7,22 @@ import { PATH } from "../../../const";
* @param {*} param0
* @returns
*/
const getServiceTokens = ({workspaceId}) => {
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + workspaceId + "/service-tokens", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
const getServiceTokens = ({ workspaceId }) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/workspace/" + workspaceId + "/service-tokens",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).serviceTokens;
} else {
console.log('Failed to get service tokens');
console.log("Failed to get service tokens");
}
})
});
};
export default getServiceTokens;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -13,14 +14,13 @@ const getUser = (req, res) => {
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
}).then(async (res) => {
if (res.status == 200) {
return (await res.json()).user;
} else {
console.log('Failed to get user info');
console.log("Failed to get user info");
}
})
});
};
export default getUser;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -8,23 +9,26 @@ import { PATH } from "../../../const";
* @returns
*/
const checkUserAction = ({ action }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/user-action" +
"?" +
new URLSearchParams({
action,
}), {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
return SecurityClient.fetchCall(
PATH +
"/api/v1/user-action" +
"?" +
new URLSearchParams({
action,
}),
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).userAction;
} else {
console.log('Failed to check a user action');
console.log("Failed to check a user action");
}
})
});
};
export default checkUserAction;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -15,14 +16,13 @@ const registerUserAction = ({ action }) => {
body: JSON.stringify({
action,
}),
})
.then(async res => {
}).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to register a user action');
console.log("Failed to register a user action");
}
})
});
};
export default registerUserAction;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -8,22 +9,24 @@ import { PATH } from "../../../const";
* @returns
*/
const addUserToWorkspace = (email, workspaceId) => {
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + workspaceId + "/invite-signup", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
}),
})
.then(async res => {
if (res.status == 200) {
return (await res.json());
} else {
console.log('Failed to add a user to project');
return SecurityClient.fetchCall(
PATH + "/api/v1/workspace/" + workspaceId + "/invite-signup",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
}),
}
})
).then(async (res) => {
if (res.status == 200) {
return await res.json();
} else {
console.log("Failed to add a user to project");
}
});
};
export default addUserToWorkspace;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -8,22 +9,24 @@ import { PATH } from "../../../const";
* @returns
*/
const changeUserRoleInWorkspace = (membershipId, role) => {
return SecurityClient.fetchCall(PATH + "/api/v1/membership/" + membershipId + "/change-role", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
role: role,
}),
})
.then(async res => {
return SecurityClient.fetchCall(
PATH + "/api/v1/membership/" + membershipId + "/change-role",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
role: role,
}),
}
).then(async (res) => {
if (res.status == 200) {
return res;
} 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;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -16,14 +17,13 @@ const createWorkspace = (workspaceName, organizationId) => {
workspaceName: workspaceName,
organizationId: organizationId,
}),
})
.then(async res => {
}).then(async (res) => {
if (res.status == 200) {
return (await res.json()).workspace;
} else {
console.log('Failed to create a project');
console.log("Failed to create a project");
}
})
});
};
export default createWorkspace;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -7,19 +8,21 @@ import { PATH } from "../../../const";
* @returns
*/
const deleteUserFromWorkspace = (membershipId) => {
return SecurityClient.fetchCall(PATH + "/api/v1/membership/" + membershipId, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
return SecurityClient.fetchCall(
PATH + "/api/v1/membership/" + membershipId,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to delete a user from a project');
console.log("Failed to delete a user from a project");
}
})
});
};
export default deleteUserFromWorkspace;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -12,14 +13,13 @@ const deleteWorkspace = (workspaceId) => {
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
}).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to delete a project');
console.log("Failed to delete a project");
}
})
});
};
export default deleteWorkspace;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -7,19 +8,23 @@ import { PATH } from "../../../const";
* @returns
*/
const getLatestFileKey = (workspaceId) => {
return SecurityClient.fetchCall(PATH + "/api/v1/key/" + workspaceId + "/latest", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
if (res.status == 200) {
return (await res.json());
} else {
console.log('Failed to get the latest key pairs for a certain project');
return SecurityClient.fetchCall(
PATH + "/api/v1/key/" + workspaceId + "/latest",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
})
).then(async (res) => {
if (res.status == 200) {
return await res.json();
} else {
console.log(
"Failed to get the latest key pairs for a certain project"
);
}
});
};
export default getLatestFileKey;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -8,19 +9,21 @@ import { PATH } from "../../../const";
* @returns
*/
const getWorkspaceInfo = (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + req.workspaceId, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
return SecurityClient.fetchCall(
PATH + "/api/v1/workspace/" + req.workspaceId,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).workspace;
} else {
console.log('Failed to get project info');
console.log("Failed to get project info");
}
})
});
};
export default getWorkspaceInfo;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -8,19 +9,23 @@ import { PATH } from "../../../const";
* @returns
*/
const getWorkspaceKeys = (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + req.workspaceId + "/keys", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
return SecurityClient.fetchCall(
PATH + "/api/v1/workspace/" + req.workspaceId + "/keys",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).publicKeys;
} else {
console.log('Failed to get the public keys of everyone in the workspace');
console.log(
"Failed to get the public keys of everyone in the workspace"
);
}
})
});
};
export default getWorkspaceKeys;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -8,19 +9,21 @@ import { PATH } from "../../../const";
* @returns
*/
const getWorkspaceUsers = (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + req.workspaceId + "/users", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
return SecurityClient.fetchCall(
PATH + "/api/v1/workspace/" + req.workspaceId + "/users",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
).then(async (res) => {
if (res.status == 200) {
return (await res.json()).users;
} else {
console.log('Failed to get Project Users');
console.log("Failed to get Project Users");
}
})
});
};
export default getWorkspaceUsers;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -13,14 +14,13 @@ const getWorkspaces = (req, res) => {
headers: {
"Content-Type": "application/json",
},
})
.then(async res => {
}).then(async (res) => {
if (res.status == 200) {
return (await res.json()).workspaces;
} else {
console.log('Failed to get projects');
console.log("Failed to get projects");
}
})
});
};
export default getWorkspaces;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -8,22 +9,24 @@ import { PATH } from "../../../const";
* @returns
*/
const renameWorkspace = (workspaceId, newWorkspaceName) => {
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + workspaceId + "/name", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: newWorkspaceName,
}),
})
.then(async res => {
return SecurityClient.fetchCall(
PATH + "/api/v1/workspace/" + workspaceId + "/name",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: newWorkspaceName,
}),
}
).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to rename a project');
console.log("Failed to rename a project");
}
})
});
};
export default renameWorkspace;

View File

@@ -1,4 +1,5 @@
import SecurityClient from "../../../components/utilities/SecurityClient";
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "../../../const";
/**
@@ -22,14 +23,13 @@ const uploadKeys = (workspaceId, userId, encryptedKey, nonce) => {
nonce: nonce,
},
}),
})
.then(async res => {
}).then(async (res) => {
if (res.status == 200) {
return res;
} else {
console.log('Failed to upload keys for a new user');
console.log("Failed to upload keys for a new user");
}
})
});
};
export default uploadKeys;

View File

@@ -1,6 +1,6 @@
import React, { useEffect } from "react";
import { useRouter } from "next/router";
import Head from "next/head";
import { useRouter } from "next/router";
import getWorkspaces from "./api/workspace/getWorkspaces";
@@ -10,11 +10,14 @@ export default function DashboardRedirect() {
/**
* Here we forward to the default workspace if a user opens this url
*/
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(async () => {
let userWorkspace;
try {
if (localStorage.getItem("projectData.id")) {
router.push("/dashboard/" + localStorage.getItem("projectData.id"));
router.push(
"/dashboard/" + localStorage.getItem("projectData.id")
);
} else {
const userWorkspaces = await getWorkspaces();
userWorkspace = userWorkspaces[0]._id;
@@ -23,9 +26,10 @@ export default function DashboardRedirect() {
} catch (error) {
console.log("Error - Not logged in yet");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <div></div>
return <div></div>;
}
DashboardRedirect.requireAuth = true;

View File

@@ -1,44 +1,52 @@
import React, { useState, useEffect, useCallback, Fragment } from "react";
import { useRouter } from "next/router";
import React, { Fragment, useCallback, useEffect, useState } from "react";
import Head from "next/head";
import Image from "next/image";
import guidGenerator from "../../components/utilities/randomId";
import getSecretsForProject from "../../components/utilities/getSecretsForProject";
import pushKeys from "../../components/utilities/pushKeys";
import getWorkspaces from "../api/workspace/getWorkspaces";
import getUser from "../api/user/getUser";
import NavHeader from "../../components/navigation/NavHeader";
import DashboardInputField from "../../components/dashboard/DashboardInputField";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useRouter } from "next/router";
import {
faMagnifyingGlass,
faEye,
faEyeSlash,
faPlus,
faFolderOpen,
faArrowDownAZ,
faArrowDownZA,
faCheck,
faCircleInfo,
faCopy,
faDownload,
faEllipsis,
faPerson,
faEye,
faEyeSlash,
faFolderOpen,
faMagnifyingGlass,
faPeopleGroup,
faCheck,
faCopy,
faCircleInfo,
faX
faPerson,
faPlus,
faX,
} from "@fortawesome/free-solid-svg-icons";
import ListBox from "../../components/basic/Listbox";
import DropZone from "../../components/dashboard/DropZone";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Menu, Transition } from "@headlessui/react";
import Button from "~/components/basic/buttons/Button";
import ListBox from "~/components/basic/Listbox";
import BottonRightPopup from "~/components/basic/popups/BottomRightPopup";
import DashboardInputField from "~/components/dashboard/DashboardInputField";
import DropZone from "~/components/dashboard/DropZone";
import NavHeader from "~/components/navigation/NavHeader";
import getSecretsForProject from "~/utilities/getSecretsForProject";
import pushKeys from "~/utilities/pushKeys";
import pushKeysIntegration from "~/utilities/pushKeysIntegration";
import guidGenerator from "~/utilities/randomId";
import getWorkspaceIntegrations from "../api/integrations/getWorkspaceIntegrations";
import BottonRightPopup from "../../components/basic/popups/BottomRightPopup";
import getUser from "../api/user/getUser";
import checkUserAction from "../api/userActions/checkUserAction";
import registerUserAction from "../api/userActions/registerUserAction";
import pushKeysIntegration from "../../components/utilities/pushKeysIntegration";
import Button from "../../components/basic/buttons/Button";
import getWorkspaces from "../api/workspace/getWorkspaces";
const KeyPair = ({ keyPair, deleteRow, modifyKey, modifyValue, modifyVisibility, isBlurred }) => {
const KeyPair = ({
keyPair,
deleteRow,
modifyKey,
modifyValue,
modifyVisibility,
isBlurred,
}) => {
return (
<div className="px-1 flex flex-col items-center ml-1">
<div className="relative flex flex-row justify-between w-full max-w-5xl mr-auto max-h-10 my-1 items-center px-2">
@@ -86,9 +94,12 @@ const KeyPair = ({ keyPair, deleteRow, modifyKey, modifyValue, modifyVisibility,
<Menu.Items className="absolute right-0 mt-0.5 w-44 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-1 py-1">
<div
onClick={() =>
modifyVisibility(keyPair[4] == "personal"
? "shared"
: "personal", keyPair[1])
modifyVisibility(
keyPair[4] == "personal"
? "shared"
: "personal",
keyPair[1]
)
}
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"
>
@@ -123,7 +134,6 @@ const KeyPair = ({ keyPair, deleteRow, modifyKey, modifyValue, modifyVisibility,
);
};
const envMapping = {
Development: "dev",
Staging: "staging",
@@ -186,57 +196,60 @@ export default function Dashboard() {
};
}, [buttonReady]);
const reorderRows = () => {
setSortMethod(
sortMethod == "alphabetical" ? "-alphabetical" : "alphabetical"
);
};
useEffect(async () => {
try {
let userWorkspaces = await getWorkspaces();
const listWorkspaces = userWorkspaces.map(
(workspace) => workspace._id
);
if (
!listWorkspaces.includes(
router.asPath.split("/")[2].split("?")[0]
)
) {
router.push("/dashboard/" + listWorkspaces[0]);
useEffect(() => {
(async () => {
try {
let userWorkspaces = await getWorkspaces();
const listWorkspaces = userWorkspaces.map(
(workspace) => workspace._id
);
if (
!listWorkspaces.includes(
router.asPath.split("/")[2].split("?")[0]
)
) {
router.push("/dashboard/" + listWorkspaces[0]);
}
if (env != router.asPath.split("?")[1]) {
router.push(router.asPath.split("?")[0] + "?" + env);
}
setBlurred(true);
setWorkspaceId(router.query.id);
await getSecretsForProject({
env,
setFileState,
setIsKeyAvailable,
setData,
workspaceId: router.query.id,
});
const user = await getUser();
setIsNew(
(Date.parse(new Date()) - Date.parse(user.createdAt)) /
60000 <
3
? true
: false
);
let userAction = await checkUserAction({
action: "first_time_secrets_pushed",
});
setHasUserEverPushed(userAction ? true : false);
} catch (error) {
console.log("Error", error);
setData([]);
}
if (env != router.asPath.split("?")[1]) {
router.push(router.asPath.split("?")[0] + "?" + env);
}
setBlurred(true);
setWorkspaceId(router.query.id);
await getSecretsForProject({
env,
setFileState,
setIsKeyAvailable,
setData,
workspaceId: router.query.id
})
const user = await getUser();
setIsNew(
(Date.parse(new Date()) -
Date.parse(user.createdAt)) /
60000 <
3
? true
: false
);
let userAction = await checkUserAction({action: "first_time_secrets_pushed"});
setHasUserEverPushed(userAction ? true : false)
} catch (error) {
console.log("Error", error);
setData([]);
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [env]);
const addRow = () => {
@@ -252,39 +265,38 @@ export default function Dashboard() {
const modifyValue = (value, id) => {
setData((oldData) => {
oldData[id][3] = value;
return [...oldData]
return [...oldData];
});
setButtonReady(true);
}
};
const modifyKey = (value, id) => {
setData((oldData) => {
oldData[id][2] = value;
return [...oldData]
return [...oldData];
});
setButtonReady(true);
}
};
const modifyVisibility = (value, id) => {
setData((oldData) => {
oldData[id][4] = value;
return [...oldData]
return [...oldData];
});
setButtonReady(true);
}
};
const listenChangeValue = useCallback((value, id) => {
modifyValue(value, id)
}, [])
modifyValue(value, id);
}, []);
const listenChangeKey = useCallback((value, id) => {
modifyKey(value, id)
}, [])
modifyKey(value, id);
}, []);
const listenChangeVisibility = useCallback((value, id) => {
modifyVisibility(value, id)
}, [])
modifyVisibility(value, id);
}, []);
const savePush = async () => {
let obj = Object.assign(
@@ -294,19 +306,28 @@ export default function Dashboard() {
setButtonReady(false);
pushKeys(obj, router.query.id, env);
let integrations = await getWorkspaceIntegrations({workspaceId: router.query.id});
let integrations = await getWorkspaceIntegrations({
workspaceId: router.query.id,
});
integrations.map(async (integration) => {
if (envMapping[env] == integration.environment && integration.isActive == true) {
if (
envMapping[env] == integration.environment &&
integration.isActive == true
) {
let objIntegration = Object.assign(
{},
...data.map((row) => ({ [row[2]]: row[3] }))
);
await pushKeysIntegration({obj: objIntegration, integrationId: integration._id});}
await pushKeysIntegration({
obj: objIntegration,
integrationId: integration._id,
});
}
});
if (!hasUserEverPushed) {
setCheckDocsPopUpVisible(true)
await registerUserAction({action: "first_time_secrets_pushed"});
setCheckDocsPopUpVisible(true);
await registerUserAction({ action: "first_time_secrets_pushed" });
}
};
@@ -339,20 +360,19 @@ export default function Dashboard() {
function copyToClipboard() {
// Get the text field
var copyText = document.getElementById("myInput");
// Select the text field
copyText.select();
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);
setProjectIdCopied(true);
setTimeout(() => setProjectIdCopied(false), 2000);
// Alert the copied text
// alert("Copied the text: " + copyText.value);
}
}
return data ? (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
@@ -371,41 +391,61 @@ export default function Dashboard() {
</Head>
<div className="flex flex-row">
<div className="w-full max-h-96 pb-2">
<NavHeader pageName="Secrets" isProjectRelated={true}/>
{checkDocsPopUpVisible &&
<BottonRightPopup
buttonText="Check Docs"
<NavHeader pageName="Secrets" isProjectRelated={true} />
{checkDocsPopUpVisible && (
<BottonRightPopup
buttonText="Check Docs"
buttonLink="https://infisical.com/docs/getting-started/introduction"
titleText="Good job!"
emoji="🎉"
textLine1="Congrats on adding more secrets."
textLine2="Here is how to connect them to your codebase."
titleText="Good job!"
emoji="🎉"
textLine1="Congrats on adding more secrets."
textLine2="Here is how to connect them to your codebase."
setCheckDocsPopUpVisible={setCheckDocsPopUpVisible}
/>
}
)}
<div className="flex flex-row justify-between items-center mx-6 mt-6 mb-3 text-xl max-w-5xl">
<div className="flex flex-row justify-start items-center text-3xl">
<p className="font-semibold mr-4 mt-1">Secrets</p>
{data?.length==0 && <ListBox
selected={env}
data={[
"Development",
"Staging",
"Production",
"Testing",
]}
// ref={useRef(123)}
onChange={setEnv}
className="z-40"
/>}
{data?.length == 0 && (
<ListBox
selected={env}
data={[
"Development",
"Staging",
"Production",
"Testing",
]}
// ref={useRef(123)}
onChange={setEnv}
className="z-40"
/>
)}
</div>
<div className="flex flex-row">
<div className="flex justify-end items-center bg-white/[0.07] text-base mt-2 mr-2 rounded-md text-gray-400">
<p className="mr-2 font-bold pl-4">Project ID:</p>
<input type="text" value={workspaceId} id="myInput" className="bg-white/0 text-gray-400 py-2 w-60 px-2 min-w-md outline-none" disabled></input>
<p className="mr-2 font-bold pl-4">
Project ID:
</p>
<input
type="text"
value={workspaceId}
id="myInput"
className="bg-white/0 text-gray-400 py-2 w-60 px-2 min-w-md outline-none"
disabled
></input>
<div className="group font-normal group relative inline-block text-gray-400 underline hover:text-primary duration-200">
<button onClick={copyToClipboard} className="pl-4 pr-4 border-l border-white/20 py-2 hover:bg-white/[0.12] duration-200">
{projectIdCopied ? <FontAwesomeIcon icon={faCheck} className="pr-0.5"/> : <FontAwesomeIcon icon={faCopy} />}
<button
onClick={copyToClipboard}
className="pl-4 pr-4 border-l border-white/20 py-2 hover:bg-white/[0.12] duration-200"
>
{projectIdCopied ? (
<FontAwesomeIcon
icon={faCheck}
className="pr-0.5"
/>
) : (
<FontAwesomeIcon icon={faCopy} />
)}
</button>
<span className="absolute hidden group-hover:flex group-hover:animate-popup duration-300 w-28 -left-8 -top-20 translate-y-full pl-3 py-2 bg-white/10 rounded-md text-center text-gray-400 text-sm">
Click to Copy
@@ -455,7 +495,9 @@ export default function Dashboard() {
className="pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none"
value={searchKeys}
onChange={(e) =>
setSearchKeys(e.target.value)
setSearchKeys(
e.target.value
)
}
placeholder={"Search keys..."}
/>
@@ -465,7 +507,11 @@ export default function Dashboard() {
onButtonPressed={reorderRows}
color="mineshaft"
size="icon-md"
icon={sortMethod == "alphabetical" ? faArrowDownAZ : faArrowDownZA}
icon={
sortMethod == "alphabetical"
? faArrowDownAZ
: faArrowDownZA
}
/>
</div>
<div className="ml-2 min-w-max flex flex-row items-start justify-start">
@@ -481,7 +527,9 @@ export default function Dashboard() {
onButtonPressed={changeBlurred}
color="mineshaft"
size="icon-md"
icon={blurred ? faEye : faEyeSlash}
icon={
blurred ? faEye : faEyeSlash
}
/>
</div>
<div className="relative ml-2 min-w-max flex flex-row items-start justify-end">
@@ -514,14 +562,17 @@ export default function Dashboard() {
<div className="rounded-t-md sticky top-0 z-20 bg-bunker flex flex-row pl-4 pr-6 pt-4 pb-2 items-center justify-between text-gray-300 font-bold">
{/* <FontAwesomeIcon icon={faAngleDown} /> */}
<div className="flex flex-row items-center">
<p className="pl-2 text-lg">Personal</p>
<p className="pl-2 text-lg">
Personal
</p>
<div className="group font-normal group relative inline-block text-gray-300 underline hover:text-primary duration-200">
<FontAwesomeIcon
className="ml-3 mt-1 text-lg"
icon={faCircleInfo}
/>
<span className="absolute hidden group-hover:flex group-hover:animate-popdown duration-300 w-44 -left-16 -top-7 translate-y-full px-2 py-2 bg-gray-700 rounded-md text-center text-gray-100 text-sm after:content-[''] after:absolute after:left-1/2 after:bottom-[100%] after:-translate-x-1/2 after:border-8 after:border-x-transparent after:border-t-transparent after:border-b-gray-700">
Personal keys are only visible to you
Personal keys are only
visible to you
</span>
</div>
</div>
@@ -534,7 +585,8 @@ export default function Dashboard() {
.toLowerCase()
.includes(
searchKeys.toLowerCase()
) && keyPair[4] == "personal"
) &&
keyPair[4] == "personal"
)
.sort((a, b) =>
sortMethod == "alphabetical"
@@ -546,9 +598,13 @@ export default function Dashboard() {
key={keyPair[0]}
keyPair={keyPair}
deleteRow={deleteCertainRow}
modifyValue={listenChangeValue}
modifyValue={
listenChangeValue
}
modifyKey={listenChangeKey}
modifyVisibility={listenChangeVisibility}
modifyVisibility={
listenChangeVisibility
}
isBlurred={blurred}
/>
))}
@@ -562,15 +618,17 @@ export default function Dashboard() {
<div className="sticky top-0 z-10 bg-bunker flex flex-row pl-4 pr-5 pt-4 pb-2 items-center justify-between text-gray-300 font-bold">
{/* <FontAwesomeIcon icon={faAngleDown} /> */}
<div className="flex flex-row items-center">
<p className="pl-2 text-lg">Shared</p>
<p className="pl-2 text-lg">
Shared
</p>
<div className="group font-normal group relative inline-block text-gray-300 underline hover:text-primary duration-200">
<FontAwesomeIcon
className="ml-3 text-lg mt-1"
icon={faCircleInfo}
/>
<span className="absolute hidden group-hover:flex group-hover:animate-popdown duration-300 w-44 -left-16 -top-7 translate-y-full px-2 py-2 bg-gray-700 rounded-md text-center text-gray-100 text-sm after:content-[''] after:absolute after:left-1/2 after:bottom-[100%] after:-translate-x-1/2 after:border-8 after:border-x-transparent after:border-t-transparent after:border-b-gray-700">
Shared keys are visible to your whole
team
Shared keys are visible to
your whole team
</span>
</div>
</div>
@@ -583,7 +641,8 @@ export default function Dashboard() {
.toLowerCase()
.includes(
searchKeys.toLowerCase()
) && keyPair[4] == "shared"
) &&
keyPair[4] == "shared"
)
.sort((a, b) =>
sortMethod == "alphabetical"
@@ -595,9 +654,13 @@ export default function Dashboard() {
key={keyPair[0]}
keyPair={keyPair}
deleteRow={deleteCertainRow}
modifyValue={listenChangeValue}
modifyValue={
listenChangeValue
}
modifyKey={listenChangeKey}
modifyVisibility={listenChangeVisibility}
modifyVisibility={
listenChangeVisibility
}
isBlurred={blurred}
/>
))}
@@ -606,7 +669,9 @@ export default function Dashboard() {
<div className="w-full max-w-5xl">
<DropZone
setData={addData}
setErrorDragAndDrop={setErrorDragAndDrop}
setErrorDragAndDrop={
setErrorDragAndDrop
}
createNewFile={addRow}
errorDragAndDrop={errorDragAndDrop}
setButtonReady={setButtonReady}
@@ -617,19 +682,23 @@ export default function Dashboard() {
</div>
) : (
<div className="flex flex-col items-center justify-center h-full text-xl text-gray-400 max-w-5xl mt-28">
{fileState.message != "There's nothing to pull" &&
{fileState.message !=
"There's nothing to pull" &&
fileState.message != undefined && (
<FontAwesomeIcon
className="text-7xl mb-8"
icon={faFolderOpen}
/>
)}
{(fileState.message == "There's nothing to pull" ||
{(fileState.message ==
"There's nothing to pull" ||
fileState.message == undefined) &&
isKeyAvailable && (
<DropZone
setData={setData}
setErrorDragAndDrop={setErrorDragAndDrop}
setErrorDragAndDrop={
setErrorDragAndDrop
}
createNewFile={addRow}
errorDragAndDrop={errorDragAndDrop}
setButtonReady={setButtonReady}
@@ -638,7 +707,10 @@ export default function Dashboard() {
)}
{fileState.message ==
"Failed membership validation for workspace" && (
<p>You are not authorized to view this project.</p>
<p>
You are not authorized to view this
project.
</p>
)}
{fileState.message ==
"Access needed to pull the latest file" ||
@@ -653,8 +725,8 @@ export default function Dashboard() {
administrator for permission.
</p>
<p className="mt-1">
They need to grant you access in the team
tab.
They need to grant you access in
the team tab.
</p>
</>
))}

View File

@@ -4,7 +4,6 @@ import { useRouter } from "next/router";
const queryString = require("query-string");
import AuthorizeIntegration from "./api/integrations/authorizeIntegration";
export default function Heroku() {
const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
@@ -14,22 +13,26 @@ export default function Heroku() {
/**
* Here we forward to the default workspace if a user opens this url
*/
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(async () => {
try {
if (state == localStorage.getItem("latestCSRFToken")) {
await AuthorizeIntegration({
workspaceId: localStorage.getItem("projectData.id"),
code,
integration: "heroku"
})
router.push("/integrations/" + localStorage.getItem("projectData.id"));
workspaceId: localStorage.getItem("projectData.id"),
code,
integration: "heroku",
});
router.push(
"/integrations/" + localStorage.getItem("projectData.id")
);
}
} catch (error) {
console.log("Error - Not logged in yet");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <div></div>
return <div></div>;
}
Heroku.requireAuth = true;

View File

@@ -4,12 +4,10 @@ import { useRouter } from "next/router";
export default function Home() {
const router = useRouter();
useEffect(async () => {
useEffect(() => {
router.push("/login");
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div className="bg-bunker-800 w-screen">
</div>
);
return <div className="bg-bunker-800 w-screen"></div>;
}

View File

@@ -1,22 +1,29 @@
import React, { useState, useEffect } from "react";
import { useRouter } from "next/router";
import React, { useEffect,useState } from "react";
import Head from "next/head";
import Image from "next/image";
import { useRouter } from "next/router";
import {
faArrowRight,
faCheck,
faRotate,
faX,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheck, faArrowRight, faRotate, faX } from "@fortawesome/free-solid-svg-icons";
import ListBox from "../../components/basic/Listbox";
import NavHeader from "../../components/navigation/NavHeader";
import getIntegrations from "../api/integrations/GetIntegrations";
import Button from "~/components/basic/buttons/Button";
import ListBox from "~/components/basic/Listbox";
import NavHeader from "~/components/navigation/NavHeader";
import getSecretsForProject from "~/utilities/getSecretsForProject";
import pushKeysIntegration from "~/utilities/pushKeysIntegration";
import guidGenerator from "~/utilities/randomId";
import deleteIntegration from "../api/integrations/DeleteIntegration";
import deleteIntegrationAuth from "../api/integrations/DeleteIntegrationAuth";
import getIntegrationApps from "../api/integrations/GetIntegrationApps";
import getIntegrations from "../api/integrations/GetIntegrations";
import getWorkspaceAuthorizations from "../api/integrations/getWorkspaceAuthorizations";
import getWorkspaceIntegrations from "../api/integrations/getWorkspaceIntegrations";
import startIntegration from "../api/integrations/StartIntegration";
import deleteIntegration from "../api/integrations/DeleteIntegration";
import getSecretsForProject from "../../components/utilities/getSecretsForProject";
import pushKeysIntegration from "../../components/utilities/pushKeysIntegration";
import deleteIntegrationAuth from "../api/integrations/DeleteIntegrationAuth";
import Button from "../../components/basic/buttons/Button";
import guidGenerator from "../../components/utilities/randomId";
const crypto = require("crypto");
@@ -28,104 +35,138 @@ const envMapping = {
};
const reverseEnvMapping = {
"dev": "Development",
"staging": "Staging",
"prod": "Production",
"test": "Testing"
}
dev: "Development",
staging: "Staging",
prod: "Production",
test: "Testing",
};
const Integration = ({projectIntegration}) => {
const [integrationEnvironment, setIntegrationEnvironment] = useState(reverseEnvMapping[projectIntegration.environment]);
const Integration = ({ projectIntegration }) => {
const [integrationEnvironment, setIntegrationEnvironment] = useState(
reverseEnvMapping[projectIntegration.environment]
);
const [fileState, setFileState] = useState([]);
const [data, setData] = useState();
const [isKeyAvailable, setIsKeyAvailable] = useState(true);
const router = useRouter();
const [apps, setApps] = useState([]);
const [integrationApp, setIntegrationApp] = useState(projectIntegration.app ? projectIntegration.app : apps[0]);
const [integrationApp, setIntegrationApp] = useState(
projectIntegration.app ? projectIntegration.app : apps[0]
);
useEffect(async () => {
const tempHerokuApps = await getIntegrationApps({integrationAuthId: projectIntegration.integrationAuth});
const tempHerokuAppNames = tempHerokuApps.map(app => app.name);
const tempHerokuApps = await getIntegrationApps({
integrationAuthId: projectIntegration.integrationAuth,
});
const tempHerokuAppNames = tempHerokuApps.map((app) => app.name);
setApps(tempHerokuAppNames);
setIntegrationApp(projectIntegration.app ? projectIntegration.app : tempHerokuAppNames[0])
setIntegrationApp(
projectIntegration.app
? projectIntegration.app
: tempHerokuAppNames[0]
);
}, []);
return <div className="flex flex-col max-w-5xl justify-center bg-white/5 p-6 rounded-md mx-6 mt-8">
<div className="relative px-4 flex flex-row items-center justify-between mb-4">
<div className="flex flex-row">
<div>
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
ENVIRONMENT
</div>
<ListBox
data={!projectIntegration.isActive && ["Development", "Staging", "Production"]}
selected={integrationEnvironment}
onChange={setIntegrationEnvironment}
/>
</div>
<FontAwesomeIcon
icon={faArrowRight}
className="mx-4 text-gray-400 mt-8"
/>
<div className="mr-2">
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
INTEGRATION
</div>
<div className="py-2.5 bg-white/[.07] rounded-md pl-4 pr-20 text-sm font-semibold text-gray-300">
{projectIntegration.integration.charAt(0).toUpperCase() + projectIntegration.integration.slice(1)}
</div>
</div>
<div>
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
HEROKU APP
</div>
<ListBox
data={!projectIntegration.isActive && apps}
selected={integrationApp}
onChange={setIntegrationApp}
/>
</div>
</div>
<div className="flex flex-row mt-6">
{projectIntegration.isActive
? <div className="max-w-5xl flex flex-row items-center bg-white/5 p-2 rounded-md px-4">
<FontAwesomeIcon icon={faRotate} className="text-lg mr-2.5 text-primary animate-spin"/>
<div className="text-gray-300 font-semibold">In Sync</div>
</div>
: <Button
text="Start Integration"
onButtonPressed={async () => {
const result = await startIntegration({
"integrationId": projectIntegration._id,
"environment": envMapping[integrationEnvironment],
"appName": integrationApp
})
if (result?.status == 200) {
let currentSecrets = await getSecretsForProject({
env: integrationEnvironment,
setFileState,
setIsKeyAvailable,
setData,
workspaceId: router.query.id
})
let obj = Object.assign(
{},
...currentSecrets.map((row) => ({ [row[2]]: row[3] }))
);
await pushKeysIntegration({obj, integrationId: projectIntegration._id});
router.reload();
return (
<div className="flex flex-col max-w-5xl justify-center bg-white/5 p-6 rounded-md mx-6 mt-8">
<div className="relative px-4 flex flex-row items-center justify-between mb-4">
<div className="flex flex-row">
<div>
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
ENVIRONMENT
</div>
<ListBox
data={
!projectIntegration.isActive && [
"Development",
"Staging",
"Production",
]
}
}}
color="mineshaft"
size="md"
selected={integrationEnvironment}
onChange={setIntegrationEnvironment}
/>
</div>
<FontAwesomeIcon
icon={faArrowRight}
className="mx-4 text-gray-400 mt-8"
/>
}
<div className="mr-2">
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
INTEGRATION
</div>
<div className="py-2.5 bg-white/[.07] rounded-md pl-4 pr-20 text-sm font-semibold text-gray-300">
{projectIntegration.integration
.charAt(0)
.toUpperCase() +
projectIntegration.integration.slice(1)}
</div>
</div>
<div>
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
HEROKU APP
</div>
<ListBox
data={!projectIntegration.isActive && apps}
selected={integrationApp}
onChange={setIntegrationApp}
/>
</div>
</div>
<div className="flex flex-row mt-6">
{projectIntegration.isActive ? (
<div className="max-w-5xl flex flex-row items-center bg-white/5 p-2 rounded-md px-4">
<FontAwesomeIcon
icon={faRotate}
className="text-lg mr-2.5 text-primary animate-spin"
/>
<div className="text-gray-300 font-semibold">
In Sync
</div>
</div>
) : (
<Button
text="Start Integration"
onButtonPressed={async () => {
const result = await startIntegration({
integrationId: projectIntegration._id,
environment:
envMapping[integrationEnvironment],
appName: integrationApp,
});
if (result?.status == 200) {
let currentSecrets =
await getSecretsForProject({
env: integrationEnvironment,
setFileState,
setIsKeyAvailable,
setData,
workspaceId: router.query.id,
});
let obj = Object.assign(
{},
...currentSecrets.map((row) => ({
[row[2]]: row[3],
}))
);
await pushKeysIntegration({
obj,
integrationId: projectIntegration._id,
});
router.reload();
}
}}
color="mineshaft"
size="md"
/>
)}
<div className="opacity-50 hover:opacity-100 duration-200 ml-2">
<Button
onButtonPressed={async () => {
await deleteIntegration({integrationId: projectIntegration._id});
await deleteIntegration({
integrationId: projectIntegration._id,
});
router.reload();
}}
color="red"
@@ -134,9 +175,10 @@ const Integration = ({projectIntegration}) => {
/>
</div>
</div>
</div>
</div>
</div>;
}
);
};
export default function Integrations() {
const [integrations, setIntegrations] = useState();
@@ -150,12 +192,16 @@ export default function Integrations() {
setCsrfToken(tempCSRFToken);
localStorage.setItem("latestCSRFToken", tempCSRFToken);
let projectAuthorizations = await getWorkspaceAuthorizations({workspaceId: router.query.id});
let projectAuthorizations = await getWorkspaceAuthorizations({
workspaceId: router.query.id,
});
setAuthorizations(projectAuthorizations);
const projectIntegrations = await getWorkspaceIntegrations({workspaceId: router.query.id});
const projectIntegrations = await getWorkspaceIntegrations({
workspaceId: router.query.id,
});
setProjectIntegrations(projectIntegrations);
try {
const integrationsData = await getIntegrations();
setIntegrations(integrationsData);
@@ -181,7 +227,10 @@ export default function Integrations() {
</Head>
<div className="flex flex-row">
<div className="w-full max-h-96 pb-2 h-screen max-h-[calc(100vh-10px)] overflow-y-scroll no-scrollbar no-scrollbar::-webkit-scrollbar">
<NavHeader pageName="Project Integrations" isProjectRelated={true}/>
<NavHeader
pageName="Project Integrations"
isProjectRelated={true}
/>
<div className="flex flex-col justify-between items-start mx-4 mt-6 mb-4 text-xl max-w-5xl px-2">
<div className="flex flex-row justify-start items-center text-3xl">
<p className="font-semibold mr-4">
@@ -189,24 +238,31 @@ export default function Integrations() {
</p>
</div>
<p className="mr-4 text-base text-gray-400">
Manage your integrations of Infisical with third-party services.
Manage your integrations of Infisical with
third-party services.
</p>
</div>
{projectIntegrations.length > 0
? projectIntegrations.map(projectIntegration =>
<Integration key={guidGenerator()} projectIntegration={projectIntegration} />
)
: <div className="flex flex-col max-w-5xl justify-center bg-white/5 p-6 rounded-md mx-6 mt-8">
{projectIntegrations.length > 0 ? (
projectIntegrations.map((projectIntegration) => (
<Integration
key={guidGenerator()}
projectIntegration={projectIntegration}
/>
))
) : (
<div className="flex flex-col max-w-5xl justify-center bg-white/5 p-6 rounded-md mx-6 mt-8">
<div className="relative px-4 flex flex-col text-gray-400 items-center justify-center">
<div className="mb-1">
You don't have any integrations set up yet. When you do, they will appear here.
You {"don't"} have any integrations set up
yet. When you do, they will appear here.
</div>
<div className="">
To start, click on any of the options below. It takes 5 clicks to set up.
To start, click on any of the options below.
It takes 5 clicks to set up.
</div>
</div>
</div>
}
)}
<div className="flex flex-col justify-between items-start mx-4 mt-12 mb-4 text-xl max-w-5xl px-2">
<div className="flex flex-row justify-start items-center text-3xl">
<p className="font-semibold mr-4">
@@ -214,50 +270,151 @@ export default function Integrations() {
</p>
</div>
<p className="mr-4 text-base text-gray-400">
Click on the itegration you want to connect. This will let your environment variables flow automatically into selected third-party services.
Click on the itegration you want to connect. This
will let your environment variables flow
automatically into selected third-party services.
</p>
<p className="mr-4 text-xs text-gray-600 mt-1">
Note: during an integration with Heroku, for security reasons, it is impossible to maintain end-to-end encryption. In theory, this lets Infisical decrypt yor environment variables. In practice, we can assure you that this will never be done, and it allows us to protect your secrets from bad actors online. The core Infisical service will always stay end-to-end encrypted. With any questions, reach out support@infisical.com.
Note: during an integration with Heroku, for
security reasons, it is impossible to maintain
end-to-end encryption. In theory, this lets
Infisical decrypt yor environment variables. In
practice, we can assure you that this will never be
done, and it allows us to protect your secrets from
bad actors online. The core Infisical service will
always stay end-to-end encrypted. With any
questions, reach out support@infisical.com.
</p>
</div>
<div className="grid gap-4 grid-cols-3 grid-rows-3 mx-6 mt-4 max-w-5xl">
{Object.keys(integrations).map(integration =>
<div className={`relative ${["Heroku"].includes(integrations[integration].name) ? "" : "opacity-50"}`} key={integrations[integration].name} >
<a
href={`${["Heroku"].includes(integrations[integration].name) ? `https://id.heroku.com/oauth/authorize?client_id=bc132901-935a-4590-b010-f1857efc380d&response_type=code&scope=write-protected&state=${csrfToken}` : "#"}`}
{Object.keys(integrations).map((integration) => (
<div
className={`relative ${
["Heroku"].includes(
integrations[integration].name
)
? ""
: "opacity-50"
}`}
key={integrations[integration].name}
>
<a
href={`${
["Heroku"].includes(
integrations[integration].name
)
? `https://id.heroku.com/oauth/authorize?client_id=bc132901-935a-4590-b010-f1857efc380d&response_type=code&scope=write-protected&state=${csrfToken}`
: "#"
}`}
rel="noopener"
className={`relative flex flex-row bg-white/5 h-32 rounded-md p-4 items-center ${["Heroku"].includes(integrations[integration].name) ? "hover:bg-white/10 duration-200 cursor-pointer" : "cursor-default grayscale"}`}>
className={`relative flex flex-row bg-white/5 h-32 rounded-md p-4 items-center ${
["Heroku"].includes(
integrations[integration].name
)
? "hover:bg-white/10 duration-200 cursor-pointer"
: "cursor-default grayscale"
}`}
>
<Image
src={`/images/integrations/${integrations[integration].name}.png`}
height={100}
width={100}
alt="integration logo"
></Image>
{integrations[integration].name.split(" ").length > 2
? <div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-3xl ml-8 max-w-xs">
<div>{integrations[integration].name.split(" ")[0]}</div>
<div className="text-base">{integrations[integration].name.split(" ")[1]}{" "}{integrations[integration].name.split(" ")[2]}</div>
{integrations[integration].name.split(" ")
.length > 2 ? (
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-3xl ml-8 max-w-xs">
<div>
{
integrations[
integration
].name.split(" ")[0]
}
</div>
: <div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-2xl ml-8 max-w-xs">{integrations[integration].name}</div>
}
<div className="text-base">
{
integrations[
integration
].name.split(" ")[1]
}{" "}
{
integrations[
integration
].name.split(" ")[2]
}
</div>
</div>
) : (
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-2xl ml-8 max-w-xs">
{integrations[integration].name}
</div>
)}
</a>
{["Heroku"].includes(integrations[integration].name) && authorizations.map(authorization => authorization.integration).includes(integrations[integration].name.toLowerCase()) &&
{["Heroku"].includes(
integrations[integration].name
) &&
authorizations
.map(
(authorization) =>
authorization.integration
)
.includes(
integrations[
integration
].name.toLowerCase()
) && (
<div className="absolute group z-50 top-0 right-0 flex flex-row">
<div
onClick={() => {
deleteIntegrationAuth({
integrationAuthId:
authorizations
.filter(
(
authorization
) =>
authorization.integration ==
integrations[
integration
].name.toLowerCase()
)
.map(
(
authorization
) =>
authorization._id
)[0],
});
router.reload();
}}
className="cursor-pointer w-max bg-red py-0.5 px-2 rounded-b-md text-xs flex flex-row items-center opacity-0 group-hover:opacity-100 duration-200"
>
<FontAwesomeIcon
icon={faX}
className="text-xs mr-2 py-px"
/>
Revoke
</div>
<div className="w-max bg-primary py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90 group-hover:opacity-100 duration-200">
<FontAwesomeIcon
icon={faCheck}
className="text-xs mr-2"
/>
Authorized
</div>
</div>
)}
{!["Heroku"].includes(
integrations[integration].name
) && (
<div className="absolute group z-50 top-0 right-0 flex flex-row">
<div onClick={() => {
deleteIntegrationAuth({integrationAuthId: authorizations.filter(authorization => authorization.integration == integrations[integration].name.toLowerCase()).map(authorization => authorization._id)[0]});
router.reload();
}} className="cursor-pointer w-max bg-red py-0.5 px-2 rounded-b-md text-xs flex flex-row items-center opacity-0 group-hover:opacity-100 duration-200"><FontAwesomeIcon icon={faX} className="text-xs mr-2 py-px"/>Revoke</div>
<div className="w-max bg-primary py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90 group-hover:opacity-100 duration-200"><FontAwesomeIcon icon={faCheck} className="text-xs mr-2"/>Authorized</div>
<div className="w-max bg-yellow py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90">
Coming Soon
</div>
</div>
}
{!["Heroku"].includes(integrations[integration].name) &&
<div className="absolute group z-50 top-0 right-0 flex flex-row">
<div className="w-max bg-yellow py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90">Coming Soon</div>
</div>
}
)}
</div>
)}
))}
</div>
</div>
</div>

View File

@@ -1,17 +1,17 @@
import React, { useState, useEffect } from "react";
import { useRouter } from "next/router";
import React, { useEffect,useState } from "react";
import Head from "next/head";
import Image from "next/image";
import Link from "next/link";
import InputField from "../components/basic/InputField";
import Error from "../components/basic/Error";
import Button from "../components/basic/buttons/Button";
import getWorkspaces from "./api/workspace/getWorkspaces";
import attemptLogin from "../components/utilities/attemptLogin";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useRouter } from "next/router";
import { faWarning } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Button from "~/components/basic/buttons/Button";
import Error from "~/components/basic/Error";
import InputField from "~/components/basic/InputField";
import attemptLogin from "~/utilities/attemptLogin";
import getWorkspaces from "./api/workspace/getWorkspaces";
export default function Login() {
const [email, setEmail] = useState("");
@@ -36,13 +36,18 @@ export default function Login() {
*/
const loginCheck = async () => {
setIsLoading(true);
await attemptLogin(email, password, setErrorLogin, router, false, true).then(
() => {
setTimeout(function () {
setIsLoading(false);
}, 2000);
}
);
await attemptLogin(
email,
password,
setErrorLogin,
router,
false,
true
).then(() => {
setTimeout(function () {
setIsLoading(false);
}, 2000);
});
};
return (
@@ -122,11 +127,17 @@ export default function Login() {
<p className="text-gray-400">I may have <Link href="/login"><u className="text-sky-500 cursor-pointer">forgotten my password.</u></Link></p>
</div> */}
</div>
{false &&
{false && (
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-300 rounded-md max-w-md mx-auto mt-4">
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-6 text-6xl"/>
We are experiencing minor technical difficulties. We are working on solving it right now. Please come back in a few minutes.
</div>}
<FontAwesomeIcon
icon={faWarning}
className="ml-2 mr-6 text-6xl"
/>
We are experiencing minor technical difficulties. We are
working on solving it right now. Please come back in a few
minutes.
</div>
)}
</div>
);
}
}

View File

@@ -1,6 +1,6 @@
import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faFolderOpen } from "@fortawesome/free-regular-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
export default function NoProjects() {
return (

View File

@@ -13,8 +13,8 @@ export default function Activity() {
<p className="mt-2 mb-1 text-xl">Your invite has expired. </p>
<p className="text-xl">Ask the administrator for a new one.</p>
<p className="text-md mt-8 text-gray-600 max-w-sm text-center">
Note: If it still doesn't work, please reach out to us at
support@infisical.com
Note: If it still {"doesn't work"}, please reach out to us
at support@infisical.com
</p>
</div>
</div>

View File

@@ -1,11 +1,12 @@
import React, { useState, useEffect } from "react";
import React, { useEffect,useState } from "react";
import Head from "next/head";
import Plan from "../../../components/billing/Plan";
import getOrganizationSubscriptions from "../../api/organization/GetOrgSubscription"
import getOrganizationUsers from "../../api/organization/GetOrgUsers"
import NavHeader from "../../../components/navigation/NavHeader";
import { STRIPE_PRODUCT_PRO, STRIPE_PRODUCT_STARTER } from "../../../components/utilities/config";
import Plan from "~/components/billing/Plan";
import NavHeader from "~/components/navigation/NavHeader";
import { STRIPE_PRODUCT_PRO, STRIPE_PRODUCT_STARTER } from "~/utilities/config";
import getOrganizationSubscriptions from "../../api/organization/GetOrgSubscription";
import getOrganizationUsers from "../../api/organization/GetOrgUsers";
export default function SettingsBilling() {
let [currentPlan, setCurrentPlan] = useState("");
@@ -46,57 +47,62 @@ export default function SettingsBilling() {
];
useEffect(async () => {
const subscriptions = await getOrganizationSubscriptions({orgId: localStorage.getItem("orgData.id")});
setCurrentPlan(subscriptions.data[0].plan.product)
const orgUsers = await getOrganizationUsers({orgId: localStorage.getItem("orgData.id")})
setNumUsers(orgUsers.length)
const subscriptions = await getOrganizationSubscriptions({
orgId: localStorage.getItem("orgData.id"),
});
setCurrentPlan(subscriptions.data[0].plan.product);
const orgUsers = await getOrganizationUsers({
orgId: localStorage.getItem("orgData.id"),
});
setNumUsers(orgUsers.length);
}, []);
return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head>
<title>Settings - Billing</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<div className="flex flex-row">
<div className="w-full max-h-screen pb-2 overflow-y-auto">
<NavHeader pageName="Usage & Billing"/>
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
<div className="flex flex-col justify-start items-start text-3xl">
<p className="font-semibold mr-4 text-gray-200">
Usage & Billing
</p>
<p className="font-normal mr-4 text-gray-400 text-base">
View and manage your organization's subscription here.
</p>
</div>
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head>
<title>Settings - Billing</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<div className="flex flex-row">
<div className="w-full max-h-screen pb-2 overflow-y-auto">
<NavHeader pageName="Usage & Billing" />
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
<div className="flex flex-col justify-start items-start text-3xl">
<p className="font-semibold mr-4 text-gray-200">
Usage & Billing
</p>
<p className="font-normal mr-4 text-gray-400 text-base">
View and manage your {"organization's"}{" "}
subscription here.
</p>
</div>
<div className="flex flex-col ml-6 text-mineshaft-50">
<p className="text-xl font-semibold">Subscription</p>
<div className="flex flex-row mt-4 overflow-x-auto">
{plans.map((plan) => (
<Plan
key={plan.name}
plan={plan}
/>
))}
</div>
<div className="flex flex-col ml-6 text-mineshaft-50">
<p className="text-xl font-semibold">Subscription</p>
<div className="flex flex-row mt-4 overflow-x-auto">
{plans.map((plan) => (
<Plan key={plan.name} plan={plan} />
))}
</div>
<p className="text-xl font-bold mt-12">Current Usage</p>
<div className="flex flex-row">
<div className="mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col">
<p className="text-6xl font-bold">{numUsers}</p>
<p className="text-gray-300">
{numUsers > 1
? "Organization members"
: "Organization member"}
</p>
</div>
<p className='text-xl font-bold mt-12'>Current Usage</p>
<div className="flex flex-row">
<div className="mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col">
<p className="text-6xl font-bold">{numUsers}</p>
<p className="text-gray-300">{numUsers > 1 ? "Organization members" : "Organization member"}</p>
</div>
{/* <div className="mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col">
{/* <div className="mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col">
<p className="text-6xl font-bold">1 </p>
<p className="text-gray-300">Organization projects</p>
</div> */}
</div>
</div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,27 +1,32 @@
import React, { useState, useEffect } from "react";
import { useRouter } from "next/router";
import React, { useEffect,useState } from "react";
import Head from "next/head";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faMagnifyingGlass, faPlus, faX } from "@fortawesome/free-solid-svg-icons";
import { useRouter } from "next/router";
import {
faMagnifyingGlass,
faPlus,
faX,
} from "@fortawesome/free-solid-svg-icons";
import { faCheck } from "@fortawesome/free-solid-svg-icons";
import InputField from "../../../components/basic/InputField";
import getWorkspaces from "../../api/workspace/getWorkspaces";
import AddIncidentContactDialog from "../../../components/basic/dialog/AddIncidentContactDialog";
import getIncidentContacts from "../../api/organization/getIncidentContacts";
import deleteIncidentContact from "../../api/organization/deleteIncidentContact";
import deleteWorkspace from "../../api/workspace/deleteWorkspace";
import AddUserDialog from "../../../components/basic/dialog/AddUserDialog";
import UserTable from "../../../components/basic/table/UserTable";
import getUser from "../../api/user/getUser";
import guidGenerator from "../../../components/utilities/randomId";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Button from "~/components/basic/buttons/Button";
import AddIncidentContactDialog from "~/components/basic/dialog/AddIncidentContactDialog";
import AddUserDialog from "~/components/basic/dialog/AddUserDialog";
import InputField from "~/components/basic/InputField";
import UserTable from "~/components/basic/table/UserTable";
import NavHeader from "~/components/navigation/NavHeader";
import guidGenerator from "~/utilities/randomId";
import addUserToOrg from "../../api/organization/addUserToOrg";
import getOrganizationUsers from "../../api/organization/GetOrgUsers";
import renameOrg from "../../api/organization/renameOrg";
import deleteIncidentContact from "../../api/organization/deleteIncidentContact";
import getIncidentContacts from "../../api/organization/getIncidentContacts";
import getOrganization from "../../api/organization/GetOrg";
import getOrganizationSubscriptions from "../../api/organization/GetOrgSubscription";
import NavHeader from "../../../components/navigation/NavHeader";
import Button from "../../../components/basic/buttons/Button";
import getOrganizationUsers from "../../api/organization/GetOrgUsers";
import renameOrg from "../../api/organization/renameOrg";
import getUser from "../../api/user/getUser";
import deleteWorkspace from "../../api/workspace/deleteWorkspace";
import getWorkspaces from "../../api/workspace/getWorkspaces";
export default function SettingsOrg() {
const [buttonReady, setButtonReady] = useState(false);
@@ -51,12 +56,12 @@ export default function SettingsOrg() {
});
let orgData = org;
setOrgName(orgData.name);
let incidentContactsData = await getIncidentContacts(localStorage.getItem("orgData.id"));
let incidentContactsData = await getIncidentContacts(
localStorage.getItem("orgData.id")
);
setIncidentContacts(
incidentContactsData?.map(
(contact) => contact.email
)
incidentContactsData?.map((contact) => contact.email)
);
const user = await getUser();
@@ -82,8 +87,10 @@ export default function SettingsOrg() {
publicKey: user.user?.publicKey,
}))
);
const subscriptions = await getOrganizationSubscriptions({orgId: localStorage.getItem("orgData.id")});
setCurrentPlan(subscriptions.data[0].plan.product)
const subscriptions = await getOrganizationSubscriptions({
orgId: localStorage.getItem("orgData.id"),
});
setCurrentPlan(subscriptions.data[0].plan.product);
}, []);
const modifyOrgName = (newName) => {
@@ -127,7 +134,10 @@ export default function SettingsOrg() {
setIncidentContacts(
incidentContacts.filter((contact) => contact != incidentContact)
);
deleteIncidentContact(localStorage.getItem("orgData.id"), incidentContact);
deleteIncidentContact(
localStorage.getItem("orgData.id"),
incidentContact
);
};
/**
@@ -160,7 +170,7 @@ export default function SettingsOrg() {
</Head>
<div className="flex flex-row">
<div className="w-full max-h-screen pb-2 overflow-y-auto">
<NavHeader pageName="Organization Settings"/>
<NavHeader pageName="Organization Settings" />
<AddIncidentContactDialog
isOpen={isAddIncidentContactOpen}
closeModal={closeAddIncidentContactModal}
@@ -201,7 +211,9 @@ export default function SettingsOrg() {
>
<Button
text="Save Changes"
onButtonPressed={() => submitChanges(orgName)}
onButtonPressed={() =>
submitChanges(orgName)
}
color="mineshaft"
size="md"
active={buttonReady}
@@ -289,7 +301,9 @@ export default function SettingsOrg() {
<div className="mt-4 mb-2 min-w-max flex flex-row items-end justify-end justify-center">
<Button
text="Add Contact"
onButtonPressed={openAddIncidentContactModal}
onButtonPressed={
openAddIncidentContactModal
}
color="mineshaft"
size="md"
icon={faPlus}
@@ -318,7 +332,10 @@ export default function SettingsOrg() {
email.includes(searchIncidentContact)
)
.map((contact) => (
<div key={guidGenerator()} className="flex flex-row items-center justify-between max-w-5xl px-4 py-3 hover:bg-white/5 border-t border-gray-600 w-full">
<div
key={guidGenerator()}
className="flex flex-row items-center justify-between max-w-5xl px-4 py-3 hover:bg-white/5 border-t border-gray-600 w-full"
>
<p className="text-gray-300">
{contact}
</p>

View File

@@ -1,15 +1,16 @@
import React, { useState, useEffect } from "react";
import React, { useEffect,useState } from "react";
import Head from "next/head";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheck, faX } from "@fortawesome/free-solid-svg-icons";
import InputField from "../../../components/basic/InputField";
import getUser from "../../api/user/getUser";
import NavHeader from "../../../components/navigation/NavHeader";
import passwordCheck from "../../../components/utilities/checks/PasswordCheck";
import changePassword from "../../../components/utilities/changePassword";
import issueBackupKey from "../../../components/utilities/issueBackupKey";
import Button from "../../../components/basic/buttons/Button";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Button from "~/components/basic/buttons/Button";
import InputField from "~/components/basic/InputField";
import NavHeader from "~/components/navigation/NavHeader";
import changePassword from "~/utilities/changePassword";
import passwordCheck from "~/utilities/checks/PasswordCheck";
import issueBackupKey from "~/utilities/issueBackupKey";
import getUser from "../../api/user/getUser";
export default function PersonalSettings() {
const [personalEmail, setPersonalEmail] = useState("");
@@ -31,8 +32,6 @@ export default function PersonalSettings() {
setPersonalName(user.firstName + " " + user.lastName);
}, []);
return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head>
@@ -41,7 +40,10 @@ export default function PersonalSettings() {
</Head>
<div className="flex flex-row">
<div className="w-full max-h-screen pb-2 overflow-y-auto">
<NavHeader pageName="Personal Settings" isProjectRelated={false}/>
<NavHeader
pageName="Personal Settings"
isProjectRelated={false}
/>
<div className="flex flex-row justify-between items-center ml-6 mt-8 mb-6 text-xl max-w-5xl">
<div className="flex flex-col justify-start items-start text-3xl">
<p className="font-semibold mr-4 text-gray-200">
@@ -137,52 +139,133 @@ export default function PersonalSettings() {
type="password"
value={newPassword}
isRequired
error={passwordErrorLength && passwordErrorLowerCase && passwordErrorNumber}
error={
passwordErrorLength &&
passwordErrorLowerCase &&
passwordErrorNumber
}
/>
</div>
{(passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber) ? <div className="w-full mt-3 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md max-w-xl mb-2">
<div className={`text-gray-400 text-sm mb-1`}>Password should contain at least:</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLength
? <FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5"/>
: <FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2"/>}
<div className={`${passwordErrorLength ? "text-gray-400" : "text-gray-600"} text-sm`}>14 characters</div>
{passwordErrorLength ||
passwordErrorLowerCase ||
passwordErrorNumber ? (
<div className="w-full mt-3 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md max-w-xl mb-2">
<div
className={`text-gray-400 text-sm mb-1`}
>
Password should contain at least:
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLength ? (
<FontAwesomeIcon
icon={faX}
className="text-md text-red mr-2.5"
/>
) : (
<FontAwesomeIcon
icon={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorLength
? "text-gray-400"
: "text-gray-600"
} text-sm`}
>
14 characters
</div>
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLowerCase ? (
<FontAwesomeIcon
icon={faX}
className="text-md text-red mr-2.5"
/>
) : (
<FontAwesomeIcon
icon={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorLowerCase
? "text-gray-400"
: "text-gray-600"
} text-sm`}
>
1 lowercase character
</div>
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorNumber ? (
<FontAwesomeIcon
icon={faX}
className="text-md text-red mr-2.5"
/>
) : (
<FontAwesomeIcon
icon={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorNumber
? "text-gray-400"
: "text-gray-600"
} text-sm`}
>
1 number
</div>
</div>
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLowerCase
? <FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5"/>
: <FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2"/>}
<div className={`${passwordErrorLowerCase ? "text-gray-400" : "text-gray-600"} text-sm`}>1 lowercase character</div>
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorNumber
? <FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5"/>
: <FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2"/>}
<div className={`${passwordErrorNumber ? "text-gray-400" : "text-gray-600"} text-sm`}>1 number</div>
</div>
</div> : <div className="py-2"></div>}
) : (
<div className="py-2"></div>
)}
<div className="flex flex-row items-center mt-3 w-52 pr-3">
<Button
text="Change Password"
onButtonPressed={() => {
if (!passwordErrorLength && !passwordErrorLowerCase && !passwordErrorNumber) {
if (
!passwordErrorLength &&
!passwordErrorLowerCase &&
!passwordErrorNumber
) {
changePassword(
personalEmail,
currentPassword,
personalEmail,
currentPassword,
newPassword,
setCurrentPasswordError,
setPasswordChanged,
setCurrentPassword,
setNewPassword
)
);
}
}}
color="mineshaft"
size="md"
active={(newPassword != "") && (currentPassword != "") && !(passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber)}
active={
newPassword != "" &&
currentPassword != "" &&
!(
passwordErrorLength ||
passwordErrorLowerCase ||
passwordErrorNumber
)
}
textDisabled="Change Password"
/>
<FontAwesomeIcon icon={faCheck} className={`ml-4 text-primary text-3xl ${passwordChanged ? "opacity-100" : "opacity-0"} duration-300`}/>
<FontAwesomeIcon
icon={faCheck}
className={`ml-4 text-primary text-3xl ${
passwordChanged
? "opacity-100"
: "opacity-0"
} duration-300`}
/>
</div>
</div>
@@ -193,10 +276,14 @@ export default function PersonalSettings() {
Emergency Kit
</p>
<p className="text-sm text-mineshaft-300 min-w-max">
Your Emergency Kit contains the information youll need to sign in to your Infisical account.
Your Emergency Kit contains the
information youll need to sign in to
your Infisical account.
</p>
<p className="text-sm text-mineshaft-300 mb-5 min-w-max">
Only the latest issued Emergency Kit remains valid. To get a new Emergency Kit, verify your password.
Only the latest issued Emergency Kit
remains valid. To get a new Emergency
Kit, verify your password.
</p>
</div>
</div>
@@ -216,11 +303,11 @@ export default function PersonalSettings() {
text="Download Emergency Kit"
onButtonPressed={() => {
issueBackupKey({
email: personalEmail,
password: backupPassword,
personalName,
email: personalEmail,
password: backupPassword,
personalName,
setBackupKeyError,
setBackupKeyIssued
setBackupKeyIssued,
});
}}
color="mineshaft"
@@ -228,7 +315,14 @@ export default function PersonalSettings() {
active={backupPassword != ""}
textDisabled="Download Emergency Kit"
/>
<FontAwesomeIcon icon={faCheck} className={`ml-4 text-primary text-3xl ${backupKeyIssued ? "opacity-100" : "opacity-0"} duration-300`}/>
<FontAwesomeIcon
icon={faCheck}
className={`ml-4 text-primary text-3xl ${
backupKeyIssued
? "opacity-100"
: "opacity-0"
} duration-300`}
/>
</div>
</div>
</div>

View File

@@ -1,17 +1,18 @@
import React, { useState, useRef, useEffect } from "react";
import { useRouter } from "next/router";
import React, { useEffect,useRef, useState } from "react";
import Head from "next/head";
import { useRouter } from "next/router";
import { faCheck, faPlus } from "@fortawesome/free-solid-svg-icons";
import InputField from "../../../components/basic/InputField";
import Button from "~/components/basic/buttons/Button";
import AddServiceTokenDialog from "~/components/basic/dialog/AddServiceTokenDialog";
import InputField from "~/components/basic/InputField";
import ServiceTokenTable from "~/components/basic/table/ServiceTokenTable";
import NavHeader from "~/components/navigation/NavHeader";
import getServiceTokens from "../../api/serviceToken/getServiceTokens";
import deleteWorkspace from "../../api/workspace/deleteWorkspace";
import getWorkspaces from "../../api/workspace/getWorkspaces";
import renameWorkspace from "../../api/workspace/renameWorkspace";
import deleteWorkspace from "../../api/workspace/deleteWorkspace";
import NavHeader from "../../../components/navigation/NavHeader";
import Button from "../../../components/basic/buttons/Button";
import ServiceTokenTable from "../../../components/basic/table/ServiceTokenTable";
import getServiceTokens from "../../api/serviceToken/getServiceTokens"
import AddServiceTokenDialog from "../../../components/basic/dialog/AddServiceTokenDialog";
export default function SettingsBasic() {
const [buttonReady, setButtonReady] = useState(false);
@@ -22,7 +23,8 @@ export default function SettingsBasic() {
useState("");
const [workspaceId, setWorkspaceId] = useState("");
const [isAddOpen, setIsAddOpen] = useState(false);
let [isAddServiceTokenDialogOpen, setIsAddServiceTokenDialogOpen] = useState(false);
let [isAddServiceTokenDialogOpen, setIsAddServiceTokenDialogOpen] =
useState(false);
useEffect(async () => {
let userWorkspaces = await getWorkspaces();
@@ -31,7 +33,9 @@ export default function SettingsBasic() {
setWorkspaceName(userWorkspace.name);
}
});
let tempServiceTokens = await getServiceTokens({workspaceId: router.query.id});
let tempServiceTokens = await getServiceTokens({
workspaceId: router.query.id,
});
setServiceTokens(tempServiceTokens);
}, []);
@@ -59,8 +63,8 @@ export default function SettingsBasic() {
const closeAddServiceTokenModal = () => {
setIsAddServiceTokenDialogOpen(false);
}
};
/**
* This function deleted a workspace.
* It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete
@@ -97,7 +101,10 @@ export default function SettingsBasic() {
/>
<div className="flex flex-row mr-6 max-w-5xl">
<div className="w-full max-h-screen pb-2 overflow-y-auto">
<NavHeader pageName="Project Settings" isProjectRelated={true}/>
<NavHeader
pageName="Project Settings"
isProjectRelated={true}
/>
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
<div className="flex flex-col justify-start items-start text-3xl">
<p className="font-semibold mr-4 text-gray-200">
@@ -133,7 +140,9 @@ export default function SettingsBasic() {
>
<Button
text="Save Changes"
onButtonPressed={() => submitChanges(workspaceName)}
onButtonPressed={() =>
submitChanges(workspaceName)
}
color="mineshaft"
size="md"
active={buttonReady}
@@ -157,6 +166,7 @@ export default function SettingsBasic() {
For more guidance, including code
snipets for various languages and
frameworks, see{" "}
{/* eslint-disable-next-line react/jsx-no-target-blank */}
<a
href="https://infisical.com/docs/getting-started/introduction"
target="_blank"
@@ -184,20 +194,30 @@ export default function SettingsBasic() {
Service Tokens
</p>
<p className="text-base text-gray-400 mb-4">
Every service token is specific to you, a certain project and a certain environment within this project.
Every service token is specific
to you, a certain project and a
certain environment within this
project.
</p>
</div>
<div className="w-48">
<Button
text="Add New Token"
onButtonPressed={() => {setIsAddServiceTokenDialogOpen(true)}}
onButtonPressed={() => {
setIsAddServiceTokenDialogOpen(
true
);
}}
color="mineshaft"
icon={faPlus}
size="md"
/>
/>
</div>
</div>
<ServiceTokenTable data={serviceTokens} workspaceName={workspaceName}/>
<ServiceTokenTable
data={serviceTokens}
workspaceName={workspaceName}
/>
</div>
{/* <div className="bg-white/5 rounded-md px-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pb-6 pt-6">

View File

@@ -1,24 +1,24 @@
import React, { useState, useRef, useEffect } from "react";
import { useRouter } from "next/router";
import React, { useEffect, useRef, useState } from "react";
import dynamic from "next/dynamic";
import Head from "next/head";
import Image from "next/image";
import Link from "next/link";
import dynamic from "next/dynamic";
import { useRouter } from "next/router";
import { faCheck, faWarning, faX } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Aes256Gcm from "~/components/aes-256-gcm";
import Button from "~/components/basic/buttons/Button";
import Error from "~/components/basic/Error";
import InputField from "~/components/basic/InputField";
import attemptLogin from "~/utilities/attemptLogin";
import passwordCheck from "~/utilities/checks/PasswordCheck";
import issueBackupKey from "~/utilities/issueBackupKey";
import InputField from "../components/basic/InputField";
import Error from "../components/basic/Error";
import Button from "../components/basic/buttons/Button";
import sendVerificationEmail from "./api/auth/SendVerificationEmail";
import checkEmailVerificationCode from "./api/auth/CheckEmailVerificationCode";
import completeAccountInformationSignup from "./api/auth/CompleteAccountInformationSignup";
import Aes256Gcm from "../components/aes-256-gcm";
import passwordCheck from "../components/utilities/checks/PasswordCheck";
import sendVerificationEmail from "./api/auth/SendVerificationEmail";
import getWorkspaces from "./api/workspace/getWorkspaces";
import attemptLogin from "../components/utilities/attemptLogin";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheck, faX, faWarning } from "@fortawesome/free-solid-svg-icons";
import issueBackupKey from "../components/utilities/issueBackupKey";
const ReactCodeInput = dynamic(import("react-code-input"));
const nacl = require("tweetnacl");
@@ -73,7 +73,8 @@ export default function SignUp() {
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
const [passwordErrorUpperCase, setPasswordErrorUpperCase] = useState(false);
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
const [passwordErrorSpecialChar, setPasswordErrorSpecialChar] = useState(false);
const [passwordErrorSpecialChar, setPasswordErrorSpecialChar] =
useState(false);
const [emailError, setEmailError] = useState(false);
const [emailErrorMessage, setEmailErrorMessage] = useState("");
const [step, setStep] = useState(1);
@@ -108,7 +109,7 @@ export default function SignUp() {
// Checking if the code matches the email.
const response = await checkEmailVerificationCode(email, code);
if (response.status == "200" || code == "111222") {
setVerificationToken((await response.json()).token)
setVerificationToken((await response.json()).token);
setStep(3);
} else {
setCodeError(true);
@@ -180,7 +181,14 @@ export default function SignUp() {
const { ciphertext, iv, tag } = Aes256Gcm.encrypt(
PRIVATE_KEY,
password.slice(0, 32).padStart(32 + (password.slice(0, 32).length - new Blob([password]).size), "0")
password
.slice(0, 32)
.padStart(
32 +
(password.slice(0, 32).length -
new Blob([password]).size),
"0"
)
);
localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY);
@@ -191,19 +199,21 @@ export default function SignUp() {
},
async () => {
client.createVerifier(async (err, result) => {
const response = await completeAccountInformationSignup({
email,
firstName,
lastName,
organizationName: firstName + "'s organization",
publicKey: PUBLIC_KEY,
ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier,
token: verificationToken
});
const response = await completeAccountInformationSignup(
{
email,
firstName,
lastName,
organizationName: firstName + "'s organization",
publicKey: PUBLIC_KEY,
ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier,
token: verificationToken,
}
);
// if everything works, go the main dashboard page.
if (!errorCheck && response.status == "200") {
@@ -243,7 +253,7 @@ export default function SignUp() {
const step1 = (
<div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-8 md:px-6 mx-1 mb-48 md:mb-16 rounded-xl drop-shadow-xl">
<p className="text-4xl font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
Let's get started
{"Let'"}s get started
</p>
<div className="flex flex-col items-center justify-center w-full md:pb-2 max-h-24 max-w-md mx-auto pt-2">
<Link href="/login">
@@ -276,7 +286,11 @@ export default function SignUp() {
and acknowledged the Privacy Policy.
</p>
<div className="text-l mt-6 m-2 md:m-8 px-8 py-1 text-lg">
<Button text="Get Started" onButtonPressed={emailCheck} size="lg"/>
<Button
text="Get Started"
onButtonPressed={emailCheck}
size="lg"
/>
</div>
</div>
</div>
@@ -286,7 +300,7 @@ export default function SignUp() {
const step2 = (
<div className="bg-bunker w-max mx-auto h-7/12 pt-10 pb-4 px-8 rounded-xl drop-shadow-xl mb-64 md:mb-16">
<p className="text-l flex justify-center text-gray-400">
We've sent a verification email to{" "}
{"We've"} sent a verification email to{" "}
</p>
<p className="text-l flex justify-center font-semibold my-2 text-gray-400">
{email}{" "}
@@ -313,7 +327,11 @@ export default function SignUp() {
<Error text="Oops. Your code is wrong. Please try again." />
)}
<div className="flex max-w-min flex-col items-center justify-center md:p-2 max-h-24 max-w-md mx-auto text-lg px-4 mt-4 mb-2">
<Button text="Verify" onButtonPressed={incrementStep} size="lg"/>
<Button
text="Verify"
onButtonPressed={incrementStep}
size="lg"
/>
</div>
<div className="flex flex-col items-center justify-center w-full max-h-24 max-w-md mx-auto pt-2">
{/* <Link href="/login">
@@ -374,37 +392,101 @@ export default function SignUp() {
type="password"
value={password}
isRequired
error={passwordErrorLength && passwordErrorNumber && passwordErrorLowerCase}
error={
passwordErrorLength &&
passwordErrorNumber &&
passwordErrorLowerCase
}
/>
{(passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber) ? <div className="w-full mt-4 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md">
<div className={`text-gray-400 text-sm mb-1`}>Password should contain at least:</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLength
? <FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5"/>
: <FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2"/>}
<div className={`${passwordErrorLength ? "text-gray-400" : "text-gray-600"} text-sm`}>14 characters</div>
{passwordErrorLength ||
passwordErrorLowerCase ||
passwordErrorNumber ? (
<div className="w-full mt-4 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md">
<div className={`text-gray-400 text-sm mb-1`}>
Password should contain at least:
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLength ? (
<FontAwesomeIcon
icon={faX}
className="text-md text-red mr-2.5"
/>
) : (
<FontAwesomeIcon
icon={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorLength
? "text-gray-400"
: "text-gray-600"
} text-sm`}
>
14 characters
</div>
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLowerCase ? (
<FontAwesomeIcon
icon={faX}
className="text-md text-red mr-2.5"
/>
) : (
<FontAwesomeIcon
icon={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorLowerCase
? "text-gray-400"
: "text-gray-600"
} text-sm`}
>
1 lowercase character
</div>
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorNumber ? (
<FontAwesomeIcon
icon={faX}
className="text-md text-red mr-2.5"
/>
) : (
<FontAwesomeIcon
icon={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorNumber
? "text-gray-400"
: "text-gray-600"
} text-sm`}
>
1 number
</div>
</div>
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLowerCase
? <FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5"/>
: <FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2"/>}
<div className={`${passwordErrorLowerCase ? "text-gray-400" : "text-gray-600"} text-sm`}>1 lowercase character</div>
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorNumber
? <FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5"/>
: <FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2"/>}
<div className={`${passwordErrorNumber ? "text-gray-400" : "text-gray-600"} text-sm`}>1 number</div>
</div>
</div> : <div className="py-2"></div>}
) : (
<div className="py-2"></div>
)}
</div>
<div className="flex flex-col items-center justify-center md:p-2 max-h-48 max-w-max mx-auto text-lg px-2 py-3">
<Button text="Sign Up" loading={isLoading} onButtonPressed={signupErrorCheck} size="lg" />
<Button
text="Sign Up"
loading={isLoading}
onButtonPressed={signupErrorCheck}
size="lg"
/>
</div>
</div>
);
// Step 4 of the sign up process (download the emergency kit pdf)
const step4 = (
<div className="bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl">
@@ -412,23 +494,32 @@ export default function SignUp() {
Save your Emergency Kit
</p>
<div className="flex flex-col items-center justify-center w-full mt-4 md:mt-8 max-w-md text-gray-400 text-md rounded-md px-2">
<div>If you get locked out of your account, your Emergency Kit is the only way to sign in.</div>
<div className="mt-3">We recommend you download it and keep it somewhere safe.</div>
<div>
If you get locked out of your account, your Emergency Kit is
the only way to sign in.
</div>
<div className="mt-3">
We recommend you download it and keep it somewhere safe.
</div>
</div>
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-400 rounded-md max-w-xs md:max-w-md mx-auto mt-4">
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-4 text-4xl"/>
It contains your Secret Key which we cannot access or recover for you if you lose it.
<FontAwesomeIcon
icon={faWarning}
className="ml-2 mr-4 text-4xl"
/>
It contains your Secret Key which we cannot access or recover
for you if you lose it.
</div>
<div className="flex flex-row items-center justify-center w-3/4 md:w-full md:p-2 max-h-28 max-w-max mx-auto mt-6 py-1 md:mt-4 text-lg text-center md:text-left">
<Button
text="Download PDF"
<Button
text="Download PDF"
onButtonPressed={async () => {
await issueBackupKey({
email,
password,
personalName: (firstName + " " + lastName),
email,
password,
personalName: firstName + " " + lastName,
setBackupKeyError,
setBackupKeyIssued
setBackupKeyIssued,
});
router.push("/dashboard/");
}}
@@ -476,7 +567,13 @@ export default function SignUp() {
/>
</div>
</Link>
{step == 1 ? step1 : step == 2 ? step2 : step == 3 ? step3 : step4}
{step == 1
? step1
: step == 2
? step2
: step == 3
? step3
: step4}
</div>
</div>
);

View File

@@ -1,19 +1,19 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
import Head from "next/head";
import Image from "next/image";
import Link from "next/link";
import InputField from "../components/basic/InputField";
import Button from "../components/basic/buttons/Button";
import completeAccountInformationSignupInvite from "./api/auth/CompleteAccountInformationSignupInvite";
import Aes256Gcm from "../components/aes-256-gcm";
import passwordCheck from "../components/utilities/checks/PasswordCheck";
import attemptLogin from "../components/utilities/attemptLogin";
import { useRouter } from "next/router";
import { faCheck, faWarning,faX } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheck, faX, faWarning } from "@fortawesome/free-solid-svg-icons";
import issueBackupKey from "../components/utilities/issueBackupKey";
import Aes256Gcm from "~/components/aes-256-gcm";
import Button from "~/components/basic/buttons/Button";
import InputField from "~/components/basic/InputField";
import attemptLogin from "~/utilities/attemptLogin";
import passwordCheck from "~/utilities/checks/PasswordCheck";
import issueBackupKey from "~/utilities/issueBackupKey";
import completeAccountInformationSignupInvite from "./api/auth/CompleteAccountInformationSignupInvite";
import verifySignupInvite from "./api/auth/VerifySignupInvite";
const nacl = require("tweetnacl");
@@ -76,7 +76,14 @@ export default function SignupInvite() {
const { ciphertext, iv, tag } = Aes256Gcm.encrypt(
PRIVATE_KEY,
password.slice(0, 32).padStart(32 + (password.slice(0, 32).length - new Blob([password]).size), "0")
password
.slice(0, 32)
.padStart(
32 +
(password.slice(0, 32).length -
new Blob([password]).size),
"0"
)
);
localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY);
@@ -87,18 +94,19 @@ export default function SignupInvite() {
},
async () => {
client.createVerifier(async (err, result) => {
const response = await completeAccountInformationSignupInvite({
email,
firstName,
lastName,
publicKey: PUBLIC_KEY,
ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier,
token: verificationToken
});
const response =
await completeAccountInformationSignupInvite({
email,
firstName,
lastName,
publicKey: PUBLIC_KEY,
ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier,
token: verificationToken,
});
// if everything works, go the main dashboard page.
if (!errorCheck && response.status == "200") {
@@ -148,19 +156,19 @@ export default function SignupInvite() {
alt="verify email"
></Image>
<div className="flex flex-row items-center justify-center w-3/4 md:w-full md:p-2 max-h-28 max-w-xs md:max-w-md mx-auto text-lg py-1 text-center md:text-left">
<Button
<Button
text="Confirm Email"
onButtonPressed={async () => {
const response = await verifySignupInvite({
email,
code: token
email,
code: token,
});
if (response.status == 200) {
setVerificationToken((await response.json()).token)
setVerificationToken((await response.json()).token);
setStep(2);
} else {
console.log("ERROR", response)
router.push("/requestnewinvite")
console.log("ERROR", response);
router.push("/requestnewinvite");
}
}}
size="lg"
@@ -213,29 +221,89 @@ export default function SignupInvite() {
type="password"
value={password}
isRequired
error={passwordErrorLength && passwordErrorNumber && passwordErrorLowerCase}
error={
passwordErrorLength &&
passwordErrorNumber &&
passwordErrorLowerCase
}
/>
{(passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber) ? <div className="w-full mt-4 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md">
<div className={`text-gray-400 text-sm mb-1`}>Password should contain at least:</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLength
? <FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5"/>
: <FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2"/>}
<div className={`${passwordErrorLength ? "text-gray-400" : "text-gray-600"} text-sm`}>14 characters</div>
{passwordErrorLength ||
passwordErrorLowerCase ||
passwordErrorNumber ? (
<div className="w-full mt-4 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md">
<div className={`text-gray-400 text-sm mb-1`}>
Password should contain at least:
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLength ? (
<FontAwesomeIcon
icon={faX}
className="text-md text-red mr-2.5"
/>
) : (
<FontAwesomeIcon
icon={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorLength
? "text-gray-400"
: "text-gray-600"
} text-sm`}
>
14 characters
</div>
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLowerCase ? (
<FontAwesomeIcon
icon={faX}
className="text-md text-red mr-2.5"
/>
) : (
<FontAwesomeIcon
icon={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorLowerCase
? "text-gray-400"
: "text-gray-600"
} text-sm`}
>
1 lowercase character
</div>
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorNumber ? (
<FontAwesomeIcon
icon={faX}
className="text-md text-red mr-2.5"
/>
) : (
<FontAwesomeIcon
icon={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorNumber
? "text-gray-400"
: "text-gray-600"
} text-sm`}
>
1 number
</div>
</div>
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLowerCase
? <FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5"/>
: <FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2"/>}
<div className={`${passwordErrorLowerCase ? "text-gray-400" : "text-gray-600"} text-sm`}>1 lowercase character</div>
</div>
<div className="flex flex-row justify-start items-center ml-1">
{passwordErrorNumber
? <FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5"/>
: <FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2"/>}
<div className={`${passwordErrorNumber ? "text-gray-400" : "text-gray-600"} text-sm`}>1 number</div>
</div>
</div> : <div className="py-2"></div>}
) : (
<div className="py-2"></div>
)}
</div>
<div className="flex flex-col items-center justify-center w-full md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-md mx-auto text-lg">
<Button
@@ -257,23 +325,32 @@ export default function SignupInvite() {
Save your Emergency Kit
</p>
<div className="flex flex-col items-center justify-center w-full mt-4 md:mt-8 max-w-md text-gray-400 text-md rounded-md px-2">
<div>If you get locked out of your account, your Emergency Kit is the only way to sign in.</div>
<div className="mt-3">We recommend you download it and keep it somewhere safe.</div>
<div>
If you get locked out of your account, your Emergency Kit is
the only way to sign in.
</div>
<div className="mt-3">
We recommend you download it and keep it somewhere safe.
</div>
</div>
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-400 rounded-md max-w-xs md:max-w-md mx-auto mt-4">
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-4 text-4xl"/>
It contains your Secret Key which we cannot access or recover for you if you lose it.
<FontAwesomeIcon
icon={faWarning}
className="ml-2 mr-4 text-4xl"
/>
It contains your Secret Key which we cannot access or recover
for you if you lose it.
</div>
<div className="flex flex-row items-center justify-center w-3/4 md:w-full md:p-2 max-h-28 max-w-xs md:max-w-md mx-auto mt-6 md:mt-8 py-1 text-lg text-center md:text-left">
<Button
text="Download PDF"
<Button
text="Download PDF"
onButtonPressed={async () => {
await issueBackupKey({
email,
password,
personalName: (firstName + " " + lastName),
email,
password,
personalName: firstName + " " + lastName,
setBackupKeyError,
setBackupKeyIssued
setBackupKeyIssued,
});
router.push("/dashboard/");
}}

View File

@@ -1,21 +1,22 @@
import React, { useState, useEffect } from "react";
import { useRouter } from "next/router";
import React, { useEffect, useState } from "react";
import Head from "next/head";
import Image from "next/image";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useRouter } from "next/router";
import { faMagnifyingGlass, faPlus } from "@fortawesome/free-solid-svg-icons";
import UserTable from "../../components/basic/table/UserTable";
import getWorkspaceUsers from "../api/workspace/getWorkspaceUsers";
import guidGenerator from "../../components/utilities/randomId";
import AddProjectMemberDialog from "../../components/basic/dialog/AddProjectMemberDialog";
// import DeleteUserDialog from '../../components/basic/dialog/DeleteUserDialog';
import addUserToWorkspace from "../api/workspace/addUserToWorkspace";
import getUser from "../api/user/getUser";
import uploadKeys from "../api/workspace/uploadKeys";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Button from "~/components/basic/buttons/Button";
import AddProjectMemberDialog from "~/components/basic/dialog/AddProjectMemberDialog";
import UserTable from "~/components/basic/table/UserTable";
import NavHeader from "~/components/navigation/NavHeader";
import guidGenerator from "~/utilities/randomId";
import getOrganizationUsers from "../api/organization/GetOrgUsers";
import NavHeader from "../../components/navigation/NavHeader";
import Button from "../../components/basic/buttons/Button";
import getUser from "../api/user/getUser";
// import DeleteUserDialog from '~/components/basic/dialog/DeleteUserDialog';
import addUserToWorkspace from "../api/workspace/addUserToWorkspace";
import getWorkspaceUsers from "../api/workspace/getWorkspaceUsers";
import uploadKeys from "../api/workspace/uploadKeys";
// #TODO: Update all the workspaceIds
const crypto = require("crypto");
@@ -85,6 +86,7 @@ export default function Users() {
const [userList, setUserList] = useState();
const [orgUserList, setOrgUserList] = useState([]);
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(async () => {
const user = await getUser();
setPersonalEmail(user.email);
@@ -98,15 +100,13 @@ export default function Users() {
firstName: user.user?.firstName,
lastName: user.user?.lastName,
email:
user.user?.email == null
? user.inviteEmail
: user.user?.email,
user.user?.email == null ? user.inviteEmail : user.user?.email,
role: user?.role,
status: user?.status,
userId: user.user?._id,
membershipId: user._id,
publicKey: user.user?.publicKey,
}))
}));
setUserList(tempUserList);
const orgUsers = await getOrganizationUsers({
orgId: localStorage.getItem("orgData.id"),
@@ -114,15 +114,15 @@ export default function Users() {
setOrgUserList(orgUsers);
setEmail(
orgUsers
?.filter((user) => user.status == "accepted")
.map((user) => user.user.email)
.filter(
(email) =>
!tempUserList
?.map((user1) => user1.email)
.includes(email)
)[0]
)
?.filter((user) => user.status == "accepted")
.map((user) => user.user.email)
.filter(
(email) =>
!tempUserList
?.map((user1) => user1.email)
.includes(email)
)[0]
);
}, []);
return userList ? (
@@ -131,7 +131,7 @@ export default function Users() {
<title>Users</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<NavHeader pageName="Project Members" isProjectRelated={true}/>
<NavHeader pageName="Project Members" isProjectRelated={true} />
<div className="flex flex-col justify-start items-start px-6 py-6 pb-4 text-3xl">
<p className="font-semibold mr-4 text-white">Project Members</p>
<p className="mr-4 text-base text-gray-400">

File diff suppressed because it is too large Load Diff