Merge branch 'main' into I-36-use-pre-built-frontend-image

This commit is contained in:
Reginald Bondoc
2022-12-05 17:50:11 +01:00
48 changed files with 1357 additions and 754 deletions

View File

@ -6,6 +6,8 @@ jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
@ -23,12 +25,13 @@ jobs:
uses: docker/build-push-action@v3
with:
push: true
context: /backend
tags: infisical/backend:latest
context: backend
tags: infisical/backend:test
-
name: Build and push frontend
uses: docker/build-push-action@v3
with:
push: true
context: /frontend
tags: infisical/frontend:latest
file: frontend/Dockerfile.dev
context: frontend
tags: infisical/frontend:test

View File

@ -0,0 +1,33 @@
name: Release Charts
on: [workflow_dispatch]
jobs:
release:
# depending on default permission settings for your org (contents being read-only or read-write for workloads), you will have to add permissions
# see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Install Helm
uses: azure/setup-helm@v3
with:
version: v3.10.0
- name: Run chart-releaser
uses: helm/chart-releaser-action@v1.4.1
with:
charts_dir: helm-charts
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

View File

@ -54,15 +54,13 @@ To quickly get started, visit our [get started guide](https://infisical.com/docs
## What's cool about this?
Infisical is simple, E2EE, and (soon to be) complete.
Infisical makes secret management simple and end-to-end encrypted by default. We're on a mission to make it more accessible to all developers, <i>not just security teams</i>.
According to a [report](https://www.ekransystem.com/en/blog/secrets-management) in 2019, only 10% of organizations use secret management solutions despite all using digital secrets to some extent.
We're on a mission to make secret management more accessible to everyone — that means building for developers, not just security teams.
If you care about efficiency and security, then Infisical is right for you.
Need any integrations or want a new feature? Feel free to [create an issue](https://github.com/Infisical/infisical/issues) or [contribute](https://infisical.com/docs/contributing/overview) directly to the repository.
We are currently working hard to make Infisical more extensive. Need any integrations or want a new feature? Feel free to [create an issue](https://github.com/Infisical/infisical/issues) or [contribute](https://infisical.com/docs/contributing/overview) directly to the repository.
## Contributing
@ -124,7 +122,7 @@ We're currently setting the foundation and building [integrations](https://infis
</tr>
<tr>
<td align="left" valign="middle">
🔜 Vercel
🔜 Vercel (https://github.com/Infisical/infisical/issues/60)
</td>
<td align="left" valign="middle">
🔜 GitLab CI/CD
@ -279,4 +277,4 @@ Looking to report a security vulnerability? Please don't post about it in GitHub
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<a href="https://github.com/dangtony98"><img src="https://avatars.githubusercontent.com/u/25857006?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mv-turtle"><img src="https://avatars.githubusercontent.com/u/78047717?s=96&v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/maidul98"><img src="https://avatars.githubusercontent.com/u/9300960?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gangjun06"><img src="https://avatars.githubusercontent.com/u/50910815?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/reginaldbondoc"><img src="https://avatars.githubusercontent.com/u/7693108?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/hanywang2"><img src="https://avatars.githubusercontent.com/u/44352119?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/tobias-mintlify"><img src="https://avatars.githubusercontent.com/u/110702161?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/0xflotus"><img src="https://avatars.githubusercontent.com/u/26602940?v=4" width="50" height="50" alt=""/></a>
<a href="https://github.com/dangtony98"><img src="https://avatars.githubusercontent.com/u/25857006?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mv-turtle"><img src="https://avatars.githubusercontent.com/u/78047717?s=96&v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/maidul98"><img src="https://avatars.githubusercontent.com/u/9300960?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gangjun06"><img src="https://avatars.githubusercontent.com/u/50910815?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/reginaldbondoc"><img src="https://avatars.githubusercontent.com/u/7693108?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/SH5H"><img src="https://avatars.githubusercontent.com/u/25437192?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/hanywang2"><img src="https://avatars.githubusercontent.com/u/44352119?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/tobias-mintlify"><img src="https://avatars.githubusercontent.com/u/110702161?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/0xflotus"><img src="https://avatars.githubusercontent.com/u/26602940?v=4" width="50" height="50" alt=""/></a>

View File

@ -15,7 +15,7 @@ var rootCmd = &cobra.Command{
Short: "Infisical CLI is used to inject environment variables into any process",
Long: `Infisical is a simple, end-to-end encrypted service that enables teams to sync and manage their environment variables across their development life cycle.`,
CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true},
Version: "0.1.5",
Version: "0.1.6",
}
// Execute adds all child commands to the root command and sets flags appropriately.

16
docs/contributing/FAQ.mdx Normal file
View File

@ -0,0 +1,16 @@
---
title: "Frequently Asked Questions"
description: "Have any questions? [Join our Slack community](https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g)."
---
## Problem with SMTP
You can normally populate `SMTP_USERNAME` and `SMTP_PASSWORD` with your usual login and password (you could also create a 'burner' email). Sometimes, there still are problems.
You can go to your Gmail account settings > security and enable “less secure apps”. This would allow Infisical to use your Gmail to send emails.
If it still doesn't work, [this](https://stackoverflow.com/questions/72547853/unable-to-send-email-in-c-sharp-less-secure-app-access-not-longer-available/72553362#72553362) should help.
## `MONGO_URL` issues
Your `MONGO_URL` should be something like `mongodb://root:example@mongo:27017/?authSource=admin`. If you want to change it (not recommended), you should make sure that you keep this URL in line with `MONGO_USERNAME=root` and `MONGO_PASSWORD=example`.

View File

@ -162,7 +162,8 @@
"pages": [
"contributing/overview",
"contributing/code-of-conduct",
"contributing/developing"
"contributing/developing",
"contributing/FAQ"
]
}
],

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)$"]
]
}
]
}
}
}

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

