Merge pull request #2152 from aheruz/feat/ENG-985-secret-share-organization

feat(ENG-985): secret share within organization
This commit is contained in:
Maidul Islam
2024-07-19 10:30:32 -04:00
committed by GitHub
15 changed files with 186 additions and 39 deletions
@@ -0,0 +1,23 @@
import { Knex } from "knex";
import { SecretSharingAccessType } from "@app/lib/types";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasColumn = await knex.schema.hasColumn(TableName.SecretSharing, "accessType");
if (!hasColumn) {
await knex.schema.table(TableName.SecretSharing, (table) => {
table.string("accessType").notNullable().defaultTo(SecretSharingAccessType.Anyone);
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasColumn = await knex.schema.hasColumn(TableName.SecretSharing, "accessType");
if (hasColumn) {
await knex.schema.table(TableName.SecretSharing, (table) => {
table.dropColumn("accessType");
});
}
}
+3
View File
@@ -5,6 +5,8 @@
import { z } from "zod"; import { z } from "zod";
import { SecretSharingAccessType } from "@app/lib/types";
import { TImmutableDBKeys } from "./models"; import { TImmutableDBKeys } from "./models";
export const SecretSharingSchema = z.object({ export const SecretSharingSchema = z.object({
@@ -16,6 +18,7 @@ export const SecretSharingSchema = z.object({
expiresAt: z.date(), expiresAt: z.date(),
userId: z.string().uuid().nullable().optional(), userId: z.string().uuid().nullable().optional(),
orgId: z.string().uuid().nullable().optional(), orgId: z.string().uuid().nullable().optional(),
accessType: z.nativeEnum(SecretSharingAccessType).default(SecretSharingAccessType.Organization),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
expiresAfterViews: z.number().nullable().optional() expiresAfterViews: z.number().nullable().optional()
+5
View File
@@ -47,3 +47,8 @@ export enum EnforcementLevel {
Hard = "hard", Hard = "hard",
Soft = "soft" Soft = "soft"
} }
export enum SecretSharingAccessType {
Anyone = "anyone",
Organization = "organization"
}
+2 -1
View File
@@ -737,7 +737,8 @@ export const registerRoutes = async (
const secretSharingService = secretSharingServiceFactory({ const secretSharingService = secretSharingServiceFactory({
permissionService, permissionService,
secretSharingDAL secretSharingDAL,
orgDAL
}); });
const secretApprovalRequestService = secretApprovalRequestServiceFactory({ const secretApprovalRequestService = secretApprovalRequestServiceFactory({
@@ -1,6 +1,7 @@
import { z } from "zod"; import { z } from "zod";
import { SecretSharingSchema } from "@app/db/schemas"; import { SecretSharingSchema } from "@app/db/schemas";
import { SecretSharingAccessType } from "@app/lib/types";
import { import {
publicEndpointLimit, publicEndpointLimit,
publicSecretShareCreationLimit, publicSecretShareCreationLimit,
@@ -55,14 +56,18 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
iv: true, iv: true,
tag: true, tag: true,
expiresAt: true, expiresAt: true,
expiresAfterViews: true expiresAfterViews: true,
accessType: true
}).extend({
orgName: z.string().optional()
}) })
} }
}, },
handler: async (req) => { handler: async (req) => {
const sharedSecret = await req.server.services.secretSharing.getActiveSharedSecretByIdAndHashedHex( const sharedSecret = await req.server.services.secretSharing.getActiveSharedSecretByIdAndHashedHex(
req.params.id, req.params.id,
req.query.hashedHex req.query.hashedHex,
req.permission?.orgId
); );
if (!sharedSecret) return undefined; if (!sharedSecret) return undefined;
return { return {
@@ -70,7 +75,9 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
iv: sharedSecret.iv, iv: sharedSecret.iv,
tag: sharedSecret.tag, tag: sharedSecret.tag,
expiresAt: sharedSecret.expiresAt, expiresAt: sharedSecret.expiresAt,
expiresAfterViews: sharedSecret.expiresAfterViews expiresAfterViews: sharedSecret.expiresAfterViews,
accessType: sharedSecret.accessType,
orgName: sharedSecret.orgName
}; };
} }
}); });
@@ -104,7 +111,8 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
tag, tag,
hashedHex, hashedHex,
expiresAt: new Date(expiresAt), expiresAt: new Date(expiresAt),
expiresAfterViews expiresAfterViews,
accessType: SecretSharingAccessType.Anyone
}); });
return { id: sharedSecret.id }; return { id: sharedSecret.id };
} }
@@ -123,7 +131,8 @@ 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(),
accessType: z.nativeEnum(SecretSharingAccessType).default(SecretSharingAccessType.Organization)
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -145,7 +154,8 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
tag, tag,
hashedHex, hashedHex,
expiresAt: new Date(expiresAt), expiresAt: new Date(expiresAt),
expiresAfterViews expiresAfterViews,
accessType: req.body.accessType
}); });
return { id: sharedSecret.id }; return { id: sharedSecret.id };
} }
@@ -1,6 +1,8 @@
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { SecretSharingAccessType } from "@app/lib/types";
import { TOrgDALFactory } from "../org/org-dal";
import { TSecretSharingDALFactory } from "./secret-sharing-dal"; import { TSecretSharingDALFactory } from "./secret-sharing-dal";
import { import {
TCreatePublicSharedSecretDTO, TCreatePublicSharedSecretDTO,
@@ -12,13 +14,15 @@ import {
type TSecretSharingServiceFactoryDep = { type TSecretSharingServiceFactoryDep = {
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
secretSharingDAL: TSecretSharingDALFactory; secretSharingDAL: TSecretSharingDALFactory;
orgDAL: TOrgDALFactory;
}; };
export type TSecretSharingServiceFactory = ReturnType<typeof secretSharingServiceFactory>; export type TSecretSharingServiceFactory = ReturnType<typeof secretSharingServiceFactory>;
export const secretSharingServiceFactory = ({ export const secretSharingServiceFactory = ({
permissionService, permissionService,
secretSharingDAL secretSharingDAL,
orgDAL
}: TSecretSharingServiceFactoryDep) => { }: TSecretSharingServiceFactoryDep) => {
const createSharedSecret = async (createSharedSecretInput: TCreateSharedSecretDTO) => { const createSharedSecret = async (createSharedSecretInput: TCreateSharedSecretDTO) => {
const { const {
@@ -30,6 +34,7 @@ export const secretSharingServiceFactory = ({
encryptedValue, encryptedValue,
iv, iv,
tag, tag,
accessType,
hashedHex, hashedHex,
expiresAt, expiresAt,
expiresAfterViews expiresAfterViews
@@ -62,13 +67,14 @@ export const secretSharingServiceFactory = ({
expiresAt, expiresAt,
expiresAfterViews, expiresAfterViews,
userId: actorId, userId: actorId,
orgId orgId,
accessType
}); });
return { id: newSharedSecret.id }; return { id: newSharedSecret.id };
}; };
const createPublicSharedSecret = async (createSharedSecretInput: TCreatePublicSharedSecretDTO) => { const createPublicSharedSecret = async (createSharedSecretInput: TCreatePublicSharedSecretDTO) => {
const { encryptedValue, iv, tag, hashedHex, expiresAt, expiresAfterViews } = createSharedSecretInput; const { encryptedValue, iv, tag, hashedHex, expiresAt, expiresAfterViews, accessType } = createSharedSecretInput;
if (new Date(expiresAt) < new Date()) { if (new Date(expiresAt) < new Date()) {
throw new BadRequestError({ message: "Expiration date cannot be in the past" }); throw new BadRequestError({ message: "Expiration date cannot be in the past" });
} }
@@ -92,7 +98,8 @@ export const secretSharingServiceFactory = ({
tag, tag,
hashedHex, hashedHex,
expiresAt, expiresAt,
expiresAfterViews expiresAfterViews,
accessType
}); });
return { id: newSharedSecret.id }; return { id: newSharedSecret.id };
}; };
@@ -105,9 +112,21 @@ export const secretSharingServiceFactory = ({
return userSharedSecrets; return userSharedSecrets;
}; };
const getActiveSharedSecretByIdAndHashedHex = async (sharedSecretId: string, hashedHex: string) => { const getActiveSharedSecretByIdAndHashedHex = async (sharedSecretId: string, hashedHex: string, orgId?: string) => {
const sharedSecret = await secretSharingDAL.findOne({ id: sharedSecretId, hashedHex }); const sharedSecret = await secretSharingDAL.findOne({ id: sharedSecretId, hashedHex });
if (!sharedSecret) return; if (!sharedSecret) return;
const orgName = sharedSecret.orgId ? (await orgDAL.findOrgById(sharedSecret.orgId))?.name : "";
// Support organization level access for secret sharing
if (sharedSecret.accessType === SecretSharingAccessType.Organization && orgId !== sharedSecret.orgId) {
return {
...sharedSecret,
encryptedValue: "",
iv: "",
tag: "",
orgName
};
}
if (sharedSecret.expiresAt && sharedSecret.expiresAt < new Date()) { if (sharedSecret.expiresAt && sharedSecret.expiresAt < new Date()) {
return; return;
} }
@@ -118,7 +137,10 @@ export const secretSharingServiceFactory = ({
} }
await secretSharingDAL.updateById(sharedSecretId, { $decr: { expiresAfterViews: 1 } }); await secretSharingDAL.updateById(sharedSecretId, { $decr: { expiresAfterViews: 1 } });
} }
return sharedSecret; if (sharedSecret.accessType === SecretSharingAccessType.Organization && orgId === sharedSecret.orgId) {
return { ...sharedSecret, orgName };
}
return { ...sharedSecret, orgName: undefined };
}; };
const deleteSharedSecretById = async (deleteSharedSecretInput: TDeleteSharedSecretDTO) => { const deleteSharedSecretById = async (deleteSharedSecretInput: TDeleteSharedSecretDTO) => {
@@ -1,3 +1,5 @@
import { SecretSharingAccessType } from "@app/lib/types";
import { ActorAuthMethod, ActorType } from "../auth/auth-type"; import { ActorAuthMethod, ActorType } from "../auth/auth-type";
export type TSharedSecretPermission = { export type TSharedSecretPermission = {
@@ -6,6 +8,7 @@ export type TSharedSecretPermission = {
actorAuthMethod: ActorAuthMethod; actorAuthMethod: ActorAuthMethod;
actorOrgId: string; actorOrgId: string;
orgId: string; orgId: string;
accessType?: SecretSharingAccessType;
}; };
export type TCreatePublicSharedSecretDTO = { export type TCreatePublicSharedSecretDTO = {
@@ -15,6 +18,7 @@ export type TCreatePublicSharedSecretDTO = {
hashedHex: string; hashedHex: string;
expiresAt: Date; expiresAt: Date;
expiresAfterViews: number; expiresAfterViews: number;
accessType: SecretSharingAccessType;
}; };
export type TCreateSharedSecretDTO = TSharedSecretPermission & TCreatePublicSharedSecretDTO; export type TCreateSharedSecretDTO = TSharedSecretPermission & TCreatePublicSharedSecretDTO;
@@ -21,7 +21,8 @@ With its zero-knowledge architecture, secrets shared via Infisical remain unread
zero knowledge architecture. zero knowledge architecture.
</Note> </Note>
3. Click on the **Share Secret** button. Set the secret, its expiration time as well as the number of views allowed. It expires as soon as any of the conditions are met. 3. Click on the **Share Secret** button. Set the secret, its expiration time and specify if the secret can be viewed only once. It expires as soon as any of the conditions are met.
Also, specify if the secret can be accessed by anyone or only people within your organization.
![Add View-Bound Sharing Secret](../../images/platform/secret-sharing/create-new-secret.png) ![Add View-Bound Sharing Secret](../../images/platform/secret-sharing/create-new-secret.png)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 39 KiB

@@ -21,7 +21,7 @@ export const EmptyState = ({
}: Props) => ( }: Props) => (
<div <div
className={twMerge( className={twMerge(
"flex w-full flex-col items-center bg-mineshaft-800 px-2 pt-6 text-bunker-300", "flex w-full flex-col items-center bg-mineshaft-800 px-2 pt-4 text-bunker-300",
className className
)} )}
> >
@@ -2,7 +2,7 @@ import { useQuery } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request"; import { apiRequest } from "@app/config/request";
import { TSharedSecret, TViewSharedSecretResponse } from "./types"; import { SecretSharingAccessType, TSharedSecret, TViewSharedSecretResponse } from "./types";
export const useGetSharedSecrets = () => { export const useGetSharedSecrets = () => {
return useQuery({ return useQuery({
@@ -17,7 +17,7 @@ export const useGetSharedSecrets = () => {
export const useGetActiveSharedSecretByIdAndHashedHex = (id: string, hashedHex: string) => { export const useGetActiveSharedSecretByIdAndHashedHex = (id: string, hashedHex: string) => {
return useQuery<TViewSharedSecretResponse, [string]>({ return useQuery<TViewSharedSecretResponse, [string]>({
queryFn: async () => { queryFn: async () => {
if(!id || !hashedHex) return Promise.resolve({ encryptedValue: "", iv: "", tag: "" }); if(!id || !hashedHex) return Promise.resolve({ encryptedValue: "", iv: "", tag: "", accessType: SecretSharingAccessType.Organization, orgName: "" });
const { data } = await apiRequest.get<TViewSharedSecretResponse>( const { data } = await apiRequest.get<TViewSharedSecretResponse>(
`/api/v1/secret-sharing/public/${id}?hashedHex=${hashedHex}` `/api/v1/secret-sharing/public/${id}?hashedHex=${hashedHex}`
); );
@@ -25,6 +25,8 @@ export const useGetActiveSharedSecretByIdAndHashedHex = (id: string, hashedHex:
encryptedValue: data.encryptedValue, encryptedValue: data.encryptedValue,
iv: data.iv, iv: data.iv,
tag: data.tag, tag: data.tag,
accessType: data.accessType,
orgName: data.orgName
}; };
} }
}); });
@@ -13,14 +13,22 @@ export type TCreateSharedSecretRequest = {
hashedHex: string; hashedHex: string;
expiresAt: Date; expiresAt: Date;
expiresAfterViews: number; expiresAfterViews: number;
accessType: SecretSharingAccessType;
}; };
export type TViewSharedSecretResponse = { export type TViewSharedSecretResponse = {
encryptedValue: string; encryptedValue: string;
iv: string; iv: string;
tag: string; tag: string;
accessType: SecretSharingAccessType;
orgName?: string;
}; };
export type TDeleteSharedSecretRequest = { export type TDeleteSharedSecretRequest = {
sharedSecretId: string; sharedSecretId: string;
}; };
export enum SecretSharingAccessType {
Anyone = "anyone",
Organization = "organization"
}
@@ -1,5 +1,6 @@
import crypto from "crypto"; import crypto from "crypto";
import { useEffect, useRef } from "react";
import { Controller } from "react-hook-form"; import { Controller } from "react-hook-form";
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import * as yup from "yup"; import * as yup from "yup";
@@ -7,13 +8,14 @@ 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, Checkbox, FormControl, Input, ModalClose, Select, SelectItem } from "@app/components/v2";
import { 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"), expiresAfterSingleView: yup.boolean().required().label("Expires After Views"),
expiresInValue: yup.number().min(1).required().label("Expiration Value"), expiresInValue: yup.number().min(1).required().label("Expiration Value"),
expiresInUnit: yup.string().required().label("Expiration Unit") expiresInUnit: yup.string().required().label("Expiration Unit"),
accessType: yup.string().required().label("General Access")
}); });
export type FormData = yup.InferType<typeof schema>; export type FormData = yup.InferType<typeof schema>;
@@ -35,6 +37,14 @@ export const AddShareSecretForm = ({
setNewSharedSecret: (value: string) => void; setNewSharedSecret: (value: string) => void;
isInputDisabled?: boolean; isInputDisabled?: boolean;
}) => { }) => {
const isMounted = useRef(true);
useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
const publicSharedSecretCreator = useCreatePublicSharedSecret(); const publicSharedSecretCreator = useCreatePublicSharedSecret();
const privateSharedSecretCreator = useCreateSharedSecret(); const privateSharedSecretCreator = useCreateSharedSecret();
const createSharedSecret = isPublic ? publicSharedSecretCreator : privateSharedSecretCreator; const createSharedSecret = isPublic ? publicSharedSecretCreator : privateSharedSecretCreator;
@@ -65,7 +75,8 @@ export const AddShareSecretForm = ({
value, value,
expiresInValue, expiresInValue,
expiresInUnit, expiresInUnit,
expiresAfterSingleView expiresAfterSingleView,
accessType
}: FormData) => { }: FormData) => {
try { try {
const key = crypto.randomBytes(16).toString("hex"); const key = crypto.randomBytes(16).toString("hex");
@@ -89,18 +100,21 @@ export const AddShareSecretForm = ({
tag, tag,
hashedHex, hashedHex,
expiresAt, expiresAt,
expiresAfterViews: expiresAfterSingleView ? 1 : 1000 expiresAfterViews: expiresAfterSingleView ? 1 : 1000,
accessType: accessType as SecretSharingAccessType
}); });
setNewSharedSecret(
`${window.location.origin}/shared/secret/${id}?key=${encodeURIComponent(
hashedHex
)}-${encodeURIComponent(key)}`
);
createNotification({ if (isMounted.current) {
text: "Successfully created a shared secret", setNewSharedSecret(
type: "success" `${window.location.origin}/shared/secret/${id}?key=${encodeURIComponent(
}); hashedHex
)}-${encodeURIComponent(key)}`
);
createNotification({
text: "Successfully created a shared secret",
type: "success"
});
}
} catch (err) { } catch (err) {
console.error(err); console.error(err);
const axiosError = err as AxiosError; const axiosError = err as AxiosError;
@@ -143,7 +157,7 @@ export const AddShareSecretForm = ({
)} )}
/> />
</div> </div>
<div className="flex w-full flex-col md:flex-row justify-start"> <div className="flex w-full flex-col md:flex-row justify-stretch">
<div className="flex justify-start"> <div className="flex justify-start">
<div className="flex justify-start"> <div className="flex justify-start">
<div className="flex w-full justify-center pr-2"> <div className="flex w-full justify-center pr-2">
@@ -188,8 +202,8 @@ export const AddShareSecretForm = ({
</div> </div>
</div> </div>
</div> </div>
<div className="sm:w-1/7 mx-auto items-center justify-center px-6 hidden md:flex"> <div className="sm:w-1/7 mx-auto items-center justify-center hidden md:flex">
<p className="px-4 mt-2 text-sm text-gray-400">AND</p> <p className="mt-2 text-sm text-gray-400">AND</p>
</div> </div>
<div className="items-center pb-4 md:pb-0 md:pt-2 flex"> <div className="items-center pb-4 md:pb-0 md:pt-2 flex">
<Controller <Controller
@@ -227,7 +241,25 @@ export const AddShareSecretForm = ({
</div> </div>
</div> </div>
</div> </div>
<div className={`flex items-center ${!inModal && "justify-start pt-2"}`}> {!isPublic && (
<Controller
control={control}
name="accessType"
defaultValue="organization"
render={({ field: { onChange, ...field } }) => (
<FormControl label="General Access">
<Select
{...field}
onValueChange={(e) => onChange(e)}
>
<SelectItem value="organization">People within your organization</SelectItem>
<SelectItem value="anyone">Anyone</SelectItem>
</Select>
</FormControl>
)}
/>
)}
<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" : "Share Secret"}
</Button> </Button>
@@ -20,12 +20,14 @@ export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean
const [hashedHex, key] = urlEncodedPublicKey const [hashedHex, key] = urlEncodedPublicKey
? urlEncodedPublicKey.toString().split("-") ? urlEncodedPublicKey.toString().split("-")
: ["", ""]; : ["", ""];
const publicKey = decodeURIComponent(urlEncodedPublicKey as string); const publicKey = decodeURIComponent(urlEncodedPublicKey as string);
const { isLoading, data } = useGetActiveSharedSecretByIdAndHashedHex( const { isLoading, data } = useGetActiveSharedSecretByIdAndHashedHex(
id as string, id as string,
hashedHex as string hashedHex as string
); );
const accessType = data?.accessType;
const orgName = data?.orgName;
const decryptedSecret = useMemo(() => { const decryptedSecret = useMemo(() => {
if (data && data.encryptedValue && publicKey) { if (data && data.encryptedValue && publicKey) {
@@ -87,6 +89,8 @@ export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean
decryptedSecret={decryptedSecret} decryptedSecret={decryptedSecret}
isUrlCopied={isUrlCopied} isUrlCopied={isUrlCopied}
copyUrlToClipboard={copyUrlToClipboard} copyUrlToClipboard={copyUrlToClipboard}
accessType={accessType}
orgName={orgName}
/> />
)} )}
</div> </div>
@@ -1,14 +1,17 @@
import { faCheck, faCopy, faEye, faEyeSlash, faKey } from "@fortawesome/free-solid-svg-icons"; import { faArrowRight, faCheck, faCopy, faEye, faEyeSlash, faKey } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { EmptyState, IconButton, Td, Tr } from "@app/components/v2"; import { Button, EmptyState, IconButton, Td, Tr } from "@app/components/v2";
import { useToggle } from "@app/hooks"; import { useToggle } from "@app/hooks";
import { SecretSharingAccessType } from "@app/hooks/api/secretSharing/types";
type Props = { type Props = {
isLoading: boolean; isLoading: boolean;
decryptedSecret: string; decryptedSecret: string;
isUrlCopied: boolean; isUrlCopied: boolean;
copyUrlToClipboard: () => void; copyUrlToClipboard: () => void;
accessType?: SecretSharingAccessType;
orgName?: string;
}; };
const replaceContentWithDot = (str: string) => { const replaceContentWithDot = (str: string) => {
@@ -24,20 +27,49 @@ export const SecretTable = ({
isLoading, isLoading,
decryptedSecret, decryptedSecret,
isUrlCopied, isUrlCopied,
copyUrlToClipboard copyUrlToClipboard,
accessType,
orgName
}: Props) => { }: Props) => {
const [isVisible, setIsVisible] = useToggle(false); const [isVisible, setIsVisible] = useToggle(false);
const title = orgName
? (<p>Someone from <strong>{orgName}</strong> organization has shared a secret with you</p>)
: (<p>You need to be logged in to view this secret</p>);
return ( return (
<div className="flex w-full items-center justify-center rounded-md border border-solid border-mineshaft-700 bg-mineshaft-800 p-2"> <div className="flex w-full items-center justify-center rounded-md border border-solid border-mineshaft-700 bg-mineshaft-800 p-2">
{isLoading && <div className="bg-mineshaft-800 text-center text-bunker-400">Loading...</div>} {isLoading && <div className="bg-mineshaft-800 text-center text-bunker-400">Loading...</div>}
{!isLoading && !decryptedSecret && ( {!isLoading && !decryptedSecret && accessType !== SecretSharingAccessType.Organization && (
<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="Secret has either expired or does not exist!" icon={faKey} /> <EmptyState title="Secret has either expired or does not exist!" icon={faKey} />
</Td> </Td>
</Tr> </Tr>
)} )}
{!isLoading && !decryptedSecret && accessType === SecretSharingAccessType.Organization && (
<Tr>
<Td colSpan={4} className="bg-mineshaft-800 text-center text-bunker-4000">
<EmptyState title={title} icon={faKey}>
<div className="flex flex-1 flex-col items-center justify-center pt-6">
<a
href="/login"
target="_blank"
rel="noopener noreferrer"
>
<Button
colorSchema="primary"
size="sm"
onClick={() => {}}
rightIcon={<FontAwesomeIcon icon={faArrowRight} className="ml-2" />}
>
Login into <strong>{orgName}</strong> to view this secret
</Button>
</a>
</div>
</EmptyState>
</Td>
</Tr>
)}
{!isLoading && decryptedSecret && ( {!isLoading && decryptedSecret && (
<div className="dark relative flex h-full w-full items-center overflow-y-auto rounded-md border border-mineshaft-700 bg-mineshaft-900 p-2 pr-2 md:p-3"> <div className="dark relative flex h-full w-full items-center overflow-y-auto rounded-md border border-mineshaft-700 bg-mineshaft-900 p-2 pr-2 md:p-3">
<div <div