diff --git a/.env.example b/.env.example index 6670a0a3a..67110d69a 100644 --- a/.env.example +++ b/.env.example @@ -67,5 +67,3 @@ CLIENT_SECRET_GITLAB_LOGIN= CAPTCHA_SECRET= NEXT_PUBLIC_CAPTCHA_SITE_KEY= - -ALLOW_RATELIMIT_UPDATES= diff --git a/backend/e2e-test/vitest-environment-knex.ts b/backend/e2e-test/vitest-environment-knex.ts index c287172f6..09ab05443 100644 --- a/backend/e2e-test/vitest-environment-knex.ts +++ b/backend/e2e-test/vitest-environment-knex.ts @@ -43,7 +43,7 @@ export default { const smtp = mockSmtpServer(); const queue = mockQueue(); const keyStore = mockKeyStore(); - const { server } = await main({ db, smtp, logger, queue, keyStore }); + const server = await main({ db, smtp, logger, queue, keyStore }); // @ts-expect-error type globalThis.testServer = server; // @ts-expect-error type diff --git a/backend/src/db/migrations/20240611151327_custom-rate-limits-for-self-hosting.ts b/backend/src/db/migrations/20240611151327_custom-rate-limits-for-self-hosting.ts index cb8007d9f..84ad9b560 100644 --- a/backend/src/db/migrations/20240611151327_custom-rate-limits-for-self-hosting.ts +++ b/backend/src/db/migrations/20240611151327_custom-rate-limits-for-self-hosting.ts @@ -6,7 +6,7 @@ import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils"; export async function up(knex: Knex): Promise { if (!(await knex.schema.hasTable(TableName.RateLimit))) { await knex.schema.createTable(TableName.RateLimit, (t) => { - t.uuid("id", { primaryKey: true }).defaultTo("00000000-0000-0000-0000-000000000000"); + t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); t.integer("readRateLimit").defaultTo(600).notNullable(); t.integer("writeRateLimit").defaultTo(200).notNullable(); t.integer("secretsRateLimit").defaultTo(60).notNullable(); diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 7c02ebdbf..80a2111fc 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -123,8 +123,7 @@ const envSchema = z .optional(), INFISICAL_CLOUD: zodStrBool.default("false"), MAINTENANCE_MODE: zodStrBool.default("false"), - CAPTCHA_SECRET: zpStr(z.string().optional()), - ALLOW_RATELIMIT_UPDATES: zodStrBool.default("false") + CAPTCHA_SECRET: zpStr(z.string().optional()) }) .transform((data) => ({ ...data, diff --git a/backend/src/main.ts b/backend/src/main.ts index 5de0cccc4..86681ef33 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -22,13 +22,12 @@ const run = async () => { const queue = queueServiceFactory(appCfg.REDIS_URL); const keyStore = keyStoreFactory(appCfg.REDIS_URL); - const { server, jobs } = await main({ db, smtp, logger, queue, keyStore }); + const server = await main({ db, smtp, logger, queue, keyStore }); const bootstrap = await bootstrapCheck({ db }); // eslint-disable-next-line process.on("SIGINT", async () => { await server.close(); await db.destroy(); - jobs.forEach((job) => job.stop()); process.exit(0); }); @@ -36,7 +35,6 @@ const run = async () => { process.on("SIGTERM", async () => { await server.close(); await db.destroy(); - jobs.forEach((job) => job.stop()); process.exit(0); }); diff --git a/backend/src/server/app.ts b/backend/src/server/app.ts index 5d94de94c..da1bca3e2 100644 --- a/backend/src/server/app.ts +++ b/backend/src/server/app.ts @@ -10,7 +10,6 @@ import fastifyFormBody from "@fastify/formbody"; import helmet from "@fastify/helmet"; import type { FastifyRateLimitOptions } from "@fastify/rate-limit"; import ratelimiter from "@fastify/rate-limit"; -import { CronJob } from "cron"; import fasitfy from "fastify"; import { Knex } from "knex"; import { Logger } from "pino"; @@ -42,7 +41,6 @@ type TMain = { // Run the server! export const main = async ({ db, smtp, logger, queue, keyStore }: TMain) => { const appCfg = getConfig(); - const cronJobs: CronJob[] = []; const server = fasitfy({ logger: appCfg.NODE_ENV === "test" ? false : logger, trustProxy: true, @@ -76,11 +74,6 @@ export const main = async ({ db, smtp, logger, queue, keyStore }: TMain) => { const rateLimitDAL = rateLimitDALFactory(db); const rateLimitService = rateLimitServiceFactory({ rateLimitDAL }); const rateLimits = await rateLimitService.getRateLimits(); - - if (rateLimits) { - cronJobs.push(rateLimitService.initializeBackgroundSync()); - } - await server.register(ratelimiter, globalRateLimiterCfg(rateLimits)); } @@ -100,7 +93,7 @@ export const main = async ({ db, smtp, logger, queue, keyStore }: TMain) => { await server.ready(); server.swagger(); - return { server, jobs: cronJobs }; + return server; } catch (err) { server.log.error(err); await queue.shutdown(); diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 0b79085cc..2f2de829f 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -1,3 +1,4 @@ +import { CronJob } from "cron"; import { Knex } from "knex"; import { z } from "zod"; @@ -907,6 +908,11 @@ export const registerRoutes = async ( secretSharing: secretSharingService }); + const cronJobs: CronJob[] = []; + if (appCfg.isProductionMode) { + cronJobs.push(rateLimitService.initializeBackgroundSync()); + } + server.decorate("store", { user: userDAL }); @@ -961,6 +967,7 @@ export const registerRoutes = async ( await server.register(registerV3Routes, { prefix: "/api/v3" }); server.addHook("onClose", async () => { + cronJobs.forEach((job) => job.stop()); await telemetryService.flushAll(); }); }; diff --git a/backend/src/services/rate-limit/rate-limit-service.ts b/backend/src/services/rate-limit/rate-limit-service.ts index a697f1afd..e458a9310 100644 --- a/backend/src/services/rate-limit/rate-limit-service.ts +++ b/backend/src/services/rate-limit/rate-limit-service.ts @@ -1,7 +1,5 @@ import { CronJob } from "cron"; -import { getConfig } from "@app/lib/config/env"; -import { ForbiddenRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { rateLimitMaxConfiguration } from "@app/server/config/rateLimiter"; @@ -15,23 +13,28 @@ type TRateLimitServiceFactoryDep = { export type TRateLimitServiceFactory = ReturnType; export const rateLimitServiceFactory = ({ rateLimitDAL }: TRateLimitServiceFactoryDep) => { + const DEFAULT_RATE_LIMIT_CONFIG_ID = "00000000-0000-0000-0000-000000000000"; + const getRateLimits = async (): Promise => { + let rateLimit: TRateLimit; + try { - return await rateLimitDAL.findOne({ id: "00000000-0000-0000-0000-000000000000" }); - } catch (error) { + rateLimit = await rateLimitDAL.findOne({ id: DEFAULT_RATE_LIMIT_CONFIG_ID }); + if (!rateLimit) { + // rate limit might not exist + rateLimit = await rateLimitDAL.create({ + // @ts-expect-error id is kept as fixed because there should only be one rate limit config per instance + id: DEFAULT_RATE_LIMIT_CONFIG_ID + }); + } + return rateLimit; + } catch (err) { return undefined; } }; const updateRateLimit = async (updates: TRateLimitUpdateDTO): Promise => { - const appCfg = getConfig(); - if (!appCfg.ALLOW_RATELIMIT_UPDATES) { - throw new ForbiddenRequestError({ - name: "Rate limit Updates Disabled", - message: "Changes to rate limits are disabled" - }); - } - return rateLimitDAL.updateById("00000000-0000-0000-0000-000000000000", updates); + return rateLimitDAL.updateById(DEFAULT_RATE_LIMIT_CONFIG_ID, updates); }; const initializeBackgroundSync = () => {