Revert "feat(api/secrets): view secret value permission"

This commit is contained in:
Maidul Islam
2025-03-05 14:15:02 -05:00
committed by GitHub
parent 8e82813894
commit bf6cfbac7a
70 changed files with 806 additions and 2202 deletions
@@ -19,7 +19,6 @@ import {
import { getKeyValue } from "@app/helpers/parseEnvVar";
import { useCreateFolder, useCreateSecretV3, useCreateWsTag, useGetWsTags } from "@app/hooks/api";
import { SecretType } from "@app/hooks/api/types";
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
const typeSchema = z
.object({
@@ -276,7 +275,7 @@ export const CreateSecretForm = ({ secretPath = "/", onClose }: Props) => {
isMulti
options={environments.filter((environment) =>
permission.can(
ProjectPermissionSecretActions.Create,
ProjectPermissionActions.Create,
subject(ProjectPermissionSub.Secrets, {
environment: environment.slug,
secretPath,
@@ -25,7 +25,6 @@ import {
ModalTrigger,
Tooltip
} from "@app/components/v2";
import { Blur } from "@app/components/v2/Blur";
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { useToggle } from "@app/hooks";
@@ -40,7 +39,6 @@ type Props = {
isVisible?: boolean;
isImportedSecret: boolean;
environment: string;
secretValueHidden: boolean;
secretPath: string;
onSecretCreate: (env: string, key: string, value: string) => Promise<void>;
onSecretUpdate: (
@@ -60,7 +58,6 @@ export const SecretEditRow = ({
isImportedSecret,
onSecretUpdate,
secretName,
secretValueHidden,
onSecretCreate,
onSecretDelete,
environment,
@@ -143,29 +140,24 @@ export const SecretEditRow = ({
/>
<div className="flex-grow border-r border-r-mineshaft-600 pl-1 pr-2">
{secretValueHidden ? (
<Blur tooltipText="You do not have permission to read the value of this secret." />
) : (
<Controller
disabled={isImportedSecret && !defaultValue}
control={control}
name="value"
render={({ field }) => (
<InfisicalSecretInput
{...field}
isReadOnly={isImportedSecret}
value={field.value as string}
key="secret-input"
isVisible={isVisible}
secretPath={secretPath}
environment={environment}
isImport={isImportedSecret}
/>
)}
/>
)}
<Controller
disabled={isImportedSecret && !defaultValue}
control={control}
name="value"
render={({ field }) => (
<InfisicalSecretInput
{...field}
isReadOnly={isImportedSecret}
value={field.value as string}
key="secret-input"
isVisible={isVisible}
secretPath={secretPath}
environment={environment}
isImport={isImportedSecret}
/>
)}
/>
</div>
<div
className={twMerge(
"flex w-24 justify-center space-x-3 pl-2 transition-all",
@@ -219,7 +211,6 @@ export const SecretEditRow = ({
<div className="opacity-0 group-hover:opacity-100">
<Tooltip content="Copy Secret">
<IconButton
isDisabled={secretValueHidden}
ariaLabel="copy-value"
onClick={handleCopySecretToClipboard}
variant="plain"
@@ -3,7 +3,6 @@ import { faLock } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Td, Tooltip, Tr } from "@app/components/v2";
import { Blur } from "@app/components/v2/Blur";
type Props = {
environments: { name: string; slug: string }[];
@@ -26,7 +25,7 @@ export const SecretNoAccessOverviewTableRow = ({ environments = [], count }: Pro
<div className="text-bunker-300">
<FontAwesomeIcon className="block" icon={faLock} />
</div>
<Blur />
<div className="blur-sm">NO ACCESS</div>
</div>
</Tooltip>
</div>
@@ -221,13 +221,10 @@ export const SecretOverviewTableRow = ({
secretPath={secretPath}
isVisible={isSecretVisible}
secretName={secretKey}
secretValueHidden={secret?.secretValueHidden || false}
defaultValue={
secret?.secretValueHidden
? ""
: secret?.valueOverride ||
secret?.value ||
importedSecret?.secret?.value
secret?.valueOverride ||
secret?.value ||
importedSecret?.secret?.value
}
secretId={secret?.id}
isOverride={Boolean(secret?.valueOverride)}
@@ -10,8 +10,12 @@ import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import { IconButton, Input, Spinner, Tooltip } from "@app/components/v2";
import { ProjectPermissionSub, useProjectPermission, useWorkspace } from "@app/context";
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
import {
ProjectPermissionActions,
ProjectPermissionSub,
useProjectPermission,
useWorkspace
} from "@app/context";
import { useToggle } from "@app/hooks";
import { useUpdateSecretV3 } from "@app/hooks/api";
import { SecretType, SecretV3RawSanitized } from "@app/hooks/api/types";
@@ -51,8 +55,8 @@ function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath }
secretTags: (secretDetails?.tags || []).map((i) => i.slug)
});
const isSecretInEnvReadOnly =
permission.can(ProjectPermissionSecretActions.DescribeSecret, secretPermissionSubject) &&
permission.cannot(ProjectPermissionSecretActions.Edit, secretPermissionSubject);
permission.can(ProjectPermissionActions.Read, secretPermissionSubject) &&
permission.cannot(ProjectPermissionActions.Edit, secretPermissionSubject);
if (isSecretInEnvReadOnly) {
return true;
}
@@ -110,31 +110,21 @@ export const QuickSearchSecretItem = ({
</Badge>
)}
{isSingleEnv ? (
<Tooltip
isDisabled={!groupSecret?.secretValueHidden}
content={
groupSecret?.secretValueHidden
? "You do not have permission to view this secret value"
: ""
}
<IconButton
size="md"
variant="plain"
colorSchema="secondary"
ariaLabel="Copy secret value"
onClick={(e) => {
e.stopPropagation();
const el = envSlugMap.get(groupSecret.env)?.name;
if (el) {
handleCopy(groupSecret.value!, el);
}
}}
>
<IconButton
size="md"
isDisabled={groupSecret?.secretValueHidden}
variant="plain"
colorSchema="secondary"
ariaLabel="Copy secret value"
onClick={(e) => {
e.stopPropagation();
const el = envSlugMap.get(groupSecret.env)?.name;
if (el) {
handleCopy(groupSecret.value!, el);
}
}}
>
<FontAwesomeIcon icon={isUrlCopied ? faCheck : faCopy} />
</IconButton>
</Tooltip>
<FontAwesomeIcon icon={isUrlCopied ? faCheck : faCopy} />
</IconButton>
) : (
<DropdownMenu>
<DropdownMenuTrigger asChild>
@@ -168,24 +158,14 @@ export const QuickSearchSecretItem = ({
)}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Tooltip
isDisabled={!groupSecret?.secretValueHidden}
content={
groupSecret?.secretValueHidden
? "You do not have permission to view this secret value"
: ""
}
<IconButton
size="md"
variant="plain"
colorSchema="secondary"
ariaLabel="View secret value"
>
<IconButton
size="md"
isDisabled={groupSecret?.secretValueHidden}
variant="plain"
colorSchema="secondary"
ariaLabel="View secret value"
>
<FontAwesomeIcon icon={faEye} />
</IconButton>
</Tooltip>
<FontAwesomeIcon icon={faEye} />
</IconButton>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Hover to Reveal...</DropdownMenuLabel>
@@ -11,7 +11,6 @@ import {
useProjectPermission,
useWorkspace
} from "@app/context";
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
import { usePopUp } from "@app/hooks";
import { useDeleteFolder, useDeleteSecretBatch } from "@app/hooks/api";
import {
@@ -59,7 +58,7 @@ export const SelectionPanel = ({ secretPath, resetSelectedEntries, selectedEntri
// user should have the ability to delete secrets/folders in at least one of the envs
const shouldShowDelete = userAvailableEnvs.some((env) =>
permission.can(
ProjectPermissionSecretActions.Delete,
ProjectPermissionActions.Delete,
subject(ProjectPermissionSub.Secrets, {
environment: env.slug,
secretPath,
@@ -111,7 +110,7 @@ export const SelectionPanel = ({ secretPath, resetSelectedEntries, selectedEntri
(accum: TDeleteSecretBatchDTO["secrets"], secretRecord) => {
const entry = secretRecord[env.slug];
const canDeleteSecret = permission.can(
ProjectPermissionSecretActions.Delete,
ProjectPermissionActions.Delete,
subject(ProjectPermissionSub.Secrets, {
environment: env.slug,
secretPath,
@@ -25,8 +25,7 @@ import {
Spinner,
Switch
} from "@app/components/v2";
import { ProjectPermissionSub, useProjectPermission } from "@app/context";
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context";
import { useDebounce } from "@app/hooks";
import { useMoveSecrets } from "@app/hooks/api";
import { useGetProjectSecretsQuickSearch } from "@app/hooks/api/dashboard";
@@ -96,7 +95,7 @@ const Content = ({
env.slug,
{
missingPermissions: permission.cannot(
ProjectPermissionSecretActions.Delete,
ProjectPermissionActions.Delete,
subject(ProjectPermissionSub.Secrets, {
environment: env.slug,
secretPath: sourceSecretPath,
@@ -25,7 +25,6 @@ import {
useProjectPermission,
useWorkspace
} from "@app/context";
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
import {
useGetImportedSecretsSingleEnv,
@@ -104,16 +103,7 @@ const Page = () => {
const projectSlug = currentWorkspace?.slug || "";
const secretPath = (routerQueryParams.secretPath as string) || "/";
const canReadSecret = permission.can(
ProjectPermissionSecretActions.DescribeSecret,
subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: "*",
secretTags: ["*"]
})
);
const canReadSecretValue = permission.can(
ProjectPermissionSecretActions.ReadValue,
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
@@ -186,7 +176,6 @@ const Page = () => {
orderDirection,
includeImports: canReadSecretImports && filter.include.import,
includeFolders: filter.include.folder,
viewSecretValue: canReadSecretValue,
includeDynamicSecrets: canReadDynamicSecret && filter.include.dynamic,
includeSecrets: canReadSecret && filter.include.secret,
tags: filter.tags
@@ -21,7 +21,6 @@ import {
faTrash
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { AxiosError } from "axios";
import FileSaver from "file-saver";
import { twMerge } from "tailwind-merge";
@@ -55,7 +54,7 @@ import {
import { usePopUp } from "@app/hooks";
import { useCreateFolder, useDeleteSecretBatch, useMoveSecrets } from "@app/hooks/api";
import { fetchProjectSecrets } from "@app/hooks/api/secrets/queries";
import { ApiErrorTypes, SecretType, TApiErrors, WsTag } from "@app/hooks/api/types";
import { SecretType, WsTag } from "@app/hooks/api/types";
import { SecretSearchInput } from "@app/pages/secret-manager/OverviewPage/components/SecretSearchInput";
import {
@@ -153,71 +152,51 @@ export const ActionBar = ({
};
const handleSecretDownload = async () => {
try {
const { secrets: localSecrets, imports: localImportedSecrets } = await fetchProjectSecrets({
workspaceId,
expandSecretReferences: true,
includeImports: true,
environment,
secretPath
});
const secretsPicked = new Set<string>();
const secretsToDownload: { key: string; value?: string; comment?: string }[] = [];
localSecrets.forEach((el) => {
secretsPicked.add(el.secretKey);
secretsToDownload.push({
key: el.secretKey,
value: el.secretValue,
comment: el.secretComment
});
const { secrets: localSecrets, imports: localImportedSecrets } = await fetchProjectSecrets({
workspaceId,
expandSecretReferences: true,
includeImports: true,
environment,
secretPath
});
const secretsPicked = new Set<string>();
const secretsToDownload: { key: string; value?: string; comment?: string }[] = [];
localSecrets.forEach((el) => {
secretsPicked.add(el.secretKey);
secretsToDownload.push({
key: el.secretKey,
value: el.secretValue,
comment: el.secretComment
});
});
for (let i = localImportedSecrets.length - 1; i >= 0; i -= 1) {
for (let j = localImportedSecrets[i].secrets.length - 1; j >= 0; j -= 1) {
const secret = localImportedSecrets[i].secrets[j];
if (!secretsPicked.has(secret.secretKey)) {
secretsToDownload.push({
key: secret.secretKey,
value: secret.secretValue,
comment: secret.secretComment
});
}
secretsPicked.add(secret.secretKey);
}
}
const file = secretsToDownload
.sort((a, b) => a.key.toLowerCase().localeCompare(b.key.toLowerCase()))
.reduce(
(prev, { key, comment, value }, index) =>
prev +
(comment
? `${index === 0 ? "#" : "\n#"} ${comment}\n${key}=${value}\n`
: `${key}=${value}\n`),
""
);
const blob = new Blob([file], { type: "text/plain;charset=utf-8" });
FileSaver.saveAs(blob, `${environment}.env`);
} catch (err) {
if (err instanceof AxiosError) {
const error = err?.response?.data as TApiErrors;
if (error?.error === ApiErrorTypes.ForbiddenError && error.message.includes("readValue")) {
createNotification({
title: "You don't have permission to download secrets",
text: "You don't have permission to view one or more of the secrets in the current folder. Please contact your administrator.",
type: "error"
for (let i = localImportedSecrets.length - 1; i >= 0; i -= 1) {
for (let j = localImportedSecrets[i].secrets.length - 1; j >= 0; j -= 1) {
const secret = localImportedSecrets[i].secrets[j];
if (!secretsPicked.has(secret.secretKey)) {
secretsToDownload.push({
key: secret.secretKey,
value: secret.secretValue,
comment: secret.secretComment
});
return;
}
secretsPicked.add(secret.secretKey);
}
createNotification({
title: "Failed to download secrets",
text: "Please try again later.",
type: "error"
});
}
const file = secretsToDownload
.sort((a, b) => a.key.toLowerCase().localeCompare(b.key.toLowerCase()))
.reduce(
(prev, { key, comment, value }, index) =>
prev +
(comment
? `${index === 0 ? "#" : "\n#"} ${comment}\n${key}=${value}\n`
: `${key}=${value}\n`),
""
);
const blob = new Blob([file], { type: "text/plain;charset=utf-8" });
FileSaver.saveAs(blob, `${environment}.env`);
};
const handleSecretBulkDelete = async () => {
@@ -9,14 +9,12 @@ import {
faPlus,
faShare,
faTag,
faTrash,
faTriangleExclamation
faTrash
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { Link } from "@tanstack/react-router";
import { format } from "date-fns";
import { twMerge } from "tailwind-merge";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications";
@@ -46,7 +44,6 @@ import {
useProjectPermission,
useWorkspace
} from "@app/context";
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
import { usePopUp, useToggle } from "@app/hooks";
import { useGetSecretVersion } from "@app/hooks/api";
import { useGetSecretAccessList } from "@app/hooks/api/secrets/queries";
@@ -125,7 +122,7 @@ export const SecretDetailSidebar = ({
const selectTagSlugs = selectedTags.map((i) => i.slug);
const cannotEditSecret = permission.cannot(
ProjectPermissionSecretActions.Edit,
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
@@ -133,29 +130,16 @@ export const SecretDetailSidebar = ({
secretTags: selectTagSlugs
})
);
const cannotReadSecretValue = permission.cannot(
ProjectPermissionSecretActions.ReadValue,
subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: secretKey,
secretTags: selectTagSlugs
})
);
const isReadOnly =
permission.can(
ProjectPermissionSecretActions.DescribeSecret,
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: secretKey,
secretTags: selectTagSlugs
})
) &&
cannotEditSecret &&
cannotReadSecretValue;
) && cannotEditSecret;
const overrideAction = watch("overrideAction");
const isOverridden =
@@ -208,16 +192,9 @@ export const SecretDetailSidebar = ({
await onSaveSecret(secret, { ...secret, ...data }, () => reset());
};
const handleReminderSubmit = async (
reminderRepeatDays: number | null | undefined,
reminderNote: string | null | undefined
) => {
await onSaveSecret(
secret,
{ ...secret, reminderRepeatDays, reminderNote, isReminderEvent: true },
() => {}
);
};
const handleReminderSubmit = async (reminderRepeatDays: number | null | undefined, reminderNote: string | null | undefined) => {
await onSaveSecret(secret, { ...secret, reminderRepeatDays, reminderNote, isReminderEvent: true }, () => { });
}
const [createReminderFormOpen, setCreateReminderFormOpen] = useToggle(false);
@@ -236,7 +213,7 @@ export const SecretDetailSidebar = ({
if (data) {
setValue("reminderRepeatDays", data.days, { shouldDirty: false });
setValue("reminderNote", data.note, { shouldDirty: false });
handleReminderSubmit(data.days, data.note);
handleReminderSubmit(data.days, data.note)
}
}}
/>
@@ -284,63 +261,38 @@ export const SecretDetailSidebar = ({
key="secret-value"
control={control}
render={({ field }) => (
<div className="flex items-center gap-2">
<FormControl
className="flex-1"
helperText={
cannotReadSecretValue ? (
<div className="flex space-x-2">
<FontAwesomeIcon
icon={faTriangleExclamation}
className="mt-0.5 text-yellow-400"
/>
<span>
The value of this secret is hidden because you do not have the
read secret value permission.
</span>
</div>
) : undefined
}
label="Value"
>
<div className="flex items-center gap-2">
<InfisicalSecretInput
isReadOnly={isReadOnly}
environment={environment}
secretPath={secretPath}
key="secret-value"
isDisabled={isOverridden || !isAllowed}
containerClassName="text-bunker-300 w-full hover:border-primary-400/50 border border-mineshaft-600 bg-bunker-800 px-2 py-1.5"
{...field}
autoFocus={false}
/>
<Tooltip
content="You don't have permission to view the secret value."
isDisabled={!secret?.secretValueHidden}
>
<Button
isDisabled={secret?.secretValueHidden}
className="px-2 py-[0.43rem] font-normal"
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faShare} />}
onClick={() => {
const value = secret?.valueOverride ?? secret?.value;
if (value) {
handleSecretShare(value);
}
}}
>
Share
</Button>
</Tooltip>
</div>
</FormControl>
</div>
<FormControl label="Value">
<InfisicalSecretInput
isReadOnly={isReadOnly}
environment={environment}
secretPath={secretPath}
key="secret-value"
isDisabled={isOverridden || !isAllowed}
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-bunker-800 px-2 py-1.5"
{...field}
autoFocus={false}
/>
</FormControl>
)}
/>
)}
</ProjectPermissionCan>
</div>
<div className="ml-1 mt-1.5 flex items-center">
<Button
className="w-full px-2 py-[0.43rem] font-normal"
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faShare} />}
onClick={() => {
const value = secret?.valueOverride ?? secret?.value;
if (value) {
handleSecretShare(value);
}
}}
>
Share
</Button>
</div>
</div>
<div className="mb-2 rounded border border-mineshaft-600 bg-mineshaft-900 p-4 px-0 pb-0">
<div className="mb-4 px-4">
@@ -666,34 +618,51 @@ export const SecretDetailSidebar = ({
<div className="mb-4flex-grow dark cursor-default text-sm text-bunker-300">
<div className="mb-2 pl-1">Version History</div>
<div className="thin-scrollbar flex h-48 flex-col space-y-2 overflow-y-auto overflow-x-hidden rounded-md border border-mineshaft-600 bg-mineshaft-900 p-4 dark:[color-scheme:dark]">
{secretVersion?.map(
({ createdAt, secretValue, version, id, secretValueHidden }, index) => (
<div key={`secret-version-${index + 1}`} className="flex flex-row">
<div key={id} className="flex w-full flex-col space-y-1">
<div className="flex items-center">
<div className="w-10">
<div className="w-fit rounded-md border border-mineshaft-600 bg-mineshaft-700 px-1 text-sm text-mineshaft-300">
v{version}
</div>
{secretVersion?.map(({ createdAt, secretValue, version, id }) => (
<div className="flex flex-row">
<div key={id} className="flex w-full flex-col space-y-1">
<div className="flex items-center">
<div className="w-10">
<div className="w-fit rounded-md border border-mineshaft-600 bg-mineshaft-700 px-1 text-sm text-mineshaft-300">
v{version}
</div>
<div>{format(new Date(createdAt), "Pp")}</div>
</div>
<div className="flex w-full cursor-default">
<div className="relative w-10">
<div className="absolute bottom-0 left-3 top-0 mt-0.5 border-l border-mineshaft-400/60" />
<div>{format(new Date(createdAt), "Pp")}</div>
</div>
<div className="flex w-full cursor-default">
<div className="relative w-10">
<div className="absolute bottom-0 left-3 top-0 mt-0.5 border-l border-mineshaft-400/60" />
</div>
<div className="flex flex-row">
<div className="h-min w-fit rounded-sm bg-primary-500/10 px-1 text-primary-300/70">
Value:
</div>
<div className="flex flex-row">
<div className="h-min w-fit rounded-sm bg-primary-500/10 px-1 text-primary-300/70">
Value:
</div>
<div className="group break-all pl-1 font-mono">
<div className="relative hidden cursor-pointer transition-all duration-200 group-[.show-value]:inline">
<button
type="button"
className="select-none"
onClick={(e) => {
if (secretValueHidden) return;
<div className="group break-all pl-1 font-mono">
<div className="relative hidden cursor-pointer transition-all duration-200 group-[.show-value]:inline">
<button
type="button"
className="select-none"
onClick={(e) => {
navigator.clipboard.writeText(secretValue || "");
const target = e.currentTarget;
target.style.borderBottom = "1px dashed";
target.style.paddingBottom = "-1px";
// Create and insert popup
const popup = document.createElement("div");
popup.className =
"w-16 flex justify-center absolute top-6 left-0 text-xs text-primary-100 bg-mineshaft-800 px-1 py-0.5 rounded-md border border-primary-500/50";
popup.textContent = "Copied!";
target.parentElement?.appendChild(popup);
// Remove popup and border after delay
setTimeout(() => {
popup.remove();
target.style.borderBottom = "none";
}, 3000);
}}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
navigator.clipboard.writeText(secretValue || "");
const target = e.currentTarget;
target.style.borderBottom = "1px dashed";
@@ -711,109 +680,72 @@ export const SecretDetailSidebar = ({
popup.remove();
target.style.borderBottom = "none";
}, 3000);
}}
onKeyDown={(e) => {
if (secretValueHidden) return;
if (e.key === "Enter" || e.key === " ") {
navigator.clipboard.writeText(secretValue || "");
const target = e.currentTarget;
target.style.borderBottom = "1px dashed";
target.style.paddingBottom = "-1px";
// Create and insert popup
const popup = document.createElement("div");
popup.className =
"w-16 flex justify-center absolute top-6 left-0 text-xs text-primary-100 bg-mineshaft-800 px-1 py-0.5 rounded-md border border-primary-500/50";
popup.textContent = "Copied!";
target.parentElement?.appendChild(popup);
// Remove popup and border after delay
setTimeout(() => {
popup.remove();
target.style.borderBottom = "none";
}, 3000);
}
}}
>
<Tooltip
className="break-normal text-xs"
content="You do not have permission to view this secret value"
isDisabled={!secretValueHidden}
>
<span
className={twMerge(
secretValueHidden && "text-xs text-bunker-300 opacity-40"
)}
>
{secretValueHidden ? "Hidden" : secretValue}
</span>
</Tooltip>
</button>
<button
type="button"
className="ml-1 cursor-pointer"
onClick={(e) => {
}
}}
>
{secretValue}
</button>
<button
type="button"
className="ml-1 cursor-pointer"
onClick={(e) => {
e.stopPropagation();
e.currentTarget
.closest(".group")
?.classList.remove("show-value");
}}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.stopPropagation();
e.currentTarget
.closest(".group")
?.classList.remove("show-value");
}}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.stopPropagation();
e.currentTarget
.closest(".group")
?.classList.remove("show-value");
}
}}
>
<FontAwesomeIcon icon={faEyeSlash} />
</button>
</div>
<span className="group-[.show-value]:hidden">
{secretValueHidden ? "******" : secretValue?.replace(/./g, "*")}
<button
type="button"
className="ml-1 cursor-pointer"
onClick={(e) => {
}
}}
>
<FontAwesomeIcon icon={faEyeSlash} />
</button>
</div>
<span className="group-[.show-value]:hidden">
{secretValue?.replace(/./g, "*")}
<button
type="button"
className="ml-1 cursor-pointer"
onClick={(e) => {
e.currentTarget.closest(".group")?.classList.add("show-value");
}}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.currentTarget
.closest(".group")
?.classList.add("show-value");
}}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.currentTarget
.closest(".group")
?.classList.add("show-value");
}
}}
>
<FontAwesomeIcon icon={faEye} />
</button>
</span>
</div>
}
}}
>
<FontAwesomeIcon icon={faEye} />
</button>
</span>
</div>
</div>
</div>
<div
className={`flex items-center justify-center ${version === secretVersion.length ? "hidden" : ""}`}
>
<Tooltip content="Restore Secret Value">
<IconButton
ariaLabel="Restore"
variant="outline_bg"
size="sm"
className="h-8 w-8 rounded-md"
onClick={() => setValue("value", secretValue)}
>
<FontAwesomeIcon icon={faArrowRotateRight} />
</IconButton>
</Tooltip>
</div>
</div>
)
)}
<div
className={`flex items-center justify-center ${version === secretVersion.length ? "hidden" : ""}`}
>
<Tooltip content="Restore Secret Value">
<IconButton
ariaLabel="Restore"
variant="outline_bg"
size="sm"
className="h-8 w-8 rounded-md"
onClick={() => setValue("value", secretValue)}
>
<FontAwesomeIcon icon={faArrowRotateRight} />
</IconButton>
</Tooltip>
</div>
</div>
))}
</div>
</div>
<div className="dark mb-4 flex-grow text-sm text-bunker-300">
@@ -1,4 +1,3 @@
/* eslint-disable no-nested-ternary */
/* eslint-disable simple-import-sort/imports */
import { ProjectPermissionCan } from "@app/components/permissions";
import {
@@ -45,8 +44,6 @@ import {
SecretReferenceTree
} from "@app/components/secrets/SecretReferenceDetails";
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
import { Blur } from "@app/components/v2/Blur";
import {
FontAwesomeSpriteName,
formSchema,
@@ -102,14 +99,8 @@ export const SecretItem = memo(
trigger,
formState: { isDirty, isSubmitting, errors }
} = useForm<TFormSchema>({
defaultValues: {
...secret,
value: secret.secretValueHidden ? "" : secret.value
},
values: {
...secret,
value: secret.secretValueHidden ? "" : secret.value
},
defaultValues: secret,
values: secret,
resolver: zodResolver(formSchema)
});
@@ -132,7 +123,7 @@ export const SecretItem = memo(
const isReadOnly =
permission.can(
ProjectPermissionSecretActions.DescribeSecret,
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
@@ -141,7 +132,7 @@ export const SecretItem = memo(
})
) &&
permission.cannot(
ProjectPermissionSecretActions.Edit,
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
@@ -150,8 +141,6 @@ export const SecretItem = memo(
})
);
const { secretValueHidden } = secret;
const [isSecValueCopied, setIsSecValueCopied] = useToggle(false);
useEffect(() => {
let timer: NodeJS.Timeout;
@@ -283,8 +272,6 @@ export const SecretItem = memo(
/>
)}
/>
) : secretValueHidden ? (
<Blur tooltipText="You do not have permission to read the value of this secret." />
) : (
<Controller
name="value"
@@ -298,7 +285,6 @@ export const SecretItem = memo(
environment={environment}
secretPath={secretPath}
{...field}
defaultValue={secretValueHidden ? "" : undefined}
containerClassName="py-1.5 rounded-md transition-all group-hover:mr-2"
/>
)}
@@ -307,7 +293,6 @@ export const SecretItem = memo(
<div key="actions" className="flex h-8 flex-shrink-0 self-start transition-all">
<Tooltip content="Copy secret">
<IconButton
isDisabled={secret.secretValueHidden}
ariaLabel="copy-value"
variant="plain"
size="sm"
@@ -515,7 +500,6 @@ export const SecretItem = memo(
)}
</ProjectPermissionCan>
<IconButton
isDisabled={secret.secretValueHidden}
className="w-0 overflow-hidden p-0 group-hover:mr-2 group-hover:w-5 data-[state=open]:w-6"
variant="plain"
size="md"
@@ -1,5 +1,4 @@
import { FontAwesomeSymbol, Input, Tooltip } from "@app/components/v2";
import { Blur } from "@app/components/v2/Blur";
import { FontAwesomeSpriteName } from "./SecretListView.utils";
@@ -35,7 +34,13 @@ export const SecretNoAccessListView = ({ count }: Props) => {
className="w-full px-0 blur-sm placeholder:text-red-500 focus:text-bunker-100 focus:ring-transparent"
/>
</div>
<Blur />
<div
className="flex w-80 flex-grow items-center border-x border-mineshaft-600 py-1 pl-4 pr-2"
tabIndex={0}
role="button"
>
<span className="blur">********</span>
</div>
</div>
</Tooltip>
))}
@@ -20,7 +20,6 @@ import {
Tooltip,
Tr
} from "@app/components/v2";
import { Blur } from "@app/components/v2/Blur";
import { useToggle } from "@app/hooks";
import { SecretV3RawSanitized } from "@app/hooks/api/secrets/types";
@@ -121,25 +120,11 @@ export const SecretItem = ({ mode, preSecret, postSecret }: Props) => {
<Td className="border-r border-mineshaft-600">Value</Td>
{isModified && (
<Td className="border-r border-mineshaft-600">
{preSecret?.secretValueHidden ? (
<Blur
className="w-min"
tooltipText="You do not have permission to read the value of this secret."
/>
) : (
<SecretInput value={preSecret?.value} />
)}
<SecretInput value={preSecret?.value} />
</Td>
)}
<Td>
{postSecret?.secretValueHidden ? (
<Blur
className="w-min"
tooltipText="You do not have permission to read the value of this secret."
/>
) : (
<SecretInput value={postSecret?.value} />
)}
<SecretInput value={postSecret?.value} />
</Td>
</Tr>
{Boolean(preSecret?.idOverride || postSecret?.idOverride) && (