feat(frontend): Adding accessType on secret sharing

This commit is contained in:
Alfonso Hernandez
2024-07-19 03:17:47 +02:00
parent e45585a909
commit eb82fc0d9a
6 changed files with 80 additions and 16 deletions
@@ -21,7 +21,7 @@ export const EmptyState = ({
}: Props) => ( }: Props) => (
<div <div
className={twMerge( className={twMerge(
"flex w-full flex-col items-center bg-mineshaft-800 px-2 pt-6 text-bunker-300", "flex w-full flex-col items-center bg-mineshaft-800 px-2 pt-4 text-bunker-300",
className className
)} )}
> >
@@ -2,7 +2,7 @@ import { useQuery } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request"; import { apiRequest } from "@app/config/request";
import { TSharedSecret, TViewSharedSecretResponse } from "./types"; import { SecretSharingAccessType, TSharedSecret, TViewSharedSecretResponse } from "./types";
export const useGetSharedSecrets = () => { export const useGetSharedSecrets = () => {
return useQuery({ return useQuery({
@@ -17,7 +17,7 @@ export const useGetSharedSecrets = () => {
export const useGetActiveSharedSecretByIdAndHashedHex = (id: string, hashedHex: string) => { export const useGetActiveSharedSecretByIdAndHashedHex = (id: string, hashedHex: string) => {
return useQuery<TViewSharedSecretResponse, [string]>({ return useQuery<TViewSharedSecretResponse, [string]>({
queryFn: async () => { queryFn: async () => {
if(!id || !hashedHex) return Promise.resolve({ encryptedValue: "", iv: "", tag: "" }); if(!id || !hashedHex) return Promise.resolve({ encryptedValue: "", iv: "", tag: "", accessType: SecretSharingAccessType.Organization, orgName: "" });
const { data } = await apiRequest.get<TViewSharedSecretResponse>( const { data } = await apiRequest.get<TViewSharedSecretResponse>(
`/api/v1/secret-sharing/public/${id}?hashedHex=${hashedHex}` `/api/v1/secret-sharing/public/${id}?hashedHex=${hashedHex}`
); );
@@ -25,6 +25,8 @@ export const useGetActiveSharedSecretByIdAndHashedHex = (id: string, hashedHex:
encryptedValue: data.encryptedValue, encryptedValue: data.encryptedValue,
iv: data.iv, iv: data.iv,
tag: data.tag, tag: data.tag,
accessType: data.accessType,
orgName: data.orgName
}; };
} }
}); });
@@ -13,14 +13,22 @@ export type TCreateSharedSecretRequest = {
hashedHex: string; hashedHex: string;
expiresAt: Date; expiresAt: Date;
expiresAfterViews: number; expiresAfterViews: number;
accessType: SecretSharingAccessType;
}; };
export type TViewSharedSecretResponse = { export type TViewSharedSecretResponse = {
encryptedValue: string; encryptedValue: string;
iv: string; iv: string;
tag: string; tag: string;
accessType: SecretSharingAccessType;
orgName?: string;
}; };
export type TDeleteSharedSecretRequest = { export type TDeleteSharedSecretRequest = {
sharedSecretId: string; sharedSecretId: string;
}; };
export enum SecretSharingAccessType {
Anyone = "anyone",
Organization = "organization"
}
@@ -7,13 +7,14 @@ import * as yup from "yup";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { encryptSymmetric } from "@app/components/utilities/cryptography/crypto"; import { encryptSymmetric } from "@app/components/utilities/cryptography/crypto";
import { Button, Checkbox, FormControl, Input, ModalClose, Select, SelectItem } from "@app/components/v2"; import { Button, Checkbox, FormControl, Input, ModalClose, Select, SelectItem } from "@app/components/v2";
import { useCreatePublicSharedSecret, useCreateSharedSecret } from "@app/hooks/api/secretSharing"; import { SecretSharingAccessType, useCreatePublicSharedSecret, useCreateSharedSecret } from "@app/hooks/api/secretSharing";
const schema = yup.object({ const schema = yup.object({
value: yup.string().max(10000).required().label("Shared Secret Value"), value: yup.string().max(10000).required().label("Shared Secret Value"),
expiresAfterSingleView: yup.boolean().required().label("Expires After Views"), expiresAfterSingleView: yup.boolean().required().label("Expires After Views"),
expiresInValue: yup.number().min(1).required().label("Expiration Value"), expiresInValue: yup.number().min(1).required().label("Expiration Value"),
expiresInUnit: yup.string().required().label("Expiration Unit") expiresInUnit: yup.string().required().label("Expiration Unit"),
accessType: yup.string().required().label("General Access")
}); });
export type FormData = yup.InferType<typeof schema>; export type FormData = yup.InferType<typeof schema>;
@@ -65,7 +66,8 @@ export const AddShareSecretForm = ({
value, value,
expiresInValue, expiresInValue,
expiresInUnit, expiresInUnit,
expiresAfterSingleView expiresAfterSingleView,
accessType
}: FormData) => { }: FormData) => {
try { try {
const key = crypto.randomBytes(16).toString("hex"); const key = crypto.randomBytes(16).toString("hex");
@@ -89,7 +91,8 @@ export const AddShareSecretForm = ({
tag, tag,
hashedHex, hashedHex,
expiresAt, expiresAt,
expiresAfterViews: expiresAfterSingleView ? 1 : 1000 expiresAfterViews: expiresAfterSingleView ? 1 : 1000,
accessType: accessType as SecretSharingAccessType
}); });
setNewSharedSecret( setNewSharedSecret(
`${window.location.origin}/shared/secret/${id}?key=${encodeURIComponent( `${window.location.origin}/shared/secret/${id}?key=${encodeURIComponent(
@@ -143,7 +146,7 @@ export const AddShareSecretForm = ({
)} )}
/> />
</div> </div>
<div className="flex w-full flex-col md:flex-row justify-start"> <div className="flex w-full flex-col md:flex-row justify-strech">
<div className="flex justify-start"> <div className="flex justify-start">
<div className="flex justify-start"> <div className="flex justify-start">
<div className="flex w-full justify-center pr-2"> <div className="flex w-full justify-center pr-2">
@@ -188,8 +191,8 @@ export const AddShareSecretForm = ({
</div> </div>
</div> </div>
</div> </div>
<div className="sm:w-1/7 mx-auto items-center justify-center px-6 hidden md:flex"> <div className="sm:w-1/7 mx-auto items-center justify-center hidden md:flex">
<p className="px-4 mt-2 text-sm text-gray-400">AND</p> <p className="mt-2 text-sm text-gray-400">AND</p>
</div> </div>
<div className="items-center pb-4 md:pb-0 md:pt-2 flex"> <div className="items-center pb-4 md:pb-0 md:pt-2 flex">
<Controller <Controller
@@ -227,7 +230,23 @@ export const AddShareSecretForm = ({
</div> </div>
</div> </div>
</div> </div>
<div className={`flex items-center ${!inModal && "justify-start pt-2"}`}> <Controller
control={control}
name="accessType"
defaultValue="organization"
render={({ field: { onChange, ...field } }) => (
<FormControl label="General Access">
<Select
{...field}
onValueChange={(e) => onChange(e)}
>
<SelectItem value="organization">People within your organization</SelectItem>
<SelectItem value="anyone">Anyone</SelectItem>
</Select>
</FormControl>
)}
/>
<div className={`flex items-center space-x-4 mt-6 ${!inModal && "justify-start pt-2"}`}>
<Button className="mr-0" type="submit" isDisabled={isSubmitting} isLoading={isSubmitting}> <Button className="mr-0" type="submit" isDisabled={isSubmitting} isLoading={isSubmitting}>
{inModal ? "Create" : "Share Secret"} {inModal ? "Create" : "Share Secret"}
</Button> </Button>
@@ -20,12 +20,15 @@ export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean
const [hashedHex, key] = urlEncodedPublicKey const [hashedHex, key] = urlEncodedPublicKey
? urlEncodedPublicKey.toString().split("-") ? urlEncodedPublicKey.toString().split("-")
: ["", ""]; : ["", ""];
const publicKey = decodeURIComponent(urlEncodedPublicKey as string); const publicKey = decodeURIComponent(urlEncodedPublicKey as string);
const { isLoading, data } = useGetActiveSharedSecretByIdAndHashedHex( const { isLoading, data } = useGetActiveSharedSecretByIdAndHashedHex(
id as string, id as string,
hashedHex as string hashedHex as string
); );
console.log(data);
const accessType = data?.accessType;
const orgName = data?.orgName;
const decryptedSecret = useMemo(() => { const decryptedSecret = useMemo(() => {
if (data && data.encryptedValue && publicKey) { if (data && data.encryptedValue && publicKey) {
@@ -87,6 +90,8 @@ export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean
decryptedSecret={decryptedSecret} decryptedSecret={decryptedSecret}
isUrlCopied={isUrlCopied} isUrlCopied={isUrlCopied}
copyUrlToClipboard={copyUrlToClipboard} copyUrlToClipboard={copyUrlToClipboard}
accessType={accessType}
orgName={orgName}
/> />
)} )}
</div> </div>
@@ -1,14 +1,17 @@
import { faCheck, faCopy, faEye, faEyeSlash, faKey } from "@fortawesome/free-solid-svg-icons"; import { faArrowRight, faCheck, faCopy, faEye, faEyeSlash, faKey } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { EmptyState, IconButton, Td, Tr } from "@app/components/v2"; import { Button, EmptyState, IconButton, Td, Tr } from "@app/components/v2";
import { useToggle } from "@app/hooks"; import { useToggle } from "@app/hooks";
import { SecretSharingAccessType } from "@app/hooks/api/secretSharing/types";
type Props = { type Props = {
isLoading: boolean; isLoading: boolean;
decryptedSecret: string; decryptedSecret: string;
isUrlCopied: boolean; isUrlCopied: boolean;
copyUrlToClipboard: () => void; copyUrlToClipboard: () => void;
accessType?: SecretSharingAccessType;
orgName?: string;
}; };
const replaceContentWithDot = (str: string) => { const replaceContentWithDot = (str: string) => {
@@ -24,20 +27,47 @@ export const SecretTable = ({
isLoading, isLoading,
decryptedSecret, decryptedSecret,
isUrlCopied, isUrlCopied,
copyUrlToClipboard copyUrlToClipboard,
accessType,
orgName
}: Props) => { }: Props) => {
const [isVisible, setIsVisible] = useToggle(false); const [isVisible, setIsVisible] = useToggle(false);
const title = (<p>You need to be logged into <strong>{orgName}</strong> to access this secret</p>);
return ( return (
<div className="flex w-full items-center justify-center rounded-md border border-solid border-mineshaft-700 bg-mineshaft-800 p-2"> <div className="flex w-full items-center justify-center rounded-md border border-solid border-mineshaft-700 bg-mineshaft-800 p-2">
{isLoading && <div className="bg-mineshaft-800 text-center text-bunker-400">Loading...</div>} {isLoading && <div className="bg-mineshaft-800 text-center text-bunker-400">Loading...</div>}
{!isLoading && !decryptedSecret && ( {!isLoading && !decryptedSecret && accessType !== SecretSharingAccessType.Organization && (
<Tr> <Tr>
<Td colSpan={4} className="bg-mineshaft-800 text-center text-bunker-400"> <Td colSpan={4} className="bg-mineshaft-800 text-center text-bunker-400">
<EmptyState title="Secret has either expired or does not exist!" icon={faKey} /> <EmptyState title="Secret has either expired or does not exist!" icon={faKey} />
</Td> </Td>
</Tr> </Tr>
)} )}
{!isLoading && !decryptedSecret && accessType === SecretSharingAccessType.Organization && (
<Tr>
<Td colSpan={4} className="bg-mineshaft-800 text-center text-bunker-4000">
<EmptyState title={title} icon={faKey}>
<div className="flex flex-1 flex-col items-center justify-center pt-6">
<a
href="/login"
target="_blank"
rel="noopener noreferrer"
>
<Button
colorSchema="primary"
size="sm"
onClick={() => {}}
rightIcon={<FontAwesomeIcon icon={faArrowRight} className="ml-2" />}
>
Login to view this secret
</Button>
</a>
</div>
</EmptyState>
</Td>
</Tr>
)}
{!isLoading && decryptedSecret && ( {!isLoading && decryptedSecret && (
<div className="dark relative flex h-full w-full items-center overflow-y-auto rounded-md border border-mineshaft-700 bg-mineshaft-900 p-2 pr-2 md:p-3"> <div className="dark relative flex h-full w-full items-center overflow-y-auto rounded-md border border-mineshaft-700 bg-mineshaft-900 p-2 pr-2 md:p-3">
<div <div