mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-16 13:56:43 +00:00
feat: secret sharing
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { openSignedAssymmetric } from "@app/components/utilities/cryptography/crypto";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import { useGetActiveSharedSecretById } from "@app/hooks/api/secretSharing";
|
||||
|
||||
import { DragonMainImage, SecretTable } from "./components";
|
||||
|
||||
export const ShareSecretPublicPage = () => {
|
||||
const router = useRouter();
|
||||
const { id, key: urlEncodedPublicKey } = router.query;
|
||||
|
||||
const publicKey = decodeURIComponent(urlEncodedPublicKey as string);
|
||||
useEffect(() => {
|
||||
if (!id || !publicKey) {
|
||||
router.push("/404");
|
||||
}
|
||||
}, [id, publicKey]);
|
||||
|
||||
const { isLoading, data } = useGetActiveSharedSecretById(id as string);
|
||||
const decryptedSecret = useMemo(() => {
|
||||
if (data && data.signedValue && publicKey) {
|
||||
const res = openSignedAssymmetric({
|
||||
signedMessage: data.signedValue,
|
||||
publicKey: publicKey as string
|
||||
});
|
||||
return res;
|
||||
}
|
||||
return "";
|
||||
}, [data, publicKey]);
|
||||
|
||||
const [timeLeft, setTimeLeft] = useState("");
|
||||
const [isUrlCopied, setIsUrlCopied] = useToggle(false);
|
||||
|
||||
useEffect(() => {
|
||||
const updateTimer = () => {
|
||||
if (data && data.expiresAt) {
|
||||
const expiryDate = new Date(data.expiresAt).getTime();
|
||||
const now = new Date().getTime();
|
||||
const distance = expiryDate - now;
|
||||
|
||||
if (distance < 0) {
|
||||
setTimeLeft("Expired");
|
||||
} else {
|
||||
const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
||||
const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
|
||||
const seconds = Math.floor((distance % (1000 * 60)) / 1000);
|
||||
setTimeLeft(`${hours}h ${minutes}m ${seconds}s`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const timer = setInterval(updateTimer, 1000);
|
||||
return () => clearInterval(timer);
|
||||
}, [data?.expiresAt]);
|
||||
|
||||
useEffect(() => {
|
||||
let timer: NodeJS.Timeout;
|
||||
if (isUrlCopied) {
|
||||
timer = setTimeout(() => setIsUrlCopied.off(), 2000);
|
||||
}
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [isUrlCopied]);
|
||||
|
||||
const copyUrlToClipboard = () => {
|
||||
navigator.clipboard.writeText(decryptedSecret as string);
|
||||
setIsUrlCopied.on();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col justify-between bg-bunker-800 text-gray-200 md:h-screen">
|
||||
<Head>
|
||||
<title>Secret Shared | Infisical</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
|
||||
<div className="my-4 flex justify-center md:my-8">
|
||||
<Image src="/images/biglogo.png" height={180} width={240} alt="Infisical logo" />
|
||||
</div>
|
||||
<p className="mb-6 px-8 text-center text-xl md:px-0 md:text-3xl">
|
||||
You’ve been shared a secret securely with Infisical.
|
||||
</p>
|
||||
<div className="flex min-h-screen w-full flex-col md:flex-row">
|
||||
<DragonMainImage />
|
||||
<div className="m-4 flex flex-1 flex-col items-center justify-start md:m-0">
|
||||
<p className="mt-8 mb-2 text-xl font-semibold text-mineshaft-100 md:mt-20">
|
||||
Secret Details
|
||||
</p>
|
||||
<div className="mb-16 rounded-lg border border-mineshaft-600 bg-mineshaft-900 md:p-8">
|
||||
<SecretTable
|
||||
isLoading={isLoading}
|
||||
sharedSecret={data}
|
||||
decryptedSecret={decryptedSecret}
|
||||
timeLeft={timeLeft}
|
||||
isUrlCopied={isUrlCopied}
|
||||
copyUrlToClipboard={copyUrlToClipboard}
|
||||
/>
|
||||
</div>
|
||||
<Link href="/">
|
||||
<a className="mt-4 cursor-pointer rounded-md bg-mineshaft-500 py-2 px-4 text-lg font-semibold duration-200 hover:bg-primary hover:text-black">
|
||||
Check Infisical out now!
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import Image from "next/image";
|
||||
|
||||
export const DragonMainImage = () => {
|
||||
return (
|
||||
<div className="hidden flex-1 flex-col items-center justify-center md:block md:items-start md:p-4">
|
||||
<Image
|
||||
src="/images/dragon-book.svg"
|
||||
height={1000}
|
||||
width={1413}
|
||||
alt="Infisical Dragon - Came to send you a secret!"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
import { faCheck, faCopy, faKey } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import {
|
||||
EmptyState,
|
||||
IconButton,
|
||||
Table,
|
||||
TableContainer,
|
||||
TBody,
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { TViewSharedSecretResponse } from "@app/hooks/api/secretSharing";
|
||||
|
||||
type Props = {
|
||||
isLoading: boolean;
|
||||
sharedSecret?: TViewSharedSecretResponse;
|
||||
decryptedSecret: string;
|
||||
timeLeft: string;
|
||||
isUrlCopied: boolean;
|
||||
copyUrlToClipboard: () => void;
|
||||
};
|
||||
|
||||
export const SecretTable = ({
|
||||
isLoading,
|
||||
sharedSecret,
|
||||
decryptedSecret,
|
||||
timeLeft,
|
||||
isUrlCopied,
|
||||
copyUrlToClipboard
|
||||
}: Props) => {
|
||||
return (
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th>Name</Th>
|
||||
<Th>Value</Th>
|
||||
<Th>Valid Until</Th>
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{!isLoading && sharedSecret && decryptedSecret && (
|
||||
<Tr key={sharedSecret.name}>
|
||||
<Td>{sharedSecret.name}</Td>
|
||||
<Td>
|
||||
<div className="flex items-center md:space-x-2">
|
||||
<div className="max-w-[20rem] flex-1 break-words">{decryptedSecret}</div>
|
||||
<IconButton
|
||||
ariaLabel="copy to clipboard"
|
||||
onClick={copyUrlToClipboard}
|
||||
className="rounded p-2 hover:bg-gray-700"
|
||||
size="xs"
|
||||
>
|
||||
<FontAwesomeIcon icon={isUrlCopied ? faCheck : faCopy} />
|
||||
</IconButton>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>{timeLeft}</Td>
|
||||
</Tr>
|
||||
)}
|
||||
{isLoading && (
|
||||
<Tr>
|
||||
<Td colSpan={4} className="bg-mineshaft-800 text-center text-bunker-400">
|
||||
Loading...
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
{!isLoading && !sharedSecret && (
|
||||
<Tr>
|
||||
<Td colSpan={4} className="bg-mineshaft-800 text-center text-bunker-400">
|
||||
<EmptyState title="No such secret is shared yet!" icon={faKey} />
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
{!isLoading && sharedSecret && !decryptedSecret && (
|
||||
<Tr>
|
||||
<Td colSpan={4} className="bg-mineshaft-800 text-center text-bunker-400">
|
||||
<EmptyState title="Invalid URL to fetch the Secret!" icon={faKey} />
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
</TBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export { DragonMainImage } from "./MainImage";
|
||||
export { SecretTable } from "./SecretTable";
|
||||
@@ -0,0 +1 @@
|
||||
export { ShareSecretPublicPage } from "./ShareSecretPublicPage";
|
||||
Reference in New Issue
Block a user