feat(api): view secret value, WIP

This commit is contained in:
Daniel Hougaard
2025-03-05 22:47:39 +04:00
parent 8c18753e3f
commit 4a66395ce6
40 changed files with 1042 additions and 303 deletions
@@ -35,6 +35,11 @@ export const GeneralPermissionPolicies = <T extends keyof NonNullable<TFormSchem
title,
isDisabled
}: Props<T>) => {
if (subject === "secrets") {
console.log("secret subject");
console.log(actions);
}
const { control } = useFormContext<TFormSchema>();
const items = useFieldArray({
control,
@@ -116,25 +121,36 @@ export const GeneralPermissionPolicies = <T extends keyof NonNullable<TFormSchem
<div className="w-1/4">Actions</div>
<div className="flex flex-grow flex-wrap justify-start gap-8">
{actions.map(({ label, value }) => {
if (subject === "secrets") {
console.log("value", value);
}
if (typeof value !== "string") return undefined;
return (
<Controller
key={`${el.id}-${label}`}
name={`permissions.${subject}.${rootIndex}.${value}` as any}
control={control}
defaultValue={false}
render={({ field }) => (
<div className="flex items-center justify-center">
<Checkbox
isDisabled={isDisabled}
isChecked={Boolean(field.value)}
onCheckedChange={field.onChange}
id={`permissions.${subject}.${rootIndex}.${String(value)}`}
>
{label}
</Checkbox>
</div>
)}
render={({ field }) => {
if (subject === "secrets") {
console.log("field", field);
}
return (
<div className="flex items-center justify-center">
<Checkbox
isDisabled={isDisabled}
isChecked={Boolean(field.value)}
onCheckedChange={field.onChange}
id={`permissions.${subject}.${rootIndex}.${String(value)}`}
>
{label}
</Checkbox>
</div>
);
}}
/>
);
})}
@@ -9,6 +9,7 @@ import {
PermissionConditionOperators,
ProjectPermissionDynamicSecretActions,
ProjectPermissionKmipActions,
ProjectPermissionSecretActions,
ProjectPermissionSecretSyncActions,
TPermissionCondition,
TPermissionConditionOperators
@@ -22,6 +23,14 @@ const GeneralPolicyActionSchema = z.object({
create: z.boolean().optional()
});
const SecretPolicyActionSchema = z.object({
read: z.boolean().optional(), // describe secret
edit: z.boolean().optional(),
delete: z.boolean().optional(),
create: z.boolean().optional(),
readValue: z.boolean().optional()
});
const CmekPolicyActionSchema = z.object({
read: z.boolean().optional(),
edit: z.boolean().optional(),
@@ -114,7 +123,7 @@ export const projectRoleFormSchema = z.object({
.refine((val) => val !== "custom", { message: "Cannot use custom as its a keyword" }),
permissions: z
.object({
[ProjectPermissionSub.Secrets]: GeneralPolicyActionSchema.extend({
[ProjectPermissionSub.Secrets]: SecretPolicyActionSchema.extend({
inverted: z.boolean().optional(),
conditions: ConditionSchema
})
@@ -283,6 +292,28 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
});
return;
}
if (subject === ProjectPermissionSub.Secrets) {
const canRead = action.includes(ProjectPermissionSecretActions.DescribeSecret);
const canEdit = action.includes(ProjectPermissionSecretActions.Edit);
const canDelete = action.includes(ProjectPermissionSecretActions.Delete);
const canCreate = action.includes(ProjectPermissionSecretActions.Create);
const canReadValue = action.includes(ProjectPermissionSecretActions.ReadValue);
// from above statement we are sure it won't be undefined
formVal[subject]!.push({
read: canRead,
create: canCreate,
edit: canEdit,
delete: canDelete,
readValue: canReadValue,
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
inverted
});
return;
}
// for other subjects
const canRead = action.includes(ProjectPermissionActions.Read);
const canEdit = action.includes(ProjectPermissionActions.Edit);
@@ -483,8 +514,9 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
[ProjectPermissionSub.Secrets]: {
title: "Secrets",
actions: [
{ label: "Read", value: "read" },
{ label: "Describe Secret", value: "read" },
{ label: "Create", value: "create" },
{ label: "Read Value", value: "readValue" },
{ label: "Modify", value: "edit" },
{ label: "Remove", value: "delete" }
]
@@ -39,6 +39,7 @@ type Props = {
isVisible?: boolean;
isImportedSecret: boolean;
environment: string;
secretValueHidden: boolean;
secretPath: string;
onSecretCreate: (env: string, key: string, value: string) => Promise<void>;
onSecretUpdate: (
@@ -58,6 +59,7 @@ export const SecretEditRow = ({
isImportedSecret,
onSecretUpdate,
secretName,
secretValueHidden,
onSecretCreate,
onSecretDelete,
environment,
@@ -151,6 +153,7 @@ export const SecretEditRow = ({
value={field.value as string}
key="secret-input"
isVisible={isVisible}
secretValueHidden={secretValueHidden}
secretPath={secretPath}
environment={environment}
isImport={isImportedSecret}
@@ -158,6 +161,7 @@ export const SecretEditRow = ({
)}
/>
</div>
<div
className={twMerge(
"flex w-24 justify-center space-x-3 pl-2 transition-all",
@@ -221,10 +221,13 @@ export const SecretOverviewTableRow = ({
secretPath={secretPath}
isVisible={isSecretVisible}
secretName={secretKey}
secretValueHidden={secret?.secretValueHidden || false}
defaultValue={
secret?.valueOverride ||
secret?.value ||
importedSecret?.secret?.value
secret?.secretValueHidden
? ""
: secret?.valueOverride ||
secret?.value ||
importedSecret?.secret?.value
}
secretId={secret?.id}
isOverride={Boolean(secret?.valueOverride)}
@@ -58,6 +58,7 @@ import {
useSelectedSecrets
} from "./SecretMainPage.store";
import { Filter, RowType } from "./SecretMainPage.types";
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
const LOADER_TEXT = [
"Retrieving your encrypted secrets...",
@@ -103,7 +104,7 @@ const Page = () => {
const projectSlug = currentWorkspace?.slug || "";
const secretPath = (routerQueryParams.secretPath as string) || "/";
const canReadSecret = permission.can(
ProjectPermissionActions.Read,
ProjectPermissionSecretActions.DescribeSecret,
subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
@@ -111,6 +112,17 @@ const Page = () => {
secretTags: ["*"]
})
);
const canReadSecretValue = permission.can(
ProjectPermissionSecretActions.ReadValue,
subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: "*",
secretTags: ["*"]
})
);
console.log("Can read secret value", canReadSecret);
const canReadSecretImports = permission.can(
ProjectPermissionActions.Read,
@@ -176,6 +188,7 @@ 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
@@ -52,6 +52,8 @@ import { ProjectType } from "@app/hooks/api/workspace/types";
import { CreateReminderForm } from "./CreateReminderForm";
import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils";
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
import { useEffect } from "react";
type Props = {
isOpen?: boolean;
@@ -122,7 +124,7 @@ export const SecretDetailSidebar = ({
const selectTagSlugs = selectedTags.map((i) => i.slug);
const cannotEditSecret = permission.cannot(
ProjectPermissionActions.Edit,
ProjectPermissionSecretActions.Edit,
subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
@@ -130,16 +132,31 @@ export const SecretDetailSidebar = ({
secretTags: selectTagSlugs
})
);
const cannotReadSecretValue = permission.cannot(
ProjectPermissionSecretActions.ReadValue,
subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: secretKey,
secretTags: selectTagSlugs
})
);
const isReadOnly =
permission.can(
ProjectPermissionActions.Read,
ProjectPermissionSecretActions.DescribeSecret,
subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: secretKey,
secretTags: selectTagSlugs
})
) && cannotEditSecret;
) &&
cannotEditSecret &&
cannotReadSecretValue;
console.log("cannotReadSecretValue", cannotReadSecretValue);
const overrideAction = watch("overrideAction");
const isOverridden =
@@ -261,7 +278,14 @@ export const SecretDetailSidebar = ({
key="secret-value"
control={control}
render={({ field }) => (
<FormControl label="Value">
<FormControl
helperText={
cannotReadSecretValue
? "The value of this secret is hidden because you don't have the read secret value permission."
: undefined
}
label="Value"
>
<InfisicalSecretInput
isReadOnly={isReadOnly}
environment={environment}
@@ -1,3 +1,4 @@
/* eslint-disable no-nested-ternary */
/* eslint-disable simple-import-sort/imports */
import { ProjectPermissionCan } from "@app/components/permissions";
import {
@@ -44,6 +45,7 @@ import {
SecretReferenceTree
} from "@app/components/secrets/SecretReferenceDetails";
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
import {
FontAwesomeSpriteName,
formSchema,
@@ -99,8 +101,14 @@ export const SecretItem = memo(
trigger,
formState: { isDirty, isSubmitting, errors }
} = useForm<TFormSchema>({
defaultValues: secret,
values: secret,
defaultValues: {
...secret,
value: secret.secretValueHidden ? "" : secret.value
},
values: {
...secret,
value: secret.secretValueHidden ? "" : secret.value
},
resolver: zodResolver(formSchema)
});
@@ -123,7 +131,7 @@ export const SecretItem = memo(
const isReadOnly =
permission.can(
ProjectPermissionActions.Read,
ProjectPermissionSecretActions.DescribeSecret,
subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
@@ -132,7 +140,7 @@ export const SecretItem = memo(
})
) &&
permission.cannot(
ProjectPermissionActions.Edit,
ProjectPermissionSecretActions.Edit,
subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
@@ -141,6 +149,9 @@ export const SecretItem = memo(
})
);
const { secretValueHidden } = secret;
console.log(`Secret Key: ${secret.key}, hidden: ${secretValueHidden}`);
const [isSecValueCopied, setIsSecValueCopied] = useToggle(false);
useEffect(() => {
let timer: NodeJS.Timeout;
@@ -279,12 +290,14 @@ export const SecretItem = memo(
control={control}
render={({ field }) => (
<InfisicalSecretInput
secretValueHidden={secretValueHidden}
isReadOnly={isReadOnly}
key="secret-value"
isVisible={isVisible}
environment={environment}
secretPath={secretPath}
{...field}
defaultValue={secretValueHidden ? "" : undefined}
containerClassName="py-1.5 rounded-md transition-all group-hover:mr-2"
/>
)}