Skip to content

Commit 4e92db6

Browse files
0xi4oLê Nam KhánhHenryHengZJtaraka-vishnumolakalataraka-vishnumolakala
authored
feat: handle 429 errors and redirect to rate-limited page (#5440)
* feat: handle 429 errors and redirect to rate-limited page * fix: simplify rate-limited page and better 429 error handling * fix: status code in quotaUsage * update: add back to home button rate-limited page * chore: fix typos in docker/worker/Dockerfile (#5435) Fix typos in docker/worker/Dockerfile * chore: fix typos in packages/components/nodes/agentflow/Condition/Condition.ts (#5436) Fix typos in packages/components/nodes/agentflow/Condition/Condition.ts * chore: fix typos in packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts (#5437) Fix typos in packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts * chore: fix typos in packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts (#5438) Fix typos in packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts * docs: fix typos in packages/ui/src/layout/MainLayout/Sidebar/MenuList/NavGroup/index.jsx (#5444) Fix typos in packages/ui/src/layout/MainLayout/Sidebar/MenuList/NavGroup/index.jsx * docs: fix typos in packages/components/nodes/engine/SubQuestionQueryEngine/SubQuestionQueryEngine.ts (#5446) Fix typos in packages/components/nodes/engine/SubQuestionQueryEngine/SubQuestionQueryEngine.ts * docs: fix typos in packages/components/nodes/embeddings/AWSBedrockEmbedding/AWSBedrockEmbedding.ts (#5447) Fix typos in packages/components/nodes/embeddings/AWSBedrockEmbedding/AWSBedrockEmbedding.ts * docs: fix typos in packages/server/README.md (#5445) Fix typos in packages/server/README.md * Bugfix/Supervisor Node AzureChatOpenAI (#5448) Integrate AzureChatOpenAI into the Supervisor node to handle user requests alongside ChatOpenAI. This enhancement allows for improved multi-agent conversation management. * Chore/JSON Array (#5467) * add separate by JSON object * add file check for Unstructured * Enhance JSON DocumentLoader: Update label and description for 'Separate by JSON Object' option, and add type check for JSON objects in array processing. * Chore/Remove Deprecated File Path Unstructured (#5478) * Refactor UnstructuredFile and UnstructuredFolder loaders to remove deprecated file path handling and enhance folder path validation. Ensure folder paths are sanitized and validated against path traversal attacks. * Update UnstructuredFolder.ts * feat(security): enhance file path validation and implement non-root D… (#5474) * feat(security): enhance file path validation and implement non-root Docker user - Validate resolved full file paths including workspace boundaries in SecureFileStore - Resolve paths before validation in readFile and writeFile operations - Run Docker container as non-root flowise user (uid/gid 1001) - Apply proper file ownership and permissions for application files Prevents path traversal attacks and follows container security best practices * Add sensitive system directory validation and Flowise internal file protection * Update Dockerfile to use default node user * update validation patterns to include additional system binary directories (/usr/bin, /usr/sbin, /usr/local/bin) * added isSafeBrowserExecutable function to validate browser executable paths for Playwright and Puppeteer loaders --------- Co-authored-by: taraka-vishnumolakala <[email protected]> Co-authored-by: Henry Heng <[email protected]> Co-authored-by: Henry <[email protected]> * Chore/docker file non root (#5479) * update dockerfile * Update Dockerfile * remove read write file tools and imports (#5480) * Bugfix/Custom Function Libraries (#5472) * Updated the executeJavaScriptCode function to automatically detect and install required libraries from import/require statements in the provided code. * Update utils.ts * lint-fix * Release/3.0.11 (#5481) [email protected] * [email protected] * Chore/Disable Unstructure Folder (#5483) * commented out unstructure folder node * Update packages/components/nodes/documentloaders/Unstructured/UnstructuredFolder.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * update: condition for handling 429 errors * update: handle rate limit errors in auth pages * fix: crash due to missing import --------- Co-authored-by: Lê Nam Khánh <[email protected]> Co-authored-by: Henry Heng <[email protected]> Co-authored-by: Taraka Vishnumolakala <[email protected]> Co-authored-by: taraka-vishnumolakala <[email protected]> Co-authored-by: Henry <[email protected]> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 7cc2c13 commit 4e92db6

File tree

8 files changed

+134
-8
lines changed

8 files changed

+134
-8
lines changed

packages/server/src/utils/quotaUsage.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export const checkUsageLimit = async (
7070
if (limit === -1) return
7171

7272
if (currentUsage > limit) {
73-
throw new InternalFlowiseError(StatusCodes.TOO_MANY_REQUESTS, `Limit exceeded: ${type}`)
73+
throw new InternalFlowiseError(StatusCodes.PAYMENT_REQUIRED, `Limit exceeded: ${type}`)
7474
}
7575
}
7676

@@ -135,7 +135,7 @@ export const checkPredictions = async (orgId: string, subscriptionId: string, us
135135
if (predictionsLimit === -1) return
136136

137137
if (currentPredictions >= predictionsLimit) {
138-
throw new InternalFlowiseError(StatusCodes.TOO_MANY_REQUESTS, 'Predictions limit exceeded')
138+
throw new InternalFlowiseError(StatusCodes.PAYMENT_REQUIRED, 'Predictions limit exceeded')
139139
}
140140

141141
return {
@@ -161,7 +161,7 @@ export const checkStorage = async (orgId: string, subscriptionId: string, usageC
161161
if (storageLimit === -1) return
162162

163163
if (currentStorageUsage >= storageLimit) {
164-
throw new InternalFlowiseError(StatusCodes.TOO_MANY_REQUESTS, 'Storage limit exceeded')
164+
throw new InternalFlowiseError(StatusCodes.PAYMENT_REQUIRED, 'Storage limit exceeded')
165165
}
166166

167167
return {

packages/ui/src/routes/AuthRoutes.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const VerifyEmailPage = Loadable(lazy(() => import('@/views/auth/verify-email'))
1010
const ForgotPasswordPage = Loadable(lazy(() => import('@/views/auth/forgotPassword')))
1111
const ResetPasswordPage = Loadable(lazy(() => import('@/views/auth/resetPassword')))
1212
const UnauthorizedPage = Loadable(lazy(() => import('@/views/auth/unauthorized')))
13+
const RateLimitedPage = Loadable(lazy(() => import('@/views/auth/rateLimited')))
1314
const OrganizationSetupPage = Loadable(lazy(() => import('@/views/organization/index')))
1415
const LicenseExpiredPage = Loadable(lazy(() => import('@/views/auth/expired')))
1516

@@ -45,6 +46,10 @@ const AuthRoutes = {
4546
path: '/unauthorized',
4647
element: <UnauthorizedPage />
4748
},
49+
{
50+
path: '/rate-limited',
51+
element: <RateLimitedPage />
52+
},
4853
{
4954
path: '/organization-setup',
5055
element: <OrganizationSetupPage />

packages/ui/src/store/context/ErrorContext.jsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,29 @@ const ErrorContext = createContext()
1010

1111
export const ErrorProvider = ({ children }) => {
1212
const [error, setError] = useState(null)
13+
const [authRateLimitError, setAuthRateLimitError] = useState(null)
1314
const navigate = useNavigate()
1415

1516
const handleError = async (err) => {
1617
console.error(err)
17-
if (err?.response?.status === 403) {
18+
if (err?.response?.status === 429 && err?.response?.data?.type === 'authentication_rate_limit') {
19+
setAuthRateLimitError("You're making a lot of requests. Please wait and try again later.")
20+
} else if (err?.response?.status === 429 && err?.response?.data?.type !== 'authentication_rate_limit') {
21+
const retryAfterHeader = err?.response?.headers?.['retry-after']
22+
let retryAfter = 60 // Default in seconds
23+
if (retryAfterHeader) {
24+
const parsedSeconds = parseInt(retryAfterHeader, 10)
25+
if (Number.isNaN(parsedSeconds)) {
26+
const retryDate = new Date(retryAfterHeader)
27+
if (!Number.isNaN(retryDate.getTime())) {
28+
retryAfter = Math.max(0, Math.ceil((retryDate.getTime() - Date.now()) / 1000))
29+
}
30+
} else {
31+
retryAfter = parsedSeconds
32+
}
33+
}
34+
navigate('/rate-limited', { state: { retryAfter } })
35+
} else if (err?.response?.status === 403) {
1836
navigate('/unauthorized')
1937
} else if (err?.response?.status === 401) {
2038
if (ErrorMessage.INVALID_MISSING_TOKEN === err?.response?.data?.message) {
@@ -44,7 +62,9 @@ export const ErrorProvider = ({ children }) => {
4462
value={{
4563
error,
4664
setError,
47-
handleError
65+
handleError,
66+
authRateLimitError,
67+
setAuthRateLimitError
4868
}}
4969
>
5070
{children}

packages/ui/src/views/auth/forgotPassword.jsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import accountApi from '@/api/account.api'
1616
// Hooks
1717
import useApi from '@/hooks/useApi'
1818
import { useConfig } from '@/store/context/ConfigContext'
19+
import { useError } from '@/store/context/ErrorContext'
1920

2021
// utils
2122
import useNotifier from '@/utils/useNotifier'
@@ -41,10 +42,13 @@ const ForgotPasswordPage = () => {
4142
const [isLoading, setLoading] = useState(false)
4243
const [responseMsg, setResponseMsg] = useState(undefined)
4344

45+
const { authRateLimitError, setAuthRateLimitError } = useError()
46+
4447
const forgotPasswordApi = useApi(accountApi.forgotPassword)
4548

4649
const sendResetRequest = async (event) => {
4750
event.preventDefault()
51+
setAuthRateLimitError(null)
4852
const body = {
4953
user: {
5054
email: usernameVal
@@ -54,6 +58,11 @@ const ForgotPasswordPage = () => {
5458
await forgotPasswordApi.request(body)
5559
}
5660

61+
useEffect(() => {
62+
setAuthRateLimitError(null)
63+
// eslint-disable-next-line react-hooks/exhaustive-deps
64+
}, [setAuthRateLimitError])
65+
5766
useEffect(() => {
5867
if (forgotPasswordApi.error) {
5968
const errMessage =
@@ -89,6 +98,11 @@ const ForgotPasswordPage = () => {
8998
{responseMsg.msg}
9099
</Alert>
91100
)}
101+
{authRateLimitError && (
102+
<Alert icon={<IconExclamationCircle />} variant='filled' severity='error'>
103+
{authRateLimitError}
104+
</Alert>
105+
)}
92106
{responseMsg && responseMsg?.type !== 'error' && (
93107
<Alert icon={<IconCircleCheck />} variant='filled' severity='success'>
94108
{responseMsg.msg}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { Box, Button, Stack, Typography } from '@mui/material'
2+
import { Link, useLocation } from 'react-router-dom'
3+
import unauthorizedSVG from '@/assets/images/unauthorized.svg'
4+
import MainCard from '@/ui-component/cards/MainCard'
5+
6+
// ==============================|| RateLimitedPage ||============================== //
7+
8+
const RateLimitedPage = () => {
9+
const location = useLocation()
10+
11+
const retryAfter = location.state?.retryAfter || 60
12+
13+
return (
14+
<MainCard>
15+
<Box
16+
sx={{
17+
display: 'flex',
18+
justifyContent: 'center',
19+
alignItems: 'center',
20+
height: 'calc(100vh - 210px)'
21+
}}
22+
>
23+
<Stack
24+
sx={{
25+
alignItems: 'center',
26+
justifyContent: 'center',
27+
maxWidth: '500px'
28+
}}
29+
flexDirection='column'
30+
>
31+
<Box sx={{ p: 2, height: 'auto' }}>
32+
<img style={{ objectFit: 'cover', height: '20vh', width: 'auto' }} src={unauthorizedSVG} alt='rateLimitedSVG' />
33+
</Box>
34+
<Typography sx={{ mb: 2 }} variant='h4' component='div' fontWeight='bold'>
35+
429 Too Many Requests
36+
</Typography>
37+
<Typography variant='body1' component='div' sx={{ mb: 2, textAlign: 'center' }}>
38+
{`You have made too many requests in a short period of time. Please wait ${retryAfter}s before trying again.`}
39+
</Typography>
40+
<Link to='/'>
41+
<Button variant='contained' color='primary'>
42+
Back to Home
43+
</Button>
44+
</Link>
45+
</Stack>
46+
</Box>
47+
</MainCard>
48+
)
49+
}
50+
51+
export default RateLimitedPage

packages/ui/src/views/auth/register.jsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import ssoApi from '@/api/sso'
1818
// Hooks
1919
import useApi from '@/hooks/useApi'
2020
import { useConfig } from '@/store/context/ConfigContext'
21+
import { useError } from '@/store/context/ErrorContext'
2122

2223
// utils
2324
import useNotifier from '@/utils/useNotifier'
@@ -111,7 +112,9 @@ const RegisterPage = () => {
111112

112113
const [loading, setLoading] = useState(false)
113114
const [authError, setAuthError] = useState('')
114-
const [successMsg, setSuccessMsg] = useState(undefined)
115+
const [successMsg, setSuccessMsg] = useState('')
116+
117+
const { authRateLimitError, setAuthRateLimitError } = useError()
115118

116119
const registerApi = useApi(accountApi.registerAccount)
117120
const ssoLoginApi = useApi(ssoApi.ssoLogin)
@@ -120,6 +123,7 @@ const RegisterPage = () => {
120123

121124
const register = async (event) => {
122125
event.preventDefault()
126+
setAuthRateLimitError(null)
123127
if (isEnterpriseLicensed) {
124128
const result = RegisterEnterpriseUserSchema.safeParse({
125129
username,
@@ -192,6 +196,7 @@ const RegisterPage = () => {
192196
}, [registerApi.error])
193197

194198
useEffect(() => {
199+
setAuthRateLimitError(null)
195200
if (!isOpenSource) {
196201
getDefaultProvidersApi.request()
197202
}
@@ -274,6 +279,11 @@ const RegisterPage = () => {
274279
)}
275280
</Alert>
276281
)}
282+
{authRateLimitError && (
283+
<Alert icon={<IconExclamationCircle />} variant='filled' severity='error'>
284+
{authRateLimitError}
285+
</Alert>
286+
)}
277287
{successMsg && (
278288
<Alert icon={<IconCircleCheck />} variant='filled' severity='success'>
279289
{successMsg}

packages/ui/src/views/auth/resetPassword.jsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from 'react'
1+
import { useEffect, useState } from 'react'
22
import { useDispatch } from 'react-redux'
33
import { Link, useNavigate, useSearchParams } from 'react-router-dom'
44

@@ -19,6 +19,9 @@ import accountApi from '@/api/account.api'
1919
import useNotifier from '@/utils/useNotifier'
2020
import { validatePassword } from '@/utils/validation'
2121

22+
// Hooks
23+
import { useError } from '@/store/context/ErrorContext'
24+
2225
// Icons
2326
import { IconExclamationCircle, IconX } from '@tabler/icons-react'
2427

@@ -70,6 +73,8 @@ const ResetPasswordPage = () => {
7073
const [loading, setLoading] = useState(false)
7174
const [authErrors, setAuthErrors] = useState([])
7275

76+
const { authRateLimitError, setAuthRateLimitError } = useError()
77+
7378
const goLogin = () => {
7479
navigate('/signin', { replace: true })
7580
}
@@ -78,6 +83,7 @@ const ResetPasswordPage = () => {
7883
event.preventDefault()
7984
const validationErrors = []
8085
setAuthErrors([])
86+
setAuthRateLimitError(null)
8187
if (!tokenVal) {
8288
validationErrors.push('Token cannot be left blank!')
8389
}
@@ -142,6 +148,11 @@ const ResetPasswordPage = () => {
142148
}
143149
}
144150

151+
useEffect(() => {
152+
setAuthRateLimitError(null)
153+
// eslint-disable-next-line react-hooks/exhaustive-deps
154+
}, [])
155+
145156
return (
146157
<>
147158
<MainCard>
@@ -155,6 +166,11 @@ const ResetPasswordPage = () => {
155166
</ul>
156167
</Alert>
157168
)}
169+
{authRateLimitError && (
170+
<Alert icon={<IconExclamationCircle />} variant='filled' severity='error'>
171+
{authRateLimitError}
172+
</Alert>
173+
)}
158174
<Stack sx={{ gap: 1 }}>
159175
<Typography variant='h1'>Reset Password</Typography>
160176
<Typography variant='body2' sx={{ color: theme.palette.grey[600] }}>

packages/ui/src/views/auth/signIn.jsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { Input } from '@/ui-component/input/Input'
1414
// Hooks
1515
import useApi from '@/hooks/useApi'
1616
import { useConfig } from '@/store/context/ConfigContext'
17+
import { useError } from '@/store/context/ErrorContext'
1718

1819
// API
1920
import authApi from '@/api/auth'
@@ -62,6 +63,8 @@ const SignInPage = () => {
6263
const [showResendButton, setShowResendButton] = useState(false)
6364
const [successMessage, setSuccessMessage] = useState('')
6465

66+
const { authRateLimitError, setAuthRateLimitError } = useError()
67+
6568
const loginApi = useApi(authApi.login)
6669
const ssoLoginApi = useApi(ssoApi.ssoLogin)
6770
const getDefaultProvidersApi = useApi(loginMethodApi.getDefaultLoginMethods)
@@ -71,6 +74,7 @@ const SignInPage = () => {
7174

7275
const doLogin = (event) => {
7376
event.preventDefault()
77+
setAuthRateLimitError(null)
7478
setLoading(true)
7579
const body = {
7680
email: usernameVal,
@@ -92,11 +96,12 @@ const SignInPage = () => {
9296

9397
useEffect(() => {
9498
store.dispatch(logoutSuccess())
99+
setAuthRateLimitError(null)
95100
if (!isOpenSource) {
96101
getDefaultProvidersApi.request()
97102
}
98103
// eslint-disable-next-line react-hooks/exhaustive-deps
99-
}, [])
104+
}, [setAuthRateLimitError, isOpenSource])
100105

101106
useEffect(() => {
102107
// Parse the "user" query parameter from the URL
@@ -179,6 +184,11 @@ const SignInPage = () => {
179184
{successMessage}
180185
</Alert>
181186
)}
187+
{authRateLimitError && (
188+
<Alert icon={<IconExclamationCircle />} variant='filled' severity='error'>
189+
{authRateLimitError}
190+
</Alert>
191+
)}
182192
{authError && (
183193
<Alert icon={<IconExclamationCircle />} variant='filled' severity='error'>
184194
{authError}

0 commit comments

Comments
 (0)