mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 19:16:22 +00:00
Merge remote-tracking branch 'origin' into ssh-certs
This commit is contained in:
@@ -18,15 +18,15 @@ export type TAccessApprovalPolicy = {
|
||||
approvers?: Approver[];
|
||||
};
|
||||
|
||||
export enum ApproverType{
|
||||
export enum ApproverType {
|
||||
User = "user",
|
||||
Group = "group"
|
||||
}
|
||||
|
||||
export type Approver ={
|
||||
export type Approver = {
|
||||
id: string;
|
||||
type: ApproverType;
|
||||
}
|
||||
};
|
||||
|
||||
export type TAccessApprovalRequest = {
|
||||
id: string;
|
||||
@@ -70,6 +70,7 @@ export type TAccessApprovalRequest = {
|
||||
secretPath?: string | null;
|
||||
envId: string;
|
||||
enforcementLevel: EnforcementLevel;
|
||||
deletedAt: Date | null;
|
||||
};
|
||||
|
||||
reviewers: {
|
||||
|
||||
@@ -8,6 +8,7 @@ export const eventToNameMap: { [K in EventType]: string } = {
|
||||
[EventType.DELETE_SECRET]: "Delete secret",
|
||||
[EventType.GET_WORKSPACE_KEY]: "Read project key",
|
||||
[EventType.AUTHORIZE_INTEGRATION]: "Authorize integration",
|
||||
[EventType.UPDATE_INTEGRATION_AUTH]: "Update integration auth",
|
||||
[EventType.UNAUTHORIZE_INTEGRATION]: "Unauthorize integration",
|
||||
[EventType.CREATE_INTEGRATION]: "Create integration",
|
||||
[EventType.DELETE_INTEGRATION]: "Delete integration",
|
||||
|
||||
@@ -23,6 +23,7 @@ export enum EventType {
|
||||
DELETE_SECRET = "delete-secret",
|
||||
GET_WORKSPACE_KEY = "get-workspace-key",
|
||||
AUTHORIZE_INTEGRATION = "authorize-integration",
|
||||
UPDATE_INTEGRATION_AUTH = "update-integration-auth",
|
||||
UNAUTHORIZE_INTEGRATION = "unauthorize-integration",
|
||||
CREATE_INTEGRATION = "create-integration",
|
||||
DELETE_INTEGRATION = "delete-integration",
|
||||
|
||||
@@ -177,11 +177,21 @@ export const useGetProjectSecretsOverview = (
|
||||
}),
|
||||
onError: (error) => {
|
||||
if (axios.isAxiosError(error)) {
|
||||
const serverResponse = error.response?.data as { message: string };
|
||||
const { message, requestId } = error.response?.data as {
|
||||
message: string;
|
||||
requestId: string;
|
||||
};
|
||||
createNotification({
|
||||
title: "Error fetching secret details",
|
||||
type: "error",
|
||||
text: serverResponse.message
|
||||
text: message,
|
||||
copyActions: [
|
||||
{
|
||||
value: requestId,
|
||||
name: "Request ID",
|
||||
label: `Request ID: ${requestId}`
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -270,11 +280,21 @@ export const useGetProjectSecretsDetails = (
|
||||
}),
|
||||
onError: (error) => {
|
||||
if (axios.isAxiosError(error)) {
|
||||
const serverResponse = error.response?.data as { message: string };
|
||||
const { message, requestId } = error.response?.data as {
|
||||
message: string;
|
||||
requestId: string;
|
||||
};
|
||||
createNotification({
|
||||
title: "Error fetching secret details",
|
||||
type: "error",
|
||||
text: serverResponse.message
|
||||
text: message,
|
||||
copyActions: [
|
||||
{
|
||||
value: requestId,
|
||||
name: "Request ID",
|
||||
label: `Request ID: ${requestId}`
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -355,11 +375,21 @@ export const useGetProjectSecretsQuickSearch = (
|
||||
}),
|
||||
onError: (error) => {
|
||||
if (axios.isAxiosError(error)) {
|
||||
const serverResponse = error.response?.data as { message: string };
|
||||
const { message, requestId } = error.response?.data as {
|
||||
message: string;
|
||||
requestId: string;
|
||||
};
|
||||
createNotification({
|
||||
title: "Error fetching secrets deep search",
|
||||
type: "error",
|
||||
text: serverResponse.message
|
||||
text: message,
|
||||
copyActions: [
|
||||
{
|
||||
value: requestId,
|
||||
name: "Request ID",
|
||||
label: `Request ID: ${requestId}`
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
export {
|
||||
useAddUserToGroup,
|
||||
useCreateGroup,
|
||||
useDeleteGroup,
|
||||
useRemoveUserFromGroup,
|
||||
useUpdateGroup} from "./mutations";
|
||||
export {
|
||||
useListGroupUsers
|
||||
} from "./queries";
|
||||
useAddUserToGroup,
|
||||
useCreateGroup,
|
||||
useDeleteGroup,
|
||||
useRemoveUserFromGroup,
|
||||
useUpdateGroup
|
||||
} from "./mutations";
|
||||
export { useGetGroupById, useListGroupUsers } from "./queries";
|
||||
|
||||
@@ -56,8 +56,9 @@ export const useUpdateGroup = () => {
|
||||
|
||||
return group;
|
||||
},
|
||||
onSuccess: ({ orgId }) => {
|
||||
onSuccess: ({ orgId, id: groupId }) => {
|
||||
queryClient.invalidateQueries(organizationKeys.getOrgGroups(orgId));
|
||||
queryClient.invalidateQueries(groupKeys.getGroupById(groupId));
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -70,8 +71,9 @@ export const useDeleteGroup = () => {
|
||||
|
||||
return group;
|
||||
},
|
||||
onSuccess: ({ orgId }) => {
|
||||
onSuccess: ({ orgId, id: groupId }) => {
|
||||
queryClient.invalidateQueries(organizationKeys.getOrgGroups(orgId));
|
||||
queryClient.invalidateQueries(groupKeys.getGroupById(groupId));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2,7 +2,10 @@ import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { EFilterReturnedUsers, TGroup, TGroupUser } from "./types";
|
||||
|
||||
export const groupKeys = {
|
||||
getGroupById: (groupId: string) => [{ groupId }, "group"] as const,
|
||||
allGroupUserMemberships: () => ["group-user-memberships"] as const,
|
||||
forGroupUserMemberships: (slug: string) =>
|
||||
[...groupKeys.allGroupUserMemberships(), slug] as const,
|
||||
@@ -10,22 +13,27 @@ export const groupKeys = {
|
||||
slug,
|
||||
offset,
|
||||
limit,
|
||||
search
|
||||
search,
|
||||
filter
|
||||
}: {
|
||||
slug: string;
|
||||
offset: number;
|
||||
limit: number;
|
||||
search: string;
|
||||
}) => [...groupKeys.forGroupUserMemberships(slug), { offset, limit, search }] as const
|
||||
filter?: EFilterReturnedUsers;
|
||||
}) => [...groupKeys.forGroupUserMemberships(slug), { offset, limit, search, filter }] as const
|
||||
};
|
||||
|
||||
type TUser = {
|
||||
id: string;
|
||||
email: string;
|
||||
username: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
isPartOfGroup: boolean;
|
||||
export const useGetGroupById = (groupId: string) => {
|
||||
return useQuery({
|
||||
enabled: Boolean(groupId),
|
||||
queryKey: groupKeys.getGroupById(groupId),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<TGroup>(`/api/v1/groups/${groupId}`);
|
||||
|
||||
return { group: data };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useListGroupUsers = ({
|
||||
@@ -33,20 +41,23 @@ export const useListGroupUsers = ({
|
||||
groupSlug,
|
||||
offset = 0,
|
||||
limit = 10,
|
||||
search
|
||||
search,
|
||||
filter
|
||||
}: {
|
||||
id: string;
|
||||
groupSlug: string;
|
||||
offset: number;
|
||||
limit: number;
|
||||
search: string;
|
||||
filter?: EFilterReturnedUsers;
|
||||
}) => {
|
||||
return useQuery({
|
||||
queryKey: groupKeys.specificGroupUserMemberships({
|
||||
slug: groupSlug,
|
||||
offset,
|
||||
limit,
|
||||
search
|
||||
search,
|
||||
filter
|
||||
}),
|
||||
enabled: Boolean(groupSlug),
|
||||
keepPreviousData: true,
|
||||
@@ -54,10 +65,11 @@ export const useListGroupUsers = ({
|
||||
const params = new URLSearchParams({
|
||||
offset: String(offset),
|
||||
limit: String(limit),
|
||||
search
|
||||
search,
|
||||
...(filter && { filter })
|
||||
});
|
||||
|
||||
const { data } = await apiRequest.get<{ users: TUser[]; totalCount: number }>(
|
||||
const { data } = await apiRequest.get<{ users: TGroupUser[]; totalCount: number }>(
|
||||
`/api/v1/groups/${id}/users`,
|
||||
{
|
||||
params
|
||||
|
||||
@@ -11,7 +11,7 @@ export type TGroup = {
|
||||
name: string;
|
||||
slug: string;
|
||||
orgId: string;
|
||||
createAt: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
role: string;
|
||||
};
|
||||
@@ -41,3 +41,18 @@ export type TGroupWithProjectMemberships = {
|
||||
slug: string;
|
||||
orgId: string;
|
||||
};
|
||||
|
||||
export type TGroupUser = {
|
||||
id: string;
|
||||
email: string;
|
||||
username: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
isPartOfGroup: boolean;
|
||||
joinedGroupAt: Date;
|
||||
};
|
||||
|
||||
export enum EFilterReturnedUsers {
|
||||
EXISTING_MEMBERS = "existingMembers",
|
||||
NON_MEMBERS = "nonMembers"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export {
|
||||
useAddExternalKms,
|
||||
useExternalKmsFetchGcpKeys,
|
||||
useLoadProjectKmsBackup,
|
||||
useRemoveExternalKms,
|
||||
useUpdateExternalKms,
|
||||
|
||||
@@ -3,7 +3,13 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { kmsKeys } from "./queries";
|
||||
import { AddExternalKmsType, KmsType } from "./types";
|
||||
import {
|
||||
AddExternalKmsType,
|
||||
ExternalKmsGcpSchemaType,
|
||||
KmsGcpKeyFetchAuthType,
|
||||
KmsType,
|
||||
UpdateExternalKmsType
|
||||
} from "./types";
|
||||
|
||||
export const useAddExternalKms = (orgId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -33,7 +39,7 @@ export const useUpdateExternalKms = (orgId: string) => {
|
||||
provider
|
||||
}: {
|
||||
kmsId: string;
|
||||
} & AddExternalKmsType) => {
|
||||
} & UpdateExternalKmsType) => {
|
||||
const { data } = await apiRequest.patch(`/api/v1/external-kms/${kmsId}`, {
|
||||
name,
|
||||
description,
|
||||
@@ -96,3 +102,44 @@ export const useLoadProjectKmsBackup = (projectId: string) => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useExternalKmsFetchGcpKeys = (orgId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
gcpRegion,
|
||||
...rest
|
||||
}: Pick<ExternalKmsGcpSchemaType, "gcpRegion"> &
|
||||
(
|
||||
| (Pick<ExternalKmsGcpSchemaType, KmsGcpKeyFetchAuthType.Credential> & {
|
||||
[KmsGcpKeyFetchAuthType.Kms]?: never;
|
||||
})
|
||||
| {
|
||||
[KmsGcpKeyFetchAuthType.Kms]: string;
|
||||
[KmsGcpKeyFetchAuthType.Credential]?: never;
|
||||
}
|
||||
)): Promise<{ keys: string[] }> => {
|
||||
const {
|
||||
[KmsGcpKeyFetchAuthType.Credential]: credential,
|
||||
[KmsGcpKeyFetchAuthType.Kms]: kmsId
|
||||
} = rest;
|
||||
|
||||
if ((credential && kmsId) || (!credential && !kmsId)) {
|
||||
throw new Error(
|
||||
`Either '${KmsGcpKeyFetchAuthType.Credential}' or '${KmsGcpKeyFetchAuthType.Kms}' must be provided, but not both.`
|
||||
);
|
||||
}
|
||||
|
||||
const { data } = await apiRequest.post("/api/v1/external-kms/gcp/keys", {
|
||||
authMethod: credential ? KmsGcpKeyFetchAuthType.Credential : KmsGcpKeyFetchAuthType.Kms,
|
||||
region: gcpRegion,
|
||||
...rest
|
||||
});
|
||||
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(kmsKeys.getExternalKmsList(orgId));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import { z } from "zod";
|
||||
|
||||
import { slugSchema } from "@app/lib/schemas";
|
||||
|
||||
export type Kms = {
|
||||
id: string;
|
||||
description: string;
|
||||
@@ -35,7 +37,8 @@ export enum KmsType {
|
||||
}
|
||||
|
||||
export enum ExternalKmsProvider {
|
||||
AWS = "aws"
|
||||
Aws = "aws",
|
||||
Gcp = "gcp"
|
||||
}
|
||||
|
||||
export const INTERNAL_KMS_KEY_ID = "internal";
|
||||
@@ -44,6 +47,10 @@ export enum KmsAwsCredentialType {
|
||||
AssumeRole = "assume-role",
|
||||
AccessKey = "access-key"
|
||||
}
|
||||
// Google uses snake_case for their enum values and we need to match that
|
||||
export enum KmsGcpCredentialType {
|
||||
ServiceAccount = "service_account"
|
||||
}
|
||||
|
||||
export const ExternalKmsAwsSchema = z.object({
|
||||
credential: z
|
||||
@@ -83,11 +90,54 @@ export const ExternalKmsAwsSchema = z.object({
|
||||
)
|
||||
});
|
||||
|
||||
export const ExternalKmsGcpCredentialSchema = z.object({
|
||||
type: z.literal(KmsGcpCredentialType.ServiceAccount),
|
||||
project_id: z.string().min(1),
|
||||
private_key_id: z.string().min(1),
|
||||
private_key: z.string().min(1),
|
||||
client_email: z.string().min(1),
|
||||
client_id: z.string().min(1),
|
||||
auth_uri: z.string().min(1),
|
||||
token_uri: z.string().min(1),
|
||||
auth_provider_x509_cert_url: z.string().min(1),
|
||||
client_x509_cert_url: z.string().min(1),
|
||||
universe_domain: z.string().min(1)
|
||||
});
|
||||
|
||||
export type ExternalKmsGcpCredentialSchemaType = z.infer<typeof ExternalKmsGcpCredentialSchema>;
|
||||
|
||||
export const ExternalKmsGcpSchema = z.object({
|
||||
credential: ExternalKmsGcpCredentialSchema.describe(
|
||||
"GCP Service Account JSON credential to connect"
|
||||
),
|
||||
gcpRegion: z.string().min(1).trim().describe("GCP region where the KMS key is located"),
|
||||
keyName: z.string().min(1).trim().describe("GCP key name")
|
||||
});
|
||||
export type ExternalKmsGcpSchemaType = z.infer<typeof ExternalKmsGcpSchema>;
|
||||
|
||||
export const ExternalKmsInputSchema = z.discriminatedUnion("type", [
|
||||
z.object({ type: z.literal(ExternalKmsProvider.AWS), inputs: ExternalKmsAwsSchema })
|
||||
z.object({ type: z.literal(ExternalKmsProvider.Aws), inputs: ExternalKmsAwsSchema }),
|
||||
z.object({ type: z.literal(ExternalKmsProvider.Gcp), inputs: ExternalKmsGcpSchema })
|
||||
]);
|
||||
|
||||
export const AddExternalKmsSchema = z.object({
|
||||
name: slugSchema({ min: 1, field: "Alias" }),
|
||||
description: z.string().trim().optional(),
|
||||
provider: ExternalKmsInputSchema
|
||||
});
|
||||
|
||||
export type AddExternalKmsType = z.infer<typeof AddExternalKmsSchema>;
|
||||
|
||||
// we need separate schema for update because the credential field is not required on GCP
|
||||
export const ExternalKmsUpdateInputSchema = z.discriminatedUnion("type", [
|
||||
z.object({ type: z.literal(ExternalKmsProvider.Aws), inputs: ExternalKmsAwsSchema }),
|
||||
z.object({
|
||||
type: z.literal(ExternalKmsProvider.Gcp),
|
||||
inputs: ExternalKmsGcpSchema.pick({ gcpRegion: true, keyName: true })
|
||||
})
|
||||
]);
|
||||
|
||||
export const UpdateExternalKmsSchema = z.object({
|
||||
name: z
|
||||
.string()
|
||||
.trim()
|
||||
@@ -96,7 +146,52 @@ export const AddExternalKmsSchema = z.object({
|
||||
message: "Alias must be a valid slug"
|
||||
}),
|
||||
description: z.string().trim().optional(),
|
||||
provider: ExternalKmsInputSchema
|
||||
provider: ExternalKmsUpdateInputSchema
|
||||
});
|
||||
|
||||
export type AddExternalKmsType = z.infer<typeof AddExternalKmsSchema>;
|
||||
export type UpdateExternalKmsType = z.infer<typeof UpdateExternalKmsSchema>;
|
||||
|
||||
const GCP_CREDENTIAL_MAX_FILE_SIZE = 8 * 1024; // 8KB
|
||||
const GCP_CREDENTIAL_ACCEPTED_FILE_TYPES = ["application/json"];
|
||||
|
||||
const AddExternalKmsGcpFormSchemaStandardInputs = z.object({
|
||||
keyObject: z
|
||||
.object({ label: z.string().trim(), value: z.string().trim() })
|
||||
.describe("GCP key name"),
|
||||
gcpRegion: z.object({ label: z.string().trim(), value: z.string().trim() }).describe("GCP Region")
|
||||
});
|
||||
|
||||
export const AddExternalKmsGcpFormSchema = z.discriminatedUnion("formType", [
|
||||
z
|
||||
.object({
|
||||
formType: z.literal("newGcpKms"),
|
||||
// `FileList` is a browser-only (window-specific) type, so we need to handle it differently on the server to avoid SSR errors
|
||||
credentialFile:
|
||||
typeof window === "undefined"
|
||||
? z.any()
|
||||
: z
|
||||
.instanceof(FileList)
|
||||
.refine((files) => files?.length === 1, "Image is required.")
|
||||
.refine(
|
||||
(files) => files?.[0]?.size <= GCP_CREDENTIAL_MAX_FILE_SIZE,
|
||||
"Max file size is 8KB."
|
||||
)
|
||||
.refine(
|
||||
(files) => GCP_CREDENTIAL_ACCEPTED_FILE_TYPES.includes(files?.[0]?.type),
|
||||
"Only .json files are accepted."
|
||||
)
|
||||
})
|
||||
.merge(AddExternalKmsGcpFormSchemaStandardInputs)
|
||||
.merge(AddExternalKmsSchema.pick({ name: true, description: true })),
|
||||
z
|
||||
.object({ formType: z.literal("updateGcpKms") })
|
||||
.merge(AddExternalKmsGcpFormSchemaStandardInputs)
|
||||
.merge(AddExternalKmsSchema.pick({ name: true, description: true }))
|
||||
]);
|
||||
|
||||
export type AddExternalKmsGcpFormSchemaType = z.infer<typeof AddExternalKmsGcpFormSchema>;
|
||||
|
||||
export enum KmsGcpKeyFetchAuthType {
|
||||
Credential = "credential",
|
||||
Kms = "kmsId"
|
||||
}
|
||||
|
||||
@@ -117,11 +117,21 @@ export const useGetProjectSecrets = ({
|
||||
queryFn: () => fetchProjectSecrets({ workspaceId, environment, secretPath }),
|
||||
onError: (error) => {
|
||||
if (axios.isAxiosError(error)) {
|
||||
const serverResponse = error.response?.data as { message: string };
|
||||
const { message, requestId } = error.response?.data as {
|
||||
message: string;
|
||||
requestId: string;
|
||||
};
|
||||
createNotification({
|
||||
title: "Error fetching secrets",
|
||||
type: "error",
|
||||
text: serverResponse.message
|
||||
text: message,
|
||||
copyActions: [
|
||||
{
|
||||
value: requestId,
|
||||
name: "Request ID",
|
||||
label: `Request ID: ${requestId}`
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -148,15 +158,24 @@ export const useGetProjectSecretsAllEnv = ({
|
||||
enabled: Boolean(workspaceId && environment),
|
||||
onError: (error: unknown) => {
|
||||
if (axios.isAxiosError(error) && !isErrorHandled) {
|
||||
const serverResponse = error.response?.data as { message: string };
|
||||
if (serverResponse.message !== ERROR_NOT_ALLOWED_READ_SECRETS) {
|
||||
const { message, requestId } = error.response?.data as {
|
||||
message: string;
|
||||
requestId: string;
|
||||
};
|
||||
if (message !== ERROR_NOT_ALLOWED_READ_SECRETS) {
|
||||
createNotification({
|
||||
title: "Error fetching secrets",
|
||||
type: "error",
|
||||
text: serverResponse.message
|
||||
text: message,
|
||||
copyActions: [
|
||||
{
|
||||
value: requestId,
|
||||
name: "Request ID",
|
||||
label: `Request ID: ${requestId}`
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
setIsErrorHandled.on();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -51,26 +51,26 @@ export enum ApiErrorTypes {
|
||||
|
||||
export type TApiErrors =
|
||||
| {
|
||||
requestId: string;
|
||||
reqId: string;
|
||||
error: ApiErrorTypes.ValidationError;
|
||||
message: ZodIssue[];
|
||||
statusCode: 401;
|
||||
statusCode: 422;
|
||||
}
|
||||
| {
|
||||
requestId: string;
|
||||
reqId: string;
|
||||
error: ApiErrorTypes.UnauthorizedError;
|
||||
message: string;
|
||||
statusCode: 401;
|
||||
}
|
||||
| {
|
||||
requestId: string;
|
||||
reqId: string;
|
||||
error: ApiErrorTypes.ForbiddenError;
|
||||
message: string;
|
||||
details: PureAbility["rules"];
|
||||
statusCode: 403;
|
||||
}
|
||||
| {
|
||||
requestId: string;
|
||||
reqId: string;
|
||||
statusCode: 400;
|
||||
message: string;
|
||||
error: ApiErrorTypes.BadRequestError;
|
||||
|
||||
Reference in New Issue
Block a user