From a6e71c98a62db7b385010210f8c9d9ec082c90f9 Mon Sep 17 00:00:00 2001 From: ShubhamPalriwala Date: Thu, 6 Jun 2024 21:43:44 +0530 Subject: [PATCH] feat: public page has direct secret creation (no modal)) --- .../components/AddShareSecretForm.tsx | 233 +++++++++++++++ .../components/AddShareSecretModal.tsx | 265 +++--------------- .../components/ShareSecretSection.tsx | 7 +- .../components/ViewAndCopySharedSecret.tsx | 39 +++ .../ShareSecretPublicPage.tsx | 24 +- 5 files changed, 327 insertions(+), 241 deletions(-) create mode 100644 frontend/src/views/ShareSecretPage/components/AddShareSecretForm.tsx create mode 100644 frontend/src/views/ShareSecretPage/components/ViewAndCopySharedSecret.tsx diff --git a/frontend/src/views/ShareSecretPage/components/AddShareSecretForm.tsx b/frontend/src/views/ShareSecretPage/components/AddShareSecretForm.tsx new file mode 100644 index 000000000..bec16fa53 --- /dev/null +++ b/frontend/src/views/ShareSecretPage/components/AddShareSecretForm.tsx @@ -0,0 +1,233 @@ +import crypto from "crypto"; + +import { Controller } from "react-hook-form"; +import { AxiosError } from "axios"; +import * as yup from "yup"; + +import { createNotification } from "@app/components/notifications"; +import { encryptSymmetric } from "@app/components/utilities/cryptography/crypto"; +import { + Button, + FormControl, + Input, + ModalClose, + SecretInput, + Select, + SelectItem +} from "@app/components/v2"; +import { useCreatePublicSharedSecret, useCreateSharedSecret } from "@app/hooks/api/secretSharing"; + +const schema = yup.object({ + value: yup.string().max(10000).required().label("Shared Secret Value"), + expiresAfterViews: yup.number().min(1).required().label("Expires After Views"), + expiresInValue: yup.number().min(1).required().label("Expiration Value"), + expiresInUnit: yup.string().required().label("Expiration Unit") +}); + +export type FormData = yup.InferType; + +export const AddShareSecretForm = ({ + isPublic, + inModal, + handleSubmit, + control, + isSubmitting, + setNewSharedSecret +}: { + isPublic: boolean; + inModal: boolean; + handleSubmit: any; + control: any; + isSubmitting: boolean; + setNewSharedSecret: (value: string) => void; +}) => { + const publicSharedSecretCreator = useCreatePublicSharedSecret(); + const privateSharedSecretCreator = useCreateSharedSecret(); + const createSharedSecret = isPublic ? publicSharedSecretCreator : privateSharedSecretCreator; + + const expirationUnitsAndActions = [ + { + unit: "Minutes", + action: (expiresAt: Date, expiresInValue: number) => + expiresAt.setMinutes(expiresAt.getMinutes() + expiresInValue) + }, + { + unit: "Hours", + action: (expiresAt: Date, expiresInValue: number) => + expiresAt.setHours(expiresAt.getHours() + expiresInValue) + }, + { + unit: "Days", + action: (expiresAt: Date, expiresInValue: number) => + expiresAt.setDate(expiresAt.getDate() + expiresInValue) + }, + { + unit: "Weeks", + action: (expiresAt: Date, expiresInValue: number) => + expiresAt.setDate(expiresAt.getDate() + expiresInValue * 7) + } + ]; + const onFormSubmit = async ({ + value, + expiresInValue, + expiresInUnit, + expiresAfterViews + }: FormData) => { + try { + const key = crypto.randomBytes(16).toString("hex"); + const hashedHex = crypto.createHash("sha256").update(key).digest("hex"); + const { ciphertext, iv, tag } = encryptSymmetric({ + plaintext: value, + key + }); + + const expiresAt = new Date(); + const updateExpiresAt = expirationUnitsAndActions.find( + (item) => item.unit === expiresInUnit + )?.action; + if (updateExpiresAt && expiresInValue) { + updateExpiresAt(expiresAt, expiresInValue); + } + + const { id } = await createSharedSecret.mutateAsync({ + encryptedValue: ciphertext, + iv, + tag, + hashedHex, + expiresAt, + expiresAfterViews + }); + setNewSharedSecret( + `${window.location.origin}/shared/secret/${id}?key=${encodeURIComponent( + hashedHex + )}-${encodeURIComponent(key)}` + ); + + createNotification({ + text: "Successfully created a shared secret", + type: "success" + }); + } catch (err) { + console.error(err); + const axiosError = err as AxiosError; + if (axiosError?.response?.status === 401) { + createNotification({ + text: "You do not have access to create shared secrets", + type: "error" + }); + } else { + createNotification({ + text: "Failed to create a shared secret", + type: "error" + }); + } + } + }; + return ( +
+
+
+ ( + + + + )} + /> +
+
+
+ ( + + + + )} + /> +
+
+

