requested changes

This commit is contained in:
Daniel Hougaard
2025-03-11 04:52:12 +04:00
parent b9b76579ac
commit 483fb458dd
12 changed files with 441 additions and 191 deletions
@@ -23,12 +23,6 @@ export function throwIfMissingSecretReadValueOrDescribePermission(
subjectFields?: SecretSubjectFields subjectFields?: SecretSubjectFields
) { ) {
try { try {
if (subjectFields) {
ForbiddenError.from(permission).throwUnlessCan(action, subject(ProjectPermissionSub.Secrets, subjectFields));
} else {
ForbiddenError.from(permission).throwUnlessCan(action, ProjectPermissionSub.Secrets);
}
} catch {
if (subjectFields) { if (subjectFields) {
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionSecretActions.DescribeAndReadValue, ProjectPermissionSecretActions.DescribeAndReadValue,
@@ -40,6 +34,12 @@ export function throwIfMissingSecretReadValueOrDescribePermission(
ProjectPermissionSub.Secrets ProjectPermissionSub.Secrets
); );
} }
} catch {
if (subjectFields) {
ForbiddenError.from(permission).throwUnlessCan(action, subject(ProjectPermissionSub.Secrets, subjectFields));
} else {
ForbiddenError.from(permission).throwUnlessCan(action, ProjectPermissionSub.Secrets);
}
} }
} }
@@ -834,6 +834,51 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
} }
}); });
server.route({
method: "GET",
url: "/accessible-secrets",
config: {
rateLimit: secretsLimit
},
schema: {
querystring: z.object({
projectId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
filterByAction: z
.enum([ProjectPermissionSecretActions.DescribeSecret, ProjectPermissionSecretActions.ReadValue])
.default(ProjectPermissionSecretActions.ReadValue)
}),
response: {
200: z.object({
secrets: secretRawSchema
.extend({
secretPath: z.string().optional()
})
.array()
.optional()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { projectId, environment, secretPath, filterByAction } = req.query;
const { secrets } = await server.services.secret.getAccessibleSecrets({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
environment,
secretPath,
projectId,
filterByAction
});
return { secrets };
}
});
server.route({ server.route({
method: "GET", method: "GET",
url: "/secrets-by-keys", url: "/secrets-by-keys",
@@ -60,6 +60,7 @@ import {
TCreateSecretDTO, TCreateSecretDTO,
TDeleteManySecretDTO, TDeleteManySecretDTO,
TDeleteSecretDTO, TDeleteSecretDTO,
TGetAccessibleSecretsDTO,
TGetASecretDTO, TGetASecretDTO,
TGetSecretReferencesTreeDTO, TGetSecretReferencesTreeDTO,
TGetSecretsDTO, TGetSecretsDTO,
@@ -200,7 +201,7 @@ export const secretV2BridgeServiceFactory = ({
const referredSecretsGroupBySecretKey = groupBy(referredSecrets, (i) => i.key); const referredSecretsGroupBySecretKey = groupBy(referredSecrets, (i) => i.key);
references.forEach((el) => { references.forEach((el) => {
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, {
environment: el.environment, environment: el.environment,
secretPath: el.secretPath, secretPath: el.secretPath,
secretName: el.secretKey, secretName: el.secretKey,
@@ -1291,7 +1292,7 @@ export const secretV2BridgeServiceFactory = ({
folderDAL, folderDAL,
secretImportDAL, secretImportDAL,
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""), decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
expandSecretReferences: shouldExpandSecretReferences ? expandSecretReferences : undefined, expandSecretReferences: shouldExpandSecretReferences && viewSecretValue ? expandSecretReferences : undefined,
hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) => { hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) => {
return hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, { return hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, {
environment: expandEnvironment, environment: expandEnvironment,
@@ -1347,7 +1348,7 @@ export const secretV2BridgeServiceFactory = ({
let secretValue = secret.encryptedValue let secretValue = secret.encryptedValue
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedValue }).toString() ? secretManagerDecryptor({ cipherTextBlob: secret.encryptedValue }).toString()
: ""; : "";
if (shouldExpandSecretReferences && secretValue) { if (shouldExpandSecretReferences && secretValue && viewSecretValue) {
// eslint-disable-next-line // eslint-disable-next-line
const expandedSecretValue = await expandSecretReferences({ const expandedSecretValue = await expandSecretReferences({
environment, environment,
@@ -2613,6 +2614,86 @@ export const secretV2BridgeServiceFactory = ({
return { tree: stackTrace, value: expandedValue }; return { tree: stackTrace, value: expandedValue };
}; };
const getAccessibleSecrets = async ({
projectId,
secretPath,
environment,
filterByAction,
actorId,
actor,
actorAuthMethod,
actorOrgId
}: TGetAccessibleSecretsDTO) => {
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, {
environment,
secretPath
});
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder) return { secrets: [] };
const secrets = await secretDAL.findByFolderIds([folder.id]);
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager,
projectId
});
const decryptedSecrets = secrets
.filter((el) => {
if (
!hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, {
environment,
secretPath,
secretName: el.key,
secretTags: el.tags.map((i) => i.slug)
})
) {
return false;
}
if (filterByAction === ProjectPermissionSecretActions.ReadValue) {
return hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, {
environment,
secretPath,
secretName: el.key,
secretTags: el.tags.map((i) => i.slug)
});
}
return true;
})
.map((secret) => {
return reshapeBridgeSecret(
projectId,
environment,
secretPath,
{
...secret,
value: secret.encryptedValue
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedValue }).toString()
: "",
comment: secret.encryptedComment
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedComment }).toString()
: ""
},
false
);
});
return {
secrets: decryptedSecrets
};
};
return { return {
createSecret, createSecret,
deleteSecret, deleteSecret,
@@ -2630,6 +2711,7 @@ export const secretV2BridgeServiceFactory = ({
getSecretsMultiEnv, getSecretsMultiEnv,
getSecretReferenceTree, getSecretReferenceTree,
getSecretsByFolderMappings, getSecretsByFolderMappings,
getSecretById getSecretById,
getAccessibleSecrets
}; };
}; };
@@ -351,3 +351,10 @@ export type TGetSecretsRawByFolderMappingsDTO = {
filters: TFindSecretsByFolderIdsFilter; filters: TFindSecretsByFolderIdsFilter;
filterByAction?: ProjectPermissionSecretActions.DescribeSecret | ProjectPermissionSecretActions.ReadValue; filterByAction?: ProjectPermissionSecretActions.DescribeSecret | ProjectPermissionSecretActions.ReadValue;
}; };
export type TGetAccessibleSecretsDTO = {
environment: string;
projectId: string;
secretPath: string;
filterByAction: ProjectPermissionSecretActions.DescribeSecret | ProjectPermissionSecretActions.ReadValue;
} & TProjectPermission;
+36 -1
View File
@@ -81,6 +81,7 @@ import {
TDeleteManySecretRawDTO, TDeleteManySecretRawDTO,
TDeleteSecretDTO, TDeleteSecretDTO,
TDeleteSecretRawDTO, TDeleteSecretRawDTO,
TGetAccessibleSecretsDTO,
TGetASecretByIdRawDTO, TGetASecretByIdRawDTO,
TGetASecretDTO, TGetASecretDTO,
TGetASecretRawDTO, TGetASecretRawDTO,
@@ -1312,6 +1313,39 @@ export const secretServiceFactory = ({
return { users: usersWithAccess, identities: identitiesWithAccess, groups: groupsWithAccess }; return { users: usersWithAccess, identities: identitiesWithAccess, groups: groupsWithAccess };
}; };
const getAccessibleSecrets = async ({
projectId,
secretPath,
actor,
actorId,
actorOrgId,
actorAuthMethod,
environment,
filterByAction
}: TGetAccessibleSecretsDTO) => {
const { shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
if (!shouldUseSecretV2Bridge) {
throw new BadRequestError({
message: "Project version does not support this endpoint.",
name: "ProjectVersionNotSupported"
});
}
const secrets = await secretV2BridgeService.getAccessibleSecrets({
projectId,
secretPath,
environment,
filterByAction,
actor,
actorId,
actorOrgId,
actorAuthMethod
});
return secrets;
};
const getSecretsRaw = async ({ const getSecretsRaw = async ({
projectId, projectId,
path, path,
@@ -3261,6 +3295,7 @@ export const secretServiceFactory = ({
getSecretReferenceTree, getSecretReferenceTree,
getSecretsRawByFolderMappings, getSecretsRawByFolderMappings,
getSecretAccessList, getSecretAccessList,
getSecretByIdRaw getSecretByIdRaw,
getAccessibleSecrets
}; };
}; };
@@ -20,6 +20,7 @@ import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-
import { SecretUpdateMode } from "../secret-v2-bridge/secret-v2-bridge-types"; import { SecretUpdateMode } from "../secret-v2-bridge/secret-v2-bridge-types";
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal"; import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal"; import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal";
import { ProjectPermissionSecretActions } from "@app/ee/services/permission/project-permission";
type TPartialSecret = Pick<TSecrets, "id" | "secretReminderRepeatDays" | "secretReminderNote">; type TPartialSecret = Pick<TSecrets, "id" | "secretReminderRepeatDays" | "secretReminderNote">;
@@ -180,6 +181,12 @@ export enum SecretsOrderBy {
Name = "name" // "key" for secrets but using name for use across resources Name = "name" // "key" for secrets but using name for use across resources
} }
export type TGetAccessibleSecretsDTO = {
secretPath: string;
environment: string;
filterByAction: ProjectPermissionSecretActions.DescribeSecret | ProjectPermissionSecretActions.ReadValue;
} & TProjectPermission;
export type TGetSecretsRawDTO = { export type TGetSecretsRawDTO = {
expandSecretReferences?: boolean; expandSecretReferences?: boolean;
path: string; path: string;
@@ -1,4 +1,5 @@
export { export {
useGetAccessibleSecrets,
useGetProjectSecretsDetails, useGetProjectSecretsDetails,
useGetProjectSecretsOverview, useGetProjectSecretsOverview,
useGetProjectSecretsQuickSearch useGetProjectSecretsQuickSearch
+56 -1
View File
@@ -11,6 +11,7 @@ import {
DashboardSecretsOrderBy, DashboardSecretsOrderBy,
TDashboardProjectSecretsQuickSearch, TDashboardProjectSecretsQuickSearch,
TDashboardProjectSecretsQuickSearchResponse, TDashboardProjectSecretsQuickSearchResponse,
TGetAccessibleSecretsDTO,
TGetDashboardProjectSecretsByKeys, TGetDashboardProjectSecretsByKeys,
TGetDashboardProjectSecretsDetailsDTO, TGetDashboardProjectSecretsDetailsDTO,
TGetDashboardProjectSecretsOverviewDTO, TGetDashboardProjectSecretsOverviewDTO,
@@ -20,6 +21,8 @@ import { OrderByDirection } from "@app/hooks/api/generic/types";
import { mergePersonalSecrets } from "@app/hooks/api/secrets/queries"; import { mergePersonalSecrets } from "@app/hooks/api/secrets/queries";
import { groupBy, unique } from "@app/lib/fn/array"; import { groupBy, unique } from "@app/lib/fn/array";
import { SecretV3Raw } from "../types";
export const dashboardKeys = { export const dashboardKeys = {
all: () => ["dashboard"] as const, all: () => ["dashboard"] as const,
getDashboardSecrets: ({ getDashboardSecrets: ({
@@ -58,7 +61,14 @@ export const dashboardKeys = {
...dashboardKeys.getDashboardSecrets({ projectId, secretPath }), ...dashboardKeys.getDashboardSecrets({ projectId, secretPath }),
"quick-search", "quick-search",
params params
] as const ] as const,
getAccessibleSecrets: ({
projectId,
secretPath,
environment,
filterByAction
}: TGetAccessibleSecretsDTO) =>
[...dashboardKeys.all(), { projectId, secretPath, environment, filterByAction }] as const
}; };
export const fetchProjectSecretsOverview = async ({ export const fetchProjectSecretsOverview = async ({
@@ -295,6 +305,22 @@ export const fetchProjectSecretsQuickSearch = async ({
return data; return data;
}; };
const fetchAccessibleSecrets = async ({
projectId,
secretPath,
environment,
filterByAction
}: TGetAccessibleSecretsDTO) => {
const { data } = await apiRequest.get<{ secrets: SecretV3Raw[] }>(
"/api/v1/dashboard/accessible-secrets",
{
params: { projectId, secretPath, environment, filterByAction }
}
);
return data.secrets;
};
export const useGetProjectSecretsQuickSearch = ( export const useGetProjectSecretsQuickSearch = (
{ {
projectId, projectId,
@@ -357,3 +383,32 @@ export const useGetProjectSecretsQuickSearch = (
placeholderData: (previousData) => previousData placeholderData: (previousData) => previousData
}); });
}; };
export const useGetAccessibleSecrets = ({
projectId,
secretPath,
environment,
filterByAction,
options
}: TGetAccessibleSecretsDTO & {
options?: Omit<
UseQueryOptions<
SecretV3Raw[],
unknown,
SecretV3Raw[],
ReturnType<typeof dashboardKeys.getAccessibleSecrets>
>,
"queryKey" | "queryFn"
>;
}) => {
return useQuery({
...options,
queryKey: dashboardKeys.getAccessibleSecrets({
projectId,
secretPath,
environment,
filterByAction
}),
queryFn: () => fetchAccessibleSecrets({ projectId, secretPath, environment, filterByAction })
});
};
+10
View File
@@ -1,3 +1,4 @@
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
import { TDynamicSecret } from "@app/hooks/api/dynamicSecret/types"; import { TDynamicSecret } from "@app/hooks/api/dynamicSecret/types";
import { OrderByDirection } from "@app/hooks/api/generic/types"; import { OrderByDirection } from "@app/hooks/api/generic/types";
import { TSecretFolder } from "@app/hooks/api/secretFolders/types"; import { TSecretFolder } from "@app/hooks/api/secretFolders/types";
@@ -101,3 +102,12 @@ export type TGetDashboardProjectSecretsByKeys = {
environment: string; environment: string;
keys: string[]; keys: string[];
}; };
export type TGetAccessibleSecretsDTO = {
projectId: string;
secretPath: string;
environment: string;
filterByAction:
| ProjectPermissionSecretActions.DescribeSecret
| ProjectPermissionSecretActions.ReadValue;
};
@@ -27,9 +27,11 @@ import {
} from "@app/components/v2"; } from "@app/components/v2";
import { Blur } from "@app/components/v2/Blur"; import { Blur } from "@app/components/v2/Blur";
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput"; import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context";
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
import { useToggle } from "@app/hooks"; import { useToggle } from "@app/hooks";
import { SecretType } from "@app/hooks/api/types"; import { SecretType } from "@app/hooks/api/types";
import { hasSecretReadValueOrDescribePermission } from "@app/lib/fn/permission";
type Props = { type Props = {
defaultValue?: string | null; defaultValue?: string | null;
@@ -79,6 +81,9 @@ export const SecretEditRow = ({
value: defaultValue || null value: defaultValue || null
} }
}); });
const { permission } = useProjectPermission();
const [isDeleting, setIsDeleting] = useToggle(); const [isDeleting, setIsDeleting] = useToggle();
const [isModalOpen, setIsModalOpen] = useState<boolean>(false); const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
@@ -120,6 +125,11 @@ export const SecretEditRow = ({
reset({ value }); reset({ value });
}; };
const canReadSecretValue = hasSecretReadValueOrDescribePermission(
permission,
ProjectPermissionSecretActions.ReadValue
);
const handleDeleteSecret = useCallback(async () => { const handleDeleteSecret = useCallback(async () => {
setIsDeleting.on(); setIsDeleting.on();
setIsModalOpen(false); setIsModalOpen(false);
@@ -229,11 +239,7 @@ export const SecretEditRow = ({
</IconButton> </IconButton>
</Tooltip> </Tooltip>
</div> </div>
<ProjectPermissionCan
I={ProjectPermissionActions.Read}
a={ProjectPermissionSub.Secrets}
>
{(isAllowed) => (
<div className="opacity-0 group-hover:opacity-100"> <div className="opacity-0 group-hover:opacity-100">
<Modal> <Modal>
<ModalTrigger asChild> <ModalTrigger asChild>
@@ -249,7 +255,7 @@ export const SecretEditRow = ({
variant="plain" variant="plain"
ariaLabel="reference-tree" ariaLabel="reference-tree"
className="h-full" className="h-full"
isDisabled={!hasSecretReference(defaultValue || "") || !isAllowed} isDisabled={!hasSecretReference(defaultValue || "") || !canReadSecretValue}
> >
<FontAwesomeIcon icon={faProjectDiagram} /> <FontAwesomeIcon icon={faProjectDiagram} />
</IconButton> </IconButton>
@@ -269,8 +275,7 @@ export const SecretEditRow = ({
</ModalContent> </ModalContent>
</Modal> </Modal>
</div> </div>
)}
</ProjectPermissionCan>
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Delete} I={ProjectPermissionActions.Delete}
a={subject(ProjectPermissionSub.Secrets, { a={subject(ProjectPermissionSub.Secrets, {
@@ -20,8 +20,9 @@ import {
} from "@app/components/v2"; } from "@app/components/v2";
import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { SecretPathInput } from "@app/components/v2/SecretPathInput";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
import { useDebounce } from "@app/hooks"; import { useDebounce } from "@app/hooks";
import { useGetProjectSecrets } from "@app/hooks/api"; import { useGetAccessibleSecrets } from "@app/hooks/api/dashboard";
const formSchema = z.object({ const formSchema = z.object({
environment: z.object({ name: z.string(), slug: z.string() }), environment: z.object({ name: z.string(), slug: z.string() }),
@@ -32,7 +33,7 @@ const formSchema = z.object({
typeof val === "string" && val.at(-1) === "/" && val.length > 1 ? val.slice(0, -1) : val typeof val === "string" && val.at(-1) === "/" && val.length > 1 ? val.slice(0, -1) : val
), ),
secrets: z secrets: z
.object({ key: z.string(), value: z.string().optional() }) .object({ secretKey: z.string(), secretValue: z.string().optional() })
.array() .array()
.min(1, "Select one or more secrets to copy") .min(1, "Select one or more secrets to copy")
}); });
@@ -78,10 +79,14 @@ export const CopySecretsFromBoard = ({
const selectedEnvSlug = watch("environment"); const selectedEnvSlug = watch("environment");
const [debouncedEnvCopySecretPath] = useDebounce(envCopySecPath); const [debouncedEnvCopySecretPath] = useDebounce(envCopySecPath);
const { data: secrets, isPending: isSecretsLoading } = useGetProjectSecrets({ const { data: accessibleSecrets, isPending: isAccessibleSecretsLoading } =
workspaceId, useGetAccessibleSecrets({
environment: selectedEnvSlug.slug, projectId: workspaceId,
secretPath: debouncedEnvCopySecretPath, secretPath: debouncedEnvCopySecretPath,
environment: selectedEnvSlug.slug,
filterByAction: shouldIncludeValues
? ProjectPermissionSecretActions.ReadValue
: ProjectPermissionSecretActions.DescribeSecret,
options: { options: {
enabled: enabled:
Boolean(workspaceId) && Boolean(workspaceId) &&
@@ -96,16 +101,16 @@ export const CopySecretsFromBoard = ({
}, [debouncedEnvCopySecretPath, selectedEnvSlug]); }, [debouncedEnvCopySecretPath, selectedEnvSlug]);
const handleSecSelectAll = () => { const handleSecSelectAll = () => {
if (secrets) { if (accessibleSecrets) {
setValue("secrets", secrets, { shouldDirty: true }); setValue("secrets", accessibleSecrets, { shouldDirty: true });
} }
}; };
const handleFormSubmit = async (data: TFormSchema) => { const handleFormSubmit = async (data: TFormSchema) => {
const secretsToBePulled: Record<string, { value: string; comments: string[] }> = {}; const secretsToBePulled: Record<string, { value: string; comments: string[] }> = {};
data.secrets.forEach(({ key, value }) => { data.secrets.forEach(({ secretKey, secretValue }) => {
secretsToBePulled[key] = { secretsToBePulled[secretKey] = {
value: (shouldIncludeValues && value) || "", value: (shouldIncludeValues && secretValue) || "",
comments: [""] comments: [""]
}; };
}); });
@@ -202,19 +207,19 @@ export const CopySecretsFromBoard = ({
<FilterableSelect <FilterableSelect
placeholder={ placeholder={
// eslint-disable-next-line no-nested-ternary // eslint-disable-next-line no-nested-ternary
isSecretsLoading isAccessibleSecretsLoading
? "Loading secrets..." ? "Loading secrets..."
: secrets?.length : accessibleSecrets?.length
? "Select secrets..." ? "Select secrets..."
: "No secrets found..." : "No secrets found..."
} }
isLoading={isSecretsLoading} isLoading={isAccessibleSecretsLoading}
options={secrets} options={accessibleSecrets}
value={value} value={value}
onChange={onChange} onChange={onChange}
isMulti isMulti
getOptionValue={(option) => option.key} getOptionValue={(option) => option.secretKey}
getOptionLabel={(option) => option.key} getOptionLabel={(option) => option.secretKey}
/> />
</FormControl> </FormControl>
)} )}
@@ -235,7 +240,10 @@ export const CopySecretsFromBoard = ({
<Switch <Switch
id="populate-include-value" id="populate-include-value"
isChecked={shouldIncludeValues} isChecked={shouldIncludeValues}
onCheckedChange={(isChecked) => setShouldIncludeValues(isChecked as boolean)} onCheckedChange={(isChecked) => {
setValue("secrets", []);
setShouldIncludeValues(isChecked as boolean);
}}
> >
Include secret values Include secret values
</Switch> </Switch>
@@ -13,8 +13,8 @@ import {
faShare, faShare,
faTag, faTag,
faTrash, faTrash,
faUser, faTriangleExclamation,
faTriangleExclamation faUser
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
@@ -728,8 +728,8 @@ export const SecretDetailSidebar = ({
<div className="mb-2 pl-1">Version History</div> <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]"> <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( {secretVersion?.map(
({ createdAt, secretValue, version, id, secretValueHidden, actor }, index) => ( ({ createdAt, secretValue, secretValueHidden, version, id, actor }) => (
<div key={`secret-version-${index + 1}`} className="flex flex-row"> <div className="flex flex-row">
<div key={id} className="flex w-full flex-col space-y-1"> <div key={id} className="flex w-full flex-col space-y-1">
<div className="flex items-center"> <div className="flex items-center">
<div className="w-10"> <div className="w-10">
@@ -739,8 +739,6 @@ export const SecretDetailSidebar = ({
</div> </div>
<div>{format(new Date(createdAt), "Pp")}</div> <div>{format(new Date(createdAt), "Pp")}</div>
</div> </div>
<div>{format(new Date(createdAt), "Pp")}</div>
</div>
<div className="flex w-full cursor-default"> <div className="flex w-full cursor-default">
<div className="relative w-10"> <div className="relative w-10">
<div className="absolute bottom-0 left-3 top-0 mt-0.5 border-l border-mineshaft-400/60" /> <div className="absolute bottom-0 left-3 top-0 mt-0.5 border-l border-mineshaft-400/60" />
@@ -750,7 +748,9 @@ export const SecretDetailSidebar = ({
<div className="flex flex-row"> <div className="flex flex-row">
<div className="flex w-fit flex-row text-sm"> <div className="flex w-fit flex-row text-sm">
Modified by: Modified by:
<Tooltip content={getModifiedByName(actor.actorType, actor.name)}> <Tooltip
content={getModifiedByName(actor.actorType, actor.name)}
>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
<div <div
onClick={() => onClick={() =>
@@ -824,11 +824,6 @@ export const SecretDetailSidebar = ({
}, 3000); }, 3000);
} }
}} }}
>
<Tooltip
className="break-normal text-xs"
content="You do not have permission to view this secret value"
isDisabled={!secretValueHidden}
> >
<span <span
className={twMerge( className={twMerge(
@@ -837,7 +832,6 @@ export const SecretDetailSidebar = ({
> >
{secretValueHidden ? "Hidden" : secretValue} {secretValueHidden ? "Hidden" : secretValue}
</span> </span>
</Tooltip>
</button> </button>
<button <button
type="button" type="button"
@@ -885,6 +879,7 @@ export const SecretDetailSidebar = ({
</div> </div>
</div> </div>
</div> </div>
</div>
<div <div
className={`flex items-center justify-center ${version === secretVersion.length ? "hidden" : ""}`} className={`flex items-center justify-center ${version === secretVersion.length ? "hidden" : ""}`}
> >