From 7d8b399102fbfe05262578805b9c742f4f78e5ab Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Mon, 26 Feb 2024 15:04:28 +0530 Subject: [PATCH] feat(server): added keystore and made server cfg fetch from keystore to avoid db calls --- backend/e2e-test/mocks/keystore.ts | 19 ++++++++++++ backend/e2e-test/vitest-environment-knex.ts | 4 ++- backend/src/cache/redis.ts | 6 ---- backend/src/keystore/keystore.ts | 13 ++++++++ backend/src/main.ts | 4 ++- backend/src/server/app.ts | 6 ++-- backend/src/server/routes/index.ts | 11 +++++-- backend/src/server/routes/v1/admin-router.ts | 2 +- .../super-admin/super-admin-service.ts | 30 +++++++++++++++---- 9 files changed, 77 insertions(+), 18 deletions(-) create mode 100644 backend/e2e-test/mocks/keystore.ts delete mode 100644 backend/src/cache/redis.ts create mode 100644 backend/src/keystore/keystore.ts diff --git a/backend/e2e-test/mocks/keystore.ts b/backend/e2e-test/mocks/keystore.ts new file mode 100644 index 000000000..f3ce008ed --- /dev/null +++ b/backend/e2e-test/mocks/keystore.ts @@ -0,0 +1,19 @@ +import { TKeyStoreFactory } from "@app/keystore/keystore"; + +export const mockKeyStore = (): TKeyStoreFactory => { + const store: Record = {}; + + return { + setItem: async (key, value) => { + store[key] = value; + return "OK"; + }, + getItem: async (key) => { + const value = store[key]; + if (typeof value === "string") { + return value; + } + return null; + } + }; +}; diff --git a/backend/e2e-test/vitest-environment-knex.ts b/backend/e2e-test/vitest-environment-knex.ts index afb1bbabe..c1c750225 100644 --- a/backend/e2e-test/vitest-environment-knex.ts +++ b/backend/e2e-test/vitest-environment-knex.ts @@ -14,6 +14,7 @@ import { AuthTokenType } from "@app/services/auth/auth-type"; import { mockQueue } from "./mocks/queue"; import { mockSmtpServer } from "./mocks/smtp"; +import { mockKeyStore } from "./mocks/keystore"; dotenv.config({ path: path.join(__dirname, "../../.env.test"), debug: true }); export default { @@ -41,7 +42,8 @@ export default { await db.seed.run(); const smtp = mockSmtpServer(); const queue = mockQueue(); - const server = await main({ db, smtp, logger, queue }); + const keyStore = mockKeyStore(); + 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/cache/redis.ts b/backend/src/cache/redis.ts deleted file mode 100644 index 4e856fac1..000000000 --- a/backend/src/cache/redis.ts +++ /dev/null @@ -1,6 +0,0 @@ -import Redis from "ioredis"; - -export const initRedisConnection = (redisUrl: string) => { - const redis = new Redis(redisUrl); - return redis; -}; diff --git a/backend/src/keystore/keystore.ts b/backend/src/keystore/keystore.ts new file mode 100644 index 000000000..99b781b24 --- /dev/null +++ b/backend/src/keystore/keystore.ts @@ -0,0 +1,13 @@ +import { Redis } from "ioredis"; + +export type TKeyStoreFactory = ReturnType; + +export const keyStoreFactory = (redisUrl: string) => { + const redis = new Redis(redisUrl); + + const setItem = async (key: string, value: string | number | Buffer) => redis.set(key, value); + + const getItem = async (key: string) => redis.get(key); + + return { setItem, getItem }; +}; diff --git a/backend/src/main.ts b/backend/src/main.ts index fab576d3b..86681ef33 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -1,6 +1,7 @@ import dotenv from "dotenv"; import { initDbConnection } from "./db"; +import { keyStoreFactory } from "./keystore/keystore"; import { formatSmtpConfig, initEnvConfig } from "./lib/config/env"; import { initLogger } from "./lib/logger"; import { queueServiceFactory } from "./queue"; @@ -19,8 +20,9 @@ const run = async () => { const smtp = smtpServiceFactory(formatSmtpConfig()); const queue = queueServiceFactory(appCfg.REDIS_URL); + const keyStore = keyStoreFactory(appCfg.REDIS_URL); - const server = await main({ db, smtp, logger, queue }); + const server = await main({ db, smtp, logger, queue, keyStore }); const bootstrap = await bootstrapCheck({ db }); // eslint-disable-next-line process.on("SIGINT", async () => { diff --git a/backend/src/server/app.ts b/backend/src/server/app.ts index 969d2fe43..556a88d7c 100644 --- a/backend/src/server/app.ts +++ b/backend/src/server/app.ts @@ -14,6 +14,7 @@ import fasitfy from "fastify"; import { Knex } from "knex"; import { Logger } from "pino"; +import { TKeyStoreFactory } from "@app/keystore/keystore"; import { getConfig } from "@app/lib/config/env"; import { TQueueServiceFactory } from "@app/queue"; import { TSmtpService } from "@app/services/smtp/smtp-service"; @@ -31,10 +32,11 @@ type TMain = { smtp: TSmtpService; logger?: Logger; queue: TQueueServiceFactory; + keyStore: TKeyStoreFactory; }; // Run the server! -export const main = async ({ db, smtp, logger, queue }: TMain) => { +export const main = async ({ db, smtp, logger, queue, keyStore }: TMain) => { const appCfg = getConfig(); const server = fasitfy({ logger: appCfg.NODE_ENV === "test" ? false : logger, @@ -70,7 +72,7 @@ export const main = async ({ db, smtp, logger, queue }: TMain) => { } await server.register(helmet, { contentSecurityPolicy: false }); - await server.register(registerRoutes, { smtp, queue, db }); + await server.register(registerRoutes, { smtp, queue, db, keyStore }); if (appCfg.isProductionMode) { await server.register(registerExternalNextjs, { diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 7366c7041..e9c35b1d7 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -34,6 +34,7 @@ import { snapshotFolderDALFactory } from "@app/ee/services/secret-snapshot/snaps import { snapshotSecretDALFactory } from "@app/ee/services/secret-snapshot/snapshot-secret-dal"; import { trustedIpDALFactory } from "@app/ee/services/trusted-ip/trusted-ip-dal"; import { trustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service"; +import { TKeyStoreFactory } from "@app/keystore/keystore"; import { getConfig } from "@app/lib/config/env"; import { TQueueServiceFactory } from "@app/queue"; import { apiKeyDALFactory } from "@app/services/api-key/api-key-dal"; @@ -112,7 +113,12 @@ import { registerV3Routes } from "./v3"; export const registerRoutes = async ( server: FastifyZodProvider, - { db, smtp: smtpService, queue: queueService }: { db: Knex; smtp: TSmtpService; queue: TQueueServiceFactory } + { + db, + smtp: smtpService, + queue: queueService, + keyStore + }: { db: Knex; smtp: TSmtpService; queue: TQueueServiceFactory; keyStore: TKeyStoreFactory } ) => { await server.register(registerSecretScannerGhApp, { prefix: "/ss-webhook" }); @@ -263,7 +269,8 @@ export const registerRoutes = async ( userDAL, authService: loginService, serverCfgDAL: superAdminDAL, - orgService + orgService, + keyStore }); const apiKeyService = apiKeyServiceFactory({ apiKeyDAL, userDAL }); diff --git a/backend/src/server/routes/v1/admin-router.ts b/backend/src/server/routes/v1/admin-router.ts index b89e3dc4c..6eec355a8 100644 --- a/backend/src/server/routes/v1/admin-router.ts +++ b/backend/src/server/routes/v1/admin-router.ts @@ -16,7 +16,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { schema: { response: { 200: z.object({ - config: SuperAdminSchema + config: SuperAdminSchema.omit({ createdAt: true, updatedAt: true }) }) } }, diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index 64dac73f2..98491ba82 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -1,4 +1,5 @@ import { TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas"; +import { TKeyStoreFactory } from "@app/keystore/keystore"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError } from "@app/lib/errors"; @@ -14,6 +15,7 @@ type TSuperAdminServiceFactoryDep = { userDAL: TUserDALFactory; authService: Pick; orgService: Pick; + keyStore: Pick; }; export type TSuperAdminServiceFactory = ReturnType; @@ -21,26 +23,44 @@ export type TSuperAdminServiceFactory = ReturnType Promise; +const ADMIN_CONFIG_KEY = "infisical-admin-cfg"; export const superAdminServiceFactory = ({ serverCfgDAL, userDAL, authService, - orgService + orgService, + keyStore }: TSuperAdminServiceFactoryDep) => { const initServerCfg = async () => { // TODO(akhilmhdh): bad pattern time less change this later to me itself - getServerCfg = () => serverCfgDAL.findOne({}); + getServerCfg = async () => { + const config = await keyStore.getItem(ADMIN_CONFIG_KEY); + if (!config) { + const serverCfg = await serverCfgDAL.findOne({}); + await keyStore.setItem(ADMIN_CONFIG_KEY, JSON.stringify(serverCfg)); + return serverCfg; + } + const keyStoreServerCfg = JSON.parse(config) as TSuperAdmin; + return { + ...keyStoreServerCfg, + // this is to allow admin router to work + createdAt: new Date(keyStoreServerCfg.createdAt), + updatedAt: new Date(keyStoreServerCfg.updatedAt) + }; + }; - const serverCfg = await serverCfgDAL.findOne({}); + const serverCfg = await getServerCfg(); if (serverCfg) return; + const newCfg = await serverCfgDAL.create({ initialized: false, allowSignUp: true }); return newCfg; }; const updateServerCfg = async (data: TSuperAdminUpdate) => { const serverCfg = await getServerCfg(); - const cfg = await serverCfgDAL.updateById(serverCfg.id, data); - return cfg; + const updatedServerCfg = await serverCfgDAL.updateById(serverCfg.id, data); + await keyStore.setItem(ADMIN_CONFIG_KEY, JSON.stringify(updatedServerCfg)); + return updatedServerCfg; }; const adminSignUp = async ({