mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 15:27:46 +00:00
feat: frontend integration
This commit is contained in:
@@ -10,6 +10,7 @@ import { useToggle } from "@app/hooks/useToggle";
|
|||||||
import { ERROR_NOT_ALLOWED_READ_SECRETS } from "./constants";
|
import { ERROR_NOT_ALLOWED_READ_SECRETS } from "./constants";
|
||||||
import {
|
import {
|
||||||
GetSecretVersionsDTO,
|
GetSecretVersionsDTO,
|
||||||
|
SecretAccessListEntry,
|
||||||
SecretType,
|
SecretType,
|
||||||
SecretV3Raw,
|
SecretV3Raw,
|
||||||
SecretV3RawResponse,
|
SecretV3RawResponse,
|
||||||
@@ -18,6 +19,7 @@ import {
|
|||||||
TGetProjectSecretsAllEnvDTO,
|
TGetProjectSecretsAllEnvDTO,
|
||||||
TGetProjectSecretsDTO,
|
TGetProjectSecretsDTO,
|
||||||
TGetProjectSecretsKey,
|
TGetProjectSecretsKey,
|
||||||
|
TGetSecretAccessListDTO,
|
||||||
TGetSecretReferenceTreeDTO,
|
TGetSecretReferenceTreeDTO,
|
||||||
TSecretReferenceTraceNode
|
TSecretReferenceTraceNode
|
||||||
} from "./types";
|
} from "./types";
|
||||||
@@ -27,6 +29,13 @@ export const secretKeys = {
|
|||||||
getProjectSecret: ({ workspaceId, environment, secretPath }: TGetProjectSecretsKey) =>
|
getProjectSecret: ({ workspaceId, environment, secretPath }: TGetProjectSecretsKey) =>
|
||||||
[{ workspaceId, environment, secretPath }, "secrets"] as const,
|
[{ workspaceId, environment, secretPath }, "secrets"] as const,
|
||||||
getSecretVersion: (secretId: string) => [{ secretId }, "secret-versions"] as const,
|
getSecretVersion: (secretId: string) => [{ secretId }, "secret-versions"] as const,
|
||||||
|
getSecretAccessList: ({
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
secretKey
|
||||||
|
}: TGetSecretAccessListDTO) =>
|
||||||
|
["secret-access-list", { workspaceId, environment, secretPath, secretKey }] as const,
|
||||||
getSecretReferenceTree: (dto: TGetSecretReferenceTreeDTO) => ["secret-reference-tree", dto]
|
getSecretReferenceTree: (dto: TGetSecretReferenceTreeDTO) => ["secret-reference-tree", dto]
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -231,6 +240,27 @@ export const useGetSecretVersion = (dto: GetSecretVersionsDTO) =>
|
|||||||
}, [])
|
}, [])
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const useGetSecretAccessList = (dto: TGetSecretAccessListDTO) =>
|
||||||
|
useQuery({
|
||||||
|
enabled: Boolean(dto.secretKey),
|
||||||
|
queryKey: secretKeys.getSecretAccessList(dto),
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await apiRequest.get<{
|
||||||
|
groups: SecretAccessListEntry[];
|
||||||
|
identities: SecretAccessListEntry[];
|
||||||
|
users: SecretAccessListEntry[];
|
||||||
|
}>(`/api/v1/secrets/raw/${dto.secretKey}/access-list`, {
|
||||||
|
params: {
|
||||||
|
workspaceId: dto.workspaceId,
|
||||||
|
environment: dto.environment,
|
||||||
|
secretPath: dto.secretPath
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const fetchSecretReferenceTree = async ({
|
const fetchSecretReferenceTree = async ({
|
||||||
secretPath,
|
secretPath,
|
||||||
projectId,
|
projectId,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ProjectPermissionActions } from "@app/context";
|
||||||
|
|
||||||
import type { WsTag } from "../tags/types";
|
import type { WsTag } from "../tags/types";
|
||||||
|
|
||||||
export enum SecretType {
|
export enum SecretType {
|
||||||
@@ -123,6 +125,13 @@ export type GetSecretVersionsDTO = {
|
|||||||
offset: number;
|
offset: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TGetSecretAccessListDTO = {
|
||||||
|
workspaceId: string;
|
||||||
|
environment: string;
|
||||||
|
secretPath: string;
|
||||||
|
secretKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type TCreateSecretsV3DTO = {
|
export type TCreateSecretsV3DTO = {
|
||||||
secretKey: string;
|
secretKey: string;
|
||||||
secretValue: string;
|
secretValue: string;
|
||||||
@@ -228,3 +237,10 @@ export type TSecretReferenceTraceNode = {
|
|||||||
secretPath: string;
|
secretPath: string;
|
||||||
children: TSecretReferenceTraceNode[];
|
children: TSecretReferenceTraceNode[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SecretAccessListEntry = {
|
||||||
|
allowedActions: ProjectPermissionActions[];
|
||||||
|
id: string;
|
||||||
|
membershipId: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|||||||
+126
-2
@@ -12,6 +12,7 @@ import {
|
|||||||
} 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";
|
||||||
|
import { Link } from "@tanstack/react-router";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
@@ -34,10 +35,17 @@ import {
|
|||||||
Tooltip
|
Tooltip
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
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,
|
||||||
|
useWorkspace
|
||||||
|
} from "@app/context";
|
||||||
import { useToggle } from "@app/hooks";
|
import { useToggle } from "@app/hooks";
|
||||||
import { useGetSecretVersion } from "@app/hooks/api";
|
import { useGetSecretVersion } from "@app/hooks/api";
|
||||||
|
import { useGetSecretAccessList } from "@app/hooks/api/secrets/queries";
|
||||||
import { SecretV3RawSanitized, WsTag } from "@app/hooks/api/types";
|
import { SecretV3RawSanitized, WsTag } from "@app/hooks/api/types";
|
||||||
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
|
|
||||||
import { CreateReminderForm } from "./CreateReminderForm";
|
import { CreateReminderForm } from "./CreateReminderForm";
|
||||||
import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils";
|
import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils";
|
||||||
@@ -86,6 +94,7 @@ export const SecretDetailSidebar = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = useProjectPermission();
|
const { permission } = useProjectPermission();
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
|
||||||
const { fields, append, remove } = useFieldArray({
|
const { fields, append, remove } = useFieldArray({
|
||||||
control,
|
control,
|
||||||
@@ -130,6 +139,13 @@ export const SecretDetailSidebar = ({
|
|||||||
secretId: secret?.id
|
secretId: secret?.id
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: secretAccessList, isPending } = useGetSecretAccessList({
|
||||||
|
workspaceId: currentWorkspace.id,
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
secretKey
|
||||||
|
});
|
||||||
|
|
||||||
const handleOverrideClick = () => {
|
const handleOverrideClick = () => {
|
||||||
if (isOverridden) {
|
if (isOverridden) {
|
||||||
// override need not be flagged delete if it was never saved in server
|
// override need not be flagged delete if it was never saved in server
|
||||||
@@ -482,8 +498,116 @@ export const SecretDetailSidebar = ({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="dark mb-4 flex-grow text-sm text-bunker-300">
|
||||||
|
<div className="mb-2">Access List</div>
|
||||||
|
{!isPending && secretAccessList === undefined && (
|
||||||
|
<Button className="w-full px-2 py-1" variant="outline_bg">
|
||||||
|
Analyze Access
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{!isPending && secretAccessList && (
|
||||||
|
<div className="flex max-h-72 flex-col space-y-2 overflow-y-auto overflow-x-hidden rounded-md border border-mineshaft-600 bg-bunker-800 p-2 dark:[color-scheme:dark]">
|
||||||
|
{secretAccessList.users.length > 0 && (
|
||||||
|
<div className="pb-3">
|
||||||
|
<div className="mb-2 font-bold">Users</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{secretAccessList.users.map((user) => (
|
||||||
|
<div className="rounded-md bg-bunker-500 px-1">
|
||||||
|
<Link
|
||||||
|
to={
|
||||||
|
`/${ProjectType.SecretManager}/$projectId/members/$membershipId` as const
|
||||||
|
}
|
||||||
|
params={{
|
||||||
|
projectId: currentWorkspace.id,
|
||||||
|
membershipId: user.membershipId
|
||||||
|
}}
|
||||||
|
className="text-secondary/80 text-sm hover:text-primary"
|
||||||
|
>
|
||||||
|
{user.name}
|
||||||
|
<Tooltip
|
||||||
|
content={user.allowedActions.join(", ")}
|
||||||
|
className="z-[100]"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faCircleQuestion}
|
||||||
|
className="ml-1"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{secretAccessList.identities.length > 0 && (
|
||||||
|
<div className="pb-3">
|
||||||
|
<div className="mb-2 font-bold">Identities</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{secretAccessList.identities.map((identity) => (
|
||||||
|
<div className="rounded-md bg-bunker-500 px-1">
|
||||||
|
<Link
|
||||||
|
to={
|
||||||
|
`/${ProjectType.SecretManager}/$projectId/identities/$identityId` as const
|
||||||
|
}
|
||||||
|
params={{
|
||||||
|
projectId: currentWorkspace.id,
|
||||||
|
identityId: identity.id
|
||||||
|
}}
|
||||||
|
className="text-secondary/80 text-sm hover:text-primary"
|
||||||
|
>
|
||||||
|
{identity.name}
|
||||||
|
<Tooltip
|
||||||
|
content={identity.allowedActions.join(", ")}
|
||||||
|
className="z-[100]"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faCircleQuestion}
|
||||||
|
className="ml-1"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{secretAccessList.groups.length > 0 && (
|
||||||
|
<div className="pb-3">
|
||||||
|
<div className="mb-2 font-bold">Groups</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{secretAccessList.groups.map((group) => (
|
||||||
|
<div className="rounded-md bg-bunker-500 px-1">
|
||||||
|
<Link
|
||||||
|
to={"/organization/groups/$groupId" as const}
|
||||||
|
params={{
|
||||||
|
groupId: group.id
|
||||||
|
}}
|
||||||
|
className="text-secondary/80 text-sm hover:text-primary"
|
||||||
|
>
|
||||||
|
{group.name}
|
||||||
|
<Tooltip
|
||||||
|
content={group.allowedActions.join(", ")}
|
||||||
|
className="z-[100]"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faCircleQuestion}
|
||||||
|
className="ml-1"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div className="flex flex-col space-y-4">
|
<div className="flex flex-col space-y-4">
|
||||||
<div className="flex items-center space-x-4">
|
<div className="mb-2 flex items-center space-x-4">
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Edit}
|
I={ProjectPermissionActions.Edit}
|
||||||
a={subject(ProjectPermissionSub.Secrets, {
|
a={subject(ProjectPermissionSub.Secrets, {
|
||||||
|
|||||||
Reference in New Issue
Block a user