Complete v1 API Key

This commit is contained in:
Tuan Dang
2023-01-15 14:34:16 +07:00
54 changed files with 1727 additions and 1597 deletions
@@ -1,14 +1,17 @@
import { useEffect, useState } from 'react'; import { ReactNode, useEffect, useState } from "react";
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
export default function RouteGuard({ children }) { type Prop = {
children: ReactNode;
};
export default function RouteGuard({ children }: Prop) {
const router = useRouter(); const router = useRouter();
const [authorized, setAuthorized] = useState(false); const [authorized, setAuthorized] = useState(false);
@@ -22,16 +25,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
@@ -39,11 +42,10 @@ export default function RouteGuard({ children }) {
/** /**
* redirect to login page if accessing a private page and not logged in * redirect to login page if accessing a private page and not logged in
* @param {*} url - the url of the page we are trying to go to
*/ */
async function authCheck(url) { async function authCheck(url:string) {
// 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();
@@ -52,16 +54,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
); );
} }
+5 -6
View File
@@ -1,16 +1,15 @@
import React from "react"; import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
export default function Error({ text }: { text: string }): JSX.Element { export default function Error({ text }: { text: string }): JSX.Element {
return ( return (
<div className="relative flex flex-row justify-center m-auto items-center w-fit rounded-full"> <div className='relative flex flex-row justify-center m-auto items-center w-fit rounded-full'>
<FontAwesomeIcon <FontAwesomeIcon
icon={faExclamationTriangle} icon={faExclamationTriangle}
className="text-red mt-1.5 mb-2 mx-2" className='text-red mt-1.5 mb-2 mx-2'
/> />
{text && ( {text && (
<p className="relative top-0 text-red mr-2 text-sm py-1">{text}</p> <p className='relative top-0 text-red mr-2 text-sm py-1'>{text}</p>
)} )}
</div> </div>
); );
+15 -15
View File
@@ -1,4 +1,4 @@
import { memo,useState } from 'react'; import { memo, useState } from 'react';
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';
@@ -28,12 +28,12 @@ const InputField = (
if (props.static === true) { if (props.static === true) {
return ( return (
<div className="flex flex-col my-2 md:my-4 justify-center w-full max-w-md"> <div className='flex flex-col my-2 md:my-4 justify-center w-full max-w-md'>
<p className="text-sm font-semibold text-gray-400 mb-0.5"> <p className='text-sm font-semibold text-gray-400 mb-0.5'>
{props.label} {props.label}
</p> </p>
{props.text && ( {props.text && (
<p className="text-xs text-gray-400 mb-2">{props.text}</p> <p className='text-xs text-gray-400 mb-2'>{props.text}</p>
)} )}
<input <input
onChange={(e) => props.onChangeHandler(e.target.value)} onChange={(e) => props.onChangeHandler(e.target.value)}
@@ -41,7 +41,7 @@ const InputField = (
placeholder={props.placeholder} placeholder={props.placeholder}
value={props.value} value={props.value}
required={props.isRequired} required={props.isRequired}
className="bg-bunker-800 text-gray-400 border border-gray-600 rounded-md text-md p-2 w-full min-w-16 outline-none" className='bg-bunker-800 text-gray-400 border border-gray-600 rounded-md text-md p-2 w-full min-w-16 outline-none'
name={props.name} name={props.name}
readOnly readOnly
autoComplete={props.autoComplete} autoComplete={props.autoComplete}
@@ -51,9 +51,9 @@ const InputField = (
); );
} else { } else {
return ( return (
<div className="flex-col w-full"> <div className='flex-col w-full'>
<div className="flex flex-row text-mineshaft-300 items-center mb-0.5"> <div className='flex flex-row text-mineshaft-300 items-center mb-0.5'>
<p className="text-sm font-semibold mr-1">{props.label}</p> <p className='text-sm font-semibold mr-1'>{props.label}</p>
{/* {props.label == "Password" && router.asPath != "/login" && ( {/* {props.label == "Password" && router.asPath != "/login" && (
<div className="mb-0.5 relative inline-block text-gray-400 underline hover:text-primary duration-200"> <div className="mb-0.5 relative inline-block text-gray-400 underline hover:text-primary duration-200">
<FontAwesomeIcon <FontAwesomeIcon
@@ -90,17 +90,17 @@ const InputField = (
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
type="button" type='button'
onClick={() => { onClick={() => {
setPasswordVisible(!passwordVisible); setPasswordVisible(!passwordVisible);
}} }}
className="absolute self-end mr-3 text-gray-400 cursor-pointer" className='absolute self-end mr-3 text-gray-400 cursor-pointer'
> >
{passwordVisible ? ( {passwordVisible ? (
<FontAwesomeIcon icon={faEyeSlash} /> <FontAwesomeIcon icon={faEyeSlash} />
@@ -110,15 +110,15 @@ const InputField = (
</button> </button>
)} )}
{props.blurred && ( {props.blurred && (
<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
key={guidGenerator()} key={guidGenerator()}
className="text-xxs mx-0.5" className='text-xxs mx-0.5'
icon={faCircle} icon={faCircle}
/> />
))} ))}
@@ -131,7 +131,7 @@ const InputField = (
)} */} )} */}
</div> </div>
{props.error && ( {props.error && (
<p className="text-red text-xs mt-0.5 mx-0 mb-2 max-w-xs"> <p className='text-red text-xs mt-0.5 mx-0 mb-2 max-w-xs'>
{props.errorText} {props.errorText}
</p> </p>
)} )}
+17 -18
View File
@@ -3,7 +3,7 @@ import { Fragment } from 'react';
import { import {
faAngleDown, faAngleDown,
faCheck, faCheck,
faPlus faPlus,
} 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 { Listbox, Transition } from '@headlessui/react'; import { Listbox, Transition } from '@headlessui/react';
@@ -25,7 +25,6 @@ interface ListBoxProps {
* @param {string[]} obj.data - all the options available * @param {string[]} obj.data - all the options available
* @param {string} obj.text - the text that shows us in front of the select option * @param {string} obj.text - the text that shows us in front of the select option
* @param {function} obj.buttonAction - if there is a button at the bottom of the list, this is the action that happens when you click the button * @param {function} obj.buttonAction - if there is a button at the bottom of the list, this is the action that happens when you click the button
* @param {string} obj.width - button width
* @returns * @returns
*/ */
export default function ListBox({ export default function ListBox({
@@ -34,37 +33,37 @@ export default function ListBox({
data, data,
text, text,
buttonAction, buttonAction,
isFull isFull,
}: ListBoxProps): JSX.Element { }: ListBoxProps): JSX.Element {
return ( return (
<Listbox value={selected} onChange={onChange}> <Listbox value={selected} onChange={onChange}>
<div className="relative"> <div className='relative'>
<Listbox.Button <Listbox.Button
className={`text-gray-400 relative ${ className={`text-gray-400 relative ${
isFull ? 'w-full' : 'w-52' isFull ? 'w-full' : 'w-52'
} cursor-default rounded-md bg-white/[0.07] hover:bg-white/[0.11] duration-200 py-2.5 pl-3 pr-10 text-left shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm`} } cursor-default rounded-md bg-white/[0.07] hover:bg-white/[0.11] duration-200 py-2.5 pl-3 pr-10 text-left shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm`}
> >
<div className="flex flex-row"> <div className='flex flex-row'>
{text} {text}
<span className="ml-1 cursor-pointer block truncate font-semibold text-gray-300 capitalize"> <span className='ml-1 cursor-pointer block truncate font-semibold text-gray-300 capitalize'>
{' '} {' '}
{selected} {selected}
</span> </span>
</div> </div>
{data && ( {data && (
<div className="cursor-pointer pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"> <div className='cursor-pointer pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2'>
<FontAwesomeIcon icon={faAngleDown} className="text-md mr-1.5" /> <FontAwesomeIcon icon={faAngleDown} className='text-md mr-1.5' />
</div> </div>
)} )}
</Listbox.Button> </Listbox.Button>
{data && ( {data && (
<Transition <Transition
as={Fragment} as={Fragment}
leave="transition ease-in duration-100" leave='transition ease-in duration-100'
leaveFrom="opacity-100" leaveFrom='opacity-100'
leaveTo="opacity-0" leaveTo='opacity-0'
> >
<Listbox.Options className="border border-mineshaft-700 z-50 p-2 absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-bunker text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"> <Listbox.Options className='border border-mineshaft-700 z-50 p-2 absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-bunker text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm'>
{data.map((person, personIdx) => ( {data.map((person, personIdx) => (
<Listbox.Option <Listbox.Option
key={personIdx} key={personIdx}
@@ -89,10 +88,10 @@ export default function ListBox({
{person} {person}
</span> </span>
{selected ? ( {selected ? (
<span className="text-primary rounded-lg absolute inset-y-0 left-0 flex items-center pl-3"> <span className='text-primary rounded-lg absolute inset-y-0 left-0 flex items-center pl-3'>
<FontAwesomeIcon <FontAwesomeIcon
icon={faCheck} icon={faCheck}
className="text-md ml-1" className='text-md ml-1'
/> />
</span> </span>
) : null} ) : null}
@@ -103,11 +102,11 @@ export default function ListBox({
{buttonAction && ( {buttonAction && (
<button <button
onClick={buttonAction} onClick={buttonAction}
className="cursor-pointer w-full" className='cursor-pointer w-full'
> >
<div className="my-0.5 relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-lime-300 duration-200 hover:text-black hover:font-semibold mt-2"> <div className='my-0.5 relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-lime-300 duration-200 hover:text-black hover:font-semibold mt-2'>
<span className="rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4"> <span className='rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4'>
<FontAwesomeIcon icon={faPlus} className="text-lg" /> <FontAwesomeIcon icon={faPlus} className='text-lg' />
</span> </span>
Add Project Add Project
</div> </div>
+14 -15
View File
@@ -1,27 +1,26 @@
import React from "react"; import { Switch } from '@headlessui/react';
import { Switch } from "@headlessui/react";
interface ToggleProps { interface ToggleProps {
enabled: boolean; enabled: boolean;
setEnabled: (value: boolean) => void; setEnabled: (value: boolean) => void;
addOverride: (value: string | undefined, pos: number) => void; addOverride: (value: string | undefined, pos: number) => void;
pos: number; pos: number;
} }
/** /**
* This is a typical 'iPhone' toggle (e.g., user for overriding secrets with personal values) * This is a typical 'iPhone' toggle (e.g., user for overriding secrets with personal values)
* @param obj * @param obj
* @param {boolean} obj.enabled - whether the toggle is turned on or off * @param {boolean} obj.enabled - whether the toggle is turned on or off
* @param {function} obj.setEnabled - change the state of the toggle * @param {function} obj.setEnabled - change the state of the toggle
* @param {function} obj.addOverride - a function that adds an override to a certain secret * @param {function} obj.addOverride - a function that adds an override to a certain secret
* @param {number} obj.pos - position of a certain secret * @param {number} obj.pos - position of a certain secret
* @returns * @returns
*/ */
export default function Toggle ({ export default function Toggle({
enabled, enabled,
setEnabled, setEnabled,
addOverride, addOverride,
pos pos,
}: ToggleProps): JSX.Element { }: ToggleProps): JSX.Element {
return ( return (
<Switch <Switch
@@ -38,12 +37,12 @@ export default function Toggle ({
enabled ? 'bg-primary' : 'bg-bunker-400' enabled ? 'bg-primary' : 'bg-bunker-400'
} relative inline-flex h-5 w-9 items-center rounded-full`} } relative inline-flex h-5 w-9 items-center rounded-full`}
> >
<span className="sr-only">Enable notifications</span> <span className='sr-only'>Enable notifications</span>
<span <span
className={`${ className={`${
enabled ? 'translate-x-[1.26rem]' : 'translate-x-0.5' enabled ? 'translate-x-[1.26rem]' : 'translate-x-0.5'
} inline-block h-3.5 w-3.5 transform rounded-full bg-bunker-800 transition`} } inline-block h-3.5 w-3.5 transform rounded-full bg-bunker-800 transition`}
/> />
</Switch> </Switch>
) );
} }
@@ -31,7 +31,6 @@ const crypto = require('crypto');
const AddApiKeyDialog = ({ const AddApiKeyDialog = ({
isOpen, isOpen,
closeModal, closeModal,
workspaceName,
apiKeys, apiKeys,
setApiKeys setApiKeys
}) => { }) => {
@@ -107,9 +106,7 @@ const AddApiKeyDialog = ({
as="h3" as="h3"
className="text-lg font-medium leading-6 text-gray-400 z-50" className="text-lg font-medium leading-6 text-gray-400 z-50"
> >
{t("section-api-key:add-dialog.title", { {t("section-api-key:add-dialog.title")}
target: workspaceName,
})}
</Dialog.Title> </Dialog.Title>
<div className="mt-2 mb-4"> <div className="mt-2 mb-4">
<div className="flex flex-col"> <div className="flex flex-col">
@@ -1,7 +1,5 @@
import React from "react"; import { faXmark } from '@fortawesome/free-solid-svg-icons';
import { faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
interface PopupProps { interface PopupProps {
buttonText: string; buttonText: string;
@@ -15,7 +13,7 @@ interface PopupProps {
/** /**
* This is the notification that pops up at the bottom right when a user performs a certain action * This is the notification that pops up at the bottom right when a user performs a certain action
* @param {object} org * @param {object} org
* @param {string} org.buttonText - text of the button inside the notification * @param {string} org.buttonText - text of the button inside the notification
* @param {string} org.buttonLink - where the button leads to * @param {string} org.buttonLink - where the button leads to
* @param {string} org.titleText - the text at the top of a notification * @param {string} org.titleText - the text at the top of a notification
@@ -23,7 +21,7 @@ interface PopupProps {
* @param {string} org.textLine1 - first line of the text in the notification * @param {string} org.textLine1 - first line of the text in the notification
* @param {string} org.textLine2 - second line of the text in the notification * @param {string} org.textLine2 - second line of the text in the notification
* @param {string} org.setCheckDocsPopUpVisible - the functions that closes the popup * @param {string} org.setCheckDocsPopUpVisible - the functions that closes the popup
* @returns * @returns
*/ */
export default function BottonRightPopup({ export default function BottonRightPopup({
buttonText, buttonText,
@@ -36,35 +34,35 @@ export default function BottonRightPopup({
}: PopupProps): JSX.Element { }: PopupProps): JSX.Element {
return ( return (
<div <div
className="z-50 drop-shadow-xl border-gray-600/50 border flex flex-col items-start bg-bunker max-w-xl text-gray-200 pt-3 pb-4 rounded-md absolute bottom-0 right-0 mr-6 mb-6" className='z-50 drop-shadow-xl border-gray-600/50 border flex flex-col items-start bg-bunker max-w-xl text-gray-200 pt-3 pb-4 rounded-md absolute bottom-0 right-0 mr-6 mb-6'
role="alert" role='alert'
> >
<div className="flex flex-row items-center justify-between w-full border-b border-gray-600/70 pb-3 px-6"> <div className='flex flex-row items-center justify-between w-full border-b border-gray-600/70 pb-3 px-6'>
<div className="font-bold text-xl mr-2 mt-0.5 flex flex-row"> <div className='font-bold text-xl mr-2 mt-0.5 flex flex-row'>
<div>{titleText}</div> <div>{titleText}</div>
<div className="ml-2.5">{emoji}</div> <div className='ml-2.5'>{emoji}</div>
</div> </div>
<button <button
className="mt-1" className='mt-1'
onClick={() => setCheckDocsPopUpVisible(false)} onClick={() => setCheckDocsPopUpVisible(false)}
> >
<FontAwesomeIcon <FontAwesomeIcon
icon={faXmark} icon={faXmark}
className="text-gray-400 text-2xl hover:text-red duration-200 cursor-pointer" className='text-gray-400 text-2xl hover:text-red duration-200 cursor-pointer'
/> />
</button> </button>
</div> </div>
<div className="block sm:inline px-6 mt-4 mb-0.5 text-gray-300"> <div className='block sm:inline px-6 mt-4 mb-0.5 text-gray-300'>
{textLine1} {textLine1}
</div> </div>
<div className="block sm:inline mb-4 px-6">{textLine2}</div> <div className='block sm:inline mb-4 px-6'>{textLine2}</div>
<div className="flex flex-row px-6 w-full"> <div className='flex flex-row px-6 w-full'>
{/*eslint-disable-next-line react/jsx-no-target-blank */} {/*eslint-disable-next-line react/jsx-no-target-blank */}
<a <a
className="font-bold p-2 bg-white/10 rounded-md w-full hover:bg-primary duration-200 hover:text-black flex justify-center" className='font-bold p-2 bg-white/10 rounded-md w-full hover:bg-primary duration-200 hover:text-black flex justify-center'
href={buttonLink} href={buttonLink}
target="_blank" target='_blank'
rel="noopener" rel='noopener'
> >
{buttonText} {buttonText}
</a> </a>
@@ -1,28 +0,0 @@
import React from "react";
export const Checkbox = ({ addAllUsers, setAddAllUsers }) => {
return (
<>
<div className="flex flex-row items-center">
{addAllUsers == true ? (
<input
type="checkbox"
className="accent-primary h-4 w-4"
checked
readOnly
onClick={() => setAddAllUsers(!addAllUsers)}
/>
) : (
<div
className="h-4 w-4 bg-bunker border border-gray-600 rounded-sm"
onClick={() => setAddAllUsers(!addAllUsers)}
></div>
)}
<label className="ml-2 text-gray-500 text-sm">
Add all members of my organization to this project.
</label>
</div>
</>
);
};
@@ -0,0 +1,31 @@
type Props = {
addAllUsers: boolean;
setAddAllUsers: (arg: boolean) => void;
};
export const Checkbox = ({ addAllUsers, setAddAllUsers }: Props) => {
return (
<>
<div className='flex flex-row items-center'>
{addAllUsers == true ? (
<input
type='checkbox'
className='accent-primary h-4 w-4'
checked
readOnly
onClick={() => setAddAllUsers(!addAllUsers)}
/>
) : (
<div
className='h-4 w-4 bg-bunker border border-gray-600 rounded-sm'
onClick={() => setAddAllUsers(!addAllUsers)}
></div>
)}
<label className='ml-2 text-gray-500 text-sm'>
Add all members of my organization to this project.
</label>
</div>
</>
);
};
+16 -16
View File
@@ -8,9 +8,9 @@ interface Props {
plan: { plan: {
name: string; name: string;
price: string; price: string;
priceExplanation: string; priceExplanation?: string;
text: string; text: string;
subtext: string; subtext?: string;
buttonTextMain: string; buttonTextMain: string;
buttonTextSecondary: string; buttonTextSecondary: string;
current: boolean; current: boolean;
@@ -27,33 +27,33 @@ export default function Plan({ plan }: Props) {
} }
`} `}
> >
<div className="flex flex-col"> <div className='flex flex-col'>
<div className="flex flex-row justify-between items-center relative z-10"> <div className='flex flex-row justify-between items-center relative z-10'>
<p className={`px-6 py-4 text-3xl font-semibold text-gray-400`}> <p className={`px-6 py-4 text-3xl font-semibold text-gray-400`}>
{plan.name} {plan.name}
</p> </p>
</div> </div>
<div className="flex flwx-row items-end justify-start mb-4"> <div className='flex flwx-row items-end justify-start mb-4'>
<p className="pl-6 text-3xl font-semibold text-primary"> <p className='pl-6 text-3xl font-semibold text-primary'>
{plan.price} {plan.price}
</p> </p>
<p className="pl-3 mb-1 text-lg text-gray-400"> <p className='pl-3 mb-1 text-lg text-gray-400'>
{plan.priceExplanation} {plan.priceExplanation}
</p> </p>
</div> </div>
<p className="relative z-10 max-w-fit px-6 text-base text-gray-400"> <p className='relative z-10 max-w-fit px-6 text-base text-gray-400'>
{plan.text} {plan.text}
</p> </p>
<p className="relative z-10 max-w-fit px-6 text-base text-gray-400"> <p className='relative z-10 max-w-fit px-6 text-base text-gray-400'>
{plan.subtext} {plan.subtext}
</p> </p>
</div> </div>
<div className="flex flex-row items-center"> <div className='flex flex-row items-center'>
{plan.current == false ? ( {plan.current == false ? (
<> <>
{plan.buttonTextMain == 'Schedule a Demo' ? ( {plan.buttonTextMain == 'Schedule a Demo' ? (
<a href="/scheduledemo" target='_blank rel="noopener"'> <a href='/scheduledemo' target='_blank rel="noopener"'>
<div className="relative z-10 mx-5 mt-3 mb-4 py-2 px-4 border border-1 border-gray-600 hover:text-black hover:border-primary text-gray-400 font-semibold hover:bg-primary bg-bunker duration-200 cursor-pointer rounded-md flex w-max"> <div className='relative z-10 mx-5 mt-3 mb-4 py-2 px-4 border border-1 border-gray-600 hover:text-black hover:border-primary text-gray-400 font-semibold hover:bg-primary bg-bunker duration-200 cursor-pointer rounded-md flex w-max'>
{plan.buttonTextMain} {plan.buttonTextMain}
</div> </div>
</a> </a>
@@ -68,7 +68,7 @@ export default function Plan({ plan }: Props) {
<button <button
onClick={() => onClick={() =>
StripeRedirect({ StripeRedirect({
orgId: tempLocalStorage('orgData.id') orgId: tempLocalStorage('orgData.id'),
}) })
} }
> >
@@ -77,10 +77,10 @@ export default function Plan({ plan }: Props) {
</div> </div>
)} )}
<a <a
href="https://infisical.com/pricing" href='https://infisical.com/pricing'
target='_blank rel="noopener"' target='_blank rel="noopener"'
> >
<div className="relative z-10 text-gray-400 font-semibold hover:text-primary duration-200 cursor-pointer mb-0.5"> <div className='relative z-10 text-gray-400 font-semibold hover:text-primary duration-200 cursor-pointer mb-0.5'>
{plan.buttonTextSecondary} {plan.buttonTextSecondary}
</div> </div>
</a> </a>
@@ -93,7 +93,7 @@ export default function Plan({ plan }: Props) {
: 'bg-chicago-700' : 'bg-chicago-700'
}`} }`}
> >
<p className="text-xs text-black font-semibold">CURRENT PLAN</p> <p className='text-xs text-black font-semibold'>CURRENT PLAN</p>
</div> </div>
)} )}
</div> </div>
@@ -1,121 +1,117 @@
import React from "react"; import Image from 'next/image';
import Image from "next/image"; import { useRouter } from 'next/router';
import { useRouter } from "next/router"; import { faCheck, faX } from '@fortawesome/free-solid-svg-icons';
import { import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
faCheck,
faX,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import deleteIntegrationAuth from "../../pages/api/integrations/DeleteIntegrationAuth"; import deleteIntegrationAuth from '../../pages/api/integrations/DeleteIntegrationAuth';
interface CloudIntegrationOption { interface CloudIntegrationOption {
isAvailable: boolean; isAvailable: boolean;
name: string; name: string;
type: string; type: string;
clientId: string; clientId: string;
docsLink: string; docsLink: string;
slug: string; slug: string;
} }
interface IntegrationAuth { interface IntegrationAuth {
_id: string; _id: string;
integration: string; integration: string;
} }
interface Props { interface Props {
cloudIntegrationOption: CloudIntegrationOption; cloudIntegrationOption: CloudIntegrationOption;
setSelectedIntegrationOption: (cloudIntegration: CloudIntegrationOption) => void; setSelectedIntegrationOption: (
integrationOptionPress: (cloudIntegrationOption: CloudIntegrationOption) => void; cloudIntegration: CloudIntegrationOption
integrationAuths: IntegrationAuth[]; ) => void;
integrationOptionPress: (
cloudIntegrationOption: CloudIntegrationOption
) => void;
integrationAuths: IntegrationAuth[];
} }
const CloudIntegration = ({ const CloudIntegration = ({
cloudIntegrationOption, cloudIntegrationOption,
setSelectedIntegrationOption, setSelectedIntegrationOption,
integrationOptionPress, integrationOptionPress,
integrationAuths integrationAuths,
}: Props) => { }: Props) => {
const router = useRouter(); const router = useRouter();
return integrationAuths ? ( return integrationAuths ? (
<div <div
className={`relative ${ className={`relative ${
cloudIntegrationOption.isAvailable cloudIntegrationOption.isAvailable
? "hover:bg-white/10 duration-200 cursor-pointer" ? 'hover:bg-white/10 duration-200 cursor-pointer'
: "opacity-50" : 'opacity-50'
} flex flex-row bg-white/5 h-32 rounded-md p-4 items-center`} } flex flex-row bg-white/5 h-32 rounded-md p-4 items-center`}
onClick={() => { onClick={() => {
if (!cloudIntegrationOption.isAvailable) return; if (!cloudIntegrationOption.isAvailable) return;
setSelectedIntegrationOption(cloudIntegrationOption); setSelectedIntegrationOption(cloudIntegrationOption);
integrationOptionPress(cloudIntegrationOption); integrationOptionPress(cloudIntegrationOption);
}} }}
key={cloudIntegrationOption.name} key={cloudIntegrationOption.name}
> >
<Image <Image
src={`/images/integrations/${cloudIntegrationOption.name}.png`} src={`/images/integrations/${cloudIntegrationOption.name}.png`}
height={70} height={70}
width={70} width={70}
alt="integration logo" alt='integration logo'
/> />
{cloudIntegrationOption.name.split(" ").length > 2 ? ( {cloudIntegrationOption.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>{cloudIntegrationOption.name.split(" ")[0]}</div> <div>{cloudIntegrationOption.name.split(' ')[0]}</div>
<div className="text-base"> <div className='text-base'>
{cloudIntegrationOption.name.split(" ")[1]}{" "} {cloudIntegrationOption.name.split(' ')[1]}{' '}
{cloudIntegrationOption.name.split(" ")[2]} {cloudIntegrationOption.name.split(' ')[2]}
</div> </div>
</div>
) : (
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-xl ml-4 max-w-xs">
{cloudIntegrationOption.name}
</div>
)}
{cloudIntegrationOption.isAvailable &&
integrationAuths
.map((authorization) => authorization.integration)
.includes(cloudIntegrationOption.name.toLowerCase()) && (
<div className="absolute group z-40 top-0 right-0 flex flex-row">
<div
onClick={(event) => {
event.stopPropagation();
deleteIntegrationAuth({
integrationAuthId: integrationAuths
.filter(
(authorization) =>
authorization.integration ==
cloudIntegrationOption.name.toLowerCase()
)
.map((authorization) => authorization._id)[0],
});
router.reload();
}}
className="cursor-pointer w-max bg-red py-0.5 px-2 rounded-b-md text-xs flex flex-row items-center opacity-0 group-hover:opacity-100 duration-200"
>
<FontAwesomeIcon
icon={faX}
className="text-xs mr-2 py-px"
/>
Revoke
</div>
<div className="w-max bg-primary py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90 group-hover:opacity-100 duration-200">
<FontAwesomeIcon
icon={faCheck}
className="text-xs mr-2"
/>
Authorized
</div>
</div>
)}
{!cloudIntegrationOption.isAvailable && (
<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">
Coming Soon
</div>
</div>
)}
</div> </div>
) : <div></div> ) : (
} <div className='font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-xl ml-4 max-w-xs'>
{cloudIntegrationOption.name}
</div>
)}
{cloudIntegrationOption.isAvailable &&
integrationAuths
.map((authorization) => authorization.integration)
.includes(cloudIntegrationOption.name.toLowerCase()) && (
<div className='absolute group z-40 top-0 right-0 flex flex-row'>
<div
onClick={(event) => {
event.stopPropagation();
deleteIntegrationAuth({
integrationAuthId: integrationAuths
.filter(
(authorization) =>
authorization.integration ==
cloudIntegrationOption.name.toLowerCase()
)
.map((authorization) => authorization._id)[0],
});
export default CloudIntegration; router.reload();
}}
className='cursor-pointer w-max bg-red py-0.5 px-2 rounded-b-md text-xs flex flex-row items-center opacity-0 group-hover:opacity-100 duration-200'
>
<FontAwesomeIcon icon={faX} className='text-xs mr-2 py-px' />
Revoke
</div>
<div className='w-max bg-primary py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90 group-hover:opacity-100 duration-200'>
<FontAwesomeIcon icon={faCheck} className='text-xs mr-2' />
Authorized
</div>
</div>
)}
{!cloudIntegrationOption.isAvailable && (
<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'>
Coming Soon
</div>
</div>
)}
</div>
) : (
<div></div>
);
};
export default CloudIntegration;
@@ -1,53 +1,56 @@
import React from "react"; import { useTranslation } from 'next-i18next';
import { useTranslation } from "next-i18next";
import CloudIntegration from "./CloudIntegration"; import CloudIntegration from './CloudIntegration';
interface CloudIntegrationOption { interface CloudIntegrationOption {
isAvailable: boolean; isAvailable: boolean;
name: string; name: string;
type: string; type: string;
clientId: string; clientId: string;
docsLink: string; docsLink: string;
slug: string; slug: string;
} }
interface Props { interface Props {
cloudIntegrationOptions: CloudIntegrationOption[]; cloudIntegrationOptions: CloudIntegrationOption[];
setSelectedIntegrationOption: () => void; setSelectedIntegrationOption: () => void;
integrationOptionPress: () => void; integrationOptionPress: () => void;
integrationAuths: any; integrationAuths: any;
} }
const CloudIntegrationSection = ({ const CloudIntegrationSection = ({
cloudIntegrationOptions, cloudIntegrationOptions,
setSelectedIntegrationOption, setSelectedIntegrationOption,
integrationOptionPress, integrationOptionPress,
integrationAuths integrationAuths,
}: Props) => { }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<> <>
<div className={`flex flex-col justify-between items-start m-4 mt-7 text-xl max-w-5xl px-2`}> <div
<h1 className="font-semibold text-3xl">{t("integrations:cloud-integrations")}</h1> className={`flex flex-col justify-between items-start m-4 mt-7 text-xl max-w-5xl px-2`}
<p className="text-base text-gray-400"> >
{t("integrations:click-to-start")} <h1 className='font-semibold text-3xl'>
</p> {t('integrations:cloud-integrations')}
</div> </h1>
<div className="grid gap-4 grid-cols-4 grid-rows-2 mx-6 max-w-5xl"> <p className='text-base text-gray-400'>
{cloudIntegrationOptions.map((cloudIntegrationOption) => ( {t('integrations:click-to-start')}
<CloudIntegration </p>
cloudIntegrationOption={cloudIntegrationOption} </div>
setSelectedIntegrationOption={setSelectedIntegrationOption} <div className='grid gap-4 grid-cols-4 grid-rows-2 mx-6 max-w-5xl'>
integrationOptionPress={integrationOptionPress} {cloudIntegrationOptions.map((cloudIntegrationOption) => (
integrationAuths={integrationAuths} <CloudIntegration
key={`cloud-integration-${cloudIntegrationOption.slug}`} cloudIntegrationOption={cloudIntegrationOption}
/> setSelectedIntegrationOption={setSelectedIntegrationOption}
))} integrationOptionPress={integrationOptionPress}
</div> integrationAuths={integrationAuths}
</> key={`cloud-integration-${cloudIntegrationOption.slug}`}
); />
} ))}
</div>
</>
);
};
export default CloudIntegrationSection; export default CloudIntegrationSection;
@@ -1,5 +1,4 @@
import React from "react"; import Image from 'next/image';
import Image from "next/image";
interface Framework { interface Framework {
name: string; name: string;
@@ -8,29 +7,33 @@ interface Framework {
docsLink: string; docsLink: string;
} }
const FrameworkIntegration = ({ const FrameworkIntegration = ({ framework }: { framework: Framework }) => {
framework
}: {
framework: Framework;
}) => {
return ( return (
<a <a
href={framework.docsLink} href={framework.docsLink}
rel="noopener" rel='noopener'
className={`relative flex flex-row items-center justify-center bg-bunker-500 hover:bg-gradient-to-tr 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 duration-200 h-32 rounded-md p-0.5 items-center cursor-pointer`}
>
<div
className={`hover:bg-white/10 duration-200 cursor-pointer 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 className={`hover:bg-white/10 duration-200 cursor-pointer 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`}> {framework?.image && (
{framework?.image && <Image <Image
src={`/images/integrations/${framework.image}.png`} src={`/images/integrations/${framework.image}.png`}
height={framework?.name ? 60 : 90} height={framework?.name ? 60 : 90}
width={framework?.name ? 60 : 90} width={framework?.name ? 60 : 90}
alt="integration logo" alt='integration logo'
></Image>} ></Image>
{framework?.name && framework?.image && <div className="h-2"></div>} )}
{framework?.name && framework.name} {framework?.name && framework?.image && <div className='h-2'></div>}
</div> {framework?.name && framework.name}
</a> </div>
</a>
); );
} };
export default FrameworkIntegration; export default FrameworkIntegration;
@@ -1,18 +1,17 @@
import React from "react"; import { useTranslation } from 'next-i18next';
import { useTranslation } from "next-i18next";
import FrameworkIntegration from "./FrameworkIntegration"; import FrameworkIntegration from './FrameworkIntegration';
interface Framework { interface Framework {
name: string; name: string;
image: string; image: string;
link: string; link: string;
slug: string; slug: string;
docsLink: string; docsLink: string;
} }
interface Props { interface Props {
frameworks: [Framework] frameworks: [Framework];
} }
const FrameworkIntegrationSection = ({ frameworks }: Props) => { const FrameworkIntegrationSection = ({ frameworks }: Props) => {
@@ -20,15 +19,17 @@ const FrameworkIntegrationSection = ({ frameworks }: Props) => {
return ( return (
<> <>
<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'>
<h1 className="font-semibold text-3xl">{t("integrations:framework-integrations")}</h1> <h1 className='font-semibold text-3xl'>
<p className="text-base text-gray-400"> {t('integrations:framework-integrations')}
{t("integrations:click-to-setup")} </h1>
<p className='text-base text-gray-400'>
{t('integrations:click-to-setup')}
</p> </p>
</div> </div>
<div className="grid gap-4 grid-cols-7 grid-rows-2 mx-6 mt-4 max-w-5xl"> <div className='grid gap-4 grid-cols-7 grid-rows-2 mx-6 mt-4 max-w-5xl'>
{frameworks.map((framework) => ( {frameworks.map((framework) => (
<FrameworkIntegration <FrameworkIntegration
framework={framework} framework={framework}
key={`framework-integration-${framework.slug}`} key={`framework-integration-${framework.slug}`}
/> />
@@ -36,7 +37,6 @@ const FrameworkIntegrationSection = ({ frameworks }: Props) => {
</div> </div>
</> </>
); );
} };
export default FrameworkIntegrationSection; export default FrameworkIntegrationSection;
@@ -1,8 +1,6 @@
import React from "react"; import guidGenerator from '~/utilities/randomId';
import guidGenerator from "~/utilities/randomId"; import Integration from './Integration';
import Integration from "./Integration";
interface Props { interface Props {
integrations: any; integrations: any;
@@ -23,23 +21,25 @@ const ProjectIntegrationSection = ({
integrations, integrations,
environments = [], environments = [],
}: Props) => { }: Props) => {
return integrations.length > 0 ? ( return integrations.length > 0 ? (
<div className="mb-12"> <div className='mb-12'>
<div className="flex flex-col justify-between items-start mx-4 mb-4 mt-6 text-xl max-w-5xl px-2"> <div className='flex flex-col justify-between items-start mx-4 mb-4 mt-6 text-xl max-w-5xl px-2'>
<h1 className="font-semibold text-3xl">Current Integrations</h1> <h1 className='font-semibold text-3xl'>Current Integrations</h1>
<p className="text-base text-gray-400"> <p className='text-base text-gray-400'>
Manage your integrations of Infisical with third-party services. Manage your integrations of Infisical with third-party services.
</p> </p>
</div>
{integrations.map((integration: IntegrationType) => (
<Integration
key={guidGenerator()}
integration={integration}
environments={environments}
/>
))}
</div> </div>
) : <div></div> {integrations.map((integration: IntegrationType) => (
} <Integration
key={guidGenerator()}
integration={integration}
environments={environments}
/>
))}
</div>
) : (
<div></div>
);
};
export default ProjectIntegrationSection; export default ProjectIntegrationSection;
+26 -22
View File
@@ -1,32 +1,36 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from 'react';
import { useRouter } from "next/router"; import { useRouter } from 'next/router';
import { import { faAngleRight } from '@fortawesome/free-solid-svg-icons';
faAngleRight, import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import getOrganization from "~/pages/api/organization/GetOrg"; import getOrganization from '~/pages/api/organization/GetOrg';
import getProjectInfo from "~/pages/api/workspace/getProjectInfo"; import getProjectInfo from '~/pages/api/workspace/getProjectInfo';
/** /**
* This is the component at the top of almost every page. * This is the component at the top of almost every page.
* It shows how to navigate to a certain page. * It shows how to navigate to a certain page.
* It future these links should also be clickable and hoverable * It future these links should also be clickable and hoverable
* @param obj * @param obj
* @param obj.pageName - Name of the page * @param obj.pageName - Name of the page
* @param obj.isProjectRelated - whether this page is related to project or now (determine if it's 2 or 3 navigation steps) * @param obj.isProjectRelated - whether this page is related to project or now (determine if it's 2 or 3 navigation steps)
* @returns * @returns
*/ */
export default function NavHeader({ pageName, isProjectRelated } : { pageName: string; isProjectRelated: boolean; }): JSX.Element { export default function NavHeader({
const [orgName, setOrgName] = useState(""); pageName,
const [workspaceName, setWorkspaceName] = useState(""); isProjectRelated,
}: {
pageName: string;
isProjectRelated?: boolean;
}): JSX.Element {
const [orgName, setOrgName] = useState('');
const [workspaceName, setWorkspaceName] = useState('');
const router = useRouter(); const router = useRouter();
useEffect(() => { useEffect(() => {
(async () => { (async () => {
const orgId = localStorage.getItem("orgData.id") const orgId = localStorage.getItem('orgData.id');
const org = await getOrganization({ const org = await getOrganization({
orgId: orgId ? orgId : "", orgId: orgId ? orgId : '',
}); });
setOrgName(org.name); setOrgName(org.name);
@@ -39,27 +43,27 @@ export default function NavHeader({ pageName, isProjectRelated } : { pageName: s
}, []); }, []);
return ( return (
<div className="pt-20 ml-6 flex flex-row items-center"> <div className='pt-20 ml-6 flex flex-row items-center'>
<div className="bg-primary-900 h-6 w-6 rounded-md flex items-center justify-center text-mineshaft-100 mr-2"> <div className='bg-primary-900 h-6 w-6 rounded-md flex items-center justify-center text-mineshaft-100 mr-2'>
{orgName?.charAt(0)} {orgName?.charAt(0)}
</div> </div>
<div className="text-primary text-sm font-semibold">{orgName}</div> <div className='text-primary text-sm font-semibold'>{orgName}</div>
{isProjectRelated && ( {isProjectRelated && (
<> <>
<FontAwesomeIcon <FontAwesomeIcon
icon={faAngleRight} icon={faAngleRight}
className="ml-3 text-sm text-gray-400 mr-3" className='ml-3 text-sm text-gray-400 mr-3'
/> />
<div className="font-semibold text-primary text-sm"> <div className='font-semibold text-primary text-sm'>
{workspaceName} {workspaceName}
</div> </div>
</> </>
)} )}
<FontAwesomeIcon <FontAwesomeIcon
icon={faAngleRight} icon={faAngleRight}
className="ml-3 text-sm text-gray-400 mr-3" className='ml-3 text-sm text-gray-400 mr-3'
/> />
<div className="text-gray-400 text-sm">{pageName}</div> <div className='text-gray-400 text-sm'>{pageName}</div>
</div> </div>
); );
} }
@@ -1,47 +1,50 @@
import React from "react"; import { useTranslation } from 'next-i18next';
import { useTranslation } from "next-i18next"; import { faWarning } from '@fortawesome/free-solid-svg-icons';
import { faWarning } 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 issueBackupKey from "../utilities/cryptography/issueBackupKey";
import Button from '../basic/buttons/Button';
import issueBackupKey from '../utilities/cryptography/issueBackupKey';
interface DownloadBackupPDFStepProps { interface DownloadBackupPDFStepProps {
incrementStep: () => void; incrementStep: () => void;
email: string; email: string;
password: string; password: string;
name: string; name: string;
} }
/** /**
* This is the step of the signup flow where the user downloads the backup pdf * This is the step of the signup flow where the user downloads the backup pdf
* @param {object} obj * @param {object} obj
* @param {function} obj.incrementStep - function that moves the user on to the next stage of signup * @param {function} obj.incrementStep - function that moves the user on to the next stage of signup
* @param {string} obj.email - user's email * @param {string} obj.email - user's email
* @param {string} obj.password - user's password * @param {string} obj.password - user's password
* @param {string} obj.name - user's name * @param {string} obj.name - user's name
* @returns * @returns
*/ */
export default function DonwloadBackupPDFStep({ incrementStep, email, password, name }: DownloadBackupPDFStepProps): JSX.Element { export default function DonwloadBackupPDFStep({
incrementStep,
email,
password,
name,
}: DownloadBackupPDFStepProps): JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div className="bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl"> <div className='bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl'>
<p className="text-4xl text-center font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary"> <p className='text-4xl text-center font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary'>
{t("signup:step4-message")} {t('signup:step4-message')}
</p> </p>
<div className="flex flex-col items-center justify-center w-full mt-4 md:mt-8 max-w-md text-gray-400 text-md rounded-md px-2"> <div className='flex flex-col items-center justify-center w-full mt-4 md:mt-8 max-w-md text-gray-400 text-md rounded-md px-2'>
<div>{t("signup:step4-description1")}</div> <div>{t('signup:step4-description1')}</div>
<div className="mt-3">{t("signup:step4-description2")}</div> <div className='mt-3'>{t('signup:step4-description2')}</div>
</div> </div>
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-400 rounded-md max-w-xs md:max-w-md mx-auto mt-4"> <div className='w-full p-2 flex flex-row items-center bg-white/10 text-gray-400 rounded-md max-w-xs md:max-w-md mx-auto mt-4'>
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-4 text-4xl" /> <FontAwesomeIcon icon={faWarning} className='ml-2 mr-4 text-4xl' />
{t("signup:step4-description3")} {t('signup:step4-description3')}
</div> </div>
<div className="flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg"> <div className='flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg'>
<Button <Button
text="Download PDF" text='Download PDF'
onButtonPressed={async () => { onButtonPressed={async () => {
await issueBackupKey({ await issueBackupKey({
email, email,
@@ -52,7 +55,7 @@ export default function DonwloadBackupPDFStep({ incrementStep, email, password,
}); });
incrementStep(); incrementStep();
}} }}
size="lg" size='lg'
/> />
</div> </div>
</div> </div>
+71 -71
View File
@@ -1,20 +1,20 @@
import React, { useState } from "react"; import React, { useState } from 'react';
import { useRouter } from "next/router"; import { useRouter } from 'next/router';
import { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next';
import { faCheck, faX } from "@fortawesome/free-solid-svg-icons"; import { faCheck, faX } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import completeAccountInformationSignup from "~/pages/api/auth/CompleteAccountInformationSignup"; import completeAccountInformationSignup from '~/pages/api/auth/CompleteAccountInformationSignup';
import Button from "../basic/buttons/Button"; import Button from '../basic/buttons/Button';
import InputField from "../basic/InputField"; import InputField from '../basic/InputField';
import attemptLogin from "../utilities/attemptLogin"; import attemptLogin from '../utilities/attemptLogin';
import passwordCheck from "../utilities/checks/PasswordCheck"; import passwordCheck from '../utilities/checks/PasswordCheck';
import Aes256Gcm from "../utilities/cryptography/aes-256-gcm"; import Aes256Gcm from '../utilities/cryptography/aes-256-gcm';
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();
interface UserInfoStepProps { interface UserInfoStepProps {
@@ -42,16 +42,16 @@ interface UserInfoStepProps {
* @param {string} obj.lastName - user's lastName * @param {string} obj.lastName - user's lastName
* @param {string} obj.setLastName - function managing the state of user's last name * @param {string} obj.setLastName - function managing the state of user's last name
*/ */
export default function UserInfoStep({ export default function UserInfoStep({
verificationToken, verificationToken,
incrementStep, incrementStep,
email, email,
password, password,
setPassword, setPassword,
firstName, firstName,
setFirstName, setFirstName,
lastName, lastName,
setLastName setLastName,
}: UserInfoStepProps): JSX.Element { }: UserInfoStepProps): JSX.Element {
const [firstNameError, setFirstNameError] = useState(false); const [firstNameError, setFirstNameError] = useState(false);
const [lastNameError, setLastNameError] = useState(false); const [lastNameError, setLastNameError] = useState(false);
@@ -63,7 +63,7 @@ export default function UserInfoStep({
const { t } = useTranslation(); const { t } = useTranslation();
const router = useRouter(); const router = useRouter();
// Verifies if the information that the users entered (name, workspace) // Verifies if the information that the users entered (name, workspace)
// is there, and if the password matches the criteria. // is there, and if the password matches the criteria.
const signupErrorCheck = async () => { const signupErrorCheck = async () => {
setIsLoading(true); setIsLoading(true);
@@ -85,7 +85,7 @@ export default function UserInfoStep({
setPasswordErrorLength, setPasswordErrorLength,
setPasswordErrorNumber, setPasswordErrorNumber,
setPasswordErrorLowerCase, setPasswordErrorLowerCase,
currentErrorCheck: errorCheck, errorCheck,
}); });
if (!errorCheck) { if (!errorCheck) {
@@ -102,11 +102,11 @@ export default function UserInfoStep({
.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(
{ {
@@ -134,10 +134,10 @@ export default function UserInfoStep({
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(
@@ -163,45 +163,45 @@ export default function UserInfoStep({
}; };
return ( return (
<div className="bg-bunker w-max mx-auto h-7/12 py-10 px-8 rounded-xl drop-shadow-xl mb-36 md:mb-16"> <div className='bg-bunker w-max mx-auto h-7/12 py-10 px-8 rounded-xl drop-shadow-xl mb-36 md:mb-16'>
<p className="text-4xl font-bold flex justify-center mb-6 text-gray-400 mx-8 md:mx-16 text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary"> <p className='text-4xl font-bold flex justify-center mb-6 text-gray-400 mx-8 md:mx-16 text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary'>
{t("signup:step3-message")} {t('signup:step3-message')}
</p> </p>
<div className="relative z-0 flex items-center justify-end w-full md:p-2 rounded-lg max-h-24"> <div className='relative z-0 flex items-center justify-end w-full md:p-2 rounded-lg max-h-24'>
<InputField <InputField
label={t("common:first-name")} label={t('common:first-name')}
onChangeHandler={setFirstName} onChangeHandler={setFirstName}
type="name" type='name'
value={firstName} value={firstName}
isRequired isRequired
errorText={ errorText={
t("common:validate-required", { t('common:validate-required', {
name: t("common:first-name"), name: t('common:first-name'),
}) as string }) as string
} }
error={firstNameError} error={firstNameError}
autoComplete="given-name" autoComplete='given-name'
/> />
</div> </div>
<div className="mt-2 flex items-center justify-center w-full md:p-2 rounded-lg max-h-24"> <div className='mt-2 flex items-center justify-center w-full md:p-2 rounded-lg max-h-24'>
<InputField <InputField
label={t("common:last-name")} label={t('common:last-name')}
onChangeHandler={setLastName} onChangeHandler={setLastName}
type="name" type='name'
value={lastName} value={lastName}
isRequired isRequired
errorText={ errorText={
t("common:validate-required", { t('common:validate-required', {
name: t("common:last-name"), name: t('common:last-name'),
}) as string }) as string
} }
error={lastNameError} error={lastNameError}
autoComplete="family-name" autoComplete='family-name'
/> />
</div> </div>
<div className="mt-2 flex flex-col items-center justify-center w-full md:p-2 rounded-lg max-h-60"> <div className='mt-2 flex flex-col items-center justify-center w-full md:p-2 rounded-lg max-h-60'>
<InputField <InputField
label={t("section-password:password")} label={t('section-password:password')}
onChangeHandler={(password: string) => { onChangeHandler={(password: string) => {
setPassword(password); setPassword(password);
passwordCheck({ passwordCheck({
@@ -209,98 +209,98 @@ export default function UserInfoStep({
setPasswordErrorLength, setPasswordErrorLength,
setPasswordErrorNumber, setPasswordErrorNumber,
setPasswordErrorLowerCase, setPasswordErrorLowerCase,
currentErrorCheck: false, errorCheck: false,
}); });
}} }}
type="password" type='password'
value={password} value={password}
isRequired isRequired
error={ error={
passwordErrorLength && passwordErrorNumber && passwordErrorLowerCase passwordErrorLength && passwordErrorNumber && passwordErrorLowerCase
} }
autoComplete="new-password" autoComplete='new-password'
id="new-password" id='new-password'
/> />
{passwordErrorLength || {passwordErrorLength ||
passwordErrorLowerCase || passwordErrorLowerCase ||
passwordErrorNumber ? ( passwordErrorNumber ? (
<div className="w-full mt-4 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md"> <div className='w-full mt-4 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md'>
<div className={`text-gray-400 text-sm mb-1`}> <div className={`text-gray-400 text-sm mb-1`}>
{t("section-password:validate-base")} {t('section-password:validate-base')}
</div> </div>
<div className="flex flex-row justify-start items-center ml-1"> <div className='flex flex-row justify-start items-center ml-1'>
{passwordErrorLength ? ( {passwordErrorLength ? (
<FontAwesomeIcon <FontAwesomeIcon
icon={faX} icon={faX}
className="text-md text-red mr-2.5" className='text-md text-red mr-2.5'
/> />
) : ( ) : (
<FontAwesomeIcon <FontAwesomeIcon
icon={faCheck} icon={faCheck}
className="text-md text-primary mr-2" className='text-md text-primary mr-2'
/> />
)} )}
<div <div
className={`${ className={`${
passwordErrorLength ? "text-gray-400" : "text-gray-600" passwordErrorLength ? 'text-gray-400' : 'text-gray-600'
} text-sm`} } text-sm`}
> >
{t("section-password:validate-length")} {t('section-password:validate-length')}
</div> </div>
</div> </div>
<div className="flex flex-row justify-start items-center ml-1"> <div className='flex flex-row justify-start items-center ml-1'>
{passwordErrorLowerCase ? ( {passwordErrorLowerCase ? (
<FontAwesomeIcon <FontAwesomeIcon
icon={faX} icon={faX}
className="text-md text-red mr-2.5" className='text-md text-red mr-2.5'
/> />
) : ( ) : (
<FontAwesomeIcon <FontAwesomeIcon
icon={faCheck} icon={faCheck}
className="text-md text-primary mr-2" className='text-md text-primary mr-2'
/> />
)} )}
<div <div
className={`${ className={`${
passwordErrorLowerCase ? "text-gray-400" : "text-gray-600" passwordErrorLowerCase ? 'text-gray-400' : 'text-gray-600'
} text-sm`} } text-sm`}
> >
{t("section-password:validate-case")} {t('section-password:validate-case')}
</div> </div>
</div> </div>
<div className="flex flex-row justify-start items-center ml-1"> <div className='flex flex-row justify-start items-center ml-1'>
{passwordErrorNumber ? ( {passwordErrorNumber ? (
<FontAwesomeIcon <FontAwesomeIcon
icon={faX} icon={faX}
className="text-md text-red mr-2.5" className='text-md text-red mr-2.5'
/> />
) : ( ) : (
<FontAwesomeIcon <FontAwesomeIcon
icon={faCheck} icon={faCheck}
className="text-md text-primary mr-2" className='text-md text-primary mr-2'
/> />
)} )}
<div <div
className={`${ className={`${
passwordErrorNumber ? "text-gray-400" : "text-gray-600" passwordErrorNumber ? 'text-gray-400' : 'text-gray-600'
} text-sm`} } text-sm`}
> >
{t("section-password:validate-number")} {t('section-password:validate-number')}
</div> </div>
</div> </div>
</div> </div>
) : ( ) : (
<div className="py-2"></div> <div className='py-2'></div>
)} )}
</div> </div>
<div className="flex flex-col items-center justify-center md:p-2 max-h-48 max-w-max mx-auto text-lg px-2 py-3"> <div className='flex flex-col items-center justify-center md:p-2 max-h-48 max-w-max mx-auto text-lg px-2 py-3'>
<Button <Button
text={t("signup:signup") ?? ""} text={t('signup:signup') ?? ''}
loading={isLoading} loading={isLoading}
onButtonPressed={signupErrorCheck} onButtonPressed={signupErrorCheck}
size="lg" size='lg'
/> />
</div> </div>
</div> </div>
) );
} }
@@ -1,6 +1,6 @@
interface PasswordCheckProps { interface PasswordCheckProps {
password: string; password: string;
currentErrorCheck: boolean; errorCheck: boolean;
setPasswordErrorLength: (value: boolean) => void; setPasswordErrorLength: (value: boolean) => void;
setPasswordErrorNumber: (value: boolean) => void; setPasswordErrorNumber: (value: boolean) => void;
setPasswordErrorLowerCase: (value: boolean) => void; setPasswordErrorLowerCase: (value: boolean) => void;
@@ -14,9 +14,8 @@ const passwordCheck = ({
setPasswordErrorLength, setPasswordErrorLength,
setPasswordErrorNumber, setPasswordErrorNumber,
setPasswordErrorLowerCase, setPasswordErrorLowerCase,
currentErrorCheck, errorCheck,
}: PasswordCheckProps) => { }: PasswordCheckProps) => {
let errorCheck = currentErrorCheck;
if (!password || password.length < 14) { if (!password || password.length < 14) {
setPasswordErrorLength(true); setPasswordErrorLength(true);
errorCheck = true; errorCheck = true;
@@ -1,11 +1,10 @@
import jsrp from 'jsrp';
import changePassword2 from '~/pages/api/auth/ChangePassword2'; import changePassword2 from '~/pages/api/auth/ChangePassword2';
import SRP1 from '~/pages/api/auth/SRP1'; import SRP1 from '~/pages/api/auth/SRP1';
import Aes256Gcm from './aes-256-gcm'; import Aes256Gcm from './aes-256-gcm';
const nacl = require('tweetnacl');
nacl.util = require('tweetnacl-util');
const jsrp = require('jsrp');
const clientOldPassword = new jsrp.client(); const clientOldPassword = new jsrp.client();
const clientNewPassword = new jsrp.client(); const clientNewPassword = new jsrp.client();
@@ -19,13 +18,13 @@ const clientNewPassword = new jsrp.client();
* @returns * @returns
*/ */
const changePassword = async ( const changePassword = async (
email, email: string,
currentPassword, currentPassword: string,
newPassword, newPassword: string,
setCurrentPasswordError, setCurrentPasswordError: (arg: boolean) => void,
setPasswordChanged, setPasswordChanged: (arg: boolean) => void,
setCurrentPassword, setCurrentPassword: (arg: string) => void,
setNewPassword setNewPassword: (arg: string) => void
) => { ) => {
try { try {
setPasswordChanged(false); setPasswordChanged(false);
@@ -34,7 +33,7 @@ const changePassword = async (
clientOldPassword.init( clientOldPassword.init(
{ {
username: email, username: email,
password: currentPassword password: currentPassword,
}, },
async () => { async () => {
const clientPublicKey = clientOldPassword.getPublicKey(); const clientPublicKey = clientOldPassword.getPublicKey();
@@ -42,7 +41,7 @@ const changePassword = async (
let serverPublicKey, salt; let serverPublicKey, salt;
try { try {
const res = await SRP1({ const res = await SRP1({
clientPublicKey: clientPublicKey clientPublicKey: clientPublicKey,
}); });
serverPublicKey = res.serverPublicKey; serverPublicKey = res.serverPublicKey;
salt = res.salt; salt = res.salt;
@@ -58,13 +57,13 @@ const changePassword = async (
clientNewPassword.init( clientNewPassword.init(
{ {
username: email, username: email,
password: newPassword password: newPassword,
}, },
async () => { async () => {
clientNewPassword.createVerifier(async (err, result) => { clientNewPassword.createVerifier(async (err, result) => {
// The Blob part here is needed to account for symbols that count as 2+ bytes (e.g., é, å, ø) // The Blob part here is needed to account for symbols that count as 2+ bytes (e.g., é, å, ø)
const { ciphertext, iv, tag } = Aes256Gcm.encrypt({ const { ciphertext, iv, tag } = Aes256Gcm.encrypt({
text: localStorage.getItem('PRIVATE_KEY'), text: localStorage.getItem('PRIVATE_KEY') as string,
secret: newPassword secret: newPassword
.slice(0, 32) .slice(0, 32)
.padStart( .padStart(
@@ -72,7 +71,7 @@ const changePassword = async (
(newPassword.slice(0, 32).length - (newPassword.slice(0, 32).length -
new Blob([newPassword]).size), new Blob([newPassword]).size),
'0' '0'
) ),
}); });
if (ciphertext) { if (ciphertext) {
@@ -88,11 +87,11 @@ const changePassword = async (
tag, tag,
salt: result.salt, salt: result.salt,
verifier: result.verifier, verifier: result.verifier,
clientProof clientProof,
}); });
if (res.status == 400) { if (res && res.status == 400) {
setCurrentPasswordError(true); setCurrentPasswordError(true);
} else if (res.status == 200) { } else if (res && res.status == 200) {
setPasswordChanged(true); setPasswordChanged(true);
setCurrentPassword(''); setCurrentPassword('');
setNewPassword(''); setNewPassword('');
@@ -22,8 +22,11 @@ type encryptAsymmetricProps = {
const encryptAssymmetric = ({ const encryptAssymmetric = ({
plaintext, plaintext,
publicKey, publicKey,
privateKey privateKey,
}: encryptAsymmetricProps): object => { }: encryptAsymmetricProps): {
ciphertext: string;
nonce: string;
} => {
const nonce = nacl.randomBytes(24); const nonce = nacl.randomBytes(24);
const ciphertext = nacl.box( const ciphertext = nacl.box(
nacl.util.decodeUTF8(plaintext), nacl.util.decodeUTF8(plaintext),
@@ -34,7 +37,7 @@ const encryptAssymmetric = ({
return { return {
ciphertext: nacl.util.encodeBase64(ciphertext), ciphertext: nacl.util.encodeBase64(ciphertext),
nonce: nacl.util.encodeBase64(nonce) nonce: nacl.util.encodeBase64(nonce),
}; };
}; };
@@ -58,7 +61,7 @@ const decryptAssymmetric = ({
ciphertext, ciphertext,
nonce, nonce,
publicKey, publicKey,
privateKey privateKey,
}: decryptAsymmetricProps): string => { }: decryptAsymmetricProps): string => {
const plaintext = nacl.box.open( const plaintext = nacl.box.open(
nacl.util.decodeBase64(ciphertext), nacl.util.decodeBase64(ciphertext),
@@ -83,7 +86,7 @@ type encryptSymmetricProps = {
*/ */
const encryptSymmetric = ({ const encryptSymmetric = ({
plaintext, plaintext,
key key,
}: encryptSymmetricProps): object => { }: encryptSymmetricProps): object => {
let ciphertext, iv, tag; let ciphertext, iv, tag;
try { try {
@@ -100,7 +103,7 @@ const encryptSymmetric = ({
return { return {
ciphertext, ciphertext,
iv, iv,
tag tag,
}; };
}; };
@@ -125,7 +128,7 @@ const decryptSymmetric = ({
ciphertext, ciphertext,
iv, iv,
tag, tag,
key key,
}: decryptSymmetricProps): string => { }: decryptSymmetricProps): string => {
let plaintext; let plaintext;
try { try {
@@ -142,5 +145,5 @@ export {
decryptAssymmetric, decryptAssymmetric,
decryptSymmetric, decryptSymmetric,
encryptAssymmetric, encryptAssymmetric,
encryptSymmetric encryptSymmetric,
}; };
@@ -1,42 +0,0 @@
/* eslint-disable */
import { initPostHog } from "~/components/analytics/posthog";
import { ENV } from "~/components/utilities/config";
class Capturer {
constructor() {
this.api = initPostHog();
}
capture(item) {
if (ENV == "production" && TELEMETRY_CAPTURING_ENABLED) {
try {
this.api.capture(item);
} catch (error) {
console.error("PostHog", error);
}
}
}
identify(id) {
if (ENV == "production" && TELEMETRY_CAPTURING_ENABLED) {
try {
this.api.identify(id);
} catch (error) {
console.error("PostHog", error);
}
}
}
}
export default class Telemetry {
constructor() {
if (!Telemetry.instance) {
Telemetry.instance = new Capturer();
}
}
getInstance() {
return Telemetry.instance;
}
}
@@ -0,0 +1,48 @@
/* eslint-disable */
import { PostHog } from 'posthog-js';
import { initPostHog } from '~/components/analytics/posthog';
import { ENV } from '~/components/utilities/config';
declare let TELEMETRY_CAPTURING_ENABLED: any;
class Capturer {
api: PostHog;
constructor() {
this.api = initPostHog()!;
}
capture(item: string) {
if (ENV == 'production' && TELEMETRY_CAPTURING_ENABLED) {
try {
this.api.capture(item);
} catch (error) {
console.error('PostHog', error);
}
}
}
identify(id: string) {
if (ENV == 'production' && TELEMETRY_CAPTURING_ENABLED) {
try {
this.api.identify(id);
} catch (error) {
console.error('PostHog', error);
}
}
}
}
export default class Telemetry {
static instance: Capturer;
constructor() {
if (!Telemetry.instance) {
Telemetry.instance = new Capturer();
}
}
getInstance() {
return Telemetry.instance;
}
}
+13 -1
View File
@@ -4,7 +4,6 @@
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "frontend",
"dependencies": { "dependencies": {
"@emotion/css": "^11.10.0", "@emotion/css": "^11.10.0",
"@emotion/server": "^11.10.0", "@emotion/server": "^11.10.0",
@@ -55,6 +54,7 @@
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/typography": "^0.5.4", "@tailwindcss/typography": "^0.5.4",
"@types/jsrp": "^0.2.4",
"@types/node": "18.11.9", "@types/node": "18.11.9",
"@types/react": "^18.0.26", "@types/react": "^18.0.26",
"@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/eslint-plugin": "^5.45.0",
@@ -1105,6 +1105,12 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true "dev": true
}, },
"node_modules/@types/jsrp": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/@types/jsrp/-/jsrp-0.2.4.tgz",
"integrity": "sha512-9RerZiVclCJNQrAaUH71s7tQpIlvyrM+1oWYwaRW+9xYYG0p2rnqSZMS0j+W+bL1tkBRUQUyXiSEUTnbv8HQjA==",
"dev": true
},
"node_modules/@types/mdast": { "node_modules/@types/mdast": {
"version": "3.0.10", "version": "3.0.10",
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz",
@@ -8675,6 +8681,12 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true "dev": true
}, },
"@types/jsrp": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/@types/jsrp/-/jsrp-0.2.4.tgz",
"integrity": "sha512-9RerZiVclCJNQrAaUH71s7tQpIlvyrM+1oWYwaRW+9xYYG0p2rnqSZMS0j+W+bL1tkBRUQUyXiSEUTnbv8HQjA==",
"dev": true
},
"@types/mdast": { "@types/mdast": {
"version": "3.0.10", "version": "3.0.10",
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz",
+3 -1
View File
@@ -6,7 +6,8 @@
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"start:docker": "next build && next start", "start:docker": "next build && next start",
"lint": "next lint" "lint": "next lint",
"type-check": "tsc --project tsconfig.json"
}, },
"dependencies": { "dependencies": {
"@emotion/css": "^11.10.0", "@emotion/css": "^11.10.0",
@@ -58,6 +59,7 @@
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/typography": "^0.5.4", "@tailwindcss/typography": "^0.5.4",
"@types/jsrp": "^0.2.4",
"@types/node": "18.11.9", "@types/node": "18.11.9",
"@types/react": "^18.0.26", "@types/react": "^18.0.26",
"@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/eslint-plugin": "^5.45.0",
+23 -12
View File
@@ -1,26 +1,37 @@
import React from "react"; 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";
export default function Custom404() { export default function Custom404() {
return ( return (
<div className="bg-bunker-800 md:h-screen flex flex-col justify-between"> <div className='bg-bunker-800 md:h-screen flex flex-col justify-between'>
<Head> <Head>
<title>Infisical | Page Not Found</title> <title>Infisical | Page Not Found</title>
<link rel="icon" href="/infisical.ico" /> <link rel='icon' href='/infisical.ico' />
</Head> </Head>
<div className="flex flex-col items-center justify-center text-gray-200 h-screen w-screen"> <div className='flex flex-col items-center justify-center text-gray-200 h-screen w-screen'>
<p className="text-4xl mt-32">Oops, something went wrong</p> <p className='text-4xl mt-32'>Oops, something went wrong</p>
<p className="mt-2 mb-1 text-lg">Think this is a mistake? Email <a className="text-primary underline underline-offset-4" href="mailto:[email protected]">team@infisical.com</a> and we`ll fix it! </p> <p className='mt-2 mb-1 text-lg'>
<Link href="/dashboard" className="bg-mineshaft-500 mt-8 py-2 px-4 rounded-md hover:bg-primary diration-200 hover:text-black cursor-pointer font-semibold"> Think this is a mistake? Email{' '}
<a
className='text-primary underline underline-offset-4'
href='mailto:[email protected]'
>
team@infisical.com
</a>{' '}
and we`ll fix it!{' '}
</p>
<Link
href='/dashboard'
className='bg-mineshaft-500 mt-8 py-2 px-4 rounded-md hover:bg-primary diration-200 hover:text-black cursor-pointer font-semibold'
>
Go to Dashboard Go to Dashboard
</Link> </Link>
<Image <Image
src="/images/dragon-404.svg" src='/images/dragon-404.svg'
height={554} height={554}
width={942} width={942}
alt="infisical dragon - page not found" alt='infisical dragon - page not found'
></Image> ></Image>
</div> </div>
</div> </div>
+21 -3
View File
@@ -1,17 +1,35 @@
import SecurityClient from '~/utilities/SecurityClient'; import SecurityClient from '~/utilities/SecurityClient';
export interface IMembershipOrg {
_id: string;
user: {
email: string;
firstName: string;
lastName: string;
_id: string;
publicKey: string;
};
inviteEmail: string;
organization: string;
role: 'owner' | 'admin' | 'member';
status: 'invited' | 'accepted';
}
/** /**
* This route lets us get all the users in an org. * This route lets us get all the users in an org.
* @param {object} obj * @param {object} obj
* @param {string} obj.orgId - organization Id * @param {string} obj.orgId - organization Id
* @returns * @returns
*/ */
const getOrganizationUsers = ({ orgId }: { orgId: string }) => { const getOrganizationUsers = ({
orgId,
}: {
orgId: string;
}): Promise<IMembershipOrg[]> => {
return SecurityClient.fetchCall('/api/v1/organization/' + orgId + '/users', { return SecurityClient.fetchCall('/api/v1/organization/' + orgId + '/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;
@@ -1,18 +1,25 @@
import SecurityClient from '~/utilities/SecurityClient'; import SecurityClient from '~/utilities/SecurityClient';
export interface IIncidentContactOrg {
_id: string;
email: string;
organization: string;
}
/** /**
* This routes gets all the incident contacts of a certain organization * This routes gets all the incident contacts of a certain organization
* @param {*} workspaceId * @param {*} workspaceId
* @returns * @returns
*/ */
const getIncidentContacts = (organizationId: string) => { const getIncidentContacts = (
organizationId: string
): Promise<IIncidentContactOrg[]> => {
return SecurityClient.fetchCall( return SecurityClient.fetchCall(
'/api/v1/organization/' + organizationId + '/incidentContactOrg', '/api/v1/organization/' + organizationId + '/incidentContactOrg',
{ {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
} },
} }
).then(async (res) => { ).then(async (res) => {
if (res && res.status == 200) { if (res && res.status == 200) {
-33
View File
@@ -1,33 +0,0 @@
import { useEffect } from "react";
import Head from "next/head";
import { useRouter } from "next/router";
import getWorkspaces from "./api/workspace/getWorkspaces";
export default function DashboardRedirect() {
const router = useRouter();
/**
* Here we forward to the default workspace if a user opens this url
*/
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(async () => {
let userWorkspace;
try {
if (localStorage.getItem("projectData.id")) {
router.push("/dashboard/" + localStorage.getItem("projectData.id"));
} else {
const userWorkspaces = await getWorkspaces();
userWorkspace = userWorkspaces[0]._id;
router.push("/dashboard/" + userWorkspace);
}
} catch (error) {
console.log("Error - Not logged in yet");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <div></div>;
}
DashboardRedirect.requireAuth = true;
+34
View File
@@ -0,0 +1,34 @@
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import getWorkspaces from './api/workspace/getWorkspaces';
export default function DashboardRedirect() {
const router = useRouter();
/**
* Here we forward to the default workspace if a user opens this url
*/
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
(async () => {
let userWorkspace;
try {
if (localStorage.getItem('projectData.id')) {
router.push('/dashboard/' + localStorage.getItem('projectData.id'));
} else {
const userWorkspaces = await getWorkspaces();
userWorkspace = userWorkspaces[0]._id;
router.push('/dashboard/' + userWorkspace);
}
} catch (error) {
console.log('Error - Not logged in yet');
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <div></div>;
}
DashboardRedirect.requireAuth = true;
-37
View File
@@ -1,37 +0,0 @@
import React, { useEffect } from "react";
import Head from "next/head";
import { useRouter } from "next/router";
const queryString = require("query-string");
import AuthorizeIntegration from "./api/integrations/authorizeIntegration";
export default function Github() {
const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
const code = parsedUrl.code;
const state = parsedUrl.state;
/**
* Here we forward to the default workspace if a user opens this url
*/
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(async () => {
try {
if (state === localStorage.getItem('latestCSRFToken')) {
localStorage.removeItem('latestCSRFToken');
await AuthorizeIntegration({
workspaceId: localStorage.getItem('projectData.id'),
code,
integration: "github",
});
router.push("/integrations/" + localStorage.getItem("projectData.id"));
}
} catch (error) {
console.error('Github integration error: ', error);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <div></div>;
}
Github.requireAuth = true;
+41
View File
@@ -0,0 +1,41 @@
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import queryString from 'query-string';
import AuthorizeIntegration from './api/integrations/authorizeIntegration';
export default function Github() {
const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split('?')[1]);
const code = parsedUrl.code;
const state = parsedUrl.state;
/**
* Here we forward to the default workspace if a user opens this url
*/
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
(async () => {
try {
if (state === localStorage.getItem('latestCSRFToken')) {
localStorage.removeItem('latestCSRFToken');
await AuthorizeIntegration({
workspaceId: localStorage.getItem('projectData.id') as string,
code: code as string,
integration: 'github',
});
router.push(
'/integrations/' + localStorage.getItem('projectData.id')
);
}
} catch (error) {
console.error('Github integration error: ', error);
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <div></div>;
}
Github.requireAuth = true;
-37
View File
@@ -1,37 +0,0 @@
import { useEffect } from "react";
import Head from "next/head";
import { useRouter } from "next/router";
const queryString = require("query-string");
import AuthorizeIntegration from "./api/integrations/authorizeIntegration";
export default function Heroku() {
const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
const code = parsedUrl.code;
const state = parsedUrl.state;
/**
* Here we forward to the default workspace if a user opens this url
*/
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(async () => {
try {
if (state === localStorage.getItem('latestCSRFToken')) {
localStorage.removeItem('latestCSRFToken');
await AuthorizeIntegration({
workspaceId: localStorage.getItem('projectData.id'),
code,
integration: "heroku",
});
router.push("/integrations/" + localStorage.getItem("projectData.id"));
}
} catch (error) {
console.error('Heroku integration error: ', error);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <div></div>;
}
Heroku.requireAuth = true;
+41
View File
@@ -0,0 +1,41 @@
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import queryString from 'query-string';
import AuthorizeIntegration from './api/integrations/authorizeIntegration';
export default function Heroku() {
const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split('?')[1]);
const code = parsedUrl.code;
const state = parsedUrl.state;
/**
* Here we forward to the default workspace if a user opens this url
*/
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
(async () => {
try {
if (state === localStorage.getItem('latestCSRFToken')) {
localStorage.removeItem('latestCSRFToken');
await AuthorizeIntegration({
workspaceId: localStorage.getItem('projectData.id') as string,
code: code as string,
integration: 'heroku',
});
router.push(
'/integrations/' + localStorage.getItem('projectData.id')
);
}
} catch (error) {
console.error('Heroku integration error: ', error);
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <div></div>;
}
Heroku.requireAuth = true;
-242
View File
@@ -1,242 +0,0 @@
import { useEffect, useState } from "react";
import Head from "next/head";
import Image from "next/image";
import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";
import {
faArrowRight,
faCheck,
faRotate,
faX,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import ActivateBotDialog from "~/components/basic/dialog/ActivateBotDialog";
import CloudIntegrationSection from "~/components/integrations/CloudIntegrationSection";
import FrameworkIntegrationSection from "~/components/integrations/FrameworkIntegrationSection";
import IntegrationSection from "~/components/integrations/IntegrationSection";
import NavHeader from "~/components/navigation/NavHeader";
import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
import frameworkIntegrationOptions from "../../public/json/frameworkIntegrations.json";
import getBot from "../api/bot/getBot";
import setBotActiveStatus from "../api/bot/setBotActiveStatus";
import getIntegrationOptions from "../api/integrations/GetIntegrationOptions";
import getWorkspaceAuthorizations from "../api/integrations/getWorkspaceAuthorizations";
import getWorkspaceIntegrations from "../api/integrations/getWorkspaceIntegrations";
import getAWorkspace from "../api/workspace/getAWorkspace";
import getLatestFileKey from "../api/workspace/getLatestFileKey";
const {
decryptAssymmetric,
encryptAssymmetric
} = require('../../components/utilities/cryptography/crypto');
const crypto = require("crypto");
export default function Integrations() {
const [cloudIntegrationOptions, setCloudIntegrationOptions] = useState([]);
const [integrationAuths, setIntegrationAuths] = useState([]);
const [environments,setEnvironments] = useState([])
const [integrations, setIntegrations] = useState([]);
const [bot, setBot] = useState(null);
const [isActivateBotDialogOpen, setIsActivateBotDialogOpen] = useState(false);
// const [isIntegrationAccessTokenDialogOpen, setIntegrationAccessTokenDialogOpen] = useState(true);
const [selectedIntegrationOption, setSelectedIntegrationOption] = useState(null);
const router = useRouter();
const workspaceId = router.query.id;
const { t } = useTranslation();
useEffect(async () => {
try {
const workspace = await getAWorkspace(workspaceId);
setEnvironments(workspace.environments);
// get cloud integration options
setCloudIntegrationOptions(
await getIntegrationOptions()
);
// get project integration authorizations
setIntegrationAuths(
await getWorkspaceAuthorizations({
workspaceId
})
);
// get project integrations
setIntegrations(
await getWorkspaceIntegrations({
workspaceId,
})
);
// get project bot
setBot(await getBot({ workspaceId }));
} catch (err) {
console.log(err);
}
}, []);
/**
* Activate bot for project by performing the following steps:
* 1. Get the (encrypted) project key
* 2. Decrypt project key with user's private key
* 3. Encrypt project key with bot's public key
* 4. Send encrypted project key to backend and set bot status to active
*/
const handleBotActivate = async () => {
let botKey;
try {
if (bot) {
// case: there is a bot
const key = await getLatestFileKey({ workspaceId });
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY');
const WORKSPACE_KEY = decryptAssymmetric({
ciphertext: key.latestKey.encryptedKey,
nonce: key.latestKey.nonce,
publicKey: key.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY
});
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: WORKSPACE_KEY,
publicKey: bot.publicKey,
privateKey: PRIVATE_KEY
});
botKey = {
encryptedKey: ciphertext,
nonce
}
setBot((await setBotActiveStatus({
botId: bot._id,
isActive: bot.isActive ? false : true,
botKey
})).bot);
}
} catch (err) {
console.error(err);
}
}
/**
* Start integration for a given integration option [integrationOption]
* @param {Object} obj
* @param {Object} obj.integrationOption - an integration option
* @param {String} obj.name
* @param {String} obj.type
* @param {String} obj.docsLink
* @returns
*/
const handleIntegrationOption = async ({ integrationOption }) => {
try {
// generate CSRF token for OAuth2 code-token exchange integrations
const state = crypto.randomBytes(16).toString("hex");
localStorage.setItem('latestCSRFToken', state);
switch (integrationOption.name) {
case 'Heroku':
window.location = `https://id.heroku.com/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=write-protected&state=${state}`;
break;
case 'Vercel':
window.location = `https://vercel.com/integrations/${integrationOption.clientSlug}/new?state=${state}`;
break;
case 'Netlify':
window.location = `https://app.netlify.com/authorize?client_id=${integrationOption.clientId}&response_type=code&state=${state}&redirect_uri=${window.location.origin}/netlify`;
break;
case 'GitHub':
window.location = `https://github.com/login/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=repo&redirect_uri=${window.location.origin}/github&state=${state}`;
break;
// case 'Fly.io':
// console.log('fly.io');
// setIntegrationAccessTokenDialogOpen(true);
// break;
}
} catch (err) {
console.log(err);
}
}
/**
* Open dialog to activate bot if bot is not active.
* Otherwise, start integration [integrationOption]
* @param {Object} integrationOption - an integration option
* @param {String} integrationOption.name
* @param {String} integrationOption.type
* @param {String} integrationOption.docsLink
* @returns
*/
const integrationOptionPress = (integrationOption) => {
try {
if (bot.isActive) {
// case: bot is active -> proceed with integration
handleIntegrationOption({ integrationOption });
return;
}
// case: bot is not active -> open modal to activate bot
setIsActivateBotDialogOpen(true);
} catch (err) {
console.error(err);
}
}
return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head>
<title>
{t("common:head-title", { title: t("integrations:title") })}
</title>
<link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" />
<meta property="og:title" content="Manage your .env files in seconds" />
<meta name="og:description" content={t("integrations:description")} />
</Head>
<div className="w-full max-h-96 pb-2 h-screen max-h-[calc(100vh-10px)] overflow-y-scroll no-scrollbar no-scrollbar::-webkit-scrollbar">
<NavHeader
pageName={t("integrations:title")}
isProjectRelated={true}
/>
<ActivateBotDialog
isOpen={isActivateBotDialogOpen}
closeModal={() => setIsActivateBotDialogOpen(false)}
selectedIntegrationOption={selectedIntegrationOption}
handleBotActivate={handleBotActivate}
handleIntegrationOption={handleIntegrationOption}
/>
{/* <IntegrationAccessTokenDialog
isOpen={isIntegrationAccessTokenDialogOpen}
closeModal={() => setIntegrationAccessTokenDialogOpen(false)}
selectedIntegrationOption={selectedIntegrationOption}
handleBotActivate={handleBotActivate}
handleIntegrationOption={handleIntegrationOption}
/> */}
<IntegrationSection integrations={integrations} environments={environments} />
{(cloudIntegrationOptions.length > 0 && bot) ? (
<CloudIntegrationSection
cloudIntegrationOptions={cloudIntegrationOptions}
setSelectedIntegrationOption={setSelectedIntegrationOption}
integrationOptionPress={integrationOptionPress}
integrationAuths={integrationAuths}
/>
) : (
<div></div>
)}
<FrameworkIntegrationSection
frameworks={frameworkIntegrationOptions}
/>
</div>
</div>
);
}
Integrations.requireAuth = true;
export const getServerSideProps = getTranslatedServerSideProps([
"integrations",
]);
+263
View File
@@ -0,0 +1,263 @@
import crypto from 'crypto';
import { useEffect, useState } from 'react';
import Head from 'next/head';
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
import ActivateBotDialog from '~/components/basic/dialog/ActivateBotDialog';
import CloudIntegrationSection from '~/components/integrations/CloudIntegrationSection';
import FrameworkIntegrationSection from '~/components/integrations/FrameworkIntegrationSection';
import IntegrationSection from '~/components/integrations/IntegrationSection';
import NavHeader from '~/components/navigation/NavHeader';
import { getTranslatedServerSideProps } from '~/utilities/withTranslateProps';
import {
decryptAssymmetric,
encryptAssymmetric,
} from '../../components/utilities/cryptography/crypto';
import frameworkIntegrationOptions from '../../public/json/frameworkIntegrations.json';
import getBot from '../api/bot/getBot';
import setBotActiveStatus from '../api/bot/setBotActiveStatus';
import getIntegrationOptions from '../api/integrations/GetIntegrationOptions';
import getWorkspaceAuthorizations from '../api/integrations/getWorkspaceAuthorizations';
import getWorkspaceIntegrations from '../api/integrations/getWorkspaceIntegrations';
import getAWorkspace from '../api/workspace/getAWorkspace';
import getLatestFileKey from '../api/workspace/getLatestFileKey';
export default function Integrations() {
const [cloudIntegrationOptions, setCloudIntegrationOptions] = useState([]);
const [integrationAuths, setIntegrationAuths] = useState([]);
const [environments, setEnvironments] = useState<
{
name: string;
slug: string;
}[]
>([]);
const [integrations, setIntegrations] = useState([]);
// TODO: These will have its type when migratiing towards react-query
const [bot, setBot] = useState<any>(null);
const [isActivateBotDialogOpen, setIsActivateBotDialogOpen] = useState(false);
// const [isIntegrationAccessTokenDialogOpen, setIntegrationAccessTokenDialogOpen] = useState(true);
const [selectedIntegrationOption, setSelectedIntegrationOption] =
useState(null);
const router = useRouter();
const workspaceId = router.query.id as string;
const { t } = useTranslation();
useEffect(() => {
(async () => {
try {
const workspace = await getAWorkspace(workspaceId);
setEnvironments(workspace.environments);
// get cloud integration options
setCloudIntegrationOptions(await getIntegrationOptions());
// get project integration authorizations
setIntegrationAuths(
await getWorkspaceAuthorizations({
workspaceId,
})
);
// get project integrations
setIntegrations(
await getWorkspaceIntegrations({
workspaceId,
})
);
// get project bot
setBot(await getBot({ workspaceId }));
} catch (err) {
console.log(err);
}
})();
}, []);
/**
* Activate bot for project by performing the following steps:
* 1. Get the (encrypted) project key
* 2. Decrypt project key with user's private key
* 3. Encrypt project key with bot's public key
* 4. Send encrypted project key to backend and set bot status to active
*/
const handleBotActivate = async () => {
let botKey;
try {
if (bot) {
// case: there is a bot
const key = await getLatestFileKey({ workspaceId });
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY');
if (!PRIVATE_KEY) {
throw new Error('Private Key missing');
}
const WORKSPACE_KEY = decryptAssymmetric({
ciphertext: key.latestKey.encryptedKey,
nonce: key.latestKey.nonce,
publicKey: key.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY,
});
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: WORKSPACE_KEY,
publicKey: bot.publicKey,
privateKey: PRIVATE_KEY,
});
botKey = {
encryptedKey: ciphertext,
nonce,
};
setBot(
(
await setBotActiveStatus({
botId: bot._id,
isActive: bot.isActive ? false : true,
botKey,
})
).bot
);
}
} catch (err) {
console.error(err);
}
};
/**
* Start integration for a given integration option [integrationOption]
* @param {Object} obj
* @param {Object} obj.integrationOption - an integration option
* @param {String} obj.name
* @param {String} obj.type
* @param {String} obj.docsLink
* @returns
*/
const handleIntegrationOption = async ({
integrationOption,
}: {
integrationOption: any;
}) => {
try {
// generate CSRF token for OAuth2 code-token exchange integrations
const state = crypto.randomBytes(16).toString('hex');
localStorage.setItem('latestCSRFToken', state);
switch (integrationOption.name) {
case 'Heroku':
window.location.assign(
`https://id.heroku.com/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=write-protected&state=${state}`
);
break;
case 'Vercel':
window.location.assign(
`https://vercel.com/integrations/${integrationOption.clientSlug}/new?state=${state}`
);
break;
case 'Netlify':
window.location.assign(
`https://app.netlify.com/authorize?client_id=${integrationOption.clientId}&response_type=code&state=${state}&redirect_uri=${window.location.origin}/netlify`
);
break;
case 'GitHub':
window.location.assign(
`https://github.com/login/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=repo&redirect_uri=${window.location.origin}/github&state=${state}`
);
break;
// case 'Fly.io':
// console.log('fly.io');
// setIntegrationAccessTokenDialogOpen(true);
// break;
}
} catch (err) {
console.log(err);
}
};
/**
* Open dialog to activate bot if bot is not active.
* Otherwise, start integration [integrationOption]
* @param {Object} integrationOption - an integration option
* @param {String} integrationOption.name
* @param {String} integrationOption.type
* @param {String} integrationOption.docsLink
* @returns
*/
const integrationOptionPress = (integrationOption: any) => {
try {
if (bot.isActive) {
// case: bot is active -> proceed with integration
handleIntegrationOption({ integrationOption });
return;
}
// case: bot is not active -> open modal to activate bot
setIsActivateBotDialogOpen(true);
} catch (err) {
console.error(err);
}
};
return (
<div className='bg-bunker-800 max-h-screen flex flex-col justify-between text-white'>
<Head>
<title>
{t('common:head-title', { title: t('integrations:title') })}
</title>
<link rel='icon' href='/infisical.ico' />
<meta property='og:image' content='/images/message.png' />
<meta property='og:title' content='Manage your .env files in seconds' />
<meta
name='og:description'
content={t('integrations:description') as string}
/>
</Head>
<div className='w-full pb-2 h-screen max-h-[calc(100vh-10px)] overflow-y-scroll no-scrollbar no-scrollbar::-webkit-scrollbar'>
<NavHeader pageName={t('integrations:title')} isProjectRelated={true} />
<ActivateBotDialog
isOpen={isActivateBotDialogOpen}
closeModal={() => setIsActivateBotDialogOpen(false)}
selectedIntegrationOption={selectedIntegrationOption}
handleBotActivate={handleBotActivate}
handleIntegrationOption={handleIntegrationOption}
/>
{/* <IntegrationAccessTokenDialog
isOpen={isIntegrationAccessTokenDialogOpen}
closeModal={() => setIntegrationAccessTokenDialogOpen(false)}
selectedIntegrationOption={selectedIntegrationOption}
handleBotActivate={handleBotActivate}
handleIntegrationOption={handleIntegrationOption}
/> */}
<IntegrationSection
integrations={integrations}
environments={environments}
/>
{cloudIntegrationOptions.length > 0 && bot ? (
<CloudIntegrationSection
cloudIntegrationOptions={cloudIntegrationOptions}
setSelectedIntegrationOption={setSelectedIntegrationOption as any}
integrationOptionPress={integrationOptionPress as any}
integrationAuths={integrationAuths}
/>
) : (
<div></div>
)}
<FrameworkIntegrationSection
frameworks={frameworkIntegrationOptions as any}
/>
</div>
</div>
);
}
Integrations.requireAuth = true;
export const getServerSideProps = getTranslatedServerSideProps([
'integrations',
]);
-41
View File
@@ -1,41 +0,0 @@
import { useEffect } from "react";
import Head from "next/head";
import { useRouter } from "next/router";
const queryString = require("query-string");
import AuthorizeIntegration from "./api/integrations/authorizeIntegration";
export default function Netlify() {
const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
const code = parsedUrl.code;
const state = parsedUrl.state;
// modify comment here
/**
* Here we forward to the default workspace if a user opens this url
*/
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(async () => {
try {
if (state === localStorage.getItem('latestCSRFToken')) {
localStorage.removeItem('latestCSRFToken');
await AuthorizeIntegration({
workspaceId: localStorage.getItem('projectData.id'),
code,
integration: "netlify"
});
router.push("/integrations/" + localStorage.getItem("projectData.id"));
}
} catch (err) {
console.error('Netlify integration error: ', err);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <div></div>;
}
Netlify.requireAuth = true;
+46
View File
@@ -0,0 +1,46 @@
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import queryString from 'query-string';
import AuthorizeIntegration from './api/integrations/authorizeIntegration';
export default function Netlify() {
const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split('?')[1]);
const code = parsedUrl.code;
const state = parsedUrl.state;
// modify comment here
/**
* Here we forward to the default workspace if a user opens this url
*/
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
(async () => {
try {
if (!code) throw new Error('Code not found');
if (state === localStorage.getItem('latestCSRFToken')) {
localStorage.removeItem('latestCSRFToken');
await AuthorizeIntegration({
workspaceId: localStorage.getItem('projectData.id') as string,
code: code as string,
integration: 'netlify',
});
router.push(
'/integrations/' + localStorage.getItem('projectData.id')
);
}
} catch (err) {
console.error('Netlify integration error: ', err);
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <div></div>;
}
Netlify.requireAuth = true;
-35
View File
@@ -1,35 +0,0 @@
import React from "react";
import Image from "next/image";
import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
export default function NoProjects() {
return (
<div className="h-full flex flex-col items-center justify-center text-gray-300 text-lg text-center w-11/12 mr-auto">
<div
className="mb-4 mr-16"
>
<Image
src="/images/dragon-cant-find.png"
height={270}
width={436}
alt="google logo"
></Image>
</div>
<div className="p-4 rounded-md bg-bunker-500 mb-8 text-bunker-300 shadow-xl">
<div className="max-w-md">
You are not part of any projects in this organization yet. When you do,
they will appear here.
</div>
<div className="max-w-md mt-4">
Create a new project, or ask other organization members to give you
neccessary permissions.
</div>
</div>
</div>
);
}
NoProjects.requireAuth = true;
export const getServerSideProps = getTranslatedServerSideProps(["noprojects"]);
+32
View File
@@ -0,0 +1,32 @@
import Image from 'next/image';
import { getTranslatedServerSideProps } from '~/utilities/withTranslateProps';
export default function NoProjects() {
return (
<div className='h-full flex flex-col items-center justify-center text-gray-300 text-lg text-center w-11/12 mr-auto'>
<div className='mb-4 mr-16'>
<Image
src='/images/dragon-cant-find.png'
height={270}
width={436}
alt='google logo'
></Image>
</div>
<div className='p-4 rounded-md bg-bunker-500 mb-8 text-bunker-300 shadow-xl'>
<div className='max-w-md'>
You are not part of any projects in this organization yet. When you
do, they will appear here.
</div>
<div className='max-w-md mt-4'>
Create a new project, or ask other organization members to give you
neccessary permissions.
</div>
</div>
</div>
);
}
NoProjects.requireAuth = true;
export const getServerSideProps = getTranslatedServerSideProps(['noprojects']);
+79 -81
View File
@@ -1,40 +1,39 @@
import { useState } from "react"; import { useState } from 'react';
import Image from "next/image"; import Image from 'next/image';
import { useRouter } from "next/router"; import { useRouter } from 'next/router';
import { faCheck, faX } from "@fortawesome/free-solid-svg-icons"; import { faCheck, faX } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import jsrp from 'jsrp';
import queryString from 'query-string';
import Button from "~/components/basic/buttons/Button"; import Button from '~/components/basic/buttons/Button';
import InputField from "~/components/basic/InputField"; import InputField from '~/components/basic/InputField';
import passwordCheck from "~/components/utilities/checks/PasswordCheck"; import passwordCheck from '~/components/utilities/checks/PasswordCheck';
import Aes256Gcm from "~/components/utilities/cryptography/aes-256-gcm"; import Aes256Gcm from '~/components/utilities/cryptography/aes-256-gcm';
import { getTranslatedStaticProps } from "~/components/utilities/withTranslateProps"; import { getTranslatedStaticProps } from '~/components/utilities/withTranslateProps';
import EmailVerifyOnPasswordReset from "./api/auth/EmailVerifyOnPasswordReset"; import EmailVerifyOnPasswordReset from './api/auth/EmailVerifyOnPasswordReset';
import getBackupEncryptedPrivateKey from "./api/auth/getBackupEncryptedPrivateKey"; import getBackupEncryptedPrivateKey from './api/auth/getBackupEncryptedPrivateKey';
import resetPasswordOnAccountRecovery from "./api/auth/resetPasswordOnAccountRecovery"; import resetPasswordOnAccountRecovery from './api/auth/resetPasswordOnAccountRecovery';
const queryString = require("query-string");
const nacl = require("tweetnacl");
const jsrp = require("jsrp");
nacl.util = require("tweetnacl-util");
const client = new jsrp.client(); const client = new jsrp.client();
export default function PasswordReset() { export default function PasswordReset() {
const router = useRouter(); const [verificationToken, setVerificationToken] = useState('');
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
const token = parsedUrl.token;
const email = parsedUrl.to?.replace(" ", "+").trim();
const [verificationToken, setVerificationToken] = useState("");
const [step, setStep] = useState(1); const [step, setStep] = useState(1);
const [backupKey, setBackupKey] = useState(""); const [backupKey, setBackupKey] = useState('');
const [privateKey, setPrivateKey] = useState(""); const [privateKey, setPrivateKey] = useState('');
const [newPassword, setNewPassword] = useState(""); const [newPassword, setNewPassword] = useState('');
const [backupKeyError, setBackupKeyError] = useState(false); const [backupKeyError, setBackupKeyError] = useState(false);
const [passwordErrorLength, setPasswordErrorLength] = useState(false); const [passwordErrorLength, setPasswordErrorLength] = useState(false);
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false); const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false); const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split('?')[1]);
const token = parsedUrl.token as string;
const email = (parsedUrl.to as string)?.replace(' ', '+').trim();
// Unencrypt the private key with a backup key // Unencrypt the private key with a backup key
const getEncryptedKeyHandler = async () => { const getEncryptedKeyHandler = async () => {
try { try {
@@ -55,13 +54,12 @@ export default function PasswordReset() {
// If everything is correct, reset the password // If everything is correct, reset the password
const resetPasswordHandler = async () => { const resetPasswordHandler = async () => {
let errorCheck = false; const errorCheck = passwordCheck({
errorCheck = passwordCheck({
password: newPassword, password: newPassword,
setPasswordErrorLength, setPasswordErrorLength,
setPasswordErrorNumber, setPasswordErrorNumber,
setPasswordErrorLowerCase, setPasswordErrorLowerCase,
currentErrorCheck: errorCheck, errorCheck: false,
}); });
if (!errorCheck) { if (!errorCheck) {
@@ -73,7 +71,7 @@ export default function PasswordReset() {
.padStart( .padStart(
32 + 32 +
(newPassword.slice(0, 32).length - new Blob([newPassword]).size), (newPassword.slice(0, 32).length - new Blob([newPassword]).size),
"0" '0'
), ),
}) as { ciphertext: string; iv: string; tag: string }; }) as { ciphertext: string; iv: string; tag: string };
@@ -96,7 +94,7 @@ export default function PasswordReset() {
// if everything works, go the main dashboard page. // if everything works, go the main dashboard page.
if (response?.status === 200) { if (response?.status === 200) {
router.push("/login"); router.push('/login');
} }
} }
); );
@@ -107,19 +105,19 @@ export default function PasswordReset() {
// Click a button to confirm email // Click a button to confirm email
const stepConfirmEmail = ( const stepConfirmEmail = (
<div className="bg-bunker flex flex-col items-center w-full py-6 max-w-xs md:max-w-lg mx-auto my-32 px-4 md:px-6 mx-1 rounded-xl drop-shadow-xl"> <div className='bg-bunker flex flex-col items-center w-full py-6 max-w-xs md:max-w-lg mx-auto my-32 px-4 md:px-6 mx-1 rounded-xl drop-shadow-xl'>
<p className="text-4xl text-center font-semibold mb-8 flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary"> <p className='text-4xl text-center font-semibold mb-8 flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary'>
Confirm your email Confirm your email
</p> </p>
<Image <Image
src="/images/envelope.svg" src='/images/envelope.svg'
height={262} height={262}
width={410} width={410}
alt="verify email" alt='verify email'
></Image> ></Image>
<div className="flex max-w-max flex-col items-center justify-center md:p-2 max-h-24 max-w-md mx-auto text-lg px-4 mt-4 mb-2"> <div className='flex flex-col items-center justify-center md:p-2 max-h-24 max-w-md mx-auto text-lg px-4 mt-4 mb-2'>
<Button <Button
text="Confirm Email" text='Confirm Email'
onButtonPressed={async () => { onButtonPressed={async () => {
const response = await EmailVerifyOnPasswordReset({ const response = await EmailVerifyOnPasswordReset({
email, email,
@@ -129,11 +127,11 @@ export default function PasswordReset() {
setVerificationToken((await response.json()).token); setVerificationToken((await response.json()).token);
setStep(2); setStep(2);
} else { } else {
console.log("ERROR", response); console.log('ERROR', response);
router.push("/email-not-verified"); router.push('/email-not-verified');
} }
}} }}
size="lg" size='lg'
/> />
</div> </div>
</div> </div>
@@ -141,34 +139,34 @@ export default function PasswordReset() {
// Input backup key // Input backup key
const stepInputBackupKey = ( const stepInputBackupKey = (
<div className="bg-bunker flex flex-col items-center w-full pt-6 pb-3 max-w-xs md:max-w-lg mx-auto my-32 px-4 md:px-6 mx-1 rounded-xl drop-shadow-xl"> <div className='bg-bunker flex flex-col items-center w-full pt-6 pb-3 max-w-xs md:max-w-lg mx-auto my-32 px-4 md:px-6 mx-1 rounded-xl drop-shadow-xl'>
<p className="text-2xl md:text-3xl w-max mx-auto flex justify-center font-semibold text-bunker-100 mb-4"> <p className='text-2xl md:text-3xl w-max mx-auto flex justify-center font-semibold text-bunker-100 mb-4'>
Enter your backup key Enter your backup key
</p> </p>
<div className="flex flex-row items-center justify-center md:pb-4 mt-4 md:mx-2"> <div className='flex flex-row items-center justify-center md:pb-4 mt-4 md:mx-2'>
<p className="text-sm flex justify-center text-gray-400 w-max max-w-md"> <p className='text-sm flex justify-center text-gray-400 w-max max-w-md'>
You can find it in your emrgency kit. You had to download the enrgency You can find it in your emrgency kit. You had to download the enrgency
kit during signup. kit during signup.
</p> </p>
</div> </div>
<div className="flex items-center justify-center w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28"> <div className='flex items-center justify-center w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28'>
<InputField <InputField
label="Backup Key" label='Backup Key'
onChangeHandler={setBackupKey} onChangeHandler={setBackupKey}
type="password" type='password'
value={backupKey} value={backupKey}
placeholder="" placeholder=''
isRequired isRequired
error={backupKeyError} error={backupKeyError}
errorText="Something is wrong with the backup key" errorText='Something is wrong with the backup key'
/> />
</div> </div>
<div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm"> <div className='flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm'>
<div className="text-l mt-6 m-8 px-8 py-3 text-lg"> <div className='text-l mt-6 m-8 px-8 py-3 text-lg'>
<Button <Button
text="Submit Backup Key" text='Submit Backup Key'
onButtonPressed={() => getEncryptedKeyHandler()} onButtonPressed={() => getEncryptedKeyHandler()}
size="lg" size='lg'
/> />
</div> </div>
</div> </div>
@@ -177,18 +175,18 @@ export default function PasswordReset() {
// Enter new password // Enter new password
const stepEnterNewPassword = ( const stepEnterNewPassword = (
<div className="bg-bunker flex flex-col items-center w-full pt-6 pb-3 max-w-xs md:max-w-lg mx-auto my-32 px-4 md:px-6 mx-1 rounded-xl drop-shadow-xl"> <div className='bg-bunker flex flex-col items-center w-full pt-6 pb-3 max-w-xs md:max-w-lg mx-auto my-32 px-4 md:px-6 mx-1 rounded-xl drop-shadow-xl'>
<p className="text-2xl md:text-3xl w-max mx-auto flex justify-center font-semibold text-bunker-100"> <p className='text-2xl md:text-3xl w-max mx-auto flex justify-center font-semibold text-bunker-100'>
Enter new password Enter new password
</p> </p>
<div className="flex flex-row items-center justify-center md:pb-4 mt-1 md:mx-2"> <div className='flex flex-row items-center justify-center md:pb-4 mt-1 md:mx-2'>
<p className="text-sm flex justify-center text-gray-400 w-max max-w-md"> <p className='text-sm flex justify-center text-gray-400 w-max max-w-md'>
Make sure you save it somewhere save. Make sure you save it somewhere save.
</p> </p>
</div> </div>
<div className="flex items-center justify-center w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28"> <div className='flex items-center justify-center w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28'>
<InputField <InputField
label="New Password" label='New Password'
onChangeHandler={(password) => { onChangeHandler={(password) => {
setNewPassword(password); setNewPassword(password);
passwordCheck({ passwordCheck({
@@ -196,70 +194,70 @@ export default function PasswordReset() {
setPasswordErrorLength, setPasswordErrorLength,
setPasswordErrorNumber, setPasswordErrorNumber,
setPasswordErrorLowerCase, setPasswordErrorLowerCase,
currentErrorCheck: false, errorCheck: false,
}); });
}} }}
type="password" type='password'
value={newPassword} value={newPassword}
isRequired isRequired
error={ error={
passwordErrorLength && passwordErrorLowerCase && passwordErrorNumber passwordErrorLength && passwordErrorLowerCase && passwordErrorNumber
} }
autoComplete="new-password" autoComplete='new-password'
id="new-password" id='new-password'
/> />
</div> </div>
{passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber ? ( {passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber ? (
<div className="w-full mt-3 bg-white/5 px-2 mx-2 flex flex-col items-start py-2 rounded-md max-w-md mb-2"> <div className='w-full mt-3 bg-white/5 px-2 mx-2 flex flex-col items-start py-2 rounded-md max-w-md mb-2'>
<div className={`text-gray-400 text-sm mb-1`}> <div className={`text-gray-400 text-sm mb-1`}>
Password should contain at least: Password should contain at least:
</div> </div>
<div className="flex flex-row justify-start items-center ml-1"> <div className='flex flex-row justify-start items-center ml-1'>
{passwordErrorLength ? ( {passwordErrorLength ? (
<FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5" /> <FontAwesomeIcon icon={faX} className='text-md text-red mr-2.5' />
) : ( ) : (
<FontAwesomeIcon <FontAwesomeIcon
icon={faCheck} icon={faCheck}
className="text-md text-primary mr-2" className='text-md text-primary mr-2'
/> />
)} )}
<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
</div> </div>
</div> </div>
<div className="flex flex-row justify-start items-center ml-1"> <div className='flex flex-row justify-start items-center ml-1'>
{passwordErrorLowerCase ? ( {passwordErrorLowerCase ? (
<FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5" /> <FontAwesomeIcon icon={faX} className='text-md text-red mr-2.5' />
) : ( ) : (
<FontAwesomeIcon <FontAwesomeIcon
icon={faCheck} icon={faCheck}
className="text-md text-primary mr-2" className='text-md text-primary mr-2'
/> />
)} )}
<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
</div> </div>
</div> </div>
<div className="flex flex-row justify-start items-center ml-1"> <div className='flex flex-row justify-start items-center ml-1'>
{passwordErrorNumber ? ( {passwordErrorNumber ? (
<FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5" /> <FontAwesomeIcon icon={faX} className='text-md text-red mr-2.5' />
) : ( ) : (
<FontAwesomeIcon <FontAwesomeIcon
icon={faCheck} icon={faCheck}
className="text-md text-primary mr-2" className='text-md text-primary mr-2'
/> />
)} )}
<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
@@ -267,14 +265,14 @@ export default function PasswordReset() {
</div> </div>
</div> </div>
) : ( ) : (
<div className="py-2"></div> <div className='py-2'></div>
)} )}
<div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm"> <div className='flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm'>
<div className="text-l mt-6 m-8 px-8 py-3 text-lg"> <div className='text-l mt-6 m-8 px-8 py-3 text-lg'>
<Button <Button
text="Submit New Password" text='Submit New Password'
onButtonPressed={() => resetPasswordHandler()} onButtonPressed={() => resetPasswordHandler()}
size="lg" size='lg'
/> />
</div> </div>
</div> </div>
@@ -282,7 +280,7 @@ export default function PasswordReset() {
); );
return ( return (
<div className="bg-bunker-800 h-screen w-full flex flex-col items-center justify-center"> <div className='bg-bunker-800 h-screen w-full flex flex-col items-center justify-center'>
{step === 1 && stepConfirmEmail} {step === 1 && stepConfirmEmail}
{step === 2 && stepInputBackupKey} {step === 2 && stepInputBackupKey}
{step === 3 && stepEnterNewPassword} {step === 3 && stepEnterNewPassword}
+18 -14
View File
@@ -1,31 +1,35 @@
import React from "react"; import Head from 'next/head';
import Head from "next/head"; import Image from 'next/image';
import Image from "next/image";
/** /**
* This is the page that shows up when a user's invitation * This is the page that shows up when a user's invitation
* to join a project/organization on Infisical has expired * to join a project/organization on Infisical has expired
*/ */
export default function RequestNewInvite() { export default function RequestNewInvite() {
return ( return (
<div className="bg-bunker-700 md:h-screen flex flex-col justify-between"> <div className='bg-bunker-700 md:h-screen flex flex-col justify-between'>
<Head> <Head>
<title>Request a New Invite</title> <title>Request a New Invite</title>
<link rel="icon" href="/infisical.ico" /> <link rel='icon' href='/infisical.ico' />
</Head> </Head>
<div className="flex flex-col items-center justify-center text-bunker-200 h-screen w-screen mt-8"> <div className='flex flex-col items-center justify-center text-bunker-200 h-screen w-screen mt-8'>
<p className="text-4xl text-primary-100">Oops, your invite has expired.</p> <p className='text-4xl text-primary-100'>
<p className="text-lg my-4">Ask your admin for a new one.</p> Oops, your invite has expired.
<p className="text-sm text-bunker-400 max-w-xs px-7 text-center leading-tight"> </p>
<span className="bg-primary-500/40 text-black px-1 rounded-md">Note:</span> If it still {"doesn't work"}, please reach out to us at <p className='text-lg my-4'>Ask your admin for a new one.</p>
<p className='text-sm text-bunker-400 max-w-xs px-7 text-center leading-tight'>
<span className='bg-primary-500/40 text-black px-1 rounded-md'>
Note:
</span>{' '}
If it still {"doesn't work"}, please reach out to us at
support@infisical.com support@infisical.com
</p> </p>
<div className=""> <div className=''>
<Image <Image
src="/images/invitation-expired.svg" src='/images/invitation-expired.svg'
height={500} height={500}
width={800} width={800}
alt="google logo" alt='google logo'
></Image> ></Image>
</div> </div>
</div> </div>
-120
View File
@@ -1,120 +0,0 @@
import { useEffect, useState } from "react";
import Head from "next/head";
import { useTranslation } from "next-i18next";
import Plan from "~/components/billing/Plan";
import NavHeader from "~/components/navigation/NavHeader";
import { STRIPE_PRODUCT_PRO, STRIPE_PRODUCT_STARTER } from "~/utilities/config";
import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
import getOrganizationSubscriptions from "../../api/organization/GetOrgSubscription";
import getOrganizationUsers from "../../api/organization/GetOrgUsers";
export default function SettingsBilling() {
let [currentPlan, setCurrentPlan] = useState("");
let [numUsers, setNumUsers] = useState("");
const { t } = useTranslation();
const plans = [
{
key: 1,
name: t("billing:starter.name"),
price: t("billing:free"),
priceExplanation: t("billing:starter.price-explanation"),
text: t("billing:starter.text"),
subtext: t("billing:starter.subtext"),
buttonTextMain: t("billing:downgrade"),
buttonTextSecondary: t("billing:learn-more"),
current: currentPlan == STRIPE_PRODUCT_STARTER,
},
{
key: 2,
name: t("billing:professional.name"),
price: "$9",
priceExplanation: t("billing:professional.price-explanation"),
subtext: t("billing:professional.subtext"),
text: t("billing:professional.text"),
buttonTextMain: t("billing:upgrade"),
buttonTextSecondary: t("billing:learn-more"),
current: currentPlan == STRIPE_PRODUCT_PRO,
},
{
key: 3,
name: t("billing:enterprise.name"),
price: t("billing:custom-pricing"),
text: t("billing:enterprise.text"),
buttonTextMain: t("billing:schedule-demo"),
buttonTextSecondary: t("billing:learn-more"),
current: false,
},
];
useEffect(async () => {
const subscriptions = await getOrganizationSubscriptions({
orgId: localStorage.getItem("orgData.id"),
});
setCurrentPlan(subscriptions.data[0].plan.id);
const orgUsers = await getOrganizationUsers({
orgId: localStorage.getItem("orgData.id"),
});
setNumUsers(orgUsers.length);
}, []);
return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head>
<title>{t("common:head-title", { title: t("billing:title") })}</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<div className="flex flex-row">
<div className="w-full max-h-screen pb-2 overflow-y-auto">
<NavHeader pageName={t("billing:title")} />
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
<div className="flex flex-col justify-start items-start text-3xl">
<p className="font-semibold mr-4 text-gray-200">
{t("billing:title")}
</p>
<p className="font-normal mr-4 text-gray-400 text-base">
{t("billing:description")}
</p>
</div>
</div>
<div className="flex flex-col ml-6 text-mineshaft-50">
<p className="text-xl font-semibold">{t("billing:subscription")}</p>
<div className="flex flex-row mt-4 overflow-x-auto">
{plans.map((plan) => (
<Plan key={plan.name} plan={plan} />
))}
</div>
<p className="text-xl font-bold mt-12">
{t("billing:current-usage")}
</p>
<div className="flex flex-row">
<div className="mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col">
<p className="text-6xl font-bold">{numUsers}</p>
<p className="text-gray-300">
{numUsers > 1
? "Organization members"
: "Organization member"}
</p>
</div>
{/* <div className="mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col">
<p className="text-6xl font-bold">1 </p>
<p className="text-gray-300">Organization projects</p>
</div> */}
</div>
</div>
</div>
</div>
</div>
);
}
SettingsBilling.requireAuth = true;
export const getServerSideProps = getTranslatedServerSideProps([
"settings",
"billing",
]);
+123
View File
@@ -0,0 +1,123 @@
import { useEffect, useState } from 'react';
import Head from 'next/head';
import { useTranslation } from 'next-i18next';
import Plan from '~/components/billing/Plan';
import NavHeader from '~/components/navigation/NavHeader';
import { STRIPE_PRODUCT_PRO, STRIPE_PRODUCT_STARTER } from '~/utilities/config';
import { getTranslatedServerSideProps } from '~/utilities/withTranslateProps';
import getOrganizationSubscriptions from '../../api/organization/GetOrgSubscription';
import getOrganizationUsers from '../../api/organization/GetOrgUsers';
export default function SettingsBilling() {
const [currentPlan, setCurrentPlan] = useState('');
const [numUsers, setNumUsers] = useState(0);
const { t } = useTranslation();
const plans = [
{
key: 1,
name: t('billing:starter.name')!,
price: t('billing:free')!,
priceExplanation: t('billing:starter.price-explanation')!,
text: t('billing:starter.text')!,
subtext: t('billing:starter.subtext')!,
buttonTextMain: t('billing:downgrade')!,
buttonTextSecondary: t('billing:learn-more')!,
current: currentPlan == STRIPE_PRODUCT_STARTER,
},
{
key: 2,
name: t('billing:professional.name')!,
price: '$9',
priceExplanation: t('billing:professional.price-explanation')!,
subtext: t('billing:professional.subtext')!,
text: t('billing:professional.text')!,
buttonTextMain: t('billing:upgrade')!,
buttonTextSecondary: t('billing:learn-more')!,
current: currentPlan == STRIPE_PRODUCT_PRO,
},
{
key: 3,
name: t('billing:enterprise.name')!,
price: t('billing:custom-pricing')!,
text: t('billing:enterprise.text')!,
buttonTextMain: t('billing:schedule-demo')!,
buttonTextSecondary: t('billing:learn-more')!,
current: false,
},
];
useEffect(() => {
(async () => {
const orgId = localStorage.getItem('orgData.id') as string;
const subscriptions = await getOrganizationSubscriptions({
orgId,
});
setCurrentPlan(subscriptions.data[0].plan.id);
const orgUsers = await getOrganizationUsers({
orgId,
});
setNumUsers(orgUsers.length);
})();
}, []);
return (
<div className='bg-bunker-800 max-h-screen flex flex-col justify-between text-white'>
<Head>
<title>{t('common:head-title', { title: t('billing:title') })}</title>
<link rel='icon' href='/infisical.ico' />
</Head>
<div className='flex flex-row'>
<div className='w-full max-h-screen pb-2 overflow-y-auto'>
<NavHeader pageName={t('billing:title')} />
<div className='flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl'>
<div className='flex flex-col justify-start items-start text-3xl'>
<p className='font-semibold mr-4 text-gray-200'>
{t('billing:title')}
</p>
<p className='font-normal mr-4 text-gray-400 text-base'>
{t('billing:description')}
</p>
</div>
</div>
<div className='flex flex-col ml-6 text-mineshaft-50'>
<p className='text-xl font-semibold'>{t('billing:subscription')}</p>
<div className='flex flex-row mt-4 overflow-x-auto'>
{plans.map((plan) => (
<Plan key={plan.name} plan={plan} />
))}
</div>
<p className='text-xl font-bold mt-12'>
{t('billing:current-usage')}
</p>
<div className='flex flex-row'>
<div className='mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col'>
<p className='text-6xl font-bold'>{numUsers}</p>
<p className='text-gray-300'>
{numUsers > 1
? 'Organization members'
: 'Organization member'}
</p>
</div>
{/* <div className="mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col">
<p className="text-6xl font-bold">1 </p>
<p className="text-gray-300">Organization projects</p>
</div> */}
</div>
</div>
</div>
</div>
</div>
);
}
SettingsBilling.requireAuth = true;
export const getServerSideProps = getTranslatedServerSideProps([
'settings',
'billing',
]);
@@ -1,23 +1,23 @@
import { useEffect, useState } from "react"; import { 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 { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next';
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 { getTranslatedServerSideProps } from "~/utilities/withTranslateProps"; import { getTranslatedServerSideProps } from '~/utilities/withTranslateProps';
import addUserToOrg from '../../api/organization/addUserToOrg'; import addUserToOrg from '../../api/organization/addUserToOrg';
import deleteIncidentContact from '../../api/organization/deleteIncidentContact'; import deleteIncidentContact from '../../api/organization/deleteIncidentContact';
@@ -37,74 +37,76 @@ export default function SettingsOrg() {
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 [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<string[]>([]);
const [searchIncidentContact, setSearchIncidentContact] = useState(''); const [searchIncidentContact, setSearchIncidentContact] = useState('');
const [userList, setUserList] = useState(); const [userList, setUserList] = useState<any[]>([]);
const [personalEmail, setPersonalEmail] = useState(''); const [personalEmail, setPersonalEmail] = useState('');
let workspaceIdTemp;
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [currentPlan, setCurrentPlan] = useState(''); const [currentPlan, setCurrentPlan] = useState('');
const workspaceId = router.query.id as string;
const { t } = useTranslation(); const { t } = useTranslation();
useEffect(async () => { useEffect(() => {
let org = await getOrganization({ (async () => {
orgId: localStorage.getItem('orgData.id') const orgId = localStorage.getItem('orgData.id') as string;
}); const org = await getOrganization({
let orgData = org; orgId,
setOrgName(orgData.name); });
let incidentContactsData = await getIncidentContacts(
localStorage.getItem('orgData.id')
);
setIncidentContacts(incidentContactsData?.map((contact) => contact.email)); setOrgName(org.name);
const incidentContactsData = await getIncidentContacts(
localStorage.getItem('orgData.id') as string
);
const user = await getUser(); setIncidentContacts(
setPersonalEmail(user.email); incidentContactsData?.map((contact) => contact.email)
);
workspaceIdTemp = router.query.id; const user = await getUser();
let orgUsers = await getOrganizationUsers({ setPersonalEmail(user.email);
orgId: localStorage.getItem('orgData.id')
}); const orgUsers = await getOrganizationUsers({
setUserList( orgId,
orgUsers.map((user) => ({ });
key: guidGenerator(),
firstName: user.user?.firstName, setUserList(
lastName: user.user?.lastName, orgUsers.map((user) => ({
email: user.user?.email == null ? user.inviteEmail : user.user?.email, key: guidGenerator(),
role: user?.role, firstName: user.user?.firstName,
status: user?.status, lastName: user.user?.lastName,
userId: user.user?._id, email: user.user?.email == null ? user.inviteEmail : user.user?.email,
membershipId: user._id, role: user?.role,
publicKey: user.user?.publicKey status: user?.status,
})) userId: user.user?._id,
); membershipId: user._id,
const subscriptions = await getOrganizationSubscriptions({ publicKey: user.user?.publicKey,
orgId: localStorage.getItem('orgData.id') }))
}); );
setCurrentPlan(subscriptions.data[0].plan.product);
const subscriptions = await getOrganizationSubscriptions({
orgId,
});
setCurrentPlan(subscriptions.data[0].plan.product);
})();
}, []); }, []);
const modifyOrgName = (newName) => { const modifyOrgName = (newName: string) => {
setButtonReady(true); setButtonReady(true);
setOrgName(newName); setOrgName(newName);
}; };
const submitChanges = (newOrgName) => { const submitChanges = (newOrgName: string) => {
renameOrg(localStorage.getItem('orgData.id'), newOrgName); renameOrg(localStorage.getItem('orgData.id') as string, newOrgName);
setButtonReady(false); setButtonReady(false);
}; };
useEffect(async () => {
setWorkspaceId(router.query.id);
}, []);
function closeAddUserModal() { function closeAddUserModal() {
setIsAddUserOpen(false); setIsAddUserOpen(false);
} }
@@ -121,18 +123,21 @@ export default function SettingsOrg() {
setIsAddIncidentContactOpen(true); setIsAddIncidentContactOpen(true);
} }
async function submitAddUserModal(email) { async function submitAddUserModal(email: string) {
await addUserToOrg(email, localStorage.getItem('orgData.id')); await addUserToOrg(email, localStorage.getItem('orgData.id') as string);
setEmail(''); setEmail('');
setIsAddUserOpen(false); setIsAddUserOpen(false);
router.reload(); router.reload();
} }
const deleteIncidentContactFully = (incidentContact) => { const deleteIncidentContactFully = (incidentContact: string) => {
setIncidentContacts( setIncidentContacts(
incidentContacts.filter((contact) => contact != incidentContact) incidentContacts.filter((contact) => contact != incidentContact)
); );
deleteIncidentContact(localStorage.getItem('orgData.id'), incidentContact); deleteIncidentContact(
localStorage.getItem('orgData.id') as string,
incidentContact
);
}; };
/** /**
@@ -142,32 +147,31 @@ export default function SettingsOrg() {
* It then deletes the workspace and forwards the user to another aviable workspace. * It then deletes the workspace and forwards the user to another aviable workspace.
*/ */
const executeDeletingWorkspace = async () => { const executeDeletingWorkspace = async () => {
let userWorkspaces = await getWorkspaces(); const userWorkspaces = await getWorkspaces();
if (userWorkspaces.length > 1) { if (userWorkspaces.length > 1) {
if ( if (
userWorkspaces.filter( userWorkspaces.filter((workspace) => workspace._id == workspaceId)[0]
(workspace) => workspace._id == router.query.id .name == workspaceToBeDeletedName
)[0].name == workspaceToBeDeletedName
) { ) {
await deleteWorkspace(router.query.id); await deleteWorkspace(workspaceId);
let userWorkspaces = await getWorkspaces(); const userWorkspaces = await getWorkspaces();
router.push('/dashboard/' + userWorkspaces[0]._id); router.push('/dashboard/' + userWorkspaces[0]._id);
} }
} }
}; };
return ( return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white"> <div className='bg-bunker-800 max-h-screen flex flex-col justify-between text-white'>
<Head> <Head>
<title> <title>
{t("common:head-title", { title: t("settings-org:title") })} {t('common:head-title', { title: t('settings-org:title') })}
</title> </title>
<link rel="icon" href="/infisical.ico" /> <link rel='icon' href='/infisical.ico' />
</Head> </Head>
<div className="flex flex-row"> <div className='flex flex-row'>
<div className="w-full max-h-screen pb-2 overflow-y-auto"> <div className='w-full max-h-screen pb-2 overflow-y-auto'>
<NavHeader pageName={t("settings-org:title")} /> <NavHeader pageName={t('settings-org:title')} />
<AddIncidentContactDialog <AddIncidentContactDialog
isOpen={isAddIncidentContactOpen} isOpen={isAddIncidentContactOpen}
closeModal={closeAddIncidentContactModal} closeModal={closeAddIncidentContactModal}
@@ -175,55 +179,56 @@ export default function SettingsOrg() {
incidentContacts={incidentContacts} incidentContacts={incidentContacts}
setIncidentContacts={setIncidentContacts} setIncidentContacts={setIncidentContacts}
/> />
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl"> <div className='flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl'>
<div className="flex flex-col justify-start items-start text-3xl"> <div className='flex flex-col justify-start items-start text-3xl'>
<p className="font-semibold mr-4 text-gray-200"> <p className='font-semibold mr-4 text-gray-200'>
{t("settings-org:title")} {t('settings-org:title')}
</p> </p>
<p className="font-normal mr-4 text-gray-400 text-base"> <p className='font-normal mr-4 text-gray-400 text-base'>
{t("settings-org:description")} {t('settings-org:description')}
</p> </p>
</div> </div>
</div> </div>
<div className="flex flex-col ml-6 text-mineshaft-50 mr-6 max-w-5xl"> <div className='flex flex-col ml-6 text-mineshaft-50 mr-6 max-w-5xl'>
<div className="flex flex-col"> <div className='flex flex-col'>
<div className="min-w-md mt-2 flex flex-col items-end pb-4"> <div className='min-w-md mt-2 flex flex-col items-end pb-4'>
<div className="bg-white/5 rounded-md px-6 py-4 flex flex-col items-start flex flex-col items-start w-full mb-6"> <div className='bg-white/5 rounded-md px-6 py-4 flex flex-col items-start w-full mb-6'>
<div className="max-h-28 w-full max-w-md mr-auto"> <div className='max-h-28 w-full max-w-md mr-auto'>
<p className="font-semibold mr-4 text-gray-200 text-xl mb-2"> <p className='font-semibold mr-4 text-gray-200 text-xl mb-2'>
{t("common:display-name")} {t('common:display-name')}
</p> </p>
<InputField <InputField
label=''
// label="Organization Name" // label="Organization Name"
onChangeHandler={modifyOrgName} onChangeHandler={modifyOrgName}
type="varName" type='varName'
value={orgName} value={orgName}
placeholder="" placeholder=''
isRequired isRequired
/> />
</div> </div>
<div className="flex justify-start w-full"> <div className='flex justify-start w-full'>
<div className={`flex justify-start max-w-sm mt-4 mb-2`}> <div className={`flex justify-start max-w-sm mt-4 mb-2`}>
<Button <Button
text={t("common:save-changes")} text={t('common:save-changes') as string}
onButtonPressed={() => submitChanges(orgName)} onButtonPressed={() => submitChanges(orgName)}
color="mineshaft" color='mineshaft'
size="md" size='md'
active={buttonReady} active={buttonReady}
iconDisabled={faCheck} iconDisabled={faCheck}
textDisabled={t("common:saved")} textDisabled={t('common:saved') as string}
/> />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="bg-white/5 rounded-md px-6 pt-6 pb-2 flex flex-col items-start flex flex-col items-start w-full mb-6"> <div className='bg-white/5 rounded-md px-6 pt-6 pb-2 flex flex-col items-start w-full mb-6'>
<p className="font-semibold mr-4 text-white text-xl"> <p className='font-semibold mr-4 text-white text-xl'>
{t("section-members:org-members")} {t('section-members:org-members')}
</p> </p>
<p className="mr-4 text-gray-400 mt-2 mb-2"> <p className='mr-4 text-gray-400 mt-2 mb-2'>
{t("section-members:org-members-description")} {t('section-members:org-members-description')}
</p> </p>
<AddUserDialog <AddUserDialog
isOpen={isAddUserOpen} isOpen={isAddUserOpen}
@@ -236,31 +241,31 @@ export default function SettingsOrg() {
orgName={orgName} orgName={orgName}
/> />
{/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */} {/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */}
<div className="pb-1 w-full flex flex-row items-start max-w-6xl"> <div className='pb-1 w-full flex flex-row items-start max-w-6xl'>
<div className="h-10 w-full bg-white/5 mt-2 flex items-center rounded-md flex flex-row items-center"> <div className='h-10 w-full bg-white/5 mt-2 flex items-center rounded-md flex-row '>
<FontAwesomeIcon <FontAwesomeIcon
className="bg-white/5 rounded-l-md py-3 pl-4 pr-2 text-gray-400" className='bg-white/5 rounded-l-md py-3 pl-4 pr-2 text-gray-400'
icon={faMagnifyingGlass} icon={faMagnifyingGlass}
/> />
<input <input
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={t("section-members:search-members")} placeholder={t('section-members:search-members') as string}
/> />
</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'>
<Button <Button
text={t("section-members:add-member")} text={t('section-members:add-member') as string}
onButtonPressed={openAddUserModal} onButtonPressed={openAddUserModal}
color="mineshaft" color='mineshaft'
size="md" size='md'
icon={faPlus} icon={faPlus}
/> />
</div> </div>
</div> </div>
{userList && ( {userList && (
<div className="overflow-y-auto max-w-6xl w-full"> <div className='overflow-y-auto max-w-6xl w-full'>
<UserTable <UserTable
userData={userList} userData={userList}
changeData={setUserList} changeData={setUserList}
@@ -271,42 +276,42 @@ export default function SettingsOrg() {
// onClick={openDeleteModal} // onClick={openDeleteModal}
// deleteUser={deleteMembership} // deleteUser={deleteMembership}
// setUserIdToBeDeleted={setUserIdToBeDeleted} // setUserIdToBeDeleted={setUserIdToBeDeleted}
className="w-full" className='w-full'
/> />
</div> </div>
)} )}
</div> </div>
<div className="bg-white/5 rounded-md px-6 pt-6 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4"> <div className='bg-white/5 rounded-md px-6 pt-6 pb-6 flex flex-col items-start w-full mb-6 mt-4'>
<div className="flex flex-row max-w-5xl justify-between items-center w-full"> <div className='flex flex-row max-w-5xl justify-between items-center w-full'>
<div className="flex flex-col justify-between w-full max-w-3xl"> <div className='flex flex-col justify-between w-full max-w-3xl'>
<p className="text-xl font-semibold mb-3 min-w-max"> <p className='text-xl font-semibold mb-3 min-w-max'>
{t("section-incident:incident-contacts")} {t('section-incident:incident-contacts')}
</p> </p>
<p className="text-xs text-gray-500 mb-2 min-w-max"> <p className='text-xs text-gray-500 mb-2 min-w-max'>
{t("section-incident:incident-contacts-description")} {t('section-incident:incident-contacts-description')}
</p> </p>
</div> </div>
<div className="mt-4 mb-2 min-w-max flex flex-row items-end justify-end justify-center"> <div className='mt-4 mb-2 min-w-max flex flex-row items-end justify-center'>
<Button <Button
text={t("section-incident:add-contact")} text={t('section-incident:add-contact') as string}
onButtonPressed={openAddIncidentContactModal} onButtonPressed={openAddIncidentContactModal}
color="mineshaft" color='mineshaft'
size="md" size='md'
icon={faPlus} icon={faPlus}
/> />
</div> </div>
</div> </div>
<div className="h-12 w-full max-w-5xl bg-white/5 mt-2 flex items-center rounded-t-md flex flwex-row items-center"> <div className='h-12 w-full max-w-5xl bg-white/5 mt-2 flex items-center rounded-t-md flwex-row'>
<FontAwesomeIcon <FontAwesomeIcon
className="bg-white/5 rounded-tl-md py-4 pl-4 pr-2 text-gray-400" className='bg-white/5 rounded-tl-md py-4 pl-4 pr-2 text-gray-400'
icon={faMagnifyingGlass} icon={faMagnifyingGlass}
/> />
<input <input
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={t("common:search")} placeholder={t('common:search') as string}
/> />
</div> </div>
{incidentContacts?.filter((email) => {incidentContacts?.filter((email) =>
@@ -317,25 +322,25 @@ export default function SettingsOrg() {
.map((contact) => ( .map((contact) => (
<div <div
key={guidGenerator()} key={guidGenerator()}
className="flex flex-row items-center justify-between max-w-5xl px-4 py-3 hover:bg-white/5 border-t border-gray-600 w-full" className='flex flex-row items-center justify-between max-w-5xl px-4 py-3 hover:bg-white/5 border-t border-gray-600 w-full'
> >
<p className="text-gray-300">{contact}</p> <p className='text-gray-300'>{contact}</p>
<div className="opacity-50 hover:opacity-100 duration-200"> <div className='opacity-50 hover:opacity-100 duration-200'>
<Button <Button
onButtonPressed={() => onButtonPressed={() =>
deleteIncidentContactFully(contact) deleteIncidentContactFully(contact)
} }
color="red" color='red'
size="icon-sm" size='icon-sm'
icon={faX} icon={faX}
/> />
</div> </div>
</div> </div>
)) ))
) : ( ) : (
<div className="w-full flex flex-row justify-center mt-6 max-w-4xl ml-6"> <div className='w-full flex flex-row justify-center mt-6 max-w-4xl ml-6'>
<p className="text-gray-400"> <p className='text-gray-400'>
{t("section-incident:no-incident-contacts")} {t('section-incident:no-incident-contacts')}
</p> </p>
</div> </div>
)} )}
@@ -386,8 +391,8 @@ export default function SettingsOrg() {
SettingsOrg.requireAuth = true; SettingsOrg.requireAuth = true;
export const getServerSideProps = getTranslatedServerSideProps([ export const getServerSideProps = getTranslatedServerSideProps([
"settings", 'settings',
"settings-org", 'settings-org',
"section-incident", 'section-incident',
"section-members", 'section-members',
]); ]);
@@ -16,20 +16,19 @@ import passwordCheck from "~/utilities/checks/PasswordCheck";
import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps"; import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
import AddApiKeyDialog from "../../../components/basic/dialog/AddApiKeyDialog"; import AddApiKeyDialog from "../../../components/basic/dialog/AddApiKeyDialog";
import deleteAPIKey from "../../api/apiKey/deleteAPIKey";
import getAPIKeys from "../../api/apiKey/getAPIKeys"; import getAPIKeys from "../../api/apiKey/getAPIKeys";
import getUser from "../../api/user/getUser"; import getUser from "../../api/user/getUser";
export default function PersonalSettings() { export default function PersonalSettings() {
const [personalEmail, setPersonalEmail] = useState(""); const [personalEmail, setPersonalEmail] = useState('');
const [personalName, setPersonalName] = useState(""); const [personalName, setPersonalName] = useState('');
const [passwordErrorLength, setPasswordErrorLength] = useState(false); const [passwordErrorLength, setPasswordErrorLength] = useState(false);
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false); const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false); const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
const [currentPasswordError, setCurrentPasswordError] = useState(false); const [currentPasswordError, setCurrentPasswordError] = useState(false);
const [currentPassword, setCurrentPassword] = useState(""); const [currentPassword, setCurrentPassword] = useState('');
const [newPassword, setNewPassword] = useState(""); const [newPassword, setNewPassword] = useState('');
const [backupPassword, setBackupPassword] = useState(""); const [backupPassword, setBackupPassword] = useState('');
const [passwordChanged, setPasswordChanged] = useState(false); const [passwordChanged, setPasswordChanged] = useState(false);
const [backupKeyIssued, setBackupKeyIssued] = useState(false); const [backupKeyIssued, setBackupKeyIssued] = useState(false);
const [backupKeyError, setBackupKeyError] = useState(false); const [backupKeyError, setBackupKeyError] = useState(false);
@@ -38,17 +37,17 @@ export default function PersonalSettings() {
const { t } = useTranslation(); const { t } = useTranslation();
const router = useRouter(); const router = useRouter();
const lang = router.locale ?? "en"; const lang = router.locale ?? 'en';
const setLanguage = async (to) => { const setLanguage = async (to: string) => {
router.push(router.asPath, router.asPath, { locale: to }); router.push(router.asPath, router.asPath, { locale: to });
localStorage.setItem("lang", to); localStorage.setItem('lang', to);
}; };
useEffect(() => { useEffect(() => {
const load = async () => { const load = async () => {
try { try {
let user = await getUser(); const user = await getUser();
setApiKeys( setApiKeys(
await getAPIKeys() await getAPIKeys()
); );
@@ -67,12 +66,12 @@ export default function PersonalSettings() {
}; };
return ( return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white"> <div className='bg-bunker-800 max-h-screen flex flex-col justify-between text-white'>
<Head> <Head>
<title> <title>
{t("common:head-title", { title: t("settings-personal:title") })} {t('common:head-title', { title: t('settings-personal:title') })}
</title> </title>
<link rel="icon" href="/infisical.ico" /> <link rel='icon' href='/infisical.ico' />
</Head> </Head>
<AddApiKeyDialog <AddApiKeyDialog
isOpen={isAddApiKeyDialogOpen} isOpen={isAddApiKeyDialogOpen}
@@ -83,16 +82,16 @@ export default function PersonalSettings() {
<div className="flex flex-row"> <div className="flex flex-row">
<div className="w-full max-h-screen pb-2 overflow-y-auto"> <div className="w-full max-h-screen pb-2 overflow-y-auto">
<NavHeader <NavHeader
pageName={t("settings-personal:title")} pageName={t('settings-personal:title')}
isProjectRelated={false} isProjectRelated={false}
/> />
<div className="flex flex-row justify-between items-center ml-6 mt-8 mb-6 text-xl max-w-5xl"> <div className='flex flex-row justify-between items-center ml-6 mt-8 mb-6 text-xl max-w-5xl'>
<div className="flex flex-col justify-start items-start text-3xl"> <div className='flex flex-col justify-start items-start text-3xl'>
<p className="font-semibold mr-4 text-gray-200"> <p className='font-semibold mr-4 text-gray-200'>
{t("settings-personal:title")} {t('settings-personal:title')}
</p> </p>
<p className="font-normal mr-4 text-gray-400 text-base"> <p className='font-normal mr-4 text-gray-400 text-base'>
{t("settings-personal:description")} {t('settings-personal:description')}
</p> </p>
</div> </div>
</div> </div>
@@ -101,13 +100,12 @@ export default function PersonalSettings() {
<p className="text-xl font-semibold self-start"> <p className="text-xl font-semibold self-start">
{t("settings-personal:change-language")} {t("settings-personal:change-language")}
</p> </p>
<div className="max-h-28 w-ful mt-4"> <div className='max-h-28 w-ful mt-4'>
<ListBox <ListBox
selected={lang} selected={lang}
onChange={setLanguage} onChange={setLanguage}
data={["en", "ko", "fr"]} data={['en', 'ko', 'fr']}
width="full" text={`${t('common:language')}: `}
text={`${t("common:language")}: `}
/> />
</div> </div>
</div> </div>
@@ -139,31 +137,31 @@ export default function PersonalSettings() {
/> />
</div> </div>
<div className="bg-white/5 rounded-md px-6 pt-5 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6"> <div className='bg-white/5 rounded-md px-6 pt-5 pb-6 flex flex-col items-start w-full mb-6'>
<div className="flex flex-row max-w-5xl justify-between items-center w-full"> <div className='flex flex-row max-w-5xl justify-between items-center w-full'>
<div className="flex flex-col justify-between w-full max-w-3xl"> <div className='flex flex-col justify-between w-full max-w-3xl'>
<p className="text-xl font-semibold mb-3 min-w-max"> <p className='text-xl font-semibold mb-3 min-w-max'>
{t("section-password:change")} {t('section-password:change')}
</p> </p>
</div> </div>
</div> </div>
<div className="max-w-xl w-full"> <div className='max-w-xl w-full'>
<InputField <InputField
label={t("section-password:current")} label={t('section-password:current') as string}
onChangeHandler={(password) => { onChangeHandler={(password) => {
setCurrentPassword(password); setCurrentPassword(password);
}} }}
type="password" type='password'
value={currentPassword} value={currentPassword}
isRequired isRequired
error={currentPasswordError} error={currentPasswordError}
errorText={t("section-password:current-wrong")} errorText={t('section-password:current-wrong') as string}
autoComplete="current-password" autoComplete='current-password'
id="current-password" id='current-password'
/> />
<div className="py-2"></div> <div className='py-2'></div>
<InputField <InputField
label={t("section-password:new")} label={t('section-password:new') as string}
onChangeHandler={(password) => { onChangeHandler={(password) => {
setNewPassword(password); setNewPassword(password);
passwordCheck({ passwordCheck({
@@ -171,10 +169,10 @@ export default function PersonalSettings() {
setPasswordErrorLength, setPasswordErrorLength,
setPasswordErrorNumber, setPasswordErrorNumber,
setPasswordErrorLowerCase, setPasswordErrorLowerCase,
currentErrorCheck: false, errorCheck: false,
}); });
}} }}
type="password" type='password'
value={newPassword} value={newPassword}
isRequired isRequired
error={ error={
@@ -182,86 +180,86 @@ export default function PersonalSettings() {
passwordErrorLowerCase && passwordErrorLowerCase &&
passwordErrorNumber passwordErrorNumber
} }
autoComplete="new-password" autoComplete='new-password'
id="new-password" id='new-password'
/> />
</div> </div>
{passwordErrorLength || {passwordErrorLength ||
passwordErrorLowerCase || passwordErrorLowerCase ||
passwordErrorNumber ? ( passwordErrorNumber ? (
<div className="w-full mt-3 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md max-w-xl mb-2"> <div className='w-full mt-3 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md max-w-xl mb-2'>
<div className={`text-gray-400 text-sm mb-1`}> <div className={`text-gray-400 text-sm mb-1`}>
{t("section-password:validate-base")} {t('section-password:validate-base')}
</div> </div>
<div className="flex flex-row justify-start items-center ml-1"> <div className='flex flex-row justify-start items-center ml-1'>
{passwordErrorLength ? ( {passwordErrorLength ? (
<FontAwesomeIcon <FontAwesomeIcon
icon={faX} icon={faX}
className="text-md text-red mr-2.5" className='text-md text-red mr-2.5'
/> />
) : ( ) : (
<FontAwesomeIcon <FontAwesomeIcon
icon={faCheck} icon={faCheck}
className="text-md text-primary mr-2" className='text-md text-primary mr-2'
/> />
)} )}
<div <div
className={`${ className={`${
passwordErrorLength ? "text-gray-400" : "text-gray-600" passwordErrorLength ? 'text-gray-400' : 'text-gray-600'
} text-sm`} } text-sm`}
> >
{t("section-password:validate-length")} {t('section-password:validate-length')}
</div> </div>
</div> </div>
<div className="flex flex-row justify-start items-center ml-1"> <div className='flex flex-row justify-start items-center ml-1'>
{passwordErrorLowerCase ? ( {passwordErrorLowerCase ? (
<FontAwesomeIcon <FontAwesomeIcon
icon={faX} icon={faX}
className="text-md text-red mr-2.5" className='text-md text-red mr-2.5'
/> />
) : ( ) : (
<FontAwesomeIcon <FontAwesomeIcon
icon={faCheck} icon={faCheck}
className="text-md text-primary mr-2" className='text-md text-primary mr-2'
/> />
)} )}
<div <div
className={`${ className={`${
passwordErrorLowerCase passwordErrorLowerCase
? "text-gray-400" ? 'text-gray-400'
: "text-gray-600" : 'text-gray-600'
} text-sm`} } text-sm`}
> >
{t("section-password:validate-case")} {t('section-password:validate-case')}
</div> </div>
</div> </div>
<div className="flex flex-row justify-start items-center ml-1"> <div className='flex flex-row justify-start items-center ml-1'>
{passwordErrorNumber ? ( {passwordErrorNumber ? (
<FontAwesomeIcon <FontAwesomeIcon
icon={faX} icon={faX}
className="text-md text-red mr-2.5" className='text-md text-red mr-2.5'
/> />
) : ( ) : (
<FontAwesomeIcon <FontAwesomeIcon
icon={faCheck} icon={faCheck}
className="text-md text-primary mr-2" className='text-md text-primary mr-2'
/> />
)} )}
<div <div
className={`${ className={`${
passwordErrorNumber ? "text-gray-400" : "text-gray-600" passwordErrorNumber ? 'text-gray-400' : 'text-gray-600'
} text-sm`} } text-sm`}
> >
{t("section-password:validate-number")} {t('section-password:validate-number')}
</div> </div>
</div> </div>
</div> </div>
) : ( ) : (
<div className="py-2"></div> <div className='py-2'></div>
)} )}
<div className="flex flex-row items-center mt-3 w-52 pr-3"> <div className='flex flex-row items-center mt-3 w-52 pr-3'>
<Button <Button
text={t("section-password:change")} text={t('section-password:change') as string}
onButtonPressed={() => { onButtonPressed={() => {
if ( if (
!passwordErrorLength && !passwordErrorLength &&
@@ -279,23 +277,23 @@ export default function PersonalSettings() {
); );
} }
}} }}
color="mineshaft" color='mineshaft'
size="md" size='md'
active={ active={
newPassword != "" && newPassword != '' &&
currentPassword != "" && currentPassword != '' &&
!( !(
passwordErrorLength || passwordErrorLength ||
passwordErrorLowerCase || passwordErrorLowerCase ||
passwordErrorNumber passwordErrorNumber
) )
} }
textDisabled={t("section-password:change")} textDisabled={t('section-password:change') as string}
/> />
<FontAwesomeIcon <FontAwesomeIcon
icon={faCheck} icon={faCheck}
className={`ml-4 text-primary text-3xl ${ className={`ml-4 text-primary text-3xl ${
passwordChanged ? "opacity-100" : "opacity-0" passwordChanged ? 'opacity-100' : 'opacity-0'
} duration-300`} } duration-300`}
/> />
</div> </div>
@@ -307,30 +305,30 @@ export default function PersonalSettings() {
<p className="text-xl font-semibold mb-3 min-w-max"> <p className="text-xl font-semibold mb-3 min-w-max">
{t("settings-personal:emergency.name")} {t("settings-personal:emergency.name")}
</p> </p>
<p className="text-sm text-mineshaft-300 min-w-max"> <p className='text-sm text-mineshaft-300 min-w-max'>
{t("settings-personal:emergency.text1")} {t('settings-personal:emergency.text1')}
</p> </p>
<p className="text-sm text-mineshaft-300 mb-5 min-w-max"> <p className='text-sm text-mineshaft-300 mb-5 min-w-max'>
{t("settings-personal:emergency.text2")} {t('settings-personal:emergency.text2')}
</p> </p>
</div> </div>
</div> </div>
<div className="w-full max-w-xl mb-4"> <div className='w-full max-w-xl mb-4'>
<InputField <InputField
label={t("section-password:current")} label={t('section-password:current') as string}
onChangeHandler={setBackupPassword} onChangeHandler={setBackupPassword}
type="password" type='password'
value={backupPassword} value={backupPassword}
isRequired isRequired
error={backupKeyError} error={backupKeyError}
errorText={t("section-password:current-wrong")} errorText={t('section-password:current-wrong') as string}
autoComplete="current-password" autoComplete='current-password'
id="current-password" id='current-password'
/> />
</div> </div>
<div className="flex flex-row items-center mt-3 w-full w-60"> <div className='flex flex-row items-center mt-3 w-60'>
<Button <Button
text={t("settings-personal:emergency.download")} text={t('settings-personal:emergency.download') as string}
onButtonPressed={() => { onButtonPressed={() => {
issueBackupKey({ issueBackupKey({
email: personalEmail, email: personalEmail,
@@ -340,15 +338,17 @@ export default function PersonalSettings() {
setBackupKeyIssued, setBackupKeyIssued,
}); });
}} }}
color="mineshaft" color='mineshaft'
size="md" size='md'
active={backupPassword != ""} active={backupPassword != ''}
textDisabled={t("settings-personal:emergency.download")} textDisabled={
t('settings-personal:emergency.download') as string
}
/> />
<FontAwesomeIcon <FontAwesomeIcon
icon={faCheck} icon={faCheck}
className={`ml-4 text-primary text-3xl ${ className={`ml-4 text-primary text-3xl ${
backupKeyIssued ? "opacity-100" : "opacity-0" backupKeyIssued ? 'opacity-100' : 'opacity-0'
} duration-300`} } duration-300`}
/> />
</div> </div>
+3 -4
View File
@@ -101,9 +101,8 @@ export default function SettingsBasic() {
if (userWorkspaces.length > 1) { if (userWorkspaces.length > 1) {
if ( if (
userWorkspaces.filter( userWorkspaces.filter((workspace) => workspace._id === workspaceId)[0]
(workspace) => workspace._id === workspaceId .name == workspaceToBeDeletedName
)[0].name == workspaceToBeDeletedName
) { ) {
await deleteWorkspace(workspaceId); await deleteWorkspace(workspaceId);
const userWorkspaces = await getWorkspaces(); const userWorkspaces = await getWorkspaces();
@@ -323,7 +322,7 @@ export default function SettingsBasic() {
</p> </p>
<div className='max-h-28 w-full max-w-md mr-auto mt-4'> <div className='max-h-28 w-full max-w-md mr-auto mt-4'>
<InputField <InputField
label={t('settings-project:project-to-delete')} label={t('settings-project:project-to-delete') as string}
onChangeHandler={setWorkspaceToBeDeletedName} onChangeHandler={setWorkspaceToBeDeletedName}
type='varName' type='varName'
value={workspaceToBeDeletedName} value={workspaceToBeDeletedName}
@@ -5,6 +5,10 @@ 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 jsrp from 'jsrp';
import queryString from 'query-string';
import nacl from 'tweetnacl';
import { encodeBase64 } from 'tweetnacl-util';
import Button from '~/components/basic/buttons/Button'; import Button from '~/components/basic/buttons/Button';
import InputField from '~/components/basic/InputField'; import InputField from '~/components/basic/InputField';
@@ -16,11 +20,7 @@ import passwordCheck from '~/utilities/checks/PasswordCheck';
import completeAccountInformationSignupInvite from './api/auth/CompleteAccountInformationSignupInvite'; import completeAccountInformationSignupInvite from './api/auth/CompleteAccountInformationSignupInvite';
import verifySignupInvite from './api/auth/VerifySignupInvite'; import verifySignupInvite from './api/auth/VerifySignupInvite';
const nacl = require('tweetnacl');
const jsrp = require('jsrp');
nacl.util = require('tweetnacl-util');
const client = new jsrp.client(); const client = new jsrp.client();
const queryString = require('query-string');
export default function SignupInvite() { export default function SignupInvite() {
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
@@ -31,21 +31,22 @@ export default function SignupInvite() {
const [passwordErrorLength, setPasswordErrorLength] = useState(false); const [passwordErrorLength, setPasswordErrorLength] = useState(false);
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false); const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false); const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split('?')[1]);
const [email, setEmail] = useState(parsedUrl.to?.replace(' ', '+').trim());
const token = parsedUrl.token;
const [errorLogin, setErrorLogin] = useState(false); const [errorLogin, setErrorLogin] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [step, setStep] = useState(1); const [step, setStep] = useState(1);
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);
const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split('?')[1]);
const token = parsedUrl.token as string;
const email = (parsedUrl.to as string)?.replace(' ', '+').trim();
// Verifies if the information that the users entered (name, workspace) is there, and if the password matched the criteria. // Verifies if the information that the users entered (name, workspace) is there, and if the password matched the criteria.
const signupErrorCheck = async () => { const signupErrorCheck = async () => {
setIsLoading(true); setIsLoading(true);
var errorCheck = false; let errorCheck = false;
if (!firstName) { if (!firstName) {
setFirstNameError(true); setFirstNameError(true);
errorCheck = true; errorCheck = true;
@@ -63,7 +64,7 @@ export default function SignupInvite() {
setPasswordErrorLength, setPasswordErrorLength,
setPasswordErrorNumber, setPasswordErrorNumber,
setPasswordErrorLowerCase, setPasswordErrorLowerCase,
errorCheck errorCheck,
}); });
if (!errorCheck) { if (!errorCheck) {
@@ -71,8 +72,8 @@ export default function SignupInvite() {
const pair = nacl.box.keyPair(); const pair = nacl.box.keyPair();
const secretKeyUint8Array = pair.secretKey; const secretKeyUint8Array = pair.secretKey;
const publicKeyUint8Array = pair.publicKey; const publicKeyUint8Array = pair.publicKey;
const PRIVATE_KEY = nacl.util.encodeBase64(secretKeyUint8Array); const PRIVATE_KEY = encodeBase64(secretKeyUint8Array);
const PUBLIC_KEY = nacl.util.encodeBase64(publicKeyUint8Array); const PUBLIC_KEY = encodeBase64(publicKeyUint8Array);
const { ciphertext, iv, tag } = Aes256Gcm.encrypt({ const { ciphertext, iv, tag } = Aes256Gcm.encrypt({
text: PRIVATE_KEY, text: PRIVATE_KEY,
@@ -81,7 +82,7 @@ export default function SignupInvite() {
.padStart( .padStart(
32 + (password.slice(0, 32).length - new Blob([password]).size), 32 + (password.slice(0, 32).length - new Blob([password]).size),
'0' '0'
) ),
}); });
localStorage.setItem('PRIVATE_KEY', PRIVATE_KEY); localStorage.setItem('PRIVATE_KEY', PRIVATE_KEY);
@@ -89,7 +90,7 @@ export default function SignupInvite() {
client.init( client.init(
{ {
username: email, username: email,
password: password password: password,
}, },
async () => { async () => {
client.createVerifier(async (err, result) => { client.createVerifier(async (err, result) => {
@@ -103,11 +104,11 @@ export default function SignupInvite() {
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 (!errorCheck && response.status == '200') { if (!errorCheck && response.status == 200) {
response = await response.json(); response = await response.json();
localStorage.setItem('publicKey', PUBLIC_KEY); localStorage.setItem('publicKey', PUBLIC_KEY);
@@ -140,42 +141,42 @@ export default function SignupInvite() {
// Step 4 of the sign up process (download the emergency kit pdf) // Step 4 of the sign up process (download the emergency kit pdf)
const stepConfirmEmail = ( const stepConfirmEmail = (
<div className="bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl"> <div className='bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl'>
<p className="text-4xl text-center font-semibold mb-6 flex justify-center text-primary-100"> <p className='text-4xl text-center font-semibold mb-6 flex justify-center text-primary-100'>
Confirm your email Confirm your email
</p> </p>
<Image <Image
src="/images/dragon-signupinvite.svg" src='/images/dragon-signupinvite.svg'
height={262} height={262}
width={410} width={410}
alt="verify email" alt='verify email'
></Image> ></Image>
<div className="flex max-w-max flex-col items-center justify-center md:p-2 max-h-24 max-w-md mx-auto text-lg px-4 mt-10 mb-2"> <div className='flex flex-col items-center justify-center md:p-2 max-h-24 max-w-md mx-auto text-lg px-4 mt-10 mb-2'>
<Button <Button
text="Confirm Email" text='Confirm Email'
onButtonPressed={async () => { onButtonPressed={async () => {
const response = await verifySignupInvite({ const response = await verifySignupInvite({
email, email,
code: token code: token,
}); });
if (response.status == 200) { if (response.status == 200) {
const res = await response.json(); const res = await response.json();
// user will have temp token if doesn't have an account // user will have temp token if doesn't have an account
// then continue with account setup workflow // then continue with account setup workflow
if(res?.token){ if (res?.token) {
setVerificationToken(res.token); setVerificationToken(res.token);
setStep(2); setStep(2);
} else { } else {
// user will be redirected to dashboard // user will be redirected to dashboard
// if not logged in gets kicked out to login // if not logged in gets kicked out to login
router.push("/dashboard") router.push('/dashboard');
} }
} else { } else {
console.log('ERROR', response); console.log('ERROR', response);
router.push('/requestnewinvite'); router.push('/requestnewinvite');
} }
}} }}
size="lg" size='lg'
/> />
</div> </div>
</div> </div>
@@ -183,37 +184,37 @@ export default function SignupInvite() {
// Because this is the invite signup - we directly go to the last step of signup (email is already verified) // Because this is the invite signup - we directly go to the last step of signup (email is already verified)
const main = ( const main = (
<div className="bg-bunker w-max mx-auto h-7/12 py-10 px-8 rounded-xl drop-shadow-xl mb-32 md:mb-16"> <div className='bg-bunker w-max mx-auto h-7/12 py-10 px-8 rounded-xl drop-shadow-xl mb-32 md:mb-16'>
<p className="text-4xl font-bold flex justify-center mb-6 text-gray-400 mx-8 md:mx-16 text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary"> <p className='text-4xl font-bold flex justify-center mb-6 text-gray-400 mx-8 md:mx-16 text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary'>
Almost there! Almost there!
</p> </p>
<div className="relative z-0 flex items-center justify-end w-full md:p-2 rounded-lg max-h-24"> <div className='relative z-0 flex items-center justify-end w-full md:p-2 rounded-lg max-h-24'>
<InputField <InputField
label="First Name" label='First Name'
onChangeHandler={setFirstName} onChangeHandler={setFirstName}
type="name" type='name'
value={firstName} value={firstName}
isRequired isRequired
errorText="Please input your first name." errorText='Please input your first name.'
error={firstNameError} error={firstNameError}
autoComplete="given-name" autoComplete='given-name'
/> />
</div> </div>
<div className="flex items-center justify-center w-full md:p-2 rounded-lg max-h-24"> <div className='flex items-center justify-center w-full md:p-2 rounded-lg max-h-24'>
<InputField <InputField
label="Last Name" label='Last Name'
onChangeHandler={setLastName} onChangeHandler={setLastName}
type="name" type='name'
value={lastName} value={lastName}
isRequired isRequired
errorText="Please input your last name." errorText='Please input your last name.'
error={lastNameError} error={lastNameError}
autoComplete="family-name" autoComplete='family-name'
/> />
</div> </div>
<div className="mt-2 flex flex-col items-center justify-center w-full md:p-2 rounded-lg max-h-60"> <div className='mt-2 flex flex-col items-center justify-center w-full md:p-2 rounded-lg max-h-60'>
<InputField <InputField
label="Password" label='Password'
onChangeHandler={(password) => { onChangeHandler={(password) => {
setPassword(password); setPassword(password);
passwordCheck({ passwordCheck({
@@ -221,35 +222,35 @@ export default function SignupInvite() {
setPasswordErrorLength, setPasswordErrorLength,
setPasswordErrorNumber, setPasswordErrorNumber,
setPasswordErrorLowerCase, setPasswordErrorLowerCase,
currentErrorCheck: false errorCheck: false,
}); });
}} }}
type="password" type='password'
value={password} value={password}
isRequired isRequired
error={ error={
passwordErrorLength && passwordErrorNumber && passwordErrorLowerCase passwordErrorLength && passwordErrorNumber && passwordErrorLowerCase
} }
autoComplete="new-password" autoComplete='new-password'
id="new-password" id='new-password'
/> />
{passwordErrorLength || {passwordErrorLength ||
passwordErrorLowerCase || passwordErrorLowerCase ||
passwordErrorNumber ? ( passwordErrorNumber ? (
<div className="w-full mt-4 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md"> <div className='w-full mt-4 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md'>
<div className={`text-gray-400 text-sm mb-1`}> <div className={`text-gray-400 text-sm mb-1`}>
Password should contain at least: Password should contain at least:
</div> </div>
<div className="flex flex-row justify-start items-center ml-1"> <div className='flex flex-row justify-start items-center ml-1'>
{passwordErrorLength ? ( {passwordErrorLength ? (
<FontAwesomeIcon <FontAwesomeIcon
icon={faX} icon={faX}
className="text-md text-red mr-2.5" className='text-md text-red mr-2.5'
/> />
) : ( ) : (
<FontAwesomeIcon <FontAwesomeIcon
icon={faCheck} icon={faCheck}
className="text-md text-primary mr-2" className='text-md text-primary mr-2'
/> />
)} )}
<div <div
@@ -260,16 +261,16 @@ export default function SignupInvite() {
14 characters 14 characters
</div> </div>
</div> </div>
<div className="flex flex-row justify-start items-center ml-1"> <div className='flex flex-row justify-start items-center ml-1'>
{passwordErrorLowerCase ? ( {passwordErrorLowerCase ? (
<FontAwesomeIcon <FontAwesomeIcon
icon={faX} icon={faX}
className="text-md text-red mr-2.5" className='text-md text-red mr-2.5'
/> />
) : ( ) : (
<FontAwesomeIcon <FontAwesomeIcon
icon={faCheck} icon={faCheck}
className="text-md text-primary mr-2" className='text-md text-primary mr-2'
/> />
)} )}
<div <div
@@ -280,16 +281,16 @@ export default function SignupInvite() {
1 lowercase character 1 lowercase character
</div> </div>
</div> </div>
<div className="flex flex-row justify-start items-center ml-1"> <div className='flex flex-row justify-start items-center ml-1'>
{passwordErrorNumber ? ( {passwordErrorNumber ? (
<FontAwesomeIcon <FontAwesomeIcon
icon={faX} icon={faX}
className="text-md text-red mr-2.5" className='text-md text-red mr-2.5'
/> />
) : ( ) : (
<FontAwesomeIcon <FontAwesomeIcon
icon={faCheck} icon={faCheck}
className="text-md text-primary mr-2" className='text-md text-primary mr-2'
/> />
)} )}
<div <div
@@ -302,17 +303,17 @@ export default function SignupInvite() {
</div> </div>
</div> </div>
) : ( ) : (
<div className="py-2"></div> <div className='py-2'></div>
)} )}
</div> </div>
<div className="flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg"> <div className='flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg'>
<Button <Button
text="Sign Up" text='Sign Up'
onButtonPressed={() => { onButtonPressed={() => {
signupErrorCheck(); signupErrorCheck();
}} }}
loading={isLoading} loading={isLoading}
size="lg" size='lg'
/> />
</div> </div>
</div> </div>
@@ -320,38 +321,38 @@ export default function SignupInvite() {
// Step 4 of the sign up process (download the emergency kit pdf) // Step 4 of the sign up process (download the emergency kit pdf)
const step4 = ( const step4 = (
<div className="bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl"> <div className='bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl'>
<p className="text-4xl text-center font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary"> <p className='text-4xl text-center font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary'>
Save your Emergency Kit Save your Emergency Kit
</p> </p>
<div className="flex flex-col items-center justify-center w-full mt-4 md:mt-8 max-w-md text-gray-400 text-md rounded-md px-2"> <div className='flex flex-col items-center justify-center w-full mt-4 md:mt-8 max-w-md text-gray-400 text-md rounded-md px-2'>
<div> <div>
If you get locked out of your account, your Emergency Kit is the only If you get locked out of your account, your Emergency Kit is the only
way to sign in. way to sign in.
</div> </div>
<div className="mt-3"> <div className='mt-3'>
We recommend you download it and keep it somewhere safe. We recommend you download it and keep it somewhere safe.
</div> </div>
</div> </div>
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-400 rounded-md max-w-xs md:max-w-md mx-auto mt-4"> <div className='w-full p-2 flex flex-row items-center bg-white/10 text-gray-400 rounded-md max-w-xs md:max-w-md mx-auto mt-4'>
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-4 text-4xl" /> <FontAwesomeIcon icon={faWarning} className='ml-2 mr-4 text-4xl' />
It contains your Secret Key which we cannot access or recover for you if It contains your Secret Key which we cannot access or recover for you if
you lose it. you lose it.
</div> </div>
<div className="flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg"> <div className='flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg'>
<Button <Button
text="Download PDF" text='Download PDF'
onButtonPressed={async () => { onButtonPressed={async () => {
await issueBackupKey({ await issueBackupKey({
email, email,
password, password,
personalName: firstName + ' ' + lastName, personalName: firstName + ' ' + lastName,
setBackupKeyError, setBackupKeyError,
setBackupKeyIssued setBackupKeyIssued,
}); });
router.push('/noprojects/'); router.push('/noprojects/');
}} }}
size="lg" size='lg'
/> />
{/* <div {/* <div
className="text-l mt-4 text-lg text-gray-400 hover:text-gray-300 duration-200 bg-white/5 px-8 hover:bg-white/10 py-3 rounded-md cursor-pointer" className="text-l mt-4 text-lg text-gray-400 hover:text-gray-300 duration-200 bg-white/5 px-8 hover:bg-white/10 py-3 rounded-md cursor-pointer"
@@ -370,18 +371,18 @@ export default function SignupInvite() {
); );
return ( return (
<div className="bg-bunker-800 h-screen flex flex-col items-center justify-center"> <div className='bg-bunker-800 h-screen flex flex-col items-center justify-center'>
<Head> <Head>
<title>Sign Up</title> <title>Sign Up</title>
<link rel="icon" href="/infisical.ico" /> <link rel='icon' href='/infisical.ico' />
</Head> </Head>
<Link href="/"> <Link href='/'>
<div className="flex justify-center mb-2 md:mb-4 opacity-80 cursor-pointer"> <div className='flex justify-center mb-2 md:mb-4 opacity-80 cursor-pointer'>
<Image <Image
src="/images/biglogo.png" src='/images/biglogo.png'
height={90} height={90}
width={120} width={120}
alt="Infisical Wide Logo" alt='Infisical Wide Logo'
/> />
</div> </div>
</Link> </Link>
+81 -63
View File
@@ -1,17 +1,17 @@
import { useEffect, useState } from "react"; import { 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 { useTranslation } from "next-i18next"; import { useTranslation } from 'next-i18next';
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 { getTranslatedServerSideProps } from "~/utilities/withTranslateProps"; import { getTranslatedServerSideProps } from '~/utilities/withTranslateProps';
import getOrganizationUsers from '../api/organization/GetOrgUsers'; import getOrganizationUsers from '../api/organization/GetOrgUsers';
import getUser from '../api/user/getUser'; import getUser from '../api/user/getUser';
@@ -21,7 +21,7 @@ import getWorkspaceUsers from '../api/workspace/getWorkspaceUsers';
import uploadKeys from '../api/workspace/uploadKeys'; import uploadKeys from '../api/workspace/uploadKeys';
interface UserProps { interface UserProps {
firstName: string; firstName: string;
lastName: string; lastName: string;
email: string; email: string;
_id: string; _id: string;
@@ -29,7 +29,7 @@ interface UserProps {
} }
interface MembershipProps { interface MembershipProps {
user: UserProps user: UserProps;
inviteEmail: string; inviteEmail: string;
role: string; role: string;
status: string; status: string;
@@ -40,7 +40,7 @@ interface MembershipProps {
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');
@@ -84,16 +84,21 @@ export default function Users() {
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(String(router.query.id), result.invitee._id, ciphertext, nonce); uploadKeys(
String(router.query.id),
result.invitee._id,
ciphertext,
nonce
);
} }
setEmail(''); setEmail('');
setIsAddOpen(false); setIsAddOpen(false);
@@ -105,7 +110,7 @@ export default function Users() {
} }
const [userList, setUserList] = useState([]); const [userList, setUserList] = useState([]);
const [orgUserList, setOrgUserList] = useState([]); const [orgUserList, setOrgUserList] = useState<any[]>([]);
useEffect(() => { useEffect(() => {
(async () => { (async () => {
@@ -114,55 +119,65 @@ export default function Users() {
// This part quiries the current users of a project // This part quiries the current users of a project
const workspaceUsers = await getWorkspaceUsers({ const workspaceUsers = await getWorkspaceUsers({
workspaceId: String(router.query.id) workspaceId: String(router.query.id),
}); });
const tempUserList = workspaceUsers.map((membership: MembershipProps) => ({ const tempUserList = workspaceUsers.map(
key: guidGenerator(), (membership: MembershipProps) => ({
firstName: membership.user?.firstName, key: guidGenerator(),
lastName: membership.user?.lastName, firstName: membership.user?.firstName,
email: membership.user?.email == null ? membership.inviteEmail : membership.user?.email, lastName: membership.user?.lastName,
role: membership?.role, email:
status: membership?.status, membership.user?.email == null
userId: membership.user?._id, ? membership.inviteEmail
membershipId: membership._id, : membership.user?.email,
publicKey: membership.user?.publicKey role: membership?.role,
})); status: membership?.status,
userId: membership.user?._id,
membershipId: membership._id,
publicKey: membership.user?.publicKey,
})
);
setUserList(tempUserList); setUserList(tempUserList);
// This is needed to know wha users from an org (if any), we are able to add to a certain project // This is needed to know wha users from an org (if any), we are able to add to a certain project
const orgUsers = await getOrganizationUsers({ const orgUsers = await getOrganizationUsers({
orgId: String(localStorage.getItem('orgData.id')) orgId: String(localStorage.getItem('orgData.id')),
}); });
setOrgUserList(orgUsers); setOrgUserList(orgUsers);
setEmail( setEmail(
orgUsers orgUsers
?.filter((membership: MembershipProps) => membership.status == 'accepted') ?.filter(
(membership: MembershipProps) => membership.status == 'accepted'
)
.map((membership: MembershipProps) => membership.user.email) .map((membership: MembershipProps) => membership.user.email)
.filter( .filter(
(email: string) => !tempUserList?.map((user1: UserProps) => user1.email).includes(email) (email: string) =>
!tempUserList
?.map((user1: UserProps) => user1.email)
.includes(email)
)[0] )[0]
); );
})(); })();
}, []); }, []);
return userList ? ( return userList ? (
<div className="bg-bunker-800 md:h-screen flex flex-col justify-start"> <div className='bg-bunker-800 md:h-screen flex flex-col justify-start'>
<Head> <Head>
<title> <title>
{t("common:head-title", { title: t("settings-members:title") })} {t('common:head-title', { title: t('settings-members:title') })}
</title> </title>
<link rel="icon" href="/infisical.ico" /> <link rel='icon' href='/infisical.ico' />
</Head> </Head>
<NavHeader <NavHeader
pageName={t("settings-members:title")} pageName={t('settings-members:title')}
isProjectRelated={true} isProjectRelated={true}
/> />
<div className="flex flex-col justify-start items-start px-6 py-6 pb-4 text-3xl"> <div className='flex flex-col justify-start items-start px-6 py-6 pb-4 text-3xl'>
<p className="font-semibold mr-4 text-white"> <p className='font-semibold mr-4 text-white'>
{t("settings-members:title")} {t('settings-members:title')}
</p> </p>
<p className="mr-4 text-base text-gray-400"> <p className='mr-4 text-base text-gray-400'>
{t("settings-members:description")} {t('settings-members:description')}
</p> </p>
</div> </div>
<AddProjectMemberDialog <AddProjectMemberDialog
@@ -171,39 +186,42 @@ export default function Users() {
submitModal={submitAddModal} submitModal={submitAddModal}
email={email} email={email}
data={orgUserList data={orgUserList
?.filter((membership: MembershipProps) => membership.status == 'accepted') ?.filter(
(membership: MembershipProps) => membership.status == 'accepted'
)
.map((membership: MembershipProps) => membership.user.email) .map((membership: MembershipProps) => membership.user.email)
.filter( .filter(
(email) => !userList?.map((user1: UserProps) => user1.email).includes(email) (email) =>
!userList?.map((user1: UserProps) => user1.email).includes(email)
)} )}
workspaceId={workspaceId} workspaceId={workspaceId}
setEmail={setEmail} setEmail={setEmail}
/> />
{/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */} {/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */}
<div className="px-6 pb-1 w-full flex flex-row items-start min-w-6xl max-w-6xl"> <div className='px-6 pb-1 w-full flex flex-row items-start min-w-6xl max-w-6xl'>
<div className="h-10 w-full bg-white/5 mt-2 flex items-center rounded-md flex flex-row items-center"> <div className='h-10 w-full bg-white/5 mt-2 flex items-center rounded-md flex flex-row items-center'>
<FontAwesomeIcon <FontAwesomeIcon
className="bg-white/5 rounded-l-md py-3 pl-4 pr-2 text-gray-400" className='bg-white/5 rounded-l-md py-3 pl-4 pr-2 text-gray-400'
icon={faMagnifyingGlass} icon={faMagnifyingGlass}
/> />
<input <input
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={String(t("section-members:search-members"))} placeholder={String(t('section-members: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'>
<Button <Button
text={String(t("section-members:add-member"))} text={String(t('section-members:add-member'))}
onButtonPressed={openAddModal} onButtonPressed={openAddModal}
color="mineshaft" color='mineshaft'
size="md" size='md'
icon={faPlus} icon={faPlus}
/> />
</div> </div>
</div> </div>
<div className="block overflow-y-auto min-w-6xl max-w-6xl px-6"> <div className='block overflow-y-auto min-w-6xl max-w-6xl px-6'>
<UserTable <UserTable
userData={userList} userData={userList}
changeData={setUserList} changeData={setUserList}
@@ -218,13 +236,13 @@ export default function Users() {
</div> </div>
</div> </div>
) : ( ) : (
<div className="relative z-10 w-10/12 mr-auto h-full ml-2 bg-bunker-800 flex flex-col items-center justify-center"> <div className='relative z-10 w-10/12 mr-auto h-full ml-2 bg-bunker-800 flex flex-col items-center justify-center'>
<div className="absolute top-0 bg-bunker h-14 border-b border-mineshaft-700 w-full"></div> <div className='absolute top-0 bg-bunker h-14 border-b border-mineshaft-700 w-full'></div>
<Image <Image
src="/images/loading/loading.gif" src='/images/loading/loading.gif'
height={70} height={70}
width={120} width={120}
alt="loading animation" alt='loading animation'
></Image> ></Image>
</div> </div>
); );
@@ -233,7 +251,7 @@ export default function Users() {
Users.requireAuth = true; Users.requireAuth = true;
export const getServerSideProps = getTranslatedServerSideProps([ export const getServerSideProps = getTranslatedServerSideProps([
"settings", 'settings',
"settings-members", 'settings-members',
"section-members", 'section-members',
]); ]);
-40
View File
@@ -1,40 +0,0 @@
import { useEffect } from "react";
import Head from "next/head";
import { useRouter } from "next/router";
const queryString = require("query-string");
import AuthorizeIntegration from "./api/integrations/authorizeIntegration";
export default function Vercel() {
const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
const code = parsedUrl.code;
const state = parsedUrl.state
/**
* Here we forward to the default workspace if a user opens this url
*/
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(async () => {
try {
if (state === localStorage.getItem('latestCSRFToken')) {
localStorage.removeItem('latestCSRFToken');
await AuthorizeIntegration({
workspaceId: localStorage.getItem('projectData.id'),
code,
integration: "vercel"
});
router.push("/integrations/" + localStorage.getItem("projectData.id"));
}
} catch (err) {
console.error('Vercel integration error: ', err);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <div></div>;
}
Vercel.requireAuth = true;
+46
View File
@@ -0,0 +1,46 @@
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import queryString from 'query-string';
import AuthorizeIntegration from './api/integrations/authorizeIntegration';
export default function Vercel() {
const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split('?')[1]);
const code = parsedUrl.code;
const state = parsedUrl.state;
/**
* Here we forward to the default workspace if a user opens this url
*/
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
(async () => {
try {
// type check
if (!code) throw new Error('Code not found');
if (state === localStorage.getItem('latestCSRFToken')) {
localStorage.removeItem('latestCSRFToken');
await AuthorizeIntegration({
workspaceId: localStorage.getItem('projectData.id') as string,
code: code as string,
integration: 'vercel',
});
router.push(
'/integrations/' + localStorage.getItem('projectData.id')
);
}
} catch (err) {
console.error('Vercel integration error: ', err);
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <div></div>;
}
Vercel.requireAuth = true;
File diff suppressed because one or more lines are too long