OR

+
+
+
+
+ ( + + + + )} + /> +
+
+ ( + + + + )} + /> +
+
+
+
+
+ + {inModal && ( + + + + )} +
+
+
+ ); +}; diff --git a/frontend/src/views/ShareSecretPage/components/AddShareSecretModal.tsx b/frontend/src/views/ShareSecretPage/components/AddShareSecretModal.tsx index 6e2ebfab0..bc6c282a1 100644 --- a/frontend/src/views/ShareSecretPage/components/AddShareSecretModal.tsx +++ b/frontend/src/views/ShareSecretPage/components/AddShareSecretModal.tsx @@ -1,53 +1,14 @@ -import crypto from "crypto"; - import { useEffect, useState } from "react"; -import { Controller, useForm } from "react-hook-form"; -import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useForm } from "react-hook-form"; import { yupResolver } from "@hookform/resolvers/yup"; -import { AxiosError } from "axios"; import * as yup from "yup"; -import { createNotification } from "@app/components/notifications"; -import { encryptSymmetric } from "@app/components/utilities/cryptography/crypto"; -import { - Button, - FormControl, - IconButton, - Input, - Modal, - ModalClose, - ModalContent, - SecretInput, - Select, - SelectItem -} from "@app/components/v2"; +import { Modal, ModalContent } from "@app/components/v2"; import { useTimedReset } from "@app/hooks"; -import { useCreatePublicSharedSecret, useCreateSharedSecret } from "@app/hooks/api/secretSharing"; import { UsePopUpState } from "@app/hooks/usePopUp"; -const expirationUnitsAndActions = [ - { - unit: "Minutes", - action: (expiresAt: Date, expiresInValue: number) => - expiresAt.setMinutes(expiresAt.getMinutes() + expiresInValue) - }, - { - unit: "Hours", - action: (expiresAt: Date, expiresInValue: number) => - expiresAt.setHours(expiresAt.getHours() + expiresInValue) - }, - { - unit: "Days", - action: (expiresAt: Date, expiresInValue: number) => - expiresAt.setDate(expiresAt.getDate() + expiresInValue) - }, - { - unit: "Weeks", - action: (expiresAt: Date, expiresInValue: number) => - expiresAt.setDate(expiresAt.getDate() + expiresInValue * 7) - } -]; +import { AddShareSecretForm } from "./AddShareSecretForm"; +import { ViewAndCopySharedSecret } from "./ViewAndCopySharedSecret"; const schema = yup.object({ value: yup.string().max(10000).required().label("Shared Secret Value"), @@ -65,9 +26,10 @@ type Props = { state?: boolean ) => void; isPublic: boolean; + inModal: boolean; }; -export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic }: Props) => { +export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic, inModal }: Props) => { const { control, reset, @@ -76,11 +38,8 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic }: Prop } = useForm({ resolver: yupResolver(schema) }); - const publicSharedSecretCreator = useCreatePublicSharedSecret(); - const privateSharedSecretCreator = useCreateSharedSecret(); - const createSharedSecret = isPublic ? publicSharedSecretCreator : privateSharedSecretCreator; - const [newSharedSecret, setnewSharedSecret] = useState(""); + const [newSharedSecret, setNewSharedSecret] = useState(""); const hasSharedSecret = Boolean(newSharedSecret); const [isUrlCopied, , setIsUrlCopied] = useTimedReset({ initialState: false @@ -96,70 +55,14 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic }: Prop } }, [isUrlCopied]); - const onFormSubmit = async ({ - value, - expiresInValue, - expiresInUnit, - expiresAfterViews - }: FormData) => { - try { - const key = crypto.randomBytes(16).toString("hex"); - const hashedHex = crypto.createHash("sha256").update(key).digest("hex"); - const { ciphertext, iv, tag } = encryptSymmetric({ - plaintext: value, - key - }); - - const expiresAt = new Date(); - const updateExpiresAt = expirationUnitsAndActions.find( - (item) => item.unit === expiresInUnit - )?.action; - if (updateExpiresAt && expiresInValue) { - updateExpiresAt(expiresAt, expiresInValue); - } - - const { id } = await createSharedSecret.mutateAsync({ - encryptedValue: ciphertext, - iv, - tag, - hashedHex, - expiresAt, - expiresAfterViews - }); - setnewSharedSecret( - `${window.location.origin}/shared/secret/${id}?key=${encodeURIComponent( - hashedHex - )}-${encodeURIComponent(key)}` - ); - - createNotification({ - text: "Successfully created a shared secret", - type: "success" - }); - } catch (err) { - console.error(err); - const axiosError = err as AxiosError; - if (axiosError?.response?.status === 401) { - createNotification({ - text: "You do not have access to create shared secrets", - type: "error" - }); - } else { - createNotification({ - text: "Failed to create a shared secret", - type: "error" - }); - } - } - }; - - return ( + // eslint-disable-next-line no-nested-ternary + return inModal ? ( { handlePopUpToggle("createSharedSecret", open); reset(); - setnewSharedSecret(""); + setNewSharedSecret(""); }} > {!hasSharedSecret ? ( -
- ( - - - - )} - /> -
-
- ( - - - - )} - /> -
-
-

