diff --git a/backend/package-lock.json b/backend/package-lock.json index 9d0a3c8c9..e3c7bc6de 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -26,6 +26,7 @@ "@fastify/rate-limit": "^9.0.0", "@fastify/request-context": "^5.1.0", "@fastify/session": "^10.7.0", + "@fastify/static": "^7.0.4", "@fastify/swagger": "^8.14.0", "@fastify/swagger-ui": "^2.1.0", "@google-cloud/kms": "^4.5.0", @@ -5406,6 +5407,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-1.1.0.tgz", "integrity": "sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==", + "license": "MIT", "engines": { "node": ">=14" } @@ -5545,6 +5547,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@fastify/send/-/send-2.1.0.tgz", "integrity": "sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA==", + "license": "MIT", "dependencies": { "@lukeed/ms": "^2.0.1", "escape-html": "~1.0.3", @@ -5563,16 +5566,85 @@ } }, "node_modules/@fastify/static": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/@fastify/static/-/static-6.12.0.tgz", - "integrity": "sha512-KK1B84E6QD/FcQWxDI2aiUCwHxMJBI1KeCUzm1BwYpPY1b742+jeKruGHP2uOluuM6OkBPI8CIANrXcCRtC2oQ==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-7.0.4.tgz", + "integrity": "sha512-p2uKtaf8BMOZWLs6wu+Ihg7bWNBdjNgCwDza4MJtTqg+5ovKmcbgbR9Xs5/smZ1YISfzKOCNYmZV8LaCj+eJ1Q==", + "license": "MIT", "dependencies": { "@fastify/accept-negotiator": "^1.0.0", "@fastify/send": "^2.0.0", "content-disposition": "^0.5.3", "fastify-plugin": "^4.0.0", - "glob": "^8.0.1", - "p-limit": "^3.1.0" + "fastq": "^1.17.0", + "glob": "^10.3.4" + } + }, + "node_modules/@fastify/static/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@fastify/static/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@fastify/static/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/@fastify/static/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@fastify/static/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" } }, "node_modules/@fastify/swagger": { @@ -5599,6 +5671,20 @@ "yaml": "^2.2.2" } }, + "node_modules/@fastify/swagger-ui/node_modules/@fastify/static": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-6.12.0.tgz", + "integrity": "sha512-KK1B84E6QD/FcQWxDI2aiUCwHxMJBI1KeCUzm1BwYpPY1b742+jeKruGHP2uOluuM6OkBPI8CIANrXcCRtC2oQ==", + "license": "MIT", + "dependencies": { + "@fastify/accept-negotiator": "^1.0.0", + "@fastify/send": "^2.0.0", + "content-disposition": "^0.5.3", + "fastify-plugin": "^4.0.0", + "glob": "^8.0.1", + "p-limit": "^3.1.0" + } + }, "node_modules/@google-cloud/kms": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/@google-cloud/kms/-/kms-4.5.0.tgz", @@ -6062,9 +6148,10 @@ "integrity": "sha512-O89xFDLW2gBoZWNXuXpBSM32/KealKCTb3JGtJdtUQc7RjAk8XzrRgyz02cPAwGKwKPxy0ivuC7UP9bmN87egQ==" }, "node_modules/@lukeed/ms": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.1.tgz", - "integrity": "sha512-Xs/4RZltsAL7pkvaNStUQt7netTkyxrS0K+RILcVr3TRMS/ToOg4I6uNfhB9SlGsnWBym4U+EaXq0f0cEMNkHA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", + "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", + "license": "MIT", "engines": { "node": ">=8" } @@ -13879,9 +13966,9 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", @@ -13903,7 +13990,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -13918,6 +14005,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express-session": { @@ -17388,15 +17479,16 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -18383,9 +18475,9 @@ "license": "ISC" }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, "node_modules/path-type": { diff --git a/backend/package.json b/backend/package.json index a7321f67d..43409e619 100644 --- a/backend/package.json +++ b/backend/package.json @@ -134,6 +134,7 @@ "@fastify/rate-limit": "^9.0.0", "@fastify/request-context": "^5.1.0", "@fastify/session": "^10.7.0", + "@fastify/static": "^7.0.4", "@fastify/swagger": "^8.14.0", "@fastify/swagger-ui": "^2.1.0", "@google-cloud/kms": "^4.5.0", diff --git a/backend/src/server/app.ts b/backend/src/server/app.ts index 52fc989cf..6405306ab 100644 --- a/backend/src/server/app.ts +++ b/backend/src/server/app.ts @@ -27,10 +27,10 @@ import { globalRateLimiterCfg } from "./config/rateLimiter"; import { addErrorsToResponseSchemas } from "./plugins/add-errors-to-response-schemas"; import { apiMetrics } from "./plugins/api-metrics"; import { fastifyErrHandler } from "./plugins/error-handler"; -import { registerExternalNextjs } from "./plugins/external-nextjs"; import { serializerCompiler, validatorCompiler, ZodTypeProvider } from "./plugins/fastify-zod"; import { fastifyIp } from "./plugins/ip"; import { maintenanceMode } from "./plugins/maintenanceMode"; +import { registerServeUI } from "./plugins/serve-ui"; import { fastifySwagger } from "./plugins/swagger"; import { registerRoutes } from "./routes"; @@ -121,10 +121,9 @@ export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, key await server.register(registerRoutes, { smtp, queue, db, auditLogDb, keyStore, hsmModule }); if (appCfg.isProductionMode) { - await server.register(registerExternalNextjs, { + await server.register(registerServeUI, { standaloneMode: appCfg.STANDALONE_MODE || IS_PACKAGED, - dir: path.join(__dirname, IS_PACKAGED ? "../../../" : "../../"), - port: appCfg.PORT + dir: path.join(__dirname, IS_PACKAGED ? "../../../" : "../../") }); } diff --git a/backend/src/server/plugins/external-nextjs.ts b/backend/src/server/plugins/external-nextjs.ts deleted file mode 100644 index 754817035..000000000 --- a/backend/src/server/plugins/external-nextjs.ts +++ /dev/null @@ -1,76 +0,0 @@ -// this plugins allows to run infisical in standalone mode -// standalone mode = infisical backend and nextjs frontend in one server -// this way users don't need to deploy two things -import path from "node:path"; - -import { IS_PACKAGED } from "@app/lib/config/env"; - -// to enabled this u need to set standalone mode to true -export const registerExternalNextjs = async ( - server: FastifyZodProvider, - { - standaloneMode, - dir, - port - }: { - standaloneMode?: boolean; - dir: string; - port: number; - } -) => { - if (standaloneMode) { - const frontendName = IS_PACKAGED ? "frontend" : "frontend-build"; - const nextJsBuildPath = path.join(dir, frontendName); - - const { default: conf } = (await import( - path.join(dir, `${frontendName}/.next/required-server-files.json`), - // @ts-expect-error type - { - assert: { type: "json" } - } - )) as { default: { config: string } }; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let NextServer: any; - - if (!IS_PACKAGED) { - /* eslint-disable */ - const { default: nextServer } = ( - await import(path.join(dir, `${frontendName}/node_modules/next/dist/server/next-server.js`)) - ).default; - - NextServer = nextServer; - } else { - /* eslint-disable */ - const nextServer = await import(path.join(dir, `${frontendName}/node_modules/next/dist/server/next-server.js`)); - - NextServer = nextServer.default; - } - - const nextApp = new NextServer({ - dev: false, - dir: nextJsBuildPath, - port, - conf: conf.config, - hostname: "local", - customServer: false - }); - - server.route({ - method: ["GET", "PUT", "PATCH", "POST", "DELETE"], - url: "/*", - schema: { - hide: true - }, - handler: (req, res) => - nextApp - .getRequestHandler()(req.raw, res.raw) - .then(() => { - res.hijack(); - }) - }); - server.addHook("onClose", () => nextApp.close()); - await nextApp.prepare(); - /* eslint-enable */ - } -}; diff --git a/backend/src/server/plugins/serve-ui.ts b/backend/src/server/plugins/serve-ui.ts new file mode 100644 index 000000000..161634758 --- /dev/null +++ b/backend/src/server/plugins/serve-ui.ts @@ -0,0 +1,34 @@ +import path from "node:path"; + +import staticServe from "@fastify/static"; + +import { IS_PACKAGED } from "@app/lib/config/env"; + +// to enabled this u need to set standalone mode to true +export const registerServeUI = async ( + server: FastifyZodProvider, + { + standaloneMode, + dir + }: { + standaloneMode?: boolean; + dir: string; + } +) => { + if (standaloneMode) { + const frontendName = IS_PACKAGED ? "frontend" : "frontend-build"; + const frontendPath = path.join(dir, frontendName); + await server.register(staticServe, { + root: frontendPath, + wildcard: false + }); + + server.get("/*", (request, reply) => { + if (request.url.startsWith("/api")) { + reply.callNotFound(); + return; + } + void reply.sendFile("index.html"); + }); + } +};