From b0a5023723e6c337dc1f9938b341adab3fd319dd Mon Sep 17 00:00:00 2001
From: lemmyMwaura
Date: Wed, 7 Aug 2024 19:23:10 +0300
Subject: [PATCH] feat: check if secret is expired before checking if secret
has password
---
.../server/routes/v1/secret-sharing-router.ts | 2 +-
.../secret-sharing/secret-sharing-service.ts | 23 ++++++++-----
.../src/hooks/api/secretSharing/queries.ts | 4 +--
frontend/src/hooks/api/secretSharing/types.ts | 7 +---
.../ViewSecretPublicPage.tsx | 34 ++++++++++++-------
.../components/PasswordContainer.tsx | 6 ++--
6 files changed, 42 insertions(+), 34 deletions(-)
diff --git a/backend/src/server/routes/v1/secret-sharing-router.ts b/backend/src/server/routes/v1/secret-sharing-router.ts
index 5fedfd5a0..de1bfe968 100644
--- a/backend/src/server/routes/v1/secret-sharing-router.ts
+++ b/backend/src/server/routes/v1/secret-sharing-router.ts
@@ -80,7 +80,7 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
orgId: req.permission?.orgId
});
- // only return secret if it exists and has no password set
+ // return undefined if it does not exist, has password set or has no more views allowed.
if (!sharedSecret || sharedSecret.password) return undefined;
return {
diff --git a/backend/src/services/secret-sharing/secret-sharing-service.ts b/backend/src/services/secret-sharing/secret-sharing-service.ts
index 14a11a65e..b37f8a771 100644
--- a/backend/src/services/secret-sharing/secret-sharing-service.ts
+++ b/backend/src/services/secret-sharing/secret-sharing-service.ts
@@ -179,10 +179,12 @@ export const secretSharingServiceFactory = ({
if (accessType === SecretSharingAccessType.Organization && orgId !== sharedSecret.orgId)
throw new UnauthorizedError();
- if (sharedSecret.password) return undefined;
+ await checkIfExpired(sharedSecret, sharedSecretId);
+
+ if (sharedSecret.password !== null) return undefined;
- // we only update the view count when secret has no password, when password is set view count is updated when we validate the password.
- manageSecretViewCount(sharedSecret, sharedSecretId);
+ // decrement when we are sure the user will view secret.
+ await decrementSecretViewCount(sharedSecret, sharedSecretId);
return {
...sharedSecret,
@@ -215,13 +217,11 @@ export const secretSharingServiceFactory = ({
if (accessType === SecretSharingAccessType.Organization && orgId !== sharedSecret.orgId)
throw new UnauthorizedError();
- if (!sharedSecret.password) return undefined;
-
const isMatch = await bcrypt.compare(password, sharedSecret.password);
- if (!isMatch) return undefined;
+ if (!isMatch) return undefined
- // reduce view count when we are sure the password matches.
- manageSecretViewCount(sharedSecret, sharedSecretId)
+ // we reduce the view count when we are sure the password matches.
+ await decrementSecretViewCount(sharedSecret, sharedSecretId);
return {
...sharedSecret,
@@ -240,7 +240,8 @@ export const secretSharingServiceFactory = ({
return deletedSharedSecret;
};
- const manageSecretViewCount = async (sharedSecret: any, sharedSecretId: string) => {
+ /** Checks if secret is expired and throws error if true */
+ const checkIfExpired = async (sharedSecret: any, sharedSecretId: string) => {
const { expiresAt, expiresAfterViews } = sharedSecret;
if (expiresAt !== null && expiresAt < new Date()) {
@@ -258,6 +259,10 @@ export const secretSharingServiceFactory = ({
message: "Access denied: Secret has expired by view count"
});
}
+ }
+
+ const decrementSecretViewCount = async (sharedSecret: any, sharedSecretId: string) => {
+ const { expiresAfterViews } = sharedSecret;
if (expiresAfterViews) {
// decrement view count if view count expiry set
diff --git a/frontend/src/hooks/api/secretSharing/queries.ts b/frontend/src/hooks/api/secretSharing/queries.ts
index b697b92b4..acf091e5b 100644
--- a/frontend/src/hooks/api/secretSharing/queries.ts
+++ b/frontend/src/hooks/api/secretSharing/queries.ts
@@ -2,7 +2,7 @@ import { useQuery } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
-import { TSharedSecret, TViewSharedSecretResponse, ValidateSecretPassword } from "./types";
+import { TSharedSecret, TViewSharedSecretResponse } from "./types";
export const secretSharingKeys = {
allSharedSecrets: () => ["sharedSecrets"] as const,
@@ -77,7 +77,7 @@ export const fetchSecretIfPasswordIsValid = async (
hashedHex: string,
password: string,
) => {
- const { data } = await apiRequest.post(
+ const { data } = await apiRequest.post(
`/api/v1/secret-sharing/public/${sharedSecretId}/validate`,
{
hashedHex,
diff --git a/frontend/src/hooks/api/secretSharing/types.ts b/frontend/src/hooks/api/secretSharing/types.ts
index 253321cbf..708780fe4 100644
--- a/frontend/src/hooks/api/secretSharing/types.ts
+++ b/frontend/src/hooks/api/secretSharing/types.ts
@@ -31,7 +31,6 @@ export type TViewSharedSecretResponse = {
tag: string;
accessType: SecretSharingAccessType;
orgName?: string;
- password?: string;
};
export type TDeleteSharedSecretRequest = {
@@ -41,8 +40,4 @@ export type TDeleteSharedSecretRequest = {
export enum SecretSharingAccessType {
Anyone = "anyone",
Organization = "organization"
-}
-
-export type ValidateSecretPassword = {
- isValid: boolean
-}
+}
\ No newline at end of file
diff --git a/frontend/src/views/ViewSecretPublicPage/ViewSecretPublicPage.tsx b/frontend/src/views/ViewSecretPublicPage/ViewSecretPublicPage.tsx
index fecf72659..27f1d37bd 100644
--- a/frontend/src/views/ViewSecretPublicPage/ViewSecretPublicPage.tsx
+++ b/frontend/src/views/ViewSecretPublicPage/ViewSecretPublicPage.tsx
@@ -5,12 +5,13 @@ import { useRouter } from "next/router";
import { faArrowRight } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { useGetActiveSharedSecretById } from "@app/hooks/api/secretSharing";
+import { TViewSharedSecretResponse, useGetActiveSharedSecretById } from "@app/hooks/api/secretSharing";
import { SecretContainer, SecretErrorContainer, PasswordContainer } from "./components";
export const ViewSecretPublicPage = () => {
const [secret, setSecret] = useState(null)
+ const [error, setError] = useState(null)
const router = useRouter();
const { id, key: urlEncodedPublicKey } = router.query;
@@ -18,17 +19,18 @@ export const ViewSecretPublicPage = () => {
? urlEncodedPublicKey.toString().split("-")
: ["", ""];
- const { data, error } = useGetActiveSharedSecretById({
+ const { data: fetchSecret, error: fetchError, isLoading } = useGetActiveSharedSecretById({
sharedSecretId: id as string,
hashedHex
});
useEffect(() => {
- if (data) setSecret(data)
- }, [data])
+ if (fetchSecret) setSecret(fetchSecret)
+ if (fetchError) setError(fetchError)
+ }, [fetchSecret, fetchError])
- const handleSecret = useCallback((val: any) => {
- setSecret(val)
+ const handleSecret = useCallback((value: TViewSharedSecretResponse) => {
+ setSecret(value)
}, [setSecret])
return (
@@ -62,13 +64,19 @@ export const ViewSecretPublicPage = () => {
- {!secret ? (
-
- ) : (
- key &&
- )
- }
- {error && }
+ {!isLoading && (
+ <>
+ {!error && !secret && (
+
+ )}
+ {!error && secret && key && }
+ {error && }
+ >
+ )}
diff --git a/frontend/src/views/ViewSecretPublicPage/components/PasswordContainer.tsx b/frontend/src/views/ViewSecretPublicPage/components/PasswordContainer.tsx
index ceef5b291..e79a3b5b1 100644
--- a/frontend/src/views/ViewSecretPublicPage/components/PasswordContainer.tsx
+++ b/frontend/src/views/ViewSecretPublicPage/components/PasswordContainer.tsx
@@ -6,7 +6,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { Button, FormControl, IconButton, Input } from "@app/components/v2";
-import { fetchSecretIfPasswordIsValid } from "@app/hooks/api/secretSharing";
+import { fetchSecretIfPasswordIsValid, TViewSharedSecretResponse } from "@app/hooks/api/secretSharing";
import { createNotification } from "@app/components/notifications";
type Props = {
@@ -33,7 +33,7 @@ export const PasswordContainer = ({ secretId, hashedHex, handleSecret }: Props)
const onFormSubmit = async ({ password }: FormData) => {
try {
- const secret = await fetchSecretIfPasswordIsValid(
+ const secret: TViewSharedSecretResponse = await fetchSecretIfPasswordIsValid(
secretId,
hashedHex,
password,
@@ -71,7 +71,7 @@ export const PasswordContainer = ({ secretId, hashedHex, handleSecret }: Props)
label="Password"
>