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
+7 -4
View File
@@ -6,6 +6,8 @@ jobs:
docker: docker:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout
uses: actions/checkout@v2
- -
name: Set up QEMU name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
@@ -23,12 +25,13 @@ jobs:
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
with: with:
push: true push: true
context: /backend context: backend
tags: infisical/backend:latest tags: infisical/backend:test
- -
name: Build and push frontend name: Build and push frontend
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
with: with:
push: true push: true
context: /frontend file: frontend/Dockerfile.dev
tags: infisical/frontend:latest context: frontend
tags: infisical/frontend:test
+33
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 "[email protected]"
- name: Install Helm
uses: azure/setup-helm@v3
with:
version: v3.10.0
- name: Run chart-releaser
uses: helm/[email protected]
with:
charts_dir: helm-charts
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
+4 -6
View File
@@ -54,15 +54,13 @@ To quickly get started, visit our [get started guide](https://infisical.com/docs
## What's cool about this? ## 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. 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. 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 ## Contributing
@@ -124,7 +122,7 @@ We're currently setting the foundation and building [integrations](https://infis
</tr> </tr>
<tr> <tr>
<td align="left" valign="middle"> <td align="left" valign="middle">
🔜 Vercel 🔜 Vercel (https://github.com/Infisical/infisical/issues/60)
</td> </td>
<td align="left" valign="middle"> <td align="left" valign="middle">
🔜 GitLab CI/CD 🔜 GitLab CI/CD
@@ -279,4 +277,4 @@ Looking to report a security vulnerability? Please don't post about it in GitHub
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
<!-- markdownlint-disable --> <!-- 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>
+1 -1
View File
@@ -15,7 +15,7 @@ var rootCmd = &cobra.Command{
Short: "Infisical CLI is used to inject environment variables into any process", 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.`, 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}, 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. // Execute adds all child commands to the root command and sets flags appropriately.
+16
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`.
+2 -1
View File
@@ -162,7 +162,8 @@
"pages": [ "pages": [
"contributing/overview", "contributing/overview",
"contributing/code-of-conduct", "contributing/code-of-conduct",
"contributing/developing" "contributing/developing",
"contributing/FAQ"
] ]
} }
], ],
+18 -15
View File
@@ -1,10 +1,21 @@
{ {
"extends": "next/core-web-vitals", "extends": [
"plugins": [ "eslint:recommended",
"simple-import-sort" "plugin:@typescript-eslint/recommended",
"next",
"next/core-web-vitals"
], ],
"parser": "@typescript-eslint/parser",
"plugins": ["simple-import-sort", "@typescript-eslint"],
"rules": { "rules": {
"react-hooks/exhaustive-deps": "off", "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/exports": "warn",
"simple-import-sort/imports": [ "simple-import-sort/imports": [
"warn", "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)(/.*|$)" "^(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 // Packages `react` related packages
[ ["^react", "^next", "^@?\\w"],
"^react",
"^next",
"^@?\\w"
],
// Internal packages. // Internal packages.
[ ["^~(/.*|$)"],
"^~(/.*|$)"
],
// Relative imports // Relative imports
[ [
"^\\.\\.(?!/?$)", "^\\.\\.(?!/?$)",
@@ -36,11 +41,9 @@
"^\\./?$" "^\\./?$"
], ],
// Style imports. // Style imports.
[ ["^.+\\.?(css|scss)$"]
"^.+\\.?(css|scss)$"
]
] ]
} }
] ]
} }
} }
+10 -8
View File
@@ -1,20 +1,22 @@
This is the client repository for Infisical. 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. Please ensure you have Docker and Docker Compose installed for your OS.
### Steps to start server ### Steps to start server
- `CD` into the repo - `CD` into the repo
- run command `docker-compose -f docker-compose.dev.yml up --build --force-recreate` - 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 - `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 ### 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 You will also need to ensure that a .env.local file exists with all required environment variables
+34 -22
View File
@@ -1,25 +1,28 @@
import React from "react"; import React from "react";
import Image from "next/image"; import Image from "next/image";
import { IconProp } from "@fortawesome/fontawesome-svg-core"; 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 = { type ButtonProps = {
text: string; text: string;
onButtonPressed: () => void; onButtonPressed: () => void;
loading: boolean; loading?: boolean;
color: string; color: string;
size: string; size: string;
icon: IconProp; icon?: IconProp;
active: boolean; active?: boolean;
iconDisabled: string; iconDisabled?: string;
textDisabled: string; textDisabled?: string;
} };
/** /**
* This is the main butto component in the app. * This is the main butto component in the app.
* @param {object} props * @param {object} props
* @param {string} props.text - text inside the button * @param {string} props.text - text inside the button
* @param {function} props.onButtonPressed - the action that happens when the button is clicked * @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 {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 {boolean} props.active - if the button is active or disabled
* @param {FontAwesomeIconProps} props.text - the icon inside the button when it is 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 * @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; // Check if the button show always be 'active' - then true;
// or if it should switch between 'active' and 'disabled' - then give the status // or if it should switch between 'active' and 'disabled' - then give the status
const activityStatus = props.active || (props.text != "" && props.textDisabled == undefined) const activityStatus =
props.active || (props.text != "" && props.textDisabled == undefined);
let styleButton = classNames(
const styleButton = classNames(
"group m-auto md:m-0 inline-block rounded-md duration-200", "group m-auto md:m-0 inline-block rounded-md duration-200",
// Setting background colors and hover modes // Setting background colors and hover modes
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 == "mineshaft" && !activityStatus && "bg-mineshaft",
(props.color == "primary" || !props.color) && activityStatus && "bg-primary hover:opacity-80", (props.color == "primary" || !props.color) &&
(props.color == "primary" || !props.color) && !activityStatus && "bg-primary", activityStatus &&
"bg-primary hover:opacity-80",
(props.color == "primary" || !props.color) &&
!activityStatus &&
"bg-primary",
props.color == "red" && "bg-red", props.color == "red" && "bg-red",
// Changing the opacity when active vs when not // 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" 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", "relative font-medium flex items-center",
// Setting the text color for the text and icon // 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" props.size == "icon" && "flex items-center justify-center"
); );
let textStyle = classNames( const textStyle = classNames(
"relative duration-200", "relative duration-200",
// Show the loading sign if the loading indicator is on // Show the loading sign if the loading indicator is on
props.loading == true ? "opacity-0" : "opacity-100", props.loading ? "opacity-0" : "opacity-100",
props.size == "md" && "text-sm", props.size == "md" && "text-sm",
props.size == "lg" && "text-lg" props.size == "lg" && "text-lg"
); );
@@ -100,7 +110,7 @@ export default function Button (props: ButtonProps): JSX.Element {
</div> </div>
{props.icon && ( {props.icon && (
<FontAwesomeIcon <FontAwesomeIcon
icon={props.icon as IconProp} icon={props.icon}
className={`flex my-auto font-extrabold ${ className={`flex my-auto font-extrabold ${
props.size == "icon-sm" ? "text-sm" : "text-md" props.size == "icon-sm" ? "text-sm" : "text-md"
} ${(props.text || props.textDisabled) && "mr-2"}`} } ${(props.text || props.textDisabled) && "mr-2"}`}
@@ -115,7 +125,9 @@ export default function Button (props: ButtonProps): JSX.Element {
/> />
)} )}
{(props.text || props.textDisabled) && ( {(props.text || props.textDisabled) && (
<p className={textStyle}>{activityStatus ? props.text : props.textDisabled}</p> <p className={textStyle}>
{activityStatus ? props.text : props.textDisabled}
</p>
)} )}
</div> </div>
</button> </button>
@@ -37,7 +37,7 @@ const AddServiceTokenDialog = ({
const [serviceTokenCopied, setServiceTokenCopied] = useState(false); const [serviceTokenCopied, setServiceTokenCopied] = useState(false);
const generateServiceToken = async () => { const generateServiceToken = async () => {
const latestFileKey = await getLatestFileKey(workspaceId); const latestFileKey = await getLatestFileKey({ workspaceId });
const key = decryptAssymmetric({ const key = decryptAssymmetric({
ciphertext: latestFileKey.latestKey.encryptedKey, ciphertext: latestFileKey.latestKey.encryptedKey,
+87 -70
View File
@@ -3,8 +3,9 @@ import { useEffect, useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { import {
faBookOpen,
faGear, faGear,
faHouse, faKey,
faMobile, faMobile,
faPlug, faPlug,
faUser, faUser,
@@ -18,6 +19,7 @@ import getOrganizationUsers from "~/pages/api/organization/GetOrgUsers";
import addUserToWorkspace from "~/pages/api/workspace/addUserToWorkspace"; import addUserToWorkspace from "~/pages/api/workspace/addUserToWorkspace";
import createWorkspace from "~/pages/api/workspace/createWorkspace"; import createWorkspace from "~/pages/api/workspace/createWorkspace";
import getWorkspaces from "~/pages/api/workspace/getWorkspaces"; import getWorkspaces from "~/pages/api/workspace/getWorkspaces";
import uploadKeys from "~/pages/api/workspace/uploadKeys";
import NavBarDashboard from "../navigation/NavBarDashboard"; import NavBarDashboard from "../navigation/NavBarDashboard";
import { import {
@@ -54,10 +56,10 @@ export default function Layout({ children }) {
const workspaces = await getWorkspaces(); const workspaces = await getWorkspaces();
const currentWorkspaces = workspaces.map((workspace) => workspace.name); const currentWorkspaces = workspaces.map((workspace) => workspace.name);
if (!currentWorkspaces.includes(workspaceName)) { if (!currentWorkspaces.includes(workspaceName)) {
const newWorkspace = await createWorkspace( const newWorkspace = await createWorkspace({
workspaceName, workspaceName,
localStorage.getItem("orgData.id") organizationId: localStorage.getItem("orgData.id")
); });
let newWorkspaceId; let newWorkspaceId;
try { try {
newWorkspaceId = newWorkspace._id; newWorkspaceId = newWorkspace._id;
@@ -114,7 +116,7 @@ export default function Layout({ children }) {
href: href:
"/dashboard/" + workspaceMapping[workspaceSelected] + "?Development", "/dashboard/" + workspaceMapping[workspaceSelected] + "?Development",
title: "Secrets", title: "Secrets",
emoji: <FontAwesomeIcon icon={faHouse} />, emoji: <FontAwesomeIcon icon={faKey} />,
}, },
{ {
href: "/users/" + workspaceMapping[workspaceSelected], href: "/users/" + workspaceMapping[workspaceSelected],
@@ -155,9 +157,7 @@ export default function Layout({ children }) {
router.push("/noprojects"); router.push("/noprojects");
} else if (router.asPath != "/noprojects") { } else if (router.asPath != "/noprojects") {
const intendedWorkspaceId = router.asPath const intendedWorkspaceId = router.asPath
.split("/") .split("/")[router.asPath.split("/").length - 1].split("?")[0];
[router.asPath.split("/").length - 1].split("?")[0];
// If a user is not a member of a workspace they are trying to access, just push them to one of theirs // If a user is not a member of a workspace they are trying to access, just push them to one of theirs
if ( if (
intendedWorkspaceId != "heroku" && intendedWorkspaceId != "heroku" &&
@@ -178,9 +178,7 @@ export default function Layout({ children }) {
userWorkspaces.map((workspace) => [workspace._id, workspace.name]) userWorkspaces.map((workspace) => [workspace._id, workspace.name])
)[ )[
router.asPath router.asPath
.split("/") .split("/")[router.asPath.split("/").length - 1].split("?")[0]]
[router.asPath.split("/").length - 1].split("?")[0]
]
); );
} }
} }
@@ -192,12 +190,9 @@ export default function Layout({ children }) {
workspaceMapping[workspaceSelected] && workspaceMapping[workspaceSelected] &&
workspaceMapping[workspaceSelected] !== workspaceMapping[workspaceSelected] !==
router.asPath router.asPath
.split("/") .split("/")[router.asPath.split("/").length - 1].split("?")[0]
[router.asPath.split("/").length - 1].split("?")[0]
) { ) {
router.push( router.push("/dashboard/" + workspaceMapping[workspaceSelected] + "?Development");
"/dashboard/" + workspaceMapping[workspaceSelected] + "?Development"
);
localStorage.setItem( localStorage.setItem(
"projectData.id", "projectData.id",
workspaceMapping[workspaceSelected] workspaceMapping[workspaceSelected]
@@ -214,66 +209,88 @@ export default function Layout({ children }) {
<NavBarDashboard /> <NavBarDashboard />
<div className="flex flex-col md:flex-row flex-1"> <div className="flex flex-col md:flex-row flex-1">
<aside className="bg-bunker-600 border-r border-mineshaft-500 w-full md:w-60 h-screen"> <aside className="bg-bunker-600 border-r border-mineshaft-500 w-full md:w-60 h-screen">
<nav> <nav className="flex flex-col justify-between items-between h-full">
<div className="py-6"></div> {/* <div className="py-6"></div> */}
<div className="flex justify-center w-full mt-7 mb-8 bg-bunker-600 w-full h-full flex flex-col items-center px-4"> <div>
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide"> <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">
PROJECT <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> </div>
{workspaceList.length > 0 ? ( <ul>
<Listbox {workspaceList.length > 0 &&
selected={workspaceSelected} menuItems.map(({ href, title, emoji }) => (
onChange={setWorkspaceSelected} <li className="mt-0.5 mx-2" key={title}>
data={workspaceList} {router.asPath.split("/")[1] === href.split("/")[1] &&
buttonAction={openModal} (["project", "billing", "org", "personal"].includes(
text="" router.asPath.split("/")[2]
workspaceMapping={workspaceMapping} )
/> ? router.asPath.split("/")[2] === href.split("/")[2]
) : ( : true) ? (
<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}>
<div <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> <p className="w-10 flex items-center justify-center text-lg">{emoji}</p>
{title} {title}
</div> </div>
</Link> ) : (
)} <Link href={href}>
</li> <div
))} className={`flex p-2.5 text-white text-sm rounded cursor-pointer hover:bg-primary-50/5`}
</ul> >
<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> </nav>
</aside> </aside>
<AddWorkspaceDialog <AddWorkspaceDialog
+1 -1
View File
@@ -88,7 +88,7 @@ const UserTable = ({
}, [userData, myUser]); }, [userData, myUser]);
const grantAccess = async (id, publicKey) => { const grantAccess = async (id, publicKey) => {
let result = await getLatestFileKey(router.query.id); let result = await getLatestFileKey({workspaceId: router.query.id});
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
+1 -1
View File
@@ -61,7 +61,7 @@ export default function Plan({ plan }) {
</button> </button>
</div> </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"> <div className="relative z-10 text-gray-400 font-semibold hover:text-primary duration-200 cursor-pointer mb-0.5">
{plan.buttonTextSecondary} {plan.buttonTextSecondary}
</div> </div>
@@ -1,4 +1,4 @@
import React, { useRef } from "react"; import React, { SyntheticEvent, useRef } from "react";
import { faCircle } from "@fortawesome/free-solid-svg-icons"; import { faCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -6,6 +6,15 @@ import guidGenerator from "../utilities/randomId";
const REGEX = /([$]{.*?})/g; 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 * This component renders the input fields on the dashboard
* @param {object} obj - the order number of a keyPair * @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.type - whether the input field is for a Key Name or for a Key Value
* @param {string} obj.value - value of the InputField * @param {string} obj.value - value of the InputField
* @param {boolean} obj.blurred - whether the input field should be blurred (behind the gray dots) or not; this can be turned on/off in the dashboard * @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 * @returns
*/ */
const DashboardInputField = ({ const DashboardInputField = ({
index, index,
onChangeHandler, onChangeHandler,
type, type,
value, value,
blurred, blurred,
duplicates duplicates,
}) => { }: DashboardInputFieldProps) => {
const ref = useRef(null); const ref = useRef<HTMLDivElement | null>(null);
const syncScroll = (e) => { const syncScroll = (e: SyntheticEvent<HTMLDivElement>) => {
ref.current.scrollTop = e.target.scrollTop; if (ref.current == null) return;
ref.current.scrollLeft = e.target.scrollLeft;
ref.current.scrollTop = e.currentTarget.scrollTop;
ref.current.scrollLeft = e.currentTarget.scrollLeft;
}; };
if (type === "varName") { if (type === "varName") {
const startsWithNumber = !isNaN(value.charAt(0)) && value != ""; const startsWithNumber = !isNaN(Number(value.charAt(0))) && value != "";
const hasDuplicates = duplicates?.includes(value); const hasDuplicates = duplicates?.includes(value);
const error = startsWithNumber || hasDuplicates; const error = startsWithNumber || hasDuplicates;
return ( return (
<div className="flex-col w-full"> <div className="flex-col w-full">
<div <div
className={`group relative flex flex-col justify-center w-full max-w-2xl border ${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 <input
onChange={(e) => onChangeHandler(e.target.value.toUpperCase(), index)} onChange={(e) =>
onChangeHandler(e.target.value.toUpperCase(), index)
}
type={type} type={type}
value={value} value={value}
className={`asolute z-10 peer font-mono ph-no-capture bg-bunker-800 rounded-md caret-white text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-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" spellCheck="false"
/> />
</div> </div>
{startsWithNumber && {startsWithNumber && (
<p className="text-red text-xs mt-0.5 mx-1 mb-2 max-w-xs"> <p className="text-red text-xs mt-0.5 mx-1 mb-2 max-w-xs">
Should not start with a number Should not start with a number
</p> </p>
} )}
{hasDuplicates && !startsWithNumber && {hasDuplicates && !startsWithNumber && (
<p className="text-red text-xs mt-0.5 mx-1 mb-2 max-w-xs"> <p className="text-red text-xs mt-0.5 mx-1 mb-2 max-w-xs">
Secret names should be unique Secret names should be unique
</p> </p>
} )}
</div> </div>
); );
} else if (type === "value") { } 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`} } 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" spellCheck="false"
/> />
<div <div
ref={ref} ref={ref}
className={`${ className={`${
blurred blurred
? "text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-400 peer-active:text-gray-400" ? "text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-400 peer-active:text-gray-400"
: "" : ""
} 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`} } 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) => {
value if (word.match(REGEX) !== null) {
.split(REGEX) return (
.map((word, id) => { <span className="ph-no-capture text-yellow" key={id}>
if (word.match(REGEX) !== null) { {word.slice(0, 2)}
return ( <span className="ph-no-capture text-yellow-200/80">
<span className="ph-no-capture text-yellow" key={id}> {word.slice(2, word.length - 1)}
{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> </span>
); {word.slice(word.length - 1, word.length) == "}" ? (
} else { <span className="ph-no-capture text-yellow">
return <span key={id} className="ph-no-capture">{word}</span>; {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> </div>
{blurred && ( {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="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"> <div className="px-2 flex flex-row items-center overflow-x-scroll no-scrollbar no-scrollbar::-webkit-scrollbar">
{value {value.split("").map(() => (
.split("") <FontAwesomeIcon
.map(() => ( key={guidGenerator()}
<FontAwesomeIcon className="text-xxs mx-0.5"
key={guidGenerator()} icon={faCircle}
className="text-xxs mx-0.5" />
icon={faCircle} ))}
/>
))}
</div> </div>
</div> </div>
)} )}
</div> </div>
</div> </div>
); );
} }
return <>Something Wrong</>;
}; };
export default React.memo(DashboardInputField); 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 Image from "next/image";
import { faUpload } from "@fortawesome/free-solid-svg-icons"; import { faUpload } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -8,27 +8,37 @@ import Error from "../basic/Error";
import parse from "../utilities/file"; import parse from "../utilities/file";
import guidGenerator from "../utilities/randomId"; 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 = ({ const DropZone = ({
setData, setData,
setErrorDragAndDrop, setErrorDragAndDrop,
createNewFile, createNewFile,
errorDragAndDrop, errorDragAndDrop,
addPresetRow,
setButtonReady, setButtonReady,
keysExist, keysExist,
numCurrentRows, numCurrentRows,
}) => { }: DropZoneProps) => {
const handleDragEnter = (e) => { const handleDragEnter = (e: DragEvent) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
}; };
const handleDragLeave = (e) => { const handleDragLeave = (e: DragEvent) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
}; };
const handleDragOver = (e) => { const handleDragOver = (e: DragEvent) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -39,22 +49,25 @@ const DropZone = ({
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
// This function function immediately parses the file after it is dropped // This function function immediately parses the file after it is dropped
const handleDrop = async (e) => { const handleDrop = async (e: DragEvent) => {
setLoading(true); setLoading(true);
setTimeout(() => setLoading(false), 5000); setTimeout(() => setLoading(false), 5000);
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
e.dataTransfer.dropEffect = "copy"; e.dataTransfer.dropEffect = "copy";
var file = e.dataTransfer.files[0], const file = e.dataTransfer.files[0];
reader = new FileReader(); const reader = new FileReader();
reader.onload = function (event) {
const keyPairs = parse(event.target.result); 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) => [ const newData = Object.keys(keyPairs).map((key, index) => [
guidGenerator(), guidGenerator(),
numCurrentRows + index, numCurrentRows + index,
key, key,
keyPairs[key], keyPairs[key as keyof typeof keyPairs],
"shared", "shared",
]); ]);
setData(newData); 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) // 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); setLoading(true);
setTimeout(() => setLoading(false), 5000); setTimeout(() => setLoading(false), 5000);
var file = e.target.files[0], if (e.currentTarget.files === null) return;
reader = new FileReader(); const file = e.currentTarget.files[0];
reader.onload = function (event) { const reader = new FileReader();
const newData = event.target.result reader.onload = (event) => {
.split("\n") if (event.target === null || event.target.result === null) return;
.map((line, index) => [ const { result } = event.target;
guidGenerator(), if (typeof result === "string") {
numCurrentRows + index, const newData = result
line.split("=")[0], .split("\n")
line.split("=").slice(1, line.split("=").length).join("="), .map((line: string, index: number) => [
"shared", guidGenerator(),
]); numCurrentRows + index,
setData(newData); line.split("=")[0],
setButtonReady(true); line.split("=").slice(1, line.split("=").length).join("="),
"shared",
]);
setData(newData);
setButtonReady(true);
}
}; };
reader.readAsText(file); reader.readAsText(file);
}; };
@@ -105,17 +123,17 @@ const DropZone = ({
) : keysExist ? ( ) : keysExist ? (
<div <div
className="opacity-60 hover:opacity-100 duration-200 relative bg-bunker outline max-w-[calc(100%-1rem)] w-full outline-dashed outline-gray-600 rounded-md outline-2 flex flex-col items-center justify-center mb-16 mx-auto mt-1 py-8 px-2" className="opacity-60 hover:opacity-100 duration-200 relative bg-bunker outline max-w-[calc(100%-1rem)] w-full outline-dashed outline-gray-600 rounded-md outline-2 flex flex-col items-center justify-center mb-16 mx-auto mt-1 py-8 px-2"
onDragEnter={(e) => handleDragEnter(e)} onDragEnter={handleDragEnter}
onDragOver={(e) => handleDragOver(e)} onDragOver={handleDragOver}
onDragLeave={(e) => handleDragLeave(e)} onDragLeave={handleDragLeave}
onDrop={(e) => handleDrop(e)} onDrop={handleDrop}
> >
<input <input
id="fileSelect" id="fileSelect"
type="file" type="file"
className="opacity-0 absolute w-full h-full" className="opacity-0 absolute w-full h-full"
accept=".txt,.env" accept=".txt,.env"
onChange={(e) => handleFileSelect(e)} onChange={handleFileSelect}
/> />
{errorDragAndDrop ? ( {errorDragAndDrop ? (
<div className="my-3 max-w-xl opacity-80"></div> <div className="my-3 max-w-xl opacity-80"></div>
@@ -142,10 +160,10 @@ const DropZone = ({
) : ( ) : (
<div <div
className="opacity-80 hover:opacity-100 duration-200 relative bg-bunker outline max-w-2xl w-full outline-dashed outline-gray-700 rounded-md outline-2 flex flex-col items-center justify-center pt-16 mb-16 px-4" className="opacity-80 hover:opacity-100 duration-200 relative bg-bunker outline max-w-2xl w-full outline-dashed outline-gray-700 rounded-md outline-2 flex flex-col items-center justify-center pt-16 mb-16 px-4"
onDragEnter={(e) => handleDragEnter(e)} onDragEnter={handleDragEnter}
onDragOver={(e) => handleDragOver(e)} onDragOver={handleDragOver}
onDragLeave={(e) => handleDragLeave(e)} onDragLeave={handleDragLeave}
onDrop={(e) => handleDrop(e)} onDrop={handleDrop}
> >
<FontAwesomeIcon icon={faUpload} className="text-7xl mb-8" /> <FontAwesomeIcon icon={faUpload} className="text-7xl mb-8" />
<p className="">Drag and drop your .env file here.</p> <p className="">Drag and drop your .env file here.</p>
@@ -154,7 +172,7 @@ const DropZone = ({
type="file" type="file"
className="opacity-0 absolute w-full h-full" className="opacity-0 absolute w-full h-full"
accept=".txt,.env" accept=".txt,.env"
onChange={(e) => handleFileSelect(e)} onChange={handleFileSelect}
/> />
<div className="flex flex-row w-full items-center justify-center mb-6 mt-5"> <div className="flex flex-row w-full items-center justify-center mb-6 mt-5">
<div className="border-t border-gray-700 w-1/5"></div> <div className="border-t border-gray-700 w-1/5"></div>
@@ -47,27 +47,44 @@ const supportOptions = [
], ],
]; ];
export default function Navbar({ onButtonPressed }) { export interface ICurrentOrg {
const router = useRouter(); name: string;
const [user, setUser] = useState({}); }
const [orgs, setOrgs] = useState([]);
const [currentOrg, setCurrentOrg] = useState([]);
useEffect(async () => { export interface IUser {
const userData = await getUser(); firstName: string;
setUser(userData); lastName: string;
const orgsData = await getOrganizations(); email: string;
setOrgs(orgsData); }
const currentOrg = await getOrganization({
orgId: localStorage.getItem("orgData.id"), /**
}); * This is the navigation bar in the main app.
setCurrentOrg(currentOrg); * 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 () => { const closeApp = async () => {
console.log("Logging out..."); console.log("Logging out...");
await logout(); await logout();
router.push("/"); router.push("/login");
}; };
return ( return (
@@ -108,7 +125,7 @@ export default function Navbar({ onButtonPressed }) {
key={guidGenerator()} key={guidGenerator()}
target="_blank" target="_blank"
rel="noopener" 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" 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"> <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"> <div className="flex flex-col items-start px-1 mt-3 mb-2">
{orgs {orgs
.filter( .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 <div
key={guidGenerator()} key={guidGenerator()}
onClick={() => { onClick={() => {
@@ -1,29 +1,37 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { faCcMastercard, faCcVisa } from "@fortawesome/free-brands-svg-icons";
import { import {
faAngleRight, faAngleRight,
faQuestionCircle,
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import { faCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import getOrganization from "~/pages/api/organization/GetOrg"; 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 [orgName, setOrgName] = useState("");
const [workspaceName, setWorkspaceName] = useState(""); const [workspaceName, setWorkspaceName] = useState("");
const router = useRouter(); const router = useRouter();
useEffect(() => { useEffect(() => {
(async () => { (async () => {
let org = await getOrganization({ const orgId = localStorage.getItem("orgData.id")
orgId: localStorage.getItem("orgData.id"), const org = await getOrganization({
orgId: orgId ? orgId : "",
}); });
setOrgName(org.name); setOrgName(org.name);
let workspace = await getWorkspaceInfo({
workspaceId: router.query.id, const workspace = await getProjectInfo({
projectId: String(router.query.id),
}); });
setWorkspaceName(workspace.name); setWorkspaceName(workspace.name);
})(); })();
+10 -2
View File
@@ -43,7 +43,7 @@ const attemptLogin = async (
let serverPublicKey, salt; let serverPublicKey, salt;
try { try {
const res = await login1(email, clientPublicKey); let res = await login1(email, clientPublicKey);
res = await res.json(); res = await res.json();
serverPublicKey = res.serverPublicKey; serverPublicKey = res.serverPublicKey;
salt = res.salt; salt = res.salt;
@@ -137,7 +137,15 @@ const attemptLogin = async (
await pushKeys( await pushKeys(
{ {
DATABASE_URL: [ 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", "personal",
], ],
TWILIO_AUTH_TOKEN: [ TWILIO_AUTH_TOKEN: [
@@ -108,11 +108,7 @@ const changePassword = async (
} }
); );
} catch (error) { } catch (error) {
console.log( console.log("Something went wrong during changing the password");
"Something went wrong during changing the password",
slat,
serverPublicKey
);
} }
return true; return true;
}; };
@@ -7,20 +7,30 @@ import { envMapping } from "../../../public/data/frequentConstants";
const crypto = require("crypto"); const crypto = require("crypto");
const { const {
decryptAssymmetric, decryptAssymmetric,
decryptSymmetric,
encryptSymmetric, encryptSymmetric,
encryptAssymmetric, encryptAssymmetric,
} = require("../cryptography/crypto"); } = require("../cryptography/crypto");
const nacl = require("tweetnacl"); const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util"); nacl.util = require("tweetnacl-util");
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"); const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
let randomBytes; let randomBytes: string;
if (Object.keys(sharedKey).length > 0) { if (Object.keys(sharedKey).length > 0) {
// case: a (shared) key exists for the workspace // case: a (shared) key exists for the workspace
randomBytes = decryptAssymmetric({ randomBytes = decryptAssymmetric({
@@ -51,11 +61,11 @@ const pushKeys = async (obj, workspaceId, env) => {
iv: ivValue, iv: ivValue,
tag: tagValue, tag: tagValue,
} = encryptSymmetric({ } = encryptSymmetric({
plaintext: obj[key][0], plaintext: obj[key as keyof typeof obj][0],
key: randomBytes, 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 { return {
ciphertextKey, ciphertextKey,
@@ -65,7 +75,7 @@ const pushKeys = async (obj, workspaceId, env) => {
ciphertextValue, ciphertextValue,
ivValue, ivValue,
tagValue, 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, type: visibility,
}; };
}); });
@@ -76,7 +86,7 @@ const pushKeys = async (obj, workspaceId, env) => {
}); });
// assymmetrically encrypt key with each receiver public keys // assymmetrically encrypt key with each receiver public keys
const keys = publicKeys.map((k) => { const keys = publicKeys.map((k: IK) => {
const { ciphertext, nonce } = encryptAssymmetric({ const { ciphertext, nonce } = encryptAssymmetric({
plaintext: randomBytes, plaintext: randomBytes,
publicKey: k.publicKey, publicKey: k.publicKey,
@@ -95,7 +105,7 @@ const pushKeys = async (obj, workspaceId, env) => {
workspaceId, workspaceId,
secrets, secrets,
keys, keys,
environment: envMapping[env], environment: envMapping[env as keyof typeof envMapping],
}); });
}; };
+436 -429
View File
File diff suppressed because it is too large Load Diff
+4 -1
View File
@@ -53,9 +53,12 @@
"devDependencies": { "devDependencies": {
"@tailwindcss/typography": "^0.5.4", "@tailwindcss/typography": "^0.5.4",
"@types/node": "18.11.9", "@types/node": "18.11.9",
"@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0",
"autoprefixer": "^10.4.7", "autoprefixer": "^10.4.7",
"eslint": "^8.28.0", "eslint": "^8.29.0",
"eslint-config-next": "^13.0.5", "eslint-config-next": "^13.0.5",
"eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-simple-import-sort": "^8.0.0", "eslint-plugin-simple-import-sort": "^8.0.0",
"postcss": "^8.4.14", "postcss": "^8.4.14",
"prettier": "2.7.1", "prettier": "2.7.1",
@@ -31,8 +31,8 @@ const issueBackupPrivateKey = ({
if (res.status == 200) { if (res.status == 200) {
return res; return res;
} else { } else {
return res;
console.log("Failed to issue the backup key"); console.log("Failed to issue the backup key");
return res;
} }
}); });
}; };
@@ -3,11 +3,8 @@ import SecurityClient from "~/utilities/SecurityClient";
/** /**
* This route logs the user out. Note: the user should authorized to do this. * This route logs the user out. Note: the user should authorized to do this.
* We first try to log out - if the authorization fails (response.status = 401), we refetch the new token, and then retry * We first try to log out - if the authorization fails (response.status = 401), we refetch the new token, and then retry
* @param {*} req
* @param {*} res
* @returns
*/ */
const logout = async (req, res) => { const logout = async () => {
return SecurityClient.fetchCall("/api/v1/auth/logout", { return SecurityClient.fetchCall("/api/v1/auth/logout", {
method: "POST", method: "POST",
headers: { headers: {
@@ -15,7 +12,7 @@ const logout = async (req, res) => {
}, },
credentials: "include", credentials: "include",
}).then((res) => { }).then((res) => {
if (res.status == 200) { if (res?.status == 200) {
SecurityClient.setToken(""); SecurityClient.setToken("");
// Delete the cookie by not setting a value; Alternatively clear the local storage // Delete the cookie by not setting a value; Alternatively clear the local storage
localStorage.setItem("publicKey", ""); localStorage.setItem("publicKey", "");
@@ -2,18 +2,17 @@ import SecurityClient from "~/utilities/SecurityClient";
/** /**
* This route lets us get info about a certain org * This route lets us get info about a certain org
* @param {*} req * @param {string} orgId - the organization ID
* @param {*} res
* @returns * @returns
*/ */
const getOrganization = (req, res) => { const getOrganization = ({ orgId }: { orgId: string; }) => {
return SecurityClient.fetchCall("/api/v1/organization/" + req.orgId, { return SecurityClient.fetchCall("/api/v1/organization/" + orgId, {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
}).then(async (res) => { }).then(async (res) => {
if (res.status == 200) { if (res?.status == 200) {
return (await res.json()).organization; return (await res.json()).organization;
} else { } else {
console.log("Failed to get org info"); console.log("Failed to get org info");
@@ -2,13 +2,13 @@ import SecurityClient from "~/utilities/SecurityClient";
/** /**
* This route lets us get all the users in an org. * This route lets us get all the users in an org.
* @param {*} req * @param {object} obj
* @param {*} res * @param {string} obj.orgId - organization Id
* @returns * @returns
*/ */
const getOrganizationUsers = (req, res) => { const getOrganizationUsers = ({ orgId }: { orgId: string; }) => {
return SecurityClient.fetchCall( return SecurityClient.fetchCall(
"/api/v1/organization/" + req.orgId + "/users", "/api/v1/organization/" + orgId + "/users",
{ {
method: "GET", method: "GET",
headers: { headers: {
@@ -16,7 +16,7 @@ const getOrganizationUsers = (req, res) => {
}, },
} }
).then(async (res) => { ).then(async (res) => {
if (res.status == 200) { if (res?.status == 200) {
return (await res.json()).users; return (await res.json()).users;
} else { } else {
console.log("Failed to get org users"); console.log("Failed to get org users");
@@ -2,18 +2,16 @@ import SecurityClient from "~/utilities/SecurityClient";
/** /**
* This route lets us get the all the orgs of a certain user. * This route lets us get the all the orgs of a certain user.
* @param {*} req
* @param {*} res
* @returns * @returns
*/ */
const getOrganizations = (req, res) => { const getOrganizations = () => {
return SecurityClient.fetchCall("/api/v1/organization", { return SecurityClient.fetchCall("/api/v1/organization", {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
}).then(async (res) => { }).then(async (res) => {
if (res.status == 200) { if (res?.status == 200) {
return (await res.json()).organizations; return (await res.json()).organizations;
} else { } else {
console.log("Failed to get orgs of a user"); console.log("Failed to get orgs of a user");
@@ -2,18 +2,15 @@ import SecurityClient from "~/utilities/SecurityClient";
/** /**
* This route gets the information about a specific user. * 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", { return SecurityClient.fetchCall("/api/v1/user", {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
}).then(async (res) => { }).then(async (res) => {
if (res.status == 200) { if (res?.status == 200) {
return (await res.json()).user; return (await res.json()).user;
} else { } else {
console.log("Failed to get user info"); console.log("Failed to get user info");
@@ -1,11 +1,12 @@
import SecurityClient from "~/utilities/SecurityClient"; import SecurityClient from "~/utilities/SecurityClient";
/** /**
* This route creates a new workspace for a user. * This route creates a new workspace for a user within a certain organization.
* @param {*} workspaceName * @param {string} workspaceName - project Name
* @param {string} organizationId - org ID
* @returns * @returns
*/ */
const createWorkspace = (workspaceName, organizationId) => { const createWorkspace = ( { workspaceName, organizationId }: { workspaceName: string; organizationId: string; }) => {
return SecurityClient.fetchCall("/api/v1/workspace", { return SecurityClient.fetchCall("/api/v1/workspace", {
method: "POST", method: "POST",
headers: { headers: {
@@ -16,7 +17,7 @@ const createWorkspace = (workspaceName, organizationId) => {
organizationId: organizationId, organizationId: organizationId,
}), }),
}).then(async (res) => { }).then(async (res) => {
if (res.status == 200) { if (res?.status == 200) {
return (await res.json()).workspace; return (await res.json()).workspace;
} else { } else {
console.log("Failed to create a project"); console.log("Failed to create a project");
@@ -2,10 +2,10 @@ import SecurityClient from "~/utilities/SecurityClient";
/** /**
* Get the latest key pairs from a certain workspace * Get the latest key pairs from a certain workspace
* @param {*} workspaceId * @param {string} workspaceId
* @returns * @returns
*/ */
const getLatestFileKey = (workspaceId) => { const getLatestFileKey = ({ workspaceId } : { workspaceId: string; }) => {
return SecurityClient.fetchCall( return SecurityClient.fetchCall(
"/api/v1/key/" + workspaceId + "/latest", "/api/v1/key/" + workspaceId + "/latest",
{ {
@@ -15,7 +15,7 @@ const getLatestFileKey = (workspaceId) => {
}, },
} }
).then(async (res) => { ).then(async (res) => {
if (res.status == 200) { if (res?.status == 200) {
return await res.json(); return await res.json();
} else { } else {
console.log("Failed to get the latest key pairs for a certain project"); console.log("Failed to get the latest key pairs for a certain project");
@@ -2,13 +2,12 @@ import SecurityClient from "~/utilities/SecurityClient";
/** /**
* This route lets us get the information of a certain project. * This route lets us get the information of a certain project.
* @param {*} req * @param {*} projectId - project ID (we renamed workspaces to projects in the app)
* @param {*} res
* @returns * @returns
*/ */
const getWorkspaceInfo = (req, res) => { const getProjectInfo = ({ projectId }: { projectId: string; }) => {
return SecurityClient.fetchCall( return SecurityClient.fetchCall(
"/api/v1/workspace/" + req.workspaceId, "/api/v1/workspace/" + projectId,
{ {
method: "GET", method: "GET",
headers: { headers: {
@@ -16,7 +15,7 @@ const getWorkspaceInfo = (req, res) => {
}, },
} }
).then(async (res) => { ).then(async (res) => {
if (res.status == 200) { if (res?.status == 200) {
return (await res.json()).workspace; return (await res.json()).workspace;
} else { } else {
console.log("Failed to get project info"); console.log("Failed to get project info");
@@ -24,4 +23,4 @@ const getWorkspaceInfo = (req, res) => {
}); });
}; };
export default getWorkspaceInfo; export default getProjectInfo;
@@ -2,13 +2,12 @@ import SecurityClient from "~/utilities/SecurityClient";
/** /**
* This route lets us get the public keys of everyone in your workspace. * This route lets us get the public keys of everyone in your workspace.
* @param {*} req * @param {string} workspaceId
* @param {*} res
* @returns * @returns
*/ */
const getWorkspaceKeys = (req, res) => { const getWorkspaceKeys = ({ workspaceId }: { workspaceId: string; }) => {
return SecurityClient.fetchCall( return SecurityClient.fetchCall(
"/api/v1/workspace/" + req.workspaceId + "/keys", "/api/v1/workspace/" + workspaceId + "/keys",
{ {
method: "GET", method: "GET",
headers: { headers: {
@@ -16,7 +15,7 @@ const getWorkspaceKeys = (req, res) => {
}, },
} }
).then(async (res) => { ).then(async (res) => {
if (res.status == 200) { if (res?.status == 200) {
return (await res.json()).publicKeys; return (await res.json()).publicKeys;
} else { } else {
console.log("Failed to get the public keys of everyone in the workspace"); console.log("Failed to get the public keys of everyone in the workspace");
@@ -2,13 +2,12 @@ import SecurityClient from "~/utilities/SecurityClient";
/** /**
* This route lets us get all the users in the workspace. * This route lets us get all the users in the workspace.
* @param {*} req * @param {string} workspaceId - workspace ID
* @param {*} res
* @returns * @returns
*/ */
const getWorkspaceUsers = (req, res) => { const getWorkspaceUsers = ({ workspaceId }: { workspaceId: string; }) => {
return SecurityClient.fetchCall( return SecurityClient.fetchCall(
"/api/v1/workspace/" + req.workspaceId + "/users", "/api/v1/workspace/" + workspaceId + "/users",
{ {
method: "GET", method: "GET",
headers: { headers: {
@@ -16,7 +15,7 @@ const getWorkspaceUsers = (req, res) => {
}, },
} }
).then(async (res) => { ).then(async (res) => {
if (res.status == 200) { if (res?.status == 200) {
return (await res.json()).users; return (await res.json()).users;
} else { } else {
console.log("Failed to get Project Users"); console.log("Failed to get Project Users");
@@ -1,19 +1,17 @@
import SecurityClient from "~/utilities/SecurityClient"; import SecurityClient from "~/utilities/SecurityClient";
/** /**
* This route lets us get the public keys of everyone in your workspace. * This route lets us get the workspaces of a certain user
* @param {*} req
* @param {*} res
* @returns * @returns
*/ */
const getWorkspaces = (req, res) => { const getWorkspaces = () => {
return SecurityClient.fetchCall("/api/v1/workspace", { return SecurityClient.fetchCall("/api/v1/workspace", {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
}).then(async (res) => { }).then(async (res) => {
if (res.status == 200) { if (res?.status == 200) {
return (await res.json()).workspaces; return (await res.json()).workspaces;
} else { } else {
console.log("Failed to get projects"); console.log("Failed to get projects");
+2 -2
View File
@@ -378,9 +378,9 @@ export default function Dashboard() {
} else if (duplicatesExist) { } else if (duplicatesExist) {
console.log("Remove the duplicated entries first!"); console.log("Remove the duplicated entries first!");
} else { } else {
// Once "Save changed is clicked", disable that button // Once "Save changes is clicked", disable that button
setButtonReady(false); 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 * Check which integrations are active for this project and environment
+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;
+4 -2
View File
@@ -197,7 +197,7 @@ export default function SignUp() {
}, },
async () => { async () => {
client.createVerifier(async (err, result) => { client.createVerifier(async (err, result) => {
const response = await completeAccountInformationSignup({ let response = await completeAccountInformationSignup({
email, email,
firstName, firstName,
lastName, lastName,
@@ -495,7 +495,9 @@ export default function SignUp() {
setBackupKeyError, setBackupKeyError,
setBackupKeyIssued, setBackupKeyIssued,
}); });
router.push("/dashboard/"); const userWorkspaces = await getWorkspaces();
let userWorkspace = userWorkspaces[0]._id;
router.push("/home/" + userWorkspace);
}} }}
size="lg" size="lg"
/> />
+2 -2
View File
@@ -92,7 +92,7 @@ export default function SignupInvite() {
}, },
async () => { async () => {
client.createVerifier(async (err, result) => { client.createVerifier(async (err, result) => {
const response = await completeAccountInformationSignupInvite({ let response = await completeAccountInformationSignupInvite({
email, email,
firstName, firstName,
lastName, lastName,
@@ -291,7 +291,7 @@ export default function SignupInvite() {
<div className="py-2"></div> <div className="py-2"></div>
)} )}
</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 <Button
text="Sign Up" text="Sign Up"
onButtonPressed={() => { onButtonPressed={() => {
+22
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>
+23
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/
+16
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"
@@ -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
@@ -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
@@ -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 }}
@@ -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
@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: infisical
+76
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/