From b5b778e241565557b5191066bd2abca9f08187b9 Mon Sep 17 00:00:00 2001 From: ShubhamPalriwala Date: Wed, 29 May 2024 11:00:24 +0530 Subject: [PATCH] fix: minor ui changes + delete expired secrets + address other feedback --- ...ng.ts => 20240528190137_secret_sharing.ts} | 2 + backend/src/db/schemas/secret-sharing.ts | 1 + .../ee/services/permission/org-permission.ts | 10 -- backend/src/server/routes/index.ts | 3 +- .../resource-cleanup-queue.ts | 6 +- .../secret-sharing/secret-sharing-dal.ts | 19 ++- .../secret-sharing/secret-sharing-service.ts | 15 +- .../src/context/OrgPermissionContext/types.ts | 2 - .../components/AddShareSecretModal.tsx | 29 ++-- .../components/ShareSecretSection.tsx | 147 +++++++----------- .../components/ShareSecretsRow.tsx | 69 ++++---- .../components/ShareSecretsTable.tsx | 23 +-- .../ShareSecretPublicPage.tsx | 42 ++--- .../components/SecretTable.tsx | 9 +- 14 files changed, 174 insertions(+), 203 deletions(-) rename backend/src/db/migrations/{20240426191241_secret_sharing.ts => 20240528190137_secret_sharing.ts} (86%) diff --git a/backend/src/db/migrations/20240426191241_secret_sharing.ts b/backend/src/db/migrations/20240528190137_secret_sharing.ts similarity index 86% rename from backend/src/db/migrations/20240426191241_secret_sharing.ts rename to backend/src/db/migrations/20240528190137_secret_sharing.ts index e9bd89f9a..c602905cc 100644 --- a/backend/src/db/migrations/20240426191241_secret_sharing.ts +++ b/backend/src/db/migrations/20240528190137_secret_sharing.ts @@ -11,7 +11,9 @@ export async function up(knex: Knex): Promise { t.text("signedValue").notNullable(); t.timestamp("expiresAt").notNullable(); t.uuid("userId").notNullable(); + t.uuid("orgId").notNullable(); t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE"); + t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE"); t.timestamps(true, true, true); }); diff --git a/backend/src/db/schemas/secret-sharing.ts b/backend/src/db/schemas/secret-sharing.ts index d53f34c4e..532f1e310 100644 --- a/backend/src/db/schemas/secret-sharing.ts +++ b/backend/src/db/schemas/secret-sharing.ts @@ -13,6 +13,7 @@ export const SecretSharingSchema = z.object({ signedValue: z.string(), expiresAt: z.date(), userId: z.string().uuid(), + orgId: z.string().uuid(), createdAt: z.date(), updatedAt: z.date() }); diff --git a/backend/src/ee/services/permission/org-permission.ts b/backend/src/ee/services/permission/org-permission.ts index d76406cfd..9fece040b 100644 --- a/backend/src/ee/services/permission/org-permission.ts +++ b/backend/src/ee/services/permission/org-permission.ts @@ -21,7 +21,6 @@ export enum OrgPermissionSubjects { Groups = "groups", Billing = "billing", SecretScanning = "secret-scanning", - SecretSharing = "secret-sharing", Identity = "identity" } @@ -37,7 +36,6 @@ export type OrgPermissionSet = | [OrgPermissionActions, OrgPermissionSubjects.Ldap] | [OrgPermissionActions, OrgPermissionSubjects.Groups] | [OrgPermissionActions, OrgPermissionSubjects.SecretScanning] - | [OrgPermissionActions, OrgPermissionSubjects.SecretSharing] | [OrgPermissionActions, OrgPermissionSubjects.Billing] | [OrgPermissionActions, OrgPermissionSubjects.Identity]; @@ -62,10 +60,6 @@ const buildAdminPermission = () => { can(OrgPermissionActions.Edit, OrgPermissionSubjects.SecretScanning); can(OrgPermissionActions.Delete, OrgPermissionSubjects.SecretScanning); - can(OrgPermissionActions.Read, OrgPermissionSubjects.SecretSharing); - can(OrgPermissionActions.Create, OrgPermissionSubjects.SecretSharing); - can(OrgPermissionActions.Delete, OrgPermissionSubjects.SecretSharing); - can(OrgPermissionActions.Read, OrgPermissionSubjects.Settings); can(OrgPermissionActions.Create, OrgPermissionSubjects.Settings); can(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings); @@ -130,10 +124,6 @@ const buildMemberPermission = () => { can(OrgPermissionActions.Edit, OrgPermissionSubjects.SecretScanning); can(OrgPermissionActions.Delete, OrgPermissionSubjects.SecretScanning); - can(OrgPermissionActions.Read, OrgPermissionSubjects.SecretSharing); - can(OrgPermissionActions.Create, OrgPermissionSubjects.SecretSharing); - can(OrgPermissionActions.Delete, OrgPermissionSubjects.SecretSharing); - can(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); can(OrgPermissionActions.Create, OrgPermissionSubjects.Identity); can(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 582f70d5a..1593515ec 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -794,7 +794,8 @@ export const registerRoutes = async ( const dailyResourceCleanUp = dailyResourceCleanUpQueueServiceFactory({ auditLogDAL, queueService, - identityAccessTokenDAL + identityAccessTokenDAL, + secretSharingDAL }); await superAdminService.initServerCfg(); diff --git a/backend/src/services/resource-cleanup/resource-cleanup-queue.ts b/backend/src/services/resource-cleanup/resource-cleanup-queue.ts index 3c8bcb1f7..afae2677f 100644 --- a/backend/src/services/resource-cleanup/resource-cleanup-queue.ts +++ b/backend/src/services/resource-cleanup/resource-cleanup-queue.ts @@ -3,10 +3,12 @@ import { logger } from "@app/lib/logger"; import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; +import { TSecretSharingDALFactory } from "../secret-sharing/secret-sharing-dal"; type TDailyResourceCleanUpQueueServiceFactoryDep = { auditLogDAL: Pick; identityAccessTokenDAL: Pick; + secretSharingDAL: Pick; queueService: TQueueServiceFactory; }; @@ -15,12 +17,14 @@ export type TDailyResourceCleanUpQueueServiceFactory = ReturnType { queueService.start(QueueName.DailyResourceCleanUp, async () => { logger.info(`${QueueName.DailyResourceCleanUp}: queue task started`); await auditLogDAL.pruneAuditLog(); await identityAccessTokenDAL.removeExpiredTokens(); + await secretSharingDAL.pruneExpiredSharedSecrets(); logger.info(`${QueueName.DailyResourceCleanUp}: queue task completed`); }); diff --git a/backend/src/services/secret-sharing/secret-sharing-dal.ts b/backend/src/services/secret-sharing/secret-sharing-dal.ts index 696719a1e..6b5090d66 100644 --- a/backend/src/services/secret-sharing/secret-sharing-dal.ts +++ b/backend/src/services/secret-sharing/secret-sharing-dal.ts @@ -1,5 +1,8 @@ +import { Knex } from "knex"; + import { TDbClient } from "@app/db"; import { TableName } from "@app/db/schemas"; +import { DatabaseError } from "@app/lib/errors"; import { ormify } from "@app/lib/knex"; export type TSecretSharingDALFactory = ReturnType; @@ -7,10 +10,18 @@ export type TSecretSharingDALFactory = ReturnType { const sharedSecretOrm = ormify(db, TableName.SecretSharing); + const pruneExpiredSharedSecrets = async (tx?: Knex) => { + try { + const today = new Date(); + const docs = await (tx || db)(TableName.SecretSharing).where("expiresAt", "<", today).del(); + return docs; + } catch (error) { + throw new DatabaseError({ error, name: "pruneExpiredSharedSecrets" }); + } + }; + return { - create: sharedSecretOrm.create, - find: sharedSecretOrm.find, - findById: sharedSecretOrm.findById, - deleteById: sharedSecretOrm.deleteById + ...sharedSecretOrm, + pruneExpiredSharedSecrets }; }; diff --git a/backend/src/services/secret-sharing/secret-sharing-service.ts b/backend/src/services/secret-sharing/secret-sharing-service.ts index a4952586a..d4b40d309 100644 --- a/backend/src/services/secret-sharing/secret-sharing-service.ts +++ b/backend/src/services/secret-sharing/secret-sharing-service.ts @@ -1,7 +1,5 @@ -import { ForbiddenError } from "@casl/ability"; - -import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; +import { UnauthorizedError } from "@app/lib/errors"; import { TSecretSharingDALFactory } from "./secret-sharing-dal"; import { TCreateSharedSecretDTO, TDeleteSharedSecretDTO, TSharedSecretPermission } from "./secret-sharing-types"; @@ -21,12 +19,13 @@ export const secretSharingServiceFactory = ({ const { actor, actorId, orgId, actorAuthMethod, actorOrgId, name, signedValue, expiresAt } = createSharedSecretInput; const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretSharing); + if (!permission) throw new UnauthorizedError({ name: "User not in org" }); const newSharedSecret = await secretSharingDAL.create({ name, signedValue, expiresAt, - userId: actorId + userId: actorId, + orgId }); return { id: newSharedSecret.id }; }; @@ -34,8 +33,8 @@ export const secretSharingServiceFactory = ({ const getSharedSecrets = async (getSharedSecretsInput: TSharedSecretPermission) => { const { actor, actorId, orgId, actorAuthMethod, actorOrgId } = getSharedSecretsInput; const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretSharing); - const userSharedSecrets = await secretSharingDAL.find({ userId: actorId }, { sort: [["expiresAt", "asc"]] }); + if (!permission) throw new UnauthorizedError({ name: "User not in org" }); + const userSharedSecrets = await secretSharingDAL.find({ userId: actorId, orgId }, { sort: [["expiresAt", "asc"]] }); return userSharedSecrets; }; @@ -50,7 +49,7 @@ export const secretSharingServiceFactory = ({ const deleteSharedSecretById = async (deleteSharedSecretInput: TDeleteSharedSecretDTO) => { const { actor, actorId, orgId, actorAuthMethod, actorOrgId, sharedSecretId } = deleteSharedSecretInput; const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.SecretSharing); + if (!permission) throw new UnauthorizedError({ name: "User not in org" }); const deletedSharedSecret = await secretSharingDAL.deleteById(sharedSecretId); return deletedSharedSecret; }; diff --git a/frontend/src/context/OrgPermissionContext/types.ts b/frontend/src/context/OrgPermissionContext/types.ts index d51191ece..95a9d00ac 100644 --- a/frontend/src/context/OrgPermissionContext/types.ts +++ b/frontend/src/context/OrgPermissionContext/types.ts @@ -19,7 +19,6 @@ export enum OrgPermissionSubjects { Groups = "groups", Billing = "billing", SecretScanning = "secret-scanning", - SecretSharing = "secret-sharing", Identity = "identity" } @@ -35,7 +34,6 @@ export type OrgPermissionSet = | [OrgPermissionActions, OrgPermissionSubjects.Ldap] | [OrgPermissionActions, OrgPermissionSubjects.Groups] | [OrgPermissionActions, OrgPermissionSubjects.SecretScanning] - | [OrgPermissionActions, OrgPermissionSubjects.SecretSharing] | [OrgPermissionActions, OrgPermissionSubjects.Billing] | [OrgPermissionActions, OrgPermissionSubjects.Identity]; diff --git a/frontend/src/views/ShareSecretPage/components/AddShareSecretModal.tsx b/frontend/src/views/ShareSecretPage/components/AddShareSecretModal.tsx index 4ffe1408a..3795bb3e9 100644 --- a/frontend/src/views/ShareSecretPage/components/AddShareSecretModal.tsx +++ b/frontend/src/views/ShareSecretPage/components/AddShareSecretModal.tsx @@ -19,11 +19,12 @@ import { Modal, ModalClose, ModalContent, + SecretInput, Select, SelectItem } from "@app/components/v2"; import { useOrganization } from "@app/context"; -import { useToggle } from "@app/hooks"; +import { useTimedReset } from "@app/hooks"; import { useCreateSharedSecret } from "@app/hooks/api/secretSharing"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -89,22 +90,20 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle }: Props) => { const createSharedSecret = useCreateSharedSecret(); const { currentOrg } = useOrganization(); const [newSharedSecret, setnewSharedSecret] = useState(""); - const [isUrlCopied, setIsUrlCopied] = useToggle(false); const hasSharedSecret = Boolean(newSharedSecret); - - useEffect(() => { - let timer: NodeJS.Timeout; - if (isUrlCopied) { - timer = setTimeout(() => setIsUrlCopied.off(), 2000); - } - - return () => clearTimeout(timer); - }, [isUrlCopied]); + const [isUrlCopied,, setIsUrlCopied] = useTimedReset({ + initialState: false, + }); const copyUrlToClipboard = () => { navigator.clipboard.writeText(newSharedSecret); - setIsUrlCopied.on(); + setIsUrlCopied(true); }; + useEffect(() => { + if (isUrlCopied) { + setTimeout(() => setIsUrlCopied(false), 2000); + } + }, [isUrlCopied]); const onFormSubmit = async ({ name, value, expiresInValue, expiresInUnit }: FormData) => { try { @@ -195,7 +194,11 @@ export const AddShareSecretModal = ({ popUp, handlePopUpToggle }: Props) => { isError={Boolean(error)} errorText={error?.message} > - + )} /> diff --git a/frontend/src/views/ShareSecretPage/components/ShareSecretSection.tsx b/frontend/src/views/ShareSecretPage/components/ShareSecretSection.tsx index e7311ee85..4ccf4e6fa 100644 --- a/frontend/src/views/ShareSecretPage/components/ShareSecretSection.tsx +++ b/frontend/src/views/ShareSecretPage/components/ShareSecretSection.tsx @@ -1,12 +1,8 @@ -import { useState } from "react"; import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createNotification } from "@app/components/notifications"; -import { OrgPermissionCan } from "@app/components/permissions"; -import { Button, Checkbox, DeleteActionModal } from "@app/components/v2"; -import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; -import { withPermission } from "@app/hoc"; +import { Button, DeleteActionModal } from "@app/components/v2"; import { usePopUp } from "@app/hooks"; import { useDeleteSharedSecret } from "@app/hooks/api/secretSharing"; @@ -15,90 +11,67 @@ import { ShareSecretsTable } from "./ShareSecretsTable"; type DeleteModalData = { name: string; id: string }; -export const ShareSecretSection = withPermission( - () => { - const deleteSharedSecret = useDeleteSharedSecret(); - const [showExpiredSharedSecrets, setShowExpiredSharedSecrets] = useState(false); +export const ShareSecretSection = () => { + const deleteSharedSecret = useDeleteSharedSecret(); + const { popUp, handlePopUpToggle, handlePopUpClose, handlePopUpOpen } = usePopUp([ + "createSharedSecret", + "deleteSharedSecretConfirmation" + ] as const); - const { popUp, handlePopUpToggle, handlePopUpClose, handlePopUpOpen } = usePopUp([ - "createSharedSecret", - "deleteSharedSecretConfirmation" - ] as const); + const onDeleteApproved = async () => { + try { + deleteSharedSecret.mutateAsync({ + sharedSecretId: (popUp?.deleteSharedSecretConfirmation?.data as DeleteModalData)?.id, + }); + createNotification({ + text: "Successfully deleted shared secret", + type: "success" + }); - const onDeleteApproved = async () => { - try { - deleteSharedSecret.mutateAsync({ - sharedSecretId: (popUp?.deleteSharedSecretConfirmation?.data as DeleteModalData)?.id, - }); - createNotification({ - text: "Successfully deleted shared secret", - type: "success" - }); + handlePopUpClose("deleteSharedSecretConfirmation"); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to delete shared secret", + type: "error" + }); + } + }; - handlePopUpClose("deleteSharedSecretConfirmation"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete shared secret", - type: "error" - }); - } - }; + return ( +
+
+

Shared Secrets

- return ( -
-
-

Shared Secrets

- - {(isAllowed) => ( - - )} - -
-
-

- Every secret shared can be accessed with the URL (shown during creation) before its - expiry. -

- { - setShowExpiredSharedSecrets(state as boolean); - }} - > - Show expired shared secrets too - -
- - - handlePopUpToggle("deleteSharedSecretConfirmation", isOpen)} - deleteKey={(popUp?.deleteSharedSecretConfirmation?.data as DeleteModalData)?.name} - onClose={() => handlePopUpClose("deleteSharedSecretConfirmation")} - onDeleteApproved={onDeleteApproved} - /> +
- ); - }, - { action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.SecretSharing } -); +
+

+ Every secret shared can be accessed with the URL (shown during creation) before its + expiry. +

+
+ + + handlePopUpToggle("deleteSharedSecretConfirmation", isOpen)} + deleteKey={(popUp?.deleteSharedSecretConfirmation?.data as DeleteModalData)?.name} + onClose={() => handlePopUpClose("deleteSharedSecretConfirmation")} + onDeleteApproved={onDeleteApproved} + /> +
+ ); +}; \ No newline at end of file diff --git a/frontend/src/views/ShareSecretPage/components/ShareSecretsRow.tsx b/frontend/src/views/ShareSecretPage/components/ShareSecretsRow.tsx index e4a104f69..b0ba810f5 100644 --- a/frontend/src/views/ShareSecretPage/components/ShareSecretsRow.tsx +++ b/frontend/src/views/ShareSecretPage/components/ShareSecretsRow.tsx @@ -2,9 +2,7 @@ import { useEffect, useState } from "react"; import { faTrashCan } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { OrgPermissionCan } from "@app/components/permissions"; import { IconButton, Td, Tr } from "@app/components/v2"; -import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; import { TSharedSecret } from "@app/hooks/api/secretSharing"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -28,38 +26,31 @@ const timeAgo = (inputDate: Date, currentDate: Date): string => { const elapsedYears = Math.abs(Math.floor(elapsedDays / 365)); if (elapsedYears > 0) { - return `${elapsedYears} year${elapsedYears === 1 ? "" : "s"} ${ - elapsedMilliseconds >= 0 ? "ago" : "from now" - }`; + 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" - }`; + 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" - }`; + 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" - }`; + 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" - }`; + 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 `${elapsedMinutes} minute${elapsedMinutes === 1 ? "" : "s"} ${elapsedMilliseconds >= 0 ? "ago" : "from now" + }`; } - return `${elapsedSeconds} second${elapsedSeconds === 1 ? "" : "s"} ${ - elapsedMilliseconds >= 0 ? "ago" : "from now" - }`; + return `${elapsedSeconds} second${elapsedSeconds === 1 ? "" : "s"} ${elapsedMilliseconds >= 0 ? "ago" : "from now" + }`; }; export const ShareSecretsRow = ({ @@ -110,26 +101,20 @@ export const ShareSecretsRow = ({

{formatDate(row.expiresAt)}

- + handlePopUpOpen("deleteSharedSecretConfirmation", { + name: row.name, + id: row.id + }) + } + colorSchema="danger" + ariaLabel="delete" > - {(isAllowed) => ( - - handlePopUpOpen("deleteSharedSecretConfirmation", { - name: row.name, - id: row.id - }) - } - colorSchema="danger" - ariaLabel="delete" - isDisabled={!isAllowed} - > - - - )} - + + ); diff --git a/frontend/src/views/ShareSecretPage/components/ShareSecretsTable.tsx b/frontend/src/views/ShareSecretPage/components/ShareSecretsTable.tsx index 4d3321eaa..75fcd501f 100644 --- a/frontend/src/views/ShareSecretPage/components/ShareSecretsTable.tsx +++ b/frontend/src/views/ShareSecretPage/components/ShareSecretsTable.tsx @@ -28,29 +28,22 @@ type Props = { id: string; } ) => void; - showExpiredSharedSecrets: boolean; }; -export const ShareSecretsTable = ({ handlePopUpOpen, showExpiredSharedSecrets }: Props) => { +export const ShareSecretsTable = ({ handlePopUpOpen }: Props) => { const [tableData, setTableData] = useState([]); - const { isLoading, data = [] } = useGetSharedSecrets(); + const { isLoading, data = [] } = useGetSharedSecrets(); useEffect(() => { if (!isLoading) { - if (!showExpiredSharedSecrets) { - setTableData(data.filter((secret) => new Date(secret.expiresAt) > new Date())); - } else { - setTableData(data); - } + setTableData(data); } - }, [isLoading, data, showExpiredSharedSecrets]); + }, [isLoading, data]); const handleSecretExpiration = () => { - if (!showExpiredSharedSecrets) { - setTableData( - data.filter((secret) => !secret.expiresAt || new Date(secret.expiresAt) > new Date()) - ); - } + setTableData( + data.filter((secret) => !secret.expiresAt || new Date(secret.expiresAt) > new Date()) + ); }; return ( @@ -77,7 +70,7 @@ export const ShareSecretsTable = ({ handlePopUpOpen, showExpiredSharedSecrets }: {!isLoading && tableData && tableData?.length === 0 && ( - + )} diff --git a/frontend/src/views/ShareSecretPublicPage/ShareSecretPublicPage.tsx b/frontend/src/views/ShareSecretPublicPage/ShareSecretPublicPage.tsx index ab34a8a14..703b8faf0 100644 --- a/frontend/src/views/ShareSecretPublicPage/ShareSecretPublicPage.tsx +++ b/frontend/src/views/ShareSecretPublicPage/ShareSecretPublicPage.tsx @@ -5,7 +5,7 @@ import Link from "next/link"; import { useRouter } from "next/router"; import { openSignedAssymmetric } from "@app/components/utilities/cryptography/crypto"; -import { useToggle } from "@app/hooks"; +import { useTimedReset } from "@app/hooks"; import { useGetActiveSharedSecretById } from "@app/hooks/api/secretSharing"; import { DragonMainImage, SecretTable } from "./components"; @@ -34,42 +34,46 @@ export const ShareSecretPublicPage = () => { }, [data, publicKey]); const [timeLeft, setTimeLeft] = useState(""); - const [isUrlCopied, setIsUrlCopied] = useToggle(false); + const [isUrlCopied,, setIsUrlCopied] = useTimedReset({ + initialState: false, + }); + + const millisecondsPerDay = 1000 * 60 * 60 * 24; + const millisecondsPerHour = 1000 * 60 * 60; + const millisecondsPerMinute = 1000 * 60; 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) { + if (data && 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 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 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`); } } }; - + const timer = setInterval(updateTimer, 1000); return () => clearInterval(timer); }, [data?.expiresAt]); useEffect(() => { - let timer: NodeJS.Timeout; if (isUrlCopied) { - timer = setTimeout(() => setIsUrlCopied.off(), 2000); + setTimeout(() => setIsUrlCopied(false), 2000); } - - return () => clearTimeout(timer); }, [isUrlCopied]); + const copyUrlToClipboard = () => { - navigator.clipboard.writeText(decryptedSecret as string); - setIsUrlCopied.on(); + navigator.clipboard.writeText(decryptedSecret); + setIsUrlCopied(true); }; return ( diff --git a/frontend/src/views/ShareSecretPublicPage/components/SecretTable.tsx b/frontend/src/views/ShareSecretPublicPage/components/SecretTable.tsx index f459b9547..97d7deca0 100644 --- a/frontend/src/views/ShareSecretPublicPage/components/SecretTable.tsx +++ b/frontend/src/views/ShareSecretPublicPage/components/SecretTable.tsx @@ -4,6 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { EmptyState, IconButton, + SecretInput, Table, TableContainer, TBody, @@ -47,7 +48,13 @@ export const SecretTable = ({ {sharedSecret.name}
-
{decryptedSecret}
+
+ +