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.
## 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:
![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! :)
@ -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>

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@ type ButtonProps = {
text: string;
onButtonPressed: () => void;
loading?: boolean;
color: string;
color?: string;
size: string;
icon?: IconProp;
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 {
static #token = "";
contructor() {}
constructor() {}
static setToken(token) {
this.#token = token;

View File

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

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.
* @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);

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": {
"@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": "*",

View File

@ -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",

View File

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

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
* @returns
*/
const getOrganizationUserProjects = (req, res) => {
const getOrganizationUserProjects = (req) => {
return SecurityClient.fetchCall(
"/api/v1/organization/" + req.orgId + "/my-workspaces",
{

View File

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

View File

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

View File

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