mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-25 14:05:03 +00:00
Merge remote-tracking branch 'upstream/main' into feat/39
This commit is contained in:
22
README.md
22
README.md
@ -48,11 +48,11 @@
|
||||
|
||||
And more.
|
||||
|
||||
## Get started
|
||||
## 🚀 Get started
|
||||
|
||||
To quickly get started, visit our [get started guide](https://infisical.com/docs/getting-started/introduction).
|
||||
|
||||
## What's cool about this?
|
||||
## 🔥 What's cool about this?
|
||||
|
||||
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>.
|
||||
|
||||
@ -62,20 +62,20 @@ If you care about efficiency and security, then Infisical is right for you.
|
||||
|
||||
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
|
||||
|
||||
Whether it's big or small, we love contributions ❤️ Check out our guide to see how to [get started](https://infisical.com/docs/contributing/overview).
|
||||
|
||||
Not sure where to get started? [Book a free, non-pressure pairing sessions with one of our teammates](mailto:tony@infisical.com?subject=Pairing%20session&body=I'd%20like%20to%20do%20a%20pairing%20session!)!
|
||||
|
||||
## Community & Support
|
||||
## 💚 Community & Support
|
||||
|
||||
- [Slack](https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g) (For live discussion with the community and the Infisical team)
|
||||
- [GitHub Discussions](https://github.com/Infisical/infisical/discussions) (For help with building and deeper conversations about features)
|
||||
- [GitHub Issues](https://github.com/Infisical/infisical-cli/issues) (For any bugs and errors you encounter using Infisical)
|
||||
- [Twitter](https://twitter.com/infisical) (Get news fast)
|
||||
|
||||
## Status
|
||||
## 🐥 Status
|
||||
|
||||
- [x] Public Alpha: Anyone can sign up over at [infisical.com](https://infisical.com) but go easy on us, there are kinks and we're just getting started.
|
||||
- [ ] Public Beta: Stable enough for most non-enterprise use-cases.
|
||||
@ -83,13 +83,13 @@ Not sure where to get started? [Book a free, non-pressure pairing sessions with
|
||||
|
||||
We're currently in Public Alpha.
|
||||
|
||||
## Stay Up-to-Date
|
||||
## 🚨 Stay Up-to-Date
|
||||
|
||||
Infisical officially launched as v.1.0 on November 21st, 2022. However, a lot of new features are coming very quickly. Watch **releases** of this repository to be notified about future updates:
|
||||
|
||||

|
||||
|
||||
## Integrations
|
||||
## 🔌 Integrations
|
||||
|
||||
We're currently setting the foundation and building [integrations](https://infisical.com/docs/integrations/overview) so secrets can be synced everywhere. Any help is welcome! :)
|
||||
|
||||
@ -261,15 +261,15 @@ We're currently setting the foundation and building [integrations](https://infis
|
||||
</table>
|
||||
|
||||
|
||||
## Open-source vs. paid
|
||||
## 🏘 Open-source vs. paid
|
||||
|
||||
This repo is entirely MIT licensed, with the exception of the `ee` directory which will contain premium enterprise features requiring a Infisical license in the future. We're currently focused on developing non-enterprise offerings first that should suit most use-cases.
|
||||
|
||||
## Security
|
||||
## 🛡 Security
|
||||
|
||||
Looking to report a security vulnerability? Please don't post about it in GitHub issue. Instead, refer to our [SECURITY.md](./SECURITY.md) file.
|
||||
|
||||
## Contributors 🦸
|
||||
## 🦸 Contributors
|
||||
|
||||
[//]: contributor-faces
|
||||
|
||||
@ -277,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/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>
|
||||
<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/asharonbaltazar"><img src="https://avatars.githubusercontent.com/u/58940073?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,19 +1,27 @@
|
||||
import React from "react";
|
||||
import { useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import {
|
||||
faCircle,
|
||||
faCircleExclamation,
|
||||
faE,
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { faCircle, faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import guidGenerator from "../utilities/randomId";
|
||||
import Error from "./Error";
|
||||
|
||||
const InputField = (props) => {
|
||||
interface InputFieldProps {
|
||||
static?: boolean;
|
||||
label: string;
|
||||
type: string;
|
||||
value: string;
|
||||
placeholder?: string;
|
||||
isRequired: boolean;
|
||||
disabled?: boolean;
|
||||
error?: boolean;
|
||||
text?: string;
|
||||
name?: string;
|
||||
blurred?: boolean;
|
||||
errorText?: string;
|
||||
onChangeHandler: (value: string) => void;
|
||||
}
|
||||
|
||||
const InputField = (props: InputFieldProps) => {
|
||||
const [passwordVisible, setPasswordVisible] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
@ -67,7 +75,7 @@ const InputField = (props) => {
|
||||
>
|
||||
<input
|
||||
onChange={(e) => props.onChangeHandler(e.target.value)}
|
||||
type={passwordVisible == false ? props.type : "text"}
|
||||
type={passwordVisible === false ? props.type : "text"}
|
||||
placeholder={props.placeholder}
|
||||
value={props.value}
|
||||
required={props.isRequired}
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-unexpected-multiline */
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
@ -23,6 +24,7 @@ import getWorkspaces from "~/pages/api/workspace/getWorkspaces";
|
||||
import uploadKeys from "~/pages/api/workspace/uploadKeys";
|
||||
|
||||
import NavBarDashboard from "../navigation/NavBarDashboard";
|
||||
import { tempLocalStorage } from "../utilities/checks/tempLocalStorage";
|
||||
import {
|
||||
decryptAssymmetric,
|
||||
encryptAssymmetric,
|
||||
@ -31,13 +33,17 @@ import Button from "./buttons/Button";
|
||||
import AddWorkspaceDialog from "./dialog/AddWorkspaceDialog";
|
||||
import Listbox from "./Listbox";
|
||||
|
||||
export default function Layout({ children }) {
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function Layout({ children }: LayoutProps) {
|
||||
const router = useRouter();
|
||||
const [workspaceList, setWorkspaceList] = useState([]);
|
||||
const [workspaceMapping, setWorkspaceMapping] = useState([{ 1: 2 }]);
|
||||
const [workspaceSelected, setWorkspaceSelected] = useState("∞");
|
||||
let [newWorkspaceName, setNewWorkspaceName] = useState("");
|
||||
let [isOpen, setIsOpen] = useState(false);
|
||||
const [newWorkspaceName, setNewWorkspaceName] = useState("");
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
@ -47,164 +53,186 @@ export default function Layout({ children }) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
|
||||
function openModal() {
|
||||
setIsOpen(true);
|
||||
}
|
||||
|
||||
// TODO: what to do about the fact that 2ids can have the same name
|
||||
|
||||
/**
|
||||
* When a user creates a new workspace, redirect them to the page of the new workspace.
|
||||
* @param {*} workspaceName
|
||||
*/
|
||||
async function submitModal(workspaceName, addAllUsers) {
|
||||
async function submitModal(workspaceName: string, addAllUsers: boolean) {
|
||||
setLoading(true);
|
||||
// timeout code.
|
||||
setTimeout(() => setLoading(false), 1500);
|
||||
const workspaces = await getWorkspaces();
|
||||
const currentWorkspaces = workspaces.map((workspace) => workspace.name);
|
||||
if (!currentWorkspaces.includes(workspaceName)) {
|
||||
const newWorkspace = await createWorkspace({
|
||||
workspaceName,
|
||||
organizationId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
let newWorkspaceId;
|
||||
try {
|
||||
newWorkspaceId = newWorkspace._id;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
if (addAllUsers) {
|
||||
let orgUsers = await getOrganizationUsers({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
|
||||
try {
|
||||
const workspaces = await getWorkspaces();
|
||||
const currentWorkspaces = workspaces.map((workspace) => workspace.name);
|
||||
if (!currentWorkspaces.includes(workspaceName)) {
|
||||
const newWorkspace = await createWorkspace({
|
||||
workspaceName,
|
||||
organizationId: tempLocalStorage("orgData.id"),
|
||||
});
|
||||
orgUsers.map(async (user) => {
|
||||
if (user.status == "accepted") {
|
||||
let result = await addUserToWorkspace(
|
||||
user.user.email,
|
||||
newWorkspaceId
|
||||
);
|
||||
if (result?.invitee && result?.latestKey) {
|
||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
||||
const newWorkspaceId = newWorkspace._id;
|
||||
|
||||
// assymmetrically decrypt symmetric key with local private key
|
||||
const key = decryptAssymmetric({
|
||||
ciphertext: result.latestKey.encryptedKey,
|
||||
nonce: result.latestKey.nonce,
|
||||
publicKey: result.latestKey.sender.publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
if (addAllUsers) {
|
||||
const orgUsers = await getOrganizationUsers({
|
||||
orgId: tempLocalStorage("orgData.id"),
|
||||
});
|
||||
orgUsers.map(async (user: any) => {
|
||||
if (user.status == "accepted") {
|
||||
const result = await addUserToWorkspace(
|
||||
user.user.email,
|
||||
newWorkspaceId
|
||||
);
|
||||
if (result?.invitee && result?.latestKey) {
|
||||
const PRIVATE_KEY = tempLocalStorage("PRIVATE_KEY");
|
||||
|
||||
const { ciphertext, nonce } = encryptAssymmetric({
|
||||
plaintext: key,
|
||||
publicKey: result.invitee.publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
// assymmetrically decrypt symmetric key with local private key
|
||||
const key = decryptAssymmetric({
|
||||
ciphertext: result.latestKey.encryptedKey,
|
||||
nonce: result.latestKey.nonce,
|
||||
publicKey: result.latestKey.sender.publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
|
||||
uploadKeys(newWorkspaceId, result.invitee._id, ciphertext, nonce);
|
||||
const { ciphertext, nonce } = encryptAssymmetric({
|
||||
plaintext: key,
|
||||
publicKey: result.invitee.publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
}) as { ciphertext: string; nonce: string };
|
||||
|
||||
uploadKeys(
|
||||
newWorkspaceId,
|
||||
result.invitee._id,
|
||||
ciphertext,
|
||||
nonce
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
router.push("/dashboard/" + newWorkspaceId + "?Development");
|
||||
setIsOpen(false);
|
||||
setNewWorkspaceName("");
|
||||
} else {
|
||||
console.error("A project with this name already exists.");
|
||||
setError(true);
|
||||
setLoading(false);
|
||||
}
|
||||
router.push("/dashboard/" + newWorkspaceId + "?Development");
|
||||
setIsOpen(false);
|
||||
setNewWorkspaceName("");
|
||||
} else {
|
||||
setError(t("error_project-already-exists"));
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setError(true);
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
function openModal() {
|
||||
setIsOpen(true);
|
||||
}
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
href:
|
||||
"/dashboard/" + workspaceMapping[workspaceSelected] + "?Development",
|
||||
"/dashboard/" +
|
||||
workspaceMapping[workspaceSelected as any] +
|
||||
"?Development",
|
||||
title: t("nav:menu.secrets"),
|
||||
emoji: <FontAwesomeIcon icon={faKey} />,
|
||||
},
|
||||
{
|
||||
href: "/users/" + workspaceMapping[workspaceSelected],
|
||||
href: "/users/" + workspaceMapping[workspaceSelected as any],
|
||||
title: t("nav:menu.members"),
|
||||
emoji: <FontAwesomeIcon icon={faUser} />,
|
||||
},
|
||||
{
|
||||
href: "/integrations/" + workspaceMapping[workspaceSelected],
|
||||
href: "/integrations/" + workspaceMapping[workspaceSelected as any],
|
||||
title: t("nav:menu.integrations"),
|
||||
emoji: <FontAwesomeIcon icon={faPlug} />,
|
||||
},
|
||||
{
|
||||
href: "/settings/project/" + workspaceMapping[workspaceSelected],
|
||||
href: "/settings/project/" + workspaceMapping[workspaceSelected as any],
|
||||
title: t("nav:menu.project-settings"),
|
||||
emoji: <FontAwesomeIcon icon={faGear} />,
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(async () => {
|
||||
useEffect(() => {
|
||||
// Put a user in a workspace if they're not in one yet
|
||||
if (
|
||||
localStorage.getItem("orgData.id") == null ||
|
||||
localStorage.getItem("orgData.id") == ""
|
||||
) {
|
||||
const userOrgs = await getOrganizations();
|
||||
localStorage.setItem("orgData.id", userOrgs[0]._id);
|
||||
}
|
||||
|
||||
let orgUserProjects = await getOrganizationUserProjects({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
let userWorkspaces = orgUserProjects;
|
||||
if (
|
||||
userWorkspaces.length == 0 &&
|
||||
router.asPath != "/noprojects" &&
|
||||
!router.asPath.includes("settings")
|
||||
) {
|
||||
router.push("/noprojects");
|
||||
} else if (router.asPath != "/noprojects") {
|
||||
const intendedWorkspaceId = router.asPath
|
||||
.split("/")
|
||||
[router.asPath.split("/").length - 1].split("?")[0];
|
||||
// If a user is not a member of a workspace they are trying to access, just push them to one of theirs
|
||||
if (
|
||||
intendedWorkspaceId != "heroku" &&
|
||||
!userWorkspaces
|
||||
.map((workspace) => workspace._id)
|
||||
.includes(intendedWorkspaceId)
|
||||
) {
|
||||
router.push("/dashboard/" + userWorkspaces[0]._id + "?Development");
|
||||
} else {
|
||||
setWorkspaceList(userWorkspaces.map((workspace) => workspace.name));
|
||||
setWorkspaceMapping(
|
||||
Object.fromEntries(
|
||||
userWorkspaces.map((workspace) => [workspace.name, workspace._id])
|
||||
)
|
||||
);
|
||||
setWorkspaceSelected(
|
||||
Object.fromEntries(
|
||||
userWorkspaces.map((workspace) => [workspace._id, workspace.name])
|
||||
)[
|
||||
router.asPath
|
||||
.split("/")
|
||||
[router.asPath.split("/").length - 1].split("?")[0]
|
||||
]
|
||||
);
|
||||
const putUserInWorkSpace = async () => {
|
||||
if (tempLocalStorage("orgData.id") === "") {
|
||||
const userOrgs = await getOrganizations();
|
||||
localStorage.setItem("orgData.id", userOrgs[0]._id);
|
||||
}
|
||||
}
|
||||
|
||||
const orgUserProjects = await getOrganizationUserProjects({
|
||||
orgId: tempLocalStorage("orgData.id"),
|
||||
});
|
||||
const userWorkspaces = orgUserProjects;
|
||||
if (
|
||||
userWorkspaces.length == 0 &&
|
||||
router.asPath != "/noprojects" &&
|
||||
!router.asPath.includes("settings")
|
||||
) {
|
||||
router.push("/noprojects");
|
||||
} else if (router.asPath != "/noprojects") {
|
||||
const intendedWorkspaceId = router.asPath
|
||||
.split("/")
|
||||
[router.asPath.split("/").length - 1].split("?")[0];
|
||||
// If a user is not a member of a workspace they are trying to access, just push them to one of theirs
|
||||
if (
|
||||
intendedWorkspaceId != "heroku" &&
|
||||
!userWorkspaces
|
||||
.map((workspace: { _id: string }) => workspace._id)
|
||||
.includes(intendedWorkspaceId)
|
||||
) {
|
||||
router.push("/dashboard/" + userWorkspaces[0]._id + "?Development");
|
||||
} else {
|
||||
setWorkspaceList(
|
||||
userWorkspaces.map((workspace: any) => workspace.name)
|
||||
);
|
||||
setWorkspaceMapping(
|
||||
Object.fromEntries(
|
||||
userWorkspaces.map((workspace: any) => [
|
||||
workspace.name,
|
||||
workspace._id,
|
||||
])
|
||||
) as any
|
||||
);
|
||||
setWorkspaceSelected(
|
||||
Object.fromEntries(
|
||||
userWorkspaces.map((workspace: any) => [
|
||||
workspace._id,
|
||||
workspace.name,
|
||||
])
|
||||
)[
|
||||
router.asPath
|
||||
.split("/")
|
||||
[router.asPath.split("/").length - 1].split("?")[0]
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
putUserInWorkSpace();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
if (
|
||||
workspaceMapping[workspaceSelected] &&
|
||||
workspaceMapping[workspaceSelected] !==
|
||||
workspaceMapping[Number(workspaceSelected)] &&
|
||||
`${workspaceMapping[Number(workspaceSelected)]}` !==
|
||||
router.asPath
|
||||
.split("/")
|
||||
[router.asPath.split("/").length - 1].split("?")[0]
|
||||
) {
|
||||
router.push(
|
||||
"/dashboard/" + workspaceMapping[workspaceSelected] + "?Development"
|
||||
"/dashboard/" +
|
||||
workspaceMapping[Number(workspaceSelected)] +
|
||||
"?Development"
|
||||
);
|
||||
localStorage.setItem(
|
||||
"projectData.id",
|
||||
workspaceMapping[workspaceSelected]
|
||||
`${workspaceMapping[Number(workspaceSelected)]}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
@ -221,18 +249,18 @@ export default function Layout({ children }) {
|
||||
<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="flex justify-center w-full mt-[4.5rem] mb-6 bg-bunker-600 h-20 flex-col items-center px-4">
|
||||
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
|
||||
{t("nav:menu.project")}
|
||||
</div>
|
||||
{workspaceList.length > 0 ? (
|
||||
<Listbox
|
||||
selected={workspaceSelected}
|
||||
onChange={setWorkspaceSelected}
|
||||
onChange={setWorkspaceSelected as any}
|
||||
data={workspaceList}
|
||||
buttonAction={openModal}
|
||||
text=""
|
||||
workspaceMapping={workspaceMapping}
|
||||
// workspaceMapping={workspaceMapping as any}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
@ -300,7 +328,9 @@ export default function Layout({ children }) {
|
||||
Infisical Guide
|
||||
</div>
|
||||
) : (
|
||||
<Link href={`/home/` + workspaceMapping[workspaceSelected]}>
|
||||
<Link
|
||||
href={`/home/` + workspaceMapping[workspaceSelected as any]}
|
||||
>
|
||||
<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`}
|
||||
>
|
||||
@ -326,7 +356,7 @@ export default function Layout({ children }) {
|
||||
<main className="flex-1 bg-bunker-800">{children}</main>
|
||||
</div>
|
||||
</div>
|
||||
<div className="block md:hidden bg-bunker-800 w-screen h-screen flex flex-col justify-center items-center">
|
||||
<div className="md:hidden bg-bunker-800 w-screen h-screen flex flex-col justify-center items-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faMobile}
|
||||
className="text-gray-300 text-7xl mb-8"
|
@ -8,14 +8,13 @@ import {
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
|
||||
|
||||
type ListBoxProps = {
|
||||
selected: string,
|
||||
onChange: () => void,
|
||||
data: string[],
|
||||
text: string,
|
||||
buttonAction: () => void,
|
||||
width: string,
|
||||
interface ListBoxProps {
|
||||
selected: string;
|
||||
onChange: () => void;
|
||||
data: string[];
|
||||
text: string;
|
||||
buttonAction: () => void;
|
||||
isFull?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -35,14 +34,14 @@ export default function ListBox({
|
||||
data,
|
||||
text,
|
||||
buttonAction,
|
||||
width,
|
||||
} : ListBoxProps): JSX.Element {
|
||||
isFull,
|
||||
}: ListBoxProps): JSX.Element {
|
||||
return (
|
||||
<Listbox value={selected} onChange={onChange}>
|
||||
<div className="relative">
|
||||
<Listbox.Button
|
||||
className={`text-gray-400 relative ${
|
||||
width == "full" ? "w-full" : "w-52"
|
||||
isFull ? "w-full" : "w-52"
|
||||
} cursor-default rounded-md bg-white/[0.07] hover:bg-white/[0.11] duration-200 py-2.5 pl-3 pr-10 text-left shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm`}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
|
@ -12,7 +12,7 @@ type ButtonProps = {
|
||||
text: string;
|
||||
onButtonPressed: () => void;
|
||||
loading?: boolean;
|
||||
color: string;
|
||||
color?: string;
|
||||
size: string;
|
||||
icon?: IconProp;
|
||||
active?: boolean;
|
||||
|
38
frontend/components/context/Notifications/Notification.tsx
Normal file
38
frontend/components/context/Notifications/Notification.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import { faXmarkCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import classnames from "classnames";
|
||||
|
||||
import { Notification as NotificationType } from "./NotificationProvider";
|
||||
|
||||
interface NotificationProps {
|
||||
notification: NotificationType;
|
||||
clearNotification: (text?: string) => void;
|
||||
}
|
||||
|
||||
const Notification = ({
|
||||
notification,
|
||||
clearNotification,
|
||||
}: NotificationProps) => {
|
||||
return (
|
||||
<div
|
||||
className={classnames(
|
||||
"w-full flex items-center justify-between px-4 py-3 rounded pointer-events-auto",
|
||||
{
|
||||
"bg-green-600": notification.type === "success",
|
||||
"bg-red-500": notification.type === "error",
|
||||
}
|
||||
)}
|
||||
role="alert"
|
||||
>
|
||||
<p className="text-white text-sm font-bold">{notification.text}</p>
|
||||
<button
|
||||
className="bg-white/5 rounded-lg p-3"
|
||||
onClick={() => clearNotification(notification.text)}
|
||||
>
|
||||
<FontAwesomeIcon className="text-white" icon={faXmarkCircle} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Notification;
|
@ -0,0 +1,64 @@
|
||||
import { createContext, ReactNode, useContext, useState } from "react";
|
||||
|
||||
import Notifications from "./Notifications";
|
||||
|
||||
type NotificationType = "success" | "error";
|
||||
|
||||
export type Notification = {
|
||||
text: string;
|
||||
type: NotificationType;
|
||||
};
|
||||
|
||||
type NotificationContextState = {
|
||||
createNotification: ({ text, type }: Notification) => void;
|
||||
};
|
||||
|
||||
const NotificationContext = createContext<NotificationContextState>({
|
||||
createNotification: () => console.log("createNotification not set!"),
|
||||
});
|
||||
|
||||
export const useNotificationContext = () => useContext(NotificationContext);
|
||||
|
||||
interface NotificationProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const NotificationProvider = ({ children }: NotificationProviderProps) => {
|
||||
const [notifications, setNotifications] = useState<Notification[]>([]);
|
||||
|
||||
const clearNotification = (text?: string) => {
|
||||
if (text) {
|
||||
return setNotifications((state) =>
|
||||
state.filter((notif) => notif.text !== text)
|
||||
);
|
||||
}
|
||||
|
||||
return setNotifications([]);
|
||||
};
|
||||
|
||||
const createNotification = ({ text, type = "success" }: Notification) => {
|
||||
const doesNotifExist = notifications.some((notif) => notif.text === text);
|
||||
|
||||
if (doesNotifExist) {
|
||||
return;
|
||||
}
|
||||
|
||||
return setNotifications((state) => [...state, { text, type }]);
|
||||
};
|
||||
|
||||
return (
|
||||
<NotificationContext.Provider
|
||||
value={{
|
||||
createNotification,
|
||||
}}
|
||||
>
|
||||
<Notifications
|
||||
notifications={notifications}
|
||||
clearNotification={clearNotification}
|
||||
/>
|
||||
{children}
|
||||
</NotificationContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotificationProvider;
|
28
frontend/components/context/Notifications/Notifications.tsx
Normal file
28
frontend/components/context/Notifications/Notifications.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import Notification from "./Notification";
|
||||
import { Notification as NotificationType } from "./NotificationProvider";
|
||||
|
||||
interface NoticationsProps {
|
||||
notifications: NotificationType[];
|
||||
clearNotification: (text?: string) => void;
|
||||
}
|
||||
|
||||
const Notifications = ({
|
||||
notifications,
|
||||
clearNotification,
|
||||
}: NoticationsProps) => {
|
||||
return (
|
||||
<div className="hidden fixed z-50 top-1 w-full inset-x-0 pointer-events-none md:flex justify-center">
|
||||
<div className="flex flex-col gap-y-2 w-96">
|
||||
{notifications.map((notif) => (
|
||||
<Notification
|
||||
key={notif.text}
|
||||
notification={notif}
|
||||
clearNotification={clearNotification}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Notifications;
|
@ -3,7 +3,7 @@ import token from "~/pages/api/auth/Token";
|
||||
export default class SecurityClient {
|
||||
static #token = "";
|
||||
|
||||
contructor() {}
|
||||
constructor() {}
|
||||
|
||||
static setToken(token) {
|
||||
this.#token = token;
|
||||
|
@ -7,6 +7,7 @@ import getOrganizationUserProjects from "~/pages/api/organization/GetOrgUserProj
|
||||
import { initPostHog } from "../analytics/posthog";
|
||||
import pushKeys from "./secrets/pushKeys";
|
||||
import { ENV } from "./config";
|
||||
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
|
||||
import SecurityClient from "./SecurityClient";
|
||||
|
||||
const nacl = require("tweetnacl");
|
||||
@ -32,7 +33,6 @@ const attemptLogin = async (
|
||||
isLogin
|
||||
) => {
|
||||
try {
|
||||
let userWorkspace, userOrg;
|
||||
client.init(
|
||||
{
|
||||
username: email,
|
||||
@ -41,66 +41,38 @@ const attemptLogin = async (
|
||||
async () => {
|
||||
const clientPublicKey = client.getPublicKey();
|
||||
|
||||
let serverPublicKey, salt;
|
||||
try {
|
||||
let res = await login1(email, clientPublicKey);
|
||||
res = await res.json();
|
||||
serverPublicKey = res.serverPublicKey;
|
||||
salt = res.salt;
|
||||
} catch (err) {
|
||||
setErrorLogin(true);
|
||||
console.log("Wrong password", err);
|
||||
}
|
||||
const { serverPublicKey, salt } = await login1(email, clientPublicKey);
|
||||
|
||||
let response;
|
||||
try {
|
||||
client.setSalt(salt);
|
||||
client.setServerPublicKey(serverPublicKey);
|
||||
const clientProof = client.getProof(); // called M1
|
||||
response = await login2(email, clientProof);
|
||||
} catch (err) {
|
||||
setErrorLogin(true);
|
||||
console.log("Password verification failed");
|
||||
}
|
||||
|
||||
// if everything works, go the main dashboard page.
|
||||
try {
|
||||
if (response.status == "200") {
|
||||
response = await response.json();
|
||||
SecurityClient.setToken(response["token"]);
|
||||
const publicKey = response["publicKey"];
|
||||
const encryptedPrivateKey = response["encryptedPrivateKey"];
|
||||
const iv = response["iv"];
|
||||
const tag = response["tag"];
|
||||
// if everything works, go the main dashboard page.
|
||||
const { token, publicKey, encryptedPrivateKey, iv, tag } =
|
||||
await login2(email, clientProof);
|
||||
SecurityClient.setToken(token);
|
||||
|
||||
const PRIVATE_KEY = Aes256Gcm.decrypt(
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
password
|
||||
.slice(0, 32)
|
||||
.padStart(
|
||||
32 +
|
||||
(password.slice(0, 32).length - new Blob([password]).size),
|
||||
"0"
|
||||
)
|
||||
);
|
||||
const privateKey = Aes256Gcm.decrypt(
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
password
|
||||
.slice(0, 32)
|
||||
.padStart(
|
||||
32 + (password.slice(0, 32).length - new Blob([password]).size),
|
||||
"0"
|
||||
)
|
||||
);
|
||||
|
||||
try {
|
||||
localStorage.setItem("publicKey", publicKey);
|
||||
localStorage.setItem("encryptedPrivateKey", encryptedPrivateKey);
|
||||
localStorage.setItem("iv", iv);
|
||||
localStorage.setItem("tag", tag);
|
||||
localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY);
|
||||
} catch (err) {
|
||||
setErrorLogin(true);
|
||||
console.error(
|
||||
"Unable to send the tokens in local storage:" + err.message
|
||||
);
|
||||
}
|
||||
} else {
|
||||
setErrorLogin(true);
|
||||
}
|
||||
saveTokenToLocalStorage({
|
||||
token,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
privateKey,
|
||||
});
|
||||
|
||||
const userOrgs = await getOrganizations();
|
||||
const userOrgsData = userOrgs.map((org) => org._id);
|
||||
@ -140,14 +112,8 @@ const attemptLogin = async (
|
||||
"mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net",
|
||||
"personal",
|
||||
],
|
||||
DB_USERNAME: [
|
||||
"user1234",
|
||||
"personal",
|
||||
],
|
||||
DB_PASSWORD: [
|
||||
"ah8jak3hk8dhiu4dw7whxwe1l",
|
||||
"personal",
|
||||
],
|
||||
DB_USERNAME: ["user1234", "personal"],
|
||||
DB_PASSWORD: ["ah8jak3hk8dhiu4dw7whxwe1l", "personal"],
|
||||
TWILIO_AUTH_TOKEN: [
|
||||
"hgSIwDAKvz8PJfkj6xkzYqzGmAP3HLuG",
|
||||
"shared",
|
||||
@ -156,7 +122,7 @@ const attemptLogin = async (
|
||||
STRIPE_SECRET_KEY: ["sk_test_7348oyho4hfq398HIUOH78", "shared"],
|
||||
},
|
||||
workspaceId: projectToLogin,
|
||||
env: "Development"
|
||||
env: "Development",
|
||||
});
|
||||
}
|
||||
try {
|
||||
|
@ -1,18 +1,21 @@
|
||||
interface PasswordCheckProps {
|
||||
password: string;
|
||||
currentErrorCheck: boolean;
|
||||
setPasswordErrorLength: (value: boolean) => void;
|
||||
setPasswordErrorNumber: (value: boolean) => void;
|
||||
setPasswordErrorLowerCase: (value: boolean) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function checks a user password with respect to some criteria.
|
||||
* @param {*} password
|
||||
* @param {*} setPasswordError
|
||||
* @param {*} setPasswordErrorMessage
|
||||
* @param {*} currentErrorCheck
|
||||
* @returns
|
||||
*/
|
||||
const passwordCheck = (
|
||||
const passwordCheck = ({
|
||||
password,
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
currentErrorCheck
|
||||
) => {
|
||||
currentErrorCheck,
|
||||
}: PasswordCheckProps) => {
|
||||
let errorCheck = currentErrorCheck;
|
||||
if (!password || password.length < 14) {
|
||||
setPasswordErrorLength(true);
|
11
frontend/components/utilities/checks/tempLocalStorage.ts
Normal file
11
frontend/components/utilities/checks/tempLocalStorage.ts
Normal file
@ -0,0 +1,11 @@
|
||||
// this is temporary util function. create error handling logic for localStorage and delete this.
|
||||
export const tempLocalStorage = (key: string) => {
|
||||
const value = localStorage.getItem(key);
|
||||
|
||||
if (value === null || value === "") {
|
||||
console.warn("No value found in localStorage for key");
|
||||
return "";
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
29
frontend/components/utilities/saveTokenToLocalStorage.ts
Normal file
29
frontend/components/utilities/saveTokenToLocalStorage.ts
Normal file
@ -0,0 +1,29 @@
|
||||
interface Props {
|
||||
publicKey: string;
|
||||
encryptedPrivateKey: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
privateTag: string;
|
||||
}
|
||||
|
||||
export const saveTokenToLocalStorage = ({
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
privateTag,
|
||||
}: Props) => {
|
||||
try {
|
||||
localStorage.setItem("publicKey", publicKey);
|
||||
localStorage.setItem("encryptedPrivateKey", encryptedPrivateKey);
|
||||
localStorage.setItem("iv", iv);
|
||||
localStorage.setItem("tag", tag);
|
||||
localStorage.setItem("PRIVATE_KEY", privateTag);
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
throw new Error(
|
||||
"Unable to send the tokens in local storage:" + err.message
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
13
frontend/package-lock.json
generated
13
frontend/package-lock.json
generated
@ -51,6 +51,7 @@
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.4",
|
||||
"@types/node": "18.11.9",
|
||||
"@types/react": "^18.0.26",
|
||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||
"@typescript-eslint/parser": "^5.45.0",
|
||||
"autoprefixer": "^10.4.7",
|
||||
@ -1143,9 +1144,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.15.tgz",
|
||||
"integrity": "sha512-iz3BtLuIYH1uWdsv6wXYdhozhqj20oD4/Hk2DNXIn1kFsmp9x8d9QB6FnPhfkbhd2PgEONt9Q1x/ebkwjfFLow==",
|
||||
"version": "18.0.26",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.26.tgz",
|
||||
"integrity": "sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"@types/scheduler": "*",
|
||||
@ -7978,9 +7979,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"@types/react": {
|
||||
"version": "18.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.15.tgz",
|
||||
"integrity": "sha512-iz3BtLuIYH1uWdsv6wXYdhozhqj20oD4/Hk2DNXIn1kFsmp9x8d9QB6FnPhfkbhd2PgEONt9Q1x/ebkwjfFLow==",
|
||||
"version": "18.0.26",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.26.tgz",
|
||||
"integrity": "sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==",
|
||||
"requires": {
|
||||
"@types/prop-types": "*",
|
||||
"@types/scheduler": "*",
|
||||
|
@ -54,6 +54,7 @@
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.4",
|
||||
"@types/node": "18.11.9",
|
||||
"@types/react": "^18.0.26",
|
||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||
"@typescript-eslint/parser": "^5.45.0",
|
||||
"autoprefixer": "^10.4.7",
|
||||
|
@ -4,6 +4,7 @@ import { config } from "@fortawesome/fontawesome-svg-core";
|
||||
|
||||
import { initPostHog } from "~/components/analytics/posthog";
|
||||
import Layout from "~/components/basic/layout";
|
||||
import NotificationProvider from "~/components/context/Notifications/NotificationProvider";
|
||||
import RouteGuard from "~/components/RouteGuard";
|
||||
import { publicPaths } from "~/const";
|
||||
import { ENV } from "~/utilities/config";
|
||||
@ -46,9 +47,11 @@ const App = ({ Component, pageProps, ...appProps }) => {
|
||||
|
||||
return (
|
||||
<RouteGuard>
|
||||
<Layout>
|
||||
<Component {...pageProps} />
|
||||
</Layout>
|
||||
<NotificationProvider>
|
||||
<Layout>
|
||||
<Component {...pageProps} />
|
||||
</Layout>
|
||||
</NotificationProvider>
|
||||
</RouteGuard>
|
||||
);
|
||||
};
|
||||
|
@ -1,20 +0,0 @@
|
||||
/**
|
||||
* This is the first step of the login process (pake)
|
||||
* @param {*} email
|
||||
* @param {*} clientPublicKey
|
||||
* @returns
|
||||
*/
|
||||
const login1 = (email, clientPublicKey) => {
|
||||
return fetch("/api/v1/auth/login1", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
clientPublicKey,
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
export default login1;
|
32
frontend/pages/api/auth/Login1.ts
Normal file
32
frontend/pages/api/auth/Login1.ts
Normal file
@ -0,0 +1,32 @@
|
||||
interface Login1 {
|
||||
serverPublicKey: string;
|
||||
salt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the first step of the login process (pake)
|
||||
* @param {*} email
|
||||
* @param {*} clientPublicKey
|
||||
* @returns
|
||||
*/
|
||||
const login1 = async (email: string, clientPublicKey: string) => {
|
||||
const response = await fetch("/api/v1/auth/login1", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
clientPublicKey,
|
||||
}),
|
||||
});
|
||||
// need precise error handling about the status code
|
||||
if (response?.status === 200) {
|
||||
const data = (await response.json()) as unknown as Login1;
|
||||
return data;
|
||||
}
|
||||
|
||||
throw new Error("Wrong password");
|
||||
};
|
||||
|
||||
export default login1;
|
@ -1,28 +0,0 @@
|
||||
/**
|
||||
* This is the second step of the login process
|
||||
* @param {*} email
|
||||
* @param {*} clientPublicKey
|
||||
* @returns
|
||||
*/
|
||||
const login2 = (email, clientProof) => {
|
||||
return fetch("/api/v1/auth/login2", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
clientProof,
|
||||
}),
|
||||
credentials: "include",
|
||||
}).then((res) => {
|
||||
if (res.status == 200) {
|
||||
console.log("User logged in", res);
|
||||
return res;
|
||||
} else {
|
||||
console.log("Failed to log in");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default login2;
|
36
frontend/pages/api/auth/Login2.ts
Normal file
36
frontend/pages/api/auth/Login2.ts
Normal file
@ -0,0 +1,36 @@
|
||||
interface Login2Response {
|
||||
encryptedPrivateKey: string;
|
||||
iv: string;
|
||||
publicKey: string;
|
||||
tag: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the second step of the login process
|
||||
* @param {*} email
|
||||
* @param {*} clientPublicKey
|
||||
* @returns
|
||||
*/
|
||||
const login2 = async (email: string, clientProof: string) => {
|
||||
const response = await fetch("/api/v1/auth/login2", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
clientProof,
|
||||
}),
|
||||
credentials: "include",
|
||||
});
|
||||
// need precise error handling about the status code
|
||||
if (response.status == 200) {
|
||||
const data = (await response.json()) as unknown as Login2Response;
|
||||
return data;
|
||||
}
|
||||
|
||||
throw new Error("Password verification failed");
|
||||
};
|
||||
|
||||
export default login2;
|
@ -6,7 +6,7 @@ import SecurityClient from "~/utilities/SecurityClient";
|
||||
* @param {*} res
|
||||
* @returns
|
||||
*/
|
||||
const getOrganizationUserProjects = (req, res) => {
|
||||
const getOrganizationUserProjects = (req) => {
|
||||
return SecurityClient.fetchCall(
|
||||
"/api/v1/organization/" + req.orgId + "/my-workspaces",
|
||||
{
|
||||
|
@ -1,5 +1,13 @@
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
interface Workspaces {
|
||||
__v: number;
|
||||
_id: string;
|
||||
name: string;
|
||||
organization: string;
|
||||
}
|
||||
[];
|
||||
|
||||
/**
|
||||
* This route lets us get the workspaces of a certain user
|
||||
* @returns
|
||||
@ -12,10 +20,11 @@ const getWorkspaces = () => {
|
||||
},
|
||||
}).then(async (res) => {
|
||||
if (res?.status == 200) {
|
||||
return (await res.json()).workspaces;
|
||||
} else {
|
||||
console.log("Failed to get projects");
|
||||
const data = (await res.json()) as unknown as { workspaces: Workspaces };
|
||||
return data.workspaces;
|
||||
}
|
||||
|
||||
throw new Error("Failed to get projects");
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -27,6 +27,7 @@ import { Menu, Transition } from "@headlessui/react";
|
||||
import Button from "~/components/basic/buttons/Button";
|
||||
import ListBox from "~/components/basic/Listbox";
|
||||
import BottonRightPopup from "~/components/basic/popups/BottomRightPopup";
|
||||
import { useNotificationContext } from "~/components/context/Notifications/NotificationProvider";
|
||||
import DashboardInputField from "~/components/dashboard/DashboardInputField";
|
||||
import DropZone from "~/components/dashboard/DropZone";
|
||||
import NavHeader from "~/components/navigation/NavHeader";
|
||||
@ -61,7 +62,7 @@ const KeyPair = ({
|
||||
modifyValue,
|
||||
modifyVisibility,
|
||||
isBlurred,
|
||||
duplicates
|
||||
duplicates,
|
||||
}) => {
|
||||
const [randomStringLength, setRandomStringLength] = useState(32);
|
||||
const { t } = useTranslation();
|
||||
@ -233,6 +234,8 @@ export default function Dashboard() {
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { createNotification } = useNotificationContext();
|
||||
|
||||
// #TODO: fix save message for changing reroutes
|
||||
// const beforeRouteHandler = (url) => {
|
||||
// const warningText =
|
||||
@ -376,46 +379,61 @@ export default function Dashboard() {
|
||||
);
|
||||
|
||||
// Checking if any of the secret keys start with a number - if so, don't do anything
|
||||
const nameErrors = !Object.keys(obj).map(key => !isNaN(key.charAt(0))).every(v => v === false);
|
||||
const duplicatesExist = data?.map(item => item[2]).filter((item, index) => index !== data?.map(item => item[2]).indexOf(item)).length > 0;
|
||||
const nameErrors = !Object.keys(obj)
|
||||
.map((key) => !isNaN(key.charAt(0)))
|
||||
.every((v) => v === false);
|
||||
const duplicatesExist =
|
||||
data
|
||||
?.map((item) => item[2])
|
||||
.filter(
|
||||
(item, index) => index !== data?.map((item) => item[2]).indexOf(item)
|
||||
).length > 0;
|
||||
|
||||
if (nameErrors) {
|
||||
console.log("Solve all name errors first!");
|
||||
} else if (duplicatesExist) {
|
||||
console.log("Remove the duplicated entries first!");
|
||||
} else {
|
||||
// Once "Save changes is clicked", disable that button
|
||||
setButtonReady(false);
|
||||
pushKeys({obj, workspaceId: router.query.id, env});
|
||||
|
||||
/**
|
||||
* Check which integrations are active for this project and environment
|
||||
* If there are any, update environment variables for those integrations
|
||||
*/
|
||||
let integrations = await getWorkspaceIntegrations({
|
||||
workspaceId: router.query.id,
|
||||
});
|
||||
integrations.map(async (integration) => {
|
||||
if (
|
||||
envMapping[env] == integration.environment &&
|
||||
integration.isActive == true
|
||||
) {
|
||||
let objIntegration = Object.assign(
|
||||
{},
|
||||
...data.map((row) => ({ [row[2]]: row[3] }))
|
||||
);
|
||||
await pushKeysIntegration({
|
||||
obj: objIntegration,
|
||||
integrationId: integration._id,
|
||||
});
|
||||
}
|
||||
return createNotification({
|
||||
text: "Solve all name errors first!",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
|
||||
// If this user has never saved environment variables before, show them a prompt to read docs
|
||||
if (!hasUserEverPushed) {
|
||||
setCheckDocsPopUpVisible(true);
|
||||
await registerUserAction({ action: "first_time_secrets_pushed" });
|
||||
if (duplicatesExist) {
|
||||
return createNotification({
|
||||
text: "Your secrets weren't saved; please fix the conflicts first.",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
|
||||
// Once "Save changed is clicked", disable that button
|
||||
setButtonReady(false);
|
||||
pushKeys({ obj, workspaceId: router.query.id, env });
|
||||
|
||||
/**
|
||||
* Check which integrations are active for this project and environment
|
||||
* If there are any, update environment variables for those integrations
|
||||
*/
|
||||
let integrations = await getWorkspaceIntegrations({
|
||||
workspaceId: router.query.id,
|
||||
});
|
||||
integrations.map(async (integration) => {
|
||||
if (
|
||||
envMapping[env] == integration.environment &&
|
||||
integration.isActive == true
|
||||
) {
|
||||
let objIntegration = Object.assign(
|
||||
{},
|
||||
...data.map((row) => ({ [row[2]]: row[3] }))
|
||||
);
|
||||
await pushKeysIntegration({
|
||||
obj: objIntegration,
|
||||
integrationId: integration._id,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// If this user has never saved environment variables before, show them a prompt to read docs
|
||||
if (!hasUserEverPushed) {
|
||||
setCheckDocsPopUpVisible(true);
|
||||
await registerUserAction({ action: "first_time_secrets_pushed" });
|
||||
}
|
||||
};
|
||||
|
||||
@ -654,7 +672,13 @@ export default function Dashboard() {
|
||||
modifyKey={listenChangeKey}
|
||||
modifyVisibility={listenChangeVisibility}
|
||||
isBlurred={blurred}
|
||||
duplicates={data?.map(item => item[2]).filter((item, index) => index !== data?.map(item => item[2]).indexOf(item))}
|
||||
duplicates={data
|
||||
?.map((item) => item[2])
|
||||
.filter(
|
||||
(item, index) =>
|
||||
index !==
|
||||
data?.map((item) => item[2]).indexOf(item)
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@ -702,7 +726,13 @@ export default function Dashboard() {
|
||||
modifyKey={listenChangeKey}
|
||||
modifyVisibility={listenChangeVisibility}
|
||||
isBlurred={blurred}
|
||||
duplicates={data?.map(item => item[2]).filter((item, index) => index !== data?.map(item => item[2]).indexOf(item))}
|
||||
duplicates={data
|
||||
?.map((item) => item[2])
|
||||
.filter(
|
||||
(item, index) =>
|
||||
index !==
|
||||
data?.map((item) => item[2]).indexOf(item)
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import ReactCodeInput from "react-code-input";
|
||||
import dynamic from "next/dynamic";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
@ -22,7 +23,7 @@ import getWorkspaces from "./api/workspace/getWorkspaces";
|
||||
import useTranslation from "next-translate/useTranslation";
|
||||
import Trans from "next-translate/Trans";
|
||||
|
||||
const ReactCodeInput = dynamic(import("react-code-input"));
|
||||
// const ReactCodeInput = dynamic(import("react-code-input"));
|
||||
const nacl = require("tweetnacl");
|
||||
const jsrp = require("jsrp");
|
||||
nacl.util = require("tweetnacl-util");
|
||||
@ -44,7 +45,7 @@ const props = {
|
||||
border: "1px solid gray",
|
||||
textAlign: "center",
|
||||
},
|
||||
};
|
||||
} as const;
|
||||
const propsPhone = {
|
||||
inputStyle: {
|
||||
fontFamily: "monospace",
|
||||
@ -60,7 +61,7 @@ const propsPhone = {
|
||||
border: "1px solid gray",
|
||||
textAlign: "center",
|
||||
},
|
||||
};
|
||||
} as const;
|
||||
|
||||
export default function SignUp() {
|
||||
const [email, setEmail] = useState("");
|
||||
@ -89,15 +90,16 @@ export default function SignUp() {
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(async () => {
|
||||
let userWorkspace;
|
||||
try {
|
||||
const userWorkspaces = await getWorkspaces();
|
||||
userWorkspace = userWorkspaces[0]._id;
|
||||
router.push("/dashboard/" + userWorkspace);
|
||||
} catch (error) {
|
||||
console.log("Error - Not logged in yet");
|
||||
}
|
||||
useEffect(() => {
|
||||
const tryAuth = async () => {
|
||||
try {
|
||||
const userWorkspaces = await getWorkspaces();
|
||||
router.push("/dashboard/" + userWorkspaces[0]._id);
|
||||
} catch (error) {
|
||||
console.log("Error - Not logged in yet");
|
||||
}
|
||||
};
|
||||
tryAuth();
|
||||
}, []);
|
||||
|
||||
/**
|
||||
@ -112,7 +114,7 @@ export default function SignUp() {
|
||||
} else if (step == 2) {
|
||||
// Checking if the code matches the email.
|
||||
const response = await checkEmailVerificationCode(email, code);
|
||||
if (response.status == "200" || code == "111222") {
|
||||
if (response.status === 200 || code == "111222") {
|
||||
setVerificationToken((await response.json()).token);
|
||||
setStep(3);
|
||||
} else {
|
||||
@ -127,7 +129,7 @@ export default function SignUp() {
|
||||
* Verifies if the entered email "looks" correct
|
||||
*/
|
||||
const emailCheck = () => {
|
||||
var emailCheckBool = false;
|
||||
let emailCheckBool = false;
|
||||
if (!email) {
|
||||
setEmailError(true);
|
||||
setEmailErrorMessage("Please enter your email.");
|
||||
@ -154,7 +156,7 @@ export default function SignUp() {
|
||||
// Verifies if the imformation that the users entered (name, workspace) is there, and if the password matched the criteria.
|
||||
const signupErrorCheck = async () => {
|
||||
setIsLoading(true);
|
||||
var errorCheck = false;
|
||||
let errorCheck = false;
|
||||
if (!firstName) {
|
||||
setFirstNameError(true);
|
||||
errorCheck = true;
|
||||
@ -167,13 +169,13 @@ export default function SignUp() {
|
||||
} else {
|
||||
setLastNameError(false);
|
||||
}
|
||||
errorCheck = passwordCheck(
|
||||
errorCheck = passwordCheck({
|
||||
password,
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
errorCheck
|
||||
);
|
||||
currentErrorCheck: errorCheck,
|
||||
});
|
||||
|
||||
if (!errorCheck) {
|
||||
// Generate a random pair of a public and a private key
|
||||
@ -191,7 +193,8 @@ export default function SignUp() {
|
||||
32 + (password.slice(0, 32).length - new Blob([password]).size),
|
||||
"0"
|
||||
)
|
||||
);
|
||||
) as { ciphertext: string; iv: string; tag: string };
|
||||
|
||||
localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY);
|
||||
|
||||
client.init(
|
||||
@ -200,45 +203,47 @@ export default function SignUp() {
|
||||
password: password,
|
||||
},
|
||||
async () => {
|
||||
client.createVerifier(async (err, result) => {
|
||||
let response = await completeAccountInformationSignup({
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
organizationName: firstName + "'s organization",
|
||||
publicKey: PUBLIC_KEY,
|
||||
ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
salt: result.salt,
|
||||
verifier: result.verifier,
|
||||
token: verificationToken,
|
||||
});
|
||||
client.createVerifier(
|
||||
async (err: any, result: { salt: string; verifier: string }) => {
|
||||
const response = await completeAccountInformationSignup({
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
organizationName: firstName + "'s organization",
|
||||
publicKey: PUBLIC_KEY,
|
||||
ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
salt: result.salt,
|
||||
verifier: result.verifier,
|
||||
token: verificationToken,
|
||||
});
|
||||
|
||||
// if everything works, go the main dashboard page.
|
||||
if (!errorCheck && response.status == "200") {
|
||||
response = await response.json();
|
||||
// if everything works, go the main dashboard page.
|
||||
if (response.status === 200) {
|
||||
// response = await response.json();
|
||||
|
||||
localStorage.setItem("publicKey", PUBLIC_KEY);
|
||||
localStorage.setItem("encryptedPrivateKey", ciphertext);
|
||||
localStorage.setItem("iv", iv);
|
||||
localStorage.setItem("tag", tag);
|
||||
localStorage.setItem("publicKey", PUBLIC_KEY);
|
||||
localStorage.setItem("encryptedPrivateKey", ciphertext);
|
||||
localStorage.setItem("iv", iv);
|
||||
localStorage.setItem("tag", tag);
|
||||
|
||||
try {
|
||||
await attemptLogin(
|
||||
email,
|
||||
password,
|
||||
setErrorLogin,
|
||||
router,
|
||||
true,
|
||||
false
|
||||
);
|
||||
incrementStep();
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
try {
|
||||
await attemptLogin(
|
||||
email,
|
||||
password,
|
||||
setErrorLogin,
|
||||
router,
|
||||
true,
|
||||
false
|
||||
);
|
||||
incrementStep();
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
@ -308,6 +313,8 @@ export default function SignUp() {
|
||||
|
||||
<div className="hidden md:block">
|
||||
<ReactCodeInput
|
||||
name=""
|
||||
inputMode="tel"
|
||||
type="text"
|
||||
fields={6}
|
||||
onChange={setCode}
|
||||
@ -317,6 +324,8 @@ export default function SignUp() {
|
||||
</div>
|
||||
<div className="block md:hidden">
|
||||
<ReactCodeInput
|
||||
name=""
|
||||
inputMode="tel"
|
||||
type="text"
|
||||
fields={6}
|
||||
onChange={setCode}
|
||||
@ -382,15 +391,15 @@ export default function SignUp() {
|
||||
<div className="mt-2 flex flex-col items-center justify-center w-full md:p-2 rounded-lg max-h-60">
|
||||
<InputField
|
||||
label={t("form-password:password")}
|
||||
onChangeHandler={(password) => {
|
||||
onChangeHandler={(password: string) => {
|
||||
setPassword(password);
|
||||
passwordCheck(
|
||||
passwordCheck({
|
||||
password,
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
false
|
||||
);
|
||||
currentErrorCheck: false,
|
||||
});
|
||||
}}
|
||||
type="password"
|
||||
value={password}
|
||||
@ -508,7 +517,7 @@ export default function SignUp() {
|
||||
setBackupKeyIssued,
|
||||
});
|
||||
const userWorkspaces = await getWorkspaces();
|
||||
let userWorkspace = userWorkspaces[0]._id;
|
||||
const userWorkspace = userWorkspaces[0]._id;
|
||||
router.push("/home/" + userWorkspace);
|
||||
}}
|
||||
size="lg"
|
Reference in New Issue
Block a user