This commit is contained in:
seunghyunOh
2022-12-11 15:07:53 +09:00
parent d503102f75
commit c4b4829694
27 changed files with 745 additions and 740 deletions
+154 -156
View File
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import * as Sentry from '@sentry/node'; import * as Sentry from '@sentry/node';
@@ -5,17 +6,17 @@ import * as bigintConversion from 'bigint-conversion';
const jsrp = require('jsrp'); const jsrp = require('jsrp');
import { User } from '../models'; import { User } from '../models';
import { createToken, issueTokens, clearTokens } from '../helpers/auth'; import { createToken, issueTokens, clearTokens } from '../helpers/auth';
import { import {
NODE_ENV, NODE_ENV,
JWT_AUTH_LIFETIME, JWT_AUTH_LIFETIME,
JWT_AUTH_SECRET, JWT_AUTH_SECRET,
JWT_REFRESH_SECRET JWT_REFRESH_SECRET
} from '../config'; } from '../config';
declare module 'jsonwebtoken' { declare module 'jsonwebtoken' {
export interface UserIDJwtPayload extends jwt.JwtPayload { export interface UserIDJwtPayload extends jwt.JwtPayload {
userId: string; userId: string;
} }
} }
const clientPublicKeys: any = {}; const clientPublicKeys: any = {};
@@ -27,47 +28,45 @@ const clientPublicKeys: any = {};
* @returns * @returns
*/ */
export const login1 = async (req: Request, res: Response) => { export const login1 = async (req: Request, res: Response) => {
try { try {
const { const {
email, email,
clientPublicKey clientPublicKey
}: { email: string; clientPublicKey: string } = req.body; }: { email: string; clientPublicKey: string } = req.body;
const user = await User.findOne({
email
}).select('+salt +verifier');
if (!user) throw new Error('Failed to find user'); const user = await User.findOne({
email
}).select('+salt +verifier');
const server = new jsrp.server(); if (!user) throw new Error('Failed to find user');
server.init(
{
salt: user.salt,
verifier: user.verifier
},
() => {
// generate server-side public key
const serverPublicKey = server.getPublicKey();
clientPublicKeys[email] = {
clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt)
};
return res.status(200).send({ const server = new jsrp.server();
serverPublicKey, server.init(
salt: user.salt {
}); salt: user.salt,
} verifier: user.verifier
); },
} catch (err) { () => {
Sentry.setUser(null); // generate server-side public key
Sentry.captureException(err); const serverPublicKey = server.getPublicKey();
return res.status(400).send({ clientPublicKeys[email] = {
message: 'Failed to start authentication process' clientPublicKey,
}); serverBInt: bigintConversion.bigintToBuf(server.bInt)
} };
return res.status(200).send({
serverPublicKey,
salt: user.salt
});
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to start authentication process'
});
}
}; };
/** /**
@@ -78,59 +77,59 @@ export const login1 = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const login2 = async (req: Request, res: Response) => { export const login2 = async (req: Request, res: Response) => {
try { try {
const { email, clientProof } = req.body; const { email, clientProof } = req.body;
const user = await User.findOne({ const user = await User.findOne({
email email
}).select('+salt +verifier +publicKey +encryptedPrivateKey +iv +tag'); }).select('+salt +verifier +publicKey +encryptedPrivateKey +iv +tag');
if (!user) throw new Error('Failed to find user'); if (!user) throw new Error('Failed to find user');
const server = new jsrp.server(); const server = new jsrp.server();
server.init( server.init(
{ {
salt: user.salt, salt: user.salt,
verifier: user.verifier, verifier: user.verifier,
b: clientPublicKeys[email].serverBInt b: clientPublicKeys[email].serverBInt
}, },
async () => { async () => {
server.setClientPublicKey(clientPublicKeys[email].clientPublicKey); server.setClientPublicKey(clientPublicKeys[email].clientPublicKey);
// compare server and client shared keys // compare server and client shared keys
if (server.checkClientProof(clientProof)) { if (server.checkClientProof(clientProof)) {
// issue tokens // issue tokens
const tokens = await issueTokens({ userId: user._id.toString() }); const tokens = await issueTokens({ userId: user._id.toString() });
// store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, {
httpOnly: true,
path: '/token',
sameSite: "strict",
secure: NODE_ENV === 'production' ? true : false
});
// return (access) token in response // store (refresh) token in httpOnly cookie
return res.status(200).send({ res.cookie('jid', tokens.refreshToken, {
token: tokens.token, httpOnly: true,
publicKey: user.publicKey, path: '/token',
encryptedPrivateKey: user.encryptedPrivateKey, sameSite: 'strict',
iv: user.iv, secure: NODE_ENV === 'production' ? true : false
tag: user.tag });
});
}
return res.status(400).send({ // return (access) token in response
message: 'Failed to authenticate. Try again?' return res.status(200).send({
}); token: tokens.token,
} publicKey: user.publicKey,
); encryptedPrivateKey: user.encryptedPrivateKey,
} catch (err) { iv: user.iv,
Sentry.setUser(null); tag: user.tag
Sentry.captureException(err); });
return res.status(400).send({ }
message: 'Failed to authenticate. Try again?'
}); return res.status(400).send({
} message: 'Failed to authenticate. Try again?'
});
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to authenticate. Try again?'
});
}
}; };
/** /**
@@ -140,29 +139,29 @@ export const login2 = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const logout = async (req: Request, res: Response) => { export const logout = async (req: Request, res: Response) => {
try { try {
await clearTokens({ await clearTokens({
userId: req.user._id.toString() userId: req.user._id.toString()
}); });
// clear httpOnly cookie
res.cookie('jid', '', {
httpOnly: true,
path: '/token',
sameSite: "strict",
secure: NODE_ENV === 'production' ? true : false
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to logout'
});
}
return res.status(200).send({ // clear httpOnly cookie
message: 'Successfully logged out.' res.cookie('jid', '', {
}); httpOnly: true,
path: '/token',
sameSite: 'strict',
secure: NODE_ENV === 'production' ? true : false
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to logout'
});
}
return res.status(200).send({
message: 'Successfully logged out.'
});
}; };
/** /**
@@ -172,9 +171,9 @@ export const logout = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const checkAuth = async (req: Request, res: Response) => export const checkAuth = async (req: Request, res: Response) =>
res.status(200).send({ res.status(200).send({
message: 'Authenticated' message: 'Authenticated'
}); });
/** /**
* Return new token by redeeming refresh token * Return new token by redeeming refresh token
@@ -183,42 +182,41 @@ export const checkAuth = async (req: Request, res: Response) =>
* @returns * @returns
*/ */
export const getNewToken = async (req: Request, res: Response) => { export const getNewToken = async (req: Request, res: Response) => {
try { try {
const refreshToken = req.cookies.jid; const refreshToken = req.cookies.jid;
if (!refreshToken) {
throw new Error('Failed to find token in request cookies');
}
const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(refreshToken, JWT_REFRESH_SECRET)
);
const user = await User.findOne({
_id: decodedToken.userId
}).select('+publicKey');
if (!user) throw new Error('Failed to authenticate unfound user'); if (!refreshToken) {
if (!user?.publicKey) throw new Error('Failed to find token in request cookies');
throw new Error('Failed to authenticate not fully set up account'); }
const token = createToken({ const decodedToken = <jwt.UserIDJwtPayload>(
payload: { jwt.verify(refreshToken, JWT_REFRESH_SECRET)
userId: decodedToken.userId );
},
expiresIn: JWT_AUTH_LIFETIME, const user = await User.findOne({
secret: JWT_AUTH_SECRET _id: decodedToken.userId
}); }).select('+publicKey');
return res.status(200).send({ if (!user) throw new Error('Failed to authenticate unfound user');
token if (!user?.publicKey)
}); throw new Error('Failed to authenticate not fully set up account');
} catch (err) { const token = createToken({
Sentry.setUser(null); payload: {
Sentry.captureException(err); userId: decodedToken.userId
return res.status(400).send({ },
message: 'Invalid request' expiresIn: JWT_AUTH_LIFETIME,
}); secret: JWT_AUTH_SECRET
} });
return res.status(200).send({
token
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Invalid request'
});
}
}; };
+14 -18
View File
@@ -5,28 +5,24 @@ import { requireAuth, validateRequest } from '../middleware';
import { authController } from '../controllers'; import { authController } from '../controllers';
import { loginLimiter } from '../helpers/rateLimiter'; import { loginLimiter } from '../helpers/rateLimiter';
router.post('/token', validateRequest, authController.getNewToken);
router.post( router.post(
'/token', '/login1',
validateRequest, loginLimiter,
authController.getNewToken body('email').exists().trim().notEmpty(),
body('clientPublicKey').exists().trim().notEmpty(),
validateRequest,
authController.login1
); );
router.post( router.post(
'/login1', '/login2',
loginLimiter, loginLimiter,
body('email').exists().trim().notEmpty(), body('email').exists().trim().notEmpty(),
body('clientPublicKey').exists().trim().notEmpty(), body('clientProof').exists().trim().notEmpty(),
validateRequest, validateRequest,
authController.login1 authController.login2
);
router.post(
'/login2',
loginLimiter,
body('email').exists().trim().notEmpty(),
body('clientProof').exists().trim().notEmpty(),
validateRequest,
authController.login2
); );
router.post('/logout', requireAuth, authController.logout); router.post('/logout', requireAuth, authController.logout);
+14 -14
View File
@@ -1,9 +1,9 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from 'react';
import Image from "next/image"; import Image from 'next/image';
import { useRouter } from "next/router"; import { useRouter } from 'next/router';
import { publicPaths } from "~/const"; import { publicPaths } from '~/const';
import checkAuth from "~/pages/api/auth/CheckAuth"; import checkAuth from '~/pages/api/auth/CheckAuth';
// #TODO: finish spinner only when the data loads fully // #TODO: finish spinner only when the data loads fully
// #TODO: Redirect somewhere if the page does not exist // #TODO: Redirect somewhere if the page does not exist
@@ -22,16 +22,16 @@ export default function RouteGuard({ children }) {
// #TODO: add the loading page when not yet authorized. // #TODO: add the loading page when not yet authorized.
const hideContent = () => setAuthorized(false); const hideContent = () => setAuthorized(false);
// const onError = () => setAuthorized(true) // const onError = () => setAuthorized(true)
router.events.on("routeChangeStart", hideContent); router.events.on('routeChangeStart', hideContent);
// router.events.on("routeChangeError", onError); // router.events.on("routeChangeError", onError);
// on route change complete - run auth check // on route change complete - run auth check
router.events.on("routeChangeComplete", authCheck); router.events.on('routeChangeComplete', authCheck);
// unsubscribe from events in useEffect return function // unsubscribe from events in useEffect return function
return () => { return () => {
router.events.off("routeChangeStart", hideContent); router.events.off('routeChangeStart', hideContent);
router.events.off("routeChangeComplete", authCheck); router.events.off('routeChangeComplete', authCheck);
// router.events.off("routeChangeError", onError); // router.events.off("routeChangeError", onError);
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -43,7 +43,7 @@ export default function RouteGuard({ children }) {
*/ */
async function authCheck(url) { async function authCheck(url) {
// Make sure that we don't redirect when the user is on the following pages. // Make sure that we don't redirect when the user is on the following pages.
const path = "/" + url.split("?")[0].split("/")[1]; const path = '/' + url.split('?')[0].split('/')[1];
// Check if the user is authenticated // Check if the user is authenticated
const response = await checkAuth(); const response = await checkAuth();
@@ -51,16 +51,16 @@ export default function RouteGuard({ children }) {
if (!publicPaths.includes(path)) { if (!publicPaths.includes(path)) {
try { try {
if (response.status !== 200) { if (response.status !== 200) {
router.push("/login"); router.push('/login');
console.log("Unauthorized to access."); console.log('Unauthorized to access.');
setAuthorized(false); setAuthorized(false);
} else { } else {
setAuthorized(true); setAuthorized(true);
console.log("Authorized to access."); console.log('Authorized to access.');
} }
} catch (error) { } catch (error) {
console.log( console.log(
"Error (probably the authCheck route is stuck again...):", 'Error (probably the authCheck route is stuck again...):',
error error
); );
} }
+13 -13
View File
@@ -1,9 +1,9 @@
import React, { useState } from "react"; import React, { useState } from 'react';
import { useRouter } from "next/router"; import { useRouter } from 'next/router';
import { faCircle, 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 { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import guidGenerator from "../utilities/randomId"; import guidGenerator from '../utilities/randomId';
interface InputFieldProps { interface InputFieldProps {
static?: boolean; static?: boolean;
@@ -23,7 +23,7 @@ interface InputFieldProps {
const InputField = ( const InputField = (
props: InputFieldProps & props: InputFieldProps &
Pick<JSX.IntrinsicElements["input"], "autoComplete" | "id"> Pick<JSX.IntrinsicElements['input'], 'autoComplete' | 'id'>
) => { ) => {
const [passwordVisible, setPasswordVisible] = useState(false); const [passwordVisible, setPasswordVisible] = useState(false);
const router = useRouter(); const router = useRouter();
@@ -75,28 +75,28 @@ const InputField = (
</div> </div>
<div <div
className={`group relative flex flex-col justify-center w-full max-w-2xl border ${ className={`group relative flex flex-col justify-center w-full max-w-2xl border ${
props.error ? "border-red" : "border-mineshaft-500" props.error ? 'border-red' : 'border-mineshaft-500'
} rounded-md`} } rounded-md`}
> >
<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}
className={`${ className={`${
props.blurred props.blurred
? "text-bunker-800 group-hover:text-gray-400 focus:text-gray-400 active:text-gray-400" ? 'text-bunker-800 group-hover:text-gray-400 focus:text-gray-400 active:text-gray-400'
: "" : ''
} ${ } ${
props.error ? "focus:ring-red/50" : "focus:ring-primary/50" props.error ? 'focus:ring-red/50' : 'focus:ring-primary/50'
} relative peer bg-bunker-800 rounded-md text-gray-400 text-md p-2 w-full min-w-16 outline-none focus:ring-4 duration-200`} } relative peer bg-bunker-800 rounded-md text-gray-400 text-md p-2 w-full min-w-16 outline-none focus:ring-4 duration-200`}
name={props.name} name={props.name}
spellCheck="false" spellCheck="false"
autoComplete={props.autoComplete} autoComplete={props.autoComplete}
id={props.id} id={props.id}
/> />
{props.label?.includes("Password") && ( {props.label?.includes('Password') && (
<button <button
onClick={() => { onClick={() => {
setPasswordVisible(!passwordVisible); setPasswordVisible(!passwordVisible);
@@ -114,7 +114,7 @@ const InputField = (
<div className="peer group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible absolute h-10 w-fit max-w-xl rounded-md flex items-center text-gray-400/50 text-clip overflow-hidden"> <div className="peer group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible absolute h-10 w-fit max-w-xl rounded-md flex items-center text-gray-400/50 text-clip overflow-hidden">
<p className="ml-2"></p> <p className="ml-2"></p>
{props.value {props.value
.split("") .split('')
.slice(0, 54) .slice(0, 54)
.map(() => ( .map(() => (
<FontAwesomeIcon <FontAwesomeIcon
@@ -1,10 +1,10 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react';
import { useRouter } from "next/router"; import { useRouter } from 'next/router';
import { faX } from "@fortawesome/free-solid-svg-icons"; import { faX } from '@fortawesome/free-solid-svg-icons';
import { reverseEnvMapping } from "../../../public/data/frequentConstants"; import { reverseEnvMapping } from '../../../public/data/frequentConstants';
import guidGenerator from "../../utilities/randomId"; import guidGenerator from '../../utilities/randomId';
import Button from "../buttons/Button"; import Button from '../buttons/Button';
/** /**
* This is the component that we utilize for the user table - in future, can reuse it for some other purposes too. * This is the component that we utilize for the user table - in future, can reuse it for some other purposes too.
+38 -38
View File
@@ -1,25 +1,25 @@
import React, { useEffect, useMemo, useState } from "react"; import React, { useEffect, useMemo, useState } from 'react';
import { useRouter } from "next/router"; import { useRouter } from 'next/router';
import { faX } from "@fortawesome/free-solid-svg-icons"; import { faX } from '@fortawesome/free-solid-svg-icons';
import deleteUserFromOrganization from "~/pages/api/organization/deleteUserFromOrganization"; import deleteUserFromOrganization from '~/pages/api/organization/deleteUserFromOrganization';
import changeUserRoleInWorkspace from "~/pages/api/workspace/changeUserRoleInWorkspace"; import changeUserRoleInWorkspace from '~/pages/api/workspace/changeUserRoleInWorkspace';
import deleteUserFromWorkspace from "~/pages/api/workspace/deleteUserFromWorkspace"; import deleteUserFromWorkspace from '~/pages/api/workspace/deleteUserFromWorkspace';
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey"; import getLatestFileKey from '~/pages/api/workspace/getLatestFileKey';
import uploadKeys from "~/pages/api/workspace/uploadKeys"; import uploadKeys from '~/pages/api/workspace/uploadKeys';
import guidGenerator from "../../utilities/randomId"; import guidGenerator from '../../utilities/randomId';
import Button from "../buttons/Button"; import Button from '../buttons/Button';
import Listbox from "../Listbox"; import Listbox from '../Listbox';
const { const {
decryptAssymmetric, decryptAssymmetric,
encryptAssymmetric, encryptAssymmetric
} = require("../../utilities/cryptography/crypto"); } = require('../../utilities/cryptography/crypto');
const nacl = require("tweetnacl"); const nacl = require('tweetnacl');
nacl.util = require("tweetnacl-util"); nacl.util = require('tweetnacl-util');
const roles = ["admin", "user"]; const roles = ['admin', 'user'];
/** /**
* This is the component that we utilize for the user table - in future, can reuse it for some other purposes too. * This is the component that we utilize for the user table - in future, can reuse it for some other purposes too.
@@ -36,13 +36,13 @@ const UserTable = ({
isOrg, isOrg,
onClick, onClick,
deleteUser, deleteUser,
setUserIdToBeDeleted, setUserIdToBeDeleted
}) => { }) => {
const [roleSelected, setRoleSelected] = useState( const [roleSelected, setRoleSelected] = useState(
Array(userData?.length).fill(userData.map((user) => user.role)) Array(userData?.length).fill(userData.map((user) => user.role))
); );
const router = useRouter(); const router = useRouter();
const [myRole, setMyRole] = useState("member"); const [myRole, setMyRole] = useState('member');
// Delete the row in the table (e.g. a user) // Delete the row in the table (e.g. a user)
// #TODO: Add a pop-up that warns you that the user is going to be deleted. // #TODO: Add a pop-up that warns you that the user is going to be deleted.
@@ -57,7 +57,7 @@ const UserTable = ({
changeData(userData.filter((v, i) => i !== index)); changeData(userData.filter((v, i) => i !== index));
setRoleSelected([ setRoleSelected([
...roleSelected.slice(0, index), ...roleSelected.slice(0, index),
...roleSelected.slice(index + 1, userData?.length), ...roleSelected.slice(index + 1, userData?.length)
]); ]);
}; };
@@ -76,10 +76,10 @@ const UserTable = ({
status: userData[index].status, status: userData[index].status,
userId: userData[index].userId, userId: userData[index].userId,
membershipId: userData[index].membershipId, membershipId: userData[index].membershipId,
publicKey: userData[index].publicKey, publicKey: userData[index].publicKey
}, }
], ],
...userData.slice(index + 1, userData?.length), ...userData.slice(index + 1, userData?.length)
]); ]);
}; };
@@ -88,22 +88,22 @@ const UserTable = ({
}, [userData, myUser]); }, [userData, myUser]);
const grantAccess = async (id, publicKey) => { const grantAccess = async (id, publicKey) => {
let result = await getLatestFileKey({workspaceId: router.query.id}); let result = await getLatestFileKey({ workspaceId: router.query.id });
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY');
// assymmetrically decrypt symmetric key with local private key // assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({ const key = decryptAssymmetric({
ciphertext: result.latestKey.encryptedKey, ciphertext: result.latestKey.encryptedKey,
nonce: result.latestKey.nonce, nonce: result.latestKey.nonce,
publicKey: result.latestKey.sender.publicKey, publicKey: result.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY, privateKey: PRIVATE_KEY
}); });
const { ciphertext, nonce } = encryptAssymmetric({ const { ciphertext, nonce } = encryptAssymmetric({
plaintext: key, plaintext: key,
publicKey: publicKey, publicKey: publicKey,
privateKey: PRIVATE_KEY, privateKey: PRIVATE_KEY
}); });
uploadKeys(router.query.id, id, ciphertext, nonce); uploadKeys(router.query.id, id, ciphertext, nonce);
@@ -158,24 +158,24 @@ const UserTable = ({
</td> </td>
<td className="flex flex-row justify-end pr-8 py-2 border-t border-0.5 border-mineshaft-700"> <td className="flex flex-row justify-end pr-8 py-2 border-t border-0.5 border-mineshaft-700">
<div className="flex justify-end mr-6 w-3/4 mx-2 w-full h-full flex flex-row items-center"> <div className="flex justify-end mr-6 w-3/4 mx-2 w-full h-full flex flex-row items-center">
{row.status == "granted" && {row.status == 'granted' &&
((myRole == "admin" && row.role != "owner") || ((myRole == 'admin' && row.role != 'owner') ||
myRole == "owner") && myRole == 'owner') &&
myUser !== row.email ? ( myUser !== row.email ? (
<Listbox <Listbox
selected={row.role} selected={row.role}
onChange={(e) => handleRoleUpdate(index, e)} onChange={(e) => handleRoleUpdate(index, e)}
data={ data={
myRole == "owner" myRole == 'owner'
? ["owner", "admin", "member"] ? ['owner', 'admin', 'member']
: ["admin", "member"] : ['admin', 'member']
} }
text="Role: " text="Role: "
membershipId={row.membershipId} membershipId={row.membershipId}
/> />
) : ( ) : (
row.status != "invited" && row.status != 'invited' &&
row.status != "verified" && ( row.status != 'verified' && (
<Listbox <Listbox
selected={row.role} selected={row.role}
text="Role: " text="Role: "
@@ -183,8 +183,8 @@ const UserTable = ({
/> />
) )
)} )}
{(row.status == "invited" || {(row.status == 'invited' ||
row.status == "verified") && ( row.status == 'verified') && (
<div className="w-full pl-9"> <div className="w-full pl-9">
<Button <Button
onButtonPressed={() => onButtonPressed={() =>
@@ -199,7 +199,7 @@ const UserTable = ({
/> />
</div> </div>
)} )}
{row.status == "completed" && myUser !== row.email && ( {row.status == 'completed' && myUser !== row.email && (
<div className="border border-mineshaft-700 rounded-md bg-white/5 hover:bg-primary text-white hover:text-black duration-200"> <div className="border border-mineshaft-700 rounded-md bg-white/5 hover:bg-primary text-white hover:text-black duration-200">
<Button <Button
onButtonPressed={() => onButtonPressed={() =>
@@ -214,7 +214,7 @@ const UserTable = ({
</div> </div>
{myUser !== row.email && {myUser !== row.email &&
// row.role != "admin" && // row.role != "admin" &&
myRole != "member" ? ( myRole != 'member' ? (
<div className="opacity-50 hover:opacity-100 flex items-center"> <div className="opacity-50 hover:opacity-100 flex items-center">
<Button <Button
onButtonPressed={(e) => onButtonPressed={(e) =>
@@ -1,8 +1,8 @@
import React, { SyntheticEvent, useRef } from "react"; import React, { SyntheticEvent, useRef } from 'react';
import { faCircle } from "@fortawesome/free-solid-svg-icons"; import { faCircle } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import guidGenerator from "../utilities/randomId"; import guidGenerator from '../utilities/randomId';
const REGEX = /([$]{.*?})/g; const REGEX = /([$]{.*?})/g;
@@ -10,7 +10,7 @@ interface DashboardInputFieldProps {
index: number; index: number;
onChangeHandler: (value: string, index: number) => void; onChangeHandler: (value: string, index: number) => void;
value: string; value: string;
type: "varName" | "value"; type: 'varName' | 'value';
blurred: boolean; blurred: boolean;
duplicates: string[]; duplicates: string[];
} }
@@ -33,7 +33,7 @@ const DashboardInputField = ({
type, type,
value, value,
blurred, blurred,
duplicates, duplicates
}: DashboardInputFieldProps) => { }: DashboardInputFieldProps) => {
const ref = useRef<HTMLDivElement | null>(null); const ref = useRef<HTMLDivElement | null>(null);
const syncScroll = (e: SyntheticEvent<HTMLDivElement>) => { const syncScroll = (e: SyntheticEvent<HTMLDivElement>) => {
@@ -43,8 +43,8 @@ const DashboardInputField = ({
ref.current.scrollLeft = e.currentTarget.scrollLeft; ref.current.scrollLeft = e.currentTarget.scrollLeft;
}; };
if (type === "varName") { if (type === 'varName') {
const startsWithNumber = !isNaN(Number(value.charAt(0))) && value != ""; const startsWithNumber = !isNaN(Number(value.charAt(0))) && value != '';
const hasDuplicates = duplicates?.includes(value); const hasDuplicates = duplicates?.includes(value);
const error = startsWithNumber || hasDuplicates; const error = startsWithNumber || hasDuplicates;
@@ -52,7 +52,7 @@ const DashboardInputField = ({
<div className="flex-col w-full"> <div className="flex-col w-full">
<div <div
className={`group relative flex flex-col justify-center w-full max-w-2xl border ${ className={`group relative flex flex-col justify-center w-full max-w-2xl border ${
error ? "border-red" : "border-mineshaft-500" error ? 'border-red' : 'border-mineshaft-500'
} rounded-md`} } rounded-md`}
> >
<input <input
@@ -62,7 +62,7 @@ const DashboardInputField = ({
type={type} type={type}
value={value} value={value}
className={`z-10 peer font-mono ph-no-capture bg-bunker-800 rounded-md caret-white text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-2 ${ className={`z-10 peer font-mono ph-no-capture bg-bunker-800 rounded-md caret-white text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-2 ${
error ? "focus:ring-red/50" : "focus:ring-primary/50" error ? 'focus:ring-red/50' : 'focus:ring-primary/50'
} duration-200`} } duration-200`}
spellCheck="false" spellCheck="false"
/> />
@@ -79,7 +79,7 @@ const DashboardInputField = ({
)} )}
</div> </div>
); );
} else if (type === "value") { } else if (type === 'value') {
return ( return (
<div className="flex-col w-full"> <div className="flex-col w-full">
<div <div
@@ -91,8 +91,8 @@ const DashboardInputField = ({
onScroll={syncScroll} onScroll={syncScroll}
className={`${ className={`${
blurred blurred
? "text-transparent group-hover:text-transparent focus:text-transparent active:text-transparent" ? 'text-transparent group-hover:text-transparent focus:text-transparent active:text-transparent'
: "" : ''
} z-10 peer font-mono ph-no-capture bg-transparent rounded-md caret-white text-transparent text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-2 focus:ring-primary/50 duration-200 no-scrollbar no-scrollbar::-webkit-scrollbar`} } z-10 peer font-mono ph-no-capture bg-transparent rounded-md caret-white text-transparent text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-2 focus:ring-primary/50 duration-200 no-scrollbar no-scrollbar::-webkit-scrollbar`}
spellCheck="false" spellCheck="false"
/> />
@@ -100,8 +100,8 @@ const DashboardInputField = ({
ref={ref} ref={ref}
className={`${ className={`${
blurred blurred
? "text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-400 peer-active:text-gray-400" ? 'text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-400 peer-active:text-gray-400'
: "" : ''
} absolute flex flex-row whitespace-pre font-mono z-0 ph-no-capture max-w-2xl overflow-x-scroll bg-bunker-800 h-9 rounded-md text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-2 focus:ring-primary/50 duration-100 no-scrollbar no-scrollbar::-webkit-scrollbar`} } absolute flex flex-row whitespace-pre font-mono z-0 ph-no-capture max-w-2xl overflow-x-scroll bg-bunker-800 h-9 rounded-md text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-2 focus:ring-primary/50 duration-100 no-scrollbar no-scrollbar::-webkit-scrollbar`}
> >
{value.split(REGEX).map((word, id) => { {value.split(REGEX).map((word, id) => {
@@ -112,7 +112,7 @@ const DashboardInputField = ({
<span className="ph-no-capture text-yellow-200/80"> <span className="ph-no-capture text-yellow-200/80">
{word.slice(2, word.length - 1)} {word.slice(2, word.length - 1)}
</span> </span>
{word.slice(word.length - 1, word.length) == "}" ? ( {word.slice(word.length - 1, word.length) == '}' ? (
<span className="ph-no-capture text-yellow"> <span className="ph-no-capture text-yellow">
{word.slice(word.length - 1, word.length)} {word.slice(word.length - 1, word.length)}
</span> </span>
@@ -135,7 +135,7 @@ const DashboardInputField = ({
{blurred && ( {blurred && (
<div className="absolute flex flex-row items-center z-20 peer pr-2 bg-bunker-800 group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible h-9 w-full max-w-2xl rounded-md text-gray-400/50 text-clip"> <div className="absolute flex flex-row items-center z-20 peer pr-2 bg-bunker-800 group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible h-9 w-full max-w-2xl rounded-md text-gray-400/50 text-clip">
<div className="px-2 flex flex-row items-center overflow-x-scroll no-scrollbar no-scrollbar::-webkit-scrollbar"> <div className="px-2 flex flex-row items-center overflow-x-scroll no-scrollbar no-scrollbar::-webkit-scrollbar">
{value.split("").map(() => ( {value.split('').map(() => (
<FontAwesomeIcon <FontAwesomeIcon
key={guidGenerator()} key={guidGenerator()}
className="text-xxs mx-0.5" className="text-xxs mx-0.5"
+17 -17
View File
@@ -1,12 +1,12 @@
import { type ChangeEvent, type DragEvent, useState } from "react"; import { type ChangeEvent, type DragEvent, useState } from 'react';
import Image from "next/image"; import Image from 'next/image';
import { faUpload } from "@fortawesome/free-solid-svg-icons"; import { faUpload } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import Button from "../basic/buttons/Button"; import Button from '../basic/buttons/Button';
import Error from "../basic/Error"; import Error from '../basic/Error';
import parse from "../utilities/file"; import parse from '../utilities/file';
import guidGenerator from "../utilities/randomId"; import guidGenerator from '../utilities/randomId';
interface DropZoneProps { interface DropZoneProps {
// TODO: change Data type from any // TODO: change Data type from any
@@ -26,7 +26,7 @@ const DropZone = ({
errorDragAndDrop, errorDragAndDrop,
setButtonReady, setButtonReady,
keysExist, keysExist,
numCurrentRows, numCurrentRows
}: DropZoneProps) => { }: DropZoneProps) => {
const handleDragEnter = (e: DragEvent) => { const handleDragEnter = (e: DragEvent) => {
e.preventDefault(); e.preventDefault();
@@ -43,7 +43,7 @@ const DropZone = ({
e.stopPropagation(); e.stopPropagation();
// set dropEffect to copy i.e copy of the source item // set dropEffect to copy i.e copy of the source item
e.dataTransfer.dropEffect = "copy"; e.dataTransfer.dropEffect = 'copy';
}; };
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -54,7 +54,7 @@ const DropZone = ({
setTimeout(() => setLoading(false), 5000); setTimeout(() => setLoading(false), 5000);
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
e.dataTransfer.dropEffect = "copy"; e.dataTransfer.dropEffect = 'copy';
const file = e.dataTransfer.files[0]; const file = e.dataTransfer.files[0];
const reader = new FileReader(); const reader = new FileReader();
@@ -68,7 +68,7 @@ const DropZone = ({
numCurrentRows + index, numCurrentRows + index,
key, key,
keyPairs[key as keyof typeof keyPairs], keyPairs[key as keyof typeof keyPairs],
"shared", 'shared'
]); ]);
setData(newData); setData(newData);
setButtonReady(true); setButtonReady(true);
@@ -94,15 +94,15 @@ const DropZone = ({
reader.onload = (event) => { reader.onload = (event) => {
if (event.target === null || event.target.result === null) return; if (event.target === null || event.target.result === null) return;
const { result } = event.target; const { result } = event.target;
if (typeof result === "string") { if (typeof result === 'string') {
const newData = result const newData = result
.split("\n") .split('\n')
.map((line: string, index: number) => [ .map((line: string, index: number) => [
guidGenerator(), guidGenerator(),
numCurrentRows + index, numCurrentRows + index,
line.split("=")[0], line.split('=')[0],
line.split("=").slice(1, line.split("=").length).join("="), line.split('=').slice(1, line.split('=').length).join('='),
"shared", 'shared'
]); ]);
setData(newData); setData(newData);
setButtonReady(true); setButtonReady(true);
@@ -1,10 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import React, { Fragment, useEffect, useState } from "react"; import React, { Fragment, useEffect, useState } from 'react';
import Image from "next/image"; import Image from 'next/image';
import { useRouter } from "next/router"; import { useRouter } from 'next/router';
import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons"; import { faGithub, faSlack } from '@fortawesome/free-brands-svg-icons';
import { faCircleQuestion } from "@fortawesome/free-regular-svg-icons"; import { faCircleQuestion } from '@fortawesome/free-regular-svg-icons';
import { import {
faAngleDown, faAngleDown,
faBook, faBook,
@@ -12,39 +12,39 @@ import {
faEnvelope, faEnvelope,
faGear, faGear,
faPlus, faPlus,
faRightFromBracket, faRightFromBracket
} from "@fortawesome/free-solid-svg-icons"; } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Menu, Transition } from "@headlessui/react"; import { Menu, Transition } from '@headlessui/react';
import logout from "~/pages/api/auth/Logout"; import logout from '~/pages/api/auth/Logout';
import getOrganization from "~/pages/api/organization/GetOrg"; import getOrganization from '~/pages/api/organization/GetOrg';
import getOrganizations from "~/pages/api/organization/getOrgs"; import getOrganizations from '~/pages/api/organization/getOrgs';
import getUser from "~/pages/api/user/getUser"; import getUser from '~/pages/api/user/getUser';
import guidGenerator from "../utilities/randomId"; import guidGenerator from '../utilities/randomId';
const supportOptions = [ const supportOptions = [
[ [
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faSlack} />, <FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faSlack} />,
"Join Slack Forum", 'Join Slack Forum',
"https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g", 'https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g'
], ],
[ [
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faBook} />, <FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faBook} />,
"Read Docs", 'Read Docs',
"https://infisical.com/docs/getting-started/introduction", 'https://infisical.com/docs/getting-started/introduction'
], ],
[ [
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faGithub} />, <FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faGithub} />,
"Open a GitHub Issue", 'Open a GitHub Issue',
"https://github.com/Infisical/infisical-cli/issues", 'https://github.com/Infisical/infisical-cli/issues'
], ],
[ [
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faEnvelope} />, <FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faEnvelope} />,
"Send us an Email", 'Send us an Email',
"mailto:[email protected]", 'mailto:[email protected]'
], ]
]; ];
export interface ICurrentOrg { export interface ICurrentOrg {
@@ -58,7 +58,7 @@ export interface IUser {
} }
/** /**
* This is the navigation bar in the main app. * This is the navigation bar in the main app.
* It has two main components: support options and user menu (inlcudes billing, logout, org/user settings) * It has two main components: support options and user menu (inlcudes billing, logout, org/user settings)
* @returns NavBar * @returns NavBar
*/ */
@@ -75,16 +75,16 @@ export default function Navbar() {
const orgsData = await getOrganizations(); const orgsData = await getOrganizations();
setOrgs(orgsData); setOrgs(orgsData);
const currentOrg = await getOrganization({ const currentOrg = await getOrganization({
orgId: String(localStorage.getItem("orgData.id")), orgId: String(localStorage.getItem('orgData.id'))
}); });
setCurrentOrg(currentOrg); setCurrentOrg(currentOrg);
})(); })();
}, []); }, []);
const closeApp = async () => { const closeApp = async () => {
console.log("Logging out..."); console.log('Logging out...');
await logout(); await logout();
router.push("/login"); router.push('/login');
}; };
return ( return (
@@ -163,7 +163,7 @@ export default function Navbar() {
</div> </div>
<div <div
onClick={() => onClick={() =>
router.push("/settings/personal/" + router.query.id) router.push('/settings/personal/' + router.query.id)
} }
className="flex flex-row items-center px-1 mx-1 my-1 hover:bg-white/5 cursor-pointer rounded-md" className="flex flex-row items-center px-1 mx-1 my-1 hover:bg-white/5 cursor-pointer rounded-md"
> >
@@ -173,11 +173,11 @@ export default function Navbar() {
<div className="flex items-center justify-between w-full"> <div className="flex items-center justify-between w-full">
<div> <div>
<p className="text-gray-300 px-2 pt-1 text-sm"> <p className="text-gray-300 px-2 pt-1 text-sm">
{" "} {' '}
{user?.firstName} {user?.lastName} {user?.firstName} {user?.lastName}
</p> </p>
<p className="text-gray-400 px-2 pb-1 text-xs"> <p className="text-gray-400 px-2 pb-1 text-xs">
{" "} {' '}
{user?.email} {user?.email}
</p> </p>
</div> </div>
@@ -194,7 +194,7 @@ export default function Navbar() {
</div> </div>
<div <div
onClick={() => onClick={() =>
router.push("/settings/org/" + router.query.id) router.push('/settings/org/' + router.query.id)
} }
className="flex flex-row items-center px-2 mt-2 py-1 hover:bg-white/5 cursor-pointer rounded-md" className="flex flex-row items-center px-2 mt-2 py-1 hover:bg-white/5 cursor-pointer rounded-md"
> >
@@ -217,7 +217,7 @@ export default function Navbar() {
> >
<div <div
onClick={() => onClick={() =>
router.push("/settings/billing/" + router.query.id) router.push('/settings/billing/' + router.query.id)
} }
className="mt-1 relative flex justify-start cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/5 duration-200 hover:text-gray-200" className="mt-1 relative flex justify-start cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/5 duration-200 hover:text-gray-200"
> >
@@ -235,7 +235,7 @@ export default function Navbar() {
<div <div
onClick={() => onClick={() =>
router.push( router.push(
"/settings/org/" + router.query.id + "?invite" '/settings/org/' + router.query.id + '?invite'
) )
} }
className="relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-primary/100 duration-200 hover:text-black hover:font-semibold mt-1" className="relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-primary/100 duration-200 hover:text-black hover:font-semibold mt-1"
@@ -255,13 +255,14 @@ export default function Navbar() {
<div className="flex flex-col items-start px-1 mt-3 mb-2"> <div className="flex flex-col items-start px-1 mt-3 mb-2">
{orgs {orgs
.filter( .filter(
(org : { _id: string }) => org._id != localStorage.getItem("orgData.id") (org: { _id: string }) =>
org._id != localStorage.getItem('orgData.id')
) )
.map((org : { _id: string; name: string; }) => ( .map((org: { _id: string; name: string }) => (
<div <div
key={guidGenerator()} key={guidGenerator()}
onClick={() => { onClick={() => {
localStorage.setItem("orgData.id", org._id); localStorage.setItem('orgData.id', org._id);
router.reload(); router.reload();
}} }}
className="flex flex-row justify-start items-center hover:bg-white/5 w-full p-1.5 cursor-pointer rounded-md" className="flex flex-row justify-start items-center hover:bg-white/5 w-full p-1.5 cursor-pointer rounded-md"
@@ -286,8 +287,8 @@ export default function Navbar() {
onClick={closeApp} onClick={closeApp}
className={`${ className={`${
active active
? "bg-red font-semibold text-white" ? 'bg-red font-semibold text-white'
: "text-gray-400" : 'text-gray-400'
} group flex w-full items-center rounded-md px-2 py-2 text-sm`} } group flex w-full items-center rounded-md px-2 py-2 text-sm`}
> >
<div className="relative flex justify-start items-center cursor-pointer select-none"> <div className="relative flex justify-start items-center cursor-pointer select-none">
+36 -36
View File
@@ -1,17 +1,17 @@
import Aes256Gcm from "~/components/utilities/cryptography/aes-256-gcm"; import Aes256Gcm from '~/components/utilities/cryptography/aes-256-gcm';
import login1 from "~/pages/api/auth/Login1"; import login1 from '~/pages/api/auth/Login1';
import login2 from "~/pages/api/auth/Login2"; import login2 from '~/pages/api/auth/Login2';
import getOrganizations from "~/pages/api/organization/getOrgs"; import getOrganizations from '~/pages/api/organization/getOrgs';
import getOrganizationUserProjects from "~/pages/api/organization/GetOrgUserProjects"; import getOrganizationUserProjects from '~/pages/api/organization/GetOrgUserProjects';
import pushKeys from "./secrets/pushKeys"; import pushKeys from './secrets/pushKeys';
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage"; import Telemetry from './telemetry/Telemetry';
import SecurityClient from "./SecurityClient"; import { saveTokenToLocalStorage } from './saveTokenToLocalStorage';
import Telemetry from "./telemetry/Telemetry"; import SecurityClient from './SecurityClient';
const nacl = require("tweetnacl"); const nacl = require('tweetnacl');
nacl.util = require("tweetnacl-util"); nacl.util = require('tweetnacl-util');
const jsrp = require("jsrp"); const jsrp = require('jsrp');
const client = new jsrp.client(); const client = new jsrp.client();
/** /**
@@ -37,7 +37,7 @@ const attemptLogin = async (
client.init( client.init(
{ {
username: email, username: email,
password: password, password: password
}, },
async () => { async () => {
const clientPublicKey = client.getPublicKey(); const clientPublicKey = client.getPublicKey();
@@ -62,7 +62,7 @@ const attemptLogin = async (
.slice(0, 32) .slice(0, 32)
.padStart( .padStart(
32 + (password.slice(0, 32).length - new Blob([password]).size), 32 + (password.slice(0, 32).length - new Blob([password]).size),
"0" '0'
) )
); );
@@ -72,36 +72,36 @@ const attemptLogin = async (
encryptedPrivateKey, encryptedPrivateKey,
iv, iv,
tag, tag,
privateKey, privateKey
}); });
const userOrgs = await getOrganizations(); const userOrgs = await getOrganizations();
const userOrgsData = userOrgs.map((org) => org._id); const userOrgsData = userOrgs.map((org) => org._id);
let orgToLogin; let orgToLogin;
if (userOrgsData.includes(localStorage.getItem("orgData.id"))) { if (userOrgsData.includes(localStorage.getItem('orgData.id'))) {
orgToLogin = localStorage.getItem("orgData.id"); orgToLogin = localStorage.getItem('orgData.id');
} else { } else {
orgToLogin = userOrgsData[0]; orgToLogin = userOrgsData[0];
localStorage.setItem("orgData.id", orgToLogin); localStorage.setItem('orgData.id', orgToLogin);
} }
let orgUserProjects = await getOrganizationUserProjects({ let orgUserProjects = await getOrganizationUserProjects({
orgId: orgToLogin, orgId: orgToLogin
}); });
orgUserProjects = orgUserProjects?.map((project) => project._id); orgUserProjects = orgUserProjects?.map((project) => project._id);
let projectToLogin; let projectToLogin;
if ( if (
orgUserProjects.includes(localStorage.getItem("projectData.id")) orgUserProjects.includes(localStorage.getItem('projectData.id'))
) { ) {
projectToLogin = localStorage.getItem("projectData.id"); projectToLogin = localStorage.getItem('projectData.id');
} else { } else {
try { try {
projectToLogin = orgUserProjects[0]; projectToLogin = orgUserProjects[0];
localStorage.setItem("projectData.id", projectToLogin); localStorage.setItem('projectData.id', projectToLogin);
} catch (error) { } catch (error) {
console.log("ERROR: User likely has no projects. ", error); console.log('ERROR: User likely has no projects. ', error);
} }
} }
@@ -110,38 +110,38 @@ const attemptLogin = async (
await pushKeys({ await pushKeys({
obj: { obj: {
DATABASE_URL: [ DATABASE_URL: [
"mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net", 'mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net',
"personal", 'personal'
], ],
DB_USERNAME: ["user1234", "personal"], DB_USERNAME: ['user1234', 'personal'],
DB_PASSWORD: ["ah8jak3hk8dhiu4dw7whxwe1l", "personal"], DB_PASSWORD: ['ah8jak3hk8dhiu4dw7whxwe1l', 'personal'],
TWILIO_AUTH_TOKEN: [ TWILIO_AUTH_TOKEN: [
"hgSIwDAKvz8PJfkj6xkzYqzGmAP3HLuG", 'hgSIwDAKvz8PJfkj6xkzYqzGmAP3HLuG',
"shared", 'shared'
], ],
WEBSITE_URL: ["http://localhost:3000", "shared"], WEBSITE_URL: ['http://localhost:3000', 'shared'],
STRIPE_SECRET_KEY: ["sk_test_7348oyho4hfq398HIUOH78", "shared"], STRIPE_SECRET_KEY: ['sk_test_7348oyho4hfq398HIUOH78', 'shared']
}, },
workspaceId: projectToLogin, workspaceId: projectToLogin,
env: "Development", env: 'Development'
}); });
} }
if (email) { if (email) {
telemetry.identify(email); telemetry.identify(email);
telemetry.capture("User Logged In"); telemetry.capture('User Logged In');
} }
if (isLogin) { if (isLogin) {
router.push("/dashboard/"); router.push('/dashboard/');
} }
} catch (error) { } catch (error) {
setErrorLogin(true); setErrorLogin(true);
console.log("Login response not available"); console.log('Login response not available');
} }
} }
); );
} catch (error) { } catch (error) {
console.log("Something went wrong during authentication"); console.log('Something went wrong during authentication');
} }
return true; return true;
}; };
+1 -1
View File
@@ -1,4 +1,4 @@
import SecurityClient from '~/utilities/SecurityClient.js'; import SecurityClient from '~/utilities/SecurityClient';
/** /**
* This function is used to check if the user is authenticated. * This function is used to check if the user is authenticated.
+13 -13
View File
@@ -1,29 +1,29 @@
import SecurityClient from "~/utilities/SecurityClient"; import SecurityClient from '~/utilities/SecurityClient';
/** /**
* This route logs the user out. Note: the user should authorized to do this. * This route logs the user out. Note: the user should authorized to do this.
* We first try to log out - if the authorization fails (response.status = 401), we refetch the new token, and then retry * We first try to log out - if the authorization fails (response.status = 401), we refetch the new token, and then retry
*/ */
const logout = async () => { const logout = async () => {
return SecurityClient.fetchCall("/api/v1/auth/logout", { return SecurityClient.fetchCall('/api/v1/auth/logout', {
method: "POST", method: 'POST',
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json'
}, },
credentials: "include", credentials: 'include'
}).then((res) => { }).then((res) => {
if (res?.status == 200) { if (res?.status == 200) {
SecurityClient.setToken(""); SecurityClient.setToken('');
// Delete the cookie by not setting a value; Alternatively clear the local storage // Delete the cookie by not setting a value; Alternatively clear the local storage
localStorage.setItem("publicKey", ""); localStorage.setItem('publicKey', '');
localStorage.setItem("encryptedPrivateKey", ""); localStorage.setItem('encryptedPrivateKey', '');
localStorage.setItem("iv", ""); localStorage.setItem('iv', '');
localStorage.setItem("tag", ""); localStorage.setItem('tag', '');
localStorage.setItem("PRIVATE_KEY", ""); localStorage.setItem('PRIVATE_KEY', '');
console.log("User logged out", res); console.log('User logged out', res);
return res; return res;
} else { } else {
console.log("Failed to log out"); console.log('Failed to log out');
} }
}); });
}; };
+1 -1
View File
@@ -1,4 +1,4 @@
import SecurityClient from '~/utilities/SecurityClient.js'; import SecurityClient from '~/utilities/SecurityClient';
/** /**
* This function fetches the encrypted secrets from the .env file * This function fetches the encrypted secrets from the .env file
+7 -7
View File
@@ -1,21 +1,21 @@
import SecurityClient from "~/utilities/SecurityClient"; import SecurityClient from '~/utilities/SecurityClient';
/** /**
* This route lets us get info about a certain org * This route lets us get info about a certain org
* @param {string} orgId - the organization ID * @param {string} orgId - the organization ID
* @returns * @returns
*/ */
const getOrganization = ({ orgId }: { orgId: string; }) => { const getOrganization = ({ orgId }: { orgId: string }) => {
return SecurityClient.fetchCall("/api/v1/organization/" + orgId, { return SecurityClient.fetchCall('/api/v1/organization/' + orgId, {
method: "GET", method: 'GET',
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json'
}, }
}).then(async (res) => { }).then(async (res) => {
if (res?.status == 200) { if (res?.status == 200) {
return (await res.json()).organization; return (await res.json()).organization;
} else { } else {
console.log("Failed to get org info"); console.log('Failed to get org info');
} }
}); });
}; };
+8 -11
View File
@@ -1,4 +1,4 @@
import SecurityClient from "~/utilities/SecurityClient"; import SecurityClient from '~/utilities/SecurityClient';
/** /**
* This route lets us get all the users in an org. * This route lets us get all the users in an org.
@@ -6,20 +6,17 @@ import SecurityClient from "~/utilities/SecurityClient";
* @param {string} obj.orgId - organization Id * @param {string} obj.orgId - organization Id
* @returns * @returns
*/ */
const getOrganizationUsers = ({ orgId }: { orgId: string; }) => { const getOrganizationUsers = ({ orgId }: { orgId: string }) => {
return SecurityClient.fetchCall( return SecurityClient.fetchCall('/api/v1/organization/' + orgId + '/users', {
"/api/v1/organization/" + orgId + "/users", method: 'GET',
{ headers: {
method: "GET", 'Content-Type': 'application/json'
headers: {
"Content-Type": "application/json",
},
} }
).then(async (res) => { }).then(async (res) => {
if (res?.status == 200) { if (res?.status == 200) {
return (await res.json()).users; return (await res.json()).users;
} else { } else {
console.log("Failed to get org users"); console.log('Failed to get org users');
} }
}); });
}; };
+6 -6
View File
@@ -1,20 +1,20 @@
import SecurityClient from "~/utilities/SecurityClient"; import SecurityClient from '~/utilities/SecurityClient';
/** /**
* This route lets us get the all the orgs of a certain user. * This route lets us get the all the orgs of a certain user.
* @returns * @returns
*/ */
const getOrganizations = () => { const getOrganizations = () => {
return SecurityClient.fetchCall("/api/v1/organization", { return SecurityClient.fetchCall('/api/v1/organization', {
method: "GET", method: 'GET',
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json'
}, }
}).then(async (res) => { }).then(async (res) => {
if (res?.status == 200) { if (res?.status == 200) {
return (await res.json()).organizations; return (await res.json()).organizations;
} else { } else {
console.log("Failed to get orgs of a user"); console.log('Failed to get orgs of a user');
} }
}); });
}; };
+6 -6
View File
@@ -1,19 +1,19 @@
import SecurityClient from "~/utilities/SecurityClient"; import SecurityClient from '~/utilities/SecurityClient';
/** /**
* This route gets the information about a specific user. * This route gets the information about a specific user.
*/ */
const getUser = () => { const getUser = () => {
return SecurityClient.fetchCall("/api/v1/user", { return SecurityClient.fetchCall('/api/v1/user', {
method: "GET", method: 'GET',
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json'
}, }
}).then(async (res) => { }).then(async (res) => {
if (res?.status == 200) { if (res?.status == 200) {
return (await res.json()).user; return (await res.json()).user;
} else { } else {
console.log("Failed to get user info"); console.log('Failed to get user info');
} }
}); });
}; };
@@ -1,4 +1,4 @@
import SecurityClient from "~/utilities/SecurityClient"; import SecurityClient from '~/utilities/SecurityClient';
/** /**
* This route creates a new workspace for a user within a certain organization. * This route creates a new workspace for a user within a certain organization.
@@ -6,21 +6,27 @@ import SecurityClient from "~/utilities/SecurityClient";
* @param {string} organizationId - org ID * @param {string} organizationId - org ID
* @returns * @returns
*/ */
const createWorkspace = ( { workspaceName, organizationId }: { workspaceName: string; organizationId: string; }) => { const createWorkspace = ({
return SecurityClient.fetchCall("/api/v1/workspace", { workspaceName,
method: "POST", organizationId
}: {
workspaceName: string;
organizationId: string;
}) => {
return SecurityClient.fetchCall('/api/v1/workspace', {
method: 'POST',
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ body: JSON.stringify({
workspaceName: workspaceName, workspaceName: workspaceName,
organizationId: organizationId, organizationId: organizationId
}), })
}).then(async (res) => { }).then(async (res) => {
if (res?.status == 200) { if (res?.status == 200) {
return (await res.json()).workspace; return (await res.json()).workspace;
} else { } else {
console.log("Failed to create a project"); console.log('Failed to create a project');
} }
}); });
}; };
@@ -1,24 +1,21 @@
import SecurityClient from "~/utilities/SecurityClient"; import SecurityClient from '~/utilities/SecurityClient';
/** /**
* Get the latest key pairs from a certain workspace * Get the latest key pairs from a certain workspace
* @param {string} workspaceId * @param {string} workspaceId
* @returns * @returns
*/ */
const getLatestFileKey = ({ workspaceId } : { workspaceId: string; }) => { const getLatestFileKey = ({ workspaceId }: { workspaceId: string }) => {
return SecurityClient.fetchCall( return SecurityClient.fetchCall('/api/v1/key/' + workspaceId + '/latest', {
"/api/v1/key/" + workspaceId + "/latest", method: 'GET',
{ headers: {
method: "GET", 'Content-Type': 'application/json'
headers: {
"Content-Type": "application/json",
},
} }
).then(async (res) => { }).then(async (res) => {
if (res?.status == 200) { if (res?.status == 200) {
return await res.json(); return await res.json();
} else { } else {
console.log("Failed to get the latest key pairs for a certain project"); console.log('Failed to get the latest key pairs for a certain project');
} }
}); });
}; };
+8 -11
View File
@@ -1,24 +1,21 @@
import SecurityClient from "~/utilities/SecurityClient"; import SecurityClient from '~/utilities/SecurityClient';
/** /**
* This route lets us get the information of a certain project. * This route lets us get the information of a certain project.
* @param {*} projectId - project ID (we renamed workspaces to projects in the app) * @param {*} projectId - project ID (we renamed workspaces to projects in the app)
* @returns * @returns
*/ */
const getProjectInfo = ({ projectId }: { projectId: string; }) => { const getProjectInfo = ({ projectId }: { projectId: string }) => {
return SecurityClient.fetchCall( return SecurityClient.fetchCall('/api/v1/workspace/' + projectId, {
"/api/v1/workspace/" + projectId, method: 'GET',
{ headers: {
method: "GET", 'Content-Type': 'application/json'
headers: {
"Content-Type": "application/json",
},
} }
).then(async (res) => { }).then(async (res) => {
if (res?.status == 200) { if (res?.status == 200) {
return (await res.json()).workspace; return (await res.json()).workspace;
} else { } else {
console.log("Failed to get project info"); console.log('Failed to get project info');
} }
}); });
}; };
@@ -1,24 +1,24 @@
import SecurityClient from "~/utilities/SecurityClient"; import SecurityClient from '~/utilities/SecurityClient';
/** /**
* This route lets us get all the users in the workspace. * This route lets us get all the users in the workspace.
* @param {string} workspaceId - workspace ID * @param {string} workspaceId - workspace ID
* @returns * @returns
*/ */
const getWorkspaceUsers = ({ workspaceId }: { workspaceId: string; }) => { const getWorkspaceUsers = ({ workspaceId }: { workspaceId: string }) => {
return SecurityClient.fetchCall( return SecurityClient.fetchCall(
"/api/v1/workspace/" + workspaceId + "/users", '/api/v1/workspace/' + workspaceId + '/users',
{ {
method: "GET", method: 'GET',
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json'
}, }
} }
).then(async (res) => { ).then(async (res) => {
if (res?.status == 200) { if (res?.status == 200) {
return (await res.json()).users; return (await res.json()).users;
} else { } else {
console.log("Failed to get Project Users"); console.log('Failed to get Project Users');
} }
}); });
}; };
@@ -1,4 +1,4 @@
import SecurityClient from "~/utilities/SecurityClient"; import SecurityClient from '~/utilities/SecurityClient';
interface Workspace { interface Workspace {
__v: number; __v: number;
@@ -12,18 +12,18 @@ interface Workspace {
* @returns * @returns
*/ */
const getWorkspaces = () => { const getWorkspaces = () => {
return SecurityClient.fetchCall("/api/v1/workspace", { return SecurityClient.fetchCall('/api/v1/workspace', {
method: "GET", method: 'GET',
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json'
}, }
}).then(async (res) => { }).then(async (res) => {
if (res?.status == 200) { if (res?.status == 200) {
const data = (await res.json()) as unknown as { workspaces: Workspace[] }; const data = (await res.json()) as unknown as { workspaces: Workspace[] };
return data.workspaces; return data.workspaces;
} }
throw new Error("Failed to get projects"); throw new Error('Failed to get projects');
}); });
}; };
+72 -72
View File
@@ -1,7 +1,7 @@
import React, { Fragment, useCallback, useEffect, useState } from "react"; import React, { Fragment, useCallback, useEffect, useState } from 'react';
import Head from "next/head"; import Head from 'next/head';
import Image from "next/image"; import Image from 'next/image';
import { useRouter } from "next/router"; import { useRouter } from 'next/router';
import { import {
faArrowDownAZ, faArrowDownAZ,
faArrowDownZA, faArrowDownZA,
@@ -18,29 +18,29 @@ import {
faPerson, faPerson,
faPlus, faPlus,
faShuffle, faShuffle,
faX, faX
} from "@fortawesome/free-solid-svg-icons"; } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Menu, Transition } from "@headlessui/react"; 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 { 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';
import getSecretsForProject from "~/components/utilities/secrets/getSecretsForProject"; import getSecretsForProject from '~/components/utilities/secrets/getSecretsForProject';
import pushKeys from "~/components/utilities/secrets/pushKeys"; import pushKeys from '~/components/utilities/secrets/pushKeys';
import pushKeysIntegration from "~/components/utilities/secrets/pushKeysIntegration"; import pushKeysIntegration from '~/components/utilities/secrets/pushKeysIntegration';
import guidGenerator from "~/utilities/randomId"; import guidGenerator from '~/utilities/randomId';
import { envMapping } from "../../public/data/frequentConstants"; import { envMapping } from '../../public/data/frequentConstants';
import getWorkspaceIntegrations from "../api/integrations/getWorkspaceIntegrations"; import getWorkspaceIntegrations from '../api/integrations/getWorkspaceIntegrations';
import getUser from "../api/user/getUser"; import getUser from '../api/user/getUser';
import checkUserAction from "../api/userActions/checkUserAction"; import checkUserAction from '../api/userActions/checkUserAction';
import registerUserAction from "../api/userActions/registerUserAction"; import registerUserAction from '../api/userActions/registerUserAction';
import getWorkspaces from "../api/workspace/getWorkspaces"; import getWorkspaces from '../api/workspace/getWorkspaces';
/** /**
* This component represent a single row for an environemnt variable on the dashboard * This component represent a single row for an environemnt variable on the dashboard
@@ -61,7 +61,7 @@ const KeyPair = ({
modifyValue, modifyValue,
modifyVisibility, modifyVisibility,
isBlurred, isBlurred,
duplicates, duplicates
}) => { }) => {
const [randomStringLength, setRandomStringLength] = useState(32); const [randomStringLength, setRandomStringLength] = useState(32);
@@ -114,7 +114,7 @@ const KeyPair = ({
<div <div
onClick={() => onClick={() =>
modifyVisibility( modifyVisibility(
keyPair[4] == "personal" ? "shared" : "personal", keyPair[4] == 'personal' ? 'shared' : 'personal',
keyPair[1] keyPair[1]
) )
} }
@@ -122,10 +122,10 @@ const KeyPair = ({
> >
<FontAwesomeIcon <FontAwesomeIcon
className="text-lg pl-1.5 pr-3" className="text-lg pl-1.5 pr-3"
icon={keyPair[4] == "personal" ? faPeopleGroup : faPerson} icon={keyPair[4] == 'personal' ? faPeopleGroup : faPerson}
/> />
<div className="text-sm"> <div className="text-sm">
{keyPair[4] == "personal" ? "Make Shared" : "Make Personal"} {keyPair[4] == 'personal' ? 'Make Shared' : 'Make Personal'}
</div> </div>
</div> </div>
<div <div
@@ -138,7 +138,7 @@ const KeyPair = ({
modifyValue( modifyValue(
[...Array(randomStringLength)] [...Array(randomStringLength)]
.map(() => Math.floor(Math.random() * 16).toString(16)) .map(() => Math.floor(Math.random() * 16).toString(16))
.join(""), .join(''),
keyPair[1] keyPair[1]
); );
} }
@@ -147,7 +147,7 @@ const KeyPair = ({
> >
<FontAwesomeIcon <FontAwesomeIcon
className="text-lg pl-1.5 pr-3" className="text-lg pl-1.5 pr-3"
icon={keyPair[3] == "" ? faPlus : faShuffle} icon={keyPair[3] == '' ? faPlus : faShuffle}
/> />
<div className="text-sm justify-between flex flex-row w-full"> <div className="text-sm justify-between flex flex-row w-full">
<p>Generate Random Hex</p> <p>Generate Random Hex</p>
@@ -210,21 +210,21 @@ export default function Dashboard() {
const [fileState, setFileState] = useState([]); const [fileState, setFileState] = useState([]);
const [buttonReady, setButtonReady] = useState(false); const [buttonReady, setButtonReady] = useState(false);
const router = useRouter(); const router = useRouter();
const [workspaceId, setWorkspaceId] = useState(""); const [workspaceId, setWorkspaceId] = useState('');
const [blurred, setBlurred] = useState(true); const [blurred, setBlurred] = useState(true);
const [isKeyAvailable, setIsKeyAvailable] = useState(true); const [isKeyAvailable, setIsKeyAvailable] = useState(true);
const [env, setEnv] = useState( const [env, setEnv] = useState(
router.asPath.split("?").length == 1 router.asPath.split('?').length == 1
? "Development" ? 'Development'
: Object.keys(envMapping).includes(router.asPath.split("?")[1]) : Object.keys(envMapping).includes(router.asPath.split('?')[1])
? router.asPath.split("?")[1] ? router.asPath.split('?')[1]
: "Development" : 'Development'
); );
const [isNew, setIsNew] = useState(false); const [isNew, setIsNew] = useState(false);
const [searchKeys, setSearchKeys] = useState(""); const [searchKeys, setSearchKeys] = useState('');
const [errorDragAndDrop, setErrorDragAndDrop] = useState(false); const [errorDragAndDrop, setErrorDragAndDrop] = useState(false);
const [projectIdCopied, setProjectIdCopied] = useState(false); const [projectIdCopied, setProjectIdCopied] = useState(false);
const [sortMethod, setSortMethod] = useState("alphabetical"); const [sortMethod, setSortMethod] = useState('alphabetical');
const [checkDocsPopUpVisible, setCheckDocsPopUpVisible] = useState(false); const [checkDocsPopUpVisible, setCheckDocsPopUpVisible] = useState(false);
const [hasUserEverPushed, setHasUserEverPushed] = useState(false); const [hasUserEverPushed, setHasUserEverPushed] = useState(false);
@@ -248,16 +248,16 @@ export default function Dashboard() {
// prompt the user if they try and leave with unsaved changes // prompt the user if they try and leave with unsaved changes
useEffect(() => { useEffect(() => {
const warningText = const warningText =
"Do you want to save your results before leaving this page?"; 'Do you want to save your results before leaving this page?';
const handleWindowClose = (e) => { const handleWindowClose = (e) => {
if (!buttonReady) return; if (!buttonReady) return;
e.preventDefault(); e.preventDefault();
return (e.returnValue = warningText); return (e.returnValue = warningText);
}; };
window.addEventListener("beforeunload", handleWindowClose); window.addEventListener('beforeunload', handleWindowClose);
// router.events.on('routeChangeStart', beforeRouteHandler); // router.events.on('routeChangeStart', beforeRouteHandler);
return () => { return () => {
window.removeEventListener("beforeunload", handleWindowClose); window.removeEventListener('beforeunload', handleWindowClose);
// router.events.off('routeChangeStart', beforeRouteHandler); // router.events.off('routeChangeStart', beforeRouteHandler);
}; };
}, [buttonReady]); }, [buttonReady]);
@@ -267,7 +267,7 @@ export default function Dashboard() {
*/ */
const reorderRows = () => { const reorderRows = () => {
setSortMethod( setSortMethod(
sortMethod == "alphabetical" ? "-alphabetical" : "alphabetical" sortMethod == 'alphabetical' ? '-alphabetical' : 'alphabetical'
); );
}; };
@@ -277,13 +277,13 @@ export default function Dashboard() {
let userWorkspaces = await getWorkspaces(); let userWorkspaces = await getWorkspaces();
const listWorkspaces = userWorkspaces.map((workspace) => workspace._id); const listWorkspaces = userWorkspaces.map((workspace) => workspace._id);
if ( if (
!listWorkspaces.includes(router.asPath.split("/")[2].split("?")[0]) !listWorkspaces.includes(router.asPath.split('/')[2].split('?')[0])
) { ) {
router.push("/dashboard/" + listWorkspaces[0]); router.push('/dashboard/' + listWorkspaces[0]);
} }
if (env != router.asPath.split("?")[1]) { if (env != router.asPath.split('?')[1]) {
router.push(router.asPath.split("?")[0] + "?" + env); router.push(router.asPath.split('?')[0] + '?' + env);
} }
setBlurred(true); setBlurred(true);
setWorkspaceId(router.query.id); setWorkspaceId(router.query.id);
@@ -293,7 +293,7 @@ export default function Dashboard() {
setFileState, setFileState,
setIsKeyAvailable, setIsKeyAvailable,
setData, setData,
workspaceId: router.query.id, workspaceId: router.query.id
}); });
const user = await getUser(); const user = await getUser();
@@ -304,11 +304,11 @@ export default function Dashboard() {
); );
let userAction = await checkUserAction({ let userAction = await checkUserAction({
action: "first_time_secrets_pushed", action: 'first_time_secrets_pushed'
}); });
setHasUserEverPushed(userAction ? true : false); setHasUserEverPushed(userAction ? true : false);
} catch (error) { } catch (error) {
console.log("Error", error); console.log('Error', error);
setData([]); setData([]);
} }
})(); })();
@@ -317,7 +317,7 @@ export default function Dashboard() {
const addRow = () => { const addRow = () => {
setIsNew(false); setIsNew(false);
setData([...data, [guidGenerator(), data.length, "", "", "shared"]]); setData([...data, [guidGenerator(), data.length, '', '', 'shared']]);
}; };
const deleteRow = (id) => { const deleteRow = (id) => {
@@ -385,15 +385,15 @@ export default function Dashboard() {
if (nameErrors) { if (nameErrors) {
return createNotification({ return createNotification({
text: "Solve all name errors first!", text: 'Solve all name errors first!',
type: "error", type: 'error'
}); });
} }
if (duplicatesExist) { if (duplicatesExist) {
return createNotification({ return createNotification({
text: "Your secrets weren't saved; please fix the conflicts first.", text: "Your secrets weren't saved; please fix the conflicts first.",
type: "error", type: 'error'
}); });
} }
@@ -406,7 +406,7 @@ export default function Dashboard() {
* If there are any, update environment variables for those integrations * If there are any, update environment variables for those integrations
*/ */
let integrations = await getWorkspaceIntegrations({ let integrations = await getWorkspaceIntegrations({
workspaceId: router.query.id, workspaceId: router.query.id
}); });
integrations.map(async (integration) => { integrations.map(async (integration) => {
if ( if (
@@ -419,7 +419,7 @@ export default function Dashboard() {
); );
await pushKeysIntegration({ await pushKeysIntegration({
obj: objIntegration, obj: objIntegration,
integrationId: integration._id, integrationId: integration._id
}); });
} }
}); });
@@ -427,7 +427,7 @@ export default function Dashboard() {
// If this user has never saved environment variables before, show them a prompt to read docs // If this user has never saved environment variables before, show them a prompt to read docs
if (!hasUserEverPushed) { if (!hasUserEverPushed) {
setCheckDocsPopUpVisible(true); setCheckDocsPopUpVisible(true);
await registerUserAction({ action: "first_time_secrets_pushed" }); await registerUserAction({ action: 'first_time_secrets_pushed' });
} }
}; };
@@ -442,12 +442,12 @@ export default function Dashboard() {
// This function downloads the secrets as a .env file // This function downloads the secrets as a .env file
const download = () => { const download = () => {
const file = data.map((item) => [item[2], item[3]].join("=")).join("\n"); const file = data.map((item) => [item[2], item[3]].join('=')).join('\n');
const blob = new Blob([file]); const blob = new Blob([file]);
const fileDownloadUrl = URL.createObjectURL(blob); const fileDownloadUrl = URL.createObjectURL(blob);
let alink = document.createElement("a"); let alink = document.createElement('a');
alink.href = fileDownloadUrl; alink.href = fileDownloadUrl;
alink.download = envMapping[env] + ".env"; alink.download = envMapping[env] + '.env';
alink.click(); alink.click();
}; };
@@ -459,7 +459,7 @@ export default function Dashboard() {
* This function copies the project id to the clipboard * This function copies the project id to the clipboard
*/ */
function copyToClipboard() { function copyToClipboard() {
var copyText = document.getElementById("myInput"); var copyText = document.getElementById('myInput');
copyText.select(); copyText.select();
copyText.setSelectionRange(0, 99999); // For mobile devices copyText.setSelectionRange(0, 99999); // For mobile devices
@@ -502,7 +502,7 @@ export default function Dashboard() {
{data?.length == 0 && ( {data?.length == 0 && (
<ListBox <ListBox
selected={env} selected={env}
data={["Development", "Staging", "Production", "Testing"]} data={['Development', 'Staging', 'Production', 'Testing']}
// ref={useRef(123)} // ref={useRef(123)}
onChange={setEnv} onChange={setEnv}
className="z-40" className="z-40"
@@ -557,7 +557,7 @@ export default function Dashboard() {
<> <>
<ListBox <ListBox
selected={env} selected={env}
data={["Development", "Staging", "Production", "Testing"]} data={['Development', 'Staging', 'Production', 'Testing']}
// ref={useRef(123)} // ref={useRef(123)}
onChange={setEnv} onChange={setEnv}
className="z-40" className="z-40"
@@ -571,7 +571,7 @@ export default function Dashboard() {
className="pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none" className="pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none"
value={searchKeys} value={searchKeys}
onChange={(e) => setSearchKeys(e.target.value)} onChange={(e) => setSearchKeys(e.target.value)}
placeholder={"Search keys..."} placeholder={'Search keys...'}
/> />
</div> </div>
<div className="ml-2 min-w-max flex flex-row items-start justify-start"> <div className="ml-2 min-w-max flex flex-row items-start justify-start">
@@ -580,7 +580,7 @@ export default function Dashboard() {
color="mineshaft" color="mineshaft"
size="icon-md" size="icon-md"
icon={ icon={
sortMethod == "alphabetical" sortMethod == 'alphabetical'
? faArrowDownAZ ? faArrowDownAZ
: faArrowDownZA : faArrowDownZA
} }
@@ -651,10 +651,10 @@ export default function Dashboard() {
keyPair[2] keyPair[2]
.toLowerCase() .toLowerCase()
.includes(searchKeys.toLowerCase()) && .includes(searchKeys.toLowerCase()) &&
keyPair[4] == "personal" keyPair[4] == 'personal'
) )
.sort((a, b) => .sort((a, b) =>
sortMethod == "alphabetical" sortMethod == 'alphabetical'
? a[2].localeCompare(b[2]) ? a[2].localeCompare(b[2])
: b[2].localeCompare(a[2]) : b[2].localeCompare(a[2])
) )
@@ -680,7 +680,7 @@ export default function Dashboard() {
</div> </div>
<div <div
className={`bg-white/5 mt-1 mb-2 rounded-md p-1 pb-2 max-w-5xl ${ className={`bg-white/5 mt-1 mb-2 rounded-md p-1 pb-2 max-w-5xl ${
data?.length > 8 ? "h-3/4" : "h-min" data?.length > 8 ? 'h-3/4' : 'h-min'
}`} }`}
> >
<div className="sticky top-0 z-40 bg-bunker flex flex-row pl-4 pr-5 pt-4 pb-2 items-center justify-between text-gray-300 font-bold"> <div className="sticky top-0 z-40 bg-bunker flex flex-row pl-4 pr-5 pt-4 pb-2 items-center justify-between text-gray-300 font-bold">
@@ -705,10 +705,10 @@ export default function Dashboard() {
keyPair[2] keyPair[2]
.toLowerCase() .toLowerCase()
.includes(searchKeys.toLowerCase()) && .includes(searchKeys.toLowerCase()) &&
keyPair[4] == "shared" keyPair[4] == 'shared'
) )
.sort((a, b) => .sort((a, b) =>
sortMethod == "alphabetical" sortMethod == 'alphabetical'
? a[2].localeCompare(b[2]) ? a[2].localeCompare(b[2])
: b[2].localeCompare(a[2]) : b[2].localeCompare(a[2])
) )
@@ -766,10 +766,10 @@ export default function Dashboard() {
/> />
)} )}
{fileState.message == {fileState.message ==
"Failed membership validation for workspace" && ( 'Failed membership validation for workspace' && (
<p>You are not authorized to view this project.</p> <p>You are not authorized to view this project.</p>
)} )}
{fileState.message == "Access needed to pull the latest file" || {fileState.message == 'Access needed to pull the latest file' ||
(!isKeyAvailable && ( (!isKeyAvailable && (
<> <>
<FontAwesomeIcon <FontAwesomeIcon
+78 -65
View File
@@ -1,36 +1,36 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react';
import Head from "next/head"; import Head from 'next/head';
import Image from "next/image"; import Image from 'next/image';
import { useRouter } from "next/router"; import { useRouter } from 'next/router';
import { import {
faArrowRight, faArrowRight,
faCheck, faCheck,
faRotate, faRotate,
faX, faX
} from "@fortawesome/free-solid-svg-icons"; } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
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 NavHeader from "~/components/navigation/NavHeader"; import NavHeader from '~/components/navigation/NavHeader';
import getSecretsForProject from "~/components/utilities/secrets/getSecretsForProject"; import getSecretsForProject from '~/components/utilities/secrets/getSecretsForProject';
import pushKeysIntegration from "~/components/utilities/secrets/pushKeysIntegration"; import pushKeysIntegration from '~/components/utilities/secrets/pushKeysIntegration';
import guidGenerator from "~/utilities/randomId"; import guidGenerator from '~/utilities/randomId';
import { import {
envMapping, envMapping,
frameworks, frameworks,
reverseEnvMapping reverseEnvMapping
} from "../../public/data/frequentConstants"; } from '../../public/data/frequentConstants';
import deleteIntegration from "../api/integrations/DeleteIntegration"; import deleteIntegration from '../api/integrations/DeleteIntegration';
import deleteIntegrationAuth from "../api/integrations/DeleteIntegrationAuth"; import deleteIntegrationAuth from '../api/integrations/DeleteIntegrationAuth';
import getIntegrationApps from "../api/integrations/GetIntegrationApps"; import getIntegrationApps from '../api/integrations/GetIntegrationApps';
import getIntegrations from "../api/integrations/GetIntegrations"; import getIntegrations from '../api/integrations/GetIntegrations';
import getWorkspaceAuthorizations from "../api/integrations/getWorkspaceAuthorizations"; import getWorkspaceAuthorizations from '../api/integrations/getWorkspaceAuthorizations';
import getWorkspaceIntegrations from "../api/integrations/getWorkspaceIntegrations"; import getWorkspaceIntegrations from '../api/integrations/getWorkspaceIntegrations';
import startIntegration from "../api/integrations/StartIntegration"; import startIntegration from '../api/integrations/StartIntegration';
const crypto = require("crypto"); const crypto = require('crypto');
const Integration = ({ projectIntegration }) => { const Integration = ({ projectIntegration }) => {
const [integrationEnvironment, setIntegrationEnvironment] = useState( const [integrationEnvironment, setIntegrationEnvironment] = useState(
@@ -47,7 +47,7 @@ const Integration = ({ projectIntegration }) => {
useEffect(async () => { useEffect(async () => {
const tempHerokuApps = await getIntegrationApps({ const tempHerokuApps = await getIntegrationApps({
integrationAuthId: projectIntegration.integrationAuth, integrationAuthId: projectIntegration.integrationAuth
}); });
const tempHerokuAppNames = tempHerokuApps.map((app) => app.name); const tempHerokuAppNames = tempHerokuApps.map((app) => app.name);
setApps(tempHerokuAppNames); setApps(tempHerokuAppNames);
@@ -67,9 +67,9 @@ const Integration = ({ projectIntegration }) => {
<ListBox <ListBox
data={ data={
!projectIntegration.isActive && [ !projectIntegration.isActive && [
"Development", 'Development',
"Staging", 'Staging',
"Production", 'Production'
] ]
} }
selected={integrationEnvironment} selected={integrationEnvironment}
@@ -116,7 +116,7 @@ const Integration = ({ projectIntegration }) => {
const result = await startIntegration({ const result = await startIntegration({
integrationId: projectIntegration._id, integrationId: projectIntegration._id,
environment: envMapping[integrationEnvironment], environment: envMapping[integrationEnvironment],
appName: integrationApp, appName: integrationApp
}); });
if (result?.status == 200) { if (result?.status == 200) {
let currentSecrets = await getSecretsForProject({ let currentSecrets = await getSecretsForProject({
@@ -124,18 +124,18 @@ const Integration = ({ projectIntegration }) => {
setFileState, setFileState,
setIsKeyAvailable, setIsKeyAvailable,
setData, setData,
workspaceId: router.query.id, workspaceId: router.query.id
}); });
let obj = Object.assign( let obj = Object.assign(
{}, {},
...currentSecrets.map((row) => ({ ...currentSecrets.map((row) => ({
[row[2]]: row[3], [row[2]]: row[3]
})) }))
); );
await pushKeysIntegration({ await pushKeysIntegration({
obj, obj,
integrationId: projectIntegration._id, integrationId: projectIntegration._id
}); });
router.reload(); router.reload();
} }
@@ -148,7 +148,7 @@ const Integration = ({ projectIntegration }) => {
<Button <Button
onButtonPressed={async () => { onButtonPressed={async () => {
await deleteIntegration({ await deleteIntegration({
integrationId: projectIntegration._id, integrationId: projectIntegration._id
}); });
router.reload(); router.reload();
}} }}
@@ -168,20 +168,20 @@ export default function Integrations() {
const [projectIntegrations, setProjectIntegrations] = useState(); const [projectIntegrations, setProjectIntegrations] = useState();
const [authorizations, setAuthorizations] = useState(); const [authorizations, setAuthorizations] = useState();
const router = useRouter(); const router = useRouter();
const [csrfToken, setCsrfToken] = useState(""); const [csrfToken, setCsrfToken] = useState('');
useEffect(async () => { useEffect(async () => {
const tempCSRFToken = crypto.randomBytes(16).toString("hex"); const tempCSRFToken = crypto.randomBytes(16).toString('hex');
setCsrfToken(tempCSRFToken); setCsrfToken(tempCSRFToken);
localStorage.setItem("latestCSRFToken", tempCSRFToken); localStorage.setItem('latestCSRFToken', tempCSRFToken);
let projectAuthorizations = await getWorkspaceAuthorizations({ let projectAuthorizations = await getWorkspaceAuthorizations({
workspaceId: router.query.id, workspaceId: router.query.id
}); });
setAuthorizations(projectAuthorizations); setAuthorizations(projectAuthorizations);
const projectIntegrations = await getWorkspaceIntegrations({ const projectIntegrations = await getWorkspaceIntegrations({
workspaceId: router.query.id, workspaceId: router.query.id
}); });
setProjectIntegrations(projectIntegrations); setProjectIntegrations(projectIntegrations);
@@ -189,7 +189,7 @@ export default function Integrations() {
const integrationsData = await getIntegrations(); const integrationsData = await getIntegrations();
setIntegrations(integrationsData); setIntegrations(integrationsData);
} catch (error) { } catch (error) {
console.log("Error", error); console.log('Error', error);
} }
}, []); }, []);
@@ -239,7 +239,9 @@ export default function Integrations() {
)} )}
<div className="flex flex-col justify-between items-start mx-4 mt-12 mb-4 text-xl max-w-5xl px-2"> <div className="flex flex-col justify-between items-start mx-4 mt-12 mb-4 text-xl max-w-5xl px-2">
<div className="flex flex-row justify-start items-center text-3xl"> <div className="flex flex-row justify-start items-center text-3xl">
<p className="font-semibold mr-4">Platform & Cloud Integrations</p> <p className="font-semibold mr-4">
Platform & Cloud Integrations
</p>
</div> </div>
<p className="mr-4 text-base text-gray-400"> <p className="mr-4 text-base text-gray-400">
Click on the itegration you want to connect. This will let your Click on the itegration you want to connect. This will let your
@@ -260,23 +262,23 @@ export default function Integrations() {
{Object.keys(integrations).map((integration) => ( {Object.keys(integrations).map((integration) => (
<div <div
className={`relative ${ className={`relative ${
["Heroku"].includes(integrations[integration].name) ['Heroku'].includes(integrations[integration].name)
? "" ? ''
: "opacity-50" : 'opacity-50'
}`} }`}
key={integrations[integration].name} key={integrations[integration].name}
> >
<a <a
href={`${ href={`${
["Heroku"].includes(integrations[integration].name) ['Heroku'].includes(integrations[integration].name)
? `https://id.heroku.com/oauth/authorize?client_id=bc132901-935a-4590-b010-f1857efc380d&response_type=code&scope=write-protected&state=${csrfToken}` ? `https://id.heroku.com/oauth/authorize?client_id=bc132901-935a-4590-b010-f1857efc380d&response_type=code&scope=write-protected&state=${csrfToken}`
: "#" : '#'
}`} }`}
rel="noopener" rel="noopener"
className={`relative flex flex-row bg-white/5 h-32 rounded-md p-4 items-center ${ className={`relative flex flex-row bg-white/5 h-32 rounded-md p-4 items-center ${
["Heroku"].includes(integrations[integration].name) ['Heroku'].includes(integrations[integration].name)
? "hover:bg-white/10 duration-200 cursor-pointer" ? 'hover:bg-white/10 duration-200 cursor-pointer'
: "cursor-default grayscale" : 'cursor-default grayscale'
}`} }`}
> >
<Image <Image
@@ -285,12 +287,12 @@ export default function Integrations() {
width={70} width={70}
alt="integration logo" alt="integration logo"
></Image> ></Image>
{integrations[integration].name.split(" ").length > 2 ? ( {integrations[integration].name.split(' ').length > 2 ? (
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-3xl ml-4 max-w-xs"> <div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-3xl ml-4 max-w-xs">
<div>{integrations[integration].name.split(" ")[0]}</div> <div>{integrations[integration].name.split(' ')[0]}</div>
<div className="text-base"> <div className="text-base">
{integrations[integration].name.split(" ")[1]}{" "} {integrations[integration].name.split(' ')[1]}{' '}
{integrations[integration].name.split(" ")[2]} {integrations[integration].name.split(' ')[2]}
</div> </div>
</div> </div>
) : ( ) : (
@@ -299,7 +301,7 @@ export default function Integrations() {
</div> </div>
)} )}
</a> </a>
{["Heroku"].includes(integrations[integration].name) && {['Heroku'].includes(integrations[integration].name) &&
authorizations authorizations
.map((authorization) => authorization.integration) .map((authorization) => authorization.integration)
.includes(integrations[integration].name.toLowerCase()) && ( .includes(integrations[integration].name.toLowerCase()) && (
@@ -313,7 +315,7 @@ export default function Integrations() {
authorization.integration == authorization.integration ==
integrations[integration].name.toLowerCase() integrations[integration].name.toLowerCase()
) )
.map((authorization) => authorization._id)[0], .map((authorization) => authorization._id)[0]
}); });
router.reload(); router.reload();
}} }}
@@ -334,7 +336,7 @@ export default function Integrations() {
</div> </div>
</div> </div>
)} )}
{!["Heroku"].includes(integrations[integration].name) && ( {!['Heroku'].includes(integrations[integration].name) && (
<div className="absolute group z-50 top-0 right-0 flex flex-row"> <div className="absolute group z-50 top-0 right-0 flex flex-row">
<div className="w-max bg-yellow py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90"> <div className="w-max bg-yellow py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90">
Coming Soon Coming Soon
@@ -351,7 +353,8 @@ export default function Integrations() {
<p className="mr-4 text-base text-gray-400"> <p className="mr-4 text-base text-gray-400">
Click on a framework to get the setup instructions. Click on a framework to get the setup instructions.
</p> </p>
</div><div className="grid gap-4 grid-cols-7 grid-rows-2 mx-6 mt-4 max-w-5xl"> </div>
<div className="grid gap-4 grid-cols-7 grid-rows-2 mx-6 mt-4 max-w-5xl">
{frameworks.map((framework) => ( {frameworks.map((framework) => (
<div key={framework.name}> <div key={framework.name}>
<a <a
@@ -359,14 +362,24 @@ export default function Integrations() {
rel="noopener" rel="noopener"
className={`relative flex flex-row items-center justify-center bg-bunker-500 hover:bg-gradient-to-tr hover:from-sky-400 hover:to-primary duration-200 h-32 rounded-md p-0.5 items-center cursor-pointer`} className={`relative flex flex-row items-center justify-center bg-bunker-500 hover:bg-gradient-to-tr hover:from-sky-400 hover:to-primary duration-200 h-32 rounded-md p-0.5 items-center cursor-pointer`}
> >
<div className={`font-semibold bg-bunker-500 flex flex-col items-center justify-center h-full w-full rounded-md text-gray-300 group-hover:text-gray-200 duration-200 ${framework?.name?.split(" ").length > 1 ? "text-sm px-1" : "text-xl px-2"} text-center w-full max-w-xs`}> <div
{framework?.image && <Image className={`font-semibold bg-bunker-500 flex flex-col items-center justify-center h-full w-full rounded-md text-gray-300 group-hover:text-gray-200 duration-200 ${
src={`/images/integrations/${framework.image}.png`} framework?.name?.split(' ').length > 1
height={framework?.name ? 60 : 90} ? 'text-sm px-1'
width={framework?.name ? 60 : 90} : 'text-xl px-2'
alt="integration logo" } text-center w-full max-w-xs`}
></Image>} >
{framework?.name && framework?.image && <div className="h-2"></div>} {framework?.image && (
<Image
src={`/images/integrations/${framework.image}.png`}
height={framework?.name ? 60 : 90}
width={framework?.name ? 60 : 90}
alt="integration logo"
></Image>
)}
{framework?.name && framework?.image && (
<div className="h-2"></div>
)}
{framework?.name && framework.name} {framework?.name && framework.name}
</div> </div>
</a> </a>
+46 -46
View File
@@ -1,62 +1,62 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react';
import Head from "next/head"; import Head from 'next/head';
import { useRouter } from "next/router"; import { useRouter } from 'next/router';
import { import {
faMagnifyingGlass, faMagnifyingGlass,
faPlus, faPlus,
faX, faX
} from "@fortawesome/free-solid-svg-icons"; } from '@fortawesome/free-solid-svg-icons';
import { faCheck } from "@fortawesome/free-solid-svg-icons"; import { faCheck } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import Button from "~/components/basic/buttons/Button"; import Button from '~/components/basic/buttons/Button';
import AddIncidentContactDialog from "~/components/basic/dialog/AddIncidentContactDialog"; import AddIncidentContactDialog from '~/components/basic/dialog/AddIncidentContactDialog';
import AddUserDialog from "~/components/basic/dialog/AddUserDialog"; import AddUserDialog from '~/components/basic/dialog/AddUserDialog';
import InputField from "~/components/basic/InputField"; import InputField from '~/components/basic/InputField';
import UserTable from "~/components/basic/table/UserTable"; import UserTable from '~/components/basic/table/UserTable';
import NavHeader from "~/components/navigation/NavHeader"; import NavHeader from '~/components/navigation/NavHeader';
import guidGenerator from "~/utilities/randomId"; import guidGenerator from '~/utilities/randomId';
import addUserToOrg from "../../api/organization/addUserToOrg"; import addUserToOrg from '../../api/organization/addUserToOrg';
import deleteIncidentContact from "../../api/organization/deleteIncidentContact"; import deleteIncidentContact from '../../api/organization/deleteIncidentContact';
import getIncidentContacts from "../../api/organization/getIncidentContacts"; import getIncidentContacts from '../../api/organization/getIncidentContacts';
import getOrganization from "../../api/organization/GetOrg"; import getOrganization from '../../api/organization/GetOrg';
import getOrganizationSubscriptions from "../../api/organization/GetOrgSubscription"; import getOrganizationSubscriptions from '../../api/organization/GetOrgSubscription';
import getOrganizationUsers from "../../api/organization/GetOrgUsers"; import getOrganizationUsers from '../../api/organization/GetOrgUsers';
import renameOrg from "../../api/organization/renameOrg"; import renameOrg from '../../api/organization/renameOrg';
import getUser from "../../api/user/getUser"; import getUser from '../../api/user/getUser';
import deleteWorkspace from "../../api/workspace/deleteWorkspace"; import deleteWorkspace from '../../api/workspace/deleteWorkspace';
import getWorkspaces from "../../api/workspace/getWorkspaces"; import getWorkspaces from '../../api/workspace/getWorkspaces';
export default function SettingsOrg() { export default function SettingsOrg() {
const [buttonReady, setButtonReady] = useState(false); const [buttonReady, setButtonReady] = useState(false);
const router = useRouter(); const router = useRouter();
const [orgName, setOrgName] = useState(""); const [orgName, setOrgName] = useState('');
const [emailUser, setEmailUser] = useState(""); const [emailUser, setEmailUser] = useState('');
const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] = useState(""); const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] = useState('');
const [searchUsers, setSearchUsers] = useState(""); const [searchUsers, setSearchUsers] = useState('');
const [workspaceId, setWorkspaceId] = useState(""); const [workspaceId, setWorkspaceId] = useState('');
const [isAddIncidentContactOpen, setIsAddIncidentContactOpen] = const [isAddIncidentContactOpen, setIsAddIncidentContactOpen] =
useState(false); useState(false);
const [isAddUserOpen, setIsAddUserOpen] = useState( const [isAddUserOpen, setIsAddUserOpen] = useState(
router.asPath.split("?")[1] == "invite" router.asPath.split('?')[1] == 'invite'
); );
const [incidentContacts, setIncidentContacts] = useState([]); const [incidentContacts, setIncidentContacts] = useState([]);
const [searchIncidentContact, setSearchIncidentContact] = useState(""); const [searchIncidentContact, setSearchIncidentContact] = useState('');
const [userList, setUserList] = useState(); const [userList, setUserList] = useState();
const [personalEmail, setPersonalEmail] = useState(""); const [personalEmail, setPersonalEmail] = useState('');
let workspaceIdTemp; let workspaceIdTemp;
const [email, setEmail] = useState(""); const [email, setEmail] = useState('');
const [currentPlan, setCurrentPlan] = useState(""); const [currentPlan, setCurrentPlan] = useState('');
useEffect(async () => { useEffect(async () => {
let org = await getOrganization({ let org = await getOrganization({
orgId: localStorage.getItem("orgData.id"), orgId: localStorage.getItem('orgData.id')
}); });
let orgData = org; let orgData = org;
setOrgName(orgData.name); setOrgName(orgData.name);
let incidentContactsData = await getIncidentContacts( let incidentContactsData = await getIncidentContacts(
localStorage.getItem("orgData.id") localStorage.getItem('orgData.id')
); );
setIncidentContacts(incidentContactsData?.map((contact) => contact.email)); setIncidentContacts(incidentContactsData?.map((contact) => contact.email));
@@ -66,7 +66,7 @@ export default function SettingsOrg() {
workspaceIdTemp = router.query.id; workspaceIdTemp = router.query.id;
let orgUsers = await getOrganizationUsers({ let orgUsers = await getOrganizationUsers({
orgId: localStorage.getItem("orgData.id"), orgId: localStorage.getItem('orgData.id')
}); });
setUserList( setUserList(
orgUsers.map((user) => ({ orgUsers.map((user) => ({
@@ -78,11 +78,11 @@ export default function SettingsOrg() {
status: user?.status, status: user?.status,
userId: user.user?._id, userId: user.user?._id,
membershipId: user._id, membershipId: user._id,
publicKey: user.user?.publicKey, publicKey: user.user?.publicKey
})) }))
); );
const subscriptions = await getOrganizationSubscriptions({ const subscriptions = await getOrganizationSubscriptions({
orgId: localStorage.getItem("orgData.id"), orgId: localStorage.getItem('orgData.id')
}); });
setCurrentPlan(subscriptions.data[0].plan.product); setCurrentPlan(subscriptions.data[0].plan.product);
}, []); }, []);
@@ -93,7 +93,7 @@ export default function SettingsOrg() {
}; };
const submitChanges = (newOrgName) => { const submitChanges = (newOrgName) => {
renameOrg(localStorage.getItem("orgData.id"), newOrgName); renameOrg(localStorage.getItem('orgData.id'), newOrgName);
setButtonReady(false); setButtonReady(false);
}; };
@@ -118,8 +118,8 @@ export default function SettingsOrg() {
} }
async function submitAddUserModal(email) { async function submitAddUserModal(email) {
await addUserToOrg(email, localStorage.getItem("orgData.id")); await addUserToOrg(email, localStorage.getItem('orgData.id'));
setEmail(""); setEmail('');
setIsAddUserOpen(false); setIsAddUserOpen(false);
router.reload(); router.reload();
} }
@@ -128,7 +128,7 @@ export default function SettingsOrg() {
setIncidentContacts( setIncidentContacts(
incidentContacts.filter((contact) => contact != incidentContact) incidentContacts.filter((contact) => contact != incidentContact)
); );
deleteIncidentContact(localStorage.getItem("orgData.id"), incidentContact); deleteIncidentContact(localStorage.getItem('orgData.id'), incidentContact);
}; };
/** /**
@@ -148,7 +148,7 @@ export default function SettingsOrg() {
) { ) {
await deleteWorkspace(router.query.id); await deleteWorkspace(router.query.id);
let userWorkspaces = await getWorkspaces(); let userWorkspaces = await getWorkspaces();
router.push("/dashboard/" + userWorkspaces[0]._id); router.push('/dashboard/' + userWorkspaces[0]._id);
} }
} }
}; };
@@ -241,7 +241,7 @@ export default function SettingsOrg() {
className="pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none" className="pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none"
value={searchUsers} value={searchUsers}
onChange={(e) => setSearchUsers(e.target.value)} onChange={(e) => setSearchUsers(e.target.value)}
placeholder={"Search members..."} placeholder={'Search members...'}
/> />
</div> </div>
<div className="mt-2 ml-2 min-w-max flex flex-row items-start justify-start"> <div className="mt-2 ml-2 min-w-max flex flex-row items-start justify-start">
@@ -302,7 +302,7 @@ export default function SettingsOrg() {
className="pl-2 text-gray-400 rounded-tr-md bg-white/5 w-full h-full outline-none" className="pl-2 text-gray-400 rounded-tr-md bg-white/5 w-full h-full outline-none"
value={searchIncidentContact} value={searchIncidentContact}
onChange={(e) => setSearchIncidentContact(e.target.value)} onChange={(e) => setSearchIncidentContact(e.target.value)}
placeholder={"Search..."} placeholder={'Search...'}
/> />
</div> </div>
{incidentContacts?.filter((email) => {incidentContacts?.filter((email) =>
+82 -82
View File
@@ -1,72 +1,72 @@
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from 'react';
import ReactCodeInput from "react-code-input"; 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';
import Link from "next/link"; import Link from 'next/link';
import { useRouter } from "next/router"; import { useRouter } from 'next/router';
import { faCheck, faWarning, faX } from "@fortawesome/free-solid-svg-icons"; import { faCheck, faWarning, faX } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import Button from "~/components/basic/buttons/Button"; import Button from '~/components/basic/buttons/Button';
import Error from "~/components/basic/Error"; import Error from '~/components/basic/Error';
import InputField from "~/components/basic/InputField"; import InputField from '~/components/basic/InputField';
import Aes256Gcm from "~/components/utilities/cryptography/aes-256-gcm"; import Aes256Gcm from '~/components/utilities/cryptography/aes-256-gcm';
import issueBackupKey from "~/components/utilities/cryptography/issueBackupKey"; import issueBackupKey from '~/components/utilities/cryptography/issueBackupKey';
import attemptLogin from "~/utilities/attemptLogin"; import attemptLogin from '~/utilities/attemptLogin';
import passwordCheck from "~/utilities/checks/PasswordCheck"; import passwordCheck from '~/utilities/checks/PasswordCheck';
import checkEmailVerificationCode from "./api/auth/CheckEmailVerificationCode"; import checkEmailVerificationCode from './api/auth/CheckEmailVerificationCode';
import completeAccountInformationSignup from "./api/auth/CompleteAccountInformationSignup"; import completeAccountInformationSignup from './api/auth/CompleteAccountInformationSignup';
import sendVerificationEmail from "./api/auth/SendVerificationEmail"; import sendVerificationEmail from './api/auth/SendVerificationEmail';
import getWorkspaces from "./api/workspace/getWorkspaces"; import getWorkspaces from './api/workspace/getWorkspaces';
// 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');
const client = new jsrp.client(); const client = new jsrp.client();
// The stye for the verification code input // The stye for the verification code input
const props = { const props = {
inputStyle: { inputStyle: {
fontFamily: "monospace", fontFamily: 'monospace',
margin: "4px", margin: '4px',
MozAppearance: "textfield", MozAppearance: 'textfield',
width: "55px", width: '55px',
borderRadius: "5px", borderRadius: '5px',
fontSize: "24px", fontSize: '24px',
height: "55px", height: '55px',
paddingLeft: "7", paddingLeft: '7',
backgroundColor: "#0d1117", backgroundColor: '#0d1117',
color: "white", color: 'white',
border: "1px solid gray", border: '1px solid gray',
textAlign: "center", textAlign: 'center'
}, }
} as const; } as const;
const propsPhone = { const propsPhone = {
inputStyle: { inputStyle: {
fontFamily: "monospace", fontFamily: 'monospace',
margin: "4px", margin: '4px',
MozAppearance: "textfield", MozAppearance: 'textfield',
width: "40px", width: '40px',
borderRadius: "5px", borderRadius: '5px',
fontSize: "24px", fontSize: '24px',
height: "40px", height: '40px',
paddingLeft: "7", paddingLeft: '7',
backgroundColor: "#0d1117", backgroundColor: '#0d1117',
color: "white", color: 'white',
border: "1px solid gray", border: '1px solid gray',
textAlign: "center", textAlign: 'center'
}, }
} as const; } as const;
export default function SignUp() { export default function SignUp() {
const [email, setEmail] = useState(""); const [email, setEmail] = useState('');
const [password, setPassword] = useState(""); const [password, setPassword] = useState('');
const [firstName, setFirstName] = useState(""); const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState(""); const [lastName, setLastName] = useState('');
const [code, setCode] = useState(""); const [code, setCode] = useState('');
const [codeError, setCodeError] = useState(false); const [codeError, setCodeError] = useState(false);
const [firstNameError, setFirstNameError] = useState(false); const [firstNameError, setFirstNameError] = useState(false);
const [lastNameError, setLastNameError] = useState(false); const [lastNameError, setLastNameError] = useState(false);
@@ -77,22 +77,22 @@ export default function SignUp() {
const [passwordErrorSpecialChar, setPasswordErrorSpecialChar] = const [passwordErrorSpecialChar, setPasswordErrorSpecialChar] =
useState(false); useState(false);
const [emailError, setEmailError] = useState(false); const [emailError, setEmailError] = useState(false);
const [emailErrorMessage, setEmailErrorMessage] = useState(""); const [emailErrorMessage, setEmailErrorMessage] = useState('');
const [step, setStep] = useState(1); const [step, setStep] = useState(1);
const router = useRouter(); const router = useRouter();
const [errorLogin, setErrorLogin] = useState(false); const [errorLogin, setErrorLogin] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [backupKeyError, setBackupKeyError] = useState(false); const [backupKeyError, setBackupKeyError] = useState(false);
const [verificationToken, setVerificationToken] = useState(); const [verificationToken, setVerificationToken] = useState('');
const [backupKeyIssued, setBackupKeyIssued] = useState(false); const [backupKeyIssued, setBackupKeyIssued] = useState(false);
useEffect(() => { useEffect(() => {
const tryAuth = async () => { const tryAuth = async () => {
try { try {
const userWorkspaces = await getWorkspaces(); const userWorkspaces = await getWorkspaces();
router.push("/dashboard/" + userWorkspaces[0]._id); router.push('/dashboard/' + userWorkspaces[0]._id);
} catch (error) { } catch (error) {
console.log("Error - Not logged in yet"); console.log('Error - Not logged in yet');
} }
}; };
tryAuth(); tryAuth();
@@ -109,8 +109,8 @@ export default function SignUp() {
setStep(2); setStep(2);
} 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 {
@@ -128,15 +128,15 @@ export default function SignUp() {
let emailCheckBool = false; let emailCheckBool = false;
if (!email) { if (!email) {
setEmailError(true); setEmailError(true);
setEmailErrorMessage("Please enter your email."); setEmailErrorMessage('Please enter your email.');
emailCheckBool = true; emailCheckBool = true;
} else if ( } else if (
!email.includes("@") || !email.includes('@') ||
!email.includes(".") || !email.includes('.') ||
!/[a-z]/.test(email) !/[a-z]/.test(email)
) { ) {
setEmailError(true); setEmailError(true);
setEmailErrorMessage("Please enter a valid email."); setEmailErrorMessage('Please enter a valid email.');
emailCheckBool = true; emailCheckBool = true;
} else { } else {
setEmailError(false); setEmailError(false);
@@ -170,7 +170,7 @@ export default function SignUp() {
setPasswordErrorLength, setPasswordErrorLength,
setPasswordErrorNumber, setPasswordErrorNumber,
setPasswordErrorLowerCase, setPasswordErrorLowerCase,
currentErrorCheck: errorCheck, currentErrorCheck: errorCheck
}); });
if (!errorCheck) { if (!errorCheck) {
@@ -187,16 +187,16 @@ export default function SignUp() {
.slice(0, 32) .slice(0, 32)
.padStart( .padStart(
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 }; ) as { ciphertext: string; iv: string; tag: string };
localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY); localStorage.setItem('PRIVATE_KEY', PRIVATE_KEY);
client.init( client.init(
{ {
username: email, username: email,
password: password, password: password
}, },
async () => { async () => {
client.createVerifier( client.createVerifier(
@@ -212,17 +212,17 @@ export default function SignUp() {
tag, tag,
salt: result.salt, salt: result.salt,
verifier: result.verifier, verifier: result.verifier,
token: verificationToken, token: verificationToken
}); });
// if everything works, go the main dashboard page. // if everything works, go the main dashboard page.
if (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(
@@ -295,10 +295,10 @@ export default function SignUp() {
const step2 = ( const step2 = (
<div className="bg-bunker w-max mx-auto h-7/12 pt-10 pb-4 px-8 rounded-xl drop-shadow-xl mb-64 md:mb-16"> <div className="bg-bunker w-max mx-auto h-7/12 pt-10 pb-4 px-8 rounded-xl drop-shadow-xl mb-64 md:mb-16">
<p className="text-l flex justify-center text-gray-400"> <p className="text-l flex justify-center text-gray-400">
{"We've"} sent a verification email to{" "} {"We've"} sent a verification email to{' '}
</p> </p>
<p className="text-l flex justify-center font-semibold my-2 text-gray-400"> <p className="text-l flex justify-center font-semibold my-2 text-gray-400">
{email}{" "} {email}{' '}
</p> </p>
<div className="hidden md:block"> <div className="hidden md:block">
<ReactCodeInput <ReactCodeInput
@@ -383,7 +383,7 @@ export default function SignUp() {
setPasswordErrorLength, setPasswordErrorLength,
setPasswordErrorNumber, setPasswordErrorNumber,
setPasswordErrorLowerCase, setPasswordErrorLowerCase,
currentErrorCheck: false, currentErrorCheck: false
}); });
}} }}
type="password" type="password"
@@ -416,7 +416,7 @@ export default function SignUp() {
)} )}
<div <div
className={`${ className={`${
passwordErrorLength ? "text-gray-400" : "text-gray-600" passwordErrorLength ? 'text-gray-400' : 'text-gray-600'
} text-sm`} } text-sm`}
> >
14 characters 14 characters
@@ -436,7 +436,7 @@ export default function SignUp() {
)} )}
<div <div
className={`${ className={`${
passwordErrorLowerCase ? "text-gray-400" : "text-gray-600" passwordErrorLowerCase ? 'text-gray-400' : 'text-gray-600'
} text-sm`} } text-sm`}
> >
1 lowercase character 1 lowercase character
@@ -456,7 +456,7 @@ export default function SignUp() {
)} )}
<div <div
className={`${ className={`${
passwordErrorNumber ? "text-gray-400" : "text-gray-600" passwordErrorNumber ? 'text-gray-400' : 'text-gray-600'
} text-sm`} } text-sm`}
> >
1 number 1 number
@@ -505,13 +505,13 @@ export default function SignUp() {
await issueBackupKey({ await issueBackupKey({
email, email,
password, password,
personalName: firstName + " " + lastName, personalName: firstName + ' ' + lastName,
setBackupKeyError, setBackupKeyError,
setBackupKeyIssued, setBackupKeyIssued
}); });
const userWorkspaces = await getWorkspaces(); const userWorkspaces = await getWorkspaces();
const userWorkspace = userWorkspaces[0]._id; const userWorkspace = userWorkspaces[0]._id;
router.push("/home/" + userWorkspace); router.push('/home/' + userWorkspace);
}} }}
size="lg" size="lg"
/> />
+34 -34
View File
@@ -1,39 +1,39 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react';
import Head from "next/head"; import Head from 'next/head';
import Image from "next/image"; import Image from 'next/image';
import { useRouter } from "next/router"; import { useRouter } from 'next/router';
import { faMagnifyingGlass, faPlus } from "@fortawesome/free-solid-svg-icons"; import { faMagnifyingGlass, faPlus } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import Button from "~/components/basic/buttons/Button"; import Button from '~/components/basic/buttons/Button';
import AddProjectMemberDialog from "~/components/basic/dialog/AddProjectMemberDialog"; import AddProjectMemberDialog from '~/components/basic/dialog/AddProjectMemberDialog';
import UserTable from "~/components/basic/table/UserTable"; import UserTable from '~/components/basic/table/UserTable';
import NavHeader from "~/components/navigation/NavHeader"; import NavHeader from '~/components/navigation/NavHeader';
import guidGenerator from "~/utilities/randomId"; import guidGenerator from '~/utilities/randomId';
import getOrganizationUsers from "../api/organization/GetOrgUsers"; import getOrganizationUsers from '../api/organization/GetOrgUsers';
import getUser from "../api/user/getUser"; import getUser from '../api/user/getUser';
// import DeleteUserDialog from '~/components/basic/dialog/DeleteUserDialog'; // import DeleteUserDialog from '~/components/basic/dialog/DeleteUserDialog';
import addUserToWorkspace from "../api/workspace/addUserToWorkspace"; import addUserToWorkspace from '../api/workspace/addUserToWorkspace';
import getWorkspaceUsers from "../api/workspace/getWorkspaceUsers"; import getWorkspaceUsers from '../api/workspace/getWorkspaceUsers';
import uploadKeys from "../api/workspace/uploadKeys"; import uploadKeys from '../api/workspace/uploadKeys';
// #TODO: Update all the workspaceIds // #TODO: Update all the workspaceIds
const crypto = require("crypto"); const crypto = require('crypto');
const { const {
decryptAssymmetric, decryptAssymmetric,
encryptAssymmetric, encryptAssymmetric
} = require("../../components/utilities/cryptography/crypto"); } = require('../../components/utilities/cryptography/crypto');
const nacl = require("tweetnacl"); const nacl = require('tweetnacl');
nacl.util = require("tweetnacl-util"); nacl.util = require('tweetnacl-util');
export default function Users() { export default function Users() {
let [isAddOpen, setIsAddOpen] = useState(false); let [isAddOpen, setIsAddOpen] = useState(false);
// let [isDeleteOpen, setIsDeleteOpen] = useState(false); // let [isDeleteOpen, setIsDeleteOpen] = useState(false);
// let [userIdToBeDeleted, setUserIdToBeDeleted] = useState(false); // let [userIdToBeDeleted, setUserIdToBeDeleted] = useState(false);
let [email, setEmail] = useState(""); let [email, setEmail] = useState('');
const [personalEmail, setPersonalEmail] = useState(""); const [personalEmail, setPersonalEmail] = useState('');
const [searchUsers, setSearchUsers] = useState(""); const [searchUsers, setSearchUsers] = useState('');
const router = useRouter(); const router = useRouter();
let workspaceId; let workspaceId;
@@ -57,25 +57,25 @@ export default function Users() {
async function submitAddModal() { async function submitAddModal() {
let result = await addUserToWorkspace(email, router.query.id); let result = await addUserToWorkspace(email, router.query.id);
if (result?.invitee && result?.latestKey) { if (result?.invitee && result?.latestKey) {
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY');
// assymmetrically decrypt symmetric key with local private key // assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({ const key = decryptAssymmetric({
ciphertext: result.latestKey.encryptedKey, ciphertext: result.latestKey.encryptedKey,
nonce: result.latestKey.nonce, nonce: result.latestKey.nonce,
publicKey: result.latestKey.sender.publicKey, publicKey: result.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY, privateKey: PRIVATE_KEY
}); });
const { ciphertext, nonce } = encryptAssymmetric({ const { ciphertext, nonce } = encryptAssymmetric({
plaintext: key, plaintext: key,
publicKey: result.invitee.publicKey, publicKey: result.invitee.publicKey,
privateKey: PRIVATE_KEY, privateKey: PRIVATE_KEY
}); });
uploadKeys(router.query.id, result.invitee._id, ciphertext, nonce); uploadKeys(router.query.id, result.invitee._id, ciphertext, nonce);
} }
setEmail(""); setEmail('');
setIsAddOpen(false); setIsAddOpen(false);
router.reload(); router.reload();
} }
@@ -93,7 +93,7 @@ export default function Users() {
workspaceId = router.query.id; workspaceId = router.query.id;
let workspaceUsers = await getWorkspaceUsers({ let workspaceUsers = await getWorkspaceUsers({
workspaceId, workspaceId
}); });
const tempUserList = workspaceUsers.map((user) => ({ const tempUserList = workspaceUsers.map((user) => ({
key: guidGenerator(), key: guidGenerator(),
@@ -104,16 +104,16 @@ export default function Users() {
status: user?.status, status: user?.status,
userId: user.user?._id, userId: user.user?._id,
membershipId: user._id, membershipId: user._id,
publicKey: user.user?.publicKey, publicKey: user.user?.publicKey
})); }));
setUserList(tempUserList); setUserList(tempUserList);
const orgUsers = await getOrganizationUsers({ const orgUsers = await getOrganizationUsers({
orgId: localStorage.getItem("orgData.id"), orgId: localStorage.getItem('orgData.id')
}); });
setOrgUserList(orgUsers); setOrgUserList(orgUsers);
setEmail( setEmail(
orgUsers orgUsers
?.filter((user) => user.status == "accepted") ?.filter((user) => user.status == 'accepted')
.map((user) => user.user.email) .map((user) => user.user.email)
.filter( .filter(
(email) => !tempUserList?.map((user1) => user1.email).includes(email) (email) => !tempUserList?.map((user1) => user1.email).includes(email)
@@ -140,7 +140,7 @@ export default function Users() {
submitModal={submitAddModal} submitModal={submitAddModal}
email={email} email={email}
data={orgUserList data={orgUserList
?.filter((user) => user.status == "accepted") ?.filter((user) => user.status == 'accepted')
.map((user) => user.user.email) .map((user) => user.user.email)
.filter( .filter(
(email) => !userList?.map((user1) => user1.email).includes(email) (email) => !userList?.map((user1) => user1.email).includes(email)
@@ -159,7 +159,7 @@ export default function Users() {
className="pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none" className="pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none"
value={searchUsers} value={searchUsers}
onChange={(e) => setSearchUsers(e.target.value)} onChange={(e) => setSearchUsers(e.target.value)}
placeholder={"Search members..."} placeholder={'Search members...'}
/> />
</div> </div>
<div className="mt-2 ml-2 min-w-max flex flex-row items-start justify-start mr-4"> <div className="mt-2 ml-2 min-w-max flex flex-row items-start justify-start mr-4">