Add back hashed hex for secret sharing

This commit is contained in:
Tuan Dang
2024-07-30 07:16:03 -07:00
parent 0d4164ea81
commit 7cbd254f06
16 changed files with 100 additions and 322 deletions

View File

@@ -17,13 +17,6 @@ export async function up(knex: Knex): Promise<void> {
t.timestamp("lastViewedAt").nullable(); t.timestamp("lastViewedAt").nullable();
}); });
} }
const doesHashedHexExist = await knex.schema.hasColumn(TableName.SecretSharing, "hashedHex");
if (doesHashedHexExist) {
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
t.dropColumn("hashedHex");
});
}
} }
} }

View File

@@ -12,6 +12,7 @@ export const SecretSharingSchema = z.object({
encryptedValue: z.string(), encryptedValue: z.string(),
iv: z.string(), iv: z.string(),
tag: z.string(), tag: z.string(),
hashedHex: z.string(),
expiresAt: z.date(), expiresAt: z.date(),
userId: z.string().uuid().nullable().optional(), userId: z.string().uuid().nullable().optional(),
orgId: z.string().uuid().nullable().optional(), orgId: z.string().uuid().nullable().optional(),

View File

@@ -57,6 +57,9 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
params: z.object({ params: z.object({
id: z.string().uuid() id: z.string().uuid()
}), }),
querystring: z.object({
hashedHex: z.string().min(1)
}),
response: { response: {
200: SecretSharingSchema.pick({ 200: SecretSharingSchema.pick({
encryptedValue: true, encryptedValue: true,
@@ -71,10 +74,11 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
} }
}, },
handler: async (req) => { handler: async (req) => {
const sharedSecret = await req.server.services.secretSharing.getActiveSharedSecretById( const sharedSecret = await req.server.services.secretSharing.getActiveSharedSecretById({
req.params.id, sharedSecretId: req.params.id,
req.permission?.orgId hashedHex: req.query.hashedHex,
); orgId: req.permission?.orgId
});
if (!sharedSecret) return undefined; if (!sharedSecret) return undefined;
return { return {
encryptedValue: sharedSecret.encryptedValue, encryptedValue: sharedSecret.encryptedValue,
@@ -97,6 +101,7 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
schema: { schema: {
body: z.object({ body: z.object({
encryptedValue: z.string(), encryptedValue: z.string(),
hashedHex: z.string(),
iv: z.string(), iv: z.string(),
tag: z.string(), tag: z.string(),
expiresAt: z.string(), expiresAt: z.string(),
@@ -109,13 +114,8 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
} }
}, },
handler: async (req) => { handler: async (req) => {
const { encryptedValue, iv, tag, expiresAt, expiresAfterViews } = req.body;
const sharedSecret = await req.server.services.secretSharing.createPublicSharedSecret({ const sharedSecret = await req.server.services.secretSharing.createPublicSharedSecret({
encryptedValue, ...req.body,
iv,
tag,
expiresAt,
expiresAfterViews,
accessType: SecretSharingAccessType.Anyone accessType: SecretSharingAccessType.Anyone
}); });
return { id: sharedSecret.id }; return { id: sharedSecret.id };
@@ -132,6 +132,7 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
body: z.object({ body: z.object({
name: z.string().max(50).optional(), name: z.string().max(50).optional(),
encryptedValue: z.string(), encryptedValue: z.string(),
hashedHex: z.string(),
iv: z.string(), iv: z.string(),
tag: z.string(), tag: z.string(),
expiresAt: z.string(), expiresAt: z.string(),

View File

@@ -8,6 +8,7 @@ import {
TCreatePublicSharedSecretDTO, TCreatePublicSharedSecretDTO,
TCreateSharedSecretDTO, TCreateSharedSecretDTO,
TDeleteSharedSecretDTO, TDeleteSharedSecretDTO,
TGetActiveSharedSecretByIdDTO,
TGetSharedSecretsDTO TGetSharedSecretsDTO
} from "./secret-sharing-types"; } from "./secret-sharing-types";
@@ -24,21 +25,21 @@ export const secretSharingServiceFactory = ({
secretSharingDAL, secretSharingDAL,
orgDAL orgDAL
}: TSecretSharingServiceFactoryDep) => { }: TSecretSharingServiceFactoryDep) => {
const createSharedSecret = async (createSharedSecretInput: TCreateSharedSecretDTO) => { const createSharedSecret = async ({
const { actor,
actor, actorId,
actorId, orgId,
orgId, actorAuthMethod,
actorAuthMethod, actorOrgId,
actorOrgId, encryptedValue,
encryptedValue, hashedHex,
iv, iv,
tag, tag,
name, name,
accessType, accessType,
expiresAt, expiresAt,
expiresAfterViews expiresAfterViews
} = createSharedSecretInput; }: TCreateSharedSecretDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
if (!permission) throw new UnauthorizedError({ name: "User not in org" }); if (!permission) throw new UnauthorizedError({ name: "User not in org" });
@@ -62,6 +63,7 @@ export const secretSharingServiceFactory = ({
const newSharedSecret = await secretSharingDAL.create({ const newSharedSecret = await secretSharingDAL.create({
name, name,
encryptedValue, encryptedValue,
hashedHex,
iv, iv,
tag, tag,
expiresAt: new Date(expiresAt), expiresAt: new Date(expiresAt),
@@ -74,8 +76,15 @@ export const secretSharingServiceFactory = ({
return { id: newSharedSecret.id }; return { id: newSharedSecret.id };
}; };
const createPublicSharedSecret = async (createSharedSecretInput: TCreatePublicSharedSecretDTO) => { const createPublicSharedSecret = async ({
const { encryptedValue, iv, tag, expiresAt, expiresAfterViews, accessType } = createSharedSecretInput; encryptedValue,
hashedHex,
iv,
tag,
expiresAt,
expiresAfterViews,
accessType
}: TCreatePublicSharedSecretDTO) => {
if (new Date(expiresAt) < new Date()) { if (new Date(expiresAt) < new Date()) {
throw new BadRequestError({ message: "Expiration date cannot be in the past" }); throw new BadRequestError({ message: "Expiration date cannot be in the past" });
} }
@@ -95,6 +104,7 @@ export const secretSharingServiceFactory = ({
const newSharedSecret = await secretSharingDAL.create({ const newSharedSecret = await secretSharingDAL.create({
encryptedValue, encryptedValue,
hashedHex,
iv, iv,
tag, tag,
expiresAt: new Date(expiresAt), expiresAt: new Date(expiresAt),
@@ -142,8 +152,11 @@ export const secretSharingServiceFactory = ({
}; };
}; };
const getActiveSharedSecretById = async (sharedSecretId: string, orgId?: string) => { const getActiveSharedSecretById = async ({ sharedSecretId, hashedHex, orgId }: TGetActiveSharedSecretByIdDTO) => {
const sharedSecret = await secretSharingDAL.findOne({ id: sharedSecretId }); const sharedSecret = await secretSharingDAL.findOne({
id: sharedSecretId,
hashedHex
});
if (!sharedSecret) if (!sharedSecret)
throw new NotFoundError({ throw new NotFoundError({
message: "Shared secret not found" message: "Shared secret not found"

View File

@@ -19,6 +19,7 @@ export type TSharedSecretPermission = {
export type TCreatePublicSharedSecretDTO = { export type TCreatePublicSharedSecretDTO = {
encryptedValue: string; encryptedValue: string;
hashedHex: string;
iv: string; iv: string;
tag: string; tag: string;
expiresAt: string; expiresAt: string;
@@ -26,6 +27,12 @@ export type TCreatePublicSharedSecretDTO = {
accessType: SecretSharingAccessType; accessType: SecretSharingAccessType;
}; };
export type TGetActiveSharedSecretByIdDTO = {
sharedSecretId: string;
hashedHex: string;
orgId?: string;
};
export type TCreateSharedSecretDTO = TSharedSecretPermission & TCreatePublicSharedSecretDTO; export type TCreateSharedSecretDTO = TSharedSecretPermission & TCreatePublicSharedSecretDTO;
export type TDeleteSharedSecretDTO = { export type TDeleteSharedSecretDTO = {

View File

@@ -36,12 +36,25 @@ export const useGetSharedSecrets = ({
}); });
}; };
export const useGetActiveSharedSecretById = (secretId: string) => { export const useGetActiveSharedSecretById = ({
sharedSecretId,
hashedHex
}: {
sharedSecretId: string;
hashedHex: string;
}) => {
return useQuery<TViewSharedSecretResponse, [string]>({ return useQuery<TViewSharedSecretResponse, [string]>({
enabled: Boolean(secretId), enabled: Boolean(sharedSecretId) && Boolean(hashedHex),
queryFn: async () => { queryFn: async () => {
const params = new URLSearchParams({
hashedHex
});
const { data } = await apiRequest.get<TViewSharedSecretResponse>( const { data } = await apiRequest.get<TViewSharedSecretResponse>(
`/api/v1/secret-sharing/public/${secretId}` `/api/v1/secret-sharing/public/${sharedSecretId}`,
{
params
}
); );
return { return {
encryptedValue: data.encryptedValue, encryptedValue: data.encryptedValue,

View File

@@ -16,6 +16,7 @@ export type TSharedSecret = {
export type TCreateSharedSecretRequest = { export type TCreateSharedSecretRequest = {
name?: string; name?: string;
encryptedValue: string; encryptedValue: string;
hashedHex: string;
iv: string; iv: string;
tag: string; tag: string;
expiresAt: Date; expiresAt: Date;

View File

@@ -13,7 +13,7 @@ import { secretKeys } from "@app/hooks/api/secrets/queries";
import { DecryptedSecret, SecretType } from "@app/hooks/api/secrets/types"; import { DecryptedSecret, SecretType } from "@app/hooks/api/secrets/types";
import { secretSnapshotKeys } from "@app/hooks/api/secretSnapshots/queries"; import { secretSnapshotKeys } from "@app/hooks/api/secretSnapshots/queries";
import { UserWsKeyPair, WsTag } from "@app/hooks/api/types"; import { UserWsKeyPair, WsTag } from "@app/hooks/api/types";
import { AddShareSecretModal2 } from "@app/views/ShareSecretPage/components/AddShareSecretModal2"; import { AddShareSecretModal } from "@app/views/ShareSecretPage/components/AddShareSecretModal";
import { useSelectedSecretActions, useSelectedSecrets } from "../../SecretMainPage.store"; import { useSelectedSecretActions, useSelectedSecrets } from "../../SecretMainPage.store";
import { Filter, GroupBy, SortDir } from "../../SecretMainPage.types"; import { Filter, GroupBy, SortDir } from "../../SecretMainPage.types";
@@ -404,7 +404,7 @@ export const SecretListView = ({
isOpen={popUp.createTag.isOpen} isOpen={popUp.createTag.isOpen}
onToggle={(isOpen) => handlePopUpToggle("createTag", isOpen)} onToggle={(isOpen) => handlePopUpToggle("createTag", isOpen)}
/> />
<AddShareSecretModal2 popUp={popUp} handlePopUpToggle={handlePopUpToggle} /> <AddShareSecretModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
</> </>
); );
}; };

View File

@@ -1,226 +0,0 @@
import crypto from "crypto";
import { useEffect, useRef } from "react";
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, ModalClose, Select, SelectItem } from "@app/components/v2";
import {
SecretSharingAccessType,
useCreatePublicSharedSecret,
useCreateSharedSecret
} from "@app/hooks/api/secretSharing";
const schema = yup.object({
value: yup.string().max(10000).required().label("Shared Secret Value"),
expiresAfterViews: yup.string().required().label("Expires After Views"),
expiresInValue: yup.string().min(1).required().label("Expiration Value"),
accessType: yup.string().required().label("General Access")
});
export type FormData = yup.InferType<typeof schema>;
// values in ms
const expiresInOptions = [
{ label: "5 min", value: 5 * 60 * 1000 },
{ label: "30 min", value: 30 * 60 * 1000 },
{ label: "1 hour", value: 60 * 60 * 1000 },
{ label: "1 day", value: 24 * 60 * 60 * 1000 },
{ label: "7 days", value: 7 * 24 * 60 * 60 * 1000 },
{ label: "14 days", value: 14 * 24 * 60 * 60 * 1000 },
{ label: "30 days", value: 30 * 24 * 60 * 60 * 1000 }
];
const viewLimitOptions = [
{ label: "1", value: 1 },
{ label: "Unlimited", value: -1 }
];
export const AddShareSecretForm = ({
isPublic,
inModal,
handleSubmit,
control,
isSubmitting,
setNewSharedSecret,
isInputDisabled
}: {
isPublic: boolean;
inModal: boolean;
handleSubmit: any;
control: any;
isSubmitting: boolean;
setNewSharedSecret: (value: string) => void;
isInputDisabled?: boolean;
}) => {
const isMounted = useRef(true);
useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
const publicSharedSecretCreator = useCreatePublicSharedSecret();
const privateSharedSecretCreator = useCreateSharedSecret();
const createSharedSecret = isPublic ? publicSharedSecretCreator : privateSharedSecretCreator;
const onFormSubmit = async ({
value,
expiresInValue,
expiresAfterViews,
accessType
}: FormData) => {
try {
const expiresAt = new Date(new Date().getTime() + Number(expiresInValue));
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 { id } = await createSharedSecret.mutateAsync({
encryptedValue: ciphertext,
iv,
tag,
expiresAt,
expiresAfterViews: expiresAfterViews === "-1" ? undefined : Number(expiresAfterViews),
accessType: accessType as SecretSharingAccessType
});
if (isMounted.current) {
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 (
<form
className="flex w-full max-w-7xl flex-col items-center"
onSubmit={handleSubmit(onFormSubmit)}
>
<div
className={`w-full ${
!inModal && "rounded-md border border-mineshaft-600 bg-mineshaft-800 p-6"
}`}
>
<div>
<Controller
control={control}
name="value"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Your Secret"
isError={Boolean(error)}
errorText={error?.message}
className="mb-2"
>
<textarea
disabled={isInputDisabled}
placeholder="Enter sensitive data to share via an encrypted link..."
{...field}
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>
<Controller
control={control}
name="expiresInValue"
defaultValue="3600000"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl label="Expires In" errorText={error?.message} isError={Boolean(error)}>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full"
>
{expiresInOptions.map(({ label, value }) => (
<SelectItem value={String(value || "")} key={label}>
{label}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
<Controller
control={control}
name="expiresAfterViews"
defaultValue="-1"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl label="Max Views" errorText={error?.message} isError={Boolean(error)}>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full"
>
{viewLimitOptions.map(({ label, value }) => (
<SelectItem value={String(value || "")} key={label}>
{label}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
{!isPublic && (
<Controller
control={control}
name="accessType"
defaultValue="organization"
render={({ field: { onChange, ...field } }) => (
<FormControl label="General Access">
<Select {...field} onValueChange={(e) => onChange(e)} className="w-full">
<SelectItem value="organization">People within your organization</SelectItem>
<SelectItem value="anyone">Anyone</SelectItem>
</Select>
</FormControl>
)}
/>
)}
<div className={`flex items-center space-x-4 pt-2 ${!inModal && ""}`}>
<Button className="mr-0" type="submit" isDisabled={isSubmitting} isLoading={isSubmitting}>
{inModal ? "Create" : "Create secret link"}
</Button>
{inModal && (
<ModalClose asChild>
<Button variant="plain" colorSchema="secondary">
Cancel
</Button>
</ModalClose>
)}
</div>
</div>
</form>
);
};

View File

@@ -10,7 +10,7 @@ type Props = {
) => void; ) => void;
}; };
export const AddShareSecretModal2 = ({ popUp, handlePopUpToggle }: Props) => { export const AddShareSecretModal = ({ popUp, handlePopUpToggle }: Props) => {
return ( return (
<Modal <Modal
isOpen={popUp?.createSharedSecret?.isOpen} isOpen={popUp?.createSharedSecret?.isOpen}

View File

@@ -7,7 +7,7 @@ import { Button, DeleteActionModal } from "@app/components/v2";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { useDeleteSharedSecret } from "@app/hooks/api/secretSharing"; import { useDeleteSharedSecret } from "@app/hooks/api/secretSharing";
import { AddShareSecretModal2 } from "./AddShareSecretModal2"; import { AddShareSecretModal } from "./AddShareSecretModal";
import { ShareSecretsTable } from "./ShareSecretsTable"; import { ShareSecretsTable } from "./ShareSecretsTable";
type DeleteModalData = { name: string; id: string }; type DeleteModalData = { name: string; id: string };
@@ -59,7 +59,7 @@ export const ShareSecretSection = () => {
</Button> </Button>
</div> </div>
<ShareSecretsTable handlePopUpOpen={handlePopUpOpen} /> <ShareSecretsTable handlePopUpOpen={handlePopUpOpen} />
<AddShareSecretModal2 popUp={popUp} handlePopUpToggle={handlePopUpToggle} /> <AddShareSecretModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<DeleteActionModal <DeleteActionModal
isOpen={popUp.deleteSharedSecretConfirmation.isOpen} isOpen={popUp.deleteSharedSecretConfirmation.isOpen}
title={`Delete ${ title={`Delete ${

View File

@@ -59,15 +59,18 @@ export const ShareSecretsTable = ({ handlePopUpOpen }: Props) => {
))} ))}
</TBody> </TBody>
</Table> </Table>
{!isLoading && data?.totalCount !== undefined && ( {!isLoading &&
<Pagination data?.secrets &&
count={data.totalCount} data.secrets.length >= perPage &&
page={page} data?.totalCount !== undefined && (
perPage={perPage} <Pagination
onChangePage={(newPage) => setPage(newPage)} count={data.totalCount}
onChangePerPage={(newPerPage) => setPerPage(newPerPage)} page={page}
/> perPage={perPage}
)} onChangePage={(newPage) => setPage(newPage)}
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
/>
)}
{!isLoading && !data?.secrets?.length && ( {!isLoading && !data?.secrets?.length && (
<EmptyState title="No secrets shared yet" icon={faKey} /> <EmptyState title="No secrets shared yet" icon={faKey} />
)} )}

View File

@@ -1,37 +0,0 @@
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { IconButton } from "@app/components/v2";
export const ViewAndCopySharedSecret = ({
inModal,
newSharedSecret,
isUrlCopied,
copyUrlToClipboard
}: {
inModal: boolean;
newSharedSecret: string;
isUrlCopied: boolean;
copyUrlToClipboard: () => void;
}) => {
return (
<div className={`flex w-full justify-center px-6 ${!inModal ? "mx-auto max-w-2xl" : ""}`}>
<div className={`${!inModal ? "border border-mineshaft-600 bg-mineshaft-800 rounded-md p-4" : ""}`}>
<div className="my-2 flex items-center justify-end rounded-md border border-mineshaft-500 bg-mineshaft-700 p-2 text-base text-gray-400">
<p className="mr-4 break-all">{newSharedSecret}</p>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={copyUrlToClipboard}
>
<FontAwesomeIcon icon={isUrlCopied ? faCheck : faCopy} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
Click to Copy
</span>
</IconButton>
</div>
</div>
</div>
);
};

View File

@@ -72,6 +72,7 @@ export const ShareSecretForm = ({ isPublic, value }: Props) => {
const expiresAt = new Date(new Date().getTime() + Number(expiresIn)); const expiresAt = new Date(new Date().getTime() + Number(expiresIn));
const key = crypto.randomBytes(16).toString("hex"); const key = crypto.randomBytes(16).toString("hex");
const hashedHex = crypto.createHash("sha256").update(key).digest("hex");
const { ciphertext, iv, tag } = encryptSymmetric({ const { ciphertext, iv, tag } = encryptSymmetric({
plaintext: secret, plaintext: secret,
key key
@@ -80,6 +81,7 @@ export const ShareSecretForm = ({ isPublic, value }: Props) => {
const { id } = await createSharedSecret.mutateAsync({ const { id } = await createSharedSecret.mutateAsync({
name, name,
encryptedValue: ciphertext, encryptedValue: ciphertext,
hashedHex,
iv, iv,
tag, tag,
expiresAt, expiresAt,
@@ -87,8 +89,11 @@ export const ShareSecretForm = ({ isPublic, value }: Props) => {
accessType accessType
}); });
setSecretLink(`${window.location.origin}/shared/secret/${id}?key=${encodeURIComponent(key)}`); setSecretLink(
`${window.location.origin}/shared/secret/${id}?key=${encodeURIComponent(
hashedHex
)}-${encodeURIComponent(key)}`
);
reset(); reset();
setCopyTextSecret("secret"); setCopyTextSecret("secret");

View File

@@ -11,9 +11,15 @@ import { SecretContainer, SecretErrorContainer } from "./components";
export const ViewSecretPublicPage = () => { export const ViewSecretPublicPage = () => {
const router = useRouter(); const router = useRouter();
const { id, key: urlEncodedPublicKey } = router.query; const { id, key: urlEncodedPublicKey } = router.query;
const key = decodeURIComponent(urlEncodedPublicKey as string);
const { data: secret, error } = useGetActiveSharedSecretById(id as string); const [hashedHex, key] = urlEncodedPublicKey
? urlEncodedPublicKey.toString().split("-")
: ["", ""];
const { data: secret, error } = useGetActiveSharedSecretById({
sharedSecretId: id as string,
hashedHex
});
return ( return (
<div className="flex h-screen flex-col justify-between bg-gradient-to-tr from-mineshaft-700 to-bunker-800 text-gray-200 dark:[color-scheme:dark]"> <div className="flex h-screen flex-col justify-between bg-gradient-to-tr from-mineshaft-700 to-bunker-800 text-gray-200 dark:[color-scheme:dark]">

View File

@@ -18,8 +18,6 @@ type Props = {
secretKey: string; secretKey: string;
}; };
// note: implementation currently doesn't account
// for in-org (authenticated) secret sharing
export const SecretContainer = ({ secret, secretKey: key }: Props) => { export const SecretContainer = ({ secret, secretKey: key }: Props) => {
const [isVisible, setIsVisible] = useToggle(false); const [isVisible, setIsVisible] = useToggle(false);
const [, isCopyingSecret, setCopyTextSecret] = useTimedReset<string>({ const [, isCopyingSecret, setCopyTextSecret] = useTimedReset<string>({