View File

@ -1,25 +1,28 @@
import React from "react";
import Image from "next/image";
import { IconProp } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon, FontAwesomeIconProps } 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;
};
/**
* This is the main butto component in the app.
* @param {object} props
* @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
@ -29,21 +32,28 @@ type ButtonProps = {
* @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
* @returns
*/
export default function Button (props: ButtonProps): JSX.Element {
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
@ -57,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
@ -69,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"
);
@ -100,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"}`}
@ -115,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>

View File

@ -37,7 +37,7 @@ const AddServiceTokenDialog = ({
const [serviceTokenCopied, setServiceTokenCopied] = useState(false);
const generateServiceToken = async () => {
const latestFileKey = await getLatestFileKey(workspaceId);
const latestFileKey = await getLatestFileKey({ workspaceId });
const key = decryptAssymmetric({
ciphertext: latestFileKey.latestKey.encryptedKey,

View File

@ -3,8 +3,9 @@ import { useEffect, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import {
faBookOpen,
faGear,
faHouse,
faKey,
faMobile,
faPlug,
faUser,
@ -18,6 +19,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 {
@ -54,10 +56,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;
@ -114,7 +116,7 @@ export default function Layout({ children }) {
href:
"/dashboard/" + workspaceMapping[workspaceSelected] + "?Development",
title: "Secrets",
emoji: <FontAwesomeIcon icon={faHouse} />,
emoji: <FontAwesomeIcon icon={faKey} />,
},
{
href: "/users/" + workspaceMapping[workspaceSelected],
@ -155,9 +157,7 @@ export default function Layout({ children }) {
router.push("/noprojects");
} else if (router.asPath != "/noprojects") {
const intendedWorkspaceId = router.asPath
.split("/")
[router.asPath.split("/").length - 1].split("?")[0];
.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" &&
@ -178,9 +178,7 @@ export default function Layout({ children }) {
userWorkspaces.map((workspace) => [workspace._id, workspace.name])
)[
router.asPath
.split("/")
[router.asPath.split("/").length - 1].split("?")[0]
]
.split("/")[router.asPath.split("/").length - 1].split("?")[0]]
);
}
}
@ -192,12 +190,9 @@ export default function Layout({ children }) {
workspaceMapping[workspaceSelected] &&
workspaceMapping[workspaceSelected] !==
router.asPath
.split("/")
[router.asPath.split("/").length - 1].split("?")[0]
.split("/")[router.asPath.split("/").length - 1].split("?")[0]
) {
router.push(
"/dashboard/" + workspaceMapping[workspaceSelected] + "?Development"
);
router.push("/dashboard/" + workspaceMapping[workspaceSelected] + "?Development");
localStorage.setItem(
"projectData.id",
workspaceMapping[workspaceSelected]
@ -214,66 +209,88 @@ 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">
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">
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-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 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">{emoji}</p>
{title}
</div>
) : 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}>
<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.5 text-white text-sm rounded cursor-pointer hover:bg-primary-50/5`}
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">{emoji}</p>
{title}
</div>
) : 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>
)}
</li>
))}
</ul>
) : (
<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

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");

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>

View File

@ -1,4 +1,4 @@
import React, { useRef } from "react";
import React, { SyntheticEvent, useRef } from "react";
import { faCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@ -6,6 +6,15 @@ 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
@ -14,51 +23,60 @@ const REGEX = /([$]{.*?})/g;
* @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 suplicated key names on 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
}) => {
const ref = useRef(null);
const syncScroll = (e) => {
ref.current.scrollTop = e.target.scrollTop;
ref.current.scrollLeft = e.target.scrollLeft;
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(value.charAt(0)) && value != "";
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`}
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)}
onChange={(e) =>
onChangeHandler(e.target.value.toUpperCase(), 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-2 ${error ? "focus:ring-red/50" : "focus:ring-primary/50"} duration-200`}
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 &&
{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 &&
)}
{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") {
@ -78,61 +96,61 @@ const DashboardInputField = ({
} 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}
<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>
)}
{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>
);
} else {
return <span key={id} className="ph-no-capture">{word}</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}
/>
))}
{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);

View File

@ -1,4 +1,4 @@
import { useState } from "react";
import { type ChangeEvent, type DragEvent, useState } from "react";
import Image from "next/image";
import { faUpload } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@ -8,27 +8,37 @@ 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,
}) => {
const handleDragEnter = (e) => {
}: DropZoneProps) => {
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();
@ -39,22 +49,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);
@ -72,23 +85,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);
};
@ -105,17 +123,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>
@ -142,10 +160,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="">Drag and drop your .env file here.</p>
@ -154,7 +172,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>

View File

@ -47,27 +47,44 @@ const supportOptions = [
],
];
export default function Navbar({ onButtonPressed }) {
const router = useRouter();
const [user, setUser] = useState({});
const [orgs, setOrgs] = useState([]);
const [currentOrg, setCurrentOrg] = useState([]);
export interface ICurrentOrg {
name: string;
}
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);
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<IUser | undefined>();
const [orgs, setOrgs] = useState([]);
const [currentOrg, setCurrentOrg] = useState<ICurrentOrg | undefined>();
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 (
@ -108,7 +125,7 @@ export default function Navbar({ onButtonPressed }) {
key={guidGenerator()}
target="_blank"
rel="noopener"
href={option[2]}
href={String(option[2])}
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">
@ -238,9 +255,9 @@ 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={() => {

View File

@ -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);
})();

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;
@ -137,7 +137,15 @@ const attemptLogin = async (
await pushKeys(
{
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: [

View File

@ -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;
};

View File

@ -7,20 +7,30 @@ 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");
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({
@ -51,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,
@ -65,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,
};
});
@ -76,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,
@ -95,7 +105,7 @@ const pushKeys = async (obj, workspaceId, env) => {
workspaceId,
secrets,
keys,
environment: envMapping[env],
environment: envMapping[env as keyof typeof envMapping],
});
};

File diff suppressed because it is too large Load Diff

View File

@ -53,9 +53,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",

View File

@ -31,8 +31,8 @@ const issueBackupPrivateKey = ({
if (res.status == 200) {
return res;
} else {
return res;
console.log("Failed to issue the backup key");
return res;
}
});
};

View File

@ -3,11 +3,8 @@ 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) => {
const logout = async () => {
return SecurityClient.fetchCall("/api/v1/auth/logout", {
method: "POST",
headers: {
@ -15,7 +12,7 @@ const logout = async (req, res) => {
},
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", "");

View File

@ -2,18 +2,17 @@ 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("/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");

View File

@ -2,13 +2,13 @@ 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(
"/api/v1/organization/" + req.orgId + "/users",
"/api/v1/organization/" + orgId + "/users",
{
method: "GET",
headers: {
@ -16,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");

View File

@ -2,18 +2,16 @@ 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) => {
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");

View File

@ -2,18 +2,15 @@ import SecurityClient from "~/utilities/SecurityClient";
/**
* This route gets the information about a specific user.
* @param {*} req
* @param {*} res
* @returns
*/
const getUser = (req, res) => {
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");

View File

@ -1,11 +1,12 @@
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) => {
const createWorkspace = ( { workspaceName, organizationId }: { workspaceName: string; organizationId: string; }) => {
return SecurityClient.fetchCall("/api/v1/workspace", {
method: "POST",
headers: {
@ -16,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");

View File

@ -2,10 +2,10 @@ 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(
"/api/v1/key/" + workspaceId + "/latest",
{
@ -15,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");

View File

@ -2,13 +2,12 @@ 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(
"/api/v1/workspace/" + req.workspaceId,
"/api/v1/workspace/" + projectId,
{
method: "GET",
headers: {
@ -16,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");
@ -24,4 +23,4 @@ const getWorkspaceInfo = (req, res) => {
});
};
export default getWorkspaceInfo;
export default getProjectInfo;

View File

@ -2,13 +2,12 @@ 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(
"/api/v1/workspace/" + req.workspaceId + "/keys",
"/api/v1/workspace/" + workspaceId + "/keys",
{
method: "GET",
headers: {
@ -16,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");

View File

@ -2,13 +2,12 @@ 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(
"/api/v1/workspace/" + req.workspaceId + "/users",
"/api/v1/workspace/" + workspaceId + "/users",
{
method: "GET",
headers: {
@ -16,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");

View File

@ -1,19 +1,17 @@
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) => {
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");

View File

@ -378,9 +378,9 @@ export default function Dashboard() {
} else if (duplicatesExist) {
console.log("Remove the duplicated entries first!");
} else {
// Once "Save changed is clicked", disable that button
// Once "Save changes is clicked", disable that button
setButtonReady(false);
pushKeys(obj, router.query.id, env);
pushKeys({obj, workspaceId: router.query.id, env});
/**
* Check which integrations are active for this project and environment

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;

View File

@ -197,7 +197,7 @@ export default function SignUp() {
},
async () => {
client.createVerifier(async (err, result) => {
const response = await completeAccountInformationSignup({
let response = await completeAccountInformationSignup({
email,
firstName,
lastName,
@ -495,7 +495,9 @@ export default function SignUp() {
setBackupKeyError,
setBackupKeyIssued,
});
router.push("/dashboard/");
const userWorkspaces = await getWorkspaces();
let userWorkspace = userWorkspaces[0]._id;
router.push("/home/" + userWorkspace);
}}
size="lg"
/>

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={() => {

22
helm-charts/README.md Normal file
View File

@ -0,0 +1,22 @@
## Usage
[Helm](https://helm.sh) must be installed to use the charts. Please refer to
Helm's [documentation](https://helm.sh/docs) to get started.
Once Helm has been set up correctly, add the repo as follows:
```
helm repo add <alias> https://infisical.github.io/helm-charts
```
If you had already added this repo earlier, run `helm repo update` to retrieve
the latest versions of the packages. You can then run `helm search repo
<alias>` to see the charts.
To install the <chart-name> chart:
helm install my-<chart-name> <alias>/<chart-name>
To uninstall the chart:
helm delete my-<chart-name>

View File

@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View File

@ -0,0 +1,16 @@
apiVersion: v2
name: infisical
description: A helm chart for a full Infisical application
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.16.0"

View File

@ -0,0 +1,45 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-backend-deployment
labels:
app: backend
namespace: {{ .Values.namespace }}
spec:
replicas: {{ .Values.backend.replicaCount }}
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: backend
image: infisical/backend
imagePullPolicy: {{ .Values.backend.image.pullPolicy }}
ports:
- containerPort: 4000
env:
{{- range $key, $value := .Values.secrets }}
{{- if eq $value "MUST_REPLACE" }}
{{ fail "Environment variables are not set. Please set all environment variables to continue." }}
{{ end }}
- name: {{ $key }}
value: {{ $value }}
{{- end }}
---
apiVersion: v1
kind: Service
metadata:
name: infisical-backend-service
namespace: {{ .Values.namespace }}
spec:
selector:
app: backend
ports:
- protocol: TCP
port: 4000
targetPort: 4000 # container port

View File

@ -0,0 +1,36 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-frontend-deployment
labels:
app: frontend
namespace: {{ .Values.namespace }}
spec:
replicas: {{ .Values.frontend.replicaCount }}
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: infisical/frontend
imagePullPolicy: {{ .Values.frontend.image.pullPolicy }}
ports:
- containerPort: 4000
---
apiVersion: v1
kind: Service
metadata:
name: infisical-frontend-service
namespace: {{ .Values.namespace }}
spec:
selector:
app: frontend
ports:
- protocol: TCP
port: 3000 # service
targetPort: 3000 # container port

View File

@ -0,0 +1,40 @@
{{ if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: infisical-ingress
namespace: {{ .Values.namespace }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
- host: {{ .Values.ingress.hostName}}
http:
paths:
- path: {{ .Values.ingress.frontend.path }}
pathType: {{ .Values.ingress.frontend.pathType }}
backend:
service:
name: infisical-frontend-service
port:
number: 3000
- path: {{ .Values.ingress.backend.path }}
pathType: {{ .Values.ingress.backend.pathType }}
backend:
service:
name: infisical-backend-service
port:
number: 4000
{{ end }}

View File

@ -0,0 +1,40 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mongodb-deployment
namespace: {{ .Values.namespace }}
labels:
app: mongodb
spec:
replicas: 1 # Cannot be scaled. To scale, you must set up Stateful Set
selector:
matchLabels:
app: mongodb
template:
metadata:
labels:
app: mongodb
spec:
containers:
- name: mongodb
image: mongo
ports:
- containerPort: 27017
env:
- name: MONGO_INITDB_ROOT_USERNAME
value: root
- name: MONGO_INITDB_ROOT_PASSWORD
value: root
---
apiVersion: v1
kind: Service
metadata:
name: mongodb-service
namespace: {{ .Values.namespace }}
spec:
selector:
app: mongodb
ports:
- protocol: TCP
port: 27017
targetPort: 27017 # container port

View File

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: infisical

View File

@ -0,0 +1,76 @@
#####
# INFISICAL K8 DEFAULT VALUES FIL
# PLEASE REPLACE VALUES/EDIT AS REQUIRED
#####
namespace: infisical
frontend:
replicaCount: 1
image:
repository:
pullPolicy: IfNotPresent
tag: "latest"
backend:
replicaCount: 1
image:
repository:
pullPolicy: IfNotPresent
tag: "latest"
ingress:
enabled: true
annotations: {}
hostName: example.com
frontend:
path: /
pathType: Prefix
backend:
path: /api
pathType: Prefix
tls: []
## Complete Ingress example
# ingress:
# enabled: true
# annotations:
# kubernetes.io/ingress.class: "nginx"
# cert-manager.io/issuer: letsencrypt-nginx
# hostName: example.com
# frontend:
# path: /
# pathType: Prefix
# backend:
# path: /api
# pathType: Prefix
# tls:
# hosts:
# - k8.infisical.com
# secretName: letsencrypt-nginx
###
### YOU MUST FILL IN ALL SECRETS BELOW
###
secrets:
# Required keys for platform encryption/decryption ops. Replace with nacl sk keys
PRIVATE_KEY: MUST_REPLACE
PUBLIC_KEY: MUST_REPLACE
ENCRYPTION_KEY: MUST_REPLACE
# JWT
# Required secrets to sign JWT tokens
JWT_SIGNUP_SECRET: MUST_REPLACE
JWT_REFRESH_SECRET: MUST_REPLACE
JWT_AUTH_SECRET: MUST_REPLACE
# Mail/SMTP
# Required to send emails
SMTP_HOST: MUST_REPLACE
SMTP_NAME: MUST_REPLACE
SMTP_USERNAME: MUST_REPLACE
SMTP_PASSWORD: MUST_REPLACE
# You may replace with Mongo Cloud URI
MONGO_URL: mongodb://root:root@mongodb-service:27017/