Merge pull request #1965 from Infisical/feat/allow-custom-rate-limits

feat: allow custom rate limits
This commit is contained in:
Maidul Islam
2024-06-13 21:08:24 -04:00
committed by GitHub
23 changed files with 633 additions and 10 deletions
+1
View File
@@ -17,6 +17,7 @@ export * from "./keys";
export * from "./ldapConfig";
export * from "./organization";
export * from "./projectUserAdditionalPrivilege";
export * from "./rateLimit";
export * from "./roles";
export * from "./scim";
export * from "./secretApproval";
@@ -0,0 +1,2 @@
export { useUpdateRateLimit } from "./mutation";
export { useGetRateLimit } from "./queries";
@@ -0,0 +1,21 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { rateLimitQueryKeys } from "./queries";
import { TRateLimit } from "./types";
export const useUpdateRateLimit = () => {
const queryClient = useQueryClient();
return useMutation<TRateLimit, {}, TRateLimit>({
mutationFn: async (opt) => {
const { data } = await apiRequest.put<{ rateLimit: TRateLimit }>("/api/v1/rate-limit", opt);
return data.rateLimit;
},
onSuccess: (data) => {
queryClient.setQueryData(rateLimitQueryKeys.rateLimit(), data);
queryClient.invalidateQueries(rateLimitQueryKeys.rateLimit());
}
});
};
@@ -0,0 +1,34 @@
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { TRateLimit } from "./types";
export const rateLimitQueryKeys = {
rateLimit: () => ["rate-limit"] as const
};
const fetchRateLimit = async () => {
const { data } = await apiRequest.get<{ rateLimit: TRateLimit }>("/api/v1/rate-limit");
return data.rateLimit;
};
export const useGetRateLimit = ({
options = {}
}: {
options?: Omit<
UseQueryOptions<
TRateLimit,
unknown,
TRateLimit,
ReturnType<typeof rateLimitQueryKeys.rateLimit>
>,
"queryKey" | "queryFn"
>;
} = {}) =>
useQuery({
queryKey: rateLimitQueryKeys.rateLimit(),
queryFn: fetchRateLimit,
...options,
enabled: options?.enabled ?? true
});
+10
View File
@@ -0,0 +1,10 @@
export type TRateLimit = {
readRateLimit: number;
writeRateLimit: number;
secretsRateLimit: number;
authRateLimit: number;
inviteUserRateLimit: number;
mfaRateLimit: number;
creationLimit: number;
publicEndpointLimit: number;
};
@@ -18,12 +18,16 @@ import {
Tab,
TabList,
TabPanel,
Tabs} from "@app/components/v2";
Tabs
} from "@app/components/v2";
import { useOrganization, useServerConfig, useUser } from "@app/context";
import { useUpdateServerConfig } from "@app/hooks/api";
import { RateLimitPanel } from "./RateLimitPanel";
enum TabSections {
Settings = "settings"
Settings = "settings",
RateLimit = "rate-limit"
}
enum SignUpModes {
@@ -117,6 +121,7 @@ export const AdminDashboardPage = () => {
<TabList>
<div className="flex w-full flex-row border-b border-mineshaft-600">
<Tab value={TabSections.Settings}>General</Tab>
<Tab value={TabSections.RateLimit}>Rate Limit</Tab>
</div>
</TabList>
<TabPanel value={TabSections.Settings}>
@@ -233,6 +238,9 @@ export const AdminDashboardPage = () => {
</Button>
</form>
</TabPanel>
<TabPanel value={TabSections.RateLimit}>
<RateLimitPanel />
</TabPanel>
</Tabs>
</div>
)}
@@ -0,0 +1,250 @@
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import { Button, ContentLoader, FormControl, Input } from "@app/components/v2";
import { useGetRateLimit, useUpdateRateLimit } from "@app/hooks/api";
const formSchema = z.object({
readRateLimit: z.number(),
writeRateLimit: z.number(),
secretsRateLimit: z.number(),
authRateLimit: z.number(),
inviteUserRateLimit: z.number(),
mfaRateLimit: z.number(),
creationLimit: z.number(),
publicEndpointLimit: z.number()
});
type TRateLimitForm = z.infer<typeof formSchema>;
export const RateLimitPanel = () => {
const { data: rateLimit, isLoading } = useGetRateLimit();
const { mutateAsync: updateRateLimit } = useUpdateRateLimit();
const {
control,
handleSubmit,
formState: { isSubmitting, isDirty }
} = useForm<TRateLimitForm>({
resolver: zodResolver(formSchema),
values: {
// eslint-disable-next-line
readRateLimit: rateLimit?.readRateLimit ?? 600,
writeRateLimit: rateLimit?.writeRateLimit ?? 200,
secretsRateLimit: rateLimit?.secretsRateLimit ?? 60,
authRateLimit: rateLimit?.authRateLimit ?? 60,
inviteUserRateLimit: rateLimit?.inviteUserRateLimit ?? 30,
mfaRateLimit: rateLimit?.mfaRateLimit ?? 20,
creationLimit: rateLimit?.creationLimit ?? 30,
publicEndpointLimit: rateLimit?.publicEndpointLimit ?? 30
}
});
const onRateLimitFormSubmit = async (formData: TRateLimitForm) => {
try {
const {
readRateLimit,
writeRateLimit,
secretsRateLimit,
authRateLimit,
inviteUserRateLimit,
mfaRateLimit,
creationLimit,
publicEndpointLimit
} = formData;
await updateRateLimit({
readRateLimit,
writeRateLimit,
secretsRateLimit,
authRateLimit,
inviteUserRateLimit,
mfaRateLimit,
creationLimit,
publicEndpointLimit
});
createNotification({
text: "Rate limits have been successfully updated. Please allow at least 10 minutes for the changes to take effect.",
type: "success"
});
} catch (e) {
console.error(e);
createNotification({
type: "error",
text: "Failed to update rate limiting setting."
});
}
};
return isLoading ? (
<ContentLoader />
) : (
<form
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
onSubmit={handleSubmit(onRateLimitFormSubmit)}
>
<div className="mb-8 flex flex-col justify-start">
<div className="mb-4 text-xl font-semibold text-mineshaft-100">
Configure rate limits
</div>
<Controller
control={control}
name="readRateLimit"
defaultValue={300}
render={({ field, fieldState: { error } }) => (
<FormControl
label="Global read requests per minute"
className="w-72"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
value={field.value}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
)}
/>
<Controller
control={control}
defaultValue={300}
name="writeRateLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Global write requests per minute"
className="w-72"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || ""}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
)}
/>
<Controller
control={control}
defaultValue={300}
name="secretsRateLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Secret requests per minute"
className="w-72"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || ""}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
)}
/>
<Controller
control={control}
defaultValue={300}
name="authRateLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Auth requests per minute"
className="w-72"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || ""}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
)}
/>
<Controller
control={control}
defaultValue={300}
name="inviteUserRateLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="User invitation requests per minute"
className="w-72"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || ""}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
)}
/>
<Controller
control={control}
defaultValue={300}
name="mfaRateLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Multi factor auth requests per minute"
className="w-72"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || ""}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
)}
/>
<Controller
control={control}
defaultValue={300}
name="creationLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="New resource creation requests per minute"
className="w-72"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || ""}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
)}
/>
<Controller
control={control}
defaultValue={300}
name="publicEndpointLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Secret sharing requests per minute"
className="w-72"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || ""}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
)}
/>
</div>
<Button type="submit" isLoading={isSubmitting} isDisabled={isSubmitting || !isDirty}>
Save
</Button>
</form>
);
};