Merge branch 'main' into feat/addHumanitecIntegration

This commit is contained in:
carlosmonastyrski
2025-03-13 10:55:40 -03:00
103 changed files with 3196 additions and 567 deletions
+2 -2
View File
@@ -1,10 +1,10 @@
export {
useAdminDeleteUser,
useAdminGrantServerAdminAccess,
useCreateAdminUser,
useUpdateAdminSlackConfig,
useUpdateServerConfig,
useUpdateServerEncryptionStrategy,
useAdminGrantServerAdminAccess
useUpdateServerEncryptionStrategy
} from "./mutation";
export {
useAdminGetUsers,
+28 -1
View File
@@ -4,19 +4,24 @@ import { apiRequest } from "@app/config/request";
import { User } from "../types";
import {
AdminGetIdentitiesFilters,
AdminGetUsersFilters,
AdminSlackConfig,
TGetServerRootKmsEncryptionDetails,
TServerConfig
} from "./types";
import { Identity } from "@app/hooks/api/identities/types";
export const adminStandaloneKeys = {
getUsers: "get-users"
getUsers: "get-users",
getIdentities: "get-identities"
};
export const adminQueryKeys = {
serverConfig: () => ["server-config"] as const,
getUsers: (filters: AdminGetUsersFilters) => [adminStandaloneKeys.getUsers, { filters }] as const,
getIdentities: (filters: AdminGetIdentitiesFilters) =>
[adminStandaloneKeys.getIdentities, { filters }] as const,
getAdminSlackConfig: () => ["admin-slack-config"] as const,
getServerEncryptionStrategies: () => ["server-encryption-strategies"] as const
};
@@ -68,6 +73,28 @@ export const useAdminGetUsers = (filters: AdminGetUsersFilters) => {
});
};
export const useAdminGetIdentities = (filters: AdminGetIdentitiesFilters) => {
return useInfiniteQuery({
initialPageParam: 0,
queryKey: adminQueryKeys.getIdentities(filters),
queryFn: async ({ pageParam }) => {
const { data } = await apiRequest.get<{ identities: Identity[] }>(
"/api/v1/admin/identity-management/identities",
{
params: {
...filters,
offset: pageParam
}
}
);
return data.identities;
},
getNextPageParam: (lastPage, pages) =>
lastPage.length !== 0 ? pages.length * filters.limit : undefined
});
};
export const useGetAdminSlackConfig = () => {
return useQuery({
queryKey: adminQueryKeys.getAdminSlackConfig(),
+5
View File
@@ -53,6 +53,11 @@ export type AdminGetUsersFilters = {
adminsOnly: boolean;
};
export type AdminGetIdentitiesFilters = {
limit: number;
searchTerm: string;
};
export type AdminSlackConfig = {
clientId: string;
clientSecret: string;
+158 -263
View File
@@ -18,268 +18,44 @@ export const SIGNUP_TEMP_TOKEN_CACHE_KEY = ["infisical__signup-temp-token"];
export const MFA_TEMP_TOKEN_CACHE_KEY = ["infisical__mfa-temp-token"];
export const AUTH_TOKEN_CACHE_KEY = ["infisical__auth-token"];
export const queryClient = new QueryClient({
mutationCache: new MutationCache({
onError: (error) => {
if (axios.isAxiosError(error)) {
const serverResponse = error.response?.data as TApiErrors;
if (serverResponse?.error === ApiErrorTypes.ValidationError) {
createNotification(
{
title: "Validation Error",
type: "error",
text: "Please check the input and try again.",
callToAction: (
<Modal>
<ModalTrigger asChild>
<Button variant="outline_bg" size="xs">
Show more
</Button>
</ModalTrigger>
<ModalContent title="Validation Error Details">
<TableContainer>
<Table>
<THead>
<Tr>
<Th>Field</Th>
<Th>Issue</Th>
</Tr>
</THead>
<TBody>
{serverResponse.message?.map(({ message, path }) => (
<Tr key={path.join(".")}>
<Td>{path.join(".")}</Td>
<Td>{message.toLowerCase()}</Td>
</Tr>
))}
</TBody>
</Table>
</TableContainer>
</ModalContent>
</Modal>
),
copyActions: [
{
value: serverResponse.reqId,
name: "Request ID",
label: `Request ID: ${serverResponse.reqId}`
}
]
},
{ closeOnClick: false }
);
return;
}
if (serverResponse?.error === ApiErrorTypes.PermissionBoundaryError) {
createNotification(
{
title: "Forbidden Access",
type: "error",
text: `${serverResponse.message}.`,
callToAction: serverResponse?.details?.missingPermissions?.length ? (
<Modal>
<ModalTrigger asChild>
<Button variant="outline_bg" size="xs">
Show more
</Button>
</ModalTrigger>
<ModalContent title="Missing Permission">
<div className="flex flex-col gap-2">
{serverResponse.details?.missingPermissions?.map((el, index) => {
const hasConditions = Boolean(Object.keys(el.conditions || {}).length);
return (
<div
key={`Forbidden-error-details-${index + 1}`}
className="rounded-md border border-gray-600 p-4"
>
<div>
You are not authorized to perform the <b>{el.action}</b> action on the{" "}
<b>{el.subject}</b> resource.{" "}
{hasConditions &&
"Your permission does not allow access to the following conditions:"}
</div>
{hasConditions && (
<ul className="flex list-disc flex-col gap-1 pl-5 pt-2 text-sm">
{Object.keys(el.conditions || {}).flatMap((field, fieldIndex) => {
const operators = (
el.conditions as Record<
string,
| string
| { [K in PermissionConditionOperators]: string | string[] }
>
)[field];
const formattedFieldName = camelCaseToSpaces(field).toLowerCase();
if (typeof operators === "string") {
return (
<li
key={`Forbidden-error-details-${index + 1}-${
fieldIndex + 1
}`}
>
<span className="font-bold capitalize">
{formattedFieldName}
</span>{" "}
<span className="text-mineshaft-200">equal to</span>{" "}
<span className="text-yellow-600">{operators}</span>
</li>
);
}
return Object.keys(operators).map((operator, operatorIndex) => (
<li
key={`Forbidden-error-details-${index + 1}-${
fieldIndex + 1
}-${operatorIndex + 1}`}
>
<span className="font-bold capitalize">
{formattedFieldName}
</span>{" "}
<span className="text-mineshaft-200">
{
formatedConditionsOperatorNames[
operator as PermissionConditionOperators
]
}
</span>{" "}
<span className="text-yellow-600">
{operators[
operator as PermissionConditionOperators
].toString()}
</span>
</li>
));
})}
</ul>
)}
</div>
);
})}
</div>
</ModalContent>
</Modal>
) : undefined,
copyActions: [
{
value: serverResponse.reqId,
name: "Request ID",
label: `Request ID: ${serverResponse.reqId}`
}
]
},
{ closeOnClick: false }
);
return;
}
if (serverResponse?.error === ApiErrorTypes.ForbiddenError) {
createNotification(
{
title: "Forbidden Access",
type: "error",
text: `${serverResponse.message}.`,
callToAction: serverResponse?.details?.length ? (
<Modal>
<ModalTrigger asChild>
<Button variant="outline_bg" size="xs">
Show more
</Button>
</ModalTrigger>
<ModalContent
title="Validation Rules"
subTitle="Please review the allowed rules below."
>
<div className="flex flex-col gap-2">
{serverResponse.details?.map((el, index) => {
const hasConditions = Boolean(Object.keys(el.conditions || {}).length);
return (
<div
key={`Forbidden-error-details-${index + 1}`}
className="rounded-md border border-gray-600 p-4"
>
<div>
{el.inverted ? "Cannot" : "Can"}{" "}
<span className="text-yellow-600">
{el.action.toString().replaceAll(",", ", ")}
</span>{" "}
{el.subject.toString()} {hasConditions && "with conditions:"}
</div>
{hasConditions && (
<ul className="flex list-disc flex-col gap-1 pl-5 pt-2 text-sm">
{Object.keys(el.conditions || {}).flatMap((field, fieldIndex) => {
const operators = (
el.conditions as Record<
string,
| string
| { [K in PermissionConditionOperators]: string | string[] }
>
)[field];
const formattedFieldName = camelCaseToSpaces(field).toLowerCase();
if (typeof operators === "string") {
return (
<li
key={`Forbidden-error-details-${index + 1}-${
fieldIndex + 1
}`}
>
<span className="font-bold capitalize">
{formattedFieldName}
</span>{" "}
<span className="text-mineshaft-200">equal to</span>{" "}
<span className="text-yellow-600">{operators}</span>
</li>
);
}
return Object.keys(operators).map((operator, operatorIndex) => (
<li
key={`Forbidden-error-details-${index + 1}-${
fieldIndex + 1
}-${operatorIndex + 1}`}
>
<span className="font-bold capitalize">
{formattedFieldName}
</span>{" "}
<span className="text-mineshaft-200">
{
formatedConditionsOperatorNames[
operator as PermissionConditionOperators
]
}
</span>{" "}
<span className="text-yellow-600">
{operators[
operator as PermissionConditionOperators
].toString()}
</span>
</li>
));
})}
</ul>
)}
</div>
);
})}
</div>
</ModalContent>
</Modal>
) : undefined,
copyActions: [
{
value: serverResponse.reqId,
name: "Request ID",
label: `Request ID: ${serverResponse.reqId}`
}
]
},
{ closeOnClick: false }
);
return;
}
createNotification({
title: "Bad Request",
export const onRequestError = (error: unknown) => {
if (axios.isAxiosError(error)) {
const serverResponse = error.response?.data as TApiErrors;
if (serverResponse?.error === ApiErrorTypes.ValidationError) {
createNotification(
{
title: "Validation Error",
type: "error",
text: `${serverResponse.message}${serverResponse.message?.endsWith(".") ? "" : "."}`,
text: "Please check the input and try again.",
callToAction: (
<Modal>
<ModalTrigger asChild>
<Button variant="outline_bg" size="xs">
Show more
</Button>
</ModalTrigger>
<ModalContent title="Validation Error Details">
<TableContainer>
<Table>
<THead>
<Tr>
<Th>Field</Th>
<Th>Issue</Th>
</Tr>
</THead>
<TBody>
{serverResponse.message?.map(({ message, path }) => (
<Tr key={path.join(".")}>
<Td>{path.join(".")}</Td>
<Td>{message.toLowerCase()}</Td>
</Tr>
))}
</TBody>
</Table>
</TableContainer>
</ModalContent>
</Modal>
),
copyActions: [
{
value: serverResponse.reqId,
@@ -287,9 +63,128 @@ export const queryClient = new QueryClient({
label: `Request ID: ${serverResponse.reqId}`
}
]
});
}
},
{ closeOnClick: false }
);
return;
}
if (serverResponse?.error === ApiErrorTypes.ForbiddenError) {
createNotification(
{
title: "Forbidden Access",
type: "error",
text: `${serverResponse.message}.`,
callToAction: serverResponse?.details?.length ? (
<Modal>
<ModalTrigger asChild>
<Button variant="outline_bg" size="xs">
Show more
</Button>
</ModalTrigger>
<ModalContent
title="Validation Rules"
subTitle="Please review the allowed rules below."
>
<div className="flex flex-col gap-2">
{serverResponse.details?.map((el, index) => {
const hasConditions = Boolean(Object.keys(el.conditions || {}).length);
return (
<div
key={`Forbidden-error-details-${index + 1}`}
className="rounded-md border border-gray-600 p-4"
>
<div>
{el.inverted ? "Cannot" : "Can"}{" "}
<span className="text-yellow-600">
{el.action.toString().replaceAll(",", ", ")}
</span>{" "}
{el.subject.toString()} {hasConditions && "with conditions:"}
</div>
{hasConditions && (
<ul className="flex list-disc flex-col gap-1 pl-5 pt-2 text-sm">
{Object.keys(el.conditions || {}).flatMap((field, fieldIndex) => {
const operators = (
el.conditions as Record<
string,
| string
| { [K in PermissionConditionOperators]: string | string[] }
>
)[field];
const formattedFieldName = camelCaseToSpaces(field).toLowerCase();
if (typeof operators === "string") {
return (
<li
key={`Forbidden-error-details-${index + 1}-${fieldIndex + 1}`}
>
<span className="font-bold capitalize">
{formattedFieldName}
</span>{" "}
<span className="text-mineshaft-200">equal to</span>{" "}
<span className="text-yellow-600">{operators}</span>
</li>
);
}
return Object.keys(operators).map((operator, operatorIndex) => (
<li
key={`Forbidden-error-details-${index + 1}-${
fieldIndex + 1
}-${operatorIndex + 1}`}
>
<span className="font-bold capitalize">{formattedFieldName}</span>{" "}
<span className="text-mineshaft-200">
{
formatedConditionsOperatorNames[
operator as PermissionConditionOperators
]
}
</span>{" "}
<span className="text-yellow-600">
{operators[operator as PermissionConditionOperators].toString()}
</span>
</li>
));
})}
</ul>
)}
</div>
);
})}
</div>
</ModalContent>
</Modal>
) : undefined,
copyActions: [
{
value: serverResponse.reqId,
name: "Request ID",
label: `Request ID: ${serverResponse.reqId}`
}
]
},
{ closeOnClick: false }
);
return;
}
createNotification({
title: "Bad Request",
type: "error",
text: `${serverResponse.message}${serverResponse.message?.endsWith(".") ? "" : "."}`,
copyActions: [
{
value: serverResponse.reqId,
name: "Request ID",
label: `Request ID: ${serverResponse.reqId}`
}
]
});
}
};
export const queryClient = new QueryClient({
mutationCache: new MutationCache({
onError: onRequestError
}),
defaultOptions: {
queries: {
@@ -16,6 +16,7 @@ import {
TDeleteFolderDTO,
TGetFoldersByEnvDTO,
TGetProjectFoldersDTO,
TProjectEnvironmentsFolders,
TSecretFolder,
TUpdateFolderBatchDTO,
TUpdateFolderDTO
@@ -23,7 +24,9 @@ import {
export const folderQueryKeys = {
getSecretFolders: ({ projectId, environment, path }: TGetProjectFoldersDTO) =>
["secret-folders", { projectId, environment, path }] as const
["secret-folders", { projectId, environment, path }] as const,
getProjectEnvironmentsFolders: (projectId: string) =>
["secret-folders", "environment", projectId] as const
};
const fetchProjectFolders = async (workspaceId: string, environment: string, path = "/") => {
@@ -37,6 +40,29 @@ const fetchProjectFolders = async (workspaceId: string, environment: string, pat
return data.folders;
};
export const useListProjectEnvironmentsFolders = (
projectId: string,
options?: Omit<
UseQueryOptions<
TProjectEnvironmentsFolders,
unknown,
TProjectEnvironmentsFolders,
ReturnType<typeof folderQueryKeys.getProjectEnvironmentsFolders>
>,
"queryKey" | "queryFn"
>
) =>
useQuery({
queryKey: folderQueryKeys.getProjectEnvironmentsFolders(projectId),
queryFn: async () => {
const { data } = await apiRequest.get<TProjectEnvironmentsFolders>(
`/api/v1/workspace/${projectId}/environment-folder-tree`
);
return data;
},
...options
});
export const useGetProjectFolders = ({
projectId,
environment,
@@ -1,3 +1,5 @@
import { WorkspaceEnv } from "@app/hooks/api/workspace/types";
export enum ReservedFolders {
SecretReplication = "__reserve_replication_"
}
@@ -6,6 +8,13 @@ export type TSecretFolder = {
id: string;
name: string;
description?: string;
parentId?: string | null;
};
export type TSecretFolderWithPath = TSecretFolder & { path: string };
export type TProjectEnvironmentsFolders = {
[key: string]: WorkspaceEnv & { folders: TSecretFolderWithPath[] };
};
export type TGetProjectFoldersDTO = {
@@ -10,13 +10,16 @@ type FolderNameAndDescription = {
export const useFolderOverview = (folders: DashboardProjectSecretsOverview["folders"]) => {
const folderNamesAndDescriptions = useMemo(() => {
const namesAndDescriptions = new Map<string, FolderNameAndDescription>();
folders?.forEach((folder) => {
if (!namesAndDescriptions.has(folder.name)) {
namesAndDescriptions.set(folder.name, { name: folder.name, description: folder.description });
namesAndDescriptions.set(folder.name, {
name: folder.name,
description: folder.description
});
}
});
return Array.from(namesAndDescriptions.values());
}, [folders]);