mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 14:27:30 +00:00
Adjustments to secret sharing styling
This commit is contained in:
@@ -95,7 +95,7 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
|
|||||||
tag: z.string(),
|
tag: z.string(),
|
||||||
hashedHex: z.string(),
|
hashedHex: z.string(),
|
||||||
expiresAt: z.string(),
|
expiresAt: z.string(),
|
||||||
expiresAfterViews: z.number()
|
expiresAfterViews: z.number().optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -110,7 +110,7 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
|
|||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
hashedHex,
|
hashedHex,
|
||||||
expiresAt: new Date(expiresAt),
|
expiresAt,
|
||||||
expiresAfterViews,
|
expiresAfterViews,
|
||||||
accessType: SecretSharingAccessType.Anyone
|
accessType: SecretSharingAccessType.Anyone
|
||||||
});
|
});
|
||||||
@@ -131,7 +131,7 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
|
|||||||
tag: z.string(),
|
tag: z.string(),
|
||||||
hashedHex: z.string(),
|
hashedHex: z.string(),
|
||||||
expiresAt: z.string(),
|
expiresAt: z.string(),
|
||||||
expiresAfterViews: z.number(),
|
expiresAfterViews: z.number().optional(),
|
||||||
accessType: z.nativeEnum(SecretSharingAccessType).default(SecretSharingAccessType.Organization)
|
accessType: z.nativeEnum(SecretSharingAccessType).default(SecretSharingAccessType.Organization)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
@@ -153,7 +153,7 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
|
|||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
hashedHex,
|
hashedHex,
|
||||||
expiresAt: new Date(expiresAt),
|
expiresAt,
|
||||||
expiresAfterViews,
|
expiresAfterViews,
|
||||||
accessType: req.body.accessType
|
accessType: req.body.accessType
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -64,12 +64,13 @@ export const secretSharingServiceFactory = ({
|
|||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
hashedHex,
|
hashedHex,
|
||||||
expiresAt,
|
expiresAt: new Date(expiresAt),
|
||||||
expiresAfterViews,
|
expiresAfterViews,
|
||||||
userId: actorId,
|
userId: actorId,
|
||||||
orgId,
|
orgId,
|
||||||
accessType
|
accessType
|
||||||
});
|
});
|
||||||
|
|
||||||
return { id: newSharedSecret.id };
|
return { id: newSharedSecret.id };
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -97,7 +98,7 @@ export const secretSharingServiceFactory = ({
|
|||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
hashedHex,
|
hashedHex,
|
||||||
expiresAt,
|
expiresAt: new Date(expiresAt),
|
||||||
expiresAfterViews,
|
expiresAfterViews,
|
||||||
accessType
|
accessType
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ export type TCreatePublicSharedSecretDTO = {
|
|||||||
iv: string;
|
iv: string;
|
||||||
tag: string;
|
tag: string;
|
||||||
hashedHex: string;
|
hashedHex: string;
|
||||||
expiresAt: Date;
|
expiresAt: string;
|
||||||
expiresAfterViews: number;
|
expiresAfterViews?: number;
|
||||||
accessType: SecretSharingAccessType;
|
accessType: SecretSharingAccessType;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export type TCreateSharedSecretRequest = {
|
|||||||
tag: string;
|
tag: string;
|
||||||
hashedHex: string;
|
hashedHex: string;
|
||||||
expiresAt: Date;
|
expiresAt: Date;
|
||||||
expiresAfterViews: number;
|
expiresAfterViews?: number;
|
||||||
accessType: SecretSharingAccessType;
|
accessType: SecretSharingAccessType;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -31,4 +31,4 @@ export type TDeleteSharedSecretRequest = {
|
|||||||
export enum SecretSharingAccessType {
|
export enum SecretSharingAccessType {
|
||||||
Anyone = "anyone",
|
Anyone = "anyone",
|
||||||
Organization = "organization"
|
Organization = "organization"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,19 +7,38 @@ 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, FormControl, ModalClose, Select, SelectItem } from "@app/components/v2";
|
||||||
import { SecretSharingAccessType, 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"),
|
expiresAfterViews: yup.string().required().label("Expires After Views"),
|
||||||
expiresInValue: yup.number().min(1).required().label("Expiration Value"),
|
expiresInValue: yup.string().min(1).required().label("Expiration Value"),
|
||||||
expiresInUnit: yup.string().required().label("Expiration Unit"),
|
|
||||||
accessType: yup.string().required().label("General Access")
|
accessType: yup.string().required().label("General Access")
|
||||||
});
|
});
|
||||||
|
|
||||||
export type FormData = yup.InferType<typeof schema>;
|
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 = ({
|
export const AddShareSecretForm = ({
|
||||||
isPublic,
|
isPublic,
|
||||||
inModal,
|
inModal,
|
||||||
@@ -49,36 +68,15 @@ export const AddShareSecretForm = ({
|
|||||||
const privateSharedSecretCreator = useCreateSharedSecret();
|
const privateSharedSecretCreator = useCreateSharedSecret();
|
||||||
const createSharedSecret = isPublic ? publicSharedSecretCreator : privateSharedSecretCreator;
|
const createSharedSecret = isPublic ? publicSharedSecretCreator : privateSharedSecretCreator;
|
||||||
|
|
||||||
const expirationUnitsAndActions = [
|
|
||||||
{
|
|
||||||
unit: "Minutes",
|
|
||||||
action: (expiresAt: Date, expiresInValue: number) =>
|
|
||||||
expiresAt.setMinutes(expiresAt.getMinutes() + expiresInValue)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
unit: "Hours",
|
|
||||||
action: (expiresAt: Date, expiresInValue: number) =>
|
|
||||||
expiresAt.setHours(expiresAt.getHours() + expiresInValue)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
unit: "Days",
|
|
||||||
action: (expiresAt: Date, expiresInValue: number) =>
|
|
||||||
expiresAt.setDate(expiresAt.getDate() + expiresInValue)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
unit: "Weeks",
|
|
||||||
action: (expiresAt: Date, expiresInValue: number) =>
|
|
||||||
expiresAt.setDate(expiresAt.getDate() + expiresInValue * 7)
|
|
||||||
}
|
|
||||||
];
|
|
||||||
const onFormSubmit = async ({
|
const onFormSubmit = async ({
|
||||||
value,
|
value,
|
||||||
expiresInValue,
|
expiresInValue,
|
||||||
expiresInUnit,
|
expiresAfterViews,
|
||||||
expiresAfterSingleView,
|
|
||||||
accessType
|
accessType
|
||||||
}: FormData) => {
|
}: FormData) => {
|
||||||
try {
|
try {
|
||||||
|
const expiresAt = new Date(new Date().getTime() + Number(expiresInValue));
|
||||||
|
|
||||||
const key = crypto.randomBytes(16).toString("hex");
|
const key = crypto.randomBytes(16).toString("hex");
|
||||||
const hashedHex = crypto.createHash("sha256").update(key).digest("hex");
|
const hashedHex = crypto.createHash("sha256").update(key).digest("hex");
|
||||||
const { ciphertext, iv, tag } = encryptSymmetric({
|
const { ciphertext, iv, tag } = encryptSymmetric({
|
||||||
@@ -86,21 +84,13 @@ export const AddShareSecretForm = ({
|
|||||||
key
|
key
|
||||||
});
|
});
|
||||||
|
|
||||||
const expiresAt = new Date();
|
|
||||||
const updateExpiresAt = expirationUnitsAndActions.find(
|
|
||||||
(item) => item.unit === expiresInUnit
|
|
||||||
)?.action;
|
|
||||||
if (updateExpiresAt && expiresInValue) {
|
|
||||||
updateExpiresAt(expiresAt, expiresInValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { id } = await createSharedSecret.mutateAsync({
|
const { id } = await createSharedSecret.mutateAsync({
|
||||||
encryptedValue: ciphertext,
|
encryptedValue: ciphertext,
|
||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
hashedHex,
|
hashedHex,
|
||||||
expiresAt,
|
expiresAt,
|
||||||
expiresAfterViews: expiresAfterSingleView ? 1 : 1000,
|
expiresAfterViews: expiresAfterViews === "-1" ? undefined : Number(expiresAfterViews),
|
||||||
accessType: accessType as SecretSharingAccessType
|
accessType: accessType as SecretSharingAccessType
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -132,9 +122,14 @@ export const AddShareSecretForm = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<form className="flex w-full flex-col items-center px-4 sm:px-0" onSubmit={handleSubmit(onFormSubmit)}>
|
<form
|
||||||
|
className="flex w-full max-w-7xl flex-col items-center"
|
||||||
|
onSubmit={handleSubmit(onFormSubmit)}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className={`w-full ${!inModal && "rounded-md border border-mineshaft-600 bg-mineshaft-800 p-6"}`}
|
className={`w-full ${
|
||||||
|
!inModal && "rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<Controller
|
<Controller
|
||||||
@@ -142,7 +137,7 @@ export const AddShareSecretForm = ({
|
|||||||
name="value"
|
name="value"
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Shared Secret"
|
label="Your Secret"
|
||||||
isError={Boolean(error)}
|
isError={Boolean(error)}
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
className="mb-2"
|
className="mb-2"
|
||||||
@@ -157,90 +152,48 @@ export const AddShareSecretForm = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full flex-col md:flex-row justify-stretch">
|
<Controller
|
||||||
<div className="flex justify-start">
|
control={control}
|
||||||
<div className="flex justify-start">
|
name="expiresInValue"
|
||||||
<div className="flex w-full justify-center pr-2">
|
defaultValue="3600000"
|
||||||
<Controller
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
control={control}
|
<FormControl label="Expires In" errorText={error?.message} isError={Boolean(error)}>
|
||||||
name="expiresInValue"
|
<Select
|
||||||
defaultValue={1}
|
defaultValue={field.value}
|
||||||
render={({ field, fieldState: { error } }) => (
|
{...field}
|
||||||
<FormControl
|
onValueChange={(e) => onChange(e)}
|
||||||
label="Expires after Time"
|
className="w-full"
|
||||||
isError={Boolean(error)}
|
>
|
||||||
errorText="Please enter a valid time duration"
|
{expiresInOptions.map(({ label, value }) => (
|
||||||
className="w-32"
|
<SelectItem value={String(value || "")} key={label}>
|
||||||
>
|
{label}
|
||||||
<Input {...field} type="number" min={0} />
|
</SelectItem>
|
||||||
</FormControl>
|
))}
|
||||||
)}
|
</Select>
|
||||||
/>
|
</FormControl>
|
||||||
</div>
|
)}
|
||||||
<div className="flex justify-center">
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="expiresInUnit"
|
name="expiresAfterViews"
|
||||||
defaultValue={expirationUnitsAndActions[1].unit}
|
defaultValue="-1"
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
<FormControl label="Unit" errorText={error?.message} isError={Boolean(error)}>
|
<FormControl label="Max Views" errorText={error?.message} isError={Boolean(error)}>
|
||||||
<Select
|
<Select
|
||||||
defaultValue={field.value}
|
defaultValue={field.value}
|
||||||
{...field}
|
{...field}
|
||||||
onValueChange={(e) => onChange(e)}
|
onValueChange={(e) => onChange(e)}
|
||||||
className="w-full border border-mineshaft-600"
|
className="w-full"
|
||||||
>
|
>
|
||||||
{expirationUnitsAndActions.map(({ unit }) => (
|
{viewLimitOptions.map(({ label, value }) => (
|
||||||
<SelectItem value={unit} key={unit}>
|
<SelectItem value={String(value || "")} key={label}>
|
||||||
{unit}
|
{label}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="sm:w-1/7 mx-auto items-center justify-center hidden md:flex">
|
|
||||||
<p className="mt-2 text-sm text-gray-400">AND</p>
|
|
||||||
</div>
|
|
||||||
<div className="items-center pb-4 md:pb-0 md:pt-2 flex">
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="expiresAfterViews"
|
|
||||||
defaultValue={1}
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
className="mb-4 w-full hidden"
|
|
||||||
label="Expires after Views"
|
|
||||||
isError={Boolean(error)}
|
|
||||||
errorText="Please enter a valid number of views"
|
|
||||||
>
|
|
||||||
<Input {...field} type="number" min={1} />
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<div className="bg-mineshaft-900 py-2 h-max rounded-md border border-mineshaft-600 px-4">
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="expiresAfterSingleView"
|
|
||||||
defaultValue={false}
|
|
||||||
render={({ field: { onBlur, value, onChange } }) => (
|
|
||||||
<Checkbox
|
|
||||||
id="is-single-view"
|
|
||||||
isChecked={value}
|
|
||||||
onCheckedChange={onChange}
|
|
||||||
isDisabled={false}
|
|
||||||
onBlur={onBlur}
|
|
||||||
>
|
|
||||||
Can be viewed only 1 time
|
|
||||||
</Checkbox>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{!isPublic && (
|
{!isPublic && (
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
@@ -248,10 +201,7 @@ export const AddShareSecretForm = ({
|
|||||||
defaultValue="organization"
|
defaultValue="organization"
|
||||||
render={({ field: { onChange, ...field } }) => (
|
render={({ field: { onChange, ...field } }) => (
|
||||||
<FormControl label="General Access">
|
<FormControl label="General Access">
|
||||||
<Select
|
<Select {...field} onValueChange={(e) => onChange(e)} className="w-full">
|
||||||
{...field}
|
|
||||||
onValueChange={(e) => onChange(e)}
|
|
||||||
>
|
|
||||||
<SelectItem value="organization">People within your organization</SelectItem>
|
<SelectItem value="organization">People within your organization</SelectItem>
|
||||||
<SelectItem value="anyone">Anyone</SelectItem>
|
<SelectItem value="anyone">Anyone</SelectItem>
|
||||||
</Select>
|
</Select>
|
||||||
@@ -261,7 +211,7 @@ export const AddShareSecretForm = ({
|
|||||||
)}
|
)}
|
||||||
<div className={`flex items-center space-x-4 pt-2 ${!inModal && ""}`}>
|
<div className={`flex items-center space-x-4 pt-2 ${!inModal && ""}`}>
|
||||||
<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" : "Create secret link"}
|
||||||
</Button>
|
</Button>
|
||||||
{inModal && (
|
{inModal && (
|
||||||
<ModalClose asChild>
|
<ModalClose asChild>
|
||||||
|
|||||||
@@ -12,9 +12,8 @@ import { ViewAndCopySharedSecret } from "./ViewAndCopySharedSecret";
|
|||||||
|
|
||||||
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"),
|
||||||
expiresAfterViews: yup.number().min(1).required().label("Expires After Views"),
|
expiresInValue: yup.string().required().label("Expiration Value"),
|
||||||
expiresInValue: yup.number().min(1).required().label("Expiration Value"),
|
expiresAfterViews: yup.string().required().label("Expires After Views")
|
||||||
expiresInUnit: yup.string().required().label("Expiration Unit")
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type FormData = yup.InferType<typeof schema>;
|
export type FormData = yup.InferType<typeof schema>;
|
||||||
|
|||||||
@@ -46,9 +46,8 @@ export const ShareSecretSection = () => {
|
|||||||
<link rel="icon" href="/infisical.ico" />
|
<link rel="icon" href="/infisical.ico" />
|
||||||
<meta property="og:image" content="/images/message.png" />
|
<meta property="og:image" content="/images/message.png" />
|
||||||
</Head>
|
</Head>
|
||||||
<div className="mb-2 flex justify-between">
|
<div className="mb-4 flex justify-between">
|
||||||
<p className="text-xl font-semibold text-mineshaft-100">Shared Secrets</p>
|
<p className="text-xl font-semibold text-mineshaft-100">Shared Secrets</p>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
colorSchema="primary"
|
colorSchema="primary"
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
|||||||
@@ -1,77 +1,14 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { faTrashCan } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
|
||||||
import { IconButton, Td, Tr } from "@app/components/v2";
|
import { IconButton, Td, Tr } from "@app/components/v2";
|
||||||
import { TSharedSecret } from "@app/hooks/api/secretSharing";
|
import { TSharedSecret } from "@app/hooks/api/secretSharing";
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
const formatDate = (date: Date): string => (date ? new Date(date).toUTCString() : "");
|
|
||||||
|
|
||||||
const isExpired = (expiresAt: Date | number | undefined): boolean => {
|
|
||||||
if (typeof expiresAt === "number") {
|
|
||||||
return expiresAt <= 0;
|
|
||||||
}
|
|
||||||
if (expiresAt instanceof Date) {
|
|
||||||
return new Date(expiresAt) < new Date();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getValidityStatusText = (expiresAt: Date): string =>
|
|
||||||
isExpired(expiresAt) ? "Expired " : "Valid for ";
|
|
||||||
|
|
||||||
const timeAgo = (inputDate: Date, currentDate: Date): string => {
|
|
||||||
const now = new Date(currentDate).getTime();
|
|
||||||
const date = new Date(inputDate).getTime();
|
|
||||||
const elapsedMilliseconds = now - date;
|
|
||||||
const elapsedSeconds = Math.abs(Math.floor(elapsedMilliseconds / 1000));
|
|
||||||
const elapsedMinutes = Math.abs(Math.floor(elapsedSeconds / 60));
|
|
||||||
const elapsedHours = Math.abs(Math.floor(elapsedMinutes / 60));
|
|
||||||
const elapsedDays = Math.abs(Math.floor(elapsedHours / 24));
|
|
||||||
const elapsedWeeks = Math.abs(Math.floor(elapsedDays / 7));
|
|
||||||
const elapsedMonths = Math.abs(Math.floor(elapsedDays / 30));
|
|
||||||
const elapsedYears = Math.abs(Math.floor(elapsedDays / 365));
|
|
||||||
|
|
||||||
if (elapsedYears > 0) {
|
|
||||||
return `${elapsedYears} year${elapsedYears === 1 ? "" : "s"} ${
|
|
||||||
elapsedMilliseconds >= 0 ? "ago" : "from now"
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
if (elapsedMonths > 0) {
|
|
||||||
return `${elapsedMonths} month${elapsedMonths === 1 ? "" : "s"} ${
|
|
||||||
elapsedMilliseconds >= 0 ? "ago" : "from now"
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
if (elapsedWeeks > 0) {
|
|
||||||
return `${elapsedWeeks} week${elapsedWeeks === 1 ? "" : "s"} ${
|
|
||||||
elapsedMilliseconds >= 0 ? "ago" : "from now"
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
if (elapsedDays > 0) {
|
|
||||||
return `${elapsedDays} day${elapsedDays === 1 ? "" : "s"} ${
|
|
||||||
elapsedMilliseconds >= 0 ? "ago" : "from now"
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
if (elapsedHours > 0) {
|
|
||||||
return `${elapsedHours} hour${elapsedHours === 1 ? "" : "s"} ${
|
|
||||||
elapsedMilliseconds >= 0 ? "ago" : "from now"
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
if (elapsedMinutes > 0) {
|
|
||||||
return `${elapsedMinutes} minute${elapsedMinutes === 1 ? "" : "s"} ${
|
|
||||||
elapsedMilliseconds >= 0 ? "ago" : "from now"
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
return `${elapsedSeconds} second${elapsedSeconds === 1 ? "" : "s"} ${
|
|
||||||
elapsedMilliseconds >= 0 ? "ago" : "from now"
|
|
||||||
}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ShareSecretsRow = ({
|
export const ShareSecretsRow = ({
|
||||||
row,
|
row,
|
||||||
handlePopUpOpen,
|
handlePopUpOpen
|
||||||
onSecretExpiration
|
|
||||||
}: {
|
}: {
|
||||||
row: TSharedSecret;
|
row: TSharedSecret;
|
||||||
handlePopUpOpen: (
|
handlePopUpOpen: (
|
||||||
@@ -84,44 +21,13 @@ export const ShareSecretsRow = ({
|
|||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
) => void;
|
) => void;
|
||||||
onSecretExpiration: (expiredSecretId: string) => void;
|
|
||||||
}) => {
|
}) => {
|
||||||
const [currentTime, setCurrentTime] = useState(new Date());
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const intervalId = setInterval(() => {
|
|
||||||
setCurrentTime(new Date());
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
return () => clearInterval(intervalId);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isExpired(row.expiresAt || row.expiresAfterViews)) {
|
|
||||||
onSecretExpiration(row.id);
|
|
||||||
}
|
|
||||||
}, [isExpired(row.expiresAt || row.expiresAfterViews)]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tr key={row.id}>
|
<Tr key={row.id} className="h-10">
|
||||||
<Td>{`${row.encryptedValue.substring(0, 5)}...`}</Td>
|
<Td>{`${row.encryptedValue.substring(0, 5)}...`}</Td>
|
||||||
<Td>
|
<Td>{format(new Date(row.createdAt), "yyyy-MM-dd - HH:mm a")}</Td>
|
||||||
<p className="text-sm text-yellow-400">{timeAgo(row.createdAt, currentTime)}</p>
|
<Td>{format(new Date(row.expiresAt), "yyyy-MM-dd - HH:mm a")}</Td>
|
||||||
<p className="text-xs text-gray-500">{formatDate(row.createdAt)}</p>
|
<Td>{row.expiresAfterViews ? row.expiresAfterViews : "-"}</Td>
|
||||||
</Td>
|
|
||||||
<Td>
|
|
||||||
<>
|
|
||||||
<p className={`text-sm ${isExpired(row.expiresAt) ? "text-red-500" : "text-green-500"}`}>
|
|
||||||
{getValidityStatusText(row.expiresAt!) + timeAgo(row.expiresAt!, currentTime)}
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-gray-500">{formatDate(row.expiresAt!)}</p>
|
|
||||||
</>
|
|
||||||
</Td>
|
|
||||||
<Td>
|
|
||||||
<p className={`text-sm ${row.expiresAfterViews <= 0 ? "text-red-500" : "text-green-500"}`}>
|
|
||||||
{row.expiresAfterViews}
|
|
||||||
</p>
|
|
||||||
</Td>
|
|
||||||
<Td>
|
<Td>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
@@ -130,10 +36,10 @@ export const ShareSecretsRow = ({
|
|||||||
id: row.id
|
id: row.id
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
colorSchema="danger"
|
variant="plain"
|
||||||
ariaLabel="delete"
|
ariaLabel="delete"
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faTrashCan} />
|
<FontAwesomeIcon icon={faTrash} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
|
|||||||
@@ -31,38 +31,25 @@ type Props = {
|
|||||||
|
|
||||||
export const ShareSecretsTable = ({ handlePopUpOpen }: Props) => {
|
export const ShareSecretsTable = ({ handlePopUpOpen }: Props) => {
|
||||||
const { isLoading, data = [] } = useGetSharedSecrets();
|
const { isLoading, data = [] } = useGetSharedSecrets();
|
||||||
|
|
||||||
let tableData = data.filter(
|
|
||||||
(secret) => new Date(secret.expiresAt) > new Date() && secret.expiresAfterViews > 0
|
|
||||||
);
|
|
||||||
const handleSecretExpiration = () => {
|
|
||||||
tableData = data.filter(
|
|
||||||
(secret) => new Date(secret.expiresAt) > new Date() && secret.expiresAfterViews > 0
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
<Table>
|
<Table>
|
||||||
<THead>
|
<THead>
|
||||||
<Tr>
|
<Tr>
|
||||||
<Th>Encrypted Secret</Th> <Th>Created</Th> <Th>Valid Until</Th> <Th>Views Left</Th>
|
<Th>Encrypted Secret</Th>
|
||||||
<Th aria-label="button" />
|
<Th>Created</Th>
|
||||||
|
<Th>Valid Until</Th>
|
||||||
|
<Th>Views Left</Th>
|
||||||
|
<Th aria-label="button" className="w-5" />
|
||||||
</Tr>
|
</Tr>
|
||||||
</THead>
|
</THead>
|
||||||
<TBody>
|
<TBody>
|
||||||
{isLoading && <TableSkeleton columns={4} innerKey="shared-secrets" />}
|
{isLoading && <TableSkeleton columns={4} innerKey="shared-secrets" />}
|
||||||
{!isLoading &&
|
{!isLoading &&
|
||||||
tableData &&
|
data?.map((row) => (
|
||||||
tableData.map((row) => (
|
<ShareSecretsRow key={row.id} row={row} handlePopUpOpen={handlePopUpOpen} />
|
||||||
<ShareSecretsRow
|
|
||||||
key={row.id}
|
|
||||||
row={row}
|
|
||||||
handlePopUpOpen={handlePopUpOpen}
|
|
||||||
onSecretExpiration={handleSecretExpiration}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
{!isLoading && tableData && tableData?.length === 0 && (
|
{!isLoading && data?.length === 0 && (
|
||||||
<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="No secrets shared yet" icon={faKey} />
|
<EmptyState title="No secrets shared yet" icon={faKey} />
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import { useGetActiveSharedSecretByIdAndHashedHex } from "@app/hooks/api/secretS
|
|||||||
import { AddShareSecretModal } from "../ShareSecretPage/components/AddShareSecretModal";
|
import { AddShareSecretModal } from "../ShareSecretPage/components/AddShareSecretModal";
|
||||||
import { SecretTable } from "./components";
|
import { SecretTable } from "./components";
|
||||||
|
|
||||||
|
// note: isNewSession: controls if the user is sharing a new secret or viewing a shared secret
|
||||||
|
|
||||||
export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean }) => {
|
export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean }) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { id, key: urlEncodedPublicKey } = router.query;
|
const { id, key: urlEncodedPublicKey } = router.query;
|
||||||
@@ -55,7 +57,7 @@ export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean
|
|||||||
return (
|
return (
|
||||||
<div className="flex h-screen flex-col overflow-y-auto bg-gradient-to-tr from-mineshaft-700 to-bunker-800 text-gray-200 dark:[color-scheme:dark]">
|
<div className="flex h-screen flex-col overflow-y-auto bg-gradient-to-tr from-mineshaft-700 to-bunker-800 text-gray-200 dark:[color-scheme:dark]">
|
||||||
<Head>
|
<Head>
|
||||||
<title>Secret Shared | Infisical</title>
|
<title>Infisical | Secret Sharing</title>
|
||||||
<link rel="icon" href="/infisical.ico" />
|
<link rel="icon" href="/infisical.ico" />
|
||||||
</Head>
|
</Head>
|
||||||
<div className="flex w-full flex-grow items-center justify-center dark:[color-scheme:dark]">
|
<div className="flex w-full flex-grow items-center justify-center dark:[color-scheme:dark]">
|
||||||
@@ -94,7 +96,6 @@ export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isNewSession && (
|
{isNewSession && (
|
||||||
<div className="px-0 sm:px-6">
|
<div className="px-0 sm:px-6">
|
||||||
<AddShareSecretModal
|
<AddShareSecretModal
|
||||||
|
|||||||
Reference in New Issue
Block a user