OR

-
-
-
-
- ( - - - - )} - /> -
-
- ( - - - - )} - /> -
-
-
-
-
- - - - -
- + ) : ( -
-

{newSharedSecret}

- - - - Click to Copy - - -
+ )}
+ ) : !hasSharedSecret ? ( + + ) : ( + ); }; diff --git a/frontend/src/views/ShareSecretPage/components/ShareSecretSection.tsx b/frontend/src/views/ShareSecretPage/components/ShareSecretSection.tsx index 41f9e55ff..a450d61f5 100644 --- a/frontend/src/views/ShareSecretPage/components/ShareSecretSection.tsx +++ b/frontend/src/views/ShareSecretPage/components/ShareSecretSection.tsx @@ -60,7 +60,12 @@ export const ShareSecretSection = () => { - + void; +}) => { + return ( +
+
+

{newSharedSecret}

+ + + + Click to Copy + + +
+
+ ); +}; diff --git a/frontend/src/views/ShareSecretPublicPage/ShareSecretPublicPage.tsx b/frontend/src/views/ShareSecretPublicPage/ShareSecretPublicPage.tsx index 45996c34f..6486fb660 100644 --- a/frontend/src/views/ShareSecretPublicPage/ShareSecretPublicPage.tsx +++ b/frontend/src/views/ShareSecretPublicPage/ShareSecretPublicPage.tsx @@ -3,11 +3,8 @@ import Head from "next/head"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/router"; -import { faPlus } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { decryptSymmetric } from "@app/components/utilities/cryptography/crypto"; -import { Button } from "@app/components/v2"; import { usePopUp, useTimedReset } from "@app/hooks"; import { useGetActiveSharedSecretByIdAndHashedHex } from "@app/hooks/api/secretSharing"; @@ -54,7 +51,7 @@ export const ShareSecretPublicPage = () => { navigator.clipboard.writeText(decryptedSecret); setIsUrlCopied(true); }; - const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp(["createSharedSecret"] as const); + const { popUp, handlePopUpToggle } = usePopUp(["createSharedSecret"] as const); return (
@@ -119,19 +116,16 @@ export const ShareSecretPublicPage = () => {

- + +
+
-

Developed by{" "}