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

This commit is contained in:
이강준
2022-12-07 20:45:49 +09:00
24 changed files with 625 additions and 376 deletions

View File

@@ -48,11 +48,11 @@
And more. And more.
## Get started ## 🚀 Get started
To quickly get started, visit our [get started guide](https://infisical.com/docs/getting-started/introduction). 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>. 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. 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). 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!)! 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) - [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 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) - [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) - [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. - [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. - [ ] 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. 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: 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:
![infisical-star-github](https://github.com/Infisical/infisical/blob/main/.github/images/star-infisical.gif?raw=true) ![infisical-star-github](https://github.com/Infisical/infisical/blob/main/.github/images/star-infisical.gif?raw=true)
## 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! :) 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> </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. 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. 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 [//]: contributor-faces
@@ -277,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/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>

View File

@@ -1,19 +1,27 @@
import React from "react"; import React, { useState } from "react";
import { useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { import { faCircle, faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons";
faCircle,
faCircleExclamation,
faE,
faEye,
faEyeSlash,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import guidGenerator from "../utilities/randomId"; 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 [passwordVisible, setPasswordVisible] = useState(false);
const router = useRouter(); const router = useRouter();
@@ -67,7 +75,7 @@ const InputField = (props) => {
> >
<input <input
onChange={(e) => props.onChangeHandler(e.target.value)} onChange={(e) => props.onChangeHandler(e.target.value)}
type={passwordVisible == false ? props.type : "text"} type={passwordVisible === false ? props.type : "text"}
placeholder={props.placeholder} placeholder={props.placeholder}
value={props.value} value={props.value}
required={props.isRequired} required={props.isRequired}

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-unexpected-multiline */
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import Link from "next/link"; import Link from "next/link";
@@ -23,6 +24,7 @@ import getWorkspaces from "~/pages/api/workspace/getWorkspaces";
import uploadKeys from "~/pages/api/workspace/uploadKeys"; import uploadKeys from "~/pages/api/workspace/uploadKeys";
import NavBarDashboard from "../navigation/NavBarDashboard"; import NavBarDashboard from "../navigation/NavBarDashboard";
import { tempLocalStorage } from "../utilities/checks/tempLocalStorage";
import { import {
decryptAssymmetric, decryptAssymmetric,
encryptAssymmetric, encryptAssymmetric,
@@ -31,13 +33,17 @@ import Button from "./buttons/Button";
import AddWorkspaceDialog from "./dialog/AddWorkspaceDialog"; import AddWorkspaceDialog from "./dialog/AddWorkspaceDialog";
import Listbox from "./Listbox"; import Listbox from "./Listbox";
export default function Layout({ children }) { interface LayoutProps {
children: React.ReactNode;
}
export default function Layout({ children }: LayoutProps) {
const router = useRouter(); const router = useRouter();
const [workspaceList, setWorkspaceList] = useState([]); const [workspaceList, setWorkspaceList] = useState([]);
const [workspaceMapping, setWorkspaceMapping] = useState([{ 1: 2 }]); const [workspaceMapping, setWorkspaceMapping] = useState([{ 1: 2 }]);
const [workspaceSelected, setWorkspaceSelected] = useState("∞"); const [workspaceSelected, setWorkspaceSelected] = useState("∞");
let [newWorkspaceName, setNewWorkspaceName] = useState(""); const [newWorkspaceName, setNewWorkspaceName] = useState("");
let [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState(false); const [error, setError] = useState(false);
@@ -47,164 +53,186 @@ export default function Layout({ children }) {
setIsOpen(false); setIsOpen(false);
} }
function openModal() {
setIsOpen(true);
}
// TODO: what to do about the fact that 2ids can have the same name // 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. * When a user creates a new workspace, redirect them to the page of the new workspace.
* @param {*} workspaceName * @param {*} workspaceName
*/ */
async function submitModal(workspaceName, addAllUsers) { async function submitModal(workspaceName: string, addAllUsers: boolean) {
setLoading(true); setLoading(true);
// timeout code.
setTimeout(() => setLoading(false), 1500); setTimeout(() => setLoading(false), 1500);
const workspaces = await getWorkspaces();
const currentWorkspaces = workspaces.map((workspace) => workspace.name); try {
if (!currentWorkspaces.includes(workspaceName)) { const workspaces = await getWorkspaces();
const newWorkspace = await createWorkspace({ const currentWorkspaces = workspaces.map((workspace) => workspace.name);
workspaceName, if (!currentWorkspaces.includes(workspaceName)) {
organizationId: localStorage.getItem("orgData.id"), const newWorkspace = await createWorkspace({
}); workspaceName,
let newWorkspaceId; organizationId: tempLocalStorage("orgData.id"),
try {
newWorkspaceId = newWorkspace._id;
} catch (error) {
console.log(error);
}
if (addAllUsers) {
let orgUsers = await getOrganizationUsers({
orgId: localStorage.getItem("orgData.id"),
}); });
orgUsers.map(async (user) => { const newWorkspaceId = newWorkspace._id;
if (user.status == "accepted") {
let result = await addUserToWorkspace(
user.user.email,
newWorkspaceId
);
if (result?.invitee && result?.latestKey) {
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
// assymmetrically decrypt symmetric key with local private key if (addAllUsers) {
const key = decryptAssymmetric({ const orgUsers = await getOrganizationUsers({
ciphertext: result.latestKey.encryptedKey, orgId: tempLocalStorage("orgData.id"),
nonce: result.latestKey.nonce, });
publicKey: result.latestKey.sender.publicKey, orgUsers.map(async (user: any) => {
privateKey: PRIVATE_KEY, 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({ // assymmetrically decrypt symmetric key with local private key
plaintext: key, const key = decryptAssymmetric({
publicKey: result.invitee.publicKey, ciphertext: result.latestKey.encryptedKey,
privateKey: PRIVATE_KEY, 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"); } catch (err) {
setIsOpen(false); console.error(err);
setNewWorkspaceName(""); setError(true);
} else {
setError(t("error_project-already-exists"));
setLoading(false); setLoading(false);
} }
} }
function openModal() {
setIsOpen(true);
}
const menuItems = [ const menuItems = [
{ {
href: href:
"/dashboard/" + workspaceMapping[workspaceSelected] + "?Development", "/dashboard/" +
workspaceMapping[workspaceSelected as any] +
"?Development",
title: t("nav:menu.secrets"), title: t("nav:menu.secrets"),
emoji: <FontAwesomeIcon icon={faKey} />, emoji: <FontAwesomeIcon icon={faKey} />,
}, },
{ {
href: "/users/" + workspaceMapping[workspaceSelected], href: "/users/" + workspaceMapping[workspaceSelected as any],
title: t("nav:menu.members"), title: t("nav:menu.members"),
emoji: <FontAwesomeIcon icon={faUser} />, emoji: <FontAwesomeIcon icon={faUser} />,
}, },
{ {
href: "/integrations/" + workspaceMapping[workspaceSelected], href: "/integrations/" + workspaceMapping[workspaceSelected as any],
title: t("nav:menu.integrations"), title: t("nav:menu.integrations"),
emoji: <FontAwesomeIcon icon={faPlug} />, emoji: <FontAwesomeIcon icon={faPlug} />,
}, },
{ {
href: "/settings/project/" + workspaceMapping[workspaceSelected], href: "/settings/project/" + workspaceMapping[workspaceSelected as any],
title: t("nav:menu.project-settings"), title: t("nav:menu.project-settings"),
emoji: <FontAwesomeIcon icon={faGear} />, emoji: <FontAwesomeIcon icon={faGear} />,
}, },
]; ];
useEffect(async () => { useEffect(() => {
// Put a user in a workspace if they're not in one yet // Put a user in a workspace if they're not in one yet
if ( const putUserInWorkSpace = async () => {
localStorage.getItem("orgData.id") == null || if (tempLocalStorage("orgData.id") === "") {
localStorage.getItem("orgData.id") == "" const userOrgs = await getOrganizations();
) { localStorage.setItem("orgData.id", userOrgs[0]._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 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(() => { useEffect(() => {
try { try {
if ( if (
workspaceMapping[workspaceSelected] && workspaceMapping[Number(workspaceSelected)] &&
workspaceMapping[workspaceSelected] !== `${workspaceMapping[Number(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[Number(workspaceSelected)] +
"?Development"
); );
localStorage.setItem( localStorage.setItem(
"projectData.id", "projectData.id",
workspaceMapping[workspaceSelected] `${workspaceMapping[Number(workspaceSelected)]}`
); );
} }
} catch (error) { } catch (error) {
@@ -221,18 +249,18 @@ export default function Layout({ children }) {
<nav className="flex flex-col justify-between items-between h-full"> <nav className="flex flex-col justify-between items-between h-full">
{/* <div className="py-6"></div> */} {/* <div className="py-6"></div> */}
<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"> <div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
{t("nav:menu.project")} {t("nav:menu.project")}
</div> </div>
{workspaceList.length > 0 ? ( {workspaceList.length > 0 ? (
<Listbox <Listbox
selected={workspaceSelected} selected={workspaceSelected}
onChange={setWorkspaceSelected} onChange={setWorkspaceSelected as any}
data={workspaceList} data={workspaceList}
buttonAction={openModal} buttonAction={openModal}
text="" text=""
workspaceMapping={workspaceMapping} // workspaceMapping={workspaceMapping as any}
/> />
) : ( ) : (
<Button <Button
@@ -300,7 +328,9 @@ export default function Layout({ children }) {
Infisical Guide Infisical Guide
</div> </div>
) : ( ) : (
<Link href={`/home/` + workspaceMapping[workspaceSelected]}> <Link
href={`/home/` + workspaceMapping[workspaceSelected as any]}
>
<div <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`} 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> <main className="flex-1 bg-bunker-800">{children}</main>
</div> </div>
</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 <FontAwesomeIcon
icon={faMobile} icon={faMobile}
className="text-gray-300 text-7xl mb-8" className="text-gray-300 text-7xl mb-8"

View File

@@ -8,14 +8,13 @@ import {
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Listbox, Transition } from "@headlessui/react"; import { Listbox, Transition } from "@headlessui/react";
interface ListBoxProps {
type ListBoxProps = { selected: string;
selected: string, onChange: () => void;
onChange: () => void, data: string[];
data: string[], text: string;
text: string, buttonAction: () => void;
buttonAction: () => void, isFull?: boolean;
width: string,
} }
/** /**
@@ -35,14 +34,14 @@ export default function ListBox({
data, data,
text, text,
buttonAction, buttonAction,
width, isFull,
} : ListBoxProps): JSX.Element { }: ListBoxProps): JSX.Element {
return ( return (
<Listbox value={selected} onChange={onChange}> <Listbox value={selected} onChange={onChange}>
<div className="relative"> <div className="relative">
<Listbox.Button <Listbox.Button
className={`text-gray-400 relative ${ 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`} } 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"> <div className="flex flex-row">

View File

@@ -12,7 +12,7 @@ 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;

View 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;

View File

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

View 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;

View File

@@ -3,7 +3,7 @@ import token from "~/pages/api/auth/Token";
export default class SecurityClient { export default class SecurityClient {
static #token = ""; static #token = "";
contructor() {} constructor() {}
static setToken(token) { static setToken(token) {
this.#token = token; this.#token = token;

View File

@@ -7,6 +7,7 @@ import getOrganizationUserProjects from "~/pages/api/organization/GetOrgUserProj
import { initPostHog } from "../analytics/posthog"; import { initPostHog } from "../analytics/posthog";
import pushKeys from "./secrets/pushKeys"; import pushKeys from "./secrets/pushKeys";
import { ENV } from "./config"; import { ENV } from "./config";
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
import SecurityClient from "./SecurityClient"; import SecurityClient from "./SecurityClient";
const nacl = require("tweetnacl"); const nacl = require("tweetnacl");
@@ -32,7 +33,6 @@ const attemptLogin = async (
isLogin isLogin
) => { ) => {
try { try {
let userWorkspace, userOrg;
client.init( client.init(
{ {
username: email, username: email,
@@ -41,66 +41,38 @@ const attemptLogin = async (
async () => { async () => {
const clientPublicKey = client.getPublicKey(); const clientPublicKey = client.getPublicKey();
let serverPublicKey, salt; const { serverPublicKey, salt } = await login1(email, clientPublicKey);
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);
}
let response;
try { try {
client.setSalt(salt); client.setSalt(salt);
client.setServerPublicKey(serverPublicKey); client.setServerPublicKey(serverPublicKey);
const clientProof = client.getProof(); // called M1 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. // if everything works, go the main dashboard page.
try { const { token, publicKey, encryptedPrivateKey, iv, tag } =
if (response.status == "200") { await login2(email, clientProof);
response = await response.json(); SecurityClient.setToken(token);
SecurityClient.setToken(response["token"]);
const publicKey = response["publicKey"];
const encryptedPrivateKey = response["encryptedPrivateKey"];
const iv = response["iv"];
const tag = response["tag"];
const PRIVATE_KEY = Aes256Gcm.decrypt( const privateKey = Aes256Gcm.decrypt(
encryptedPrivateKey, encryptedPrivateKey,
iv, iv,
tag, tag,
password password
.slice(0, 32) .slice(0, 32)
.padStart( .padStart(
32 + 32 + (password.slice(0, 32).length - new Blob([password]).size),
(password.slice(0, 32).length - new Blob([password]).size), "0"
"0" )
) );
);
try { saveTokenToLocalStorage({
localStorage.setItem("publicKey", publicKey); token,
localStorage.setItem("encryptedPrivateKey", encryptedPrivateKey); publicKey,
localStorage.setItem("iv", iv); encryptedPrivateKey,
localStorage.setItem("tag", tag); iv,
localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY); tag,
} catch (err) { privateKey,
setErrorLogin(true); });
console.error(
"Unable to send the tokens in local storage:" + err.message
);
}
} else {
setErrorLogin(true);
}
const userOrgs = await getOrganizations(); const userOrgs = await getOrganizations();
const userOrgsData = userOrgs.map((org) => org._id); const userOrgsData = userOrgs.map((org) => org._id);
@@ -140,14 +112,8 @@ const attemptLogin = async (
"mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net", "mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net",
"personal", "personal",
], ],
DB_USERNAME: [ DB_USERNAME: ["user1234", "personal"],
"user1234", DB_PASSWORD: ["ah8jak3hk8dhiu4dw7whxwe1l", "personal"],
"personal",
],
DB_PASSWORD: [
"ah8jak3hk8dhiu4dw7whxwe1l",
"personal",
],
TWILIO_AUTH_TOKEN: [ TWILIO_AUTH_TOKEN: [
"hgSIwDAKvz8PJfkj6xkzYqzGmAP3HLuG", "hgSIwDAKvz8PJfkj6xkzYqzGmAP3HLuG",
"shared", "shared",
@@ -156,7 +122,7 @@ const attemptLogin = async (
STRIPE_SECRET_KEY: ["sk_test_7348oyho4hfq398HIUOH78", "shared"], STRIPE_SECRET_KEY: ["sk_test_7348oyho4hfq398HIUOH78", "shared"],
}, },
workspaceId: projectToLogin, workspaceId: projectToLogin,
env: "Development" env: "Development",
}); });
} }
try { try {

View File

@@ -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. * 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, password,
setPasswordErrorLength, setPasswordErrorLength,
setPasswordErrorNumber, setPasswordErrorNumber,
setPasswordErrorLowerCase, setPasswordErrorLowerCase,
currentErrorCheck currentErrorCheck,
) => { }: PasswordCheckProps) => {
let errorCheck = currentErrorCheck; let errorCheck = currentErrorCheck;
if (!password || password.length < 14) { if (!password || password.length < 14) {
setPasswordErrorLength(true); setPasswordErrorLength(true);

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

View 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
);
}
}
};

View File

@@ -51,6 +51,7 @@
"devDependencies": { "devDependencies": {
"@tailwindcss/typography": "^0.5.4", "@tailwindcss/typography": "^0.5.4",
"@types/node": "18.11.9", "@types/node": "18.11.9",
"@types/react": "^18.0.26",
"@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0", "@typescript-eslint/parser": "^5.45.0",
"autoprefixer": "^10.4.7", "autoprefixer": "^10.4.7",
@@ -1143,9 +1144,9 @@
"optional": true "optional": true
}, },
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "18.0.15", "version": "18.0.26",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.15.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.26.tgz",
"integrity": "sha512-iz3BtLuIYH1uWdsv6wXYdhozhqj20oD4/Hk2DNXIn1kFsmp9x8d9QB6FnPhfkbhd2PgEONt9Q1x/ebkwjfFLow==", "integrity": "sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==",
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
"@types/scheduler": "*", "@types/scheduler": "*",
@@ -7978,9 +7979,9 @@
"optional": true "optional": true
}, },
"@types/react": { "@types/react": {
"version": "18.0.15", "version": "18.0.26",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.15.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.26.tgz",
"integrity": "sha512-iz3BtLuIYH1uWdsv6wXYdhozhqj20oD4/Hk2DNXIn1kFsmp9x8d9QB6FnPhfkbhd2PgEONt9Q1x/ebkwjfFLow==", "integrity": "sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==",
"requires": { "requires": {
"@types/prop-types": "*", "@types/prop-types": "*",
"@types/scheduler": "*", "@types/scheduler": "*",

View File

@@ -54,6 +54,7 @@
"devDependencies": { "devDependencies": {
"@tailwindcss/typography": "^0.5.4", "@tailwindcss/typography": "^0.5.4",
"@types/node": "18.11.9", "@types/node": "18.11.9",
"@types/react": "^18.0.26",
"@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0", "@typescript-eslint/parser": "^5.45.0",
"autoprefixer": "^10.4.7", "autoprefixer": "^10.4.7",

View File

@@ -4,6 +4,7 @@ import { config } from "@fortawesome/fontawesome-svg-core";
import { initPostHog } from "~/components/analytics/posthog"; import { initPostHog } from "~/components/analytics/posthog";
import Layout from "~/components/basic/layout"; import Layout from "~/components/basic/layout";
import NotificationProvider from "~/components/context/Notifications/NotificationProvider";
import RouteGuard from "~/components/RouteGuard"; import RouteGuard from "~/components/RouteGuard";
import { publicPaths } from "~/const"; import { publicPaths } from "~/const";
import { ENV } from "~/utilities/config"; import { ENV } from "~/utilities/config";
@@ -46,9 +47,11 @@ const App = ({ Component, pageProps, ...appProps }) => {
return ( return (
<RouteGuard> <RouteGuard>
<Layout> <NotificationProvider>
<Component {...pageProps} /> <Layout>
</Layout> <Component {...pageProps} />
</Layout>
</NotificationProvider>
</RouteGuard> </RouteGuard>
); );
}; };

View File

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

View 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;

View File

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

View 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;

View File

@@ -6,7 +6,7 @@ import SecurityClient from "~/utilities/SecurityClient";
* @param {*} res * @param {*} res
* @returns * @returns
*/ */
const getOrganizationUserProjects = (req, res) => { const getOrganizationUserProjects = (req) => {
return SecurityClient.fetchCall( return SecurityClient.fetchCall(
"/api/v1/organization/" + req.orgId + "/my-workspaces", "/api/v1/organization/" + req.orgId + "/my-workspaces",
{ {

View File

@@ -1,5 +1,13 @@
import SecurityClient from "~/utilities/SecurityClient"; 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 * This route lets us get the workspaces of a certain user
* @returns * @returns
@@ -12,10 +20,11 @@ const getWorkspaces = () => {
}, },
}).then(async (res) => { }).then(async (res) => {
if (res?.status == 200) { if (res?.status == 200) {
return (await res.json()).workspaces; const data = (await res.json()) as unknown as { workspaces: Workspaces };
} else { return data.workspaces;
console.log("Failed to get projects");
} }
throw new Error("Failed to get projects");
}); });
}; };

View File

@@ -27,6 +27,7 @@ import { Menu, Transition } from "@headlessui/react";
import Button from "~/components/basic/buttons/Button"; import Button from "~/components/basic/buttons/Button";
import ListBox from "~/components/basic/Listbox"; import ListBox from "~/components/basic/Listbox";
import BottonRightPopup from "~/components/basic/popups/BottomRightPopup"; import BottonRightPopup from "~/components/basic/popups/BottomRightPopup";
import { useNotificationContext } from "~/components/context/Notifications/NotificationProvider";
import DashboardInputField from "~/components/dashboard/DashboardInputField"; import DashboardInputField from "~/components/dashboard/DashboardInputField";
import DropZone from "~/components/dashboard/DropZone"; import DropZone from "~/components/dashboard/DropZone";
import NavHeader from "~/components/navigation/NavHeader"; import NavHeader from "~/components/navigation/NavHeader";
@@ -61,7 +62,7 @@ const KeyPair = ({
modifyValue, modifyValue,
modifyVisibility, modifyVisibility,
isBlurred, isBlurred,
duplicates duplicates,
}) => { }) => {
const [randomStringLength, setRandomStringLength] = useState(32); const [randomStringLength, setRandomStringLength] = useState(32);
const { t } = useTranslation(); const { t } = useTranslation();
@@ -233,6 +234,8 @@ export default function Dashboard() {
const { t } = useTranslation(); const { t } = useTranslation();
const { createNotification } = useNotificationContext();
// #TODO: fix save message for changing reroutes // #TODO: fix save message for changing reroutes
// const beforeRouteHandler = (url) => { // const beforeRouteHandler = (url) => {
// const warningText = // 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 // 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 nameErrors = !Object.keys(obj)
const duplicatesExist = data?.map(item => item[2]).filter((item, index) => index !== data?.map(item => item[2]).indexOf(item)).length > 0; .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) { if (nameErrors) {
console.log("Solve all name errors first!"); return createNotification({
} else if (duplicatesExist) { text: "Solve all name errors first!",
console.log("Remove the duplicated entries first!"); type: "error",
} else {
// Once "Save changes is clicked", disable that button
setButtonReady(false);
pushKeys({obj, workspaceId: router.query.id, env});
/**
* Check which integrations are active for this project and environment
* If there are any, update environment variables for those integrations
*/
let integrations = await getWorkspaceIntegrations({
workspaceId: router.query.id,
});
integrations.map(async (integration) => {
if (
envMapping[env] == integration.environment &&
integration.isActive == true
) {
let objIntegration = Object.assign(
{},
...data.map((row) => ({ [row[2]]: row[3] }))
);
await pushKeysIntegration({
obj: objIntegration,
integrationId: integration._id,
});
}
}); });
}
// If this user has never saved environment variables before, show them a prompt to read docs if (duplicatesExist) {
if (!hasUserEverPushed) { return createNotification({
setCheckDocsPopUpVisible(true); text: "Your secrets weren't saved; please fix the conflicts first.",
await registerUserAction({ action: "first_time_secrets_pushed" }); 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} modifyKey={listenChangeKey}
modifyVisibility={listenChangeVisibility} modifyVisibility={listenChangeVisibility}
isBlurred={blurred} 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> </div>
@@ -702,7 +726,13 @@ export default function Dashboard() {
modifyKey={listenChangeKey} modifyKey={listenChangeKey}
modifyVisibility={listenChangeVisibility} modifyVisibility={listenChangeVisibility}
isBlurred={blurred} 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> </div>

View File

@@ -1,4 +1,5 @@
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import ReactCodeInput from "react-code-input";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import Head from "next/head"; import Head from "next/head";
import Image from "next/image"; import Image from "next/image";
@@ -22,7 +23,7 @@ import getWorkspaces from "./api/workspace/getWorkspaces";
import useTranslation from "next-translate/useTranslation"; import useTranslation from "next-translate/useTranslation";
import Trans from "next-translate/Trans"; 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 nacl = require("tweetnacl");
const jsrp = require("jsrp"); const jsrp = require("jsrp");
nacl.util = require("tweetnacl-util"); nacl.util = require("tweetnacl-util");
@@ -44,7 +45,7 @@ const props = {
border: "1px solid gray", border: "1px solid gray",
textAlign: "center", textAlign: "center",
}, },
}; } as const;
const propsPhone = { const propsPhone = {
inputStyle: { inputStyle: {
fontFamily: "monospace", fontFamily: "monospace",
@@ -60,7 +61,7 @@ const propsPhone = {
border: "1px solid gray", border: "1px solid gray",
textAlign: "center", textAlign: "center",
}, },
}; } as const;
export default function SignUp() { export default function SignUp() {
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
@@ -89,15 +90,16 @@ export default function SignUp() {
const { t } = useTranslation(); const { t } = useTranslation();
useEffect(async () => { useEffect(() => {
let userWorkspace; const tryAuth = async () => {
try { try {
const userWorkspaces = await getWorkspaces(); const userWorkspaces = await getWorkspaces();
userWorkspace = userWorkspaces[0]._id; router.push("/dashboard/" + userWorkspaces[0]._id);
router.push("/dashboard/" + userWorkspace); } catch (error) {
} catch (error) { console.log("Error - Not logged in yet");
console.log("Error - Not logged in yet"); }
} };
tryAuth();
}, []); }, []);
/** /**
@@ -112,7 +114,7 @@ export default function SignUp() {
} else if (step == 2) { } else if (step == 2) {
// Checking if the code matches the email. // Checking if the code matches the email.
const response = await checkEmailVerificationCode(email, code); const response = await checkEmailVerificationCode(email, code);
if (response.status == "200" || code == "111222") { if (response.status === 200 || code == "111222") {
setVerificationToken((await response.json()).token); setVerificationToken((await response.json()).token);
setStep(3); setStep(3);
} else { } else {
@@ -127,7 +129,7 @@ export default function SignUp() {
* Verifies if the entered email "looks" correct * Verifies if the entered email "looks" correct
*/ */
const emailCheck = () => { const emailCheck = () => {
var emailCheckBool = false; let emailCheckBool = false;
if (!email) { if (!email) {
setEmailError(true); setEmailError(true);
setEmailErrorMessage("Please enter your email."); 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. // Verifies if the imformation that the users entered (name, workspace) is there, and if the password matched the criteria.
const signupErrorCheck = async () => { const signupErrorCheck = async () => {
setIsLoading(true); setIsLoading(true);
var errorCheck = false; let errorCheck = false;
if (!firstName) { if (!firstName) {
setFirstNameError(true); setFirstNameError(true);
errorCheck = true; errorCheck = true;
@@ -167,13 +169,13 @@ export default function SignUp() {
} else { } else {
setLastNameError(false); setLastNameError(false);
} }
errorCheck = passwordCheck( errorCheck = passwordCheck({
password, password,
setPasswordErrorLength, setPasswordErrorLength,
setPasswordErrorNumber, setPasswordErrorNumber,
setPasswordErrorLowerCase, setPasswordErrorLowerCase,
errorCheck currentErrorCheck: errorCheck,
); });
if (!errorCheck) { if (!errorCheck) {
// Generate a random pair of a public and a private key // 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), 32 + (password.slice(0, 32).length - new Blob([password]).size),
"0" "0"
) )
); ) as { ciphertext: string; iv: string; tag: string };
localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY); localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY);
client.init( client.init(
@@ -200,45 +203,47 @@ export default function SignUp() {
password: password, password: password,
}, },
async () => { async () => {
client.createVerifier(async (err, result) => { client.createVerifier(
let response = await completeAccountInformationSignup({ async (err: any, result: { salt: string; verifier: string }) => {
email, const response = await completeAccountInformationSignup({
firstName, email,
lastName, firstName,
organizationName: firstName + "'s organization", lastName,
publicKey: PUBLIC_KEY, organizationName: firstName + "'s organization",
ciphertext, publicKey: PUBLIC_KEY,
iv, ciphertext,
tag, iv,
salt: result.salt, tag,
verifier: result.verifier, salt: result.salt,
token: verificationToken, verifier: result.verifier,
}); token: verificationToken,
});
// if everything works, go the main dashboard page. // if everything works, go the main dashboard page.
if (!errorCheck && response.status == "200") { if (response.status === 200) {
response = await response.json(); // response = await response.json();
localStorage.setItem("publicKey", PUBLIC_KEY); localStorage.setItem("publicKey", PUBLIC_KEY);
localStorage.setItem("encryptedPrivateKey", ciphertext); localStorage.setItem("encryptedPrivateKey", ciphertext);
localStorage.setItem("iv", iv); localStorage.setItem("iv", iv);
localStorage.setItem("tag", tag); localStorage.setItem("tag", tag);
try { try {
await attemptLogin( await attemptLogin(
email, email,
password, password,
setErrorLogin, setErrorLogin,
router, router,
true, true,
false false
); );
incrementStep(); incrementStep();
} catch (error) { } catch (error) {
setIsLoading(false); setIsLoading(false);
}
} }
} }
}); );
} }
); );
} else { } else {
@@ -308,6 +313,8 @@ export default function SignUp() {
<div className="hidden md:block"> <div className="hidden md:block">
<ReactCodeInput <ReactCodeInput
name=""
inputMode="tel"
type="text" type="text"
fields={6} fields={6}
onChange={setCode} onChange={setCode}
@@ -317,6 +324,8 @@ export default function SignUp() {
</div> </div>
<div className="block md:hidden"> <div className="block md:hidden">
<ReactCodeInput <ReactCodeInput
name=""
inputMode="tel"
type="text" type="text"
fields={6} fields={6}
onChange={setCode} 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"> <div className="mt-2 flex flex-col items-center justify-center w-full md:p-2 rounded-lg max-h-60">
<InputField <InputField
label={t("form-password:password")} label={t("form-password:password")}
onChangeHandler={(password) => { onChangeHandler={(password: string) => {
setPassword(password); setPassword(password);
passwordCheck( passwordCheck({
password, password,
setPasswordErrorLength, setPasswordErrorLength,
setPasswordErrorNumber, setPasswordErrorNumber,
setPasswordErrorLowerCase, setPasswordErrorLowerCase,
false currentErrorCheck: false,
); });
}} }}
type="password" type="password"
value={password} value={password}
@@ -508,7 +517,7 @@ export default function SignUp() {
setBackupKeyIssued, setBackupKeyIssued,
}); });
const userWorkspaces = await getWorkspaces(); const userWorkspaces = await getWorkspaces();
let userWorkspace = userWorkspaces[0]._id; const userWorkspace = userWorkspaces[0]._id;
router.push("/home/" + userWorkspace); router.push("/home/" + userWorkspace);
}} }}
size="lg" size="lg"