mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 21:00:58 +03:00
Chore/WorkspaceID Check (#5228)
* feat: Require workspace ID for API key operations - Added validation to ensure `activeWorkspaceId` is present in user requests for all API key operations (get, create, update, import, delete). - Updated `getWorkspaceSearchOptions` and `getWorkspaceSearchOptionsFromReq` to throw an error if `workspaceId` is not provided. - Modified service methods to enforce `workspaceId` as a required parameter for database operations related to API keys. * feat: Enforce workspace ID as a required field across multiple interfaces and services - Updated various interfaces to make `workspaceId` a mandatory field instead of optional. - Enhanced assistant and export-import service methods to require `workspaceId` for operations, ensuring proper validation and error handling. - Modified database entity definitions to reflect the change in `workspaceId` from optional to required. - Improved error handling in controllers to check for `activeWorkspaceId` before proceeding with requests. * Require workspace ID in various controllers and services - Updated controllers for credentials, datasets, document stores, evaluations, evaluators, and variables to enforce the presence of `workspaceId`. - Enhanced error handling to throw appropriate errors when `workspaceId` is not provided. - Modified service methods to accept `workspaceId` as a mandatory parameter for operations, ensuring consistent validation across the application. * Update EvaluatorRunner and index to require workspaceId for evaluator retrieval - Modified the runAdditionalEvaluators function to accept workspaceId as a parameter. * lint fixes * Enhancement/Integrate workspaceId in chatflow and flow-config services - Updated chatflow and flow-config controllers to require workspaceId for fetching chatflows. - Modified service methods to accept workspaceId as a parameter, ensuring proper context for chatflow retrieval. * lint fix * get rid of redundant isApiKeyValidated * refactor: update permission checks for chatflows and agentflows routes - Enhanced permission checks in chatflows routes to include agentflows permissions for create, read, update, and delete operations. - Updated navigation paths in authentication views to redirect to the home page instead of chatflows after successful login or registration. * fix(DefaultRedirect.jsx): add redirect unauthenticated users to login * fix(RequireAuth.jsx): check permissions for routes without display property * fix(DefaultRedirect.jsx): WorkspaceSwitcher api spam * fix(routes/chatflows/index.ts): use checkAnyPermission for chatflow/has-changed/:id/:lastUpdatedDateTime * fix(routes/chatflows/index.ts): use checkAnyPermission for delete request chatflow/:id * fix(controllers/text-to-speech/index.ts): add workspace ID validation in generateTextToSpeech * fix(controllers/internal-predictions/index.ts): add chatflow retrieval and validation using workspaceId * feat(services\credentials\index.ts): add filter by workspaceId for getCredentialById * chore(routes/chat-messages/index.ts): unused chat-messages route * feat(services/chatflows/index.ts): add filter by workspaceId for deleteChatflow * feat(services/marketplaces/index.ts): add filter by workspaceId for deleteCustomTemplate * feat(tools): add filter by workspaceId for read, update, and delete --------- Co-authored-by: Vinod Paidimarry <vinodkiran@outlook.in> Co-authored-by: Yau <33013947+chungyau97@users.noreply.github.com> Co-authored-by: chungyau97 <chungyau97@gmail.com>
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
import { useAuth } from '@/hooks/useAuth'
|
||||
import { useConfig } from '@/store/context/ConfigContext'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
// Import all view components
|
||||
import Account from '@/views/account'
|
||||
import Executions from '@/views/agentexecutions'
|
||||
import Agentflows from '@/views/agentflows'
|
||||
import APIKey from '@/views/apikey'
|
||||
import Assistants from '@/views/assistants'
|
||||
import Login from '@/views/auth/login'
|
||||
import LoginActivityPage from '@/views/auth/loginActivity'
|
||||
import SSOConfig from '@/views/auth/ssoConfig'
|
||||
import Unauthorized from '@/views/auth/unauthorized'
|
||||
import Chatflows from '@/views/chatflows'
|
||||
import Credentials from '@/views/credentials'
|
||||
import EvalDatasets from '@/views/datasets'
|
||||
import Documents from '@/views/docstore'
|
||||
import EvalEvaluation from '@/views/evaluations/index'
|
||||
import Evaluators from '@/views/evaluators'
|
||||
import Marketplaces from '@/views/marketplaces'
|
||||
import RolesPage from '@/views/roles'
|
||||
import Logs from '@/views/serverlogs'
|
||||
import Tools from '@/views/tools'
|
||||
import UsersPage from '@/views/users'
|
||||
import Variables from '@/views/variables'
|
||||
import Workspaces from '@/views/workspace'
|
||||
|
||||
/**
|
||||
* Component that redirects users to the first accessible page based on their permissions
|
||||
* This prevents 403 errors when users don't have access to the default chatflows page
|
||||
*/
|
||||
export const DefaultRedirect = () => {
|
||||
const { hasPermission, hasDisplay } = useAuth()
|
||||
const { isOpenSource } = useConfig()
|
||||
const isGlobal = useSelector((state) => state.auth.isGlobal)
|
||||
const isAuthenticated = useSelector((state) => state.auth.isAuthenticated)
|
||||
|
||||
// Define the order of routes to check (based on the menu order in dashboard.js)
|
||||
const routesToCheck = [
|
||||
{ component: Chatflows, permission: 'chatflows:view' },
|
||||
{ component: Agentflows, permission: 'agentflows:view' },
|
||||
{ component: Executions, permission: 'executions:view' },
|
||||
{ component: Assistants, permission: 'assistants:view' },
|
||||
{ component: Marketplaces, permission: 'templates:marketplace,templates:custom' },
|
||||
{ component: Tools, permission: 'tools:view' },
|
||||
{ component: Credentials, permission: 'credentials:view' },
|
||||
{ component: Variables, permission: 'variables:view' },
|
||||
{ component: APIKey, permission: 'apikeys:view' },
|
||||
{ component: Documents, permission: 'documentStores:view' },
|
||||
// Evaluation routes (with display flags)
|
||||
{ component: EvalDatasets, permission: 'datasets:view', display: 'feat:datasets' },
|
||||
{ component: Evaluators, permission: 'evaluators:view', display: 'feat:evaluators' },
|
||||
{ component: EvalEvaluation, permission: 'evaluations:view', display: 'feat:evaluations' },
|
||||
// Management routes (with display flags)
|
||||
{ component: SSOConfig, permission: 'sso:manage', display: 'feat:sso-config' },
|
||||
{ component: RolesPage, permission: 'roles:manage', display: 'feat:roles' },
|
||||
{ component: UsersPage, permission: 'users:manage', display: 'feat:users' },
|
||||
{ component: Workspaces, permission: 'workspace:view', display: 'feat:workspaces' },
|
||||
{ component: LoginActivityPage, permission: 'loginActivity:view', display: 'feat:login-activity' },
|
||||
// Other routes
|
||||
{ component: Logs, permission: 'logs:view', display: 'feat:logs' },
|
||||
{ component: Account, display: 'feat:account' }
|
||||
]
|
||||
|
||||
// If user is not authenticated, show login page
|
||||
if (!isAuthenticated) {
|
||||
return <Login />
|
||||
}
|
||||
|
||||
// For open source, show chatflows (no permission checks)
|
||||
if (isOpenSource) {
|
||||
return <Chatflows />
|
||||
}
|
||||
|
||||
// For global admins, show chatflows (they have access to everything)
|
||||
if (isGlobal) {
|
||||
return <Chatflows />
|
||||
}
|
||||
|
||||
// Check each route in order and return the first accessible component
|
||||
for (const route of routesToCheck) {
|
||||
const { component: Component, permission, display } = route
|
||||
|
||||
// Check permission if specified
|
||||
const hasRequiredPermission = !permission || hasPermission(permission)
|
||||
|
||||
// Check display flag if specified
|
||||
const hasRequiredDisplay = !display || hasDisplay(display)
|
||||
|
||||
// If user has both required permission and display access, return this component
|
||||
if (hasRequiredPermission && hasRequiredDisplay) {
|
||||
return <Component />
|
||||
}
|
||||
}
|
||||
|
||||
// If no accessible routes found, show unauthorized page
|
||||
// This should rarely happen as most users should have at least one permission
|
||||
return <Unauthorized />
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import MainLayout from '@/layout/MainLayout'
|
||||
import Loadable from '@/ui-component/loading/Loadable'
|
||||
|
||||
import { RequireAuth } from '@/routes/RequireAuth'
|
||||
import { DefaultRedirect } from '@/routes/DefaultRedirect'
|
||||
|
||||
// chatflows routing
|
||||
const Chatflows = Loadable(lazy(() => import('@/views/chatflows')))
|
||||
@@ -77,11 +78,7 @@ const MainRoutes = {
|
||||
children: [
|
||||
{
|
||||
path: '/',
|
||||
element: (
|
||||
<RequireAuth permission={'chatflows:view'}>
|
||||
<Chatflows />
|
||||
</RequireAuth>
|
||||
)
|
||||
element: <DefaultRedirect />
|
||||
},
|
||||
{
|
||||
path: '/chatflows',
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Navigate } from 'react-router'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { useConfig } from '@/store/context/ConfigContext'
|
||||
import { useAuth } from '@/hooks/useAuth'
|
||||
import { useConfig } from '@/store/context/ConfigContext'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Navigate } from 'react-router'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
/**
|
||||
* Checks if a feature flag is enabled
|
||||
@@ -29,13 +29,18 @@ const checkFeatureFlag = (features, display, children) => {
|
||||
|
||||
export const RequireAuth = ({ permission, display, children }) => {
|
||||
const location = useLocation()
|
||||
const { isCloud, isOpenSource, isEnterpriseLicensed } = useConfig()
|
||||
const { isCloud, isOpenSource, isEnterpriseLicensed, loading } = useConfig()
|
||||
const { hasPermission } = useAuth()
|
||||
const isGlobal = useSelector((state) => state.auth.isGlobal)
|
||||
const currentUser = useSelector((state) => state.auth.user)
|
||||
const features = useSelector((state) => state.auth.features)
|
||||
const permissions = useSelector((state) => state.auth.permissions)
|
||||
|
||||
// Step 0: Wait for config to load
|
||||
if (loading) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Step 1: Authentication Check
|
||||
// Redirect to login if user is not authenticated
|
||||
if (!currentUser) {
|
||||
@@ -50,29 +55,36 @@ export const RequireAuth = ({ permission, display, children }) => {
|
||||
|
||||
// Cloud & Enterprise: Check both permissions and feature flags
|
||||
if (isCloud || isEnterpriseLicensed) {
|
||||
// Allow access to basic features (no display property)
|
||||
if (!display) return children
|
||||
// Routes with display property - check feature flags
|
||||
if (display) {
|
||||
// Check if user has any permissions
|
||||
if (permissions.length === 0) {
|
||||
return <Navigate to='/unauthorized' replace state={{ path: location.pathname }} />
|
||||
}
|
||||
|
||||
// Check if user has any permissions
|
||||
if (permissions.length === 0) {
|
||||
return <Navigate to='/unauthorized' replace state={{ path: location.pathname }} />
|
||||
// Organization admins bypass permission checks
|
||||
if (isGlobal) {
|
||||
return checkFeatureFlag(features, display, children)
|
||||
}
|
||||
|
||||
// Check user permissions and feature flags
|
||||
if (!permission || hasPermission(permission)) {
|
||||
return checkFeatureFlag(features, display, children)
|
||||
}
|
||||
|
||||
return <Navigate to='/unauthorized' replace />
|
||||
}
|
||||
|
||||
// Organization admins bypass permission checks
|
||||
if (isGlobal) {
|
||||
return checkFeatureFlag(features, display, children)
|
||||
// Standard routes: check permissions (global admins bypass)
|
||||
if (permission && !hasPermission(permission) && !isGlobal) {
|
||||
return <Navigate to='/unauthorized' replace />
|
||||
}
|
||||
|
||||
// Check user permissions and feature flags
|
||||
if (!permission || hasPermission(permission)) {
|
||||
return checkFeatureFlag(features, display, children)
|
||||
}
|
||||
|
||||
return <Navigate to='/unauthorized' replace />
|
||||
return children
|
||||
}
|
||||
|
||||
// Fallback: Allow access if none of the above conditions match
|
||||
return children
|
||||
// Fallback: If none of the platform types match, deny access
|
||||
return <Navigate to='/unauthorized' replace />
|
||||
}
|
||||
|
||||
RequireAuth.propTypes = {
|
||||
|
||||
@@ -201,7 +201,7 @@ const RegisterPage = () => {
|
||||
useEffect(() => {
|
||||
if (ssoLoginApi.data) {
|
||||
store.dispatch(loginSuccess(ssoLoginApi.data))
|
||||
navigate(location.state?.path || '/chatflows')
|
||||
navigate(location.state?.path || '/')
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
@@ -112,7 +112,7 @@ const SignInPage = () => {
|
||||
if (loginApi.data) {
|
||||
setLoading(false)
|
||||
store.dispatch(loginSuccess(loginApi.data))
|
||||
navigate(location.state?.path || '/chatflows')
|
||||
navigate(location.state?.path || '/')
|
||||
//navigate(0)
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ const SignInPage = () => {
|
||||
useEffect(() => {
|
||||
if (ssoLoginApi.data) {
|
||||
store.dispatch(loginSuccess(ssoLoginApi.data))
|
||||
navigate(location.state?.path || '/chatflows')
|
||||
navigate(location.state?.path || '/')
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
@@ -19,7 +19,7 @@ const SSOSuccess = () => {
|
||||
if (user) {
|
||||
if (user.status === 200) {
|
||||
store.dispatch(loginSuccess(user.data))
|
||||
navigate('/chatflows')
|
||||
navigate('/')
|
||||
} else {
|
||||
navigate('/login')
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ const OrganizationSetupPage = () => {
|
||||
setLoading(false)
|
||||
store.dispatch(loginSuccess(loginApi.data))
|
||||
localStorage.setItem('username', loginApi.data.name)
|
||||
navigate(location.state?.path || '/chatflows')
|
||||
navigate(location.state?.path || '/')
|
||||
//navigate(0)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user