Conversation
Enable Flowise to run behind an ingress with a path prefix (e.g., /flowise) so it can be accessed via URLs like https://example.com/flowise/. - Add VITE_BASE_PATH env var for UI build (vite base option + React Router basename) - Add FLOWISE_BASE_PATH env var for server (API routes, static files, admin dashboard) - Update baseURL in constant.js to include base path for API calls Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Summary of ChangesHello @tianwei-liu, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces comprehensive support for deploying the application under a custom base path, which is crucial for environments where the application is served from a sub-directory or behind a reverse proxy. It involves updating both the backend API endpoints and the frontend UI routing and asset loading mechanisms to correctly resolve paths relative to the specified base path. Additionally, a minor update to the file system utility functions was made. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces support for a custom base path, allowing the application to be hosted under a sub-directory. The changes span across the server and UI packages, updating route definitions, API URLs, and static file serving to respect the FLOWISE_BASE_PATH and VITE_BASE_PATH environment variables.
My review focuses on the implementation details. I've identified a couple of areas for improvement in packages/server/src/index.ts:
- A potential bug where an unescaped
basePathis used in a regular expression, which could lead to incorrect URL matching if the path contains special characters. - A maintainability issue with the
basePathvariable being defined in multiple places.
Overall, the changes are good and address the feature requirement. Addressing the feedback will make the implementation more robust and maintainable.
| const URL_CASE_INSENSITIVE_REGEX: RegExp = new RegExp(`${basePath}/api/v1/`, 'i') | ||
| const URL_CASE_SENSITIVE_REGEX: RegExp = new RegExp(`${basePath}/api/v1/`) |
There was a problem hiding this comment.
When constructing a regular expression from a variable, it's a good practice to escape any special regex characters within that variable to prevent unexpected behavior. The basePath could potentially contain characters that have special meaning in regex, like a dot (.), which could lead to incorrect URL matching. I suggest escaping basePath before using it in the RegExp constructor.
| const URL_CASE_INSENSITIVE_REGEX: RegExp = new RegExp(`${basePath}/api/v1/`, 'i') | |
| const URL_CASE_SENSITIVE_REGEX: RegExp = new RegExp(`${basePath}/api/v1/`) | |
| const URL_CASE_INSENSITIVE_REGEX: RegExp = new RegExp(`${basePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/api/v1/`, 'i') | |
| const URL_CASE_SENSITIVE_REGEX: RegExp = new RegExp(`${basePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/api/v1/`) |
| if (next) next() | ||
| }) | ||
|
|
||
| const basePath = process.env.FLOWISE_BASE_PATH || '' |
There was a problem hiding this comment.
- Move basePath to class property for single source of truth - Escape special regex characters in basePath to prevent incorrect URL matching - Use this.basePath consistently throughout the class Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
|
||
| const rateLimiter = this.rateLimiterManager.getRateLimiterById(id) | ||
| this.app.use('/admin/queues', rateLimiter, verifyTokenForBullMQDashboard, this.queueManager.getBullBoardRouter()) | ||
| this.app.use(`${this.basePath}/admin/queues`, rateLimiter, verifyTokenForBullMQDashboard, this.queueManager.getBullBoardRouter()) |
Check failure
Code scanning / CodeQL
Missing rate limiting High
Copilot Autofix
AI 2 months ago
Copilot could not generate an autofix suggestion
Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.
| this.app.get(`${this.basePath}/*`, (req: Request, res: Response) => { | ||
| res.sendFile(uiHtmlPath) | ||
| }) |
Check failure
Code scanning / CodeQL
Missing rate limiting High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
In general, the problem is fixed by adding rate limiting middleware in front of the route handler that performs filesystem access (res.sendFile(uiHtmlPath)), so that any client can only trigger this expensive operation at a controlled rate. Since this project already has a RateLimiterManager utility and uses it for the BullMQ admin dashboard, the best fix is to reuse that infrastructure: create a specific rate limiter for SPA UI routes and apply it to the fallback GET handlers that call sendFile.
Concretely, inside packages/server/src/index.ts within the config (or equivalent) method where the SPA UI routes are set up, we will:
- Initialize a new rate limiter via
this.rateLimiterManager.addRateLimiter, giving it an ID such as'ui_spa_route'. We will choose reasonable defaults (e.g., 60-second window, 100 requests), and allow a custom message via an environment variable while not changing other behavior. - Retrieve the corresponding middleware via
this.rateLimiterManager.getRateLimiterById. - Apply this middleware to:
- The basePath SPA fallback route:
this.app.get(${this.basePath}/*, ...). - The root SPA fallback
this.app.get('*', ...)whenthis.basePathis not set.
We will not alter the static file middleware (express.static(uiBuildPath)) or the redirect from'/'tothis.basePath; only thesendFile(uiHtmlPath)routes need the limiter. All changes remain withinpackages/server/src/index.ts, relying only on the existingRateLimiterManagerimport.
- The basePath SPA fallback route:
| @@ -350,6 +350,16 @@ | ||
|
|
||
| this.app.use(this.basePath || '/', express.static(uiBuildPath)) | ||
|
|
||
| // Initialize UI SPA rate limiter for routes serving index.html | ||
| const uiRateLimiterId = 'ui_spa_route' | ||
| await this.rateLimiterManager.addRateLimiter( | ||
| uiRateLimiterId, | ||
| 60, | ||
| 100, | ||
| process.env.UI_RATE_LIMIT_MESSAGE || 'Too many requests to UI, please try again later.' | ||
| ) | ||
| const uiRateLimiter = this.rateLimiterManager.getRateLimiterById(uiRateLimiterId) | ||
|
|
||
| // Redirect root to basePath if basePath is set | ||
| if (this.basePath) { | ||
| this.app.get('/', (req: Request, res: Response) => { | ||
| @@ -357,13 +367,17 @@ | ||
| }) | ||
|
|
||
| // SPA fallback for routes under basePath only | ||
| this.app.get(`${this.basePath}/*`, (req: Request, res: Response) => { | ||
| res.sendFile(uiHtmlPath) | ||
| }) | ||
| this.app.get( | ||
| `${this.basePath}/*`, | ||
| uiRateLimiter, | ||
| (req: Request, res: Response) => { | ||
| res.sendFile(uiHtmlPath) | ||
| } | ||
| ) | ||
| } else { | ||
| // SPA fallback for all routes when no basePath | ||
| // Only serve index.html for routes that don't look like file requests | ||
| this.app.get('*', (req: Request, res: Response, next) => { | ||
| this.app.get('*', uiRateLimiter, (req: Request, res: Response, next) => { | ||
| // Skip if it looks like a file request (has extension) | ||
| const ext = path.extname(req.path) | ||
| if (ext) { |
|
Addressed review feedback in commit 5b16a82:
The rate limiting warnings from the security bot are pre-existing issues not introduced by this PR - the SPA fallback routes existed before, and the BullMQ dashboard route already has rate limiting middleware applied. |
- Use absolute paths in index.html for manifest.json, favicon.ico, logo192.png so Vite can transform them with base path - Remove manual config.basename prepending in NavItem since BrowserRouter already handles it (was causing doubled paths) - Add basePath to server logout redirects in account.controller.ts - Fix SSO login redirects to include basePath in signIn, register, and organization pages - Fix ErrorContext path comparison to handle base path - Use navigate() instead of window.location.href in account page Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
| this.app.get('*', (req: Request, res: Response, next) => { | ||
| // Skip if it looks like a file request (has extension) | ||
| const ext = path.extname(req.path) | ||
| if (ext) { | ||
| // It's a file request, let it 404 naturally | ||
| return next() | ||
| } | ||
| // Otherwise serve the SPA | ||
| res.sendFile(uiHtmlPath) | ||
| }) |
Check failure
Code scanning / CodeQL
Missing rate limiting High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
In general, the fix is to apply a rate-limiting middleware to the route(s) that trigger filesystem access so that a single client cannot make unlimited requests in a short time. This can be done with either an existing project-specific rate limiter (RateLimiterManager) or a standard package such as express-rate-limit. Since RateLimiterManager is already imported from ./utils/rateLimit, the best fix with minimal functional change is to instantiate an appropriate limiter (e.g., per-IP, reasonable max requests per window) and apply it only to the SPA fallback and optionally the static UI route, without altering the route logic itself.
Concretely for packages/server/src/index.ts inside async config() where the UI static serving is configured, we will: (1) create a rate limiter instance using RateLimiterManager (e.g., const uiRateLimiter = RateLimiterManager.getLimiter('ui-static') or similar; since we don't see its API, we’ll define a plausible, minimal interface consistent with a typical Express middleware factory, such as RateLimiterManager.getRateLimiter(options) that returns an Express middleware function); (2) apply this rate limiter to express.static(uiBuildPath) via this.app.use(...) so that all static UI asset requests are limited; and (3) also apply the same limiter to the SPA fallback route(s) (this.app.get('*', ...) and ${this.basePath}/*) using this.app.get(path, uiRateLimiter, handler) so file-serving is rate-limited. This keeps behavior identical under normal load while protecting against abusive request rates.
Because we must not change imports, and RateLimiterManager is already imported, the only needed changes are within the shown config() section: define a limiter middleware variable before the UI routes and insert it into the relevant app.use/app.get calls. No new methods or external dependencies are required.
| @@ -348,8 +348,15 @@ | ||
| const uiBuildPath = path.join(packagePath, 'build') | ||
| const uiHtmlPath = path.join(packagePath, 'build', 'index.html') | ||
|
|
||
| this.app.use(this.basePath || '/', express.static(uiBuildPath)) | ||
| // Apply rate limiting to UI static assets and SPA fallback to prevent abuse | ||
| const uiRateLimiter = RateLimiterManager.getRateLimiter({ | ||
| keyPrefix: 'ui-static', | ||
| windowMs: 15 * 60 * 1000, | ||
| max: 1000 | ||
| }) | ||
|
|
||
| this.app.use(this.basePath || '/', uiRateLimiter, express.static(uiBuildPath)) | ||
|
|
||
| // Redirect root to basePath if basePath is set | ||
| if (this.basePath) { | ||
| this.app.get('/', (req: Request, res: Response) => { | ||
| @@ -357,13 +363,13 @@ | ||
| }) | ||
|
|
||
| // SPA fallback for routes under basePath only | ||
| this.app.get(`${this.basePath}/*`, (req: Request, res: Response) => { | ||
| this.app.get(`${this.basePath}/*`, uiRateLimiter, (req: Request, res: Response) => { | ||
| res.sendFile(uiHtmlPath) | ||
| }) | ||
| } else { | ||
| // SPA fallback for all routes when no basePath | ||
| // Only serve index.html for routes that don't look like file requests | ||
| this.app.get('*', (req: Request, res: Response, next) => { | ||
| this.app.get('*', uiRateLimiter, (req: Request, res: Response, next) => { | ||
| // Skip if it looks like a file request (has extension) | ||
| const ext = path.extname(req.path) | ||
| if (ext) { |
Summary
Add support for deploying Flowise behind a reverse proxy or ingress with a custom base path (e.g.,
/flowise). This allows serving Flowise from a sub-directory URL likehttps://example.com/flowise/.Fixes: #2911
Problem
When deploying Flowise behind a reverse proxy with a path prefix:
Solution
Server-side (
FLOWISE_BASE_PATH){basePath}/api/v1/{basePath}//to base path when configuredUI-side (
VITE_BASE_PATH)baseoption for correct asset URL prefixesbasenamefor client-side navigationbaseURLto include base pathindex.html(manifest.json, favicon, etc.)Configuration
Files Changed
Server
packages/server/src/index.ts- Base path routing, static serving, SPA fallbackpackages/server/src/enterprise/middleware/passport/index.ts- Auth redirectspackages/server/src/enterprise/controllers/account.controller.ts- Logout redirectpackages/server/.env.example- AddFLOWISE_BASE_PATHexampleUI
packages/ui/vite.config.js- Vitebaseoptionpackages/ui/src/config.js- React Routerbasenamepackages/ui/src/store/constant.js- APIbaseURLpackages/ui/src/index.jsx- Pass basename to BrowserRouterpackages/ui/index.html- Fix relative asset pathspackages/ui/src/layout/MainLayout/Sidebar/MenuList/NavItem/index.jsx- Fix double basenamepackages/ui/src/store/context/ErrorContext.jsx- Fix path comparisonpackages/ui/src/views/auth/signIn.jsx- Fix SSO redirectpackages/ui/src/views/auth/register.jsx- Fix SSO redirectpackages/ui/src/views/organization/index.jsx- Fix SSO redirectpackages/ui/src/views/account/index.jsx- Fix login redirectpackages/ui/.env.example- AddVITE_BASE_PATHexampleTesting
VITE_BASE_PATH=/flowise pnpm buildFLOWISE_BASE_PATH=/flowise pnpm starthttp://localhost:3000/flowise//flowise/chatflows)Review Feedback Addressed