Merge remote-tracking branch 'origin/main' into misc/move-audit-logs-to-dedicated

This commit is contained in:
Sheen Capadngan
2024-10-04 22:16:46 +08:00
135 changed files with 4314 additions and 581 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"
}
+16 -2
View File
@@ -25,7 +25,8 @@ export enum DynamicSecretProviders {
ElasticSearch = "elastic-search",
MongoDB = "mongo-db",
RabbitMq = "rabbit-mq",
AzureEntraId = "azure-entra-id"
AzureEntraId = "azure-entra-id",
Ldap = "ldap"
}
export enum SqlProviders {
@@ -188,7 +189,20 @@ export type TDynamicSecretProvider =
applicationId: string;
clientSecret: string;
};
};
}
| {
type: DynamicSecretProviders.Ldap;
inputs: {
url: string;
binddn: string;
bindpass: string;
ca?: string | undefined;
creationLdif: string;
revocationLdif: string;
rollbackLdif?: string;
};
};
;
export type TCreateDynamicSecretDTO = {
projectSlug: string;
+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"
}
@@ -3,13 +3,21 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { secretSharingKeys } from "./queries";
import { TCreateSharedSecretRequest, TDeleteSharedSecretRequest, TSharedSecret } from "./types";
import {
TCreatedSharedSecret,
TCreateSharedSecretRequest,
TDeleteSharedSecretRequest,
TSharedSecret
} from "./types";
export const useCreateSharedSecret = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (inputData: TCreateSharedSecretRequest) => {
const { data } = await apiRequest.post<TSharedSecret>("/api/v1/secret-sharing", inputData);
const { data } = await apiRequest.post<TCreatedSharedSecret>(
"/api/v1/secret-sharing",
inputData
);
return data;
},
onSuccess: () => queryClient.invalidateQueries(secretSharingKeys.allSharedSecrets())
@@ -20,7 +28,7 @@ export const useCreatePublicSharedSecret = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (inputData: TCreateSharedSecretRequest) => {
const { data } = await apiRequest.post<TSharedSecret>(
const { data } = await apiRequest.post<TCreatedSharedSecret>(
"/api/v1/secret-sharing/public",
inputData
);
@@ -8,7 +8,7 @@ export const secretSharingKeys = {
allSharedSecrets: () => ["sharedSecrets"] as const,
specificSharedSecrets: ({ offset, limit }: { offset: number; limit: number }) =>
[...secretSharingKeys.allSharedSecrets(), { offset, limit }] as const,
getSecretById: (arg: { id: string; hashedHex: string; password?: string }) => [
getSecretById: (arg: { id: string; hashedHex: string | null; password?: string }) => [
"shared-secret",
arg
]
@@ -46,7 +46,7 @@ export const useGetActiveSharedSecretById = ({
password
}: {
sharedSecretId: string;
hashedHex: string;
hashedHex: string | null;
password?: string;
}) => {
return useQuery<TViewSharedSecretResponse>(
@@ -55,7 +55,7 @@ export const useGetActiveSharedSecretById = ({
const { data } = await apiRequest.post<TViewSharedSecretResponse>(
`/api/v1/secret-sharing/public/${sharedSecretId}`,
{
hashedHex,
...(hashedHex && { hashedHex }),
password
}
);
@@ -63,7 +63,7 @@ export const useGetActiveSharedSecretById = ({
return data;
},
{
enabled: Boolean(sharedSecretId) && Boolean(hashedHex)
enabled: Boolean(sharedSecretId)
}
);
};
@@ -13,13 +13,14 @@ export type TSharedSecret = {
tag: string;
};
export type TCreatedSharedSecret = {
id: string;
};
export type TCreateSharedSecretRequest = {
name?: string;
password?: string;
encryptedValue: string;
hashedHex: string;
iv: string;
tag: string;
secretValue: string;
expiresAt: Date;
expiresAfterViews?: number;
accessType?: SecretSharingAccessType;
@@ -28,6 +29,7 @@ export type TCreateSharedSecretRequest = {
export type TViewSharedSecretResponse = {
isPasswordProtected: boolean;
secret: {
secretValue?: string;
encryptedValue: string;
iv: string;
tag: string;
@@ -44,4 +46,3 @@ export enum SecretSharingAccessType {
Anyone = "anyone",
Organization = "organization"
}
+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";
+6 -3
View File
@@ -1,7 +1,10 @@
import { useEffect, useState } from "react";
import { Dispatch, SetStateAction, useEffect, useState } from "react";
// Ref: https://usehooks.com/useDebounce/
export const useDebounce = <T extends unknown>(value: T, delay = 500): T => {
export const useDebounce = <T extends unknown>(
value: T,
delay = 500
): [T, Dispatch<SetStateAction<T>>] => {
// State and setters for debounced value
const [debouncedValue, setDebouncedValue] = useState(value);
@@ -22,5 +25,5 @@ export const useDebounce = <T extends unknown>(value: T, delay = 500): T => {
[value, delay] // Only re-call effect if value or delay changes
);
return debouncedValue;
return [debouncedValue, setDebouncedValue];
};
+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
};
};