mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-04 15:26:20 +00:00
feat: hide expiry condition to end user & fix decrement condition in knex
This commit is contained in:
@@ -128,7 +128,7 @@ export const ormify = <DbOps extends object, Tname extends keyof Tables>(db: Kne
|
|||||||
}
|
}
|
||||||
if ($decr) {
|
if ($decr) {
|
||||||
Object.entries($decr).forEach(([incrementField, incrementValue]) => {
|
Object.entries($decr).forEach(([incrementField, incrementValue]) => {
|
||||||
void query.increment(incrementField, incrementValue);
|
void query.decrement(incrementField, incrementValue);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const [docs] = await query;
|
const [docs] = await query;
|
||||||
|
|||||||
@@ -62,9 +62,7 @@ export const secretSharingServiceFactory = ({
|
|||||||
await secretSharingDAL.deleteById(sharedSecretId);
|
await secretSharingDAL.deleteById(sharedSecretId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await secretSharingDAL.updateById(sharedSecretId, {
|
await secretSharingDAL.updateById(sharedSecretId, { $decr: { expiresAfterViews: 1 } });
|
||||||
expiresAfterViews: sharedSecret.expiresAfterViews - 1
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return sharedSecret;
|
return sharedSecret;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ export const useGetActiveSharedSecretByIdAndHashedHex = (id: string, hashedHex:
|
|||||||
encryptedValue: data.encryptedValue,
|
encryptedValue: data.encryptedValue,
|
||||||
iv: data.iv,
|
iv: data.iv,
|
||||||
tag: data.tag,
|
tag: data.tag,
|
||||||
expiresAt: data.expiresAt,
|
|
||||||
expiresAfterViews: data.expiresAfterViews
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ export type TViewSharedSecretResponse = {
|
|||||||
encryptedValue: string;
|
encryptedValue: string;
|
||||||
iv: string;
|
iv: string;
|
||||||
tag: string;
|
tag: string;
|
||||||
expiresAt?: Date;
|
|
||||||
expiresAfterViews?: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TDeleteSharedSecretRequest = {
|
export type TDeleteSharedSecretRequest = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
@@ -39,45 +39,10 @@ export const ShareSecretPublicPage = () => {
|
|||||||
return "";
|
return "";
|
||||||
}, [data, publicKey]);
|
}, [data, publicKey]);
|
||||||
|
|
||||||
const [timeLeft, setTimeLeft] = useState("");
|
|
||||||
const [isUrlCopied, , setIsUrlCopied] = useTimedReset<boolean>({
|
const [isUrlCopied, , setIsUrlCopied] = useTimedReset<boolean>({
|
||||||
initialState: false
|
initialState: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const millisecondsPerDay = 1000 * 60 * 60 * 24;
|
|
||||||
const millisecondsPerHour = 1000 * 60 * 60;
|
|
||||||
const millisecondsPerMinute = 1000 * 60;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const updateTimer = () => {
|
|
||||||
if (data) {
|
|
||||||
if (data.expiresAt) {
|
|
||||||
const expirationTime = new Date(data.expiresAt).getTime();
|
|
||||||
const currentTime = new Date().getTime();
|
|
||||||
const timeDifference = expirationTime - currentTime;
|
|
||||||
|
|
||||||
if (timeDifference < 0) {
|
|
||||||
setTimeLeft("Expired");
|
|
||||||
} else {
|
|
||||||
const hoursRemaining = Math.floor(
|
|
||||||
(timeDifference % millisecondsPerDay) / millisecondsPerHour
|
|
||||||
);
|
|
||||||
const minutesRemaining = Math.floor(
|
|
||||||
(timeDifference % millisecondsPerHour) / millisecondsPerMinute
|
|
||||||
);
|
|
||||||
const secondsRemaining = Math.floor((timeDifference % millisecondsPerMinute) / 1000);
|
|
||||||
setTimeLeft(`${hoursRemaining}h ${minutesRemaining}m ${secondsRemaining}s`);
|
|
||||||
}
|
|
||||||
} else if (data.expiresAfterViews) {
|
|
||||||
setTimeLeft(`${data.expiresAfterViews - 1} more views`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const timer = setInterval(updateTimer, 1000);
|
|
||||||
return () => clearInterval(timer);
|
|
||||||
}, [data?.expiresAt]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isUrlCopied) {
|
if (isUrlCopied) {
|
||||||
setTimeout(() => setIsUrlCopied(false), 2000);
|
setTimeout(() => setIsUrlCopied(false), 2000);
|
||||||
@@ -106,14 +71,12 @@ export const ShareSecretPublicPage = () => {
|
|||||||
<DragonMainImage />
|
<DragonMainImage />
|
||||||
<div className="m-4 flex flex-1 flex-col items-center justify-start md:m-0">
|
<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">
|
<p className="mt-8 mb-2 text-xl font-semibold text-mineshaft-100 md:mt-20">
|
||||||
Secret Details
|
Shared Secret
|
||||||
</p>
|
</p>
|
||||||
<div className="mb-16 rounded-lg border border-mineshaft-600 bg-mineshaft-900 md:p-8">
|
<div className="mb-4 rounded-lg md:p-2">
|
||||||
<SecretTable
|
<SecretTable
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
sharedSecret={data}
|
|
||||||
decryptedSecret={decryptedSecret}
|
decryptedSecret={decryptedSecret}
|
||||||
timeLeft={timeLeft}
|
|
||||||
isUrlCopied={isUrlCopied}
|
isUrlCopied={isUrlCopied}
|
||||||
copyUrlToClipboard={copyUrlToClipboard}
|
copyUrlToClipboard={copyUrlToClipboard}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,90 +1,44 @@
|
|||||||
import { faCheck, faCopy, faKey } from "@fortawesome/free-solid-svg-icons";
|
import { faCheck, faCopy, faKey } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import {
|
import { EmptyState, IconButton, SecretInput, Td, Tr } from "@app/components/v2";
|
||||||
EmptyState,
|
|
||||||
IconButton,
|
|
||||||
SecretInput,
|
|
||||||
Table,
|
|
||||||
TableContainer,
|
|
||||||
TBody,
|
|
||||||
Td,
|
|
||||||
Th,
|
|
||||||
THead,
|
|
||||||
Tr
|
|
||||||
} from "@app/components/v2";
|
|
||||||
import { TViewSharedSecretResponse } from "@app/hooks/api/secretSharing";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
sharedSecret?: TViewSharedSecretResponse;
|
|
||||||
decryptedSecret: string;
|
decryptedSecret: string;
|
||||||
timeLeft: string;
|
|
||||||
isUrlCopied: boolean;
|
isUrlCopied: boolean;
|
||||||
copyUrlToClipboard: () => void;
|
copyUrlToClipboard: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SecretTable = ({
|
export const SecretTable = ({
|
||||||
isLoading,
|
isLoading,
|
||||||
sharedSecret,
|
|
||||||
decryptedSecret,
|
decryptedSecret,
|
||||||
timeLeft,
|
|
||||||
isUrlCopied,
|
isUrlCopied,
|
||||||
copyUrlToClipboard
|
copyUrlToClipboard
|
||||||
}: Props) => {
|
}: Props) => (
|
||||||
return (
|
<div className="flex items-center rounded border border-solid border-mineshaft-700 bg-mineshaft-800 p-4">
|
||||||
<TableContainer>
|
{isLoading && <div className="bg-mineshaft-800 text-center text-bunker-400">Loading...</div>}
|
||||||
<Table>
|
{!isLoading && !decryptedSecret && (
|
||||||
<THead>
|
<Tr>
|
||||||
<Tr>
|
<Td colSpan={4} className="bg-mineshaft-800 text-center text-bunker-400">
|
||||||
<Th>Secret</Th>
|
<EmptyState title="Secret has either expired or does not exist!" icon={faKey} />
|
||||||
<Th>Valid Until</Th>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
</THead>
|
)}
|
||||||
<TBody>
|
{!isLoading && decryptedSecret && (
|
||||||
{!isLoading && sharedSecret && decryptedSecret && (
|
<>
|
||||||
<Tr key={sharedSecret.encryptedValue}>
|
<div className="max-w-[20rem] flex-1 break-words pr-4">
|
||||||
<Td>
|
<SecretInput isVisible value={decryptedSecret} readOnly />
|
||||||
<div className="flex items-center md:space-x-2">
|
</div>
|
||||||
<div className="max-w-[20rem] flex-1 break-words">
|
<IconButton
|
||||||
<SecretInput isVisible value={decryptedSecret} readOnly />
|
ariaLabel="copy to clipboard"
|
||||||
</div>
|
onClick={copyUrlToClipboard}
|
||||||
<IconButton
|
className="rounded p-2 hover:bg-gray-700"
|
||||||
ariaLabel="copy to clipboard"
|
size="xs"
|
||||||
onClick={copyUrlToClipboard}
|
>
|
||||||
className="rounded p-2 hover:bg-gray-700"
|
<FontAwesomeIcon icon={isUrlCopied ? faCheck : faCopy} />
|
||||||
size="xs"
|
</IconButton>
|
||||||
>
|
</>
|
||||||
<FontAwesomeIcon icon={isUrlCopied ? faCheck : faCopy} />
|
)}
|
||||||
</IconButton>
|
</div>
|
||||||
</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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
Reference in New Issue
Block a user