Merge pull request #2497 from scott-ray-wilson/kms-feature

Feature: KMS MVP
This commit is contained in:
Scott Wilson
2024-10-03 15:15:08 -07:00
committed by GitHub
87 changed files with 2685 additions and 230 deletions
+3
View File
@@ -0,0 +1,3 @@
export * from "./mutations";
export * from "./queries";
export * from "./types";
@@ -0,0 +1,90 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { encodeBase64 } from "tweetnacl-util";
import { apiRequest } from "@app/config/request";
import { cmekKeys } from "@app/hooks/api/cmeks/queries";
import {
TCmekDecrypt,
TCmekDecryptResponse,
TCmekEncrypt,
TCmekEncryptResponse,
TCreateCmek,
TDeleteCmek,
TUpdateCmek
} from "@app/hooks/api/cmeks/types";
export const useCreateCmek = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (payload: TCreateCmek) => {
const { data } = await apiRequest.post("/api/v1/kms/keys", payload);
return data;
},
onSuccess: (_, { projectId }) => {
queryClient.invalidateQueries(cmekKeys.getCmeksByProjectId({ projectId }));
}
});
};
export const useUpdateCmek = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ keyId, name, description, isDisabled }: TUpdateCmek) => {
const { data } = await apiRequest.patch(`/api/v1/kms/keys/${keyId}`, {
name,
description,
isDisabled
});
return data;
},
onSuccess: (_, { projectId }) => {
queryClient.invalidateQueries(cmekKeys.getCmeksByProjectId({ projectId }));
}
});
};
export const useDeleteCmek = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ keyId }: TDeleteCmek) => {
const { data } = await apiRequest.delete(`/api/v1/kms/keys/${keyId}`);
return data;
},
onSuccess: (_, { projectId }) => {
queryClient.invalidateQueries(cmekKeys.getCmeksByProjectId({ projectId }));
}
});
};
export const useCmekEncrypt = () => {
return useMutation({
mutationFn: async ({ keyId, plaintext, isBase64Encoded }: TCmekEncrypt) => {
const { data } = await apiRequest.post<TCmekEncryptResponse>(
`/api/v1/kms/keys/${keyId}/encrypt`,
{
plaintext: isBase64Encoded ? plaintext : encodeBase64(Buffer.from(plaintext))
}
);
return data;
}
});
};
export const useCmekDecrypt = () => {
return useMutation({
mutationFn: async ({ keyId, ciphertext }: TCmekDecrypt) => {
const { data } = await apiRequest.post<TCmekDecryptResponse>(
`/api/v1/kms/keys/${keyId}/decrypt`,
{
ciphertext
}
);
return data;
}
});
};
+53
View File
@@ -0,0 +1,53 @@
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { CmekOrderBy, TListProjectCmeksDTO, TProjectCmeksList } from "@app/hooks/api/cmeks/types";
import { OrderByDirection } from "@app/hooks/api/generic/types";
export const cmekKeys = {
all: ["cmek"] as const,
lists: () => [...cmekKeys.all, "list"] as const,
getCmeksByProjectId: ({ projectId, ...filters }: TListProjectCmeksDTO) =>
[...cmekKeys.lists(), projectId, filters] as const
};
export const useGetCmeksByProjectId = (
{
projectId,
offset = 0,
limit = 100,
orderBy = CmekOrderBy.Name,
orderDirection = OrderByDirection.ASC,
search = ""
}: TListProjectCmeksDTO,
options?: Omit<
UseQueryOptions<
TProjectCmeksList,
unknown,
TProjectCmeksList,
ReturnType<typeof cmekKeys.getCmeksByProjectId>
>,
"queryKey" | "queryFn"
>
) => {
return useQuery({
queryKey: cmekKeys.getCmeksByProjectId({
projectId,
offset,
limit,
orderBy,
orderDirection,
search
}),
queryFn: async () => {
const { data } = await apiRequest.get<TProjectCmeksList>("/api/v1/kms/keys", {
params: { projectId, offset, limit, search, orderBy, orderDirection }
});
return data;
},
enabled: Boolean(projectId) && (options?.enabled ?? true),
keepPreviousData: true,
...options
});
};
+58
View File
@@ -0,0 +1,58 @@
import { OrderByDirection } from "@app/hooks/api/generic/types";
export type TCmek = {
id: string;
name: string;
description?: string;
encryptionAlgorithm: EncryptionAlgorithm;
projectId: string;
isDisabled: boolean;
isReserved: boolean;
orgId: string;
version: number;
createdAt: string;
updatedAt: string;
};
type ProjectRef = { projectId: string };
type KeyRef = { keyId: string };
export type TCreateCmek = Pick<TCmek, "name" | "description" | "encryptionAlgorithm"> & ProjectRef;
export type TUpdateCmek = KeyRef &
Partial<Pick<TCmek, "name" | "description" | "isDisabled">> &
ProjectRef;
export type TDeleteCmek = KeyRef & ProjectRef;
export type TCmekEncrypt = KeyRef & { plaintext: string; isBase64Encoded?: boolean };
export type TCmekDecrypt = KeyRef & { ciphertext: string };
export type TProjectCmeksList = {
keys: TCmek[];
totalCount: number;
};
export type TListProjectCmeksDTO = {
projectId: string;
offset?: number;
limit?: number;
orderBy?: CmekOrderBy;
orderDirection?: OrderByDirection;
search?: string;
};
export type TCmekEncryptResponse = {
ciphertext: string;
};
export type TCmekDecryptResponse = {
plaintext: string;
};
export enum CmekOrderBy {
Name = "name"
}
export enum EncryptionAlgorithm {
AES_GCM_256 = "aes-256-gcm",
AES_GCM_128 = "aes-128-gcm"
}
+4 -4
View File
@@ -8,9 +8,9 @@ import { AddExternalKmsType, KmsType } from "./types";
export const useAddExternalKms = (orgId: string) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ slug, description, provider }: AddExternalKmsType) => {
mutationFn: async ({ name, description, provider }: AddExternalKmsType) => {
const { data } = await apiRequest.post("/api/v1/external-kms", {
slug,
name,
description,
provider
});
@@ -28,14 +28,14 @@ export const useUpdateExternalKms = (orgId: string) => {
return useMutation({
mutationFn: async ({
kmsId,
slug,
name,
description,
provider
}: {
kmsId: string;
} & AddExternalKmsType) => {
const { data } = await apiRequest.patch(`/api/v1/external-kms/${kmsId}`, {
slug,
name,
description,
provider
});
+1 -1
View File
@@ -46,7 +46,7 @@ export const useGetActiveProjectKms = (projectId: string) => {
} = await apiRequest.get<{
secretManagerKmsKey: {
id: string;
slug: string;
name: string;
isExternal: string;
};
}>(`/api/v1/workspace/${projectId}/kms`);
+3 -3
View File
@@ -5,7 +5,7 @@ export type Kms = {
id: string;
description: string;
orgId: string;
slug: string;
name: string;
external: {
id: string;
status: string;
@@ -21,7 +21,7 @@ export type KmsListEntry = {
isDisabled: boolean;
createdAt: string;
updatedAt: string;
slug: string;
name: string;
externalKms: {
provider: string;
status: string;
@@ -88,7 +88,7 @@ export const ExternalKmsInputSchema = z.discriminatedUnion("type", [
]);
export const AddExternalKmsSchema = z.object({
slug: z
name: z
.string()
.trim()
.min(1)
+2 -2
View File
@@ -122,6 +122,6 @@ export type TOrgIdentitiesList = {
};
export enum OrgIdentityOrderBy {
Name = "name",
Role = "role"
Name = "name"
// Role = "role"
}
+1
View File
@@ -1,5 +1,6 @@
export { useDebounce } from "./useDebounce";
export { useLeaveConfirm } from "./useLeaveConfirm";
export { usePagination } from "./usePagination";
export { usePersistentState } from "./usePersistentState";
export { usePopUp } from "./usePopUp";
export { useSyntaxHighlight } from "./useSyntaxHighlight";
+31
View File
@@ -0,0 +1,31 @@
import { useState } from "react";
import { OrderByDirection } from "@app/hooks/api/generic/types";
import { useDebounce } from "@app/hooks/useDebounce";
export const usePagination = <T extends string>(initialOrderBy: T) => {
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(20);
const [orderDirection, setOrderDirection] = useState(OrderByDirection.ASC);
const [orderBy, setOrderBy] = useState<T>(initialOrderBy);
const [search, setSearch] = useState("");
const [debouncedSearch] = useDebounce(search);
const offset = (page - 1) * perPage;
return {
offset,
limit: perPage,
page,
setPage,
perPage,
setPerPage,
orderDirection,
setOrderDirection,
debouncedSearch,
search,
setSearch,
orderBy,
setOrderBy
};
};