mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 00:27:35 +00:00
misc: addressed review comments
This commit is contained in:
@@ -6,7 +6,7 @@ import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
|||||||
export async function up(knex: Knex): Promise<void> {
|
export async function up(knex: Knex): Promise<void> {
|
||||||
if (!(await knex.schema.hasTable(TableName.RateLimit))) {
|
if (!(await knex.schema.hasTable(TableName.RateLimit))) {
|
||||||
await knex.schema.createTable(TableName.RateLimit, (t) => {
|
await knex.schema.createTable(TableName.RateLimit, (t) => {
|
||||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
t.uuid("id", { primaryKey: true }).defaultTo("00000000-0000-0000-0000-000000000000");
|
||||||
t.integer("readRateLimit").defaultTo(600).notNullable();
|
t.integer("readRateLimit").defaultTo(600).notNullable();
|
||||||
t.integer("writeRateLimit").defaultTo(200).notNullable();
|
t.integer("writeRateLimit").defaultTo(200).notNullable();
|
||||||
t.integer("secretsRateLimit").defaultTo(60).notNullable();
|
t.integer("secretsRateLimit").defaultTo(60).notNullable();
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import { Logger } from "pino";
|
|||||||
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { TQueueServiceFactory } from "@app/queue";
|
import { TQueueServiceFactory } from "@app/queue";
|
||||||
|
import { rateLimitDALFactory } from "@app/services/rate-limit/rate-limit-dal";
|
||||||
|
import { rateLimitServiceFactory } from "@app/services/rate-limit/rate-limit-service";
|
||||||
import { TSmtpService } from "@app/services/smtp/smtp-service";
|
import { TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
|
|
||||||
import { globalRateLimiterCfg } from "./config/rateLimiter";
|
import { globalRateLimiterCfg } from "./config/rateLimiter";
|
||||||
@@ -69,8 +71,11 @@ export const main = async ({ db, smtp, logger, queue, keyStore }: TMain) => {
|
|||||||
|
|
||||||
// Rate limiters and security headers
|
// Rate limiters and security headers
|
||||||
if (appCfg.isProductionMode) {
|
if (appCfg.isProductionMode) {
|
||||||
await server.register<FastifyRateLimitOptions>(ratelimiter, await globalRateLimiterCfg(db));
|
const rateLimitDAL = rateLimitDALFactory(db);
|
||||||
|
const rateLimits = await rateLimitServiceFactory({ rateLimitDAL }).getRateLimits();
|
||||||
|
await server.register<FastifyRateLimitOptions>(ratelimiter, globalRateLimiterCfg(rateLimits));
|
||||||
}
|
}
|
||||||
|
|
||||||
await server.register(helmet, { contentSecurityPolicy: false });
|
await server.register(helmet, { contentSecurityPolicy: false });
|
||||||
|
|
||||||
await server.register(maintenanceMode);
|
await server.register(maintenanceMode);
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import type { RateLimitOptions, RateLimitPluginOptions } from "@fastify/rate-limit";
|
import type { RateLimitOptions, RateLimitPluginOptions } from "@fastify/rate-limit";
|
||||||
import { Redis } from "ioredis";
|
import { Redis } from "ioredis";
|
||||||
import { Knex } from "knex";
|
|
||||||
|
|
||||||
|
import { TRateLimit } from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { rateLimitDALFactory } from "@app/services/rate-limit/rate-limit-dal";
|
|
||||||
import { rateLimitServiceFactory } from "@app/services/rate-limit/rate-limit-service";
|
|
||||||
|
|
||||||
// GET endpoints
|
// GET endpoints
|
||||||
export const readLimit: RateLimitOptions = {
|
export const readLimit: RateLimitOptions = {
|
||||||
@@ -63,31 +61,20 @@ export const publicEndpointLimit: RateLimitOptions = {
|
|||||||
keyGenerator: (req) => req.realIp
|
keyGenerator: (req) => req.realIp
|
||||||
};
|
};
|
||||||
|
|
||||||
async function fetchRateLimitsFromDb(db: Knex) {
|
export const globalRateLimiterCfg = async (rateLimits: TRateLimit): Promise<RateLimitPluginOptions> => {
|
||||||
try {
|
|
||||||
const rateLimitDAL = rateLimitDALFactory(db);
|
|
||||||
const rateLimits = await rateLimitServiceFactory({ rateLimitDAL }).getRateLimits();
|
|
||||||
|
|
||||||
readLimit.max = rateLimits.readRateLimit;
|
|
||||||
publicEndpointLimit.max = rateLimits.publicEndpointLimit;
|
|
||||||
writeLimit.max = rateLimits.writeRateLimit;
|
|
||||||
secretsLimit.max = rateLimits.secretsRateLimit;
|
|
||||||
authRateLimit.max = rateLimits.authRateLimit;
|
|
||||||
inviteUserRateLimit.max = rateLimits.inviteUserRateLimit;
|
|
||||||
mfaRateLimit.max = rateLimits.mfaRateLimit;
|
|
||||||
creationLimit.max = rateLimits.creationLimit;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching rate limits:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const globalRateLimiterCfg = async (db: Knex): Promise<RateLimitPluginOptions> => {
|
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const redis = appCfg.isRedisConfigured
|
const redis = appCfg.isRedisConfigured
|
||||||
? new Redis(appCfg.REDIS_URL, { connectTimeout: 500, maxRetriesPerRequest: 1 })
|
? new Redis(appCfg.REDIS_URL, { connectTimeout: 500, maxRetriesPerRequest: 1 })
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
await fetchRateLimitsFromDb(db);
|
readLimit.max = rateLimits.readRateLimit;
|
||||||
|
publicEndpointLimit.max = rateLimits.publicEndpointLimit;
|
||||||
|
writeLimit.max = rateLimits.writeRateLimit;
|
||||||
|
secretsLimit.max = rateLimits.secretsRateLimit;
|
||||||
|
authRateLimit.max = rateLimits.authRateLimit;
|
||||||
|
inviteUserRateLimit.max = rateLimits.inviteUserRateLimit;
|
||||||
|
mfaRateLimit.max = rateLimits.mfaRateLimit;
|
||||||
|
creationLimit.max = rateLimits.creationLimit;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
timeWindow: 60 * 1000,
|
timeWindow: 60 * 1000,
|
||||||
|
|||||||
@@ -39,10 +39,15 @@ export const registerRateLimitRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
schema: {
|
schema: {
|
||||||
body: RateLimitSchema.omit({
|
body: z.object({
|
||||||
id: true,
|
readRateLimit: z.number().optional(),
|
||||||
createdAt: true,
|
writeRateLimit: z.number().optional(),
|
||||||
updatedAt: true
|
secretsRateLimit: z.number().optional(),
|
||||||
|
authRateLimit: z.number().optional(),
|
||||||
|
inviteUserRateLimit: z.number().optional(),
|
||||||
|
mfaRateLimit: z.number().optional(),
|
||||||
|
creationLimit: z.number().optional(),
|
||||||
|
publicEndpointLimit: z.number().optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
|||||||
@@ -10,34 +10,23 @@ type TRateLimitServiceFactoryDep = {
|
|||||||
export type TRateLimitServiceFactory = ReturnType<typeof rateLimitServiceFactory>;
|
export type TRateLimitServiceFactory = ReturnType<typeof rateLimitServiceFactory>;
|
||||||
|
|
||||||
export const rateLimitServiceFactory = ({ rateLimitDAL }: TRateLimitServiceFactoryDep) => {
|
export const rateLimitServiceFactory = ({ rateLimitDAL }: TRateLimitServiceFactoryDep) => {
|
||||||
const initRateLimits = async (): Promise<TRateLimit> => {
|
|
||||||
const rateLimit = await rateLimitDAL.create({});
|
|
||||||
return rateLimit;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getRateLimits = async (): Promise<TRateLimit> => {
|
const getRateLimits = async (): Promise<TRateLimit> => {
|
||||||
let rateLimit = (await rateLimitDAL.find({}))[0];
|
return rateLimitDAL.findOne({ id: "00000000-0000-0000-0000-000000000000" });
|
||||||
if (!rateLimit) {
|
|
||||||
rateLimit = await initRateLimits();
|
|
||||||
}
|
|
||||||
return rateLimit;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateRateLimit = async (updates: TRateLimitUpdateDTO): Promise<TRateLimit> => {
|
const updateRateLimit = async (updates: TRateLimitUpdateDTO): Promise<TRateLimit> => {
|
||||||
const rateLimit = await rateLimitDAL.findOne({});
|
const rateLimit = await rateLimitDAL.findOne({
|
||||||
if (!rateLimit) throw new BadRequestError({ name: "Rate Limit Update", message: "Rate Limit does not exist yet" });
|
id: "00000000-0000-0000-0000-000000000000"
|
||||||
|
});
|
||||||
|
|
||||||
const updateData: Record<string, number> = {};
|
if (!rateLimit) {
|
||||||
for (const [key, value] of Object.entries(updates)) {
|
throw new BadRequestError({ name: "Rate Limit Update", message: "Rate Limit does not exist yet" });
|
||||||
updateData[key] = value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedRateLimit = await rateLimitDAL.updateById(rateLimit.id, updateData);
|
return rateLimitDAL.updateById(rateLimit.id, updates);
|
||||||
return updatedRateLimit;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
initRateLimits,
|
|
||||||
getRateLimits,
|
getRateLimits,
|
||||||
updateRateLimit
|
updateRateLimit
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export * from "./keys";
|
|||||||
export * from "./ldapConfig";
|
export * from "./ldapConfig";
|
||||||
export * from "./organization";
|
export * from "./organization";
|
||||||
export * from "./projectUserAdditionalPrivilege";
|
export * from "./projectUserAdditionalPrivilege";
|
||||||
|
export * from "./rateLimit";
|
||||||
export * from "./roles";
|
export * from "./roles";
|
||||||
export * from "./scim";
|
export * from "./scim";
|
||||||
export * from "./secretApproval";
|
export * from "./secretApproval";
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ import {
|
|||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useOrganization, useServerConfig, useUser } from "@app/context";
|
import { useOrganization, useServerConfig, useUser } from "@app/context";
|
||||||
import { useUpdateServerConfig } from "@app/hooks/api";
|
import { useUpdateServerConfig } from "@app/hooks/api";
|
||||||
import { useGetRateLimit, useUpdateRateLimit } from "@app/hooks/api/rateLimit";
|
|
||||||
|
import { RateLimitPanel } from "./RateLimitPanel";
|
||||||
|
|
||||||
enum TabSections {
|
enum TabSections {
|
||||||
Settings = "settings",
|
Settings = "settings",
|
||||||
@@ -38,22 +39,13 @@ const formSchema = z.object({
|
|||||||
signUpMode: z.nativeEnum(SignUpModes),
|
signUpMode: z.nativeEnum(SignUpModes),
|
||||||
allowedSignUpDomain: z.string().optional().nullable(),
|
allowedSignUpDomain: z.string().optional().nullable(),
|
||||||
trustSamlEmails: z.boolean(),
|
trustSamlEmails: z.boolean(),
|
||||||
trustLdapEmails: z.boolean(),
|
trustLdapEmails: z.boolean()
|
||||||
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 TDashboardForm = z.infer<typeof formSchema>;
|
type TDashboardForm = z.infer<typeof formSchema>;
|
||||||
export const AdminDashboardPage = () => {
|
export const AdminDashboardPage = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const data = useServerConfig();
|
const data = useServerConfig();
|
||||||
const { data: rateLimit } = useGetRateLimit();
|
|
||||||
const { config } = data;
|
const { config } = data;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -68,15 +60,7 @@ export const AdminDashboardPage = () => {
|
|||||||
signUpMode: config.allowSignUp ? SignUpModes.Anyone : SignUpModes.Disabled,
|
signUpMode: config.allowSignUp ? SignUpModes.Anyone : SignUpModes.Disabled,
|
||||||
allowedSignUpDomain: config.allowedSignUpDomain,
|
allowedSignUpDomain: config.allowedSignUpDomain,
|
||||||
trustSamlEmails: config.trustSamlEmails,
|
trustSamlEmails: config.trustSamlEmails,
|
||||||
trustLdapEmails: config.trustLdapEmails,
|
trustLdapEmails: config.trustLdapEmails
|
||||||
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
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -85,7 +69,6 @@ export const AdminDashboardPage = () => {
|
|||||||
const { user, isLoading: isUserLoading } = useUser();
|
const { user, isLoading: isUserLoading } = useUser();
|
||||||
const { orgs } = useOrganization();
|
const { orgs } = useOrganization();
|
||||||
const { mutateAsync: updateServerConfig } = useUpdateServerConfig();
|
const { mutateAsync: updateServerConfig } = useUpdateServerConfig();
|
||||||
const { mutateAsync: updateRateLimit } = useUpdateRateLimit();
|
|
||||||
|
|
||||||
const isNotAllowed = !user?.superAdmin;
|
const isNotAllowed = !user?.superAdmin;
|
||||||
|
|
||||||
@@ -122,42 +105,6 @@ export const AdminDashboardPage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRateLimitFormSubmit = async (formData: TDashboardForm) => {
|
|
||||||
try {
|
|
||||||
const {
|
|
||||||
readRateLimit,
|
|
||||||
writeRateLimit,
|
|
||||||
secretsRateLimit,
|
|
||||||
authRateLimit,
|
|
||||||
inviteUserRateLimit,
|
|
||||||
mfaRateLimit,
|
|
||||||
creationLimit,
|
|
||||||
publicEndpointLimit
|
|
||||||
} = formData;
|
|
||||||
|
|
||||||
await updateRateLimit({
|
|
||||||
readRateLimit,
|
|
||||||
writeRateLimit,
|
|
||||||
secretsRateLimit,
|
|
||||||
authRateLimit,
|
|
||||||
inviteUserRateLimit,
|
|
||||||
mfaRateLimit,
|
|
||||||
creationLimit,
|
|
||||||
publicEndpointLimit
|
|
||||||
});
|
|
||||||
createNotification({
|
|
||||||
text: "Successfully changed rate limits. Please restart your server",
|
|
||||||
type: "success"
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
createNotification({
|
|
||||||
type: "error",
|
|
||||||
text: "Failed to update rate limiting setting."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto max-w-7xl px-4 pb-12 text-white dark:[color-scheme:dark]">
|
<div className="container mx-auto max-w-7xl px-4 pb-12 text-white dark:[color-scheme:dark]">
|
||||||
<div className="mx-auto mb-6 w-full max-w-7xl pt-6">
|
<div className="mx-auto mb-6 w-full max-w-7xl pt-6">
|
||||||
@@ -292,175 +239,7 @@ export const AdminDashboardPage = () => {
|
|||||||
</form>
|
</form>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel value={TabSections.RateLimit}>
|
<TabPanel value={TabSections.RateLimit}>
|
||||||
<form
|
<RateLimitPanel />
|
||||||
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">
|
|
||||||
Set Rate Limits for your Infisical Instance
|
|
||||||
</div>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="readRateLimit"
|
|
||||||
defaultValue={300}
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="Read Requests allowed 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="Write Requests allowed 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="Secrets allowed 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 allowed 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="Invite User Requests allowed 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 allowed 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="Creation Requests allowed 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="Public Endpoints Requests allowed 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>
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,248 @@
|
|||||||
|
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, 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 } = 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: "Successfully changed rate limits. Please restart your server",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to update rate limiting setting."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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">
|
||||||
|
Set Rate Limits for your Infisical Instance
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="readRateLimit"
|
||||||
|
defaultValue={300}
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Read Requests allowed 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="Write Requests allowed 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 allowed 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 allowed 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="Invite User Requests allowed 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 allowed 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="Creation Requests allowed 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="Public Endpoints Requests allowed 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user