mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-10 16:28:40 +00:00
Merge pull request #3478 from Infisical/fix/UISecretEditPermissionButNotReadValuePermission
fix(secrets-table): UI fix for users with edit permissions but not read secret value permission
This commit is contained in:
@@ -2,7 +2,7 @@ import path from "node:path";
|
|||||||
|
|
||||||
import RE2 from "re2";
|
import RE2 from "re2";
|
||||||
|
|
||||||
import { TableName, TSecretFolders, TSecretsV2 } from "@app/db/schemas";
|
import { SecretType, TableName, TSecretFolders, TSecretsV2 } from "@app/db/schemas";
|
||||||
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { groupBy } from "@app/lib/fn";
|
import { groupBy } from "@app/lib/fn";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
@@ -720,7 +720,7 @@ export const reshapeBridgeSecret = (
|
|||||||
secretReminderRecipients: secret.secretReminderRecipients || [],
|
secretReminderRecipients: secret.secretReminderRecipients || [],
|
||||||
...(secretValueHidden
|
...(secretValueHidden
|
||||||
? {
|
? {
|
||||||
secretValue: INFISICAL_SECRET_VALUE_HIDDEN_MASK,
|
secretValue: secret.type === SecretType.Personal ? secret.value : INFISICAL_SECRET_VALUE_HIDDEN_MASK,
|
||||||
secretValueHidden: true
|
secretValueHidden: true
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
|||||||
onFocus={(evt) => {
|
onFocus={(evt) => {
|
||||||
onFocus?.(evt);
|
onFocus?.(evt);
|
||||||
setIsSecretFocused.on();
|
setIsSecretFocused.on();
|
||||||
|
evt.currentTarget.select();
|
||||||
}}
|
}}
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
|
|||||||
+43
-24
@@ -4,6 +4,7 @@ import { subject } from "@casl/ability";
|
|||||||
import {
|
import {
|
||||||
faCheck,
|
faCheck,
|
||||||
faCopy,
|
faCopy,
|
||||||
|
faEyeSlash,
|
||||||
faProjectDiagram,
|
faProjectDiagram,
|
||||||
faTrash,
|
faTrash,
|
||||||
faXmark
|
faXmark
|
||||||
@@ -25,7 +26,6 @@ import {
|
|||||||
ModalTrigger,
|
ModalTrigger,
|
||||||
Tooltip
|
Tooltip
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { Blur } from "@app/components/v2/Blur";
|
|
||||||
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context";
|
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context";
|
||||||
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
||||||
@@ -124,7 +124,13 @@ export const SecretEditRow = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reset({ value });
|
if (secretValueHidden && !isOverride) {
|
||||||
|
setTimeout(() => {
|
||||||
|
reset({ value: defaultValue || null });
|
||||||
|
}, 50);
|
||||||
|
} else {
|
||||||
|
reset({ value });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const canReadSecretValue = hasSecretReadValueOrDescribePermission(
|
const canReadSecretValue = hasSecretReadValueOrDescribePermission(
|
||||||
@@ -132,6 +138,16 @@ export const SecretEditRow = ({
|
|||||||
ProjectPermissionSecretActions.ReadValue
|
ProjectPermissionSecretActions.ReadValue
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const canEditSecretValue = permission.can(
|
||||||
|
ProjectPermissionSecretActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
secretName,
|
||||||
|
secretTags: ["*"]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const handleDeleteSecret = useCallback(async () => {
|
const handleDeleteSecret = useCallback(async () => {
|
||||||
setIsDeleting.on();
|
setIsDeleting.on();
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
@@ -153,29 +169,32 @@ export const SecretEditRow = ({
|
|||||||
deleteKey={secretName}
|
deleteKey={secretName}
|
||||||
onDeleteApproved={handleDeleteSecret}
|
onDeleteApproved={handleDeleteSecret}
|
||||||
/>
|
/>
|
||||||
|
{secretValueHidden && !isOverride && (
|
||||||
|
<Tooltip
|
||||||
|
content={`You do not have access to view the current value${canEditSecretValue && !isRotatedSecret ? ", but you can set a new one" : "."}`}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon className="pl-2" size="sm" icon={faEyeSlash} />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
<div className="flex-grow border-r border-r-mineshaft-600 pl-1 pr-2">
|
<div className="flex-grow border-r border-r-mineshaft-600 pl-1 pr-2">
|
||||||
{secretValueHidden ? (
|
<Controller
|
||||||
<Blur tooltipText="You do not have permission to read the value of this secret." />
|
disabled={isImportedSecret && !defaultValue}
|
||||||
) : (
|
control={control}
|
||||||
<Controller
|
name="value"
|
||||||
disabled={isImportedSecret && !defaultValue}
|
render={({ field }) => (
|
||||||
control={control}
|
<InfisicalSecretInput
|
||||||
name="value"
|
{...field}
|
||||||
render={({ field }) => (
|
isReadOnly={isImportedSecret || (isRotatedSecret && !isOverride)}
|
||||||
<InfisicalSecretInput
|
value={field.value as string}
|
||||||
{...field}
|
key="secret-input"
|
||||||
isReadOnly={isImportedSecret || isRotatedSecret}
|
isVisible={isVisible && !secretValueHidden}
|
||||||
value={field.value as string}
|
secretPath={secretPath}
|
||||||
key="secret-input"
|
environment={environment}
|
||||||
isVisible={isVisible}
|
isImport={isImportedSecret}
|
||||||
secretPath={secretPath}
|
defaultValue={secretValueHidden ? "" : undefined}
|
||||||
environment={environment}
|
/>
|
||||||
isImport={isImportedSecret}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|||||||
+29
-7
@@ -1,3 +1,4 @@
|
|||||||
|
import { subject } from "@casl/ability";
|
||||||
import { faCircle } from "@fortawesome/free-regular-svg-icons";
|
import { faCircle } from "@fortawesome/free-regular-svg-icons";
|
||||||
import {
|
import {
|
||||||
faAngleDown,
|
faAngleDown,
|
||||||
@@ -14,6 +15,11 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { Button, Checkbox, TableContainer, Td, Tooltip, Tr } from "@app/components/v2";
|
import { Button, Checkbox, TableContainer, Td, Tooltip, Tr } from "@app/components/v2";
|
||||||
|
import { useProjectPermission } from "@app/context";
|
||||||
|
import {
|
||||||
|
ProjectPermissionSecretActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "@app/context/ProjectPermissionContext/types";
|
||||||
import { useToggle } from "@app/hooks";
|
import { useToggle } from "@app/hooks";
|
||||||
import { SecretType, SecretV3RawSanitized } from "@app/hooks/api/secrets/types";
|
import { SecretType, SecretV3RawSanitized } from "@app/hooks/api/secrets/types";
|
||||||
import { WorkspaceEnv } from "@app/hooks/api/types";
|
import { WorkspaceEnv } from "@app/hooks/api/types";
|
||||||
@@ -64,6 +70,28 @@ export const SecretOverviewTableRow = ({
|
|||||||
const totalCols = environments.length + 1; // secret key row
|
const totalCols = environments.length + 1; // secret key row
|
||||||
const [isSecretVisible, setIsSecretVisible] = useToggle();
|
const [isSecretVisible, setIsSecretVisible] = useToggle();
|
||||||
|
|
||||||
|
const { permission } = useProjectPermission();
|
||||||
|
|
||||||
|
const getDefaultValue = (
|
||||||
|
secret: SecretV3RawSanitized | undefined,
|
||||||
|
importedSecret: { secret?: SecretV3RawSanitized } | undefined
|
||||||
|
) => {
|
||||||
|
const canEditSecretValue = permission.can(
|
||||||
|
ProjectPermissionSecretActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: secret?.env || "",
|
||||||
|
secretPath: secret?.path || "",
|
||||||
|
secretName: secret?.key || "",
|
||||||
|
secretTags: ["*"]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (secret?.secretValueHidden && !secret?.valueOverride) {
|
||||||
|
return canEditSecretValue ? "******" : "";
|
||||||
|
}
|
||||||
|
return secret?.valueOverride || secret?.value || importedSecret?.secret?.value || "";
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tr isHoverable isSelectable onClick={() => setIsFormExpanded.toggle()} className="group">
|
<Tr isHoverable isSelectable onClick={() => setIsFormExpanded.toggle()} className="group">
|
||||||
@@ -228,13 +256,7 @@ export const SecretOverviewTableRow = ({
|
|||||||
isVisible={isSecretVisible}
|
isVisible={isSecretVisible}
|
||||||
secretName={secretKey}
|
secretName={secretKey}
|
||||||
secretValueHidden={secret?.secretValueHidden || false}
|
secretValueHidden={secret?.secretValueHidden || false}
|
||||||
defaultValue={
|
defaultValue={getDefaultValue(secret, importedSecret)}
|
||||||
secret?.secretValueHidden
|
|
||||||
? ""
|
|
||||||
: secret?.valueOverride ||
|
|
||||||
secret?.value ||
|
|
||||||
importedSecret?.secret?.value
|
|
||||||
}
|
|
||||||
secretId={secret?.id}
|
secretId={secret?.id}
|
||||||
isOverride={Boolean(secret?.valueOverride)}
|
isOverride={Boolean(secret?.valueOverride)}
|
||||||
isImportedSecret={isImportedSecret}
|
isImportedSecret={isImportedSecret}
|
||||||
|
|||||||
+32
-8
@@ -46,10 +46,9 @@ import {
|
|||||||
} from "@app/components/secrets/SecretReferenceDetails";
|
} from "@app/components/secrets/SecretReferenceDetails";
|
||||||
|
|
||||||
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
||||||
import { Blur } from "@app/components/v2/Blur";
|
|
||||||
import { hasSecretReadValueOrDescribePermission } from "@app/lib/fn/permission";
|
import { hasSecretReadValueOrDescribePermission } from "@app/lib/fn/permission";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faKey, faRotate } from "@fortawesome/free-solid-svg-icons";
|
import { faEyeSlash, faKey, faRotate } from "@fortawesome/free-solid-svg-icons";
|
||||||
import {
|
import {
|
||||||
FontAwesomeSpriteName,
|
FontAwesomeSpriteName,
|
||||||
formSchema,
|
formSchema,
|
||||||
@@ -57,6 +56,8 @@ import {
|
|||||||
TFormSchema
|
TFormSchema
|
||||||
} from "./SecretListView.utils";
|
} from "./SecretListView.utils";
|
||||||
|
|
||||||
|
const hiddenValue = "******";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
secret: SecretV3RawSanitized;
|
secret: SecretV3RawSanitized;
|
||||||
onSaveSecret: (
|
onSaveSecret: (
|
||||||
@@ -95,6 +96,23 @@ export const SecretItem = memo(
|
|||||||
const { permission } = useProjectPermission();
|
const { permission } = useProjectPermission();
|
||||||
const { isRotatedSecret } = secret;
|
const { isRotatedSecret } = secret;
|
||||||
|
|
||||||
|
const canEditSecretValue = permission.can(
|
||||||
|
ProjectPermissionSecretActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
secretName: secret.key,
|
||||||
|
secretTags: ["*"]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const getDefaultValue = () => {
|
||||||
|
if (secret.secretValueHidden) {
|
||||||
|
return canEditSecretValue ? hiddenValue : "";
|
||||||
|
}
|
||||||
|
return secret.valueOverride || secret.value || "";
|
||||||
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
control,
|
control,
|
||||||
@@ -108,11 +126,11 @@ export const SecretItem = memo(
|
|||||||
} = useForm<TFormSchema>({
|
} = useForm<TFormSchema>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
...secret,
|
...secret,
|
||||||
value: secret.secretValueHidden ? "" : secret.value
|
value: getDefaultValue()
|
||||||
},
|
},
|
||||||
values: {
|
values: {
|
||||||
...secret,
|
...secret,
|
||||||
value: secret.secretValueHidden ? "" : secret.value
|
value: getDefaultValue()
|
||||||
},
|
},
|
||||||
resolver: zodResolver(formSchema)
|
resolver: zodResolver(formSchema)
|
||||||
});
|
});
|
||||||
@@ -154,6 +172,7 @@ export const SecretItem = memo(
|
|||||||
secretTags: selectedTagSlugs
|
secretTags: selectedTagSlugs
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const { secretValueHidden } = secret;
|
const { secretValueHidden } = secret;
|
||||||
|
|
||||||
const [isSecValueCopied, setIsSecValueCopied] = useToggle(false);
|
const [isSecValueCopied, setIsSecValueCopied] = useToggle(false);
|
||||||
@@ -286,6 +305,13 @@ export const SecretItem = memo(
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
role="button"
|
role="button"
|
||||||
>
|
>
|
||||||
|
{secretValueHidden && !isOverriden && (
|
||||||
|
<Tooltip
|
||||||
|
content={`You do not have access to view the current value${canEditSecretValue && !isRotatedSecret ? ", but you can set a new one" : "."}`}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon className="pr-2" size="sm" icon={faEyeSlash} />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
{isOverriden ? (
|
{isOverriden ? (
|
||||||
<Controller
|
<Controller
|
||||||
name="valueOverride"
|
name="valueOverride"
|
||||||
@@ -301,8 +327,6 @@ export const SecretItem = memo(
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
) : secretValueHidden ? (
|
|
||||||
<Blur tooltipText="You do not have permission to read the value of this secret." />
|
|
||||||
) : (
|
) : (
|
||||||
<Controller
|
<Controller
|
||||||
name="value"
|
name="value"
|
||||||
@@ -312,11 +336,11 @@ export const SecretItem = memo(
|
|||||||
<InfisicalSecretInput
|
<InfisicalSecretInput
|
||||||
isReadOnly={isReadOnly || isRotatedSecret}
|
isReadOnly={isReadOnly || isRotatedSecret}
|
||||||
key="secret-value"
|
key="secret-value"
|
||||||
isVisible={isVisible}
|
isVisible={isVisible && !secretValueHidden}
|
||||||
environment={environment}
|
environment={environment}
|
||||||
secretPath={secretPath}
|
secretPath={secretPath}
|
||||||
{...field}
|
{...field}
|
||||||
defaultValue={secretValueHidden ? "" : undefined}
|
defaultValue={secretValueHidden ? hiddenValue : undefined}
|
||||||
containerClassName="py-1.5 rounded-md transition-all"
|
containerClassName="py-1.5 rounded-md transition-all"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
-2
@@ -49,8 +49,6 @@ export const SecretListView = ({
|
|||||||
isProtectedBranch = false,
|
isProtectedBranch = false,
|
||||||
importedBy
|
importedBy
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
console.log("secretssssss", secrets);
|
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { popUp, handlePopUpToggle, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
const { popUp, handlePopUpToggle, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
||||||
"deleteSecret",
|
"deleteSecret",
|
||||||
|
|||||||
Reference in New Issue
Block a user