Merge remote-tracking branch 'upstream/main' into feat/39

This commit is contained in:
이강준
2022-12-06 22:07:29 +09:00
163 changed files with 2639 additions and 1327 deletions
+18 -15
View File
@@ -1,10 +1,21 @@
{
"extends": "next/core-web-vitals",
"plugins": [
"simple-import-sort"
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"next",
"next/core-web-vitals"
],
"parser": "@typescript-eslint/parser",
"plugins": ["simple-import-sort", "@typescript-eslint"],
"rules": {
"react-hooks/exhaustive-deps": "off",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"simple-import-sort/exports": "warn",
"simple-import-sort/imports": [
"warn",
@@ -18,15 +29,9 @@
"^(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"
],
["^react", "^next", "^@?\\w"],
// Internal packages.
[
"^~(/.*|$)"
],
["^~(/.*|$)"],
// Relative imports
[
"^\\.\\.(?!/?$)",
@@ -36,11 +41,9 @@
"^\\./?$"
],
// Style imports.
[
"^.+\\.?(css|scss)$"
]
["^.+\\.?(css|scss)$"]
]
}
]
}
}
}
+10 -8
View File
@@ -1,20 +1,22 @@
This is the client repository for Infisical.
## Before you get started with development locally
## Before you get started with development locally
Please ensure you have Docker and Docker Compose installed for your OS.
### Steps to start server
### Steps to start server
- `CD` into the repo
- run command `docker-compose -f docker-compose.dev.yml up --build --force-recreate`
- Vist localhost:3000 and the website should be live
- Visit localhost:3000 and the website should be live
### Steps to shutdown this Docker compose
### Steps to shutdown this Docker compose
- `CD` into this repo
- run command `docker-compose -f docker-compose-dev.yml down`
- run command `docker-compose -f docker-compose.dev.yml down`
### Notes
Any changes made to local files in the `/components`, `/pages`, `styles` will be hot reloaded. If would like like to watch for other files or folders live, please add them to the docker volume.
Any changes made to local files in the `/components`, `/pages`, `/styles` will be hot reloaded. If would like like to watch for other files or folders live, please add them to the docker volume.
You will also need to ensure that a .env.local file exists with all required environment variables
@@ -1,8 +1,8 @@
import React, { useState } from "react";
import React from "react";
import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
export default function Error({ text }) {
export default function Error({ text }: { text: string }): JSX.Element {
return (
<div className="relative bg-red-500 opacity-100 border flex flex-row justify-center m-auto items-center w-fit rounded-full mb-4">
<FontAwesomeIcon
@@ -1,6 +1,5 @@
import React from "react";
import { Fragment } from "react";
import { useRouter } from "next/router";
import {
faAngleDown,
faCheck,
@@ -9,9 +8,25 @@ import {
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Listbox, Transition } from "@headlessui/react";
type ListBoxProps = {
selected: string,
onChange: () => void,
data: string[],
text: string,
buttonAction: () => void,
width: string,
}
/**
* This is the component that we use for drop down lists.
* @param {*} param0
* @param {object} obj
* @param {string} obj.selected - the item that is currently selected
* @param {function} obj.onChange - what happends if you select the item inside a list
* @param {string[]} obj.data - all the options available
* @param {string} obj.text - the text that shows us in front of the select option
* @param {function} obj.buttonAction - if there is a button at the bottom of the list, this is the action that happens when you click the button
* @param {string} obj.width - button width
* @returns
*/
export default function ListBox({
@@ -21,10 +36,7 @@ export default function ListBox({
text,
buttonAction,
width,
workspaceMapping = [],
}) {
const router = useRouter();
} : ListBoxProps): JSX.Element {
return (
<Listbox value={selected} onChange={onChange}>
<div className="relative">
@@ -61,7 +73,7 @@ export default function ListBox({
`my-0.5 relative cursor-default select-none py-2 pl-10 pr-4 rounded-md ${
selected ? "bg-white/10 text-gray-400 font-bold" : ""
} ${
active & !selected
active && !selected
? "bg-white/5 text-mineshaft-200 cursor-pointer"
: "text-gray-400"
} `
+46 -20
View File
@@ -1,35 +1,59 @@
import React from "react";
import Image from "next/image";
import { IconProp } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
FontAwesomeIcon,
FontAwesomeIconProps,
} from "@fortawesome/react-fontawesome";
var classNames = require("classnames");
const classNames = require("classnames");
type ButtonProps = {
text: string;
onButtonPressed: () => void;
loading: boolean;
loading?: boolean;
color: string;
size: string;
icon: IconProp;
active: boolean;
iconDisabled: string;
textDisabled: string;
}
icon?: IconProp;
active?: boolean;
iconDisabled?: string;
textDisabled?: string;
};
export default function Button (props: ButtonProps): JSX.Element {
/**
* This is the main butto component in the app.
* @param {object} props
* @param {string} props.text - text inside the button
* @param {function} props.onButtonPressed - the action that happens when the button is clicked
* @param {boolean} props.loading - if a button is currently in the laoding state
* @param {string} props.color - button color
* @param {string} props.size - button size
* @param {FontAwesomeIconProps} props.icon - the icon inside the button
* @param {boolean} props.active - if the button is active or disabled
* @param {FontAwesomeIconProps} props.text - the icon inside the button when it is disabled
* @param {string} props.textDisable - text inside the button when it is disabled
* @returns
*/
export default function Button(props: ButtonProps): JSX.Element {
// Check if the button show always be 'active' - then true;
// or if it should switch between 'active' and 'disabled' - then give the status
const activityStatus = props.active || (props.text != "" && props.textDisabled == undefined)
let styleButton = classNames(
const activityStatus =
props.active || (props.text != "" && props.textDisabled == undefined);
const styleButton = classNames(
"group m-auto md:m-0 inline-block rounded-md duration-200",
// Setting background colors and hover modes
props.color == "mineshaft" && activityStatus && "bg-mineshaft-700 hover:bg-primary",
props.color == "mineshaft" &&
activityStatus &&
"bg-mineshaft-700 hover:bg-primary",
props.color == "mineshaft" && !activityStatus && "bg-mineshaft",
(props.color == "primary" || !props.color) && activityStatus && "bg-primary hover:opacity-80",
(props.color == "primary" || !props.color) && !activityStatus && "bg-primary",
(props.color == "primary" || !props.color) &&
activityStatus &&
"bg-primary hover:opacity-80",
(props.color == "primary" || !props.color) &&
!activityStatus &&
"bg-primary",
props.color == "red" && "bg-red",
// Changing the opacity when active vs when not
@@ -43,7 +67,7 @@ export default function Button (props: ButtonProps): JSX.Element {
props.size == "icon-sm" && "h-9 w-9 flex items-center justify-center"
);
let styleMainDiv = classNames(
const styleMainDiv = classNames(
"relative font-medium flex items-center",
// Setting the text color for the text and icon
@@ -55,11 +79,11 @@ export default function Button (props: ButtonProps): JSX.Element {
props.size == "icon" && "flex items-center justify-center"
);
let textStyle = classNames(
const textStyle = classNames(
"relative duration-200",
// Show the loading sign if the loading indicator is on
props.loading == true ? "opacity-0" : "opacity-100",
props.loading ? "opacity-0" : "opacity-100",
props.size == "md" && "text-sm",
props.size == "lg" && "text-lg"
);
@@ -86,7 +110,7 @@ export default function Button (props: ButtonProps): JSX.Element {
</div>
{props.icon && (
<FontAwesomeIcon
icon={props.icon as IconProp}
icon={props.icon}
className={`flex my-auto font-extrabold ${
props.size == "icon-sm" ? "text-sm" : "text-md"
} ${(props.text || props.textDisabled) && "mr-2"}`}
@@ -101,7 +125,9 @@ export default function Button (props: ButtonProps): JSX.Element {
/>
)}
{(props.text || props.textDisabled) && (
<p className={textStyle}>{activityStatus ? props.text : props.textDisabled}</p>
<p className={textStyle}>
{activityStatus ? props.text : props.textDisabled}
</p>
)}
</div>
</button>
@@ -9,6 +9,7 @@ import nacl from "tweetnacl";
import addServiceToken from "~/pages/api/serviceToken/addServiceToken";
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
import { envMapping } from "../../../public/data/frequentConstants";
import {
decryptAssymmetric,
encryptAssymmetric,
@@ -17,13 +18,6 @@ import Button from "../buttons/Button";
import InputField from "../InputField";
import ListBox from "../Listbox";
const envMapping = {
Development: "dev",
Staging: "staging",
Production: "prod",
Testing: "test",
};
const expiryMapping = {
"1 day": 86400,
"7 days": 604800,
@@ -45,7 +39,7 @@ const AddServiceTokenDialog = ({
const { t } = useTranslate();
const generateServiceToken = async () => {
const latestFileKey = await getLatestFileKey(workspaceId);
const latestFileKey = await getLatestFileKey({ workspaceId });
const key = decryptAssymmetric({
ciphertext: latestFileKey.latestKey.encryptedKey,
+98 -63
View File
@@ -4,10 +4,11 @@ import Link from "next/link";
import { useRouter } from "next/router";
import useTranslation from "next-translate/useTranslation";
import {
faBookOpen,
faGear,
faHouse,
faLink,
faKey,
faMobile,
faPlug,
faUser,
} from "@fortawesome/free-solid-svg-icons";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
@@ -19,6 +20,7 @@ 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 uploadKeys from "~/pages/api/workspace/uploadKeys";
import NavBarDashboard from "../navigation/NavBarDashboard";
import {
@@ -57,10 +59,10 @@ export default function Layout({ children }) {
const workspaces = await getWorkspaces();
const currentWorkspaces = workspaces.map((workspace) => workspace.name);
if (!currentWorkspaces.includes(workspaceName)) {
const newWorkspace = await createWorkspace(
const newWorkspace = await createWorkspace({
workspaceName,
localStorage.getItem("orgData.id")
);
organizationId: localStorage.getItem("orgData.id"),
});
let newWorkspaceId;
try {
newWorkspaceId = newWorkspace._id;
@@ -117,7 +119,7 @@ export default function Layout({ children }) {
href:
"/dashboard/" + workspaceMapping[workspaceSelected] + "?Development",
title: t("nav:menu.secrets"),
emoji: <FontAwesomeIcon icon={faHouse} />,
emoji: <FontAwesomeIcon icon={faKey} />,
},
{
href: "/users/" + workspaceMapping[workspaceSelected],
@@ -127,7 +129,7 @@ export default function Layout({ children }) {
{
href: "/integrations/" + workspaceMapping[workspaceSelected],
title: t("nav:menu.integrations"),
emoji: <FontAwesomeIcon icon={faLink} />,
emoji: <FontAwesomeIcon icon={faPlug} />,
},
{
href: "/settings/project/" + workspaceMapping[workspaceSelected],
@@ -160,7 +162,6 @@ export default function Layout({ children }) {
const intendedWorkspaceId = router.asPath
.split("/")
[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" &&
@@ -217,66 +218,100 @@ export default function Layout({ children }) {
<NavBarDashboard />
<div className="flex flex-col md:flex-row flex-1">
<aside className="bg-bunker-600 border-r border-mineshaft-500 w-full md:w-60 h-screen">
<nav>
<div className="py-6"></div>
<div className="flex justify-center w-full mt-7 mb-8 bg-bunker-600 w-full h-full flex flex-col items-center px-4">
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
{t("nav:menu.project")}
<nav className="flex flex-col justify-between items-between h-full">
{/* <div className="py-6"></div> */}
<div>
<div className="flex justify-center w-full mt-[4.5rem] mb-6 bg-bunker-600 w-full h-20 flex flex-col items-center px-4">
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
{t("nav:menu.project")}
</div>
{workspaceList.length > 0 ? (
<Listbox
selected={workspaceSelected}
onChange={setWorkspaceSelected}
data={workspaceList}
buttonAction={openModal}
text=""
workspaceMapping={workspaceMapping}
/>
) : (
<Button
text="Add Project"
onButtonPressed={openModal}
color="mineshaft"
size="md"
icon={faPlus}
/>
)}
</div>
{workspaceList.length > 0 ? (
<Listbox
selected={workspaceSelected}
onChange={setWorkspaceSelected}
data={workspaceList}
buttonAction={openModal}
text=""
workspaceMapping={workspaceMapping}
/>
) : (
<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}>
<ul>
{workspaceList.length > 0 &&
menuItems.map(({ href, title, emoji }) => (
<li className="mt-0.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 relative px-0.5 py-2.5 text-white text-sm rounded cursor-pointer bg-primary-50/10`}
>
<p className="ml-2 mr-4">{emoji}</p>
<div className="absolute top-0 my-1 ml-1 inset-0 bg-primary w-1 rounded-xl mr-1"></div>
<p className="w-6 ml-4 mr-2 flex items-center justify-center text-lg">
{emoji}
</p>
{title}
</div>
</Link>
)}
</li>
))}
</ul>
) : router.asPath == "/noprojects" ? (
<div
className={`flex p-2.5 text-white text-sm rounded`}
>
<p className="w-10 flex items-center justify-center text-lg">
{emoji}
</p>
{title}
</div>
) : (
<Link href={href}>
<div
className={`flex p-2.5 text-white text-sm rounded cursor-pointer hover:bg-primary-50/5`}
>
<p className="w-10 flex items-center justify-center text-lg">
{emoji}
</p>
{title}
</div>
</Link>
)}
</li>
))}
</ul>
</div>
<div className="w-full mt-40 mb-4 px-2">
{router.asPath.split("/")[1] === "home" ? (
<div
className={`flex relative px-0.5 py-2.5 text-white text-sm rounded cursor-pointer bg-primary-50/10`}
>
<div className="absolute top-0 my-1 ml-1 inset-0 bg-primary w-1 rounded-xl mr-1"></div>
<p className="w-6 ml-4 mr-2 flex items-center justify-center text-lg">
<FontAwesomeIcon icon={faBookOpen} />
</p>
Infisical Guide
</div>
) : (
<Link href={`/home/` + workspaceMapping[workspaceSelected]}>
<div
className={`flex p-2.5 text-white text-sm rounded cursor-pointer hover:bg-primary-50/5 mt-max border border-dashed border-bunker-400`}
>
<p className="w-10 flex items-center justify-center text-lg">
<FontAwesomeIcon icon={faBookOpen} />
</p>
Infisical Guide
</div>
</Link>
)}
</div>
</nav>
</aside>
<AddWorkspaceDialog
@@ -2,6 +2,19 @@ import React from "react";
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
/**
* This is the notification that pops up at the bottom right when a user performs a certain action
* @param {object} org
* @param {string} org.buttonText - text of the button inside the notification
* @param {string} org.buttonLink - where the button leads to
* @param {string} org.titleText - the text at the top of a notification
* @param {string} org.emoji - the emoji in the notification title
* @param {string} org.textLine1 - first line of the text in the notification
* @param {string} org.textLine2 - second line of the text in the notification
* @param {string} org.setCheckDocsPopUpVisible - the functions that closes the popup
* @returns
*/
export default function BottonRightPopup({
buttonText,
buttonLink,
@@ -2,18 +2,10 @@ import React, { useEffect, useState } from "react";
import { useRouter } from "next/router";
import { faX } from "@fortawesome/free-solid-svg-icons";
import { reverseEnvMapping } from "../../../public/data/frequentConstants";
import guidGenerator from "../../utilities/randomId";
import Button from "../buttons/Button";
const roles = ["admin", "user"];
const reverseEnvMapping = {
dev: "Development",
staging: "Staging",
prod: "Production",
test: "Testing",
};
/**
* This is the component that we utilize for the user table - in future, can reuse it for some other purposes too.
* #TODO: add the possibility of choosing and doing operations on multiple users.
+1 -1
View File
@@ -88,7 +88,7 @@ const UserTable = ({
}, [userData, myUser]);
const grantAccess = async (id, publicKey) => {
let result = await getLatestFileKey(router.query.id);
let result = await getLatestFileKey({workspaceId: router.query.id});
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
+1 -1
View File
@@ -61,7 +61,7 @@ export default function Plan({ plan }) {
</button>
</div>
)}
<a href="/pricing" target='_blank rel="noopener"'>
<a href="https://infisical.com/pricing" target='_blank rel="noopener"'>
<div className="relative z-10 text-gray-400 font-semibold hover:text-primary duration-200 cursor-pointer mb-0.5">
{plan.buttonTextSecondary}
</div>
@@ -1,131 +0,0 @@
import React, { Fragment } from "react";
import { faCircle } from "@fortawesome/free-solid-svg-icons";
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
*/
const findReferences = (text) => {
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 += "}";
}
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 {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 {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
*/
const DashboardInputField = ({
index,
onChangeHandler,
type,
value,
blurred,
}) => {
if (type === "varName") {
return (
<div className="flex-col w-full">
<div
className={`group relative flex flex-col justify-center w-full max-w-2xl border border-mineshaft-500 rounded-md`}
>
<input
onChange={(e) => onChangeHandler(e.target.value, index)}
type={type}
value={value}
className="asolute z-10 peer font-mono ph-no-capture bg-bunker-800 rounded-md caret-white text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-200"
spellCheck="false"
/>
</div>
</div>
);
} else if (type === "value") {
return (
<div className="flex-col w-full">
<div
className={`group relative flex flex-col justify-center w-full max-w-2xl border border-mineshaft-500 rounded-md`}
>
<input
onChange={(e) => onChangeHandler(e.target.value, index)}
type={type}
value={value}
className={`${
blurred
? "text-transparent group-hover:text-transparent focus:text-transparent active:text-transparent"
: ""
} asolute z-10 peer font-mono ph-no-capture bg-transparent rounded-md caret-white text-transparent text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-200`}
spellCheck="false"
/>
<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 h-9 rounded-md text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-100`}
>
{findReferences(value).map((texts, id) => {
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>
);
})}
</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)
.map(() => (
<FontAwesomeIcon
key={guidGenerator()}
className="text-xxs mx-0.5"
icon={faCircle}
/>
))}
</div>
)}
</div>
</div>
);
}
};
export default React.memo(DashboardInputField);
@@ -0,0 +1,156 @@
import React, { SyntheticEvent, useRef } from "react";
import { faCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import guidGenerator from "../utilities/randomId";
const REGEX = /([$]{.*?})/g;
interface DashboardInputFieldProps {
index: number;
onChangeHandler: (value: string, index: number) => void;
value: string;
type: "varName" | "value";
blurred: boolean;
duplicates: string[];
}
/**
* 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 {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 {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
* @param {string[]} obj.duplicates - list of all the duplicated key names on the dashboard
* @returns
*/
const DashboardInputField = ({
index,
onChangeHandler,
type,
value,
blurred,
duplicates,
}: DashboardInputFieldProps) => {
const ref = useRef<HTMLDivElement | null>(null);
const syncScroll = (e: SyntheticEvent<HTMLDivElement>) => {
if (ref.current == null) return;
ref.current.scrollTop = e.currentTarget.scrollTop;
ref.current.scrollLeft = e.currentTarget.scrollLeft;
};
if (type === "varName") {
const startsWithNumber = !isNaN(Number(value.charAt(0))) && value != "";
const hasDuplicates = duplicates?.includes(value);
const error = startsWithNumber || hasDuplicates;
return (
<div className="flex-col w-full">
<div
className={`group relative flex flex-col justify-center w-full max-w-2xl border ${
error ? "border-red" : "border-mineshaft-500"
} rounded-md`}
>
<input
onChange={(e) =>
onChangeHandler(e.target.value.toUpperCase(), index)
}
type={type}
value={value}
className={`z-10 peer font-mono ph-no-capture bg-bunker-800 rounded-md caret-white text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-2 ${
error ? "focus:ring-red/50" : "focus:ring-primary/50"
} duration-200`}
spellCheck="false"
/>
</div>
{startsWithNumber && (
<p className="text-red text-xs mt-0.5 mx-1 mb-2 max-w-xs">
Should not start with a number
</p>
)}
{hasDuplicates && !startsWithNumber && (
<p className="text-red text-xs mt-0.5 mx-1 mb-2 max-w-xs">
Secret names should be unique
</p>
)}
</div>
);
} else if (type === "value") {
return (
<div className="flex-col w-full">
<div
className={`group relative whitespace-pre flex flex-col justify-center w-full max-w-2xl border border-mineshaft-500 rounded-md`}
>
<input
value={value}
onChange={(e) => onChangeHandler(e.target.value, index)}
onScroll={syncScroll}
className={`${
blurred
? "text-transparent group-hover:text-transparent focus:text-transparent active:text-transparent"
: ""
} 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-2 focus:ring-primary/50 duration-200 no-scrollbar no-scrollbar::-webkit-scrollbar`}
spellCheck="false"
/>
<div
ref={ref}
className={`${
blurred
? "text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-400 peer-active:text-gray-400"
: ""
} absolute flex flex-row whitespace-pre font-mono z-0 ph-no-capture max-w-2xl overflow-x-scroll bg-bunker-800 h-9 rounded-md text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-2 focus:ring-primary/50 duration-100 no-scrollbar no-scrollbar::-webkit-scrollbar`}
>
{value.split(REGEX).map((word, id) => {
if (word.match(REGEX) !== null) {
return (
<span className="ph-no-capture text-yellow" key={id}>
{word.slice(0, 2)}
<span className="ph-no-capture text-yellow-200/80">
{word.slice(2, word.length - 1)}
</span>
{word.slice(word.length - 1, word.length) == "}" ? (
<span className="ph-no-capture text-yellow">
{word.slice(word.length - 1, word.length)}
</span>
) : (
<span className="ph-no-capture text-yellow-400">
{word.slice(word.length - 1, word.length)}
</span>
)}
</span>
);
} else {
return (
<span key={id} className="ph-no-capture">
{word}
</span>
);
}
})}
</div>
{blurred && (
<div className="absolute flex flex-row items-center z-20 peer pr-2 bg-bunker-800 group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible h-9 w-full max-w-2xl rounded-md text-gray-400/50 text-clip">
<div className="px-2 flex flex-row items-center overflow-x-scroll no-scrollbar no-scrollbar::-webkit-scrollbar">
{value.split("").map(() => (
<FontAwesomeIcon
key={guidGenerator()}
className="text-xxs mx-0.5"
icon={faCircle}
/>
))}
</div>
</div>
)}
</div>
</div>
);
}
return <>Something Wrong</>;
};
export default React.memo(DashboardInputField);
@@ -1,4 +1,4 @@
import { useState } from "react";
import { type ChangeEvent, type DragEvent, useState } from "react";
import Image from "next/image";
import useTranslation from "next-translate/useTranslation";
import { faUpload } from "@fortawesome/free-solid-svg-icons";
@@ -9,29 +9,39 @@ import Error from "../basic/Error";
import parse from "../utilities/file";
import guidGenerator from "../utilities/randomId";
interface DropZoneProps {
// TODO: change Data type from any
setData: (data: any) => void;
setErrorDragAndDrop: (hasError: boolean) => void;
createNewFile: () => void;
errorDragAndDrop: boolean;
setButtonReady: (isReady: boolean) => void;
keysExist: boolean;
numCurrentRows: number;
}
const DropZone = ({
setData,
setErrorDragAndDrop,
createNewFile,
errorDragAndDrop,
addPresetRow,
setButtonReady,
keysExist,
numCurrentRows,
}) => {
}: DropZoneProps) => {
const { t } = useTranslation();
const handleDragEnter = (e) => {
const handleDragEnter = (e: DragEvent) => {
e.preventDefault();
e.stopPropagation();
};
const handleDragLeave = (e) => {
const handleDragLeave = (e: DragEvent) => {
e.preventDefault();
e.stopPropagation();
};
const handleDragOver = (e) => {
const handleDragOver = (e: DragEvent) => {
e.preventDefault();
e.stopPropagation();
@@ -42,22 +52,25 @@ const DropZone = ({
const [loading, setLoading] = useState(false);
// This function function immediately parses the file after it is dropped
const handleDrop = async (e) => {
const handleDrop = async (e: DragEvent) => {
setLoading(true);
setTimeout(() => setLoading(false), 5000);
e.preventDefault();
e.stopPropagation();
e.dataTransfer.dropEffect = "copy";
var file = e.dataTransfer.files[0],
reader = new FileReader();
reader.onload = function (event) {
const keyPairs = parse(event.target.result);
const file = e.dataTransfer.files[0];
const reader = new FileReader();
reader.onload = (event) => {
if (event.target === null || event.target.result === null) return;
// parse function's argument looks like to be ArrayBuffer
const keyPairs = parse(event.target.result as Buffer);
const newData = Object.keys(keyPairs).map((key, index) => [
guidGenerator(),
numCurrentRows + index,
key,
keyPairs[key],
keyPairs[key as keyof typeof keyPairs],
"shared",
]);
setData(newData);
@@ -75,23 +88,28 @@ const DropZone = ({
};
// This function is used when the user manually selects a file from the in-browser dircetory (not drag and drop)
const handleFileSelect = (e) => {
const handleFileSelect = (e: ChangeEvent<HTMLInputElement>) => {
setLoading(true);
setTimeout(() => setLoading(false), 5000);
var file = e.target.files[0],
reader = new FileReader();
reader.onload = function (event) {
const newData = event.target.result
.split("\n")
.map((line, index) => [
guidGenerator(),
numCurrentRows + index,
line.split("=")[0],
line.split("=").slice(1, line.split("=").length).join("="),
"shared",
]);
setData(newData);
setButtonReady(true);
if (e.currentTarget.files === null) return;
const file = e.currentTarget.files[0];
const reader = new FileReader();
reader.onload = (event) => {
if (event.target === null || event.target.result === null) return;
const { result } = event.target;
if (typeof result === "string") {
const newData = result
.split("\n")
.map((line: string, index: number) => [
guidGenerator(),
numCurrentRows + index,
line.split("=")[0],
line.split("=").slice(1, line.split("=").length).join("="),
"shared",
]);
setData(newData);
setButtonReady(true);
}
};
reader.readAsText(file);
};
@@ -108,17 +126,17 @@ const DropZone = ({
) : keysExist ? (
<div
className="opacity-60 hover:opacity-100 duration-200 relative bg-bunker outline max-w-[calc(100%-1rem)] w-full outline-dashed outline-gray-600 rounded-md outline-2 flex flex-col items-center justify-center mb-16 mx-auto mt-1 py-8 px-2"
onDragEnter={(e) => handleDragEnter(e)}
onDragOver={(e) => handleDragOver(e)}
onDragLeave={(e) => handleDragLeave(e)}
onDrop={(e) => handleDrop(e)}
onDragEnter={handleDragEnter}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
<input
id="fileSelect"
type="file"
className="opacity-0 absolute w-full h-full"
accept=".txt,.env"
onChange={(e) => handleFileSelect(e)}
onChange={handleFileSelect}
/>
{errorDragAndDrop ? (
<div className="my-3 max-w-xl opacity-80"></div>
@@ -143,10 +161,10 @@ const DropZone = ({
) : (
<div
className="opacity-80 hover:opacity-100 duration-200 relative bg-bunker outline max-w-2xl w-full outline-dashed outline-gray-700 rounded-md outline-2 flex flex-col items-center justify-center pt-16 mb-16 px-4"
onDragEnter={(e) => handleDragEnter(e)}
onDragOver={(e) => handleDragOver(e)}
onDragLeave={(e) => handleDragLeave(e)}
onDrop={(e) => handleDrop(e)}
onDragEnter={handleDragEnter}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
<FontAwesomeIcon icon={faUpload} className="text-7xl mb-8" />
<p className="">{t("common:drop-zone")}</p>
@@ -155,7 +173,7 @@ const DropZone = ({
type="file"
className="opacity-0 absolute w-full h-full"
accept=".txt,.env"
onChange={(e) => handleFileSelect(e)}
onChange={handleFileSelect}
/>
<div className="flex flex-row w-full items-center justify-center mb-6 mt-5">
<div className="border-t border-gray-700 w-1/5"></div>
@@ -21,6 +21,7 @@ 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";
import logout from "~/pages/api/auth/Logout";
/**
* @param {(key: string) => string} t
*/
@@ -47,31 +48,48 @@ const supportOptions = (t) => [
],
];
export default function Navbar({ onButtonPressed }) {
export interface ICurrentOrg {
name: string;
}
export interface IUser {
firstName: string;
lastName: string;
email: string;
}
/**
* This is the navigation bar in the main app.
* It has two main components: support options and user menu (inlcudes billing, logout, org/user settings)
* @returns NavBar
*/
export default function Navbar() {
const router = useRouter();
const [user, setUser] = useState({});
const [user, setUser] = useState<IUser | undefined>();
const [orgs, setOrgs] = useState([]);
const [currentOrg, setCurrentOrg] = useState([]);
const [currentOrg, setCurrentOrg] = useState<ICurrentOrg | undefined>();
const { t } = useTranslation("");
const supportOptionsList = useMemo(() => supportOptions(t), [t]);
useEffect(async () => {
const userData = await getUser();
setUser(userData);
const orgsData = await getOrganizations();
setOrgs(orgsData);
const currentOrg = await getOrganization({
orgId: localStorage.getItem("orgData.id"),
});
setCurrentOrg(currentOrg);
useEffect(() => {
(async () => {
const userData = await getUser();
setUser(userData);
const orgsData = await getOrganizations();
setOrgs(orgsData);
const currentOrg = await getOrganization({
orgId: String(localStorage.getItem("orgData.id")),
});
setCurrentOrg(currentOrg);
})();
}, []);
const closeApp = async () => {
console.log("Logging out...");
await logout();
router.push("/");
router.push("/login");
};
return (
@@ -110,8 +128,8 @@ export default function Navbar({ onButtonPressed }) {
<a
key={guidGenerator()}
target="_blank"
rel="noopener noreferrer"
href={url}
rel="noopener"
href={String(url)}
className="font-normal text-gray-300 duration-200 rounded-md w-full flex items-center py-0.5"
>
<div className="relative flex justify-start items-center cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/10 duration-200 hover:text-gray-200 w-full">
@@ -241,9 +259,10 @@ export default function Navbar({ onButtonPressed }) {
<div className="flex flex-col items-start px-1 mt-3 mb-2">
{orgs
.filter(
(org) => org._id != localStorage.getItem("orgData.id")
(org: { _id: string }) =>
org._id != localStorage.getItem("orgData.id")
)
.map((org) => (
.map((org: { _id: string; name: string }) => (
<div
key={guidGenerator()}
onClick={() => {
@@ -1,29 +1,37 @@
import React, { useEffect, useState } from "react";
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";
import getOrganization from "~/pages/api/organization/GetOrg";
import getWorkspaceInfo from "~/pages/api/workspace/getWorkspaceInfo";
import getProjectInfo from "~/pages/api/workspace/getProjectInfo";
export default function NavHeader({ pageName, isProjectRelated }) {
/**
* This is the component at the top of almost every page.
* It shows how to navigate to a certain page.
* It future these links should also be clickable and hoverable
* @param obj
* @param obj.pageName - Name of the page
* @param obj.isProjectRelated - whether this page is related to project or now (determine if it's 2 or 3 navigation steps)
* @returns
*/
export default function NavHeader({ pageName, isProjectRelated } : { pageName: string; isProjectRelated: boolean; }): JSX.Element {
const [orgName, setOrgName] = useState("");
const [workspaceName, setWorkspaceName] = useState("");
const router = useRouter();
useEffect(() => {
(async () => {
let org = await getOrganization({
orgId: localStorage.getItem("orgData.id"),
const orgId = localStorage.getItem("orgData.id")
const org = await getOrganization({
orgId: orgId ? orgId : "",
});
setOrgName(org.name);
let workspace = await getWorkspaceInfo({
workspaceId: router.query.id,
const workspace = await getProjectInfo({
projectId: String(router.query.id),
});
setWorkspaceName(workspace.name);
})();
@@ -1,8 +1,6 @@
import { PATH } from "~/const";
import token from "~/pages/api/auth/Token";
export default class SecurityClient {
static authOrigins = [PATH];
static #token = "";
contructor() {}
@@ -13,13 +11,12 @@ export default class SecurityClient {
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.authOrigins.includes(destOrigin)) {
if (this.#token) {
req.headers.set("Authorization", "Bearer " + this.#token);
return fetch(req);
}
+15 -7
View File
@@ -43,7 +43,7 @@ const attemptLogin = async (
let serverPublicKey, salt;
try {
const res = await login1(email, clientPublicKey);
let res = await login1(email, clientPublicKey);
res = await res.json();
serverPublicKey = res.serverPublicKey;
salt = res.salt;
@@ -134,10 +134,18 @@ const attemptLogin = async (
// If user is logging in for the first time, add the example keys
if (isSignUp) {
await pushKeys(
{
await pushKeys({
obj: {
DATABASE_URL: [
"mongodb+srv://this_is:an_example@mongodb.net",
"mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net",
"personal",
],
DB_USERNAME: [
"user1234",
"personal",
],
DB_PASSWORD: [
"ah8jak3hk8dhiu4dw7whxwe1l",
"personal",
],
TWILIO_AUTH_TOKEN: [
@@ -147,9 +155,9 @@ const attemptLogin = async (
WEBSITE_URL: ["http://localhost:3000", "shared"],
STRIPE_SECRET_KEY: ["sk_test_7348oyho4hfq398HIUOH78", "shared"],
},
projectToLogin,
"Development"
);
workspaceId: projectToLogin,
env: "Development"
});
}
try {
if (email) {
@@ -108,11 +108,7 @@ const changePassword = async (
}
);
} catch (error) {
console.log(
"Something went wrong during changing the password",
slat,
serverPublicKey
);
console.log("Something went wrong during changing the password");
}
return true;
};
@@ -2,6 +2,12 @@ const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
const aes = require("./aes-256-gcm");
type encryptAsymmetricProps = {
plaintext: string;
publicKey: string;
privateKey: string;
}
/**
* Return assymmetrically encrypted [plaintext] using [publicKey] where
* [publicKey] likely belongs to the recipient.
@@ -13,7 +19,7 @@ const aes = require("./aes-256-gcm");
* @returns {String} ciphertext - base64-encoded ciphertext
* @returns {String} nonce - base64-encoded nonce
*/
const encryptAssymmetric = ({ plaintext, publicKey, privateKey }) => {
const encryptAssymmetric = ({ plaintext, publicKey, privateKey }: encryptAsymmetricProps): object => {
const nonce = nacl.randomBytes(24);
const ciphertext = nacl.box(
nacl.util.decodeUTF8(plaintext),
@@ -28,6 +34,13 @@ const encryptAssymmetric = ({ plaintext, publicKey, privateKey }) => {
};
};
type decryptAsymmetricProps = {
ciphertext: string;
nonce: string;
publicKey: string;
privateKey: string;
}
/**
* Return assymmetrically decrypted [ciphertext] using [privateKey] where
* [privateKey] likely belongs to the recipient.
@@ -38,7 +51,7 @@ const encryptAssymmetric = ({ plaintext, publicKey, privateKey }) => {
* @param {String} obj.privateKey - base64-encoded private key of the receiver (current user)
* @param {String} plaintext - UTF8 plaintext
*/
const decryptAssymmetric = ({ ciphertext, nonce, publicKey, privateKey }) => {
const decryptAssymmetric = ({ ciphertext, nonce, publicKey, privateKey }: decryptAsymmetricProps): string => {
const plaintext = nacl.box.open(
nacl.util.decodeBase64(ciphertext),
nacl.util.decodeBase64(nonce),
@@ -49,13 +62,18 @@ const decryptAssymmetric = ({ ciphertext, nonce, publicKey, privateKey }) => {
return nacl.util.encodeUTF8(plaintext);
};
type encryptSymmetricProps = {
plaintext: string;
key: string;
}
/**
* Return symmetrically encrypted [plaintext] using [key].
* @param {Object} obj
* @param {String} obj.plaintext - plaintext to encrypt
* @param {String} obj.key - 16-byte hex key
*/
const encryptSymmetric = ({ plaintext, key }) => {
const encryptSymmetric = ({ plaintext, key }: encryptSymmetricProps): object => {
let ciphertext, iv, tag;
try {
const obj = aes.encrypt(plaintext, key);
@@ -75,6 +93,13 @@ const encryptSymmetric = ({ plaintext, key }) => {
};
};
type decryptSymmetricProps = {
ciphertext: string;
iv: string;
tag: string;
key: string;
}
/**
* Return symmetrically decypted [ciphertext] using [iv], [tag],
* and [key].
@@ -85,7 +110,7 @@ const encryptSymmetric = ({ plaintext, key }) => {
* @param {String} obj.key - 32-byte hex key
*
*/
const decryptSymmetric = ({ ciphertext, iv, tag, key }) => {
const decryptSymmetric = ({ ciphertext, iv, tag, key }: decryptSymmetricProps): string => {
let plaintext;
try {
plaintext = aes.decrypt(ciphertext, iv, tag, key);
@@ -97,9 +122,9 @@ const decryptSymmetric = ({ ciphertext, iv, tag, key }) => {
return plaintext;
};
module.exports = {
encryptAssymmetric,
export {
decryptAssymmetric,
encryptSymmetric,
decryptSymmetric,
encryptAssymmetric,
encryptSymmetric,
};
@@ -1,5 +1,6 @@
import getSecrets from "~/pages/api/files/GetSecrets";
import { envMapping } from "../../../public/data/frequentConstants";
import guidGenerator from "../randomId";
const {
@@ -9,13 +10,6 @@ const {
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
const envMapping = {
Development: "dev",
Staging: "staging",
Production: "prod",
Testing: "test",
};
const getSecretsForProject = async ({
env,
setFileState,
@@ -2,29 +2,35 @@ import uploadSecrets from "~/pages/api/files/UploadSecrets";
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
import getWorkspaceKeys from "~/pages/api/workspace/getWorkspaceKeys";
import { envMapping } from "../../../public/data/frequentConstants";
const crypto = require("crypto");
const {
decryptAssymmetric,
decryptSymmetric,
encryptSymmetric,
encryptAssymmetric,
} = require("../cryptography/crypto");
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
const envMapping = {
Development: "dev",
Staging: "staging",
Production: "prod",
Testing: "test",
};
export interface IK {
publicKey: string;
userId: string;
}
const pushKeys = async (obj, workspaceId, env) => {
let sharedKey = await getLatestFileKey(workspaceId);
/**
* This function pushes the keys to the database after decrypting them end-to-end
* @param {object} obj
* @param {object} obj.obj - object with all the key pairs
* @param {object} obj.workspaceId - the id of a project to which a user is pushing
* @param {object} obj.env - which environment a user is pushing to
*/
const pushKeys = async({ obj, workspaceId, env }: { obj: object; workspaceId: string; env: string; }) => {
const sharedKey = await getLatestFileKey({ workspaceId });
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
let randomBytes;
let randomBytes: string;
if (Object.keys(sharedKey).length > 0) {
// case: a (shared) key exists for the workspace
randomBytes = decryptAssymmetric({
@@ -55,11 +61,11 @@ const pushKeys = async (obj, workspaceId, env) => {
iv: ivValue,
tag: tagValue,
} = encryptSymmetric({
plaintext: obj[key][0],
plaintext: obj[key as keyof typeof obj][0],
key: randomBytes,
});
const visibility = obj[key][1] != null ? obj[key][1] : "personal";
const visibility = obj[key as keyof typeof obj][1] != null ? obj[key as keyof typeof obj][1] : "personal";
return {
ciphertextKey,
@@ -69,7 +75,7 @@ const pushKeys = async (obj, workspaceId, env) => {
ciphertextValue,
ivValue,
tagValue,
hashValue: crypto.createHash("sha256").update(obj[key][0]).digest("hex"),
hashValue: crypto.createHash("sha256").update(obj[key as keyof typeof obj][0]).digest("hex"),
type: visibility,
};
});
@@ -80,7 +86,7 @@ const pushKeys = async (obj, workspaceId, env) => {
});
// assymmetrically encrypt key with each receiver public keys
const keys = publicKeys.map((k) => {
const keys = publicKeys.map((k: IK) => {
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: randomBytes,
publicKey: k.publicKey,
@@ -99,7 +105,7 @@ const pushKeys = async (obj, workspaceId, env) => {
workspaceId,
secrets,
keys,
environment: envMapping[env],
environment: envMapping[env as keyof typeof envMapping],
});
};
-2
View File
@@ -1,5 +1,3 @@
export const PATH = process.env.NEXT_PUBLIC_WEBSITE_URL;
export const publicPaths = [
`/`,
// `/integrations`,
+1 -1
View File
@@ -209,7 +209,7 @@
"description": "Manage your integrations of Infisical with third-party services.",
"no-integrations1": "You don't have any integrations set up yet. When you do, they will appear here.",
"no-integrations2": "To start, click on any of the options below. It takes 5 clicks to set up.",
"available": "Available Integrations",
"available": "Platform & Cloud Integrations",
"available-text1": "Click on the itegration you want to connect. This will let your environment variables flow automatically into selected third-party services.",
"available-text2": " 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 [email protected]."
}
+4 -8
View File
@@ -2,16 +2,12 @@
const nextTranslate = require("next-translate");
const ContentSecurityPolicy = `
default-src ${process.env.NEXT_PUBLIC_WEBSITE_URL};
script-src ${
process.env.NEXT_PUBLIC_WEBSITE_URL
} https://app.posthog.com https://infisical.com https://assets.calendly.com/ https://js.stripe.com https://api.stripe.com 'unsafe-inline' 'unsafe-eval';
default-src 'self';
script-src 'self' https://app.posthog.com https://js.stripe.com https://api.stripe.com 'unsafe-inline' 'unsafe-eval';
style-src 'self' https://rsms.me 'unsafe-inline';
child-src https://infisical.com https://api.stripe.com;
child-src https://api.stripe.com;
frame-src https://js.stripe.com/ https://api.stripe.com;
connect-src ws://${process.env.NEXT_PUBLIC_WEBSITE_URL?.split("//")[1]} ${
process.env.NEXT_PUBLIC_WEBSITE_URL
} https://api.github.com/repos/Infisical/infisical-cli https://api.heroku.com/ https://id.heroku.com/oauth/authorize https://id.heroku.com/oauth/token https://checkout.stripe.com https://app.posthog.com https://infisical.com https://api.stripe.com https://vitals.vercel-insights.com/v1/vitals;
connect-src 'self' https://api.heroku.com/ https://id.heroku.com/oauth/authorize https://id.heroku.com/oauth/token https://checkout.stripe.com https://app.posthog.com https://api.stripe.com;
img-src 'self' https://*.stripe.com https://i.ytimg.com/ data:;
media-src;
font-src 'self' https://maxcdn.bootstrapcdn.com https://rsms.me https://fonts.gstatic.com;
+378 -409
View File
File diff suppressed because it is too large Load Diff
+4 -1
View File
@@ -54,9 +54,12 @@
"devDependencies": {
"@tailwindcss/typography": "^0.5.4",
"@types/node": "18.11.9",
"@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0",
"autoprefixer": "^10.4.7",
"eslint": "^8.28.0",
"eslint": "^8.29.0",
"eslint-config-next": "^13.0.5",
"eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-simple-import-sort": "^8.0.0",
"postcss": "^8.4.14",
"prettier": "2.7.1",
+1 -2
View File
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -14,7 +13,7 @@ const changePassword2 = ({
verifier,
clientProof,
}) => {
return SecurityClient.fetchCall(PATH + "/api/v1/password/change-password", {
return SecurityClient.fetchCall("/api/v1/password/change-password", {
method: "POST",
headers: {
"Content-Type": "application/json",
+1 -2
View File
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient.js";
/**
@@ -9,7 +8,7 @@ import SecurityClient from "~/utilities/SecurityClient.js";
* @returns
*/
const checkAuth = async (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/auth/checkAuth", {
return SecurityClient.fetchCall("/api/v1/auth/checkAuth", {
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -1,5 +1,3 @@
import { PATH } from "~/const";
/**
* This route check the verification code from the email that user just recieved
* @param {*} email
@@ -7,7 +5,7 @@ import { PATH } from "~/const";
* @returns
*/
const checkEmailVerificationCode = (email, code) => {
return fetch(PATH + "/api/v1/signup/email/verify", {
return fetch("/api/v1/signup/email/verify", {
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -1,5 +1,3 @@
import { PATH } from "~/const";
/**
* This function is called in the end of the signup process.
* It sends all the necessary nformation to the server.
@@ -28,7 +26,7 @@ const completeAccountInformationSignup = ({
verifier,
token,
}) => {
return fetch(PATH + "/api/v1/signup/complete-account/signup", {
return fetch("/api/v1/signup/complete-account/signup", {
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -1,5 +1,3 @@
import { PATH } from "~/const";
/**
* This function is called in the end of the signup process.
* It sends all the necessary nformation to the server.
@@ -26,7 +24,7 @@ const completeAccountInformationSignupInvite = ({
verifier,
token,
}) => {
return fetch(PATH + "/api/v1/signup/complete-account/invite", {
return fetch("/api/v1/signup/complete-account/invite", {
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -13,7 +12,7 @@ const issueBackupPrivateKey = ({
clientProof,
}) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/password/backup-private-key",
"/api/v1/password/backup-private-key",
{
method: "POST",
headers: {
@@ -32,8 +31,8 @@ const issueBackupPrivateKey = ({
if (res.status == 200) {
return res;
} else {
return res;
console.log("Failed to issue the backup key");
return res;
}
});
};
+1 -3
View File
@@ -1,5 +1,3 @@
import { PATH } from "~/const";
/**
* This is the first step of the login process (pake)
* @param {*} email
@@ -7,7 +5,7 @@ import { PATH } from "~/const";
* @returns
*/
const login1 = (email, clientPublicKey) => {
return fetch(PATH + "/api/v1/auth/login1", {
return fetch("/api/v1/auth/login1", {
method: "POST",
headers: {
"Content-Type": "application/json",
+1 -3
View File
@@ -1,5 +1,3 @@
import { PATH } from "~/const";
/**
* This is the second step of the login process
* @param {*} email
@@ -7,7 +5,7 @@ import { PATH } from "~/const";
* @returns
*/
const login2 = (email, clientProof) => {
return fetch(PATH + "/api/v1/auth/login2", {
return fetch("/api/v1/auth/login2", {
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -1,22 +1,18 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route logs the user out. Note: the user should authorized to do this.
* We first try to log out - if the authorization fails (response.status = 401), we refetch the new token, and then retry
* @param {*} req
* @param {*} res
* @returns
*/
const logout = async (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/auth/logout", {
const logout = async () => {
return SecurityClient.fetchCall("/api/v1/auth/logout", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
credentials: "include",
}).then((res) => {
if (res.status == 200) {
if (res?.status == 200) {
SecurityClient.setToken("");
// Delete the cookie by not setting a value; Alternatively clear the local storage
localStorage.setItem("publicKey", "");
+1 -2
View File
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -7,7 +6,7 @@ import SecurityClient from "~/utilities/SecurityClient";
* @returns
*/
const SRP1 = ({ clientPublicKey }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/password/srp1", {
return SecurityClient.fetchCall("/api/v1/password/srp1", {
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -1,11 +1,9 @@
import { PATH } from "~/const";
/**
* This route send the verification email to the user's email (contains a 6-digit verification code)
* @param {*} email
*/
const sendVerificationEmail = (email) => {
fetch(PATH + "/api/v1/signup/email/signup", {
fetch("/api/v1/signup/email/signup", {
method: "POST",
headers: {
"Content-Type": "application/json",
+1 -3
View File
@@ -1,7 +1,5 @@
import { PATH } from "~/const";
const token = async (req, res) => {
return fetch(PATH + "/api/v1/auth/token", {
return fetch("/api/v1/auth/token", {
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -1,5 +1,3 @@
import { PATH } from "~/const";
/**
* This route verifies the signup invite link
* @param {*} email
@@ -7,7 +5,7 @@ import { PATH } from "~/const";
* @returns
*/
const verifySignupInvite = ({ email, code }) => {
return fetch(PATH + "/api/v1/invite-org/verify", {
return fetch("/api/v1/invite-org/verify", {
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -1,5 +1,3 @@
import { PATH } from "~/const";
/**
* This route lets us get the public key of infisical. Th euser doesn't have to be authenticated since this is just the public key.
* @param {*} req
@@ -7,7 +5,7 @@ import { PATH } from "~/const";
* @returns
*/
const publicKeyInfisical = (req, res) => {
return fetch(PATH + "/api/v1/key/publicKey/infisical", {
return fetch("/api/v1/key/publicKey/infisical", {
method: "GET",
headers: {
"Content-Type": "application/json",
-2
View File
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient.js";
/**
@@ -9,7 +8,6 @@ import SecurityClient from "~/utilities/SecurityClient.js";
*/
const getSecrets = async (workspaceId, env) => {
return SecurityClient.fetchCall(
PATH +
"/api/v1/secret/" +
workspaceId +
"?" +
+1 -2
View File
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -8,7 +7,7 @@ import SecurityClient from "~/utilities/SecurityClient";
* @returns
*/
const uploadSecrets = async ({ workspaceId, secrets, keys, environment }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/secret/" + workspaceId, {
return SecurityClient.fetchCall("/api/v1/secret/" + workspaceId, {
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -1,9 +1,8 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
const changeHerokuConfigVars = ({ integrationId, key, secrets }) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/integration/" + integrationId + "/sync",
"/api/v1/integration/" + integrationId + "/sync",
{
method: "POST",
headers: {
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -8,7 +7,7 @@ import SecurityClient from "~/utilities/SecurityClient";
*/
const deleteIntegration = ({ integrationId }) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/integration/" + integrationId,
"/api/v1/integration/" + integrationId,
{
method: "DELETE",
headers: {
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -8,7 +7,7 @@ import SecurityClient from "~/utilities/SecurityClient";
*/
const deleteIntegrationAuth = ({ integrationAuthId }) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/integration-auth/" + integrationAuthId,
"/api/v1/integration-auth/" + integrationAuthId,
{
method: "DELETE",
headers: {
@@ -1,9 +1,8 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
const getIntegrationApps = ({ integrationAuthId }) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/integration-auth/" + integrationAuthId + "/apps",
"/api/v1/integration-auth/" + integrationAuthId + "/apps",
{
method: "GET",
headers: {
@@ -1,8 +1,7 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
const getIntegrations = () => {
return SecurityClient.fetchCall(PATH + "/api/v1/integration/integrations", {
return SecurityClient.fetchCall("/api/v1/integration/integrations", {
method: "GET",
headers: {
"Content-Type": "application/json",
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -8,7 +7,7 @@ import SecurityClient from "~/utilities/SecurityClient";
*/
const startIntegration = ({ integrationId, appName, environment }) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/integration/" + integrationId,
"/api/v1/integration/" + integrationId,
{
method: "PATCH",
headers: {
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -8,7 +7,7 @@ import SecurityClient from "~/utilities/SecurityClient";
*/
const AuthorizeIntegration = ({ workspaceId, code, integration }) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/integration-auth/oauth-token",
"/api/v1/integration-auth/oauth-token",
{
method: "POST",
headers: {
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -8,7 +7,7 @@ import SecurityClient from "~/utilities/SecurityClient";
*/
const getWorkspaceAuthorizations = ({ workspaceId }) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/workspace/" + workspaceId + "/authorizations",
"/api/v1/workspace/" + workspaceId + "/authorizations",
{
method: "GET",
headers: {
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -8,7 +7,7 @@ import SecurityClient from "~/utilities/SecurityClient";
*/
const getWorkspaceIntegrations = ({ workspaceId }) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/workspace/" + workspaceId + "/integrations",
"/api/v1/workspace/" + workspaceId + "/integrations",
{
method: "GET",
headers: {
@@ -1,20 +1,18 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get info about a certain org
* @param {*} req
* @param {*} res
* @param {string} orgId - the organization ID
* @returns
*/
const getOrganization = (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + req.orgId, {
const getOrganization = ({ orgId }: { orgId: string; }) => {
return SecurityClient.fetchCall("/api/v1/organization/" + orgId, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then(async (res) => {
if (res.status == 200) {
if (res?.status == 200) {
return (await res.json()).organization;
} else {
console.log("Failed to get org info");
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -9,7 +8,7 @@ import SecurityClient from "~/utilities/SecurityClient";
*/
const getOrganizationProjects = (req, res) => {
return SecurityClient.fetchCall(
PATH + "/api/organization/" + req.orgId + "/workspaces",
"/api/organization/" + req.orgId + "/workspaces",
{
method: "GET",
headers: {
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -9,7 +8,7 @@ import SecurityClient from "~/utilities/SecurityClient";
*/
const getOrganizationSubscriptions = (req, res) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + req.orgId + "/subscriptions",
"/api/v1/organization/" + req.orgId + "/subscriptions",
{
method: "GET",
headers: {
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -9,7 +8,7 @@ import SecurityClient from "~/utilities/SecurityClient";
*/
const getOrganizationUserProjects = (req, res) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + req.orgId + "/my-workspaces",
"/api/v1/organization/" + req.orgId + "/my-workspaces",
{
method: "GET",
headers: {
@@ -1,15 +1,14 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get all the users in an org.
* @param {*} req
* @param {*} res
* @param {object} obj
* @param {string} obj.orgId - organization Id
* @returns
*/
const getOrganizationUsers = (req, res) => {
const getOrganizationUsers = ({ orgId }: { orgId: string; }) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + req.orgId + "/users",
"/api/v1/organization/" + orgId + "/users",
{
method: "GET",
headers: {
@@ -17,7 +16,7 @@ const getOrganizationUsers = (req, res) => {
},
}
).then(async (res) => {
if (res.status == 200) {
if (res?.status == 200) {
return (await res.json()).users;
} else {
console.log("Failed to get org users");
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -9,7 +8,7 @@ import SecurityClient from "~/utilities/SecurityClient";
*/
const StripeRedirect = ({ orgId }) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + orgId + "/customer-portal-session",
"/api/v1/organization/" + orgId + "/customer-portal-session",
{
method: "POST",
headers: {
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -8,7 +7,7 @@ import SecurityClient from "~/utilities/SecurityClient";
*/
const addIncidentContact = (organizationId, email) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + organizationId + "/incidentContactOrg",
"/api/v1/organization/" + organizationId + "/incidentContactOrg",
{
method: "POST",
headers: {
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -8,7 +7,7 @@ import SecurityClient from "~/utilities/SecurityClient";
* @returns
*/
const addUserToOrg = (email, orgId) => {
return SecurityClient.fetchCall(PATH + "/api/v1/invite-org/signup", {
return SecurityClient.fetchCall("/api/v1/invite-org/signup", {
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -8,7 +7,7 @@ import SecurityClient from "~/utilities/SecurityClient";
*/
const deleteIncidentContact = (organizaionId, email) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + organizaionId + "/incidentContactOrg",
"/api/v1/organization/" + organizaionId + "/incidentContactOrg",
{
method: "DELETE",
headers: {
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -8,7 +7,7 @@ import SecurityClient from "~/utilities/SecurityClient";
*/
const deleteUserFromOrganization = (membershipId) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/membership-org/" + membershipId,
"/api/v1/membership-org/" + membershipId,
{
method: "DELETE",
headers: {
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -8,7 +7,7 @@ import SecurityClient from "~/utilities/SecurityClient";
*/
const getIncidentContacts = (organizationId) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + organizationId + "/incidentContactOrg",
"/api/v1/organization/" + organizationId + "/incidentContactOrg",
{
method: "GET",
headers: {
@@ -1,20 +1,17 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get the all the orgs of a certain user.
* @param {*} req
* @param {*} res
* @returns
*/
const getOrganizations = (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/organization", {
const getOrganizations = () => {
return SecurityClient.fetchCall("/api/v1/organization", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then(async (res) => {
if (res.status == 200) {
if (res?.status == 200) {
return (await res.json()).organizations;
} else {
console.log("Failed to get orgs of a user");
+1 -2
View File
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -9,7 +8,7 @@ import SecurityClient from "~/utilities/SecurityClient";
*/
const renameOrg = (orgId, newOrgName) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/organization/" + orgId + "/name",
"/api/v1/organization/" + orgId + "/name",
{
method: "PATCH",
headers: {
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -15,7 +14,7 @@ const addServiceToken = ({
encryptedKey,
nonce,
}) => {
return SecurityClient.fetchCall(PATH + "/api/v1/service-token/", {
return SecurityClient.fetchCall("/api/v1/service-token/", {
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -8,7 +7,7 @@ import SecurityClient from "~/utilities/SecurityClient";
*/
const getServiceTokens = ({ workspaceId }) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/workspace/" + workspaceId + "/service-tokens",
"/api/v1/workspace/" + workspaceId + "/service-tokens",
{
method: "GET",
headers: {
@@ -1,20 +1,16 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route gets the information about a specific user.
* @param {*} req
* @param {*} res
* @returns
*/
const getUser = (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/user", {
const getUser = () => {
return SecurityClient.fetchCall("/api/v1/user", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then(async (res) => {
if (res.status == 200) {
if (res?.status == 200) {
return (await res.json()).user;
} else {
console.log("Failed to get user info");
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -9,7 +8,6 @@ import SecurityClient from "~/utilities/SecurityClient";
*/
const checkUserAction = ({ action }) => {
return SecurityClient.fetchCall(
PATH +
"/api/v1/user-action" +
"?" +
new URLSearchParams({
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -7,7 +6,7 @@ import SecurityClient from "~/utilities/SecurityClient";
* @returns
*/
const registerUserAction = ({ action }) => {
return SecurityClient.fetchCall(PATH + "/api/v1/user-action", {
return SecurityClient.fetchCall("/api/v1/user-action", {
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -9,7 +8,7 @@ import SecurityClient from "~/utilities/SecurityClient";
*/
const addUserToWorkspace = (email, workspaceId) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/workspace/" + workspaceId + "/invite-signup",
"/api/v1/workspace/" + workspaceId + "/invite-signup",
{
method: "POST",
headers: {
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -9,7 +8,7 @@ import SecurityClient from "~/utilities/SecurityClient";
*/
const changeUserRoleInWorkspace = (membershipId, role) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/membership/" + membershipId + "/change-role",
"/api/v1/membership/" + membershipId + "/change-role",
{
method: "POST",
headers: {
@@ -1,13 +1,13 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route creates a new workspace for a user.
* @param {*} workspaceName
* This route creates a new workspace for a user within a certain organization.
* @param {string} workspaceName - project Name
* @param {string} organizationId - org ID
* @returns
*/
const createWorkspace = (workspaceName, organizationId) => {
return SecurityClient.fetchCall(PATH + "/api/v1/workspace", {
const createWorkspace = ( { workspaceName, organizationId }: { workspaceName: string; organizationId: string; }) => {
return SecurityClient.fetchCall("/api/v1/workspace", {
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -17,7 +17,7 @@ const createWorkspace = (workspaceName, organizationId) => {
organizationId: organizationId,
}),
}).then(async (res) => {
if (res.status == 200) {
if (res?.status == 200) {
return (await res.json()).workspace;
} else {
console.log("Failed to create a project");
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -7,7 +6,7 @@ import SecurityClient from "~/utilities/SecurityClient";
* @returns
*/
const deleteUserFromWorkspace = (membershipId) => {
return SecurityClient.fetchCall(PATH + "/api/v1/membership/" + membershipId, {
return SecurityClient.fetchCall("/api/v1/membership/" + membershipId, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -7,7 +6,7 @@ import SecurityClient from "~/utilities/SecurityClient";
* @returns
*/
const deleteWorkspace = (workspaceId) => {
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + workspaceId, {
return SecurityClient.fetchCall("/api/v1/workspace/" + workspaceId, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
@@ -1,14 +1,13 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* Get the latest key pairs from a certain workspace
* @param {*} workspaceId
* @param {string} workspaceId
* @returns
*/
const getLatestFileKey = (workspaceId) => {
const getLatestFileKey = ({ workspaceId } : { workspaceId: string; }) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/key/" + workspaceId + "/latest",
"/api/v1/key/" + workspaceId + "/latest",
{
method: "GET",
headers: {
@@ -16,7 +15,7 @@ const getLatestFileKey = (workspaceId) => {
},
}
).then(async (res) => {
if (res.status == 200) {
if (res?.status == 200) {
return await res.json();
} else {
console.log("Failed to get the latest key pairs for a certain project");
@@ -1,15 +1,13 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get the information of a certain project.
* @param {*} req
* @param {*} res
* @param {*} projectId - project ID (we renamed workspaces to projects in the app)
* @returns
*/
const getWorkspaceInfo = (req, res) => {
const getProjectInfo = ({ projectId }: { projectId: string; }) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/workspace/" + req.workspaceId,
"/api/v1/workspace/" + projectId,
{
method: "GET",
headers: {
@@ -17,7 +15,7 @@ const getWorkspaceInfo = (req, res) => {
},
}
).then(async (res) => {
if (res.status == 200) {
if (res?.status == 200) {
return (await res.json()).workspace;
} else {
console.log("Failed to get project info");
@@ -25,4 +23,4 @@ const getWorkspaceInfo = (req, res) => {
});
};
export default getWorkspaceInfo;
export default getProjectInfo;
@@ -1,15 +1,13 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get the public keys of everyone in your workspace.
* @param {*} req
* @param {*} res
* @param {string} workspaceId
* @returns
*/
const getWorkspaceKeys = (req, res) => {
const getWorkspaceKeys = ({ workspaceId }: { workspaceId: string; }) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/workspace/" + req.workspaceId + "/keys",
"/api/v1/workspace/" + workspaceId + "/keys",
{
method: "GET",
headers: {
@@ -17,7 +15,7 @@ const getWorkspaceKeys = (req, res) => {
},
}
).then(async (res) => {
if (res.status == 200) {
if (res?.status == 200) {
return (await res.json()).publicKeys;
} else {
console.log("Failed to get the public keys of everyone in the workspace");
@@ -1,15 +1,13 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get all the users in the workspace.
* @param {*} req
* @param {*} res
* @param {string} workspaceId - workspace ID
* @returns
*/
const getWorkspaceUsers = (req, res) => {
const getWorkspaceUsers = ({ workspaceId }: { workspaceId: string; }) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/workspace/" + req.workspaceId + "/users",
"/api/v1/workspace/" + workspaceId + "/users",
{
method: "GET",
headers: {
@@ -17,7 +15,7 @@ const getWorkspaceUsers = (req, res) => {
},
}
).then(async (res) => {
if (res.status == 200) {
if (res?.status == 200) {
return (await res.json()).users;
} else {
console.log("Failed to get Project Users");
@@ -1,20 +1,17 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get the public keys of everyone in your workspace.
* @param {*} req
* @param {*} res
* This route lets us get the workspaces of a certain user
* @returns
*/
const getWorkspaces = (req, res) => {
return SecurityClient.fetchCall(PATH + "/api/v1/workspace", {
const getWorkspaces = () => {
return SecurityClient.fetchCall("/api/v1/workspace", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then(async (res) => {
if (res.status == 200) {
if (res?.status == 200) {
return (await res.json()).workspaces;
} else {
console.log("Failed to get projects");
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -9,7 +8,7 @@ import SecurityClient from "~/utilities/SecurityClient";
*/
const renameWorkspace = (workspaceId, newWorkspaceName) => {
return SecurityClient.fetchCall(
PATH + "/api/v1/workspace/" + workspaceId + "/name",
"/api/v1/workspace/" + workspaceId + "/name",
{
method: "POST",
headers: {
+1 -2
View File
@@ -1,4 +1,3 @@
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
@@ -10,7 +9,7 @@ import SecurityClient from "~/utilities/SecurityClient";
* @returns
*/
const uploadKeys = (workspaceId, userId, encryptedKey, nonce) => {
return SecurityClient.fetchCall(PATH + "/api/v1/key/" + workspaceId, {
return SecurityClient.fetchCall("/api/v1/key/" + workspaceId, {
method: "POST",
headers: {
"Content-Type": "application/json",
+48 -39
View File
@@ -35,19 +35,13 @@ import pushKeys from "~/components/utilities/secrets/pushKeys";
import pushKeysIntegration from "~/components/utilities/secrets/pushKeysIntegration";
import guidGenerator from "~/utilities/randomId";
import { envMapping } from "../../public/data/frequentConstants";
import getWorkspaceIntegrations from "../api/integrations/getWorkspaceIntegrations";
import getUser from "../api/user/getUser";
import checkUserAction from "../api/userActions/checkUserAction";
import registerUserAction from "../api/userActions/registerUserAction";
import getWorkspaces from "../api/workspace/getWorkspaces";
const envMapping = {
Development: "dev",
Staging: "staging",
Production: "prod",
Testing: "test",
};
/**
* This component represent a single row for an environemnt variable on the dashboard
* @param {object} obj
@@ -57,6 +51,7 @@ const envMapping = {
* @param {function} obj.modifyValue - modify the value of a certain environment variable
* @param {function} obj.modifyVisibility - switch between public/private visibility
* @param {boolean} obj.isBlurred - if the blurring setting is turned on
* @param {string[]} obj.duplicates - list of all the duplicates secret names on the dashboard
* @returns
*/
const KeyPair = ({
@@ -66,20 +61,22 @@ const KeyPair = ({
modifyValue,
modifyVisibility,
isBlurred,
duplicates
}) => {
const [randomStringLength, setRandomStringLength] = useState(32);
const { t } = useTranslation();
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">
<div className="relative flex flex-row justify-between w-full max-w-5xl mr-auto max-h-14 my-1 items-start px-2">
<div className="min-w-xl w-96">
<div className="flex items-center md:px-1 rounded-lg mt-4 md:mt-0 max-h-10">
<div className="flex items-center md:px-1 rounded-lg mt-4 md:mt-0 max-h-16">
<DashboardInputField
onChangeHandler={modifyKey}
type="varName"
index={keyPair[1]}
value={keyPair[2]}
duplicates={duplicates}
/>
</div>
</div>
@@ -114,7 +111,7 @@ const KeyPair = ({
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute z-40 right-0 mt-0.5 w-[20rem] 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">
<Menu.Items className="absolute z-50 drop-shadow-xl right-0 mt-0.5 w-[20rem] origin-top-right rounded-md bg-bunker border border-mineshaft-500 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none px-1 py-1">
<div
onClick={() =>
modifyVisibility(
@@ -378,37 +375,47 @@ export default function Dashboard() {
...data.map((row) => ({ [row[2]]: [row[3], row[4]] }))
);
// Once "Save changed is clicked", disable that button
setButtonReady(false);
pushKeys(obj, router.query.id, env);
// Checking if any of the secret keys start with a number - if so, don't do anything
const nameErrors = !Object.keys(obj).map(key => !isNaN(key.charAt(0))).every(v => v === false);
const duplicatesExist = data?.map(item => item[2]).filter((item, index) => index !== data?.map(item => item[2]).indexOf(item)).length > 0;
/**
* Check which integrations are active for this project and environment
* If there are any, update environment variables for those integrations
*/
let integrations = await getWorkspaceIntegrations({
workspaceId: router.query.id,
});
integrations.map(async (integration) => {
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,
});
if (nameErrors) {
console.log("Solve all name errors first!");
} else if (duplicatesExist) {
console.log("Remove the duplicated entries first!");
} else {
// Once "Save changes is clicked", disable that button
setButtonReady(false);
pushKeys({obj, workspaceId: router.query.id, env});
/**
* Check which integrations are active for this project and environment
* If there are any, update environment variables for those integrations
*/
let integrations = await getWorkspaceIntegrations({
workspaceId: router.query.id,
});
integrations.map(async (integration) => {
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,
});
}
});
// If this user has never saved environment variables before, show them a prompt to read docs
if (!hasUserEverPushed) {
setCheckDocsPopUpVisible(true);
await registerUserAction({ action: "first_time_secrets_pushed" });
}
});
// If this user has never saved environment variables before, show them a prompt to read docs
if (!hasUserEverPushed) {
setCheckDocsPopUpVisible(true);
await registerUserAction({ action: "first_time_secrets_pushed" });
}
};
@@ -647,6 +654,7 @@ export default function Dashboard() {
modifyKey={listenChangeKey}
modifyVisibility={listenChangeVisibility}
isBlurred={blurred}
duplicates={data?.map(item => item[2]).filter((item, index) => index !== data?.map(item => item[2]).indexOf(item))}
/>
))}
</div>
@@ -694,6 +702,7 @@ export default function Dashboard() {
modifyKey={listenChangeKey}
modifyVisibility={listenChangeVisibility}
isBlurred={blurred}
duplicates={data?.map(item => item[2]).filter((item, index) => index !== data?.map(item => item[2]).indexOf(item))}
/>
))}
</div>
+142
View File
@@ -0,0 +1,142 @@
import React, { useEffect, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import { IconProp } from "@fortawesome/fontawesome-svg-core";
import { faSlack } from "@fortawesome/free-brands-svg-icons";
import { faCheckCircle, faHandPeace, faNetworkWired, faPlug, faPlus, faStar, faUserPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import getOrganizationUsers from "../api/organization/GetOrgUsers";
import checkUserAction from "../api/userActions/checkUserAction";
import registerUserAction from "../api/userActions/registerUserAction";
type ItemProps = {
text: string;
subText: string;
complete: boolean;
icon: IconProp;
time: string;
userAction?: string;
link?: string;
};
const learningItem = ({ text, subText, complete, icon, time, userAction, link }: ItemProps): JSX.Element => {
if (link) {
return (
<Link href={link}>
<a target={`${link.includes("https") ? "_blank" : "_self"}`} rel="noopener noreferrer" className="w-full">
<div
onClick={async () => {
if (userAction) {
await registerUserAction({
action: userAction
})
}
}}
className="relative bg-bunker-700 hover:bg-bunker-500 shadow-xl duration-200 rounded-md border border-dashed border-bunker-400 pl-2 pr-6 py-2 h-[5.5rem] w-full flex items-center justify-between overflow-hidden my-1.5 cursor-pointer">
<div className="flex flex-row items-center mr-4">
<FontAwesomeIcon icon={icon} className="text-4xl mx-2 w-16" />
{complete &&
<div className="bg-bunker-700 w-7 h-7 rounded-full absolute left-11 top-10 p-2 flex items-center justify-center">
<FontAwesomeIcon icon={faCheckCircle} className="text-4xl w-5 h-5 text-green" />
</div>}
<div className="flex flex-col items-start">
<div className="text-xl font-semibold mt-0.5">{text}</div>
<div className="text-sm font-normal">{subText}</div>
</div>
</div>
<div className={`pr-4 font-semibold text-sm w-28 text-right ${complete && "text-green"}`}>
{complete ? "Complete!" : "About " + time}
</div>
{complete && <div className="absolute bottom-0 left-0 h-1 w-full bg-green"></div>}
</div>
</a>
</Link>
);
} else {
return (
<div
onClick={async () => {
if (userAction) {
await registerUserAction({
action: userAction
})
}
}}
className="relative bg-bunker-700 hover:bg-bunker-500 shadow-xl duration-200 rounded-md border border-dashed border-bunker-400 pl-2 pr-6 py-2 h-[5.5rem] w-full flex items-center justify-between overflow-hidden my-1.5 cursor-pointer">
<div className="flex flex-row items-center mr-4">
<FontAwesomeIcon icon={icon} className="text-4xl mx-2 w-16" />
{complete &&
<div className="bg-bunker-700 w-7 h-7 rounded-full absolute left-11 top-10">
<FontAwesomeIcon icon={faCheckCircle} className="absolute text-4xl left-12 top-16 w-5 h-5 text-green" />
</div>}
<div className="flex flex-col items-start">
<div className="text-xl font-semibold mt-0.5">{text}</div>
<div className="text-sm font-normal mt-0.5">{subText}</div>
</div>
</div>
<div className={`pr-4 font-semibold text-sm w-28 text-right ${complete && "text-green"}`}>
{complete ? "Complete!" : "About " + time}
</div>
{complete && <div className="absolute bottom-0 left-0 h-1 w-full bg-green"></div>}
</div>
);
}
}
/**
* This tab is called Home because in the future it will include some company news,
* updates, roadmap, relavant blogs, etc. Currently it only has the setup instruction
* for the new users
*/
export default function Home() {
const router = useRouter();
const [hasUserClickedSlack, setHasUserClickedSlack] = useState(false);
const [hasUserClickedIntro, setHasUserClickedIntro] = useState(false);
const [hasUserStarred, setHasUserStarred] = useState(false);
const [usersInOrg, setUsersInOrg] = useState(false);
useEffect(() => {
const checkUserActionsFunction = async () => {
const userActionSlack = await checkUserAction({
action: "slack_cta_clicked",
});
setHasUserClickedSlack(userActionSlack ? true : false);
const userActionIntro = await checkUserAction({
action: "intro_cta_clicked",
});
setHasUserClickedIntro(userActionIntro ? true : false);
const userActionStar = await checkUserAction({
action: "star_cta_clicked",
});
setHasUserStarred(userActionStar ? true : false);
const orgId = localStorage.getItem("orgData.id");
const orgUsers = await getOrganizationUsers({
orgId: orgId ? orgId : "",
});
setUsersInOrg(orgUsers.length > 1)
};
checkUserActionsFunction();
}, []);
return (
<div className="mx-6 lg:mx-0 w-full overflow-y-scroll pt-20 h-screen">
<div className="flex flex-col items-center text-gray-300 text-lg mx-auto max-w-2xl lg:max-w-3xl xl:max-w-4xl py-6">
<div className="text-3xl font-bold text-left w-full">Your quick start guide</div>
<div className="text-md text-left w-full pt-2 pb-4 text-bunker-300">Click on the items below and follow the instructions.</div>
{learningItem({ text: "Get to know Infisical", subText: "", complete: hasUserClickedIntro, icon: faHandPeace, time: "3 min", userAction: "intro_cta_clicked", link: "https://www.youtube.com/watch?v=JS3OKYU2078" })}
{learningItem({ text: "Add your secrets", subText: "Click to see example secrets, and add your own.", complete: false, icon: faPlus, time: "2 min", userAction: "first_time_secrets_pushed", link: "/dashboard/" + router.query.id })}
{learningItem({ text: "Inject secrets locally", subText: "Replace .env files with a more secure an efficient alternative.", complete: false, icon: faNetworkWired, time: "8 min", link: "https://infisical.com/docs/getting-started/quickstart" })}
{learningItem({ text: "Integrate Infisical with your infrastructure", subText: "Only a few integrations are currently available. Many more coming soon!", complete: false, icon: faPlug, time: "15 min", link: "https://infisical.com/docs/integrations/overview" })}
{learningItem({ text: "Invite your teammates", subText: "", complete: usersInOrg, icon: faUserPlus, time: "2 min", link: "/settings/org/" + router.query.id + "?invite" })}
{learningItem({ text: "Join Infisical Slack", subText: "Have any questions? Ask us!", complete: hasUserClickedSlack, icon: faSlack, time: "1 min", userAction: "slack_cta_clicked", link: "https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g" })}
{learningItem({ text: "Star Infisical on GitHub", subText: "Like what we're doing? You know what to do! :)", complete: hasUserStarred, icon: faStar, time: "1 min", userAction: "star_cta_clicked", link: "https://github.com/Infisical/infisical" })}
</div>
</div>
);
}
Home.requireAuth = true;
+50 -19
View File
@@ -18,6 +18,11 @@ import getSecretsForProject from "~/components/utilities/secrets/getSecretsForPr
import pushKeysIntegration from "~/components/utilities/secrets/pushKeysIntegration";
import guidGenerator from "~/utilities/randomId";
import {
envMapping,
frameworks,
reverseEnvMapping,
} from "../../public/data/frequentConstants";
import deleteIntegration from "../api/integrations/DeleteIntegration";
import deleteIntegrationAuth from "../api/integrations/DeleteIntegrationAuth";
import getIntegrationApps from "../api/integrations/GetIntegrationApps";
@@ -28,20 +33,6 @@ import startIntegration from "../api/integrations/StartIntegration";
const crypto = require("crypto");
const envMapping = {
Development: "dev",
Staging: "staging",
Production: "prod",
Testing: "test",
};
const reverseEnvMapping = {
dev: "Development",
staging: "Staging",
prod: "Production",
test: "Testing",
};
const Integration = ({ projectIntegration }) => {
const [integrationEnvironment, setIntegrationEnvironment] = useState(
reverseEnvMapping[projectIntegration.environment]
@@ -262,7 +253,7 @@ export default function Integrations() {
{t("integrations:available-text2")}
</p>
</div>
<div className="grid gap-4 grid-cols-3 grid-rows-3 mx-6 mt-4 max-w-5xl">
<div className="grid gap-4 grid-cols-4 grid-rows-2 mx-6 mt-4 max-w-5xl">
{Object.keys(integrations).map((integration) => (
<div
className={`relative ${
@@ -287,12 +278,12 @@ export default function Integrations() {
>
<Image
src={`/images/integrations/${integrations[integration].name}.png`}
height={100}
width={100}
height={70}
width={70}
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 className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-3xl ml-4 max-w-xs">
<div>{integrations[integration].name.split(" ")[0]}</div>
<div className="text-base">
{integrations[integration].name.split(" ")[1]}{" "}
@@ -300,7 +291,7 @@ export default function Integrations() {
</div>
</div>
) : (
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-2xl ml-8 max-w-xs">
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-xl ml-4 max-w-xs">
{integrations[integration].name}
</div>
)}
@@ -350,6 +341,46 @@ export default function Integrations() {
</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">Framework Integrations</p>
</div>
<p className="mr-4 text-base text-gray-400">
Click on a framework to get the setup instructions.
</p>
</div>
<div className="grid gap-4 grid-cols-7 grid-rows-2 mx-6 mt-4 max-w-5xl">
{frameworks.map((framework) => (
<div key={framework.name}>
<a
href={framework.link}
rel="noopener"
className={`relative flex flex-row items-center justify-center bg-bunker-500 hover:bg-gradient-to-tr hover:from-sky-400 hover:to-primary duration-200 h-32 rounded-md p-0.5 items-center cursor-pointer`}
>
<div
className={`font-semibold bg-bunker-500 flex flex-col items-center justify-center h-full w-full rounded-md text-gray-300 group-hover:text-gray-200 duration-200 ${
framework?.name?.split(" ").length > 1
? "text-sm px-1"
: "text-xl px-2"
} text-center w-full max-w-xs`}
>
{framework?.image && (
<Image
src={`/images/integrations/${framework.image}.png`}
height={framework?.name ? 60 : 90}
width={framework?.name ? 60 : 90}
alt="integration logo"
></Image>
)}
{framework?.name && framework?.image && (
<div className="h-2"></div>
)}
{framework?.name && framework.name}
</div>
</a>
</div>
))}
</div>
</div>
</div>
</div>
+1 -1
View File
@@ -54,7 +54,7 @@ export default function SettingsBilling() {
orgId: localStorage.getItem("orgData.id"),
});
setCurrentPlan(subscriptions.data[0].plan.product);
setCurrentPlan(subscriptions.data[0].plan.id);
const orgUsers = await getOrganizationUsers({
orgId: localStorage.getItem("orgData.id"),
});
+10 -8
View File
@@ -201,7 +201,7 @@ export default function SignUp() {
},
async () => {
client.createVerifier(async (err, result) => {
const response = await completeAccountInformationSignup({
let response = await completeAccountInformationSignup({
email,
firstName,
lastName,
@@ -473,7 +473,7 @@ export default function SignUp() {
</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={t("auth:signup")}
text={t("signup:signup")}
loading={isLoading}
onButtonPressed={signupErrorCheck}
size="lg"
@@ -486,19 +486,19 @@ export default function SignUp() {
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">
<p className="text-4xl text-center font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
{t("auth:step4-message")}
{t("signup:step4-message")}
</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>{t("auth:step4-description1")}</div>
<div className="mt-3">{t("auth:step4-description2")}</div>
<div>{t("signup:step4-description1")}</div>
<div className="mt-3">{t("signup:step4-description2")}</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" />
{t("auth:step4-description3")}
{t("signup:step4-description3")}
</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={t("auth:step4-download")}
text={t("signup:step4-download")}
onButtonPressed={async () => {
await issueBackupKey({
email,
@@ -507,7 +507,9 @@ export default function SignUp() {
setBackupKeyError,
setBackupKeyIssued,
});
router.push("auth:/dashboard/");
const userWorkspaces = await getWorkspaces();
let userWorkspace = userWorkspaces[0]._id;
router.push("/home/" + userWorkspace);
}}
size="lg"
/>
+2 -2
View File
@@ -92,7 +92,7 @@ export default function SignupInvite() {
},
async () => {
client.createVerifier(async (err, result) => {
const response = await completeAccountInformationSignupInvite({
let response = await completeAccountInformationSignupInvite({
email,
firstName,
lastName,
@@ -291,7 +291,7 @@ export default function SignupInvite() {
<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">
<div className="flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg">
<Button
text="Sign Up"
onButtonPressed={() => {
+82
View File
@@ -0,0 +1,82 @@
const envMapping = {
Development: "dev",
Staging: "staging",
Production: "prod",
Testing: "test",
};
const reverseEnvMapping = {
dev: "Development",
staging: "Staging",
prod: "Production",
test: "Testing",
};
const frameworks = [{
"name": "Docker",
"image": "Docker",
"link": "https://infisical.com/docs/integrations/platforms/docker"
}, {
"name": "Docker Compose",
"image": "Docker Compose",
"link": "https://infisical.com/docs/integrations/platforms/docker-compose"
}, {
"name": "React",
"image": "React",
"link": "https://infisical.com/docs/integrations/frameworks/react"
}, {
"name": "Vue",
"image": "Vue",
"link": "https://infisical.com/docs/integrations/frameworks/vue"
}, {
"image": "Express",
"link": "https://infisical.com/docs/integrations/frameworks/express"
},{
"image": "Next.js",
"link": "https://infisical.com/docs/integrations/frameworks/nextjs"
}, {
"name": "Django",
"image": "Django",
"link": "https://infisical.com/docs/integrations/frameworks/django"
}, {
"name": "NestJS",
"image": "NestJS",
"link": "https://infisical.com/docs/integrations/frameworks/nestjs"
}, {
"name": "Nuxt",
"image": "Nuxt",
"link": "https://infisical.com/docs/integrations/frameworks/nuxt"
}, {
"name": "Gatsby",
"image": "Gatsby",
"link": "https://infisical.com/docs/integrations/frameworks/gatsby"
}, {
"name": "Remix",
"image": "Remix",
"link": "https://infisical.com/docs/integrations/frameworks/remix"
}, {
"name": "Vite",
"image": "Vite",
"link": "https://infisical.com/docs/integrations/frameworks/vite"
}, {
"image": "Fiber",
"link": "https://infisical.com/docs/integrations/frameworks/fiber"
}, {
"name": "Flask",
"image": "Flask",
"link": "https://infisical.com/docs/integrations/frameworks/flask"
}, {
"name": "Laravel",
"image": "Laravel",
"link": "https://infisical.com/docs/integrations/frameworks/laravel"
}, {
"image": "Rails",
"link": "https://infisical.com/docs/integrations/frameworks/rails"
}
]
export {
envMapping,
frameworks,
reverseEnvMapping
};
Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Some files were not shown because too many files have changed in this diff Show More