Merge pull request #2021 from Infisical/feat/add-option-to-share-secrets-directly

feat: added option to share secret directly from main page
This commit is contained in:
Maidul Islam
2024-06-25 15:55:04 -04:00
committed by GitHub
6 changed files with 82 additions and 25 deletions

View File

@@ -7,6 +7,7 @@ import {
faCircleDot,
faClock,
faPlus,
faShare,
faTag
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -57,6 +58,7 @@ type Props = {
) => Promise<void>;
tags: WsTag[];
onCreateTag: () => void;
handleSecretShare: (value: string) => void;
};
export const SecretDetailSidebar = ({
@@ -69,7 +71,8 @@ export const SecretDetailSidebar = ({
tags,
onCreateTag,
environment,
secretPath
secretPath,
handleSecretShare
}: Props) => {
const {
register,
@@ -381,7 +384,7 @@ export const SecretDetailSidebar = ({
rows={5}
/>
</FormControl>
<div className="my-2 mb-6 border-b border-mineshaft-600 pb-4">
<div className="my-2 mb-4 border-b border-mineshaft-600 pb-4">
<Controller
control={control}
name="skipMultilineEncoding"
@@ -412,7 +415,17 @@ export const SecretDetailSidebar = ({
)}
/>
</div>
<div className="dark mb-4 flex-grow text-sm text-bunker-300">
<div className="ml-1 flex items-center space-x-2">
<Button
className="px-2 py-1"
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faShare} />}
onClick={() => handleSecretShare(secret.valueOverride ?? secret.value)}
>
Share Secret
</Button>
</div>
<div className="dark mt-4 mb-4 flex-grow text-sm text-bunker-300">
<div className="mb-2">Version History</div>
<div className="flex h-48 flex-col space-y-2 overflow-y-auto overflow-x-hidden rounded-md border border-mineshaft-600 bg-bunker-800 p-2 dark:[color-scheme:dark]">
{secretVersion?.map(({ createdAt, value, id }, i) => (

View File

@@ -61,6 +61,7 @@ type Props = {
onCreateTag: () => void;
environment: string;
secretPath: string;
handleSecretShare: () => void;
};
export const SecretItem = memo(
@@ -75,7 +76,8 @@ export const SecretItem = memo(
onCreateTag,
onToggleSecretSelect,
environment,
secretPath
secretPath,
handleSecretShare
}: Props) => {
const { currentWorkspace } = useWorkspace();
const { permission } = useProjectPermission();
@@ -420,8 +422,9 @@ export const SecretItem = memo(
<Tooltip
content={
secretReminderRepeatDays && secretReminderRepeatDays > 0
? `Every ${secretReminderRepeatDays} day${Number(secretReminderRepeatDays) > 1 ? "s" : ""
}
? `Every ${secretReminderRepeatDays} day${
Number(secretReminderRepeatDays) > 1 ? "s" : ""
}
`
: "Reminder"
}
@@ -461,6 +464,20 @@ export const SecretItem = memo(
</PopoverTrigger>
)}
</ProjectPermissionCan>
<IconButton
className="w-0 overflow-hidden p-0 group-hover:mr-2 group-hover:w-5 data-[state=open]:w-6"
variant="plain"
size="md"
ariaLabel="share-secret"
onClick={handleSecretShare}
>
<Tooltip content="Share Secret">
<FontAwesomeSymbol
className="h-3.5 w-3.5"
symbolName={FontAwesomeSpriteName.ShareSecret}
/>
</Tooltip>
</IconButton>
<PopoverContent
className="w-auto border border-mineshaft-600 bg-mineshaft-800 p-2 drop-shadow-2xl"
sticky="always"

View File

@@ -13,6 +13,7 @@ import { secretKeys } from "@app/hooks/api/secrets/queries";
import { DecryptedSecret, SecretType } from "@app/hooks/api/secrets/types";
import { secretSnapshotKeys } from "@app/hooks/api/secretSnapshots/queries";
import { UserWsKeyPair, WsTag } from "@app/hooks/api/types";
import { AddShareSecretModal } from "@app/views/ShareSecretPage/components/AddShareSecretModal";
import { useSelectedSecretActions, useSelectedSecrets } from "../../SecretMainPage.store";
import { Filter, GroupBy, SortDir } from "../../SecretMainPage.types";
@@ -95,7 +96,8 @@ export const SecretListView = ({
const { popUp, handlePopUpToggle, handlePopUpOpen, handlePopUpClose } = usePopUp([
"deleteSecret",
"secretDetail",
"createTag"
"createTag",
"createSharedSecret"
] as const);
// strip of side effect queries
@@ -365,6 +367,11 @@ export const SecretListView = ({
onDeleteSecret={onDeleteSecret}
onDetailViewSecret={onDetailViewSecret}
onCreateTag={onCreateTag}
handleSecretShare={() =>
handlePopUpOpen("createSharedSecret", {
value: secret.valueOverride ?? secret.value
})
}
/>
))}
</div>
@@ -391,11 +398,18 @@ export const SecretListView = ({
onSaveSecret={handleSaveSecret}
tags={wsTags}
onCreateTag={() => handlePopUpOpen("createTag")}
handleSecretShare={(value: string) => handlePopUpOpen("createSharedSecret", { value })}
/>
<CreateTagModal
isOpen={popUp.createTag.isOpen}
onToggle={(isOpen) => handlePopUpToggle("createTag", isOpen)}
/>
<AddShareSecretModal
popUp={popUp}
handlePopUpToggle={handlePopUpToggle}
isPublic={false}
inModal
/>
</>
);
};

View File

@@ -10,6 +10,7 @@ import {
faCopy,
faEllipsis,
faKey,
faShare,
faTags
} from "@fortawesome/free-solid-svg-icons";
import { z } from "zod";
@@ -66,7 +67,8 @@ export enum FontAwesomeSpriteName {
Override = "secret-override",
Close = "close",
CheckedCircle = "check-circle",
ReplicatedSecretKey = "secret-replicated"
ReplicatedSecretKey = "secret-replicated",
ShareSecret = "share-secret"
}
// this is an optimization technique
@@ -82,5 +84,6 @@ export const FontAwesomeSpriteSymbols = [
{ icon: faCodeBranch, symbol: FontAwesomeSpriteName.Override },
{ icon: faClose, symbol: FontAwesomeSpriteName.Close },
{ icon: faCheckCircle, symbol: FontAwesomeSpriteName.CheckedCircle },
{ icon: faClone, symbol: FontAwesomeSpriteName.ReplicatedSecretKey }
{ icon: faClone, symbol: FontAwesomeSpriteName.ReplicatedSecretKey },
{ icon: faShare, symbol: FontAwesomeSpriteName.ShareSecret }
];

View File

@@ -6,14 +6,7 @@ import * as yup from "yup";
import { createNotification } from "@app/components/notifications";
import { encryptSymmetric } from "@app/components/utilities/cryptography/crypto";
import {
Button,
FormControl,
Input,
ModalClose,
Select,
SelectItem
} from "@app/components/v2";
import { Button, FormControl, Input, ModalClose, Select, SelectItem } from "@app/components/v2";
import { useCreatePublicSharedSecret, useCreateSharedSecret } from "@app/hooks/api/secretSharing";
const schema = yup.object({
@@ -31,7 +24,8 @@ export const AddShareSecretForm = ({
handleSubmit,
control,
isSubmitting,
setNewSharedSecret
setNewSharedSecret,
isInputDisabled
}: {
isPublic: boolean;
inModal: boolean;
@@ -39,6 +33,7 @@ export const AddShareSecretForm = ({
control: any;
isSubmitting: boolean;
setNewSharedSecret: (value: string) => void;
isInputDisabled?: boolean;
}) => {
const publicSharedSecretCreator = useCreatePublicSharedSecret();
const privateSharedSecretCreator = useCreateSharedSecret();
@@ -124,12 +119,13 @@ export const AddShareSecretForm = ({
};
return (
<form className="flex w-full flex-col items-center" onSubmit={handleSubmit(onFormSubmit)}>
<div className={`${!inModal && "border border-mineshaft-600 bg-mineshaft-800 rounded-md p-6"}`}>
<div
className={`${!inModal && "rounded-md border border-mineshaft-600 bg-mineshaft-800 p-6"}`}
>
<div className="mb-4">
<Controller
control={control}
name="value"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Shared Secret"
@@ -137,16 +133,17 @@ export const AddShareSecretForm = ({
errorText={error?.message}
>
<textarea
disabled={isInputDisabled}
placeholder="Enter sensitive data to share via an encrypted link..."
{...field}
className="py-1.5 w-full h-40 placeholder:text-mineshaft-400 rounded-md transition-all group-hover:mr-2 text-bunker-300 hover:border-primary-400/30 focus:border-primary-400/50 outline-none border border-mineshaft-600 bg-mineshaft-900 px-2 min-h-[70px]"
className="h-40 min-h-[70px] w-full rounded-md border border-mineshaft-600 bg-mineshaft-900 py-1.5 px-2 text-bunker-300 outline-none transition-all placeholder:text-mineshaft-400 hover:border-primary-400/30 focus:border-primary-400/50 group-hover:mr-2"
/>
</FormControl>
)}
/>
</div>
<div className="flex w-full flex-row justify-center">
<div className="hidden sm:block sm:w-2/6 flex">
<div className="flex hidden sm:block sm:w-2/6">
<Controller
control={control}
name="expiresAfterViews"
@@ -163,12 +160,12 @@ export const AddShareSecretForm = ({
)}
/>
</div>
<div className="hidden sm:flex sm:w-1/7 items-center justify-center px-2 mx-auto">
<div className="sm:w-1/7 mx-auto hidden items-center justify-center px-2 sm:flex">
<p className="px-4 text-sm text-gray-400">OR</p>
</div>
<div className="w-full sm:w-3/6 flex justify-end">
<div className="flex w-full justify-end sm:w-3/6">
<div className="flex justify-start">
<div className="flex w-full pr-2 justify-center">
<div className="flex w-full justify-center pr-2">
<Controller
control={control}
name="expiresInValue"

View File

@@ -34,6 +34,7 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic, inModa
control,
reset,
handleSubmit,
setValue,
formState: { isSubmitting }
} = useForm<FormData>({
resolver: yupResolver(schema)
@@ -45,6 +46,8 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic, inModa
initialState: false
});
const [isSecretInputDisabled, setIsSecretInputDisabled] = useState(false);
const copyUrlToClipboard = () => {
navigator.clipboard.writeText(newSharedSecret);
setIsUrlCopied(true);
@@ -55,6 +58,13 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic, inModa
}
}, [isUrlCopied]);
useEffect(() => {
if (popUp.createSharedSecret.data) {
setValue("value", (popUp.createSharedSecret.data as { value: string }).value);
setIsSecretInputDisabled(true);
}
}, [popUp.createSharedSecret.data]);
// eslint-disable-next-line no-nested-ternary
return inModal ? (
<Modal
@@ -63,6 +73,7 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic, inModa
handlePopUpToggle("createSharedSecret", open);
reset();
setNewSharedSecret("");
setIsSecretInputDisabled(false);
}}
>
<ModalContent
@@ -77,6 +88,7 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic, inModa
handleSubmit={handleSubmit}
isSubmitting={isSubmitting}
setNewSharedSecret={setNewSharedSecret}
isInputDisabled={isSecretInputDisabled}
/>
) : (
<ViewAndCopySharedSecret
@@ -96,6 +108,7 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic, inModa
handleSubmit={handleSubmit}
isSubmitting={isSubmitting}
setNewSharedSecret={setNewSharedSecret}
isInputDisabled={isSecretInputDisabled}
/>
) : (
<ViewAndCopySharedSecret