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:
Henry Heng
2025-10-29 11:33:27 +00:00
committed by GitHub
parent e925801b63
commit 5df09a15b8
61 changed files with 905 additions and 322 deletions
+100
View File
@@ -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 />
}
+2 -5
View File
@@ -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',
+33 -21
View File
@@ -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 = {
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -19,7 +19,7 @@ const SSOSuccess = () => {
if (user) {
if (user.status === 200) {
store.dispatch(loginSuccess(user.data))
navigate('/chatflows')
navigate('/')
} else {
navigate('/login')
}
+1 -1
View File
@@ -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)
}